Lab: SSRF to Lambda Runtime
Objective
Use SSRF to reach internal services inside the Lambda execution environment and leak sensitive data.
Why This Matters
Cloud services run internal APIs that are never exposed to the internet. AWS Lambda has a Runtime API on 169.254.100.1:9001 that manages invocations. If an attacker can make the Lambda fetch an arbitrary URL, they can reach these internal services and extract data that should never leave the function.
Real-world parallel: SSRF to the EC2 metadata service at 169.254.169.254 has been exploited in major breaches including Capital One (2019), where attackers used SSRF to steal IAM credentials and access S3 buckets containing 100+ million customer records.
The Vulnerable Code
The /redirect endpoint fetches whatever URL the caller passes:
@app.route('/redirect')
def web():
site = request.args.get('url')
response = urllib.request.urlopen(site) # no allowlist
output = json.dumps(response.read().decode('utf-8'))
return jsonify({"output": output}), 200
There is no validation on the url parameter. The server will fetch any address — external or internal.
Steps
Before you start: make sure you are in the project directory.
cd /workspaces/very-vulnerable-serverless source ~/.lab-env
Step 1 — Confirm SSRF with an external URL
Fetch a public service to prove the Lambda is making requests on your behalf:
curl "$ENDPOINT/redirect?url=https://httpbin.org/ip"
You will see the Lambda function’s public IP address in the response — not yours. The server made the request, not your browser.
Step 2 — Reach the Lambda Runtime API
The Lambda Runtime API runs on 169.254.100.1:9001 inside every Lambda function. It is meant for the Lambda runtime only. Hit it:
curl "$ENDPOINT/redirect?url=http://169.254.100.1:9001/2018-06-01/runtime/invocation/next"
This returns invocation context that should never leave the function — request IDs, deadlines, and internal routing information.
Step 3 — Understand the impact
From here, an attacker could:
- Read internal configuration and environment variables
- Chain with command injection to steal AWS credentials (we did this in the previous lab)
- Reach other internal services running on localhost or within the VPC
What Happened
The application trusts user input as a URL and fetches it without any validation. There is no allowlist of permitted domains or protocols. The server becomes a proxy that reaches internal services the attacker cannot access directly.
Fix: Validate URLs against an allowlist of permitted domains. Block private IP ranges (127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16). Use a URL parser to check the resolved IP, not just the hostname (to prevent DNS rebinding).
- Next step: Insecure Deserialization