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