개발일지(일간)

23년 03월 11일

move2 2023. 3. 11. 23:45

배포 후 xss에 대한 대비를 해 주었다.

xss란 사이트 간 스크립팅, 크로스 사이트 스크립팅(영어: Cross-site scripting XSS[*])은 웹 애플리케이션에서 많이 나타나는 취약점의 하나로 웹사이트 관리자가 아닌 이가 웹 페이지에 악성 스크립트를 삽입할 수 있다.

만든 사이트가 기본적인 스크립트에 대해서 방비가 하나도 되어있지 않기때문에 설정을 해 주었다.

메인페이지에서 상품들을 페이징해서 띄우는데, 만약 상품이름에 <script>alert(1);</script>란 스크립트가 들어가 있으면 메인페이지만 띄워도 저렇게 알럿이 뜨게 된다. 

찾아보니 lucy-xss-servlet-filter를 사용해서 막을 수 있는 방법이 있다는데 먹히지 않았다.

이유는lucy-xss-servlet-filter가 form-data 방식에만 유효하고, json으로 넘어가는 데이터에 대해서는 필터링 해주지 않기 때문이다. 때문에, 스프링부트의 @RequestBody,@ResponseBody를 통해 값을 주고받는 우리 프로젝트에는 적합하지 않았다.

때문에 HttpMessageConverter를 사용해서 Jackson 같은 Mapper를 통해 JSON 문자열로 Response에 담겨질때 Mapper가  XSS 방지 처리를 해주도록 하는 방식을 적용하기로 했다.

httpMessageConverter는 Bean으로 등록시 자동으로 메세지 컨버터 목록에 추가 되기때문에 bean으로 등록해주어야 한다.

코드는 다음과 같다.

@Slf4j
@RequiredArgsConstructor
@Configuration
public class WebMvcConfig {

    private final ObjectMapper objectMapper;

    @Bean
    public MappingJackson2HttpMessageConverter jsonEscapeConverter() {
        ObjectMapper copy = objectMapper.copy();
        copy.getFactory().setCharacterEscapes(new HtmlCharacterEscapes());
        return new MappingJackson2HttpMessageConverter(copy);
    }
}

 

결국, json에서 xss처리를 하고 싶다면, Jackson의 com.fasterxml.jackson.core.io.CharacterEscapes를 상속하는 클래스 A를 직접 만들어서 처리해야 할 특수문자를 지정하고, ObjectMapper에 A를 설정하고, ObjectMapper를 MessageConverter에 등록해서 Response가 클라이언트에 나가기 전에 MessageConverter가 xss방지 처리를 해주게 해야한다.

클래스 A는 다음과 같이 생성했다.

package com.example.townmarket.common.config;

import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;
import org.apache.commons.lang3.StringEscapeUtils;

public class HtmlCharacterEscapes extends CharacterEscapes {

  private final int[] asciiEscapes;

  public HtmlCharacterEscapes() {

    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)));
  }
}

 

적용후 스크립트가 실행되지 않는 모습을 볼 수 있다.