Back/Spring Java

웹 보안 XSS(Cross Site Scripting) 취약점 대응

밍꿔 2023. 6. 25. 21:41


반응형

XSS(Cross Site Scripting)란?

 - 웹페이지에 스크립트 코드를 삽입하여 의도하지 않은 명령을 실행시키거나 쿠키, 세션등을 탈 취 할 수 있는 취약점이다.

 

웹 보안 취약점 대응 방법은 보통 lucy filter와 같은 라이브러리를 적용하며, 추가로 필터로 대응되지 않는 

케이스들에 대한 예외처리를 추가로 진행한다. (lucy filter의 request body, 에디터HTML 데이터)

 

 


Lucy XSS Filter 적용

 - 웹어플리케이션으로 들어오는 모든 요청 파라미터에 대해 기본적인 XSS 방어 필터링을 수행한다.

 - 화이트 리스트 방식의 필터.

 

 

maven pom.xml

<dependency>
    <groupId>com.navercorp.lucy</groupId>
    <artifactId>lucy-xss-servlet</artifactId>
    <version>2.0.1</version>
</dependency>

 

web.xml

<filter>
    <filter-name>xssEscapeServletFilter</filter-name>
    <filter-class>com.navercorp.lucy.security.xss.servletfilter.XssEscapeServletFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>xssEscapeServletFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>XSS</filter-name>
    <filter-class>com.diquest.common.web.CrossScriptingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>XSS</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

CrossScriptingFilter.java

import java.io.IOException;
import java.util.Collection;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CrossScriptingFilter implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void destroy() {
    }

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

}

 

RequestWrapper.java

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.nhncorp.lucy.security.xss.XssFilter;

public class RequestWrapper extends HttpServletRequestWrapper {
  private byte[] b;

  public RequestWrapper(HttpServletRequest servletRequest) throws IOException {
    super(servletRequest);
    XssFilter filter = XssFilter.getInstance("lucy-xss-sax.xml");
    b = new String(filter.doFilter(getBody(servletRequest))).getBytes("UTF-8");
  }

  @Override
  public ServletInputStream getInputStream() throws IOException {
    final ByteArrayInputStream bis = new ByteArrayInputStream(b);
    return new ServletInputStreamImpl(bis);
  }

  class ServletInputStreamImpl extends ServletInputStream {
    private InputStream is;
    public ServletInputStreamImpl(InputStream bis) {
      is = bis;
    }
    @Override
    public int read() throws IOException {
      return is.read();
    }
    @Override
    public int read(byte[] b) throws IOException {
      return is.read(b);
    }
  }

  public static String getBody(HttpServletRequest request) throws IOException {
    String body = null;
    String bodyRes = null;
    BufferedReader br = null;
    StringBuilder sb = new StringBuilder();
    try {
      InputStream inputStream = request.getInputStream();
      if (inputStream != null) {
        br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        char[] charBuffer = new char[128];
        int bytesRead = -1;
        while ((bytesRead = br.read(charBuffer)) > 0) {
          sb.append(charBuffer, 0, bytesRead);
        }

      } else {
        sb.append("");
      }
    } catch (IOException ex) {
      throw ex;
    } finally {
      if (br != null) {
        try {
          br.close();
        } catch (IOException ex) {
          throw ex;
        }
      }
    }
    body = sb.toString();
    return body;
  }
}

 

lucy-xss.xml 설정 파일

 - 보통은 resource폴더 하위에 알맞게 위치시키며, 설정 파일은 아래의 첨부파일 참고.

 

 


추가 예외케이스

 - 에디터 사용 시, 내용은 html형태로 저장된다. 단순히 html entity 변환하는것에 초첨을 맞추다보면,

img태그나 기타 에디터 기능들이 동작을 안하는 경우가 발생한다.
때문에, 스크립트 공격이 있을 수 있는 케이스를 블랙리스트로 추가로 관리한다.

public static String[] eventArray = {"onclick", "ondblclick", "onmouseover", "onmouseout",
      "onchange", "onmousedown", "onmouseenter", "onmouseleave", "onmousemove", "onmouseup",
      "onfocus", "onfocusin", "onfocusout", "ondrag", "ondragleave", "ondragenter", "ondragover",
      "ondragdrop", "ondragstart", "ondragend", "onload", "onsubmit", "onunload", "onmousewheel",
      "onkeyup", "onkeypress", "onkeydown", "onpointercancel", "onpointerdown", "onpointerenter",
      "onpointerleave", "onpointermove", "onpointerout", "onpointerover", "onpointerup",
      "onpointerup", "onscroll", "onresize", "onselect", "onwheel", "oninput", "onblur",
      "onauxclick", "onreset", "confirm", "alert", "console.log", "onerror", "prompt",
      "oncontextmenu", "innerHTML", "eval", "onactive", "ondataavailable", "oncut", "onafterupdate",
      "onbeforeactivate", "onbeforedeactivate", "onbeforecut", "onbounce", "onbeforecopy",
      "ondblclick", "ondeactivate", "ondatasetchanged", "onbeforeprint", "onbeforepaste",
      "onbeforeupload", "onselectsatrt", "onpaste", "onpropertychange", "ondrop",
      "ondatasetcomplete", "onmove", "oncellchange", "onfinish", "onstop", "onlayoutcomplete",
      "onrowexit", "onbefore", "onstart", "onrowinserted", "onrowdelete", "onfilterchange",
      "oncontrolselected", "onlosecapture", "onrowenter", "onhelp", "onreadystatechange",
      "onrepeat", "onerrorupdate", "onselectionchange"};
          
public static String checkScriptEvent(String s){
    for(String eventStr : eventArray){
        if(s.contains(eventStr)) {
            s = s.replaceAll(eventStr + "=\".*?\"|" + eventStr + "='.*?'|" + eventStr + "=&quot;.*?&quot;|" + eventStr + "=.*?|" + eventStr, "");
            for (String dtEventStr : eventArray) {
                if (s.contains(dtEventStr) && eventStr.equals(dtEventStr)) {
                    s = checkScriptEvent(s);
                }
            }
        }
    }
    return s;
}

 

checkScriptEvent에서 재귀형태로 호출 하는 이유는 블랙리스트함수를 반복해서 사용할때도 제외하기 위함.

ex) ononerrorerror, onononscrollscrollscroll

 

 

lucy-xss.xml
0.00MB
lucy-xss-sax.xml
0.00MB
lucy-xss-servlet-filter-rule.xml
0.00MB
lucy-xss-superset.xml
0.00MB
lucy-xss-superset-sax.xml
0.00MB

반응형