Container Host Breakout – Part 2

This is part 2 of container host breakout where you will learn how to interact with low-level APIs and other OCI tools like containerd to escalate to the root user.

Container Host Breakout – Part 2

This part is in continuation to the last post. If you haven't read that, I would recommend you to first read it and then come back here – Attacking Docker Hosts – Part 1. We will now continue exploring more low-level misconfigurations in the docker hosts and how to exploit them to get the root user shell. I will be discussing the following labs from attack defence. If you haven't subscribed to their plan, you must subscribe to practice these labs

The low-level runtime and services that I will be discussing are already discussed and recommended in Understanding Container Architecture. I recommend you to first read this and then come here. I won't be explaining them here.

So let's begin...

LAB: Low-Level Container Runtime

In this lab, you will find that docker is not installed but runc is installed. So basically runc is a container creation and running tool which uses OCI specifications (stored in JSON file). You will find runc installed in the old systems that adopted containerization in the early stages

Runc is installed

So let's create the OCI specification in the current directory and configure mounts to allow read-write permission on the bind mount of the root directory of the host to the root directory of the container.

{
	"destination": "/",
	"source": "/",
	"type": "bind",
	"options": ["rbind", "rw"]
}
Configuration for the bind mount

I have used the rbind option in the config to allow recursive bind all the submounts into the container and the rw option to allow both read and write permission on the system. You need to copy and paste and configure as shown below in the diff.

Once you have configured the bind mount, create the "rootfs" directory in the working directory as this directory will be used by the container.

Create container spec and update the mount config

Now run the container simply by executing runc run <name> and it will spawn you the root user shell. From here you can retrieve the flag file or backdoor the system

Escalate to the root user

LAB: Abusing Group Membership

You know this already, I am just refreshing the knowledge here for both of us. By default, the docker runs its Unix domain socket and with DAC permissions root as owner and docker as its group. This socket file can be found in /run/docker.sock

If you are in the same group (docker) then you can actually talk to docker daemon as the group in the socket file has read-write permission.

The current user is in a group of docker

If you are unsure, use docker images to check whether the current logged in user can talk to the Docker daemon or not. The successful response will at least have the column names and for failure, you will get an error like "permission denied while trying to connect to the Docker daemon socket"

Testing connection

Run a docker container with bind-mount configuration and use modified-ubuntu:latest. You will find out that it is running an SSH server because that is what is configured in the entry point of the container. To override the container entry point of the container, you can do it by passing the --entrypoint /path/to/new/entrypoint argument to containers create or run command

Changing entry point to spawn a shell

In this case, let's not escalate via chroot. You will see sudo is installed. So let's configure sudo for student users to run any command as any user or group without any password. To do that you need to add the following configuration in the file

student ALL=(ALL:ALL) NOPASSWD: ALL

Append the above lines in /etc/sudoers file, the main config file for sudo command and verify it by listing the last 10 lines using the tail command

Add sudo config for the student user

Now close the container and check sudo permissions for the current user using the sudo -l command. You will find out that the student users can now run the commands as the root user. To escalate to the root user, simply execute the switch user command with sudo – sudo su -l root. This will perform root user login and land you into the home directory of the root (/root)

Escalate to the root user

LAB: Leveraging Containerd

In this and the next lab, you will learn about another tool to manage containers and that is containerd and its client ctr. As I told you at the starting of the container architecture series that docker is not only the one that is available in the market, there are other techniques as well. You hear docker more as it is widely accepted and people put a lot of effort into it.

Docker installed but daemon not running

In this lab, the containerd service is running and it has two images in the local repositories. I will be using the first one modified-alpine:latest. You can use anyone you want

Containerd is running and the client is installed

Everything you will see is the same as the docker, but in this, you will have to verbosely define the mount config. The parameters of the mount as described below

  • type – the type of mount you want to perform (here, bind)
  • src – the source of the mount (generally on the host)
  • dst – the destination of the mount (generally on the container)
ctr run --rm \ 
	--mount type=bind,src=/,dst=/,options=rbind:ro \
	registry:5000/modified-alpine:latest \ 
	exploit \ 
	bash -c "whoami;id"

Usually, when a container runs as a root, it only has one group which is the user itself (root). In this case, when you will execute the above command, it will show you the root user with so many groups in it. This means you are already on the host of the root and don't need to chroot. It is because you have mounted the root of the host onto the root of the container

Test the command

Since the sudo is already installed on the host, let's add the student user into a sudoers file so that it could run any command as any user without being prompted for a password. You can do this by executing the following command

ctr run --rm \ 
	--mount type=bind,src=/,dst=/,options=rbind:rw \ 
	registry:5000/modified-alpine:latest \ 
	exploit \ 
	bash -c "echo 'student ALL=NOPASSWD: ALL' | tee -a /etc/sudoers"

You now can switch the user to the root user with sudo. This will directly log in and land you into the home directory of the root user. Congrats, now you own this system

Add sudo config for the student user and escalate

LAB: Leveraging Containerd II

In this lab, we will not focus on actually getting the root user shell because the flag is kept in the memory of the running process and you need to dump it from there itself. To be honest, I am obsessed with process injection and memory dump techniques.

For security reasons, you can only perform a read operation via privileged user or if the current user has CAP_SYS_PTRACE capability. So let's quickly add the sudo configuration for the student user via ctr and allow it to execute any command as any user without entering the password.

Configure student in /etc/sudoers file to run any command as any user

In the description of the lab, it mentioned that "retrieve the flag stored in the memory of the "flag-holder" process running on the host system!". We are already on the host system but was not having root user privileges.

Find out the PID of the running process

With a naive Google search, I found that you can actually use gcore to dump the memory of the running process by mentioning its PID and an output file.  You can call the core file a memory dump of the process. If a program terminates abnormally, a core file is created by the system to store a memory image of the terminated process. A core file produced by gcore is equivalent to one produced by the kernel when the process crashes

Once you have dumped the file, you can use strings and grep to find the flag in the dump (as shown below)

Dump memory of 236 process and grep the flag content