부트모아

[V2] Spring Security CSRF

승무_ 2024. 9. 28. 14:39

부트모아 취약점을 분석 중 대표적인 클라이언트 사이드 취약점인 CSRF를 발견하였다.

CSRF
사용자가 자신의 의지와는 상관없이 공격자가 의도한 수정, 삭제, 등록 행위 등 특정 웹사이트에 요청하게 하는 공격

공격자가 위와 같은 스크립트가 포함된 게시글을 작성했을때

위와 같은 게시글이 작성되었다.

관리자는 해당 버튼이 홈페이지 링크인줄 알고 확인차 눌렀더니 관리자의 의도와 상관없이 관리자 이름으로 공지사항이 작성되었다.

 

대응

요청에 대한 검증을 하기 위해 CSRF 토큰을 직접 만들려 했으나, Spring Security 문서를 읽던 중 관련 기능이 이미 구현되어 있다는 것을 알고 프로젝트에 적용해 보기로 하였다.

 

tokenRepository.loadToken(request)

  • 요청 세션에서 CSRF Token 객체를 조회한다. key는 HttpSessionCsrfTokenRepository.CSRF_TOKEN 이다.

 tokenRepository.generateToken(request)

  • CSRF Token이 없을 경우 DefaultCsrfToken 생성자를 통해 CSRF Token을 발급한다.

tokenRepository.saveToken()

  •  세션 내에 key = HttpSessionCsrfTokenRepository.CSRF_TOKEN, value = 생성한 CsrfToken 객체를 저장한다.

request.setAttribute(CsrfToken.class.getName(), csrfToken)

  • HttpServletRequest 를 통해 csrfToken 값을 조회할 수 있도록 설정해주는 부분이다.

requireCsrfProtectionMatcher.matches(request)

  • Csrf 검증 메서드를 체크한다. 기본적으로 GET, HEAD, TRACE, OPTIONS를 제외한 모든 메서드에 대해서는 CsrfToken을 검증한다. 만약 GET으로 요청이 들어왔다면 검증 없이 다음 Filter로 넘어간다.

request.getHeader(csrfToken.getHeaderName()) , getParameter(csrfToken.getParameterName())

  • 요청 헤더에 X-CSRF-TOKEN 값이 있는지 확인하고, 없을 경우 요청 바디에서 _csrf 값이 있는지 확인한다.
  • 클라이언트는 요청 헤더의 X-CSRF-TOKEN 혹은 요청 바디의 _csrf 값 둘 중 하나로 CsrfToken 값을 보내면 된다.

equalsConstantTime(csrfToken.getToken(), actualToken)

  • 세션에 저장(혹은 생성)한 토큰값과 클라이언트에서 보낸 토큰 값을 비교하여 일치할 경우 다음 필터를 호출하고 불일치할 경우 accessDeniedHandler 메서드를 호출하여 예외 처리한다.

공식 문서를 통해 위와 같은 내용을 학습하였고 프로젝트에 적용해 보니

<form> 안에 hidden 타입으로 토큰이 자동으로 들어왔다.

'A학원 홈페이지' <form> 스크립트에는 CSRF Token 정보가 없어 403 에러가 뜨며 공격자가 의도한 요청이 막히는 것을 확인할 수 있었다.