Bug 2568072 (CVE-2026-123710)

Summary: CVE-2026-123710 quay: Red Hat Quay: unauthenticated registry signing-key takeover via service key rotation
Product: [Other] Security Response Reporter: OSIDB Bzimport <bzimport>
Component: vulnerabilityAssignee: Product Security <prodsec-ir-bot>
Status: NEW --- QA Contact:
Severity: urgent Docs Contact:
Priority: urgent    
Version: unspecifiedCC: doconnor, security-response-team, zmiele
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: ---
Doc Text:
A flaw was found in Red Hat Quay. An unauthenticated client can obtain an anonymous registry token with an audience it controls, present that token to the service key rotation endpoint, and install its own approved signing key with no expiry. Registry tokens signed with that key are accepted for any repository, allowing read and write access to private images and replacement of repository tags.
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description OSIDB Bzimport 2026-11-06 10:22:33 UTC
An unauthenticated client can install an attacker-controlled, never-expiring service signing key in Red Hat Quay and then forge registry tokens that grant read and write access to every repository.

The service key server authorises key rotation by checking only that the supplied bearer JWT is RS256-signed by an already-approved key for that service and carries the expected audience and issuer. It does not check what the token was issued for. /v2/auth mints exactly such a token for anonymous callers when FEATURE_ANONYMOUS_ACCESS is enabled, and the audience is taken from the client-supplied service parameter with no pinning. The key server ignores the token's grants.

The attacker PUTs their own JWK to /keys/services/quay/keys/<kid>. replace_service_key copies the signer's approval onto the new key and stores it with no expiration when the expiration parameter is omitted. The new key is trusted immediately, and registry JWTs signed with it are honoured on /v2 for any repository, including push and overwrite. Rotation also deletes the legitimate instance key as a side effect, and the attacker key can delete replacement instance keys.

Root cause references:
- endpoints/v2/v2auth.py: audience_param = request.args.get("service")
- util/security/registry_jwt.py: tokens include access and context claims
- endpoints/keyserver/__init__.py: _validate_jwt checks signature, aud and iss only, then discards the payload
- data/model/service_keys.py: replace_service_key copies approval, accepts a null expiration, and deletes the signer
- auth/registry_jwt_auth.py: access claims map directly to pull, push and * grants

Confirmed against master 9226d3a5 and the 3.19-unstable image. The logic is present from the first public commit.