Researchers from Paloalto Networks’ Unit42 discovered an issue in the implementation of the Docker cp command that can lead to full container escape if exploited by an attacker.
This would allow an attacker full root control of the host and all other containers in it.
The vulnerability can be exploited, provided that a container has been compromised by a previous attack (e.g. through any other vulnerability), or when a user runs a malicious container image from an untrusted source (registry or other).
If the user then executes the vulnerable cp command to copy files out of the compromised container, the attacker can escape and run commands on host system: it is the first complete container breakout since the severe runC vulnerability discovered back in February .
A PoC is available
According with Unit42 analysis , the possible attack scenario is a Docker user that copies some files from either:
- A container running a malicious image with bad libnss_*.so libraries.
- A compromised container where an attacker replaced the libnss_*.so libraries.
So, in order to exploit this vulnerability, we need to build a malicious libnss library:
I arbitrarily chose libnss_files.so. I downloaded the library’s source and added one function, run_at_link(), to one of the source files. I also defined the function with the constructor attribute. The constructor attribute (a GCC-specific syntax) indicates that the run_at_link function is to be executed as an initialization function for our library when it is loaded by a process. This means that when the docker-tar process will dynamically load our malicious library, run_at_link will be executed. Below is the run_at_link code, shortened for brevity.
run_at_link first verifies it runs in the context of docker-tar, since other, normal container processes might also load it. This is done by checking the /proc directory. If run_at_link runs in the context of docker-tar, this directory will be empty, since the procfs mount on /proc only exists in the container mount namespace.
Next, run_at_link replaces the evil libnss library with the original one. This ensures that any subsequent processes run by the exploit won’t accidentally load the malicious version and retrigger the execution of run_at_link.
Then, to simplify the exploit, run_at_link attempts to run an executable file at path /breakout in the container. This allows the rest of the exploit to be written in bash for example, instead of C. Leaving the rest of the logic out of run_at_link also means we don’t have to recompile the evil library for every change in the exploit, but rather just change the breakout binary.
Researchers also published a demo video, with a docker user that runs a malicious image that contains our evil libnss_files.so library and then tries to copy some logs from the container.
The /breakout binary in the image is a simple bash script that mounts the host filesystem to the container at /host_fs and also writes a message to /evil on the host: