Spring Boot에서 json을 위한 XSS Filter 적용하기

옛날 Spring을 처음 접하면서 엄청 놀랐었다. “와.. 간단한 설정이면 웹앱 개발 뚝딱이네..”
최근 Spring Boot를 이용하여 개발을 하면서 많은 것을 느꼈다. “이제 개발할 건 비즈니스 로직 뿐이네..”
그러나 최근 XSS 를 방어하기 위한 설정을 하다가 또 한 번 놀랐다.
“Spring에 XSS 방어할 설정이 없다고?”

Filter와 Interceptor

  • XSS를 방어하기 위해 제일 처음 떠오른 것은 역시 Filter와 Interceptor
  • 오픈 소스가 있지 않을까?
    • Lucy XSS Filter (링크)
      • Naver에서 blog나 cafe 서비스를 하면서 겪었을 고충들을 느낄 수 있음
      • 잘 만들었지만, 한글 임에도 불구하고 설명이 좀 복잡
      • Request param 또는 form-data로 전달 된 경우만 filtering
      • RequestBody를 통해 raw data로 보내는 것은 잡지 못함 (2019년 3월 기준)
  • 결국 모두가 비슷한 생각을 함

Message Conveter

Message Conveter를 적용

  • 위에서 제안된 Message Conveter를 WebMvcConfigurer에 적용
    • WebMvcConfigurerAdapter는 Spring Boot 2.0 이후 deprecated 됨
      • WebMvcConfigurer에 configureMessageConverters를 override 해서 사용
    • Adapter를 사용하지 않기 때문인지 configureMessageConverters에 기본적으로 default converters가 전달 됨
      • 참고: configureMessageConverters는 default converters를 사용하지 않고, 사용자가 입력한 것만 쓰게만드는 method
      • 이지만, WebMvcConfigurer에서 받을 땐, 이미 default converters가 생성되어 있음
      • 즉, 우리가 override할 MappingJackson2HttpMessageConverter가 이미 포함되어 있음
    • @EnableWebMvc annotation을 WebMvcConfigurer에 적용하면, 현재 정의한 MessageConverters로 기존 설정을 override함
      • 단, 이 경우 default converter들이 없어지므로 별도로 설정 필요
  • (주의!) MappingJackson2HttpMessageConverter 이 여러개 일 경우 내가 추가한 것이 선택되지 않을 수 있음
    • 그러므로 application/json으로 선택되는 converter를 덮어 써야 함
    • 기존것을 수정할 순 없고, 찾아서 지우고, 새로운 것을 add해야 함
  • 수정된 코드
      @Override
      public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    // Replace MessageConverter from default WebMvcConfigurer
    Iterator<HttpMessageConverter<?>> converterIterator = converters.iterator();
    while(converterIterator.hasNext()) {
      // Do NOT add new one, MUST replace
      HttpMessageConverter converter = converterIterator.next();
      if (converter.getSupportedMediaTypes().contains(MediaType.APPLICATION_JSON))
        converterIterator.remove();
    }
    converters.add(createCustomHtmlEscapingConveter());
      }
    
  • ObjectMapper 추가 설정
    • Default Converter 는 기본적으로 존재하지 않는 property는 무시하고 mapping을 해줌
    • 그런데 그냥 new ObjectMapper()로 생성해버리면, 존재하지 않는 property 전달 시 에러 발생
      • https://stackoverflow.com/a/12730655/8350542
      • FAIL_ON_UNKNOWN_PROPERTIES 를 false로 설정

결과

  • RequestBody annotation엔 적용이 안됨(확인 필요)
  • ResponseBody annotation엔 적용 성공!
      // Request
      { 
        "data": "hello <script>alert('xss attack');</script>" 
      }
          
      // Response
      {
        "result": "hello &lt;script&gt;alert('xss attack');&lt;/script&gt;"
      }
    

기타 참고 자료

  • https://stackoverflow.com/questions/22461663/convert-inputstream-to-jsonobject
  • https://stackoverflow.com/questions/6511880/how-to-parse-a-json-input-stream/13267720
  • https://www.baeldung.com/spring-boot-add-filter
  • https://supawer0728.github.io/2018/04/04/spring-filter-interceptor/
  • https://github.com/ihoneymon/boot-spring-boot/wiki/%EC%9E%90%EB%8F%99%EA%B5%AC%EC%84%B1%EB%90%9C-Spring-MVC-%EA%B5%AC%EC%84%B1-%EC%A0%9C%EC%96%B4%ED%95%98%EA%B8%B0