Skip to content

java-security-core/xss-protector

Repository files navigation

Lucy XSS Filter for Spring Boot

네이버 Lucy XSS Filter를 사용한 강력한 XSS 공격 방어 솔루션

Spring Boot 애플리케이션에 즉시 적용 가능한 XSS 필터링 모듈입니다. GET, POST, PUT, DELETE, PATCH 등 모든 HTTP 메서드의 파라미터와 JSON body를 자동으로 필터링합니다.

주요 기능

모든 HTTP 메서드 지원

  • GET, POST, PUT, DELETE, PATCH 등 모든 메서드에 XSS 필터링 적용
  • URL 쿼리 파라미터, Form 데이터, JSON Request Body 모두 지원

HTML 엔티티 변환

  • 위험한 문자를 HTML 엔티티로 변환: <&lt;, >&gt;
  • 데이터 손실 없이 안전하게 보존

스마트 필터링

  • 파일 업로드(multipart/form-data)는 자동 제외
  • 이중 치환 방지 로직 내장

검증된 라이브러리

  • 네이버에서 개발한 Lucy XSS Filter 사용
  • 프로덕션 환경에서 검증된 안정성

기술 스택

  • Spring Boot 3.3.2+
  • OpenJDK 21
  • Gradle 8.5
  • Lucy XSS Servlet Filter 2.0.1

빠른 시작

1. 프로젝트에 파일 복사

다음 파일들을 당신의 Spring Boot 프로젝트에 복사하세요:

src/main/java/com/lg/common/xss/
├── config/
│   ├── FilterConfig.java              # Filter 등록 설정
│   └── XssRequestBodyAdvice.java      # JSON body 필터링
├── filter/
│   ├── XssEscapeUtil.java             # HTML 엔티티 변환 유틸
│   ├── XssProtectionFilter.java       # XSS 방어 Filter
│   └── XssRequestWrapper.java         # Request Wrapper
└── src/main/resources/
    └── lucy-xss-superset.xml          # Lucy XSS 설정 파일

2. 의존성 추가

build.gradle에 Lucy XSS 라이브러리를 추가하세요:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1'
}

// 컴파일러 옵션 추가 (매개변수 이름 보존)
tasks.withType(JavaCompile).configureEach {
    options.compilerArgs << '-parameters'
}

3. 패키지 경로 변경

복사한 파일들의 패키지를 당신의 프로젝트에 맞게 수정하세요:

// 변경 전
package com.lg.common.xss.filter;

// 변경 후 (예시)
package com.yourcompany.yourproject.security.xss;

4. 애플리케이션 실행

Spring Boot 애플리케이션을 실행하면 자동으로 XSS 필터링이 적용됩니다.

./gradlew bootRun

로그에서 다음 메시지를 확인하세요:

XSS Protection Filter initialized successfully
XSS Filter test: '<script>alert('xss')</script>' -> '&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;'

프로젝트 구조

lucy_filter/
├── src/main/java/com/lg/common/xss/
│   ├── XssFilterApplication.java              # 메인 애플리케이션 (테스트용)
│   ├── config/
│   │   ├── FilterConfig.java                  # ✨ Filter 등록 설정
│   │   └── XssRequestBodyAdvice.java          # ✨ JSON body 필터링
│   ├── filter/
│   │   ├── XssEscapeUtil.java                 # ✨ HTML 엔티티 변환 유틸
│   │   ├── XssProtectionFilter.java           # ✨ XSS 방어 Filter
│   │   └── XssRequestWrapper.java             # ✨ Request Wrapper
│   └── controller/
│       └── TestController.java                # 테스트용 Controller
├── src/main/resources/
│   ├── application.properties                 # 애플리케이션 설정
│   └── lucy-xss-superset.xml                  # ✨ Lucy XSS Filter 설정
├── build.gradle                               # Gradle 빌드 파일
└── test-xss.ps1                               # PowerShell 테스트 스크립트

✨ = 필수 파일 (다른 프로젝트에 복사해야 함)

아키텍처

필터링 흐름

HTTP Request
    ↓
XssProtectionFilter (Servlet Filter)
    ├─→ multipart/form-data? → Skip (파일 업로드)
    ├─→ Already filtered? → Skip (이중 치환 방지)
    └─→ XssRequestWrapper
            ├─→ URL 쿼리 파라미터 필터링
            └─→ Form 데이터 필터링
    ↓
XssRequestBodyAdvice (RequestBodyAdvice)
    └─→ JSON Request Body 필터링
    ↓
Controller
    ↓
Response (필터링된 데이터)

주요 컴포넌트

컴포넌트 역할 필터링 대상
XssProtectionFilter Servlet Filter로 모든 요청을 가로챔 -
XssRequestWrapper Request Wrapper로 파라미터 필터링 URL 쿼리, Form 데이터
XssRequestBodyAdvice RequestBodyAdvice로 JSON body 필터링 JSON Request Body
XssEscapeUtil HTML 엔티티 변환 유틸 모든 String 값

사용 예시

GET 요청 (URL 파라미터)

요청:

curl "http://localhost:8080/api/user?name=<script>alert('xss')</script>"

필터링 결과:

{
  "name": "&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;"
}

POST 요청 (JSON)

요청:

curl -X POST http://localhost:8080/api/user \
  -H "Content-Type: application/json" \
  -d '{"name":"<script>alert(\"xss\")</script>","email":"test@test.com"}'

필터링 결과:

{
  "name": "&lt;script&gt;alert(&#34;xss&#34;)&lt;/script&gt;",
  "email": "test@test.com"
}

POST 요청 (Form Data)

요청:

curl -X POST http://localhost:8080/api/user \
  -d "name=<img src=x onerror=alert(1)>" \
  -d "email=test@test.com"

필터링 결과:

{
  "name": "&lt;img src=x onerror=alert(1)&gt;",
  "email": "test@test.com"
}

설정 커스터마이징

1. 특정 URL 제외하기

FilterConfig.java를 수정하여 특정 URL을 필터링에서 제외할 수 있습니다:

@Bean
public FilterRegistrationBean<XssProtectionFilter> xssFilterRegistration() {
    FilterRegistrationBean<XssProtectionFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(xssProtectionFilter);
    registrationBean.addUrlPatterns("/*");

    // 특정 URL 제외
    registrationBean.addInitParameter("excludeUrls", "/api/public/*,/health");

    registrationBean.setOrder(1);
    registrationBean.setName("xssProtectionFilter");
    return registrationBean;
}

2. 허용할 HTML 태그 설정

lucy-xss-superset.xml을 수정하여 안전한 HTML 태그를 허용할 수 있습니다:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://www.nhncorp.com/lucy-xss"
        extends="lucy-xss-default.xml">

    <!-- 특정 태그 허용 -->
    <elementRule>
        <element name="p">
            <attributes>
                <attribute name="style"/>
                <attribute name="class"/>
            </attributes>
        </element>
        <element name="br"/>
        <element name="strong"/>
        <element name="em"/>
        <element name="a">
            <attributes>
                <attribute name="href"/>
                <attribute name="target"/>
            </attributes>
        </element>
    </elementRule>
</config>

3. 로깅 레벨 조정

application.properties에서 로깅 레벨을 조정할 수 있습니다:

# XSS 필터 로깅 (DEBUG로 설정 시 모든 필터링 내역 출력)
logging.level.com.lg.common.xss=INFO

# 필터링 전후 값을 확인하려면 DEBUG로 변경
logging.level.com.lg.common.xss=DEBUG

테스트

Windows PowerShell

프로젝트에 포함된 test-xss.ps1 스크립트로 모든 HTTP 메서드를 테스트할 수 있습니다:

.\test-xss.ps1

개별 테스트

# GET 테스트
curl.exe "http://localhost:8080/api/test?name=<script>alert('xss')</script>&message=Hello"

# POST JSON 테스트
curl.exe -X POST http://localhost:8080/api/test -H "Content-Type: application/json" -d "{\"name\":\"<script>alert('xss')</script>\",\"message\":\"Hello\"}"

# POST Form 테스트
curl.exe -X POST http://localhost:8080/api/test/form -d "name=<script>alert('xss')</script>" -d "message=Hello"

# PUT 테스트
curl.exe -X PUT http://localhost:8080/api/test/1 -H "Content-Type: application/json" -d "{\"title\":\"<img src=x onerror=alert(1)>\",\"content\":\"Test\"}"

# DELETE 테스트
curl.exe -X DELETE "http://localhost:8080/api/test/1?reason=<script>alert('xss')</script>"

# PATCH 테스트
curl.exe -X PATCH http://localhost:8080/api/test/1 -H "Content-Type: application/json" -d "{\"status\":\"<svg onload=alert(1)>\"}"

실행 방법 (테스트 프로젝트)

1. Gradle 빌드

./gradlew clean build

2. 애플리케이션 실행

./gradlew bootRun

또는

java -jar build/libs/lucy_filter-0.0.1-SNAPSHOT.jar

3. 접속 확인

  • 기본 포트: http://localhost:8080
  • 헬스 체크: http://localhost:8080/api/test/health

주요 특징 상세 설명

1. 이중 치환 방지

XssRequestWrapper에서 XSS_FILTERED_ATTRIBUTE 속성을 사용하여 이미 필터링된 요청인지 확인합니다:

private static final String XSS_FILTERED_ATTRIBUTE = "XSS_FILTERED";

public XssRequestWrapper(HttpServletRequest request) {
    super(request);
    // 이중 치환 방지를 위한 플래그 설정
    request.setAttribute(XSS_FILTERED_ATTRIBUTE, true);
}

public static boolean isAlreadyFiltered(HttpServletRequest request) {
    return request.getAttribute(XSS_FILTERED_ATTRIBUTE) != null;
}

2. 파일 업로드 제외

Content-Typemultipart/form-data인 경우 필터링을 건너뜁니다:

private boolean isMultipartRequest(HttpServletRequest request) {
    String contentType = request.getContentType();
    if (!StringUtils.hasText(contentType)) {
        return false;
    }
    return contentType.toLowerCase().startsWith(MULTIPART_FORM_DATA);
}

3. JSON Body 필터링

RequestBodyAdvice를 구현하여 JSON Request Body의 모든 String 필드를 필터링합니다:

@ControllerAdvice
public class XssRequestBodyAdvice extends RequestBodyAdviceAdapter {

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                               MethodParameter parameter, Type targetType,
                               Class<? extends HttpMessageConverter<?>> converterType) {

        if (body instanceof Map) {
            filterMap((Map<String, Object>) body);
        } else {
            filterObject(body);
        }
        return body;
    }
}

로그 확인

애플리케이션 실행 시 다음과 같은 로그를 확인할 수 있습니다:

2026-01-23 14:30:00 - XSS Protection Filter initialized successfully
2026-01-23 14:30:00 - XSS Filter test: '<script>alert('xss')</script>' -> '&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;'
2026-01-23 14:30:15 - Applying XSS filter to GET request: /api/test
2026-01-23 14:30:15 - XSS filtered [param: name] '<script>alert('xss')</script>' -> '&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;'
2026-01-23 14:30:20 - Applying XSS filter to POST request: /api/test
2026-01-23 14:30:20 - XSS filtered [JSON field: name] '<script>alert('xss')</script>' -> '&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;'
2026-01-23 14:30:25 - Skipping XSS filter for multipart request: POST /api/test/upload

성능 고려사항

필터링 오버헤드

  • URL 파라미터/Form 데이터: 매우 낮음 (< 1ms)
  • JSON Body: 낮음 (1-5ms, 데이터 크기에 따라 다름)
  • 대용량 요청: JSON body가 큰 경우 필터링 시간 증가 가능

최적화 팁

  1. 특정 URL 제외: 성능이 중요한 API는 필터링에서 제외
  2. 로깅 레벨 조정: 프로덕션 환경에서는 INFO 레벨 사용
  3. 필터 순서: FilterConfig에서 setOrder(1)로 가장 먼저 실행되도록 설정

주의사항

  1. 성능: 모든 요청에 필터링이 적용되므로 대용량 트래픽 환경에서는 성능 테스트 필요
  2. 허용 태그 설정: 비즈니스 요구사항에 맞게 lucy-xss-superset.xml 수정 필요
  3. JSON 응답: 필터링된 데이터는 HTML 엔티티로 변환되어 반환됨
  4. 데이터베이스: 필터링된 데이터가 그대로 저장되므로, 저장 전 디코딩이 필요한 경우 별도 처리 필요

FAQ

Q1. 이미 저장된 데이터에도 적용되나요?

아니요. XSS 필터는 HTTP 요청 시점에만 작동합니다. 이미 데이터베이스에 저장된 데이터는 별도로 처리해야 합니다.

Q2. React/Vue 같은 프론트엔드 프레임워크와 함께 사용해도 되나요?

네, 가능합니다. 프론트엔드 프레임워크의 XSS 방어와 함께 사용하여 다층 방어를 구현할 수 있습니다.

Q3. HTML 엔티티를 원본으로 되돌리려면?

Spring의 HtmlUtils.htmlUnescape() 또는 Apache Commons의 StringEscapeUtils.unescapeHtml4()를 사용하세요.

Q4. 특정 필드만 필터링하거나 제외할 수 있나요?

XssRequestBodyAdvice를 수정하여 특정 필드를 제외하거나, 어노테이션 기반 필터링을 구현할 수 있습니다.

Q5. REST API가 아닌 일반 MVC에서도 작동하나요?

네, Servlet Filter 기반이므로 Spring MVC, Thymeleaf 등 모든 Spring 웹 애플리케이션에서 작동합니다.

기여

이 프로젝트는 오픈소스입니다. 버그 리포트, 기능 제안, Pull Request를 환영합니다.

라이선스

이 프로젝트는 학습 및 참고용으로 제공됩니다. 자유롭게 사용하고 수정할 수 있습니다.

참고 자료


Made with ❤️ for secure Spring Boot applications

About

Lucy XSS Filter for Spring Boot - Complete XSS protection with HTML entity escaping

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors