시냅스

Java 코드 예시로 보는 CSRF 설명과 예방 본문

Java, Spring

Java 코드 예시로 보는 CSRF 설명과 예방

ted k 2023. 3. 9. 22:51

 

https://nordvpn.com/ko/blog/csrf/

 

CSRF

Cross-Site Request Forgery

  • 공격자의 요청이 사용자의 요청인 것처럼 속이는 공격 방식
  • 사용자가 특정 사이트에 로그인한 상태로 악의적인 웹사이트에 방문하여 로그인한 특정 사이트에 악성 요청을 보내도록 유도
  • 사용자가 인증한 세션에서 웹 애플리케이션이 정상적인 요청과 비정상적인 요청을 구분하지 못하는 점을 악용
    • 쿠키와 같은 정보를 포함하고 있기 때문에
  • XSS와 비슷하지만 XSS는 세션이 없어도 가능하다.

 

예시

<https://bank.com/transfer?to=another_user&amount=1000>
  • bank 라는 사이트에서는 타인에게 송금할 때 위와 같은 url을 사용한다.
<html>
<body>
  <form action="https://bank.com/transfer?to=attacker&amount=1000000" method="POST">
    <input type="submit" value="Click here to win a million dollars!">
  </form>
</body>
</html>
  • 공격자가 위와 같은 HTML을 사용자에게 전송한다.
  • 위의 폼을 통해 공격자에게 1000000 원을 이체하게 된다.
  • 이때 서버는 인증된 세션값을 올바른지, 올바른지 인식하지 못하므로 송금을 수행하게 된다.

 

예방

  • 사용자 차원의 예방
    • 사용하지 않는 웹 애플리케이션 로그아웃
    • 로그인 정보 안정하게 보관
    • 여러 웹사이트를 동시에 사용하지 않는다.
  • 서버 차원의 예방
    • CSRF Token
      • 서버에서는 모든 폼 요청에 대해 CSRF 토큰을 생성하고 폼에 포함시켜 요청 시 검증한다.
      • SameSite 쿠키 속성 설정
        • 쿠키가 언제나 동일한 사이트에서 전송되도록 하는 방법
      • Referer 검증
        • HTTP Referer 헤더는 요청이 시작된 웹 페이지의 URL을 제공한다.
        • Referer 헤더를 검증하여 요청이 유효한지 여부를 가린다.
        • Referer 헤더를 변경할 수 있으므로 그다지 실효성을 갖진 않는다.
    • 단, REST API는 stateless (session 이나 cookie 를 사용하지 않기 때문에) 하며, CORS 를 사용하여 다른 도메인에서도 API를 호출할 수 있도록 하기 때문에 CSRF를 사용하기 어렵다
      • CORS : 다른 도메인에서 리소스에 접근하는 것을 허용하는 보안 기능
    • 따라서 spring security 에서 기본적으로 제공하는 csrf 공격을 방지하는 기능을 spring security를 통해 disable 시켜 헤더에서 검사하지 않게 한다.

아래 예시에서 CSRF Token 에 대한 사용법을 spring security 를 사용하여 간략히 알아본다.

 

예시

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      // CSRF 설정 추가
      .csrf()
        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
  }
 
  // ...
}
  • HttpOnly가 설정되어 있다면 javascript 에서 Cookie에 접근할 수 없으므로 false로 한다.
  • 위에서 설명했듯, csrf token 은 spring security 에서 기본적으로 제공한다.
  • spring security를 사용하지 않는다면, 유저가 로그인할 때 session에 임의의 값을 csrf token 으로 직접지정하거나, cookie 나 header에 저장할 수 있겠다.

 

<!DOCTYPE html>
<html>
<head>
  <!-- CSRF Token을 생성하기 위한 태그 추가 -->
  <meta name="_csrf" th:content="${_csrf.token}" />
  <meta name="_csrf_header" th:content="${_csrf.headerName}" />
</head>
<body>
  <form method="POST" action="/form">
    <!-- CSRF Token을 전송하기 위한 hidden 태그 추가 -->
    <input type="hidden" name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <!-- form 입력 필드들 -->
    <input type="text" name="username" />
    <input type="password" name="password" />
    <button type="submit">로그인</button>
  </form>
</body>
</html>
  • 클라이언트에서는 csrf token을 전송하며 함께 보내준다

 

@Controller
public class MyController {
 
  @PostMapping("/form")
  public String handleFormSubmit(@RequestParam("username") String username, 
                                  @RequestParam("password") String password,
                                  HttpServletRequest request) {
    // CSRF Token 검증하기
    CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    if (token != null) {
      String csrfToken = token.getToken();
      String csrfHeader = request.getHeader("X-CSRF-TOKEN");
      if (!csrfToken.equals(csrfHeader)) {
        throw new IllegalStateException("Invalid CSRF token.");
      }
    }
 
    // ...
  }
}
  • 서버에서는 위와 같이 csrf token을 클라이언트로부터 받아 검증하는 절차를 거칠 수 있게 된다.
Comments