HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🤩
개발
/
🔐
Spring Security
/
😮
CsrfFilter
😮

CsrfFilter

Csrf
  • CSRF (Cross-site request forgery) — 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격을 말함
    • CSRF를 통해 악의적인 공격자는 사용자의 권한을 도용하여 중요 기능을 실행하는 것이 가능해짐 (아래 2개 조건을 만족해야 함)
      • 위조 요청을 전송하는 서비스에 사용자가 로그인 상태
      • 사용자가 해커가 만든 피싱 사이트에 접속
      • https://rusyasoft.github.io/java/2019/02/15/spring-security-csrf-from-context/

      공격 과정 (위키피디아 참고)

      1. 이용자는 웹사이트에 로그인하여 정상적인 쿠키를 발급받는다
      1. 공격자는 다음과 같은 링크를 이메일이나 게시판 등의 경로를 통해 이용자에게 전달한다.
        1. http://www.geocities.com/attacker
      1. 공격용 HTML 페이지는 다음과 같은 이미지태그를 가진다.
        1. <img src= "https://travel.service.com/travel_update?.src=Korea&.dst=Hell">
          해당 링크는 클릭시 정상적인 경우 출발지와 도착지를 등록하기위한 링크이다. 위의 경우 도착지를 변조하였다.
      1. 이용자가 공격용 페이지를 열면, 브라우저는 이미지 파일을 받아오기 위해 공격용 URL을 연다.
      1. 이용자의 승인이나 인지 없이 출발지와 도착지가 등록됨으로써 공격이 완료된다. 해당 서비스 페이지는 등록 과정에 대해 단순히 쿠키를 통한 본인확인 밖에 하지 않으므로 공격자가 정상적인 이용자의 수정이 가능하게 된다.
    • XSS는 자바스크립트를 실행시키는 것이고, CSRF는 특정한 행동을 시키는 것으로 XSS과 CSRF는 다른 공격 기법임
CsrfFilter
  • Csrf 방지 방법
    • Referrer 검증 — Request의 referrer를 확인하여 domain이 일치하는지 확인 (정상적으로 웹서버에서 제공하는 페이지인지를 확인)
    • CSRF Token 활용
      • 사용자의 세션에 임의의 토큰 값을 저장하고 (로그인 완료 여부와 상관없음), 사용자의 요청 마다 해당 토큰 값을 포함 시켜 전송
      • 리소스를 변경해야하는 요청(POST, PUT, DELETE 등)을 받을 때마다 사용자의 세션에 저장된 토큰 값과 요청 파라미터에 전달되는 토큰 값이 일치하는 지 검증
      • 브라우저가 아닌 클라이언트에서 사용하는 서비스의 경우 CSRF 보호를 비활성화 할 수 있음
      • 로그인 페이지를 보면 _csrf 라는 이름으로 hidden input이 있는것을 확인할 수 있다
        로그인 페이지를 보면 _csrf 라는 이름으로 hidden input이 있는것을 확인할 수 있다
  • CsrfFilter는 요청이 리소스를 변경해야 하는 요청인지 확인하고, 맞다면 CSRF 토큰을 검증함 (기본적으로 활성화됨)
    • CsrfTokenRepository — CSRF 토큰 저장소 인터페이스이며 기본 구현체로 HttpSessionCsrfTokenRepository 클래스가 사용됨
    • protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(HttpServletResponse.class.getName(), response); CsrfToken csrfToken = this.tokenRepository.loadToken(request); boolean missingToken = (csrfToken == null); if (missingToken) { csrfToken = this.tokenRepository.generateToken(request); this.tokenRepository.saveToken(csrfToken, request, response); } request.setAttribute(CsrfToken.class.getName(), csrfToken); request.setAttribute(csrfToken.getParameterName(), csrfToken); if (!this.requireCsrfProtectionMatcher.matches(request)) { if (this.logger.isTraceEnabled()) { this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher); } filterChain.doFilter(request, response); return; } String actualToken = request.getHeader(csrfToken.getHeaderName()); if (actualToken == null) { actualToken = request.getParameter(csrfToken.getParameterName()); } if (!equalsConstantTime(csrfToken.getToken(), actualToken)) { this.logger.debug( LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request))); AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken); this.accessDeniedHandler.handle(request, response, exception); return; } filterChain.doFilter(request, response); }
      CsrfFilter 구현 일부 발췌