제목 및 내용에서 나오는
Spring Legacy라는 표현은Spring Boot를 활용하지 않는
환경을 의미합니다.
현재 프로젝트는
Spring MVC 4버전과JSP를 활용한SSR을 하고 있습니다.
그리고 부가적으로 전자정부프레임워크에서 제공하는 공통 유틸 코드를 사용 중입니다.
이번 글의 핵심인HtmlTagFilter.java도 그중 하나입니다.
이번에 다른 팀의 서비스를 테스트할 기회가 생겨서 이것저것 만져봤다.
간단한 게시판이였다. (물론 내부적으로는 뭔가 복잡하게 돌아가는 거 같았다).
그런데 보다보니, 문제가 있었다.
테스트를 아래와 같은 절차로 해봤다.
테스트 절차
"<중요> 이 게시물은 함부로 지워서는 안됩니다" 라고 작성 <중요> 이 게시물은 함부로 지워서는 안됩니다
사실 이 부분은 딱히 내가 낸 버그도 아니고 그냥 테스트해서 알려주기만 해도 되지만
그냥 해결법도 작성하기로 했다. 일단 왜 이렇게 동작하는지 이유나 알고 가자.
이건 HtmlTagFilter.java 를 web.xml에 Filter 를 아래처럼 작성해서 그렇다.
<filter>
<filter-name>HTMLTagFilter</filter-name>
<filter-class>egovframework.test.egov.util.HTMLTagFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HTMLTagFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
이러면 HTTP 요청 중에서 http method: post, content-type: x-www-form-urlencoded
이면서 url 형태가 *.do 인 것들은 모두 HTMLTagFilter 필터의 로직을 태울 수 있다.
앞서 봤던 수정 요청도 이런 조건에 부합하여, HtmlTagFilter 의 영향을 받은 것이다.
참고로 multipart 요청에 대해서는 작동이 안됩니다.
multipart 에 대한 적용법은 인터넷을 찾으면 쉽게 찾을 수 있습니다.
spring 설정을 살짝 바꿔주면 되는 걸로 기억합니다.
HtmlTagFilter 의 코드를 보고 싶다면 이 글의 하단에 있는
HtmlTagFilter 내부 로직목차를 확인하자.
이 클래스에 대해 간단히 설명하자면, 우리가 작성한 Html Tag 문자열(ex: >, < , etc...)을 Html Entity 로 수정하는 작업을 하는 것이다.
그리고 이 작업의 목적은 XSS 방어입니다.
Html Entity란Html Tag를 브라우저가 하나의 문자열로 번역하고 표기하기 위한
특수한 문자를 의미합니다. 예를 들어서<는<같은 문자열로 표기할 수 있습니다.
브라우저는<를 보면 Html Tag가 아닌 문자열로서 "<" 를 표기합니다.이런
Html Entity가 없었다면 브라우저에 "<" 는 절대로 표기 못했을 겁니다 😃
정리하자면 이런 상황입니다!
1) 우리가 수정 요청을 하면 이 필터는 내가 작성한 문자열인 <, > 에 대하여
<, > 로 수정
2) 해당 문자열이 그대로 DB 에 저장
3) 이후 상세조회 요청이 오면 2번에서 저장한 문자열(<, >)이 그대로 조회된다.
4) JSP 에서는 <c:out value=${vo.description} /> 를 통해서 해당 문자열을 화면에 보여준다.
5) 사용자들은 <중요> 이 게시물은 함부로 지워서는 안됩니다 라는 문구를 보게 된다.
3가지 경우의 수만 생각하면 됩니다.
하나하나 알아봅시다.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:out escapeXml="false" value="${vo.description}" />
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<input value='<c:out escapeXml="false" value="${vo.description}" />' />
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<textarea><c:out value="${vo.description}" /></textarea>
끝!
public class HTMLTagFilterRequestWrapper extends HttpServletRequestWrapper {
public HTMLTagFilterRequestWrapper(HttpServletRequest request) {
super(request);
}
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
for (int i = 0; i < values.length; i++) {
if (values[i] != null) {
StringBuffer strBuff = new StringBuffer();
for (int j = 0; j < values[i].length(); j++) {
char c = values[i].charAt(j);
switch (c) {
case '<':
strBuff.append("<");
break;
case '>':
strBuff.append(">");
break;
case '&':
strBuff.append("&");
break;
case '"':
strBuff.append(""");
break;
case '\'':
strBuff.append("'");
break;
default:
strBuff.append(c);
break;
}
}
values[i] = strBuff.toString();
} else {
values[i] = null;
}
}
return values;
}
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
StringBuffer strBuff = new StringBuffer();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
switch (c) {
case '<':
strBuff.append("<");
break;
case '>':
strBuff.append(">");
break;
case '&':
strBuff.append("&");
break;
case '"':
strBuff.append(""");
break;
case '\'':
strBuff.append("'");
break;
default:
strBuff.append(c);
break;
}
}
value = strBuff.toString();
return value;
}
}