ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Controller의 @RequestBody 에서 XSS 처리 방법
    설치&설정 관련/Spring Framework 2016. 7. 22. 16:56

    XSS를 네이버에서 만든 Lucy의 servlet-filter 를 이용하여 지금까지 쉽게 처리 해 왔습니다.


    XSS 관련해서 POST로 처리 하는 부분이 제대로 동작 하지 않는 것을 확인하여 체크해본 결과 Lucy는 RequestParameter관련한 지원만 합니다.


    따라서 Spring에서 @RequestBody를 이용한 부분은 처리할 수 없습니다.


    이에 구글링으로 검색해본 결과 MessageConverter를 이용하여 처리 하는 방법을 확인 하였습니다.


    처리 방법은 spring 4.2.5 기준으로 다음과 같습니다.


    먼저 다음과 같은 ObjectMapper를 가진 FactoryBean을 생성 합니다.


    package kr.pe.lahuman;

    import org.apache.commons.lang3.StringEscapeUtils;

    import org.springframework.beans.factory.FactoryBean;


    import com.fasterxml.jackson.core.SerializableString;

    import com.fasterxml.jackson.core.io.CharacterEscapes;

    import com.fasterxml.jackson.core.io.SerializedString;

    import com.fasterxml.jackson.databind.DeserializationFeature;

    import com.fasterxml.jackson.databind.ObjectMapper;


    public class HtmlEscapingObjectMapperFactory implements FactoryBean<ObjectMapper> {


        private final ObjectMapper objectMapper;


    public HtmlEscapingObjectMapperFactory() {

            objectMapper = new ObjectMapper();

            objectMapper.getFactory().setCharacterEscapes(new HTMLCharacterEscapes());

            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        }


        @Override

        public ObjectMapper getObject() throws Exception {

            return objectMapper;

        }


        @Override

        public Class<?> getObjectType() {

            return ObjectMapper.class;

        }


        @Override

        public boolean isSingleton() {

            return true;

        }


        public static class HTMLCharacterEscapes extends CharacterEscapes {


            private final int[] asciiEscapes;


            public HTMLCharacterEscapes() {

                // start with set of characters known to require escaping (double-quote, backslash etc)

                asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();

                // and force escaping of a few others:

                asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;

                asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;

                asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;

                asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;

                asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;

            }



            @Override

            public int[] getEscapeCodesForAscii() {

                return asciiEscapes;

            }


            // and this for others; we don't need anything special here

            @Override

            public SerializableString getEscapeSequence(int ch) {

                return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));

            }

        }

    }



    그리고 spring-servlet.xml 설정에 다음과 같이 처리 합니다.

    <bean id="htmlEscapingObjectMapper" class="kr.pe.lahuman.HtmlEscapingObjectMapperFactory" />


    <mvc:annotation-driven>

       <mvc:message-converters>

           <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >

            <property name="objectMapper" ref="htmlEscapingObjectMapper"></property>

           </bean>

       </mvc:message-converters>

    </mvc:annotation-driven>




    출처 : http://stackoverflow.com/questions/25403676/initbinder-with-requestbody-escaping-xss-in-spring-3-2-4



    방법 2: RequestWrapper를 이용하여 처리하는 방법


    request의 값을 변조할 경우 많이 사용 하는 방법으로 Request를 Wrapping 하여 사용자가 원하는 데이터를 가공 하는 방식이다.


    처리 코드는 다음과 같다.


    먼저 Request를 변환 하기 위한 Filter를 생성 한다.

    package kr.pe.lahuman;


    import java.io.IOException;


    import javax.servlet.Filter;

    import javax.servlet.FilterChain;

    import javax.servlet.FilterConfig;

    import javax.servlet.ServletException;

    import javax.servlet.ServletRequest;

    import javax.servlet.ServletResponse;

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletResponse;


    public class RequestBodyXSSFIleter implements Filter {

    @Override

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

    throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest)req;

      HttpServletResponse response = (HttpServletResponse)res;

      RequestWrapper requestWrapper = null;

      try{

      requestWrapper = new RequestWrapper(request);

      }catch(Exception e){

      e.printStackTrace();

      }

      chain.doFilter(requestWrapper, response);

    }

    @Override

    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override

    public void destroy() {}

    }



    RequestWrapper를 생성한다.

    package kr.pe.lahuman;


    import java.io.BufferedReader;

    import java.io.ByteArrayInputStream;

    import java.io.IOException;

    import java.io.InputStream;

    import java.io.InputStreamReader;


    import javax.servlet.ServletInputStream;

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletRequestWrapper;


    import org.apache.commons.io.IOUtils;


    import com.nhncorp.lucy.security.xss.XssFilter;


    public class RequestWrapper extends HttpServletRequestWrapper {

    private byte[] b;

    public RequestWrapper(HttpServletRequest request) throws IOException {

    super(request);

      XssFilter filter = XssFilter.getInstance("lucy-xss-sax.xml");

      b = new String(filter.doFilter(getBody(request))).getBytes();

    }

    public ServletInputStream getInputStream() throws IOException {

      final ByteArrayInputStream bis = new ByteArrayInputStream(b);

     

      return new ServletInputStreamImpl(bis);

      }

     

      class ServletInputStreamImpl extends ServletInputStream{

      private InputStream is;

     

      public ServletInputStreamImpl(InputStream bis){

      is = bis;

      }

     

      public int read() throws IOException {

      return is.read();

      }

     

      public int read(byte[] b) throws IOException {

      return is.read(b);

      }

      }


     

      public static String getBody(HttpServletRequest request) throws IOException {


         String body = null;

         StringBuilder stringBuilder = new StringBuilder();

         BufferedReader bufferedReader = null;


         try {

             InputStream inputStream = request.getInputStream();

             if (inputStream != null) {

                 bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

                 char[] charBuffer = new char[128];

                 int bytesRead = -1;

                 while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {

                     stringBuilder.append(charBuffer, 0, bytesRead);

                 }

             } else {

                 stringBuilder.append("");

             }

         } catch (IOException ex) {

             throw ex;

         } finally {

             if (bufferedReader != null) {

                 try {

                     bufferedReader.close();

                 } catch (IOException ex) {

                     throw ex;

                 }

             }

         }


         body = stringBuilder.toString();

         return body;

      }

    }


    마지막으로 web.xml에 해당 filter를 추가 한다.

      <filter>

      <filter-name>RequestBodyXSSFilter</filter-name>

      <filter-class>kr.pe.lahuman.RequestBodyXSSFIleter</filter-class>

      </filter>

        <filter-mapping>

        <filter-name>RequestBodyXSSFilter</filter-name>

        <url-pattern>/*</url-pattern>

      </filter-mapping>


    출처 : http://shonm.tistory.com/549

Designed by Tistory.