서블릿 필터를 활용해 XSS 공격 방어하기

심야·2023년 8월 7일
0

웹 개발

목록 보기
45/46
post-thumbnail

XSS

기존 코드에서는 게시물을 읽을 경우 XSS 공격으로 쿠키를 탈취할 수 있다.

공격을 방어하기 위해 서블릿 필터로 HTML 엔티티 인코딩을 적용하고 특정 문자열은 블랙리스트 필터링을 이용해 치환하겠다. 블랙리스트 필터링은 모든 xss 공격을 방어할 수 없다. 우회 기법이 존재하며 이 글에서는 학습 목적으로 사용할 뿐임을 명심하자.

Servlet Filter

내 게시판은 확인 결과, stored xss 취약점만 존재한다.
우선, 두 가지 방법으로 XSS를 대응할 수 있다.
1. 클라이언트에서 글을 작성하면 DB에 넣기 전 서버 측에서 파라미터를 검증한다.
2. DB에서 데이터를 가져온 다음, 서버에서 클라이언트에 응답하기 전에 데이터를 검증한다.

두 방식 모두 장단점이 있다.
1번의 경우, DB에 데이터를 넣는 모든 코드에서 파라미터를 검증해야 하는데 규모가 클수록 검증해야 하는 파라미터가 많다.
2번의 경우도 마찬가지다. 클라이언트에 응답하는 모든 코드에서 데이터를 검증해야 하는데 규모가 클수록 개발자가 검증하는 코드를 추가하지 못할 수도 있다.

그러나 다행히도 서블릿 필터를 사용하면 두 방식의 단점을 해소할 수 있다.

Servlet Filter

서블릿 필터가 궁금하면 아래 글을 참고하자.
간단하게 설명하자면, Client로부터 Server로 HTTP 요청이 들어오기 전에 서블릿을 거쳐서 필터링 하는 것을 서블릿 필터라고 한다.

Servlet Filter란?

Code

public class XSSFilter implements Filter{
    public FilterConfig filterConfig;

    public void init(FilterConfig filterConfig) throws SecurityException {
        this.filterConfig = filterConfig;
    }

    public void destroy(){
        this.filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new RequestWrapper((HttpServletRequest) request), response);
    }
}

XSS Filter를 구현하고 cleanXSS 메서드로 <, >, ", ' 특수문자를 치환한다. 그리고 eval, javascript:, script, iframe, embed 문자열이 포함되었다면 빈문자열로 치환한다.

public final class RequestWrapper extends HttpServletRequestWrapper {

    public RequestWrapper(HttpServletRequest servletRequest) {
        super(servletRequest);
    }

    public String[] getParameterValues(String parameter) {

        String[] values = super.getParameterValues(parameter);
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = cleanXSS(values[i]);
        }
        return encodedValues;
    }

    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        if (value == null) {
            return null;
        }
        return cleanXSS(value);
    }

    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (value == null)
            return null;
        return cleanXSS(value);

    }

    private String cleanXSS(String value) {
        String returnVal = value;
        returnVal = returnVal.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        returnVal = returnVal.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");
        returnVal = returnVal.replaceAll("'", "&#39;");
        returnVal = returnVal.replaceAll("eval\\((.*)\\)", "");
        returnVal = returnVal.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        returnVal = returnVal.replaceAll("script", "");
        returnVal = returnVal.replaceAll("iframe", "");
        returnVal = returnVal.replaceAll("embed", "");
        return returnVal;
    }
}

필터를 적용하려면 web.xml에 설정을 추가해야 한다.

  <filter>
    <filter-name>XSS</filter-name>
    <filter-class>util.XSSFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>XSS</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

필터가 XSS 공격을 정상적으로 필터링하고 있는지 확인하겠다.

필터링 결과

script 문자열이 제거되었고 꺽쇠와 작은 따옴표 그리고 괄호 모두 HTML 엔티티 인코딩이 적용되었다.

필터링 과정

이 글에서는 content 파라미터를 필터링 하는 경우만 작성했지만 title 파라미터, id 파라미터 뿐를 포함한 모든 HTTP 요청과 응답을 필터링한다.
단, JSON 데이터는 검사하지 않는다.

  1. 꺽쇠가 존재하면 HTML 엔티티 인코딩을 적용한다.

  2. 괄호가 존재하면 HTML 엔티티 인코딩을 적용한다.

  3. 작은 따옴표가 존재하면 HTML 엔티티 인코딩을 적용한다.

  4. script 문자열이 존재하면 빈 문자열로 치환한다.

라이브러리

이 글에서는 학습 목적을 위해서 직접 서블릿 필터를 추가했다. 하지만 현업에서는 네이버에서 만든 lucy-xss-servlet-filter와 같은 XSS 라이브러리를 사용하는 것을 추천한다.

주의할 점

  1. web.xml의 filter-class 경로를 설정할 때 java 디렉터리의 하위 경로부터 인식하기 때문에 패키지 경로가 package util이라면 바로 utll.XSSFilter로 지정하면 된다.
  2. 내 톰캣이랑 메이븐이 이상한 것 같은데.. war 파일을 clean 명령어로 제거한 후, 컴파일과 패키지 명령어로 war 파일을 생성한다. 생성이 완료되면 기존의 war 파일은 제거하고 톰캣을 재부팅 해 war 파일을 배치한다.

참고

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=adamdoha&logNo=221665607853
https://sowon-dev.github.io/2022/04/20/220420XSS/

profile
하루하루 성실하게, 인생 전체는 되는대로.

0개의 댓글