HandlerInterceptor의 적절한 예제가 맞는지 조언 부탁드립니다.

633 views
Skip to first unread message

선영욱

unread,
Jan 20, 2010, 8:44:14 PM1/20/10
to ks...@googlegroups.com
안녕하세요, 많이 늦었지만 새해 복 많이 받으세요. ^^
 
오늘 조언을 듣고 싶은 분야는 다름이 아니라 HandlerInterceptor 입니다.
 
제가 개발한 app에 대해서 고객사에서 보안검증 심사를 하신다고 하는데, 그 중 크로스 서버 스크립팅 공격의 취약점도 확인하신다고 하더라구요.
 
그래서 가장 빨리 해당 내용을 적용할 방법이 없을까 하다가 HandlerInterceptor가 생각이 나서 적용을 해 보았습니다.(이런 부분이 생길때 마다 스프링을 쓰길 잘했다는 생각이 듭니다. 물론 다른 잘 만든 프레임웍으로도 가능한 부분이긴 하겠지만요)
 
머릿속에 그린 시나리오는 request 객채에서 input, textarea 등으로 넘어져 오는 값에서 유효하지 않은 단어가 포함된 경우 그것을 Exception으로 처리하도록 하는 간단한 방법으로 그려 보았습니다.
 
먼저 적용대상은 SimpleUrlHandlerMapping으로 등록된 것들을 대상으로 Interceptor를 걸도록 했습니다.
 
    <bean id="simpleUrlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="interceptors">
        <list>
          <ref local="xssHandlerInterceptor" />
        </list>
     </property>
        <property name="order">
            <value>0</value>
        </property>
        <property name="mappings">
            <props>
                <prop key="/index.htm">indexController</prop>
                ...
            </props>
        </property>
    </bean>
 
 
그리고 HandlerInterceptor은 다음과 같이 정의 했습니다.
 
 <bean id="xssHandlerInterceptor" class="kr.co.inogard.dtia.controller.interceptor.XSSHandlerInterceptor">
        <property name="invaildWordMappings">
            <props>
                <prop key="javascript">javascript</prop>
                <prop key="document">document.</prop>
                <prop key="alert">alert(</prop>
               </props>
  </property>
 </bean>
 
property에 유효하지 않은 단어를 미리 등록해 놓고, 입력값과 비교하는 방법으로 진행하려고 했습니다.
 
다음은 실제 소스 입니다.
 
import java.util.Enumeration;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import kr.co.inogard.dtia.utils.StringUtil;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
 *
 *<ul>
 * <li>프  로  그  램 : XSSHandlerInterceptor.java</li>
 * <li>프로그램설명 : 크로스 서버 스크립팅(XSS) 공격 방지용 필터</li>
 * <li>관련프로그램 : </li>
 * <li>관련  테이블 : </li>
 * <li>작  성  일  자 : 2010. 01. 20</li>
 * <li>수  정  내  역 : </li>
 * <li>참  고  사  항 : </li>
 * </ul>
 * @author 선영욱
 */
public class XSSHandlerInterceptor extends HandlerInterceptorAdapter {
 
 private Properties invaildWordMappings;
 
 public void setInvaildWordMappings(Properties invaildWordMappings) {
  this.invaildWordMappings = invaildWordMappings;
 }
 /**
  * 전처리
  * @param request
  * @param response
  * @param handler
  * @return
  */
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
     throws Exception {
   
  for(Enumeration names = request.getParameterNames(); names.hasMoreElements();) {
   String name = (String)names.nextElement();
   String val = StringUtil.nvl(request.getParameter(name));
   
   String ret = getMatchInvaildWord(val);
   if(null != ret){
    String message = name+" 항목에 유효하지 않은 단어("+ret+")가 포함되어 크로스 서버 스크립팅(XSS)검사에 실패하였습니다.";
    Exception e = new RuntimeException(message);
    request.setAttribute("error", e);
    request.setAttribute("uri", request.getRequestURI());
    request.setAttribute("message", message);
    request.setAttribute("exception_type", "RuntimeException");
    throw e;
   }
  }
    
  return true;
 }
 
 /**
  * 유효하지 않은 단어가 포함되었는지 확인
  * @param val
  * @return
  */
 public boolean existInvaildWord(String val) throws Exception {
  boolean existInvaildWord = false;
  if(null != getMatchInvaildWord(val)){
   existInvaildWord = true;
  }
  return existInvaildWord;
 }
 
 /**
  * 유효하지 않은 단어가 포함되었다면 어떤 단어인지 반환
  * @param val
  * @return
  */
 public String getMatchInvaildWord(String val) throws Exception {
  for(Enumeration names = invaildWordMappings.propertyNames(); names.hasMoreElements();) {
   String key = (String) names.nextElement();
   String invaildWord = invaildWordMappings.getProperty(key);
   
   if(val.indexOf(invaildWord) > -1){
    return invaildWord;
   }
  }
  return null;
 }
}
 
이런 방법으로 처리를 해서 테스트를 해 보았는데요, 원하는 대로 동작은 하는 것 같습니다.
 
하지만 이런 상황에서 적절한(성능, 또는 기타 다른 부분을 놓친 것이 있는 것 같아서요.)지 다른 분들의 조언을 듣고 싶습니다.
 
긴 글 읽어주셔서 감사합니다.
 
오늘도 좋은 하루 되세요. ^^

Sanghyuk Jung

unread,
Jan 20, 2010, 9:36:25 PM1/20/10
to ks...@googlegroups.com
 XSS filter는 servlet filter 레벨로도 구현한 코드들이 여기저기서 많이 본 것 같네요.
 
 그런데 위의 코드에는 request.getParameter()로만 필터링할 값을 가지고 오고 있는데,  하나의 키로 여러개의 값이 들어올 경우 (checkbox를 여러개 체크한다거나 할때)도 필터가 될까요? 여러개의 값일 때는 request.getParameterValues를 보통 쓰는데, 이럴 경우도 filtering 해줘야 한다면 약간 구현을 바꿔야할지도 모릅니다.
 
  그럴 필요성이 있다면 value가 몇개인지에 관련없이 Map<String,String[]>의 형태로 값을 던져주는 request.getParameterMap을 쓰고 거기서 얻은 String[]을 loop을 돌면서 filter해주고, 다시  String[]에 값을 넣어주는 것이 좋지 않을까해요. ( 물론 java5이전부터 쓰인 API이니 Map<String,String[]>으로 generics를 적용해서 명시적으로 API에서 던져주는 것은 아니에요; )
  거기서 얻은 Map에 다시 put을 하는 것은 안 될수도 있을 거 같구요(WAS에 따라 다르겠지만 unmodifiable map인가.. 그런 종류를 리턴하는 경우도 본 것도 같아요) 다만 String[]로 넘어오니 그 배열에다가 다시 필터링한 값을 넣어주면 Map안에서 참조하는 값도 바뀔 수 있겠죠.
 
얼마나 큰 성능차이가 있을지는 모르겠는데, request에서 key를 먼저 꺼내서어서 다시 getParameter를 하는 것보다는 한번에 Map으로 가지고 오는 것이 성능이 더 좋을지도 모릅니다.
 

 
2010년 1월 21일 오전 10:44, 선영욱 <twinmo...@gmail.com>님의 말:
--
Google 그룹스 'Korea Spring User Group' 그룹에 가입했으므로 본 메일이 전송되었습니다.
이 그룹에 게시하려면 ks...@googlegroups.com(으)로 이메일을 보내세요.
그룹에서 탈퇴하려면 ksug+uns...@googlegroups.com로 이메일을 보내주세요.
더 많은 옵션을 보려면 http://groups.google.com/group/ksug?hl=ko에서 그룹을 방문하세요.


Jin Kim

unread,
Jan 20, 2010, 10:32:21 PM1/20/10
to ks...@googlegroups.com
>>  거기서 얻은 Map에 다시 put을 하는 것은 안 될수도 있을 거 같구요(WAS에 따라 다르겠지만 unmodifiable map인가.. 그런 종류를 리턴하는 경우도 본 것도 같아요) 다만 String[]로 넘어오니 그 배열에다가 다시 필터링한 값을 넣어주면 Map안에서 참조하는 값도 바뀔 수 있겠죠.

서블릿 스펙에는 immutable Map을 리턴한다고 되어 있으니까 WAS에 따라 될지는 몰라도 그렇게 쓰면 안되겠죠.

저도 이런 식으로 HanlderInterceptor 나 서블릿 필터에 파라미터 값을 검증하는 기능을 만들어 본 적이 있습니다만 좀 복잡하더군요.  웹 보안 가이드 등을 보면 파라미터 값을 검증할 때는 "안 되는 경우를 필터링"하는 방식보다는 "되는 경우를 제외하곤 모두 필터링"하는 방식을 사용할 것을 권장하는 데요, 이러러면 각 요청마다 받을 파라미터 형식을 정의해 놓고 그에 따라 검증할 필요가 있죠. 

전 xml 설정 파일을 두어서 url 패턴에 따라 받을 파라미터 형식을 지정하게끔 했는데 만드는 건 그럭저럭 괜찮은데 설정하는 건 꽤 노가다더군요. 한참 개발할 때 그때 그때 설정하는 건 고역이고 또 개발이 끝나가면 설정을 안 하게 되죠 :) 그래서 유명무실해 지게 되었습니다.

spring 2.5부터 요청 url을 컨트롤러에 매핑할 때 Annotation을 이용할 수 있게 되었으니까 Controller에 Annotation으로 이 설정을 할 수 있지 않을까 ... 해봤는데 안 되겠더군요. 3.0에선 달라지지 않을까 기대해 보긴 하지만 얼핏 보기론 DefaultAnnotationHandlerMapping에 별 변화가 없는 것 같아 보이더군요. :)

2010년 1월 21일 오전 11:36, Sanghyuk Jung <ben...@gmail.com>님의 말:

Sanghyuk Jung

unread,
Jan 21, 2010, 12:23:38 AM1/21/10
to ks...@googlegroups.com
request.getParametersMap이 immutable map 리턴하는 것이 서블릿 스펙이였군요. 그렇다면 모든 WAS가 다 그렇게 반환할 것 같습니다.
 
위에서 말씀드린 것처럼 value로 들어간 String[] 레퍼런스를 얻어와서 for loop를 돌면서 참조를 바꿔주면 어짜피 다시 put안 해도 filter된 String값이 들어갈 수 있지 않을까요? 
 
String[] input = (String[])value;
for (int i = 0, n = input.length; i < n; i++) {
input[i] = input[i].replaceAll(FILTER_PATTERN, "");
}

2010년 1월 21일 오후 12:32, Jin Kim <eige...@gmail.com>님의 말:

wansu yang

unread,
Jun 3, 2010, 11:55:20 PM6/3/10
to ks...@googlegroups.com
시간이 많이 지나 이 글을 필요에 의해 보게 되었습니다.

filter 를 통해 취약한 string 에 대한 점검 및 치환을 통한 방법은 많이 보아 왔습니다.

위 경우 interceptor 보다 delegateFilter 를 사용한 방법이 더 넓은 범위를 커버하지 않을 까 생각이 들었습니다.

그리고 이런 방법에 추가로 mvc 를 사용하는 경우 사용되는 request 의 바인딩 처리를 통해 해결을 볼 수도 있지 않을까? 라는 생각이 들었습니다.

그것은 바로 command 객체의 속성들을 일반 primitive type 이나 string type으로 받는게 아니라 객체로 propertyedit 하면 모든 문제가 간단하게(?? 물론 각각에 객체를 만드는 귀찮음이 존재하는군요..) 해결 가능하지않나 생각이 듭니다.

class TestCommand{
      private Account account;
      private Money money;

}
......

protected iniBinder(....){
      binder.registorProper....(String.class,new XSSCodeReplacePropertyEditor());
      binder.registorProper....(Account.class,new AccountPropertyEditor());
........
}


어떤 값들을 기본 타입이 아닌 의미있는 객체로 만들어 쓰다보니 초기 객체 생성에 귀찮은 점도 있지만 나름 장점도 있는것같습니다.
2010년 1월 21일 오후 2:23, Sanghyuk Jung <ben...@gmail.com>님의 말:



--
===========================================================

안녕하세요! 양완수 입니다.
+HP      : 010 2008 4167
+Email  : ywsa...@gmail.com , yang...@daumsoft.com
+
===========================================================

Sanghyuk Jung

unread,
May 24, 2011, 7:03:05 PM5/24/11
to ks...@googlegroups.com
참고로 덧붙이자면

request.getParameter()로 받은 값만 검사하면 하나의 이름으로 여러개의 값이 들어간 파라미터들은 지나치게 됩니다.

한 화면에서 같은 파라미터이름으로 다수 목록을 입력하는 변수도 XSS검증을 해야한다면 request.getParameterMap으로 받은 변수값을 검사하시는 편이 좋습니다.

request.getParameterMap으로 Map을 받으면 <String,String[]>의 형이 return되어서 여러개의 값이 들어간 변수의 값도 똑같이 참조됩니다. 값이 하나인 경우는 크기가 1인 String[]이 들어가게 되지요.

 이런식의 코드가 되겠죠.

@SupressWarnings(“unchecked”)
Map<String,String[]> params = request.getParameterMap();

for(String[] values : params.values()){
  for(String input : values){
   검사
  }
}


2010년 6월 4일 오후 12:55, wansu yang <ywsa...@gmail.com>님의 말:
Reply all
Reply to author
Forward
0 new messages