안녕하세요! 프뚜입니다.
이전에는 RequestFilter를 통해서 XSS 처리 후 데이터베이스에 insert, update등을 했습니다. 하지만 XSS 처리가 된 데이터를 비지니스로직에서 사용해야할 때가 발생되었습니다. 이후 RequestFilter가 아닌 ResponseFilter에서 처리하게 되었습니다.
[개발 환경]
- OS: Windows 10 64bit
- JAVA: 1.8
# XSS Filter Class 생성하기
@Component
public class XSSFilter implements Filter {
// 2022.12.26[프뚜]: path 제외시킬 URI
private String[] excludePathPatterns = {
"/exclude1/*",
"/exclude2/**/double/*"
};
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 2022.12.26[프뚜]: 정규식을 통해 excludePath 문자를 변경
String asterisk = "([^/]+)";
String doubleAsterisk = "(.+)";
for (int i = 0; i < excludePathPatterns.length; i++) {
excludePathPatterns[i] = excludePathPatterns[i].replaceAll("[*][*]", doubleAsterisk)
.replaceAll("[*]", asterisk);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
System.out.println("[LOG] URI: " + httpServletRequest.getRequestURI());
if (isExcludePath(httpServletRequest)) {
chain.doFilter(request, response);
} else {
// 2022.12.26[프뚜]: Response Wrapper
XSSFilterWrapper responseWrapper = new XSSFilterWrapper(httpServletResponse);
chain.doFilter(request, responseWrapper);
// 2022.12.26[프뚜]: 최종 데이터 처리
byte[] bytes = responseWrapper.replaceXSS();
int byteLength = bytes.length;
if (byteLength > 0) {
response.setContentLength(byteLength);
response.getOutputStream().write(bytes);
response.flushBuffer();
}
}
}
private boolean isExcludePath(HttpServletRequest httpServletRequest) {
for (String pattern : excludePathPatterns) {
// 2022.12.26[프뚜]: 정규식으로 변한 path를 Pattern.matches를 통해 비교
if (Pattern.matches(pattern, httpServletRequest.getRequestURI())) {
return true;
}
}
return false;
}
}
필터에 모든 URI가 적용되기 때문에 excloude할 수 있는 메서드를 구현했습니다. (정규표현식 사용)
- *: /를 제외한 어떠한 문자도 허용
- **: 모든 문자도 허용(여러 뎁스의 URI도 허용)
# XSSFilterWrapper Class 생성하기
@Component
public class XSSFilterWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream byteArrayOutputStream;
private ServletOutputStream servletOutputStream;
public XSSFilterWrapper(HttpServletResponse httpServletResponse) {
super(httpServletResponse);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (servletOutputStream == null) {
byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
servletOutputStream = new ServletOutputStream() {
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener listener) {
}
@Override
public void write(int b) throws IOException {
dataOutputStream.write(b);
}
};
}
return servletOutputStream;
}
public byte[] replaceXSS() {
// 2022.12.26[프뚜]: XSS 처리
return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8)
.replaceAll("\\<", "<")
.replaceAll("\\>", ">")
.replaceAll("\"", """)
.replaceAll("'", "'")
.getBytes(StandardCharsets.UTF_8);
}
}
response의 모든 데이터에 대해 XSS 처리를 합니다.
# TestController Class 생성하기
@RequiredArgsConstructor
@RestController
public class TestController {
@GetMapping("/")
public ResponseEntity<?> main() throws Exception {
return ResponseEntity.ok().body("main");
}
@PostMapping("/test")
public ResponseEntity<?> test(@RequestBody Map param) throws Exception {
return ResponseEntity.ok().body(param);
}
@PostMapping("/exclude1/test")
public ResponseEntity<?> exclude1(@RequestBody Map param) throws Exception {
return ResponseEntity.ok().body(param);
}
@PostMapping("/exclude2/t/e/s/t/double/test")
public ResponseEntity<?> exclude2(@RequestBody Map param) throws Exception {
return ResponseEntity.ok().body(param);
}
}
정상적으로 적용되었는 지와 exclude가 정상적으로 되는 지 확인할 수 있는 RequestMapping을 합니다.
# Postman으로 테스트하기
/test (POST)의 결과 XSSFilter가 정상적용됨을 확인할 수 있습니다.
/exclude1/test (POST)의 결과 XSSFilter적용이 되지 않음을 확인할 수 있습니다. excludePath에 /exclude1/*로 되어있었기 때문에 위 URI는 Filter적용이 되지 않았습니다.
/exclude2/t/e/s/t/double/test (POST)의 결과 XSSFilter적용이 되지 않음을 확인할 수 있습니다. excludePath에 /exclude2/**/double/*로 되어있었기 때문에 위 URI는 Filter적용이 되지 않았습니다.
자세한 내용은 소스코드를 통해 확인해보실 수 있습니다. (제목과 Git Comment는 같습니다.)
'프로그램 > JAVA' 카테고리의 다른 글
[Logback] Spring log 제외 및 설정하기 (1) | 2023.01.07 |
---|---|
[Regex] JAVA replace 정규 문자 변경하기 (2) | 2023.01.05 |
[Regex] JAVA 정규식표현 사용하기 (0) | 2023.01.04 |
[AOP] SpringBoot AOP 사용하기 (2) | 2022.12.30 |
[JWT] Spring Interceptor 세팅 및 사용하기 (2) | 2022.12.28 |