Docker is quite a viral technology. A significant part of the IT world relies on Docker containers as they are easy to use and portable. In this article, we’ll look under the hood of Docker container privileges. We will consider some scenarios of malicious Docker images and how to protect your host machine from that. Moreover, we’ll tackle the uid and gid mechanism in linux kernel.
Docker is sharing a kernel with the host machine
The great part of the Docker is that it is lightweight, but what does it entail? The Docker container does not have its own kernel. It relies on the host kernel, so the user inside of the docker container with uuid=0 is the same user on the host system with uuid=0.
Wait… What? Yes, the user with uuid=0 is a root. So he has full privileges. When we install the docker, we go through docker post-install. There is a little warning there:
The docker group grants privileges equivalent to the root user. For details on how this impacts security in your system, see Docker Daemon Attack Surface.
So when you run a docker process, it gets the privileges of the root. What can possibly go wrong? Well… a lot of things. Bare with me as we go through some of the dark scenarios of malicious Docker images.
We want to use the debian:stretch base image, and then execute the rm -rf /home/notImportantDir command.
We build the image with Dockerfile from the current directory and we specify a friendly name with the -t option.
Now the fun part. Let’s run this very innocent docker image and give the container / directory as volume. The -v flag gives us the ability to mount a volume, so we mount / volume from the host and we specify that it will be available at /home/notImportantDir/.
Oops, something went wrong? Yep, long story short, you got no system.
What happened there?
The / from the host was mounted in the Docker container as /home/notImportantDir/ directory. The Docker container executes the rm -rf /home/notImportantDir command inside of the container. Basically, the command removes everything from the / directory on host system. A basic user can execute this command, but it will not harm the system, as he has no rights to delete core files of the system. When root executes this command, that is the point when things can get really bad.
Stealing some private data owned by root
As we have root access we can think of stealing some confidential data. For example, we can steal some environment variables (env) of processes. It is a standard approach to keep confidential data like API keys, app settings or secret passphrases in env, so it can be a precious piece of data for the attacker. Let’s prepare some docker image for this scenario.
Here is the Dockerfile with environment for node app and with some bash commands to execute.
This is the file that is the most important. This script iterates through all the processes and it prints all the environment variables. The output of this file is then stored in the stolenEnv.txt file by the cmd.sh script.
This file executes the readEnv.sh script and puts the output in stolenEnv.txt, then executes the node app.
This node app reads the stolenEnv.txt file and makes a post request with the environment variables.
Let’s build it.
Now the run command
What will happen now is:
- The image will run and it will prepare environment for the node app.
- It will execute cmd.sh which will steal env of all the running processes on host system and put it in stolenEnv.txt and then it will run the npm start command which will run the node app.
- The node app will read the stolenEnv.txt file and will post the data to the server under http://theaftDomain.com/stolen-env domain.
- The server gets the data and attacker can use this data for his evil purposes.
How do uids map between Docker container and the host?
When a user wants to perform some action on a file, the kernel checks if the uid has enough privileges to proceed. It’s crucial to understand that kernel does not recognize usernames. The username is assigned to a specific uid thanks to external tools. On the kernel level, only the uid/gid matter. Going forward, when we run docker, it requires root privileges. The docker process runs the docker container process. That process inherits the privileges form the parent process. In this case, the docker process that runs as root. As a result, the docker container process grants root privileges.
How to prevent running Docker containers as root?
We can think of two cases. The first one is when we run a docker image from an untrusted source. The second case is when we want to build an image and want the container to run with ordinary user’s permissions.
Running untrusted images
The best solution is to use the –user option. It gives the ability to specify a uid that is the owner of a Docker container process. Using the –user option, you have to remember that it will override the user specified in Dockerfile.
This is the same command as the one that we used when we ran the container in the first example. But the result will differ now – it will not erase all system files as the container does not have privileges of root.
Building an image that will run as basic user
The Docker container with every run creates a new group with gid=1000 and adds the user with uid=1000 to this group. Such Dockerfile creates an image that will be run as a basic user. It means that the container will not have root privileges and won’t be able to do any harm to the host system.
Docker containers should not run as root
In this article, we walked through some of the malicious Docker images examples. We went through kernel guid and uid mechanisms and how it maps between Docker container and kernel. There was a little bit about user privileges and how to force the container to run as a specific user. Keep the above examples in the back of your head when you run any Docker image.
P.S. during the writing of this article, twelve ubuntu VMs were harmed.