最近做的一个项目进行安全测试时测出了 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
FreeEx 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)就行了。。不用包装
|