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: | vulnerability | Assignee: | Product Security <prodsec-ir-bot> |
| Status: | NEW --- | QA Contact: | |
| Severity: | urgent | Docs Contact: | |
| Priority: | urgent | ||
| Version: | unspecified | CC: | 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: | |||
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.