2023. 8. 17. 18:45ㆍ코딩/Web
XSS는 두가지가 있습니다.
1) Reflected XSS : URL 파라미터(주로 GET)에 스크립트를 넣어 서버 저장 없이 스크립트 실행
2) Stored XSS : 게시글, 댓글 등 XSS 스크립트가 서버 DB에 저장되어, 해당 게시글/댓글 보여질때마다 스크립트 실행
이 글에서는 백앤드 REST API에서의 Stored XSS를 막는 XSS Filter를 적용하는 법과
Spring Security를 통해 Reflected XSS를 막는 헤더를 추가하는 법을 작성합니다.
최종 코드만 바로 적용하고 싶으신 분들은 아래 내용만 복사하셔서 프로젝트에 적용하시면 됩니다.
1번의 gradle과 xml코드
2번의 HtmlCharacterEscapes와 XssConfig 클래스 Java 코드
다른 글의 XSS Filter 소스를 적용하였지만
JSON에 대한 XSS FIlter가 적용되지 않아 이 글을 보러 오신 분들은
2번의 XssConfig 클래스의 configureMessageConverters 메서드를 추가해주시면 됩니다.
해당 코드들에 동작원리에 대한 부분은 최하단의 참고자료들을 보시면 더 정확하게 이해하실 수 있습니다.
1. Lucy 필터 적용
Lucy 필터는 네이버에서 개발된 XSS Filter입니다.
XML 설정만으로 XSS 방어가 가능해지고, 비즈니스 레이어의 코드 수정 없이 XSS 필터를 적용할 수 있습니다.
GitHub - naver/lucy-xss-servlet-filter
Gradle)
implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1'
implementation 'org.apache.commons:commons-text:1.10.0' // 밑에 JSON 필터에 쓰임
XML 코드)
resources 하단에 lucy-xss-servlet-filter-rule.xml 파일을 생성한 후 아래 내용을 작성하면 됩니다.
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://www.navercorp.com/lucy-xss-servlet">
<defenders>
<defender>
<name>xssPreventerDefender</name>
<class>com.navercorp.lucy.security.xss.servletfilter.defender.XssPreventerDefender</class>
</defender>
<defender>
<name>xssSaxFilterDefender</name>
<class>com.navercorp.lucy.security.xss.servletfilter.defender.XssSaxFilterDefender</class>
<init-param>
<param-value>lucy-xss-superset-sax.xml</param-value>
<param-value>false</param-value>
</init-param>
</defender>
<defender>
<name>xssFilterDefender</name>
<class>com.navercorp.lucy.security.xss.servletfilter.defender.XssFilterDefender</class>
<init-param>
<param-value>lucy-xss.xml</param-value>
<param-value>false</param-value>
</init-param>
</defender>
</defenders>
<default>
<defender>xssPreventerDefender</defender>
</default>
<global>
<params>
<param name="globalParameter" useDefender="false" />
<param name="globalPrefixParameter1" usePrefix="true" useDefender="false" />
<param name="globalPrefixParameter2" usePrefix="true" />
<param name="globalPrefixParameter3" usePrefix="false" useDefender="false" />
</params>
</global>
<url-rule-set>
<!-- <url-rule>
<url disable="true">/login/login/loginAjax</url>
</url-rule> -->
</url-rule-set>
</config>
실제 적용을 위해 XssConfig.java 파일을 만들어주고, 필터를 적용합니다.
import com.navercorp.lucy.security.xss.servletfilter.XssEscapeServletFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class XssConfig implements WebMvcConfigurer {
// lucy 필터 적용
@Bean
public FilterRegistrationBean<XssEscapeServletFilter> filterRegistrationBean() {
final FilterRegistrationBean<XssEscapeServletFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setFilter(new XssEscapeServletFilter());
filterRegistration.setOrder(1);
filterRegistration.addUrlPatterns("/*"); //filter를 거칠 url patterns
return filterRegistration;
}
}
이렇게 하면 String 등 일반적인 자료형은 XSS Filter가 적용됩니다.
다만 흔히 REST에서 사용되는 JSON 파라미터로 들어오는 값은 필터가 안되어서
해당 부분에 대해 추가로 XSS Filter를 작성해야합니다.
2. JSON 파라미터 XSS Filter 작성
XSS 방지 처리할 특수문자들을 지정하는 HtmlCharacterEscapes 클래스를 추가해줍니다.
추가로 그냥 사용할 시, 이모지가 필터링 될 수 있으므로 이모지 관련 필터 예외해주는 코드가 추가됩니다
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;
import org.apache.commons.text.StringEscapeUtils;
public class HtmlCharacterEscapes extends CharacterEscapes {
private final int[] asciiEscapes;
public HtmlCharacterEscapes() {
// 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;
}
@Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
// Emoji 처리를 위한 필터 예외처리 코드 추가 적용
@Override
public SerializableString getEscapeSequence(int ch) {
SerializedString serializedString = null;
char charAt = (char) ch;
//emoji jackson parse 오류에 따른 예외 처리
if (Character.isHighSurrogate(charAt) || Character.isLowSurrogate(charAt)) {
StringBuilder sb = new StringBuilder();
sb.append("\\u");
sb.append(String.format("%04x",ch));
serializedString = new SerializedString(sb.toString());
} else {
serializedString = new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString(charAt)));
}
return serializedString;
}
}
위 HtmlCharacterEscapes 클래스를 아까 작성한 XssConfig에 추가로 적용합니다.
스프링의 경우 Jackson을 이용하여 JSON 파라미터를 파싱하므로
Jackson에 대한 HttpMessageConverter에 방금 만든 특수문자 필터를 추가합니다.
그리고 configureMessageConverters 메서드로
위에 특수문자 필터를 추가한 jsonEscapeConverter를 컨버터에 추가해줍니다.
이 부분이 추가되지 않으면 실제 JSON 파라미터에 필터링이 적용되지 않을 수 있습니다.
(아래 참고자료들을 보면 해당 부분이 빠져있는 경우가 많습니다. 제가 이 글을 쓴 이유이기도 합니다 ㅠ)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.navercorp.lucy.security.xss.servletfilter.XssEscapeServletFilter;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class XssConfig implements WebMvcConfigurer {
private final ObjectMapper objectMapper;
// 방금 만든 JSON 파라미터용 필터를 실제 MessageConverters에 적용!
// 해당 코드가 없으면 JSON 파라미터 필터링이 적용되지 않음
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jsonEscapeConverter());
}
// lucy 필터 적용
@Bean
public FilterRegistrationBean<XssEscapeServletFilter> filterRegistrationBean() {
final FilterRegistrationBean<XssEscapeServletFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setFilter(new XssEscapeServletFilter());
filterRegistration.setOrder(1);
filterRegistration.addUrlPatterns("/*"); //filter를 거칠 url patterns
return filterRegistration;
}
// JSON 파라미터 필터
@Bean
public MappingJackson2HttpMessageConverter jsonEscapeConverter() {
ObjectMapper copy = objectMapper.copy();
copy.getFactory().setCharacterEscapes(new HtmlCharacterEscapes());
return new MappingJackson2HttpMessageConverter(copy);
}
}
이후 실제 서버에서 테스트를 해보면
앞의 HtmlCharacterEscapes 클래스에 작성해둔 특수문자들이 < 등으로 변경되는 것을 확인하실 수 있습니다.
3. Spring Security를 통한 Xss 필터링 헤더 추가
사실 이 부분은 굳이 안해도 되긴 하는데
스프링 시큐리티에 해당 기능이 있어서 추가합니다.
스프링 시큐리티의 config에서 Xss Protection이라는 기능이 있습니다.
뭔가해서 확인해봤는데, 위의 stored Xss에 관련된 것은 아니고
X-XSS-Protection 이라는 헤더를 추가해줘서 Reflected XSS 를 막는다고 합니다.
다만 해당 헤더가 standard가 아니라서 모든 사용자에게 동작하지는 않는다고 합니다.
Mdn Docs 설명 : X-XSS-Protection - HTTP | MDN (mozilla.org)
스프링 시큐리티 Config 파일에서 아래 헤더를 추가해주면 됩니다.
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers()
.xssProtection()
.and()
.contentSecurityPolicy("script-src 'self'");
return http.build();
}
}
참고자료)
lucy필터로 xss 방어하기(feat JSON, time, 이모지) (tistory.com)
Spring에서 JSON에 XSS 방지 처리 하기 - 뒤태지존의 끄적거림 (homoefficio.github.io)
Spring Boot에서 JSON API에 XSS Filter 적용하기 (tistory.com)
GitHub - Spring-Boot-Course/Spring-Boot-XSS-Protection: XSS Protection at JSON
Prevent Cross-Site Scripting (XSS) in a Spring Application | Baeldung
'코딩 > Web' 카테고리의 다른 글
크롬 안전하지 않은 정보를 제출하려 함 에러 문제와 임시해결책 (0) | 2021.03.19 |
---|