V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
liugp5201314
V2EX  ›  Java

SpringMVC 增加了一个 xss 过滤器,导致 Controller 上传的文件为空

  •  2
     
  •   liugp5201314 · May 7, 2020 · 3929 views
    This topic created in 2182 days ago, the information mentioned may be changed or developed.

    最近做的一个项目进行安全测试时测出了 SQL 注入问题,严重级别为高危,怎么办呢?我还是个雏,还没学会飞呢,挠挠头,硬上吧,然后把之前项目里的 xss 都弄过来修修改改,然后跑起来,震惊了,竟然全都过滤了,是的,全都过滤了,连上传的文件都给我过滤了,咋办?再百度,结果全是千篇一律的抄袭,没一个能用的,还是发个帖子大家帮我瞅瞅,看看怎么解决一下,头发都挠掉一大把了,听说植发一根二十块,听着都吓人。

    这是调用过滤器:

    
    ```java
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException{
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    String enctype = request.getContentType();
    if (StringUtils.isNotBlank(enctype) && enctype.contains("multipart/form-data")) {
       final MultipartResolver multipartResolver = SpringUtil.getBean("multipartResolver");
       final MultipartHttpServletRequest multipartHttpServletRequest = 	  	   multipartResolver.resolveMultipart((HttpServletRequest) request);
       chain.doFilter(new XssHttpServletRequestWrapper(multipartHttpServletRequest),   response);
    } else {
         chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
    }
    }
    
    
    这是重写的方法:
    
    ```java
    public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
    {
        /**
         * @param request
         */
        public XssHttpServletRequestWrapper(HttpServletRequest request)
        {
            super(request);
        }
    
        /**
         * 覆盖 getHeader 方法,将参数名和参数值都做 xss 过滤。
         * 如果需要获得原始的值,则通过 super.getHeaders(name)来获取
         * getHeaderNames 也可能需要覆盖
         */
        @Override
        public String getHeader(String name) {
    
            String value = super.getHeader(EscapeUtil.escape(name));
            if (value != null) {
                value = EscapeUtil.escape(value);
            }
            return value;
        }
    
        @Override
        public String getParameter(String name){
            String value = super.getParameter(name);
            if (value != null)
            {
            String escapseValue = EscapeUtil.escape(value.trim());
            return escapseValue;
            }
            return super.getParameter(name);
        }
    
        @Override
        public String[] getParameterValues(String name)
        {
            String[] values = super.getParameterValues(name);
            if (values != null)
            {
                int length = values.length;
                String[] escapseValues = new String[length];
                for (int i = 0; i < length; i++)
                {
                    // 防 xss 攻击和过滤前后空格
                    escapseValues[i] = EscapeUtil.escape(values[i]).trim();
                }
                return escapseValues;
            }
            return super.getParameterValues(name);
        }
    
    }
    

    这是过滤规则:

    public class EscapeUtil
    {
        public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";
    
        private static final char[][] TEXT = new char[64][];
    
        static
        {
            for (int i = 0; i < 64; i++)
            {
                TEXT[i] = new char[] { (char) i };
            }
    
            // special HTML characters
            TEXT['\''] = "&#039;".toCharArray(); // 单引号
            TEXT['"'] = "&#34;".toCharArray(); // 单引号
            TEXT['&'] = "&#38;".toCharArray(); // &符
            TEXT['<'] = "&#60;".toCharArray(); // 小于号
            TEXT['>'] = "&#62;".toCharArray(); // 大于号
        }
    
        /**
         * 转义文本中的 HTML 字符为安全的字符
         * 
         * @param text 被转义的文本
         * @return 转义后的文本
         */
        public static String escape(String text)
        {
            return encode(text);
        }
    
        /**
         * 还原被转义的 HTML 特殊字符
         * 
         * @param content 包含转义符的 HTML 内容
         * @return 转换后的字符串
         */
        public static String unescape(String content)
        {
            return decode(content);
        }
    
        /**
         * 清除所有 HTML 标签,但是不删除标签内的内容
         * 
         * @param content 文本
         * @return 清除标签后的文本
         */
        public static String clean(String content)
        {
            return new HTMLFilter().filter(content);
        }
    
        /**
         * Escape 编码
         * 
         * @param text 被编码的文本
         * @return 编码后的字符
         */
        private static String encode(String text)
        {
            int len;
            if ((text == null) || ((len = text.length()) == 0))
            {
                return StringUtils.EMPTY;
            }
            StringBuilder buffer = new StringBuilder(len + (len >> 2));
            char c;
            for (int i = 0; i < len; i++)
            {
                c = text.charAt(i);
                if (c < 64)
                {
                    buffer.append(TEXT[c]);
                }
                else
                {
                    buffer.append(c);
                }
            }
            return buffer.toString();
        }
    
        /**
         * Escape 解码
         * 
         * @param content 被转义的内容
         * @return 解码后的字符串
         */
        public static String decode(String content)
        {
            if (StringUtils.isEmpty(content))
            {
                return content;
            }
    
            StringBuilder tmp = new StringBuilder(content.length());
            int lastPos = 0, pos = 0;
            char ch;
            while (lastPos < content.length())
            {
                pos = content.indexOf("%", lastPos);
                if (pos == lastPos)
                {
                    if (content.charAt(pos + 1) == 'u')
                    {
                        ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16);
                        tmp.append(ch);
                        lastPos = pos + 6;
                    }
                    else
                    {
                        ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16);
                        tmp.append(ch);
                        lastPos = pos + 3;
                    }
                }
                else
                {
                    if (pos == -1)
                    {
                        tmp.append(content.substring(lastPos));
                        lastPos = content.length();
                    }
                    else
                    {
                        tmp.append(content.substring(lastPos, pos));
                        lastPos = pos;
                    }
                }
            }
            return tmp.toString();
        }
    
        public static void main(String[] args)
        {
            String html = "<script>alert(1);</script>";
            // String html = "<scr<script>ipt>alert(\"XSS\")</scr<script>ipt>";
            // String html = "<123";
            System.out.println(EscapeUtil.clean(html));
            System.out.println(EscapeUtil.escape(html));
            System.out.println(EscapeUtil.unescape(html));
        }
    }
    
    16 replies    2020-05-16 14:58:29 +08:00
    waa
        1
    waa  
       May 7, 2020
    在 controller 方法参数中直接添加 MultipartHttpServletRequest 形式参数,通过 multipartHttpservletRequest 对象获取 MultipartFile 和其余的请求参数。我的是这样解决的
    jzmws
        2
    jzmws  
       May 7, 2020
    forty 测试的 ??
    dushixiang
        3
    dushixiang  
       May 7, 2020
    1. sql 注入你加 xss 过滤器有啥作用?
    2. 判断 ContentType 之后的处理有问题。
    sagaxu
        4
    sagaxu  
       May 7, 2020 via Android
    你这个思路很 php
    richard1122
        5
    richard1122  
       May 7, 2020
    想起了无数年前 PHP 开的 magic quote
    chendy
        6
    chendy  
       May 8, 2020
    建议直接写 servlet 算了,这 springmvc 用的不如不用
    liugp5201314
        7
    liugp5201314  
    OP
       May 8, 2020
    @FreeEx 第一次弄,我也是百度的,说 xss 是防 sql 注入的
    liugp5201314
        8
    liugp5201314  
    OP
       May 8, 2020
    @waa 是吧原来 controller 里的 HttpServletRequest 这个参数替换为 MultipartHttpServletRequest 这个吗
    MrMario
        9
    MrMario  
       May 8, 2020
    注入问题根源是 sql 的处理,预编译+参数绑定,或者使用 ORM 框架就好(个别框架使用需注意)
    liugp5201314
        10
    liugp5201314  
    OP
       May 9, 2020
    @MrMario 框架已经订好了,这是个维护的项目,我只能改功能,不能改框架
    ice2neet
        11
    ice2neet  
       May 9, 2020
    sql 注入不是应该处理 sql 吗?
    BryceL
        12
    BryceL  
       May 12, 2020
    写个 AOP 对入参进行处理。你这 sql 注入是指的参数的问题把。
    xinQing
        13
    xinQing  
       May 13, 2020
    哈哈,我遇到过类似的问题。搞了个过滤器过滤请求内容,然后 controller 里面的数据拿不到了。这是因为正常情况下流只能处理一次,你过滤器消费了,后续就没有了。你要采用 warpper 包装 Request,让 Request 支持可重复消费。spring 可以用这个包装下 org.springframework.web.util.ContentCachingRequestWrapper
    liugp5201314
        14
    liugp5201314  
    OP
       May 14, 2020
    @xinQing 你看我上边的代码。我已经重写了 warpper 了。但还是不行,不知是不是哪里写的不对
    xinQing
        15
    xinQing  
       May 15, 2020
    @liugp5201314 说了啊,你写的有问题,你看看 org.springframework.web.util.ContentCachingRequestWrapper 用 ByteArrayInputStream 缓存数据,使流支持可重复读取
    340244120w
        16
    340244120w  
       May 16, 2020
    上传的接口 直接 chain.doFilter(request, response)就行了。。不用包装
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5213 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 59ms · UTC 05:41 · PVG 13:41 · LAX 22:41 · JFK 01:41
    ♥ Do have faith in what you're doing.