CSRF(Cross-Site Request Forgery)

·2024년 7월 19일

Web

목록 보기
2/6
post-thumbnail

Cross-Site Request Forgery (CSRF)는 사용자가 신뢰하는 웹 애플리케이션에서 사용자 모르게 명령을 실행하게 만드는 공격이다. 사용자가 의도하지 않은 웹 요청을 제출하도록 속인다.

CSRF 공격은 사용자의 브라우저가 특정 웹사이트에 신뢰된 요청을 보내도록 만들어 세션 상태 변경, 계정 조작 등의 행위를 수행할 수 있다.

예를 들어, 은행 웹 사이트가 현재 로그인된 사용자가 다른 은행으로 돈을 이체할 수 있는 폼을 제공한다고 가정하자.

이체 폼

<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>

이체 HTTP 요청

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

이제 은행 웹 사이트에 인증한 후 로그아웃하지 않고 악성 웹 사이트를 방문했다고 가정한다.

악성 웹 사이트에는 다음과 같은 폼이 포함된 HTML 페이지가 있다.

<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>

버튼을 클릭하면, 악성 사용자에게 100 달러를 이체하게 된다. 악성 웹 사이트가 쿠키를 볼 수는 없지만, 은행과 관련된 쿠키가 요청과 함께 전송되기 때문이다.

더 심각한 문제는 이 과정이 자바스크립트를 사용해 자동화될 수 있다는 점이다. 사용자가 버튼을 클릭할 필요도 없다는 것이다. 원래는 정상적인 웹 사이트였지만, XSS 공격을 받아 취약점이 생긴 사이트를 방문해도 이런 일이 생길 수 있다.

CSRF 공격 방어 방법

CSRF 공격이 가능한 이유는 피해자의 웹사이트와 공격자의 웹사이트에서 보낸 HTTP 요청이 정확히 동일하기 때문이다. CSRF 공격을 방어하기 위해서는 악성 사이트가 제공할 수 없는 무언가가 요청에 포함되어 있어야 두 요청을 구별할 수 있다.

  1. 동기화 토큰 패턴

    CSRF 공격으로부터 보호하는 가장 일반적이고 종합적인 방법은 동기화 토큰 패턴을 사용하는 것이다. 세션 쿠키와 더불어 각 HTTP 요청에 보안 무작위 생성 값을 요구하는 것이다. 이 값을 CSRF 토큰이라고 한다.

    HTTP 요청이 제출되면 서버는 예상되는 CSRF 토큰을 조회하고 HTTP 요청의 실제 CSRF 토큰과 비교해야 한다. 값이 일치하지 않으면 HTTP 요청을 거부해야 한다.

    HTTP 매개변수 또는 HTTP 헤더에 실제 CSRF 토큰이 포함되도록 요구하면 CSRF 공격으로부터 보호할 수 있다. 쿠키에 CSRF 토큰을 포함시키는 것은 브라우저가 자동으로 쿠키를 HTTP 요청에 포함시키기 때문에 작동하지 않는다.

    <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>

    이 폼의 HTTP 요청은 다음과 같다.

    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 토큰을 예상된 CSRF 토큰과 비교할 때 이체가 실패한다.

  2. SameSite 속성

    서버는 쿠키를 설정할 때 SameSite 속성을 지정하여 외부 사이트에서 요청이 올 때 쿠키를 전송하지 않도록 할 수 있다.

    Spring Security는 세션 쿠키의 생성을 직접 제어하지 않으므로 SameSite 속성에 대한 지원을 제공하지 않는다. Spring Session은 서블릿 기반 애플리케이션에서 SameSite 속성을 지원한다. Spring Framework의 CookieWebSessionIdResolver는 WebFlux 기반 애플리케이션에서 SameSite 속성에 대한 기본 지원을 제공한다.

    SameSite 속성을 사용하여 CSRF 공격을 방어할 때 몇 가지 중요한 고려 사항이 있다.

    SameSite 속성을 Strict로 설정하면 강력한 방어를 제공하지만 사용자를 혼란스럽게 할 수 있다. 예를 들어, 사용자가 social.example.com에 로그인된 상태에서 email.example.org에서 이메일을 받고 이메일에 포함된 링크를 클릭하면, 사용자는 social.example.com에 인증된 상태로 이동할 것으로 기대한다. 그러나 SameSite 속성이 Strict로 설정된 경우 쿠키가 전송되지 않아 사용자가 인증되지 않는다.

Spring Security는 POST 요청과 같은 안전하지 않은 HTTP 메서드에 대해 기본적으로 CSRF 공격으로부터 보호하므로 추가 코드는 필요하지 않지만, 기본 구성을 명시적으로 지정할 수 있다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf(Customizer.withDefaults());
		return http.build();
	}
}

Spring Security의 CSRF 처리 단계를 나타낸 그림이다.

  1. DeferredCsrfToken이 로드된다. 이는 CsrfTokenRepository를 참조하여 나중에 저장된 CsrfToken을 불러올 수 있게 한다.
  2. CsrfTokenRequestHandler에 Supplier이 제공된다. 이는 CsrfToken을 애플리케이션 전체에서 사용할 수 있도록 요청 속성에 넣는 역할을 한다.
  3. CSRF 보호 처리가 시작된다. 현재 요청이 CSRF 보호가 필요한지 확인한다. 필요 없다면, 다음 단계로 넘어가고 처리를 마친다.
  4. CSRF 보호가 필요하다면, DeferredCsrfToken에서 저장된 CsrfToken을 불러온다.
  5. 클라이언트가 보낸 실제 CSRF 토큰이 있으면, CsrfTokenRequestHandler로 이 토큰을 확인한다.
  6. 실제 CSRF 토큰을 저장된 CsrfToken과 비교한다. 일치하면 다음 단계로 넘어가고 처리를 마친다.
  7. 실제 CSRF 토큰이 잘못되었거나 없으면, AccessDeniedException을 AccessDeniedHandler에 전달하고 처리를 마친다.

참고

Cross Site Request Forgery (CSRF) :: Spring Security

Cross-site request forgery

Cross Site Request Forgery (CSRF) :: Spring Security

profile
개발블로그👩🏻‍💻

0개의 댓글