CSRF란?
CSRF Cross-Site Request Forgery의 약자로 인증된 사용자의 권한을 악용하여 해당 사용자가 의도하지 않은 요청을 웹사이트에 전송하는 공격 기법입니다. 공격자는 사용자가 이미 인증된 상태를 악용하여 사용자의 의도와는 무관한 작업을 수행하게 만듭니다. 다시 말해 인증된 요청과 위조된 요청을 구분하지 못하고 서버에서 요청을 처리하여 문제가 생기는 것을 말하는데요. 웹 개발자라면 반드시 알아야하는 부분입니다. 오늘은 이것에 대해서 알아보도록 하겠습니다.
CSRF 공격
CSRF 공격에 대해서 예시와 함께 알아보도록하겠습니다.
예시로 은행 웹사이트에서 로그인한 사용자로부터 돈을 이체할 수 있는 Form이 있다고 가정하겠습니다.
<form method="post" action="/transfer">
<input type="text" name="amount"/>
<input type="text" name="account"/>
<input type="submit" value="Transfer"/>
</form>
HTTP 요청은 다음과 같습니다.
POST /transfer HTTP/1.1
Host: bank.jours.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=10000&account=7654
다음은 은행 웹사이트에서 로그아웃하지 않고 위조된 웹사이트를 방문한다고 가정하겠습니다. 그 웹사이트에는 다음과 같은 HTML이 있습니다.
<form method="post" action="https://bank.jours.com/transfer">
<input type="hidden" name="amount" value="100000"/>
<input type="hidden" name="account" value="1234"/> <!-- 공격자 계좌번호 -->
<input type="submit" value="Win Money!"/>
</form>
위조된 웹사이트에서 submit을 하면 공격자 계좌로 송금이 될겁니다. 이는 위조된 웹사이트가 사용자의 쿠키를 볼 수 없지만 은행과 관련된 쿠키는 여전히 남아 요청과 함께 전송되기 때문에 발생합니다. 더욱 큰 문제는 버튼을 클릭해 submit 하지 않아도 JavaScript를 사용하여 자동화하여 제출할 수 있다는 것입니다. 그렇다면 어떻게 이 문제를 해결할 수 있을까요?
CSRF 방어
읽기전용 메소드
CSRF를 방어하기위해서는 읽기전용 메소드가 선행되어야 합니다.
HTTP Method중 GET, HEAD, OPTIONS, TRACE 메소드는 반드시 읽기전용 메소드가 되어야합니다.
// 잘못된 예시 - GET으로 데이터 변경
@GetMapping("/user/delete/{id}") // ❌ 절대 하면 안 됨
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
// 올바른 예시 - POST로 데이터 변경
@PostMapping("/user/delete/{id}") // ✅ 올바른 방법
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
1. Synchrozier Token Pattern
form 안에 CSRF 토큰을 넣어주는겁니다. 그러면 서버는 토큰을 조회하여 값이 일치하지 않으면 요청을 거부할 수 있게됩니다. 핵심은 쿠키는 브라우저에서 자동으로 HTTP 요청에 포함되지만 CSRF 토큰이 브라우저에서 자동으로 포함되지 않는다는 것입니다.
<form method="post" action="/transfer">
<input type="text" name="amount"/>
<input type="text" name="account"/>
<input type="hidden" name="_csrf" value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="submit" value="Transfer"/>
</form>
HTTP 요청은 다음과 같습니다.
POST /transfer HTTP/1.1
Host: bank.jours.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100000&account=7654&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
<!-- Thymeleaf 에서 @{} 를 사용하면 자동으로 CSRF 토큰이 포함됨 -->
<form th:action="@{/login}" method="post">
SameSite
쿠키에 SameSite 속성을 지정하는 것입니다. 서버는 SameSite 속성을 지정하여 외부 사이트에서 오는 쿠키를 보낼지 여부를 정할 수 있습니다.
설정값 | 설명 |
Strict |
|
Lax |
|
None |
|
# application.properties
# strict, lax, none 중 설정
server.servlet.session.cookie.same-site=strict
Spring Boot 2.6.0 이상에서는 SameSite=Lax 가 Default입니다.
SameSite 주의점
- 브라우저가 SameSite를 지원하지 않을 수 있습니다. 예전 브라우저를 사용한다면 SameSite가 지원되지 않을 수 있습니다.
- strict 설정 시 social.jours.com - email.jours.com 과의 쿠키는 전송되지않습니다.
REST API의 CSRF 설정
REST API에서는 CSRF를 disabled 해도 괜찮다고합니다. 왜냐하면 API 요청시 인증정보(Jwt, OAuth2, Client Key 등)를 포함하여 전송하기 때문에 불필요하게 CSRF 인증정보를 저장하지 않아도 되는 것입니다.
Spring Security 에서의 CSRF 설정
Spring Security 에서 CSRF를 설정할 수 있습니다. 기본적으로 CSRF 토큰을 지원합니다.
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.csrf(Customizer.withDefaults());
return http.build();
}
csrf 설정 메소드
http.csrf(csrf -> {
csrf
// CSRF 완전 비활성화
.disable()
// 특정 경로 CSRF 검증 제외
.ignoringAntMatchers("/api/**")
// RequestMatcher로 더 복잡한 조건으로 제외할 경로 설정
.ignoringRequestMatchers(requestMatcher)
// CSRF 토큰 저장소 설정
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// 커스텀 CSRF 토큰 저장소 설정
.csrfTokenRepository(new CustomCsrfTokenRepository())
// CSRF 토큰 생성 요청 처리 경로 설정 (기본값: "_csrf")
.csrfTokenRequestHandler(requestHandler)
// 세션 속성 이름 설정 (기본값: "CSRF_TOKEN")
.sessionAuthenticationStrategy(sessionAuthenticationStrategy)
// CSRF 토큰 필터 이전에 실행될 필터 추가
.requireCsrfProtectionMatcher(requestMatcher)
});
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 는 JavaScript에서 CSRF 토큰을 사용 할 수 있도록 쿠키에 저장하는 것인데, 위에서 설명했듯이 API는 csrf.disabled() 해서 사용하는 것이 더 유용할 수 있습니다.
결론
CSRF 공격은 웹 애플리케이션의 중요한 보안 위협이지만, 적절한 방어 메커니즘을 구현함으로써 효과적으로 방어할 수 있습니다. 특히 CSRF 토큰, SameSite 쿠키 설정, 그리고 적절한 헤더 검증을 조합하여 사용하는 것이 권장됩니다.
'일반 > CS' 카테고리의 다른 글
[CS][Spring Security] CORS에 대해서 알아보자 (0) | 2024.12.16 |
---|---|
Maven Central Repository에 라이브러리 등록하기 (0) | 2024.09.19 |
[CS] MVC 패턴 (0) | 2024.04.17 |
HTTP GET과 POST 차이 (0) | 2024.01.25 |
URI와 URL의 차이점 (Feat : URN) (0) | 2024.01.21 |