[Issue] CSRF Attack | JeongKeepsCalm

[Issue] CSRF Attack

CSRF(Cross-Site Request Forgery)

  • 공격자는 사용자가 인지하지 못한 상태에서 사용자의 권한을 이용해 특정 요청을 서버에 보내도록 유도
  • 공격자는 사용자의 권한으로 악의적인 작업을 수행

CSRF 공격 방어

Security Code

1
2
3
4
5
6
7
8
private void csrf() throws Exception {
    httpSecurity.csrf(csrf -> csrf
            .ignoringRequestMatchers(new AntPathRequestMatcher("/api/**"))
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .csrfTokenRequestHandler(new SpaCsrfTokenTestHandler())
            )
            .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
}
  • ignoringRequestMatchers
    • /api/** 경로의 요청은 CSRF 검증을 무시한다.
  • csrfTokenRepository
    • CSRF 토큰을 쿠키에 저장
  • csrfTokenRequestHandler
    • SPA용 CSRF 토큰 처리 핸들러를 설정
    • Thymeleaf로 개발된 애플리케이션은 전통적인 서버 사이드 렌더링 방식을 사용하지만, 일부 페이지나 기능에서 SPA 방식으로 동작할 수 있다. 이 경우 SPA용 CSRF 토큰 처리 핸들러가 필요

SPA vs MPA

  • SPA(Single Page Application)
    • 단일 페이지에서 동적으로 콘텐츠를 로드
  • MPA(Multiple Page Application)
    • 여러 페이지로 구성


1
2
3
4
5
<form name="formTest" action="/auth/test" method="post" target="test_popup">
    <input type="hidden" name="_csrf" th:value="${_csrf.token}" />
    <input type="hidden" name="test1" value="testValue1">
    <input type="hidden" name="test2" value="testValue2">
</form>

api 로 시작되지 않는 url에 DB 내 데이터를 변경할 수 있는 메소드가 존재할 경우
input type="hidden" name="_csrf" th:value="${_csrf.token}"
해당 input이 필요하다.



CsrfCookieFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
final class CsrfCookieFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
        if (csrfToken != null) {
            csrfToken.getToken(); // CSRF 토큰을 강제로 로드
        }
        filterChain.doFilter(request, response);

    }
}


SpaCsrfTokenTestHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler {

    private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
        // XorCsrfTokenRequestAttributeHandler로 BREACH 보호 처리
        this.delegate.handle(request, response, csrfToken);
    }

    @Override
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {

        // 요청 헤더에 CSRF 토큰이 있으면 해당 값을 사용
        if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
            return super.resolveCsrfTokenValue(request, csrfToken);
        }
        // 그 외의 경우, 요청 파라미터에서 CSRF 토큰 값을 가져옴
        return this.delegate.resolveCsrfTokenValue(request, csrfToken);

    }

}