This is a fun little project I made as a part of a homework assignment for the class CYSE-570 at George Mason University. It illustrates how you can inject a reverse shell into any docker container to trojanize it. For me the most interesting part is how the reverse shell operates.
Modify the Dockerfile so the reverse shell will try to connect to your host on the docker network.
Run demo.sh
on a linux system with the following dependencies:
- docker
- make
- gcc (but if you modify the makefile you can use a different CC)
- tmux (for the GUI)
It abuses dynamic linking. No, really. ProfessionallyEvil explains it better than I can. The TLDR is, when the linker goes to execute your program, it will bind the symbols present in the library denoted by the LD_PRELOAD environment variable first. If the library has any internal setup it needs to do, that work is performed in the "constructor". By annotating our function as the constructor, we get code execution before the legitimate process is loaded.
This can have legitmate uses Stack Overflow peeps claim you can inject a custom malloc this way. But in our case we are using it to inject a reverse shell into a docker container. Since a docker container is usually one program, this shell gets inserted right before that program runs. It is pretty sneaky.
The way this attack is supposed to work is you, as an attacker, upload a poisoned container image to your company's repository. Then, any developer building on that image (ubuntu:latest in our case) builds against a container that has our neat little reverse shell in it. In an ideal world, this container makes it to production. Then we can be nefarious with it.
This is not very sophisticated, so here are some easy mitigations:
- Use network ACLs to prevent these sorts of connections to the internet
- Audit container layers to make sure its what you expect
- Use access control to prevent users from poisoning your image cache
- Verify the sha of all container images pulled
- Sign all container images and verify signatures
- Make sure the Docker container is trying to connect to the correct ip address and port
- Make sure the Docker container host-container network is up
- Check your firewall
For the original assignment we were supposed to use dockerscan to trojanize a docker image with the command dockerscan image modify trojanize
. Unfortunately, it is written targeting python 3.5 and the python packaging ecosystem has changed enough it doesn't work out of the box. I tried to get it running in a python 3.5 container but still ran into issues with handling docker layers. Dockerscan is a more advanced tool, but it just injeccts a shared object held in git. I wanted to know more about how injecting that file was causing the reverse shell to run.
Yes! I actually developed it with podman. Right now the demo script uses docker because for some reason my default podman network has disappeared and I don't want to reboot yet.
Thanks to ProfessionallyEvil, izenynn, cr0hn, and most importantly Dr. Mohamed Gebril for making this little project possible.
Dockerscan would keep the sha of the final layer the same after performing the trojanize. Reimplementing that would be pretty neat.