1.6 웹 기반 중요기능 수행 요청 유효성 검증
개요
웹 애플리케이션에서 상태를 변경하는 중요 기능(예: 비밀번호 변경, 글쓰기/삭제, 계좌 이체 등)을 수행할 때, 해당 요청이 정상적인 사용자로부터 온 유효한 요청인지 검증하는 것은 매우 중요합니다. 이러한 검증이 부족할 경우 크로스사이트 요청 위조(CSRF: Cross-Site Request Forgery) 공격에 취약해질 수 있습니다.
CSRF는 공격자가 사용자의 웹 브라우저(이미 특정 사이트에 로그인된 상태)를 이용하여, 사용자 모르게 공격자가 의도한 요청(예: 비밀번호 변경 요청)을 해당 사이트에 보내도록 만드는 공격입니다. 사용자는 자신이 요청하지 않은 작업이 자신의 권한으로 실행되는 피해를 입게 됩니다.
이 기준은 상태 변경을 유발하는 중요 기능 요청 시, 해당 요청의 출처와 의도를 검증하여 CSRF 공격을 방어하기 위한 보안 요구사항을 정의합니다.
보안 대책
- - 안티-CSRF 토큰 사용 (Synchronizer Token Pattern): 상태 변경 요청 시, 서버는 예측 불가능한 비밀 토큰(Anti-CSRF Token)을 생성하여 사용자 세션에 저장하고, 해당 토큰을 웹 페이지의 숨겨진 필드(hidden field)나 메타 태그 등에 포함시켜 전달합니다. 사용자가 요청을 보낼 때 이 토큰을 함께 전송하도록 하고, 서버는 수신된 토큰과 세션에 저장된 토큰을 비교하여 일치할 경우에만 요청을 처리합니다. 이는 CSRF 방어의 가장 표준적이고 효과적인 방법입니다.
- - Double Submit Cookie 방식: 서버는 세션 쿠키와 별개로, 예측 불가능한 값을 가진 쿠키를 사용자에게 발급합니다. 상태 변경 요청 시, 클라이언트 측 스크립트는 이 쿠키 값을 읽어 요청 파라미터(또는 헤더)에 포함시켜 전송합니다. 서버는 수신된 쿠키 값과 파라미터(헤더) 값이 일치하는지 비교하여 요청을 검증합니다. (세션 상태를 유지하지 않는 경우 고려 가능)
- - Referer 헤더 검증: HTTP 요청 헤더의 `Referer` 값을 확인하여, 요청이 허용된 도메인(자신의 웹사이트)에서 시작되었는지 검증합니다. (단, Referer 헤더는 브라우저 설정이나 프록시 등에 의해 누락되거나 위변조될 수 있으므로 보조적인 수단으로 사용해야 합니다.)
- - SameSite 쿠키 속성 사용: 세션 쿠키에 `SameSite` 속성을 `Lax` 또는 `Strict`로 설정하여, 다른 도메인에서 시작된 요청(Cross-Site Request)에 쿠키가 전송되는 것을 제한함으로써 CSRF 공격을 완화할 수 있습니다. (최신 브라우저 지원 필요)
- - 중요 작업 시 재인증: 비밀번호 변경, 중요 정보 수정 등 민감한 작업을 수행하기 전에 사용자에게 비밀번호 재입력 등 추가 인증을 요구하여 보안을 강화합니다.
- - GET 요청 지양: 상태 변경을 유발하는 작업은 HTTP GET 메서드를 사용하지 않고 POST, PUT, DELETE 등의 메서드를 사용해야 합니다. (GET 요청은 CSRF 공격에 더 취약)
코드 예시 (Java - Spring Security CSRF 보호)
Spring Security는 CSRF 보호 기능을 기본적으로 제공하며, 주로 Synchronizer Token Pattern을 사용합니다.
안전한 설정 (Spring Security 설정):
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ... 다른 설정들 ...
.csrf() // CSRF 보호 활성화 (기본값)
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 필요 시 쿠키 방식 설정
.and()
// ... 나머지 설정 ...
}
}
<!-- JSP + Spring Security Taglibs -->
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<form action="/change-password" method="post">
<input type="password" name="newPassword" />
<!-- Spring Security가 자동으로 CSRF 토큰 hidden input 추가 -->
<sec:csrfInput />
<button type="submit">비밀번호 변경</button>
</form>
<!-- Thymeleaf + Spring Security -->
<form th:action="@{/change-password}" method="post">
<input type="password" name="newPassword" />
<!-- Thymeleaf가 자동으로 CSRF 토큰 hidden input 추가 -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<button type="submit">비밀번호 변경</button>
</form>
Python(Django, Flask-WTF), C#(ASP.NET Core AntiForgeryToken), JavaScript(프레임워크 사용 시 또는 직접 헤더 설정) 등 다른 환경에서도 프레임워크가 제공하는 안티-CSRF 기능을 사용하거나, 토큰 기반 방식을 직접 구현하여 CSRF 공격을 방어해야 합니다.