User
Stacked is an insane reated machine on HackTheBox created by TheCyberGeek. For the user part we will abuse an XSS in a contact form referer header after fuzzing a vhost. Through this we discover an email on an internal page which reveals another vhost that is a localstack endpoint. The endpoint is vulnerable to command injection with user interaction in the lambda service. Combining this with the XSS we get a foothold in a docker container. Monitoring the creation of a valid lamdba function we will discover another command injection which gets us root on the docker this time. We then will use the certificates in the root folder to interact with the open docker port, mount the host filesystem in a container, write an ssh key to root’s authorized keys and grab the flag after logging in.
Nmap
As usual we start our enumeration off with a nmap scan against all ports followed by a script and version scan against the open ones to get an inital overview of the attack surface.
All ports
1 |
|
Script and Version
1 |
|
Internal mail
The nmap scan reveals a hostname which we add to our /etc/hosts
and visit the page. It displays a very static looking page without much functionality.
Since there seem to be hostnames at play we next try to fuzz another vhost and find portfolio.stacked.htb
.
1 |
|
Visiting this page it lookes like a localstack development platform.
Scrolling down we can download a docker-compose.yml
which reveals some potential internal ports listening on the machine aswell as some services.
docker-compose.yml
1 |
|
Further down is also a contact form and it was mentioned above that any messages will be checked around the clock. This seems like a good point to check for XSS.
We fill out the form, intercept the request with burp, send it to repeater and stand up a ncat listener to capture the user request if we are successfull.
1 |
|
Trying for XSS in the the subject first, there seems to be a blacklist being implemented.
The Referer
header doesn’t seem to be checked though and sending a simple payload we get a hit about one minute later on our listener.
The request reveals an internal webpage in its own referer header which we can try to exfiltrate in the next step. Note here that one of the parameters seems like it needs to be unique for every request so we will just add a letter to the parameters with each request.
1 |
|
We want to load a script in the users browser which sends a request to http://mail.stacked.htb/
and sends the base64 encoded response back to us. This can be achived with two XML HTTP Requests’s, one getting the page, the other sending the data back to us.
a.js
1 |
|
After standing up a python webserver to serve our js payload we can now send the request in repeater.
1 |
|
About a minute later we get a hit for our a.js
and a few seconds later it returns the base64 encoded source code of the internal website.
1 |
|
Looking at it there is another email from Jeremy which looks interesting.
1 |
|
We adjust our a.js
to now fetch http://mail.stacked.htb/read-mail.php?id=1
and repeat the steps from before.
a.js
1 |
|
We get a hit about one minuter later again and the source follows a few seconds later aswell.
1 |
|
The email states that there is an S3 instance set up at s3-testing.stacked.htb
and we can use a serverless instance using node to work from.
1 |
|
Localstack command injection
Adding the vhost to our /etc/hosts
we can enumerate running services on the endpoint with /health
.
1 |
|
Checking for possible vulnerabilities in localstack lambda seems to be quite interesting. Following this article it seems like we can inject commands into a lambda function name. The only condition for this to work is that a user refreshes the dashboard which we can control with the found XSS.
Using this snippet from github we can create our own simple function interacting with the endpoint using aws-cli.
First we need to zip up the lambda.js
from the repository, which basically just creates a function app printing Hello World
.
lambda.js
1 |
|
1 |
|
Next we have to create the javascript for the XSS wich redirects the user to the dashboard we’ve seen in the dockerfile.
b.js
1 |
|
Now we can stand up a python webserver for the javascript payload and our reverse shell payload in index.html
. We also listen with ncat to catch our reverse shell.
1 |
|
index.html
1 |
|
1 |
|
Now we can create a function app with the command injection payload in the name and quickly send the request for the XSS in burp.
1 |
|
After about a minute again we first get a hit for the javascript payload and soon afterwards for our reverse shell.
1 |
|
We upgrade the reverse shell using python, fix the terminal size and can now grab the user flag.
1 |
|
Root
Command injection # 2
After looking around in the container there does not seem an obvious way to privesc, so the best shot might be to take a close look at the localstack functionalities. Using the github repository again we create a fully functioning lambda app and monitor on the docker with pspy
for process creation.
1 |
|
We can take the setup.sh
from the repository, remove the fail checks, exchange the awslocal command for aws, add our endpoint to the aws commands and remove the id
in the later path parameters to avoid errors.
setup.sh
1 |
|
1 |
|
Running the script the lambda app gets successfully created and the end of the script verifies it exists.
1 |
|
Checking on pspy now we see what is happening on the backend. A docker get’s created for the app and one command is executed with sh -c
. Inside this command is a parameter which gets the handler
variable passed we specified in setup.sh
. This looks like a perfect opportunity for another command injection.
1 |
|
We adjust the setup.sh
with a new function name to avoid conflict with the earlier created app. We also change the handler
value to our command injection payload. First we break out of the quotes and add a ;
to each end to make sure the payload runs. As actual payload we use the same index.html
as above.
setup.sh
1 |
|
Now all we need to do is stand up our webserver again to serve the payload and a listener to catch the reverse shell.
1 |
|
1 |
|
Running our setup.sh
again it does not fully finish because it is busy executing our reverse shell.
1 |
|
We see a hit on our webserver and get a shell back on our listener, which we upgrade again using python.
1 |
|
The nmap scan in the beginning revealed that the docker port is listening but we didn’t have the necessary certificates to interact with it. Those certificates are in the /root/.docker/
of the docker container.
1 |
|
Mount host
Tranfsering the certificates over to our machine and trying to interact with docker it needs another hostname for which the cert is valid so we add stacked
to our /etc/hosts
.
1 |
|
Now we can list all the contiainers.
1 |
|
The plan now is to create a container with the host file system mounted and sh
set as entrypoint. First we list all availabe images to create our container.
1 |
|
Using the second image we are able to create a container with the root of the host filesystem mounted in it and connect to it.
1 |
|
We could already read the flag now but let’s quickly add our public ssh key to root’s authorized keys.
1 |
|
With this we can now ssh into host machine as the root user and add the flag to our collection.
1 |
|