CSRF를 파헤쳐 보자
CSRF는 아마 Spring Security를 설정하고 이전과 같이 http 요청을 보낼 때 403 에러가 뜨는 경우 처음 접하는 경우가 많다고 생각합니다.
CSRF는 악의적인 웹사이트에서 사용자가 의도치 않게 특정 웹 어플리케이션에 대한 요청을 보내는 공격을 막기 위한 보안 기술입니다. Spring Security에서는 CSRF 공격으로부터 보호하기 위해 기본적으로 활성화되어 있습니다.
아래와 같이 Spring Security에서 CSRF 보호를 해제하기 위해 disable() 메서드를 사용하는 경우, CSRF 보호 기능이 완전히 비활성화되어 웹 어플리케이션이 보안 취약성에 노출될 수 있습니다. 따라서 이를 사용할 때에는 보안적인 측면을 고려하여 신중하게 사용해야 합니다.
웹에선 다른 도메인의 페이지에서 쿠키와 같은 인증 정보를 전송해 CSRF가 보안에 위협이 되나 앱에선 Same-Origin Policy 제한이 적용되지 않아 상대적으로 안전하다. 그러나 나는 지금 웹 개발을 하고 있으므로 이번 기회에 확실히 이해하고자 한다. 복잡한 지식일수록 하이 리턴이라 점점 쌓여가는 나의 개발 두뇌는 내 가치를 더욱 더 올려준다 크크ㅡ크
(해-피) ( 맛있따 )
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable();
}
}
일반적으로 Spring Security에서 CSRF 보호를 사용하면, 서버는 사용자에게 CSRF 토큰을 제공하고, 클라이언트에서는 이 토큰을 요청에 포함시켜서 서버로 보내야 합니다. 이러한 방식으로 악의적인 사이트에서는 정상적인 사용자의 CSRF 토큰을 획득하기 어려워집니다.
CSRF 예시
예를 들어, 당신이 은행 웹사이트에 로그인한 후 로그아웃하지 않고 다른 탭에서 악의적인 웹사이트에 접속한다고 가정해봅시다. 이 악의적인 웹사이트는 자바스크립트나 특정 버튼 등을 이용하여 은행 웹사이트에 자동으로 돈을 이체하는 요청을 보낼 수 있습니다. 이때, 악의적인 사용자는 이미 로그인된 은행 계정의 권한을 이용해 자동으로 돈을 이체하는 행위를 수행할 수 있습니다. 사용자는 이런 동작을 의도하지 않았지만 이미 인증된 상태에서 이루어지는 요청이기 때문에 웹사이트는 이를 유효한 요청으로 처리할 것입니다.
해결방법
1. Referrer Check : 백엔드에서 요청의 referer를 확인하여 도메인이 일치하는지 검증하는 방법!
2. SameSite : HTTP 쿠키에서 CSRF공격 방지를 위해 넣는 설정으로 3가지 옵션이 있는데 None, Lax, Strict가 있음
(상세한 내용이 많으므로 여기선 간략히 정리)
None : 쿠키가 모든 상황에서 전송됨
Lax : 쿠키가 동일한 사이트에서의 요청이 있을 때 혹은 top-level navigation을 통해 요청이 보내질 때 쿠키로 전송
Stick :는 쿠키가 동일한 사이트 내의 요청으로만 전송되도록한다.
3. CSRF Token :이 토큰은 사용자의 세션과 연관되어 있어서 악의적인 웹사이트가 이를 알지 못하고 제대로 된 요청을 만들기 어렵게 만듭니다. 이를 통해 사용자는 자신의 의도에 맞게 웹사이트를 이용할 수 있고, CSRF 공격으로부터 안전해집니다.
이 방법은 세션, 쿠키 외에도 각각의 http 요청에 토큰이라는 임의의 난수로 생성된 값이 있어야 하는 방법이다.
이 방식의 핵심은 CSRF Token은 브라우저가 HTTP Request에 자동으로 추가하는 정보가 아니여야 한다는 점이다. 예를들어, 실제 HTTP 파라미터나 헤더에서 요구하는 것은 CSRF 공격으로부터 방어가 되지만 쿠키는 브라우저에서 HTTP 요청에 자동으로 넣어주기 때문에 CSRF 토큰을 쿠키에 넣는 건은 방어법이 될 수 없다.
그렇다면 CSRF Token이 어떻게 HTTP Request에 추가되도록 하는 것일까?
이는 서버에서 웹 페이지를 발행할 때 CSRF Token 값을 넣어주고 사용자에 세션에 저장해두는 방식으로 해결한다.
필자는 3번 방법을 택하였다. 먼저 시큐리티 적용 후 아래와 같이 2가지를 수정하였다.
폼(form) 태그로 값을 넘기는 경우
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
fetch를 사용하여 api를 호출하는 경우
1. CSRF Token 설정: 웹 페이지의 섹션에 CSRF Token을 설정
<head>
<meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
</head>
2. JavaScript에서 CSRF Token 얻기
var csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content');
3. Fetch API를 사용하여 서버에 요청을 보내기, 이때 CSRF Token을 헤더에 추가하기
const hoonyList = document.getElementById('hoony');
fetch(`/hoony/select?email=${userEmail}`, {
method: 'GET',
headers: {
'X-CSRF-TOKEN': csrfToken
},
})
.then(response => response.json()) // JSON 응답 파싱
.then(data => {
// 나머지 코드
})
.catch(error => {
console.error('오류 발생:', error);
});
JWT를 사용하면 csrf를 해제해도 된다?
-> 필자는 긍정도 부정도 아니다. 프로젝트마다 다를 수 있다.
rest api에서 client는 권한이 필요한 요청을 하기 위해서는 요청에 필요한 인증 정보를(OAuth2, jwt토큰 등)을 포함시켜야 한다. 따라서 서버에 인증정보를 저장하지 않기 때문에 굳이 불필요한 csrf 코드들을 작성할 필요가 없다.
하지만 Jwt 토큰을 사용할 경우에도 결국 해당 토큰을 쿠키에 넣어야 하는데 이는 세션 id가 쿠키에 실려 전송되는 것과 같은 원리로 CSRF 공격에 똑같이 취약한 것 아닌가? (물론 필자는 공부 중이라 확실한 답변은 아니다)
좀 더 자세하게 기술하자면
일반적으로 JWT(Jason Web Token)을 사용하는 경우에는 세션을 사용하지 않고 stateless한 방식으로 인증을 처리하므로, JWT는 토큰 자체에 사용자 정보를 포함하고 있어 서버에서 별도의 세션 상태를 유지할 필요가 없기 때문에 jwt를 사용하면 csrf는 사용할 필요가 없다. 세션을 사용하지 않아도 결국 jwt자체는 쿠키에 저장해서 보안처리를 하게되고 그렇다면 csrf에 취약한 부분이 생긴다. 이것에 내 결론이다.
일단 jwt를 사용할 경우 대부분은 세션을 사용하지 않으므로 csrf기능은 비활성화를 대부분 하는 것 같다. 이것에 관하여는 더 정밀한 조사가 필요할 것 같다.
마무리
자신의 프로젝트 혹은 보호하고자 하는 목적, 내용의 수준에 맞는 암호수준의 밸런스와 이것을 결정할 수 있는 개발자로서의 능력이 가장 중요하다고 생각했다.
(풀기 쉬운 암호문은 거는 것만으로도 못하므로,,,,,,,)
'Spring > Spring Security' 카테고리의 다른 글
[SpringBoot] 스프링부트 Spring Security (0) | 2023.02.12 |
---|