XSS 테스트

Jung·2021년 12월 14일
0

TIL

목록 보기
64/77
post-thumbnail

댓글 input에 script문을 넣으면 어떻게 될까?라는 생각이 들었다. 그래서 바로 테스트해보았다.

역시는 역시다. script문이 아주 잘 적용된다!

<script>alert('hi')</script>

위 코드를 input에 넣고 저장을 했을 때 <, > 와 같은 특수문자들이 그대로 전송이 되어서 그렇다.
이런 XSS 이슈를 막아놓지 않는다면 보안상 위험하기 때문에 막아야한다!

혹시 몰라서 html 태그들도 넣어봤지만 아주 잘 적용된다! 🤣

다른 팀들 프로젝트에도 해봤는데 적용이 잘 되었다. 다들 모르고 계셔서 알려드렸다. 뿌듯하다. ㅎㅎ

XSS(cross-site scripting)는 웹 애플리케이션에서 많이 나타나는 취약점의 하나로 웹사이트 관리자가 아닌 이가 웹 페이지에 악성 스크립트를 삽입할 수 있는 취약점이다. 주로 여러 사용자가 보게 되는 전자 게시판에 악성 스크립트가 담긴 글을 올리는 형태로 이루어진다. 이 취약점은 웹 애플리케이션이 사용자로부터 입력 받은 값을 제대로 검사하지 않고 사용할 경우 나타난다. 이 취약점으로 해커가 사용자의 정보(쿠키, 세션 등)를 탈취하거나, 자동으로 비정상적인 기능을 수행하게 할 수 있다. 주로 다른 웹사이트와 정보를 교환하는 식으로 작동하므로 사이트 간 스크립팅이라고 한다.

참고
https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85

스프링에서 XSS를 해결하기 위한 방법이 있을까 찾아보던 도중 json response 결과로 특정 문자를 Escape 처리되게 하는 방법을 발견했다.

1. build.gradle의 dependencies에 라이브러리를 추가한다.

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

Apache Commons Text 라이브러리는 문자열에서 작동하는 알고리즘에 중점을 둔 라이브러리다. 이 라이브러리의 최신 안정 릴리스는 1.9 버전이므로 1.9 버전을 사용한다.

2. HTMLCharacterEscapes 클래스 생성한다.

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() {
        // 유니코드의 처음 128자(ASCII 문자)에 대한 이스케이프 처리를 결정하기 위해 호출
        return asciiEscapes;
    }

    @Override
    public SerializableString getEscapeSequence(int ch) {
        // 특정 문자에 사용할 이스케이프 시퀀스를 결정하기 위해 호출
        return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));
    }
}

3. WebMvcConfigurer 인터페이스 상속 받아 MappingJackson2HttpMessageConverter를 재정의한다.

여기서 잠깐! 😎

https://www.baeldung.com/spring-httpmessageconverter-rest 에 나온 말에 의하면

If we're using Spring Boot we can avoid implementing the WebMvcConfigurer and adding all the Message Converters manually as we did above.

We can just define different HttpMessageConverter beans in the context, and Spring Boot will add them automatically to the autoconfiguration that it creates:

Spring Boot를 사용하면 HttpMessageConverter를 커스터마이징할 때WebMvcConfigurer 인터페이스구현(implement) 할 필요가 없다고 한다.

MappingJackson2HttpMessageConverter 위로 계속 가다 보면 HttpMessageConverter가 있다. MappingJackson2HttpMessageConverterHttpMessageConverter의 자자자자식이다.

즉, Spring Boot는 Jackson 라이브러리를 포함하고 있어서 자동으로 MappingJackson2HttpMessageConverter를 사용하여 JSON으로 변환한다. Spring Boot를 사용하면 MappingJackson2HttpMessageConverter를 커스터마이징 할 때 WebMvcConfigurerimplement 할 필요가 없다. 해당 bean을 선언만 해주면 된다.

@Bean
public MappingJackson2HttpMessageConverter jsonEscapeConverter() {
    // MappingJackson2HttpMessageConverter Default ObjectMapper 설정 및 ObjectMapper Config 설정
    ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
    objectMapper.getFactory().setCharacterEscapes(new HtmlCharacterEscapes());
    return new MappingJackson2HttpMessageConverter(objectMapper);
}

이처럼 해당 Bean 등록만 해줘도 된다고 한다.

이모지 에러 😮‍💨

로컬에서 테스트 했을 때는 이모지가 없어서 정상적으로 실행이 됐다. 하지만 로컬에서 테스트한 부분을 실제 배포된 서비스에 적용하려고 했을 때 에러가 발생했다. 배포된 서비스에는 이모지가 사용되어서 커스터마이징 된 MappingJackson2HttpMessageConverter에 오류가 있었던 것 같다.

HTMLCharacterEscapes 클래스의 getEscapeSequence(int ch) 메소드를 아래와 같이 수정하면 된다.

@Override
public SerializableString getEscapeSequence(int ch) {
    SerializedString serializedString;
    char charAt = (char) ch;
    if (Character.isHighSurrogate(charAt) || Character.isLowSurrogate(charAt)) {
        StringBuilder sb = new StringBuilder();
        sb.append("\\u"); // \u 다음은 유니코드로 인식됨
        sb.append(String.format("%04x", ch)); // 16진수를 4자리로 표현, 4자리 아닐 시 0으로 채움
        serializedString = new SerializedString(sb.toString());
    } else {
        serializedString = new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString(charAt)));
    }
    return serializedString;
}

참고
https://inseok9068.github.io/springboot/springboot-xss-response/

https://medium.com/@dltkdals2202/spring-boot-%EC%97%90%EC%84%9C-json-%ED%83%80%EC%9E%85-xss-prevention-e9ce7b02c05b

https://www.adobe.io/experience-manager/reference-materials/6-5/javadoc/com/fasterxml/jackson/core/JsonpCharacterEscapes.html

https://www.baeldung.com/spring-httpmessageconverter-rest

profile
97kim.github.io

0개의 댓글