Hello World! Once you get a foothold on the machine that has access to docker via 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 post, I will be discussing the following labs from the AttackDefense platform.
If you want to practice the labs, I have another two of them for you. To solve these labs, you can take references from Container Breakout (Part 1 and Part 2).
In this lab, you are provided with the docker CLI and a low privileged shell. To prevent direct escalation to the root user, bind mounts are disabled for the low privileged user, as you can see in the following screenshot.
Not only the bind mount but also spawning privileged containers are disabled. This is because, in the case of privileged containers, the filesystems of the host will be references in the /dev
and can be mounted.
The docker CLI depends on the UNIX socket by default but sends the default JSON schema which is then audited by the API. Luckily after searching, I found that it is possible to execute curl requests with a Unix socket. Read more.
Sometime http:///request/path
doesn't work with curl. In that case, you can use any hostname like http://local/request/path
. This will be only used to successfully parse the URI string and curl will use the host and port details from the Unix socket instead.
The docker engine accepts the requests in the form of the JSON object which is then parsed and validated by the API. So basically it will reject all the privileged properties in the HostConfig
field like Binds
, Privileged
and CapAdd
and Capabilities
.
In the lab description, it says that a firewall can be bypassed by sending different JSON structures. This means there has to be some different JSON payload supported by the engine but is ignored (not checked) by the firewall.
After trying certain naive JSON payloads in the HostConfig, I got no positive response from the endpoint. So I looked at the lab handbook to see what the actual payload is.
In this, they are using Binds
outside the HostConfig
attribute and in the manual, it is working. So the following is the curl request that does the same. In this payload Image is used to tell the engine which base image should be used to create a container, Entrypoint is used to override the default value from the ENTRYPOINT
in the image and you already know what Binds will do.
This time executing the curl command will succeed resulting in content creation. Recall that docker run
is the combination of two docker commands docker container create
and then docker container start
.
You can confirm this by executing docker ps
with -a
flag to all the containers, whether they are created, running or stopped.
Additionally to verify theBinds
configuration you can usedocker inspect <container>
command. This will give you all the details of the container and you will also find the bind mounts information in it.
Now start the container and immediately get the /bin/sh
shell using docker exec
command on the same container. You will see the /host
directory
When I asked the AttackDefense about why Binds
field is supported outside HostConfig object, I receive the following response, which is hard for me to believe.
I tried searching google, watched a few videos from the docker conferences and also searched this in the reference of the docker API (in all the versions available). If you find any concrete reasoning for this, please do let me know.
In this lab, you are provided with a target server running a vulnerable web application on a 10000 port and the docker TCP service on the default port. Did you notice the question mark sign ?
besides the docker service? What is that? The STATE is open which means that nmap has recognized the service from the nmap-services list, but could not confirm it [read more].
This will make sense after seeing the curl request below. This is blocked by the firewall which is looking for Bypass-Token
in the header or in the environment variables.
The nmap service detector function was unable to confirm the docker service because of this unsuccessful response. It returned the service from the heuristics with the assumption that the default service would be running on port 2375.
It is dead-end for now. But there is another service running Wolf CMS and you are provided with the login credentials in the lab description. Enter the credentials on the form below.
The admin login page in Wolf CMS can be found at /?/admin/login
request path
There is a vulnerability in the current version which can allow you to upload any file and which can be then executed/viewed from /public/<filename>
request path.
Create a simple php script to dump all the environments using printenv
shell command. To execute the system command, you can use system()
function in the php.
<?php system("printenv"); ?>
Once the file is uploaded, execute it to retrieve the environment variables. You will find the Bypass-Token environment value which is now required to authenticate the curl requests.
This was the first guess that came out of intuition after reading a message from the error in the above curl request. While spawning the docker container, -e
flag is used to set the environment variable for that container which is then inherited by all the processes.
Now if you would try the same curl request endpoint (/version), it will work and return version information as you do docker --version
.
💡
Alternatively, you can also use docker cli by setting custom HttpHeaders
in the docker config file. The default storage of this file is in $HOME/.docker
or you can use --config
option in the docker CLI.
In this post, I will stick to curl, because it is cool and to learn docker-engine interaction with it.
Now is the time to list the images stored on the host system which will be required to start the container. This can be done by calling /images/json endpoint.
Create a container with Binds
information to mount the host filesystem at /host and Detach
set it to true using the wolfcms:latest
image. It will start in the background and run the container. This would be the equivalent command: docker container create -v /:/host -d.
To start the container, execute the POST request at /containers/<ID>/start endpoint. I trust the docker api, but if you don't, you can try checking the status of the containers using /containers/json endpoint.
When you run the docker exec command on the terminal, under the hood it creates and then starts the exec session and attaches it to your console. This is how you will now get the shell access from the remote container.
By sending a POST request to the /containers/<ID>/exec endpoint, you can create an exec session. Standard i/o are only required for the interactive sessions, not to obtain a reverse shell. Provide the reverse shell command in the Cmd field.
I have copied this from the pentestermonkey reverse shell cheatsheet.
Once the exec session is created, it is not started automatically. You must execute the POST request on the /exec/<EXEC ID>/start endpoint.
Note: Before starting the exec session, remember to start the netcat listener. I made that mistake the first time, so I'll have to redo the exec.
As soon as you will get the reverse connection, chroot in the /host
directory and retrieve the flag file.