Files
spring-boot-examples/docs/security/09_보안 강화.md
2025-04-08 19:56:24 +09:00

8.3 KiB

아래는 "스프링 시큐리티" 책의 9장에 포함될 "CSRF(Cross-Site Request Forgery) 방어", "XSS(Cross-Site Scripting)와 콘텐츠 보안 정책", 그리고 "보안 헤더 추가"에 대한 내용입니다. 각 보안 위협의 개념과 스프링부트에서의 대응 방법을 실습 가능하도록 설명했습니다.


9장. 보안 강화 기법

9.1 CSRF(Cross-Site Request Forgery) 방어

**CSRF(Cross-Site Request Forgery)**는 사용자가 의도하지 않은 요청을 악의적인 웹사이트를 통해 서버로 전송하게 만드는 공격입니다. 예를 들어, 사용자가 로그인한 상태에서 악성 사이트의 링크를 클릭하면, 사용자의 인증 쿠키를 활용해 은행 계좌 이체 같은 요청이 실행될 수 있습니다.

CSRF 동작 원리
  • 공격자는 피해자가 로그인한 상태를 가정.
  • 피해자가 공격자의 사이트에서 숨겨진 폼(예: <form action="http://bank.com/transfer" method="post">)을 실행.
  • 브라우저가 자동으로 인증 쿠키를 포함해 요청 전송.
스프링 시큐리티의 CSRF 보호

스프링 시큐리티는 기본적으로 CSRF 보호를 활성화하며, POST, PUT, DELETE 같은 상태 변경 요청에 CSRF 토큰을 요구합니다.

기본 설정
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .formLogin();
        return http.build();
    }
}
  • CSRF 토큰은 <input type="hidden" name="_csrf" value="token"> 형태로 폼에 자동 추가(Thymeleaf 사용 시).
  • 토큰은 세션마다 고유하며, 요청 시 서버가 이를 검증.
Thymeleaf에서 사용
<form method="post" th:action="@{/update}">
    <input type="text" name="data">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
    <button type="submit">제출</button>
</form>
CSRF 비활성화

Stateless 인증(JWT 등)을 사용할 때는 CSRF 보호가 필요 없을 수 있습니다:

http
    .csrf(csrf -> csrf.disable())
    .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
방어 팁
  • GET 요청 제한: 상태 변경은 POST로만 처리.
  • 토큰 확인: 모든 변경 요청에 CSRF 토큰 포함.
  • SameSite 쿠키: 세션 쿠키에 SameSite=Strict 설정 추가(아래 보안 헤더 참조).

9.2 XSS(Cross-Site Scripting)와 콘텐츠 보안 정책

**XSS(Cross-Site Scripting)**는 공격자가 웹 페이지에 악성 스크립트를 삽입해 사용자의 브라우저에서 실행시키는 공격입니다. 이를 통해 세션 쿠키를 탈취하거나 페이지를 조작할 수 있습니다.

XSS 유형
  • Reflected XSS: 악성 스크립트가 URL 파라미터 등으로 전달되어 즉시 실행.
  • Stored XSS: 악성 스크립트가 데이터베이스에 저장되어 모든 사용자에게 노출.
  • DOM-based XSS: 클라이언트 측 스크립트가 조작됨.
스프링에서의 XSS 방어
  1. 입력 검증: 사용자 입력을 철저히 검증하고 sanitization 적용.
    • HtmlUtils.htmlEscape()로 HTML 이스케이프:
    import org.springframework.web.util.HtmlUtils;
    String safeInput = HtmlUtils.htmlEscape(userInput);
    
  2. 템플릿 엔진: Thymeleaf는 기본적으로 출력값을 이스케이프해 XSS 방어.
    <p th:text="${userInput}"></p> <!-- 자동 이스케이프 -->
    <p th:utext="${userInput}"></p> <!-- 이스케이프 비활성화, 주의 필요 -->
    
콘텐츠 보안 정책 (CSP)

**CSP(Content Security Policy)**는 브라우저가 허용된 소스에서만 리소스를 로드하도록 제한해 XSS를 방어합니다. HTTP 헤더로 설정합니다.

CSP 설정
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.StaticHeadersWriter;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .addHeaderWriter(new StaticHeadersWriter("Content-Security-Policy",
                    "default-src 'self'; script-src 'self' https://trusted.cdn.com;"))
            )
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin();
        return http.build();
    }
}
  • default-src 'self': 기본적으로 같은 출처에서만 리소스 로드.
  • script-src 'self' https://trusted.cdn.com: 스크립트는 자체 및 신뢰된 CDN에서만 허용.
방어 팁
  • 출력 이스케이프: 모든 동적 콘텐츠를 이스케이프 처리.
  • CSP 강화: 외부 리소스 최소화 및 엄격한 정책 적용.
  • 입력 제한: 허용된 문자만 허용(예: 정규식 사용).

9.3 보안 헤더 추가

보안 헤더는 브라우저의 기본 보안 기능을 강화해 다양한 공격을 방지합니다. 스프링 시큐리티는 이를 쉽게 추가할 수 있는 설정을 제공합니다.

주요 보안 헤더
  1. X-Content-Type-Options:

    • nosniff: MIME 타입 스니핑 방지.
    • 설정:
    http.headers(headers -> headers.contentTypeOptions());
    
  2. X-Frame-Options:

    • DENY 또는 SAMEORIGIN: 클릭재킹(Clickjacking) 방지.
    • 설정:
    http.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()));
    
  3. X-XSS-Protection:

    • 1; mode=block: 브라우저의 XSS 필터 활성화(구형 브라우저 지원).
    • 설정:
    http.headers(headers -> headers.xssProtection());
    
  4. Strict-Transport-Security (HSTS):

    • HTTPS 강제 적용 및 중간자 공격 방지.
    • 설정:
    http.headers(headers -> headers.httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000)));
    
  5. Content-Security-Policy: 위 CSP 섹션 참조.

  6. Referrer-Policy:

    • 참조 정보 제한(예: no-referrer-when-downgrade).
    • 설정:
    http.headers(headers -> headers.referrerPolicy(referrer -> referrer.policy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE)));
    
종합 설정 예제
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .contentTypeOptions()
                .frameOptions(frame -> frame.deny())
                .xssProtection()
                .httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000).includeSubDomains(true))
                .addHeaderWriter(new StaticHeadersWriter("Referrer-Policy", "strict-origin-when-cross-origin"))
                .addHeaderWriter(new StaticHeadersWriter("Content-Security-Policy", "default-src 'self'"))
            )
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin();
        return http.build();
    }
}
SameSite 쿠키 설정

CSRF 방어를 강화하려면 세션 쿠키에 SameSite 속성 추가:

# application.properties
server.servlet.session.cookie.same-site=strict
  • Strict: 타사 사이트에서 쿠키 전송 차단.
  • Lax: GET 요청은 허용, POST 등은 차단.
실습 예제
  1. CSRF 토큰 없이 POST 요청 시 403 확인.
  2. XSS 공격 시도(예: <script>alert('xss')</script>) 후 이스케이프 동작 확인.
  3. 브라우저 개발자 도구에서 보안 헤더 적용 여부 점검.

위 내용은 CSRF, XSS, 보안 헤더의 개념과 스프링 시큐리티에서의 방어 방법을 실습 가능하도록 설명했습니다. 추가적인 예제나 세부 사항이 필요하면 말씀해 주세요!