Blog / Java/Kotlin / Filter를 사용한 XSS(Cross-Site Scripting) 처리

Filter를 사용한 XSS(Cross-Site Scripting) 처리

Spring 4로 개인 블로그 툴을 만들던 중, CKEditor source 모드에서 스크립트를 입력하고 저장했더니 XSS가 그대로 실행되는 문제가 발생했다.

CKEditor에서 스크립트 입력 화면
CKEditor에서 스크립트 입력 화면

XSS 실행 결과 화면
XSS 실행 결과 화면

이런...😱

예전에는 <script>&lt;script&gt; 로 바꿔주는 메서드를 만들어 비즈니스 로직마다 일일이 집어넣었으나, 이번에는 Filter로 일괄 처리하기로 한다.


1. 사용 라이브러리

네이버에서 만든 lucy-xss-filter 라이브러리를 적용하여 HttpServletRequest를 통해 받아오는 인자들을 Escape 처리한다.


2. HttpServletRequestWrapper 확장 클래스 구현

HttpServletRequest의 파라미터 값을 가져온 뒤 Escape 처리한 값을 동일한 key로 리턴하기 위해 HttpServletRequestWrapper 를 상속한 확장 클래스를 생성한다.

getParameter, getParameterValues, getHeader 등 값을 가져오는 메서드를 오버라이드하여 필터 적용 결과를 리턴하도록 구현한다.

java
import com.nhncorp.lucy.security.xss.XssFilter;
import com.nhncorp.lucy.security.xss.XssPreventer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Arrays;
 
public class RequestWrapperForXssFiltering extends HttpServletRequestWrapper {
 
    public RequestWrapperForXssFiltering(HttpServletRequest httpServletRequest) {
        super(httpServletRequest);
    }
 
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) return null;
        boolean doPreventer = getPreventerFlag(name);
        return Arrays.stream(values)
            .map(v -> doFilter(v, doPreventer))
            .toArray(String[]::new);
    }
 
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (value == null) return null;
        return doFilter(value, getPreventerFlag(name));
    }
 
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (value == null) return null;
        return doFilter(value, true);
    }
 
    private String doFilter(String value, boolean doPreventer) {
        if (doPreventer) {
            return XssPreventer.escape(value);
        } else {
            XssFilter xssFilter = XssFilter.getInstance("lucy-xss-config.xml");
            return xssFilter.doFilter(value);
        }
    }
 
    private Boolean getPreventerFlag(String name) {
        if (name == null) return true;
        return !name.toLowerCase().startsWith("content");
    }
}
Lucy-XSS 라이브러리의 두 가지 기능
기능 적용 대상 동작
XssPreventer 단순 문자열 파라미터 전체 값을 Escape 처리
XssFilter HTML 태그가 필요한 본문(content*) 스크립트 실행 등만 선별적으로 필터링

getPreventerFlag 메서드에서 파라미터명이 content로 시작하는 경우를 HTML 본문으로 간주하여 XssFilter를 적용하고, 그 외에는 XssPreventer를 적용한다.

참고: content로 하드코딩한 부분은 개인 프로젝트이므로 일단 넘어간다. 😅


3. XssFilter 설정 파일

Lucy-XSS Filter는 XML 파일로 정의된 필터링 정책에 의거하여 구동된다.

  • lucy-xss-superset.xml, white-url.xml, lucy-xss.xml 파일을 lucy-xss-config.xml로 통합하여 루트 디렉터리에 배치
  • XML 파일 경로가 잘못되거나 없으면 라이브러리 내 기본 설정(필터링 레벨 낮음)을 읽어들임

lucy-xss-config.xml 설정 파일 구성
lucy-xss-config.xml 설정 파일 구성


4. Filter 클래스 구현

Spring의 OncePerRequestFilter를 상속받아 Filter 클래스를 구현한다.
HTTP method가 GET 또는 POST일 경우에만 XSS Filter가 적용되도록 한다.

java
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
public class CrossSiteScriptingFilter extends OncePerRequestFilter {
 
    @Override
    protected void doFilterInternal(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            FilterChain filterChain) throws ServletException, IOException {
 
        if (httpServletRequest.getMethod().equals("GET")
                || httpServletRequest.getMethod().equals("POST")) {
            filterChain.doFilter(
                new RequestWrapperForXssFiltering(httpServletRequest),
                httpServletResponse
            );
        } else {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    }
}

5. web.xml에 Filter 등록

만든 Filter 클래스를 web.xml에 추가한다.

web.xml 필터 등록 화면
web.xml 필터 등록 화면


6. 결과 확인

서버 재기동 후 스크립트를 포함하여 테스트 입력하면 아래와 같이 스크립트가 Escape 처리되어 동작하지 않고 DB에 정상적으로 저장된다.

XSS 필터링 결과 확인
XSS 필터링 결과 확인

Written by
author
풍우래기

여행을 좋아하는 집돌이 개발자입니다.

블로그에 새로운 글이 발행되었습니다.