XSS / 방지 필터 적용[Spring]

스브코·2021년 11월 20일
0

사이트 간 스크립팅(XSS: Cross-Site Scripting)

웹 어플리케이션에서 많이 나타나는 취약점의 하나로, 웹사이트 관리자가 아닌 유저들이 악성 스크립트를 삽입할 수 있는 취약점이다.

ex)

이런식으로 alert창이 나오게 글을 입력을 하기도 하고 버튼을 삽입하기도 함.

대응방안 :

  1. script문장에 존재한 특수문자를 메타캐릭터로 변환시킨다.
 		 < ---- &lt

                > ---- &gt
                (  ---- &#40

                )  ---- &#41

               #  ---- &#35

               &  ---- &#38
  1. Contents 구문 사용

  2. Replace 구문 사용

뭐 이런식으로 대응을 하는 방법이 있다고 하네요.

네이버의 lucy-xss-filter를 적용해보았다.

뭐 시키는 가이드 라인데로 적용을 시켜보았다.

의존성 주입

    implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.0'

lucy-xss-servlet-filter-rule.xml 생성(깃헙에 있는거 복사함)

<?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="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 별 필터링 룰 선언 -->
    <url-rule-set>

        <!-- url disable이 true이면 지정한 url 내의 모든 파라메터는 필터링 되지 않는다. -->
        <url-rule>
            <url disable="true">/disableUrl1.do</url>
        </url-rule>

        <!-- url disable이 false인 설정은 기본이기 때문에 불필요하다. 아래와 같은 불필요한 설정은 하지 않는다.-->
        <url-rule>
            <url disable="false">/disableUrl2.do</url>
        </url-rule>

        <!-- url disable이 true이면 지정한 url 내의 모든 파라메터가 필터링 되지 않기 때문에 <params> 로 선언한 설정은 적용되지 않는다.
               아래와 같은 불필요한 설정은 하지 않는다. -->
        <url-rule>
            <url disable="true">/disableUrl3.do</url>
            <params>
                <param name="query" useDefender="false" />
                <param name="prefix1" usePrefix="true" />
                <param name="prefix2" usePrefix="false" useDefender="false" />
                <param name="prefix3" usePrefix="true" useDefender="true" />
                <param name="prefix4" usePrefix="true" useDefender="false" />
                <param name="prefix5" usePrefix="false" useDefender="true" />
            </params>
        </url-rule>

        <!-- url disable이 false인 설정은 기본이기 때문에 불필요하다. <params> 선언한 설정은 적용이 된다.-->
        <url-rule>
            <url disable="false">/disableUrl4.do</url>
            <params>
                <!-- disableUrl4.do 의 query 파라메터와 prefix4로 시작하는 파라메터들은 필터링 되지 않는다.
                        usePrefix가 false, useDefender가 true인 설정은 기본이기 때문에 불필요하다. -->
                <param name="query" useDefender="false" />
                <param name="prefix1" usePrefix="true" />
                <param name="prefix2" usePrefix="false" useDefender="false" />
                <param name="prefix3" usePrefix="true" useDefender="true" />
                <param name="prefix4" usePrefix="true" useDefender="false" />
                <param name="prefix5" usePrefix="false" useDefender="true" />
                <param name="prefix6" usePrefix="true">
                    <defender>xssSaxFilterDefender</defender>
                </param>
            </params>
        </url-rule>

        <!-- url1 내의 url1Parameter는 필터링 되지 않으며 또한 url1PrefixParameter로 시작하는 파라메터도 필터링 되지 않는다. globalParameter는
                상위 글로벌 global 설정에 동일한 이름으로 되어있지만 url-rule 설정을 더 우선하여 따른다. -->
        <url-rule>
            <url>/url1.do</url>
            <params>
                <param name="url1Parameter" useDefender="false" />
                <param name="url1PrefixParameter" usePrefix="true" useDefender="false" />
                <param name="globalParameter"/>
            </params>
        </url-rule>

        <!-- url2 내의 url2Parameter1만 필터링 되지 않으며 url2Parameter2는 xssSaxFilterDefender를 사용해 필터링 한다.  -->
        <url-rule>
            <url>/url2.do</url>
            <params>
                <param name="url2Parameter1" useDefender="false">
                    <defender>xssPreventerDefender</defender>
                </param>
                <param name="url2Parameter2">
                    <defender>xssSaxFilterDefender</defender>
                </param>
                <param name="url2Parameter3">
                    <defender>xssPreventerDefender</defender>
                </param>
            </params>
        </url-rule>
    </url-rule-set>
</config>

빈 생성 후 적용

public class MyblogApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyblogApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean xssEscapeServletFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new XssEscapeServletFilter());
        registrationBean.setOrder(1);  // @Order로 처리.
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }

이렇게 하고 돌렸는데 뭐 Post로 해서 글쓰기 할때 xss 공격을 해봤는데
에잉...? 안되는데 싶어서 다시 찾아봤다...

https://jojoldu.tistory.com/470 여기 블로그 내용을 보니

lucy 필터는 form data 전송 방식엔 유효하지만, @RequestBody로 전달되는 JSON 요청은 처리해주지 않는다

이런 내용이 있어서 JSON API에 XSS 필터를 적용해보았다.

의존성 주입

implementation 'org.apache.commons:commons-text:1.8'

escape할 character를 지정해주는 클래스 생성

package com.sparta.myblog;

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() {
        // 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;
    }

    @Override
    public int[] getEscapeCodesForAscii() {
        return asciiEscapes;
    }

    @Override
    public SerializableString getEscapeSequence(int ch) {
        return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));
    }
}

configuration 클래스 생성

package com.sparta.myblog;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

@Slf4j
@RequiredArgsConstructor
@Configuration
public class WebMvcConfig {

    private final ObjectMapper objectMapper;

    @Bean
    public MappingJackson2HttpMessageConverter jsonEscapeConverter() {
        ObjectMapper copy = objectMapper.copy();
        copy.getFactory().setCharacterEscapes(new com.sparta.myblog.HTMLCharacterEscapes());
        return new MappingJackson2HttpMessageConverter(copy);
    }
}

잘 적용이 되네요.!

profile
익히는 속도가 까먹는 속도를 추월하는 그날까지...

0개의 댓글