XSS란
약자를 줄이면 CSS가 맞으나, 우리가 흔히 아는 css와 용어가 겹치므로 XSS라고 정의된 해킹 기법이다.
XSS 처리를 하지 않을 경우 사용자가 코딩지식이 조금이라도 있는 사람이라면 매우 치명적일 수 있다.
예를 들어보자.
다음과 같은 문자열을 넣은 게시글이 있다.
사람들이 이 글을 눌러서 본문을 볼때마다 다음과 같은 메시지를 만날수 있겠다.
먼저, 데이터베이스에 저장되어있는 XSS처리를 해준 내용과 XSS처리를 하지 않은 내용을 비교해서 살펴보자면
첫번째 row가 xss처리를 한부분,
두번째 row가 xss처리를 안한 부분이다.
이처럼 아무런 처리를 하지 않은 상태에서
주소창 혹은 페이지에 <script>alert('XSS공격!!!')</script> 와 같은 코드를 넣는것을 허용하게 되면,
악의적으로 페이지를 망치거나, 세션 및 쿠키 정보 등을 해커가 알 수 있게 되는것이다.
DB에서 불러오는 게시판의 경우 <table> 코드 안에 <table>을 하나 더 넣고 </table> 코드를 입력하지 않을 때,
UI가 깨져보이는것 등 문제가 심각해질 수 있다.
스프링구조에서 이러한 문제를 해결해보자.
최종적으로
< 문자는 <으로
< 문자는 < 문자로
> 문자는 >
> 문자는 > 문자로
" 문자는 " 등
서버단으로 들어갈때, 클라이언트단으로 내보낼때 각각 치환해줄것이다.
물론 컨트롤러로 넘어가기전 서비스단에서 replaceall("<","<"); 이런식으로 처리할 수도 있으나
처리할 양이 방대해지고,
dao를 호출하기 전, dao에서 받아온 값에 대해 처리를 해주어야 하기 때문에 번거로워지고 느려지겠다.
관련 자료를 찾던 중, 심플하게 처리할 수 있는 lucy-xss 라이브러리에 대해 알게되었다.
먼저 pom.xml에 dependency를 추가해주자.
이후 resources에 추가해줘야하는 소스부분은 다음과 같다.
<?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 파라메터는 필터링 되지 않으며 또한 globalPrefixParameter1로
시작하는 파라메터도 필터링 되지 않는다. globalPrefixParameter2는 필터링 되며 globalPrefixParameter3은
필터링 되지 않지만 더 정확한 표현이 가능하므로 globalPrefixParameter2, globalPrefixParameter3과
같은 불분명한 표현은 사용하지 않는 것이 좋다. -->
<params>
<!-- <param name="JSONDS1" useDefender="false" /> -->
</params>
</global>
<!-- url 별 필터링 룰 선언 -->
<url-rule-set>
<!-- url disable이 true이면 지정한 url 내의 모든 파라메터는 필터링 되지 않는다. -->
<!-- <url-rule> -->
<!-- <url disable="true">/eform/Clip.jsp</url> -->
<!-- <url disable="true">/ajax/eformUserData</url> -->
<!-- </url-rule> -->
</url-rule-set>
</config>
lucy-xss-servlet-filter-rule.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://www.nhncorp.com/lucy-xss">
<elementRule>
<element name="p" />
<element name="a" />
<element name="abbr" />
<element name="acronym" />
<element name="adress" />
<element name="applet" />
<element name="area" />
<element name="b" />
<element name="base" />
<element name="basefont" />
<element name="bdo" />
<element name="big" />
<element name="blockquote" />
<element name="body" />
<element name="br" />
<element name="button" />
<element name="caption" />
<element name="center" />
<element name="cite" />
<element name="code" />
<element name="col" />
<element name="colgroup" />
<element name="dd" />
<element name="del" />
<element name="dfn" />
<element name="dir" />
<element name="div" />
<element name="dl" />
<element name="dt" />
<element name="em" />
<element name="embed" />
<element name="fieldset" />
<element name="font" />
<element name="form" />
<element name="frame" />
<element name="frameset" />
<element name="h1" />
<element name="h2" />
<element name="h3" />
<element name="h4" />
<element name="h5" />
<element name="h6" />
<element name="head" />
<element name="hr" />
<element name="html" />
<element name="i" />
<element name="iframe" />
<element name="img" />
<element name="input" />
<element name="ins" />
<element name="isindex" />
<element name="kbd" />
<element name="label" />
<element name="legend" />
<element name="li" />
<element name="link" />
<element name="map" />
<element name="marquee" />
<element name="menu" />
<element name="meta" />
<element name="nobr" />
<element name="noframes" />
<element name="noscript" />
<element name="object" />
<element name="ol" />
<element name="optgroup" />
<element name="option" />
<element name="p" />
<element name="param" />
<element name="pre" />
<element name="q" />
<element name="rt" />
<element name="ruby" />
<element name="s" />
<element name="samp" />
<!-- <element name="script"/> -->
<element name="select" />
<element name="small" />
<element name="span" />
<element name="strike" />
<element name="strong" />
<element name="style" />
<element name="sub" />
<element name="sup" />
<element name="table" />
<element name="tbody" />
<element name="td" />
<element name="textarea" />
<element name="tfoot" />
<element name="th" />
<element name="thead" />
<element name="title" />
<element name="tr" />
<element name="tt" />
<element name="u" />
<element name="ul" />
<element name="var" />
<element name="wbr" />
<element name="xml" />
<element name="xmp" />
<!-- HTML5 added at 2012.04.10 Start -->
<element name="article" />
<element name="aside" />
<element name="audio" />
<element name="bdi" />
<element name="canvas" />
<element name="command" />
<element name="datalist" />
<element name="details" />
<element name="figcaption" />
<element name="figure" />
<element name="footer" />
<element name="header" />
<element name="hgroup" />
<element name="keygen" />
<element name="mark" />
<element name="meter" />
<element name="nav" />
<element name="output" />
<element name="progress" />
<element name="rp" />
<element name="section" />
<element name="source" />
<element name="summary" />
<element name="time" />
<element name="track" />
<element name="video" />
<!-- HTML5 added at 2012.04.10 End -->
<!-- IE핵 처리를 위해 추가 -->
<element name="IEHackExtension" disable="ture">
</element>
</elementRule>
<attributeRule>
<attribute name="src">
<allowedPattern><![CDATA[['"]?\s*http://.*]]></allowedPattern>
</attribute>
<attribute name="href">
<notAllowedPattern><![CDATA[(?i:script)]]></notAllowedPattern>
<notAllowedPattern><![CDATA[(?i:\.css)]]></notAllowedPattern>
</attribute>
<attribute name="style" disable="false" exceptionTagList="a" /> <!-- 2013.12.24 수정 : A 태그는 style 속성에 의한 우회 공격 이슈로 style 속성을 배제힌다. -->
</attributeRule>
</config>
lucy-xss-sax.xml
이렇게 처리를 한다면, input태그로 넘어오는 값이나, 주소창에 입력한 값 등은 변환이 되겠다.
하지만 나는 easyui를 주로 사용하고 있고,
easyui는 대부분 json타입의 데이터를 자바스크립트로 변환해서 사용하는 식이다.
데이터를 json으로 주고 받을때는 lucy로 처리해준 xss방지가 안먹는다는 걸 알게되었고 추가적인 처리를 해줘야했다..
먼저, servlet-context.xml에 다음 코드를 추가한 뒤,
pom.xml에 json 관련 라이브러리를 추가
CharacterEscapes클래스를 상속받은 HTMLCharacterEscapes 클래스를 만들어준다.
import java.util.List;
import org.apache.commons.lang3.text.translate.AggregateTranslator;
import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
import org.apache.commons.lang3.text.translate.EntityArrays;
import org.apache.commons.lang3.text.translate.LookupTranslator;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HTMLCharacterEscapes extends CharacterEscapes {
private final int[] asciiEscapes;
private final CharSequenceTranslator translator;
public HTMLCharacterEscapes() {
// 1. XSS 방지 처리할 특수 문자 지정
asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['\"'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['('] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes[')'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['#'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
// 2. XSS 방지 처리 특수 문자 인코딩 값 지정
translator = new AggregateTranslator(new LookupTranslator(EntityArrays.BASIC_ESCAPE()), // <, >, &, " 는 여기에 포함됨
new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()),
new LookupTranslator(EntityArrays.HTML40_EXTENDED_ESCAPE()),
// 여기에서 커스터마이징 가능
new LookupTranslator(
new String[][] {
{ "(", "(" },
{ ")", ")" },
{ "#", "#" },
{ "\'", "'" }
}));
}
@Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
@Override
public SerializableString getEscapeSequence(int ch) {
return new SerializedString(translator.translate(Character.toString((char) ch)));
// 참고 - 커스터마이징이 필요없다면 아래와 같이 Apache Commons Lang3에서 제공하는 메서드를 써도 된다.
// return new
// SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char)
// ch)));
}
@Bean
public WebMvcConfigurer controlTowerWebConfigurerAdapter() {
return new WebMvcConfigurer() {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// super.configureMessageConverters(converters);
// 5. WebMvcConfigurerAdapter에 MessageConverter 추가
converters.add(htmlEscapingConveter());
}
private HttpMessageConverter<?> htmlEscapingConveter() {
ObjectMapper objectMapper = new ObjectMapper();
// 3. ObjectMapper에 특수 문자 처리 기능 적용
objectMapper.getFactory().setCharacterEscapes(new HTMLCharacterEscapes());
// 4. MessageConverter에 ObjectMapper 설정
MappingJackson2HttpMessageConverter htmlEscapingConverter = new MappingJackson2HttpMessageConverter();
htmlEscapingConverter.setObjectMapper(objectMapper);
return htmlEscapingConverter;
}
};
}
}
HTMLCharacterEscapes 클래스를 사용하는 HtmlEscapingObjectMapperFactory 클래스를 추가하면 끝.
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.beans.factory.FactoryBean;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HtmlEscapingObjectMapperFactory implements FactoryBean {
private final ObjectMapper objectMapper;
public HtmlEscapingObjectMapperFactory() {
objectMapper = new ObjectMapper();
objectMapper.getFactory().setCharacterEscapes(new HTMLCharacterEscapes());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public ObjectMapper getObject() throws Exception {
return objectMapper;
}
@Override
public Class<?> getObjectType() {
return ObjectMapper.class;
}
@Override
public boolean isSingleton() {
return true;
}
public static class HTMLCharacterEscapes extends CharacterEscapes {
private final int[] asciiEscapes;
public HTMLCharacterEscapes() {
// start with set of characters known to require escaping (double-quote,
// backslash etc)
asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
// and force escaping of a few others:
asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
}
@Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
// and this for others; we don't need anything special here
@Override
public SerializableString getEscapeSequence(int ch) {
return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));
}
}
}
ObjectMapper에서 json 변환이 되는것이다.
ObjectMapper에 대한 사용법 설명 참고
https://mkyong.com/java/how-to-convert-java-object-to-from-json-jackson/
이상, 스프링에서 json 포함하여 xss공격 방지를 해보았다.
'SpringFramework' 카테고리의 다른 글
Maven 오프라인 설정 (1) | 2020.02.17 |
---|