NAVER DEVELOPERS에서 개발한 XSS 필터인 lucy-xss-servlet-filter
를 분석해보고자 한다.
분석하는 이유는 Naver Bugbounty에서 XSS를 찾아보려고 했으나, 실패했기 때문이다..
+) 추가 : 분석한 내용을 활용해서 XSS 성공했다!!! goood!
기존의 lucy-xss-filter
가 아래와 같은 한계를 가지기 때문에 개발하게 되었다고 함.
lucy-xss-servlet-filter
는 자바 서블릿 필터 기반의 라이브러리이며, 아래와 같은 장점이 있다고 함.
자바 서블릿? (출처)
서블릿(servlet)은 서버에서 웹페이지 등을 동적으로 생성하거나 데이터 처리를 수행하기 위해 자바로 작성된 프로그램이다. 클라이언트 요청을 처리하고 그 결과를 다시 클라이언트에게 전송하는 servlet 클래스의 구현 규칙을 지킨 자바프로그램이다. servlet은 Java 코드 안에 HTML 태그가 삽입되며, .java가 확장자이다.
이에 대해 HTML을 코딩하기가 불편하다는 단점을 극복하기 위해 HTML 내부에 Java 코드를 삽입하는 형식이 JSP이다. 서블릿의 단점을 보완하고자 만든 '서버 스크립트 기술'인 것이다.
실제 사용하는 형태(MVC 모델)에서는, Servelt과 JSP가 각각의 장점을 살려서 사용되는 것을 볼 수 있다. JSP는 사용자에게 결과를 보여주는 View 부분을, Servelt은 사용자의 요청을 받아 분석하고 처리한 후 다시 응답하는 Controller 부분을 담당한다.
자바 서블릿 필터? (출처)
서블릿 필터는 HTTP 요청과 응답을 변경할 수 있는 재사용 가능한 형태의 서블릿 코드이다. 필터는 객체의 형태로 존재하며 클라이언트로부터 오는 요청(request)과 최종 자원(서블릿/JSP/기타 문서) 사이에 위치하여 클라이언트의 요청 정보를 알맞게 변경할 수 있으며, 또한 필터는 최종 자원과 클라이언트로 가는 응답(response) 사이에 위치하여 최종 자원의 요청 결과를 알맞게 변경할 수 있다. 이를 그림으로 표현하면 다음과 같다.
여러 개의 필터가 모여 하나의 체인(chain)을 형성할 수도 있다. 이 경우에 첫 번째 필터가 변경하는 요청 정보는 클라이언트의 요청 정보가 되지만, 체인의 두 번째 필터가 변경하는 요청 정보는 첫 번째 필터를 통해서 변경된 요청 정보가 된다. 즉, 요청 정보는 변경에 변경에 변경을 거듭하게 되는 것이다.
우리가 분석하려는 lucy-xss-servlet-filter
필터도 클라이언트와 서버자원(.nhn
) 사이에서 사용자의 입력을 필터링해서 전달하는 역할을 한다.
Filter
인터페이스 (출처)
필터를 구현하는 데 있어 핵심적인 인터페이스 및 클래스는 총 3개, javax.servlet.Filter
, javax.servlet.ServletRequestWrapper
, javax.servlet.ServletResponseWrapper
이다.
그 중에서도 Filter
인터페이스는 사용자가 필터의 목적에 맞게 직접 구현해야 하는 인터페이스이다. ServletRequestWrapper
클래스와 SerlvetResponseWrapper
클래스는 각각 요청, 응답을 변경한 결과를 저장할 래퍼 클래스를 나타낸다.
Filter
인터페이스에는 다음과 같은 메소드가 선언되어 있다.
public void init(FilterConfig filterConfig) throws ServletException
// 필터를 웹 콘테이너내에 생성한 후 초기화할 때 호출한다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException {
chain.doFilter(request, response);
}
// 1. request 파라미터를 이용하여 요청값 필터 작업 수행.
// 2. 체인의 다음 필터 처리
// 3. response를 이용하여 응답값 필터링 작업 수행
public void destroy()
// 필터가 웹 콘테이너에서 삭제될 때 호출된다. 필터가 사용한 자원을 반납.
doFilter()
메소드의 세 번째 파라미터는 FilterChain
객체인데, 이는 클라이언트가 요청한 자원에 이르기까지 클라이언트의 요청이 거쳐가게 되는 필터 체인을 나타낸다.
FilterChain
을 사용함으로써 필터는 체인에 있는 다음 필터에 변경한 요청과 응답을 건내줄 수 있게 된다. 요청을 필터링한 객체가 또 다시 응답을 필터링하게 되는데, 위 그림을 참고하면 그 순서는 반대가 됨을 알 수 있다.
SAX
, DOM
차이
XML을 파싱하는 파서에서의 차이라던데, 자세한 원리는 다음에 알아보는 것으로 하자. lucy-xss-servlet-filter
에서는 두 개가 서로 구분된다.
lucy-xss-filter 과의 연관성
XssPreventerDefender
, XssSaxFilterDefender
, XssFilterDefender
3개의 필터가 존재한다. 위 세 가지 클래스는 lucy-xss-filter
의 api를 호출한다.
Lucy-Xss-Servlet-Filter → lucy-xss-filter
XssPreventerDefender
→ XssPreventer
XssSaxFilterDefender
→ XssSaxFilter
XssFilterDefender
→ XssFilter
결국 lucy-xss-filter
을 가져다 쓰기 때문에, 저것을 분석해야 한다는 뜻이다!
XssFilter
(DOM, SAX) (구분되어 있지만 동작 원리는 비슷한 듯함..)
XSS 공격이 가능한 HTML 요소를 신뢰할 수 있는 코드로 변환하거나 삭제한다. 공격이 가능하지 않은 HTML 요소는 허용한다. 화이트리스트 방식을 기반으로 필터링한다.
XssPreventer
모든 입력 문자열을 단순히 바꾸어 HTML 태그로 인식될 수 없도록 한다.
경험상 대부분의 페이지에서 이것을 사용하는 듯 하다.
< → <
> → >
" → "
' → '
XssFilter
vs XssPreventer
짧은 문자열 파라미터 같은 경우에는 XssPreventer
을 쓰는 것이 좋다고 한다. 그러나 HTML 태그를 input으로 받아야 할 경우에는 XssFilter
를 사용하는 것이 좋다고 한다.
XssFilter
Rule
elementRule
: element 필터링 규칙에 없는 요소는 필터링 대상이 됨. (화이트리스트!!)
element
: 하나의 요소에 대한 필터링 규칙.
disable
: name에 설정된 요소를 elementRule 에서 제거한다. (제거 → 필터링 적용)endTag
: End Tag의 존재 유무. (엔드 태그가 존재할 때에만 화이트리스트 적용)attributeRule
: 속성에 대한 필터링 규칙
Base64Decoding
: 속성 값에 필터링 규칙을 적용하기 전에 base64 디코딩 수행 여부notAllowedPattern
: 속성 값으로 허용되지 않는 정규 표현식을 입력한다. 패턴에 해당되는 모든 속성 값은 필터링 된다. allowedPattern
이 동시에 정의되어 있을 경우에는, not .. 을 기본 적용한 후 allowed .. 로 예외처리할 수 있다.lucy-xss-superset.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- version 20180725-1 -->
<config xmlns="http://www.nhncorp.com/lucy-xss" extends="lucy-xss-default.xml">
<elementRule>
<element name="body" disable="true" /> <!-- <BODY ONLOAD=alert("XSS")>, <BODY BACKGROUND="javascript:alert('XSS')"> -->
<element name="embed" disable="true" />
<element name="iframe" disable="true" /> <!-- <IFRAME SRC=”http://hacker-site.com/xss.html”> -->
<element name="meta" disable="true" />
<element name="object" disable="true" />
<element name="script" disable="true" /> <!-- <SCRIPT> alert(“XSS”); </SCRIPT> -->
<element name="style" disable="true" />
<element name="link" disable="true" />
<element name="base" disable="true" />
<element name="button" endTag="true">
<attributes>
<ref name="Attrs"/>
<ref name="name"/>
<ref name="value"/>
<ref name="type"/>
<ref name="disabled"/>
<ref name="tabindex"/>
<ref name="accesskey"/>
<ref name="Html5GlobalAttr"/>
<ref name="autofocus"/>
<ref name="form"/>
<ref name="formenctype"/>
<ref name="formmethod"/>
<ref name="formnovalidate"/>
<ref name="formtarget"/>
</attributes>
</element>
</elementRule>
<attributeRule>
<attribute name="data" base64Decoding="true">
<notAllowedPattern><![CDATA[(?i:s\\*c\\*r\\*i\\*p\\*t\\*:)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[(?i:d\\*a\\*t\\*a\\*:)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
</attribute>
<attribute name="src" base64Decoding="true">
<notAllowedPattern><![CDATA[(?i:s\\*c\\*r\\*i\\*p\\*t\\*:)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[(?i:d\\*a\\*t\\*a\\*:)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
</attribute>
<attribute name="style">
<notAllowedPattern><![CDATA[(?i:j\\*a\\*v\\*a\\*s\\*c\\*r\\*i\\*p\\*t\\*:)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[(?i:e\\*x\\*p\\*r\\*e\\*s\\*s\\*i\\*o\\*n)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
</attribute>
<attribute name="href">
<notAllowedPattern><![CDATA[(?i:j\\*a\\*v\\*a\\*s\\*c\\*r\\*i\\*p\\*t\\*:)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
</attribute>
</attributeRule>
</config>
lucy-xss-superset.xml 이 우선 적용되며, 포함되지 않은 내용에 대해서는 lucy-xss-default.xml이 적용된다. 여기에 포함된 element와 attribute만이 사용될 수 있다. (화이트리스트 방식)
NAVER Bugbounty 부분은 비공개.