다음과 같이 어떤 은행에 1000원을 이체하는 GET 요청이 있다고 해보자.
GET http://netbank.com/transfer.do?acct=PersonB&amount=1000 HTTP/1.1
한 해커는 위와 같은 요청을 수정하여 다음과 같이 자신의 계좌로 이체하도록 만들 수 있다.
GET http://netbank.com/transfer.do?acct=AttackerA&amount=1000 HTTP/1.1
그리고 이렇게 수정한 링크를 하이퍼링크로 삽입하여 여러 사용자들에게 메일을 보낼 수 있게된다.
<a href="http://netbank.com/transfer.do?acct=AttackerA&amount=1000">Read more!</a>
이런 메일을 받은 어떤 사람이 은행 계정에 로그인된 상태로 클릭하게되면 해커의 계좌로 이체하게 되는 것이다.
POST 요청으로 이체를 수행하는 은행은 위와같이 <a>
태그를 사용할 수 없다. 하지만 embedded javascript를 사용하여 <form>
를 전송하도록 실행하게 만들 수 있다.
<body onload="document.forms[0].submit()">
<form action="http://netbank.com/transfer.do" method="POST">
<input type="hidden" name="acct" value="AttackerA"/>
<input type="hidden" name="amount" value="$100"/>
<input type="submit" value="View my pictures!"/>
</form>
</body>
이러한 CSRF Attack을 방어하기위해 악의적인 요청인지 아닌지를 판단할 수 있는 방법이 CSRF token이다. 예를 들어 POST 요청을 하는 <form>
은 다음과 같이 _csrf
라는 값이 랜덤으로 생성된다.
<form method="post"
action="/transfer">
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="hidden"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
따라서 정상적인 루트로 요청한 것들만 정상적으로 처리되게 한다. 요청을 할 때마다 CSRF token이 필요한데 사용자만 알 수 있는 CSRF token을 해커가 알 수 없으니 앞서 살펴본 방법으로 공격할 수가 없는 것이다.
[reference]
https://docs.spring.io/spring-security/reference/features/exploits/csrf.html
https://www.imperva.com/learn/application-security/csrf-cross-site-request-forgery/