SSRF Exploitation
SSRF exploitation uses a confirmed server-side request to reach internal services, cloud metadata endpoints, and internal APIs not accessible externally. Confirm SSRF with an out-of-band callback first through SSRF detection before probing internal infrastructure. Prioritize cloud metadata over internal port scanning. A single SSRF hit on AWS IMDS can yield credentials that open the entire cloud account. If the application response body reflects content from the fetched URL, exploitation is straightforward. Blind SSRF limits you to inferring results from response timing and HTTP status codes.
- AWS IMDS credential theft retrieves IAM role credentials from 169.254.169.254 for cloud account access.
- Internal service access reaches admin panels, Redis, databases, and APIs not exposed externally.
- Internal network scanning maps live hosts and open ports across private IP ranges via response timing.
- Filter bypasses use hex, integer, and short-form IP encoding plus redirect chains and DNS rebinding.
AWS IMDS Credential Theft
The AWS Instance Metadata Service exposes IAM role credentials at 169.254.169.254. This is the highest-impact SSRF target in cloud environments. The first request returns the name of the IAM role attached to the instance. An empty response means no role is attached, or IMDSv2 token enforcement is active. IMDSv2 requires a PUT request to obtain a session token first, which most SSRF vulnerabilities cannot issue. If you get a role name back, immediately request its credentials using that name in the second URL.
ec2-role-name
{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "abc123...",
"Token": "AQoD...",
"Expiration": "2026-03-28T18:00:00Z"
}
Use the returned credentials directly from your Kali machine to enumerate AWS resources. Set all three environment variables before running any AWS CLI command. Run sts get-caller-identity first to confirm the role name and account ID. The Expiration field shows when the token rotates, so act before it expires. From there, enumerate S3 buckets with aws s3 ls, list EC2 instances, and check IAM policies to understand the role's permissions. Roles attached to app servers often have broad S3 or SSM access.
GCP Metadata Endpoint
Google Cloud instances expose metadata at metadata.google.internal. Unlike AWS, GCP requires a Metadata-Flavor: Google header on all requests. Most SSRF vulnerabilities let you control the URL but not request headers. If the vulnerable application forwards custom headers or the SSRF function accepts header parameters, include the required header. Without it, the metadata service returns a 403. The service account token endpoint returns an OAuth access token usable with gcloud and the GCP REST API.
{"access_token":"ya29.c...","expires_in":3600,"token_type":"Bearer"}
my-gcp-project-123
If the application cannot pass the Metadata-Flavor header, try the legacy endpoint at http://metadata.google.internal/computeMetadata/v1beta1/, which does not require the header. Google deprecated it but some instances still serve it.
Azure IMDS
Azure VMs expose the Instance Metadata Service at 169.254.169.254. Like GCP, it requires a custom header: Metadata: true. Without the header, the service returns 400 Bad Request. The identity endpoint returns an OAuth access token for the managed identity attached to the VM. Use this token with the Azure REST API or az CLI to enumerate resources, storage accounts, and Key Vault secrets.
{"compute":{"name":"web-vm","resourceGroupName":"prod-rg","subscriptionId":"abc-123..."}}
{"access_token":"eyJ0eXAi...","expires_in":"86400","resource":"https://management.azure.com/"}
Internal Service Access
Use SSRF to reach internal services not exposed externally. Target common internal ports: 8080 and 8443 for internal web applications, 6379 for Redis, 5432 for PostgreSQL, 3306 for MySQL, 27017 for MongoDB, and 9200 for Elasticsearch. Response body content identifies what is running. An HTML page means an internal admin panel is accessible. A JSON response usually indicates an internal API. A 401 or 403 means the service exists but requires authentication. A connection timeout means the port is closed or filtered on that host.
<html><title>Admin Panel</title>
-ERR wrong number of arguments for 'get' command
A Redis error response means the SSRF reached an unauthenticated Redis instance. Send Redis commands through the SSRF to write SSH keys or a web shell to disk. Redis accepts plain text commands on port 6379 by default. Use the gopher:// protocol in your SSRF payload to send raw multi-line commands if the application allows non-HTTP schemes. The gopher protocol lets you craft arbitrary TCP payloads, making it applicable to any text-based service including Memcached and SMTP. If gopher is blocked, look for the internal admin panel to provide a higher-privilege entry point instead.
Local File Read via file://
When the SSRF handler supports the file:// URI scheme, read local files directly from the server filesystem. This is equivalent to XXE file read but through the SSRF vector. Libraries like Python urllib and PHP file_get_contents handle file:// natively. Java HttpURLConnection does not support file:// by default, but URL.openStream() does. Target the same high-value files as XXE: /etc/passwd, .env, SSH keys, and application config files.
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
DB_HOST=10.0.0.5 DB_USER=app DB_PASS=s3cretP@ss
Gopher Protocol for Raw TCP
The gopher:// scheme sends arbitrary TCP data to a target host and port. This makes it possible to interact with text-based services like Redis, Memcached, SMTP, and even craft raw HTTP requests. The format is gopher://host:port/_payload where the payload is URL-encoded raw TCP data. The leading underscore after the slash is consumed by the gopher handler and not sent. This example writes a PHP web shell to disk through an unauthenticated Redis instance reachable via SSRF.
The URL-encoded payload sends Redis RESP commands: FLUSHALL, SET a key containing a PHP shell, CONFIG SET the dump directory to the webroot, CONFIG SET the filename to shell.php, and SAVE to write the database to disk. After the save completes, access the shell at http://$TARGET_IP/shell.php?cmd=id. Not all SSRF libraries support gopher. Python urllib removed gopher support in Python 3. PHP curl supports it if not disabled in php.ini. Java URL class does not support gopher. If gopher is blocked, the dict:// scheme is a limited alternative that sends a single command: dict://127.0.0.1:6379/INFO.
Internal Network Scanning
Use SSRF to map the internal network by probing private IP ranges. The scan loop treats exit code 000 as a timeout and prints any other code as a live response. A 200 means a web service is running on that host. A 401 or 403 means something exists but requires authentication. Any non-timeout response confirms a live host. Target 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 ranges. After finding live hosts, probe each on common service ports to identify what is running. Slow response times relative to the timeout value can also indicate filtered ports worth revisiting.
200 10.0.0.5 200 10.0.0.12 401 10.0.0.20
Blind SSRF Techniques
When the response body does not reflect content from the fetched URL, use indirect signals to extract information. Timing differences reveal whether a port is open or closed: an open port returns quickly, a closed port resets immediately, and a filtered port times out. Response size differences can also leak data when the application returns different error pages for different backend responses. DNS-based confirmation works when all other channels are blocked: point the SSRF at a URL containing a unique subdomain on your DNS server and watch for the resolution.
Port 22: 45ms Port 80: 52ms Port 443: 3001ms Port 3306: 41ms Port 6379: 48ms Port 8080: 3002ms
Ports 22, 80, 3306, and 6379 responded quickly, indicating open ports. Ports 443 and 8080 timed out at 3 seconds, indicating filtered or closed ports. For DNS-based confirmation, use a unique identifier per probe so you can correlate which request triggered the resolution. Collaborator-style tools (Burp Collaborator, interactsh) automate this. When the application returns different HTTP status codes for open versus closed backend ports, use the status code as the signal instead of timing.
SSRF Filter Bypasses
When the application blocks obvious internal addresses, try alternative representations. 0x7f000001 is 127.0.0.1 in hex notation. 2130706433 is the same address as a 32-bit unsigned integer. 127.1 resolves to 127.0.0.1 on most systems. Also try alternative URL schemes: dict://, gopher://, and file:// sometimes bypass filters that only block http://. If the filter uses a domain allowlist, register a domain that resolves to 127.0.0.1 and use it as the SSRF target. Some applications follow redirects from trusted domains, which you can chain to reach an internal address.
The same integer, hex, and short-form encodings apply to any blocklisted destination, including the cloud metadata address shown above. Encode it the same way to slip past a filter that only matches the literal string. If the application resolves the URL at validation time and makes the request later, DNS rebinding can bypass IP-based filters by switching the resolved address between the two operations. This requires controlling a DNS record with a very short TTL. When all bypass attempts fail, check whether the application follows HTTP redirects. Host a redirect on an external server pointing to your internal target, then use your server's URL as the SSRF payload.
References
-
Internal service targeting and bypass techniques
-
AWS: IMDS EndpointsDOCS: AWS: IMDS Endpoints, docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html (opens in new tab)
Metadata service paths for IAM credential retrieval