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

2020-05-07 14:22:09 +08:00
 liugp5201314

最近做的一个项目进行安全测试时测出了 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));
    }
}
3022 次点击
所在节点    Java
16 条回复
waa
2020-05-07 17:02:00 +08:00
在 controller 方法参数中直接添加 MultipartHttpServletRequest 形式参数,通过 multipartHttpservletRequest 对象获取 MultipartFile 和其余的请求参数。我的是这样解决的
jzmws
2020-05-07 20:11:42 +08:00
forty 测试的 ??
FreeEx
2020-05-07 20:48:59 +08:00
1. sql 注入你加 xss 过滤器有啥作用?
2. 判断 ContentType 之后的处理有问题。
sagaxu
2020-05-07 21:00:14 +08:00
你这个思路很 php
richard1122
2020-05-07 21:14:00 +08:00
想起了无数年前 PHP 开的 magic quote
chendy
2020-05-08 07:59:33 +08:00
建议直接写 servlet 算了,这 springmvc 用的不如不用
liugp5201314
2020-05-08 10:33:44 +08:00
@FreeEx 第一次弄,我也是百度的,说 xss 是防 sql 注入的
liugp5201314
2020-05-08 10:35:11 +08:00
@waa 是吧原来 controller 里的 HttpServletRequest 这个参数替换为 MultipartHttpServletRequest 这个吗
MrMario
2020-05-08 14:42:09 +08:00
注入问题根源是 sql 的处理,预编译+参数绑定,或者使用 ORM 框架就好(个别框架使用需注意)
liugp5201314
2020-05-09 10:30:21 +08:00
@MrMario 框架已经订好了,这是个维护的项目,我只能改功能,不能改框架
ice2neet
2020-05-09 13:43:24 +08:00
sql 注入不是应该处理 sql 吗?
BryceL
2020-05-12 11:17:53 +08:00
写个 AOP 对入参进行处理。你这 sql 注入是指的参数的问题把。
xinQing
2020-05-13 18:01:53 +08:00
哈哈,我遇到过类似的问题。搞了个过滤器过滤请求内容,然后 controller 里面的数据拿不到了。这是因为正常情况下流只能处理一次,你过滤器消费了,后续就没有了。你要采用 warpper 包装 Request,让 Request 支持可重复消费。spring 可以用这个包装下 org.springframework.web.util.ContentCachingRequestWrapper
liugp5201314
2020-05-14 10:14:08 +08:00
@xinQing 你看我上边的代码。我已经重写了 warpper 了。但还是不行,不知是不是哪里写的不对
xinQing
2020-05-15 09:27:54 +08:00
@liugp5201314 说了啊,你写的有问题,你看看 org.springframework.web.util.ContentCachingRequestWrapper 用 ByteArrayInputStream 缓存数据,使流支持可重复读取
340244120w
2020-05-16 14:58:29 +08:00
上传的接口 直接 chain.doFilter(request, response)就行了。。不用包装

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/669276

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX