Exploiting Security Checks on Bind Mount

In this post, you'll learn how to acquire privileged access on the host computer by abusing the bind mount security limitation in the Docker API firewall.

Exploiting Security Checks on Bind Mount
Photo by Zoltan Tasi / Unsplash

Hello World! Once you get a foothold on the machine that has access to docker via CLI, Unix socket, or TCP connection, it is damn easy to get access to the host machine by mounting its file system in the container and running that container. Keeping that in the mind, security teams often implement the API firewall that sits in between the docker engine and the API server. This is responsible to filter the user inputs and pass on only the allowed ones.

In this lab exercise, we can start a container and are only allowed to bind the mount /tmp directory of the host file system. How do you think someone can escape from the low-privileged user's shell to the high-privileged root user's shell? Let's dive in and explore...

The current user can access docker-engine via Unix socket and can successfully list the images or the containers state.

Check the docker CLI connection

Well, it's not that easy to create a privileged container via docker CLI as you can see the request is already dropped by the currently deployed firewall. With all the capabilities, it would be easy by exploiting CAP_DAC_* or CAP_SYS_MODULE capabilities. For more information, read Containers Breakout - Part 2.

Privileged options are blocked while creating the container

But wait! We can mount the /tmp directory. Right? How can we use this to get access to the host machine? OK, I am thinking it out loud. If I am wrong, don't laugh: What if we can somehow mount /etc/shadow and then edit it to overwrite the root user password?

Problem with this approach:

  • It is not allowed to mount any files on the system, only /tmp directory is allowed.
  • You cannot copy the file here in the directory, because the copy operation requires: Open file in reading mode \( \to \) Read the contents, and save in the new file. In this case, because of insufficient DAC access, it will fail at opening the file.

It is allowed to have the Symbolic Link (symlink) of any file. Basically, any operation on the symlink will be passed on to the source file. Since we are not the root user here but will be in the running container, maybe the shadow file can be readable there. Let's try this out by creating the shadow file symbolic link and mount /tmp of the host to /host directory in the container and try to read the file.

/etc/shadow file symlink does not work

Oops! That was a terrible failure. But no worries, this means, there is a different approach to it. Any changes made to the file in the bind mount in the container get reflected back to the file on the host machine. Ownership details such as username and group or the permissions like RWX or S[U/G]ID bit information are also stored with the file in its inode structure.

So what if we have the bash shell and set the SUID bit via chmod with root user via chown. We can get the root user bash shell by calling it with -p flag (privileged mode in the bash). If this all sounds new to you, I would recommend you read Demystifying SUID and SGID bits.

In the following screenshot, I have copied the bash shell binary from the /bin/bash to the /tmp directory, changed the ownership of the bash shell to root:root, which will set both user and group to root. At last, I have added the SUID bit to only the user set in the DAC permissions.

You can use the chmod +s /host/bash to set the SUID and SGID bits. In this case anyone would work, because both group and user is set to the root.
Assign SUID bit to the bash and change ownership to root user

Now exit from the container and look at the binary in the /tmp directory and you will experience a "Wow!" moment. The DAC permissions of the bash binary indeed changed and it spawned the privileged shell. Use this shell instance to find the flag file and submit it.

Spawn a privileged bash shell