CSRF(Cross Site Request Forgery), 한국어로 직역하면 교차 사이트 요청 위조라 할 수 있다.<form method="post" action="/transfer">
<input type="text" name="amount"/>
<input type="text" name="routingNumber"/>
<input type="text" name="account"/>
<input type="submit" value="Transfer"/>
</form>
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
<form method="post" action="https://bank.example.com/transfer">
<input type="hidden" name="amount" value="100.00"/>
<input type="hidden" name="routingNumber" value="evilsRoutingNumber"/>
<input type="hidden" name="account" value="evilsAccountNumber"/>
<input type="submit" value="Win Money!"/>
</form>
이러한 상황 속에서 사용자가 “Win Money!” 버튼을 클릭하는 순간
사용자의 은행 계좌에서 100달러가 악성 사용자의 계좌로 이체되어버림.
더 나쁜 점은 이 과정이 자바스크립트를 통해 자동화될 수도 있다는 것.
그렇다면 이러한 공격으로부터 사용자를 어떻게 보호할 수 있을까?
CSRF 공격으로부터 보호하는 가장 보편적이고 포괄적인 방법은 동기화 토큰 패턴(Synchronizer Token Pattern)을 사용하는 것.
요청이 서버로 전송되면 서버는 요구하는 CSRF 토큰(expected token)과 요청에 포함된 실제 CSRF 토큰(actual token)을 비교하고 두 개가 일치하지 않으면 요청을 거부함.
CSRF 토큰이 포함된 송금 요청 폼 예시.
CSRF 토큰을 _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="text" name="account"/>
<input type="submit" value="Transfer"/>
</form>
외부 사이트는 동일 출처 정책(Same-Origin Policy)에 의해 응답 내용을 읽을 수 없으므로 CSRF 토큰 값을 알 수 없음.
요청은 아래와 같이 전송됨.
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
_csrf 파라미터가 포함되어 있고 공격자는 이 토큰 값을 맞출 수 없기 때문에 요청 검증에 실패함.CSRF 공격을 방어하는 또 다른 방법은 쿠키에 SameSite 속성을 지정하는 것.
Spring Security는 세션 쿠키를 생성하지 않으므로 SameSite 속성을 직접 제어하지 않음.
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
SameSite 속성의 유효한 값은 아래와 같음.
세션 쿠키에 SameSite 속성이 설정되면 브라우저는 정상적인 웹사이트에서 오는 요청에는 계속해서 JSESSIONID 쿠키를 전송함.
CSRF 공격 방어를 위해 SameSite 속성을 사용할 때 주의해야 할 몇 가지 중요한 고려 사항이 있습니다.
SameSite 옵션을 Strict로 설정하면 더 강력한 방어가 제공되지만 사용자를 혼란스럽게 할 수도 있음.
또 다른 고려 사항은 SameSite 속성이 사용자를 보호하기 위해서는 브라우저가 SameSite 속성을 지원해야 함.
위와 같은 이유로 일반적으로 우리는 SameSite 속성을 CSRF 공격에 대한 유일한 보호 수단이 아닌 심층 방어(defense in depth)의 한 부분으로 사용할 것을 권장함.
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
애플리케이션이 상태 비저장(stateless)이라면?
Ex)
기본 인증(Basic Authentication)을 사용하는 애플리케이션 또한 CSRF 공격에 취약함.
로그인 요청을 위조하는 것을 방지하기 위해 로그인 HTTP 요청은 CSRF 공격으로부터 보호되어야 함.
공격은 아래와 같이 수행됨.
로그인 HTTP 요청이 CSRF 공격으로부터 보호되도록 하는 과정에서 발생할 수 있는 문제점은 세션 타임아웃(session timeout)이 발생할 수 있다는 점.
로그아웃 요청 위조를 방지하기 위해 로그아웃 HTTP 요청은 CSRF 공격으로부터 보호되어야 함.
로그아웃 HTTP 요청을 CSRF 공격으로부터 보호할 때도, 세션이 만료되어 요청이 거부되는 문제가 발생할 수 있습니다.
일반적으로 서버는 요구하는 CSRF 토큰(expected CSRF token)을 세션에 저장함.
이 문제를 해결하기 위한 몇 가지 방법(각각 장단점이 있음)은 아래와 같음.
세션 만료가 임박했음을 사용자에게 알려주는 JavaScript 구현.
CSRF 토큰을 쿠키에 저장하는 방법.
multipart/form-data 형식의 요청(파일 업로드)을 CSRF로부터 보호하는 것은 일종의 닭이 먼저냐, 달걀이 먼저냐 문제를 야기함.
이를 해결하기 위한 두 가지 방법은 아래와 같고 각각 장단점이 존재함.
Spring Security의 CSRF 보호 기능을 멀티파트(Multipart) 파일 업로드와 통합하기 전에, 먼저 CSRF 보호 없이도 업로드가 가능한지 확인해야 함.
_method 값을 통해 DELETE 요청처럼 처리할 수 있음.<form action="/process" method="post">
<!-- ... -->
<input type="hidden" name="_method" value="delete"/>
</form>
이러한 HTTP 메서드 재정의는 필터(Filter) 에서 수행되며 반드시 Spring Security의 필터보다 먼저 배치되어야 함.
여기서 재정의가 POST 요청에서만 발생하므로 실제로 큰 문제가 되는 경우는 드물지만, 보안상의 모범 사례로는 항상 Spring Security 필터보다 앞에 위치시키는 것이 바람직함.