HTB Business CTF 2025 - Dashboarded writeup
Dashboarded was a challenge at the HTB Business CTF 2025 (cough Global Cyber Skills Benchmark CTF 2025 cough) from the ‘Cloud’ category.
For this challenge we get a single public IP address: 3.15.107.79.
In this challenge, I got the chance to use some cloud-specific SSRF attacks and I learned about some AWS tools.
On that IP address, we find a web app displaying some data.
The page has a single button labeled ‘Check Status’. When the button is clicked, a POST request gets sent to the backend.
It looks like the body of the request contains an URL that gets called and its response gets sent back to the user.
This looks like a good opportunity to attempt SSRF.
The easiest way to test this is to replace the URL with a public URL like google.com or ipinfo.io.
I like using the later because when it detects a CLI user agent (like curl) it returns a JSON response. That can be useful sometimes.
It looks like the this application is indeed vulnerable to SSRF, the app received the response from ipinfo.io and parsed it.
We can see the server’s public IP address and some basic information about it (nothing surprising, I already suspected it’s being hosted in AWS).
However, SSRF is a way bigger deal when it is on a cloud instance (like AWS EC2). Those instances expose some internal endpoints that expose metadata about the instance.
You can read more about this kind of SSRF here or here.
First, I tested if I can use this kind of attack here by trying to read “http://169.254.169.254/latest/meta-data/”.
Success! We get a list of different things that we can access.
Of course, I went straight for the credentials at “http://169.254.169.254/latest/meta-data/iam/security-credentials/APICallerRole”.
I got the credentials for the APICallerRole in AWS.
An important detail here is that the access key begins with ‘ASIA’. This is a short-term access key. This means that they should expire in a relative short amount of time (around an hour in this case) and when authenticating we need to include the security token (the long one).
You can read more about types of credentials here.
I also tried to retrieve another set of credentials from “http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance”
Those credentials belong to the EC2 instance, not to a role like the previous ones.
I tried using AWS STS and tools like pacu to find out what those credentials are authorized to do.
However, I couldn’t find any permissions assigned to these credentials.
After many attempts at elevating my privileges in AWS, I happened to take a step back and I looked at the API that was originally called by this endpoint.
If I remove the ‘/status’ endpoint from the URL (https://inyunqef0e.execute-api.us-east-2.amazonaws.com/api/), I found that there are 2 endpoints: /status and /private.
Trying to call /private results in an authentication error.
This might be what we need to do to solve this challenge.
However, the EC2 instance might have some kind of credentials or some kind of bypass for this. So I tried using the SSRF again to call the /private endpoint.
I couldn’t read any results and I am not sure if this was because the answer couldn’t be parsed correctly or because the answer wasn’t a valid one.
I would’ve expected the vulnerable app to render at least part of the “Missing token” message that I could see in my browser.
So I think the API call works, we get a different response but for some reason I cannot see it.
The API that is being called here is built with AWS API Gateway. This can be identified by the ‘execute-api’ part in the URL (or at least that’s how I found out about this).
After reading a bit more about AWS API Gateway I found awscurl, a tool for sending requests to aws services.
Note: You need to have your AWS credentials set using aws-cli (or just written in ~/.aws/credentials)
After installing awscurl and setting up the APICaller credentials as the default credentials, I ran this command
awscurl --service execute-api --region us-east-2 \
-X GET "https://inyunqef0e.execute-api.us-east-2.amazonaws.com/api/private"
Success! The API returned a response that also contained the flag for this challenge: HTB{d4sh1nG_tHr0ugH_DaSHbO4rDs}