2016년 10월 18일 화요일

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

Spring 4로 간단한 개인 블로그 툴을 만들고 있다…

CKEditor source 모드에서 저렇게 하고 저장을 누르니

이런…

Cross-Site Scripting 처리를 해줘야 하는데 예전 직장에 있었을 때는 <script>&lt;script&gt; 따위로 바꿔주는 메서드를 만들어서 인자값을 받아 insert 혹은 update 수행하는 비즈니스 로직에 일일히 집어넣어서 처리했었으나… 그건 좀 단순무식한 방법 같고 이번에는 필터로 구현해보기로 한다.

우선 검색을 해보니 네이버에서 만든 lucy-xss-filter 라는 라이브러리가 꽤 유용한 듯하다.

이 라이브러리를 적용해서 HttpServletRequest 객체를 통해 받아오는 인자들을 Escape처리 하는 것으로 한다.

HttpServletRequest 객체에서 parameter 값들을 가져오는 것은 가능한데 이 값들을 바꿔서 동일한 key에 다시 셋팅하려면 어떻게 해야 하는가?

이럴 때는 ServletRequest 인터페이스를 참조하는 javax.servlet.http.HttpServletRequestWrapper 클래스를 사용하면 된다.

정확히는 HttpServletRequestWrapper 클래스의 확장 클래스를 생성하고… getParameter, getParameterValues, getHeader 등 값을 가져오는 메서드를 오버라이드하여 원 데이터에 필터를 적용한 값을 리턴하도록 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 == nullreturn 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 == nullreturn null
        return doFilter(value, getPreventerFlag(name)); 
    } 
    
    @Override 
    public String getHeader(String name) { 
        String value = super.getHeader(name); 
        if(value == nullreturn 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 == nullreturn true
        return !name.toLowerCase().startsWith("content")?true:false
    } 
}
cs

우선 lucy-xss-filter 라이브러리의 퀵 가이드를 참고하여 인자값을 Escape처리하여 그 결과값을 리턴하는 doFilter 라는 메서드를 만든다.

이 라이브러리는 크게 Lucy-XSS Filter와 Lucy-XSS Preventer 두 가지 기능을 제공하는데 단순 문자열에 대해서는 Preventer를 적용하여 전체 값을 바꿔주고 글 본문과 같이 HTML태그의 기능이 적용되어야 하는 내용은 Filter를 사용하여 스크립트 실행 같은 것들만 선별적으로 필터링되도록 구현해야 한다.

그래서 getPreventerFlag 메서드로 parameter명이 ‘content’로 시작하는 녀석은 HTML태그가 적용되어야 하는 것으로 간주하여 false와 true를 리턴하도록 하고 이 flag에 따라 Preventer 또는 Filter가 적용되도록 했다. ‘content’로 박아넣은 하드 코딩이 좀 마음에 안들지만…

어차피 내 개인 프로젝트니 넘어간다.

그리고 XSS 처리한 값을 리턴해줘야 할 필요성이 있는 getParameter 같은 메서드들을 오버라이딩하여 doFilter 메서드를 이용해 Escape된 값이 리턴되도록 구현한다. (여기에서는 getParameter, getParameterValues, getHeader에 적용)

Lucy-XSS Filter는 XML파일로 정의된 필터링 정책에 의거하여 구동되는데 XML파일의 설정에 대해서는 레퍼런스 가이드를 참고한다. 내 경우는 lucy-xss-superset.xml, white-url.xml 파일과 lucy-xss.xml 파일을 lucy-xss-config.xml라는 이름으로 바꾸어 이 3개 파일을 루트 디렉터리에 넣고 소스 상에서 lucy-xss-config.xml 파일을 읽어들이도록 했다. XML 파일 경로가 잘못되었거나 없으면 라이브러리 내 기본 설정을 읽어들이는데 필터링 레벨이 좀 낮다.

그리고 필터 클래스를 만들어 줌. Spring을 사용하기 때문에 Spring의 OncePerRequestFilter 클래스를 상속받아 만든다. HTTP method가 GET이나 POST일 경우에만 XSS Filter가 적용되도록 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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); 
        } 
    } 
}
 
cs

만든 필터 클래스를 web.xml에 아래와 같이 추가하고,

서버 리부트 후 스크립트를 포함하여 테스트 입력하면 아래와 같이 필터링되어 스크립트가 동작하지 않고 잘 들어가 있는 것을 확인할 수 있다.

Kotlin, SpringBoot 3, GraalVM 환경에서 Native Image로 컴파일하여 애플리케이션 실행

Spring Boot 3부터, GraalVM Native Image를 공식 지원하여 애플리케이션의 시작 속도와 메모리 사용량을 크게 줄일 수 있다. Native Image란 기존의 JVM 기반 위에서 돌아가는 Java 애플리케이션과는 달리 JVM 없이...