xss 보안 설정에 네이버에서 제공하는 lucy 를 사용하고 자 한다.
lucy 는 두가 지가 있는데 , 전체를 적용하는 lucy-xss-servlet-filter 와
일일 개별적으로 적용하는 lucy-xss-servlet-filter 가 있다.
네이버에서 설명하는 내용이다.
신규로 개발하는 서비스에는 lucy-xss-servlet-filter를 사용하는 것을 추천하지만, 기존 잘 운영되는 시스템에 lucy-xss-servlet-filter를 사용하는 것은 추천하지 않습니다. 입력 파라메터가 전부 필터링 되기 때문에 서비스가 잘 동작하지 않는 의도치 않은 결과가 발생할 수 있기 때문입니다.
기존 시스템에 적용하시려면 lucy-xss-filter를 사용해 개발자가 일일히 필터링 하는 방식을 추천드립니다. 하지만 이 방법은 개발자의 집중력를 믿어야 하기 때문에 언젠가는 뚫릴 수 밖에 없는 방식입니다. 그렇기 때문에 시간을 충분히 확보하실 수 있으시거나 기존에 서블릿 필터 기반으로 XSS 공격을 방어하셨다면 이번 기회에 lucy-xss-servlet-filter를 적용해 XSS 공격에서 해방되시는 걸 추천드립니다.
https://github.com/naver/lucy-xss-filter
https://github.com/naver/lucy-xss-servlet-filter
문제는 에디터에서 적용할 경우 예외를 해 줘야 한다.
Lucy-XSS Filter, Lucy-XSS Preventer 선택 기준
XSS Filter는 보안에 중점을 두면서도, HTML 태그 또한 정상 동작하도록 하는 White List 방식의 XSS 공격 방어 라이브러리이다.
XSS Preventer는 파라미터로 받은 문자열을 Escape(<→< >→> "→" '→') 하는 XSS공격 방어 라이브러리이다. 즉 HTML이 아닌 단순 텍스트 파라미터에 대해서는 XSS Preventer를 사용해 전체를 Escaping 하는 것이 올바른 대응 방법이고
게시판, 메일, 방명록 등 HTML 태그 기능이 필요한 서비스는 XSS Filter를 사용해 필터링 하는 것이 효과적인 방법이므로 개발자는 두 가지 상황을 고려해 방어 라이브러리를 사용해야 한다.
마지막으로 사용자 입력데이터가 뷰에 다시 노출시킬 목적이 아닌 Business Logic에만 쓰이는 데이터일 경우에는 둘 다 사용하지 말아야 한다. 불필요한 eacape/unescape이 발생해 원본데이터가 훼손될 수 있다.
빠른 사용법 가이드
링크 => Quick Start Guide
자세한 내용은 위 링크를 보면 될 것이다.
방법
내가 사용한 방법은 두가지 다 사용하는 것이다.
특히, editor 를 사용시에는 필터가 적용되면 안 된다. 왜냐하면은 에디터내에서 필터기능을 하기 때문에
xss 필터를 적용시
<pre>
<script>alert("테스트");</script>
</pre>
이와 같이 출력되어 나타나서 보기가 안 좋다.
따라서, 루시 서블릿 필터로 전체 적용을 하고 editor 영역만 컨트롤에서 다음과 같이
unescape () 처리 하는 코드만 작성해 주자. 그러면 깨끗하게 처리되어 진다.
XssPreventer.unescape("");
ex) vo.setMessage(XssPreventer.unescape(vo.getMessage()));
1. 라이브러리를 의존성 주입을 하자.
<!-- lucy xss 설치 --> <dependency> <groupId>com.navercorp.lucy</groupId> <artifactId>lucy-xss</artifactId> <version>1.6.3</version> </dependency> <!-- lucy-xss-servlet --> <dependency> <groupId>com.navercorp.lucy</groupId> <artifactId>lucy-xss-servlet</artifactId> <version>2.0.0</version> </dependency>
2. web.xml 에 다음과 같이 설정하자.
참조 : https://github.com/naver/lucy-xss-servlet-filter
<!-- 한글 처리를 위한 인코딩 필터 --> <!-- xssEscapeServletFilter는 CharacterEncodingFilter 뒤에 위치해야 한다. --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <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>
3. resources / lucy-xss-servlet-filter-rule.xml 카피해서 복사
아래 내용을 카피해서 복사해서 넣자.
servelt-filter 사용시 여기 내용을 변경 하면 되는데,
나는 이것보다 editor 사용시
XssPreventer.unescape(""); 적용이 간편한것 같다.
<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://www.navercorp.com/lucy-xss-servlet"> <defenders> <!-- XssPreventer 등록 --> <defender> <name>xssPreventerDefender</name> <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssPreventerDefender</class> </defender> <!-- XssSaxFilter 등록 --> <defender> <name>xssSaxFilterDefender</name> <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssSaxFilterDefender</class> <init-param> <param-value>lucy-xss-sax.xml</param-value> <!-- lucy-xss-filter의 sax용 설정파일 --> <param-value>false</param-value> <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 --> </init-param> </defender> <!-- XssFilter 등록 --> <defender> <name>xssFilterDefender</name> <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssFilterDefender</class> <init-param> <param-value>lucy-xss.xml</param-value> <!-- lucy-xss-filter의 dom용 설정파일 --> <param-value>false</param-value> <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 --> </init-param> </defender> </defenders> <!-- default defender 선언, 별다른 defender 선언이 없으면 default defender를 사용해 필터링 한다. --> <default> <defender>xssPreventerDefender</defender> </default> <!-- global 필터링 룰 선언 --> <global> <!-- 모든 url에서 들어오는 globalParameter 파라메터는 필터링 되지 않으며 또한 globalPrefixParameter로 시작하는 파라메터도 필터링 되지 않는다. --> <params> <param name="globalParameter" useDefender="false" /> <param name="globalPrefixParameter" usePrefix="true" useDefender="false" /> </params> </global> <!-- url 별 필터링 룰 선언 --> <url-rule-set> <!-- url disable이 true이면 지정한 url 내의 모든 파라메터는 필터링 되지 않는다. --> <url-rule> <url disable="true">/disableUrl1.do</url> </url-rule> <!-- url1 내의 url1Parameter는 필터링 되지 않으며 또한 url1PrefixParameter로 시작하는 파라메터도 필터링 되지 않는다. --> <url-rule> <url>/url1.do</url> <params> <param name="url1Parameter" useDefender="false" /> <param name="url1PrefixParameter" usePrefix="true" useDefender="false" /> </params> </url-rule> <!-- url2 내의 url2Parameter1만 필터링 되지 않으며 url2Parameter2는 xssSaxFilterDefender를 사용해 필터링 한다. --> <url-rule> <url>/url2.do</url> <params> <param name="url2Parameter1" useDefender="false" /> <param name="url2Parameter2"> <defender>xssSaxFilterDefender</defender> </param> </params> </url-rule> </url-rule-set> </config>
설정은 다 되었다. 다음 내용은 테스트 이다.
중요한것은 글을 작성시 editor 사용하는 경우이다. mybatis 로 sql insert 되기전에
XssPreventer.unescape() 처리를 해주자. 나는 컨트롤 영역에서 해주었다.
4. 테스트 1
lucy-xss-filter 설정 되었는지 테스트 영역에서
다음과 같은 코드로 테스트를 실행 해 보자. 아래 내용을 복사 하자.
TestXss
package net.macaronics.web.xss; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.nhncorp.lucy.security.xss.XssFilter; import com.nhncorp.lucy.security.xss.XssPreventer; import com.nhncorp.lucy.security.xss.XssSaxFilter; import junit.framework.Assert; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"}) public class TestXss { @SuppressWarnings("deprecation") @Test public void testXssPreventer() { String dirty = "\"><script>alert('xss');</script>"; String clean = XssPreventer.escape(dirty); Assert.assertEquals(clean, ""><script>alert('xss');</script>"); Assert.assertEquals(dirty, XssPreventer.unescape(clean)); } @Test public void pairQuoteCheckOtherCase() { XssFilter filter = XssFilter.getInstance("lucy-xss-superset.xml"); String dirty = "<img src=\"<img src=1\\ onerror=alert(1234)>\" onerror=\"alert('XSS')\">"; String expected = "<img src=\"\"><!-- Not Allowed Attribute Filtered ( onerror=alert(1234)) --><img src=1\\>\" onerror=\"alert('XSS')\">"; String clean = filter.doFilter(dirty); Assert.assertEquals(expected, clean); dirty = "<img src='<img src=1\\ onerror=alert(1234)>\" onerror=\"alert('XSS')\">"; expected = "<img src=''><!-- Not Allowed Attribute Filtered ( onerror=alert(1234)) --><img src=1\\>\" onerror=\"alert('XSS')\">"; clean = filter.doFilter(dirty); Assert.assertEquals(expected, clean); } @Test public void testSuperSetFix() { XssSaxFilter filter = XssSaxFilter.getInstance("lucy-xss-superset-sax.xml"); String clean = "<TABLE class=\"NHN_Layout_Main\" style=\"TABLE-LAYOUT: fixed\" cellSpacing=\"0\" cellPadding=\"0\" width=\"743\">" + "</TABLE>" + "<SPAN style=\"COLOR: #66cc99\"></SPAN>"; String filtered = filter.doFilter(clean); Assert.assertEquals(clean, filtered); } }
실제로 데이터가 DB 에 어떻게 적용되는지 그리고 어떻게 출력되는 지 테스트를 해보자.
자세한 내용은
home pc 에서 노트북으로 변경 으로
나의 깃허브의 test02 프로젝트를 복사해서 test 프로젝트에서 개발 하였다.
https://github.com/braverokmc79/spring_simple_blog
SQL
create table tbl_message( mid int not null auto_increment, targetid varchar(50) not null, sender varchar(50) not null, message text not null, opendate timestamp, senddate timestamp not null default now(), primary key(mid) );
messageMapper.xml
<insert id="create"> insert into tbl_message (targetid, sender, message) values (#{targetid} , #{sender}, #{message}) </insert>
XssTestController
package test.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.nhncorp.lucy.security.xss.XssPreventer; import test.domain.MessageVO; import test.service.MessageService; @Controller public class XssTestController { @Autowired private MessageService service; @RequestMapping(value="xssform",method=RequestMethod.GET) public String xssTest() throws Exception{ return "test/xss"; } @RequestMapping(value="xssform", method=RequestMethod.POST) public String xssTes2(Model model, MessageVO vo) throws Exception{ vo.setMessage(XssPreventer.unescape(vo.getMessage())); service.create(vo); model.addAttribute("message", service.listMessage()); return "test/xssresult"; } }
글 작성시 에 POST
vo.setMessage(XssPreventer.unescape(vo.getMessage()));
코드를 볼 수 있다. editor 를 적용한 message 에 만 예외처리를 한것이다.
xss.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Macaronics.net</title> <!-- 합쳐지고 최소화된 최신 CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> <!-- 부가적인 테마 --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css"> <!-- 합쳐지고 최소화된 최신 자바스크립트 --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> <script src="//cdn.ckeditor.com/4.7.3/standard/ckeditor.js"></script> </head> <body> <h1 class="text-center">XSS Test</h1> <div class="row"> <div class="col-xl-2 col-sm-2"></div> <div class="col-xl-8 col-sm-8"> <form action="xssform" method="post"> <table class="table"> <tr> <td>targetid</td> <td><input type="text" class="form-control" value="" name="targetid"></td> </tr> <tr> <td>내용</td> <td class="span12"><textarea rows="5" class="form-control" name="message"></textarea> <!-- textarea 를 ckeditor 로 변경 시킴 --> </tr> <tr> <td>sender</td> <td><input type="text" class="form-control" value="admin" name="sender"></td> </tr> <tr> <td colspan="2"><input type="submit" value="전송"></td> </tr> </table> </form> </div> </div> <script> CKEDITOR.replace("message", { filebrowserUploadUrl : "/ckeditorupload3" // filebrowserImageUploadUrl: 'MacaronicsServlet?command=ckeditor_upload' }); </script> </body> </html>
xssresult.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Macaronics.net</title> <!-- 합쳐지고 최소화된 최신 CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> <!-- 부가적인 테마 --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css"> <!-- 합쳐지고 최소화된 최신 자바스크립트 --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> </head> <body> <h1 class="text-center">XSS Test</h1> <div class="row"> <div class="col-xl-2 col-sm-2"></div> <div class="col-xl-8 col-sm-8"> <c:forEach items="${message }" var="ms"> <table class="table"> <tr> <td>아이디</td> <td>${ms.mid }</td> </tr> <tr> <td>targetid</td> <td>${ms.targetid}</td> </tr> <tr> <td>내용</td> <td class="span12">${ms.message}</td> </tr> <tr> <td>sender</td> <td>${ms.sender}</td> </tr> <tr> <td colspan="2"><a href="xssform" >다시 테스트</a></td> </tr> </table> </c:forEach> </div> </div> </body> </html>
테스트한 결과 이미지 이다.
제작 : macaronics.net - Developer Jun Ho Choi
소스 : 소스가 필요한 분은 비밀 댓글로 작성해 주세요.
댓글 ( 2)
댓글 남기기