SpringMVC过滤字符防御xss攻击
2021-06-18 本文已影响0人
星钻首席小管家
1.参数重写类ParameterRequestWrapper
package com.yxtech.business.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yxtech.utils.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* 参数重写类
* @author sp.
* <br/>date: 2021-06-18.
*/
public class ParameterRequestWrapper {
/**
* 过滤JSON数据请求参数
* @param request
* @param response
*/
@SuppressWarnings("unchecked")
public ServletRequest parameterRequest(HttpServletRequest request, ServletResponse response) throws IOException {
RepeatedlyRequestWrapper requestWrapper = new RepeatedlyRequestWrapper(request, response);
new ParameterRequestWrapper().parameterGetRequest(request,requestWrapper);
// 读取请求内容
BufferedReader br;
br = requestWrapper.getReader();
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
JSONObject jsonObject = JSONObject.parseObject(sb.toString());
Map<String, Object> map = new HashMap<String, Object>();
map = JSON.toJavaObject(jsonObject, Map.class);
if(null != map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
Class<?> aClass = entry.getValue().getClass();
String name = aClass.getName();
if(StringUtils.isNotEmpty(name) && "java.lang.String".equalsIgnoreCase(name)){
// 重写参数
map.put(entry.getKey(), xssEncode(entry.getValue().toString().trim()));
}
}
// 把参数转换之后放到我们的body里面
String json = JSON.toJSONString(map);
requestWrapper.setBody(json.getBytes("UTF-8"));
}
return requestWrapper;
}
/**
* 过滤URL请求参数
* @param request
* @param requestWrapper
*/
public void parameterGetRequest(HttpServletRequest request, RepeatedlyRequestWrapper requestWrapper){
Map<String, String[]> parameterMap = request.getParameterMap();
Map<String, String[]> parames = new HashMap<String, String[]>();
if(null != parameterMap) {
Set<String> set =parameterMap.keySet();
Iterator<String> it=set.iterator();
while(it.hasNext()){
String key= (String) it.next();
String[] values = parameterMap.get(key);
//values[0] = values[0].trim();
values[0] = xssEncode(values[0].trim());
parames.put(key, values);
}
requestWrapper.setParams(parames);
}
}
/**
* 将容易引起xss漏洞的半角字符直接替换成全角字符
*
* @param s
* @return
*/
private static String xssEncode(String s) {
if (s == null || s.isEmpty()) {
return s;
}
StringBuilder sb = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '>':
sb.append('>');// 全角大于号
break;
case '<':
sb.append('<');// 全角小于号
break;
case '\'':
sb.append('‘');// 全角单引号
break;
case '\"':
sb.append('“');// 全角双引号
break;
case '&':
sb.append('&');// 全角
break;
case '\\':
sb.append('\');// 全角斜线
break;
case '#':
sb.append('#');// 全角井号
break;
// case '%': // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c
// processUrlEncoder(sb, s, i);
// break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
public static void processUrlEncoder(StringBuilder sb, String s, int index){
if(s.length() >= index + 2){
if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'c' || s.charAt(index+2) == 'C')){ // %3c, %3C
sb.append('<');
return;
}
if(s.charAt(index+1) == '6' && s.charAt(index+2) == '0'){ // %3c (0x3c=60)
sb.append('<');
return;
}
if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'e' || s.charAt(index+2) == 'E')){ // %3e, %3E
sb.append('>');
return;
}
if(s.charAt(index+1) == '6' && s.charAt(index+2) == '2'){ // %3e (0x3e=62)
sb.append('>');
return;
}
}
sb.append(s.charAt(index));
}
}
2.读取body参数类RepeatedlyRequestWrapper
package com.yxtech.business.filter;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
/**
* 读取body参数
* @author sp.
* <br/>date: 2021-06-18.
*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
private Map<String , String[]> params = new HashMap<String, String[]>();
private byte[] body;
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
body = HttpHelper.getBodyString(request).getBytes("UTF-8");
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
};
}
//重写getParameter 参数从当前类中的map获取
@Override
public String[] getParameterValues(String name) {
return params.get(name);
}
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
public void setParams(Map<String, String[]> params) {
this.params = params;
}
}
3.将字节数组转换为字符串HttpHelper
package com.yxtech.business.filter;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* 用于将字节数组转换为字符串
*/
public class HttpHelper {
/**
* 获取请求Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
4.过滤器RepeatableFilter
package com.yxtech.business.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Repeatable 过滤器
* @author sp.
* <br/>date: 2021-06-18.
*/
public class RepeatableFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String contentType = request.getContentType();
ServletRequest requestWrapper = null;
// 如果上传的是文件(multipart/form-data),就不走过滤
if (request instanceof HttpServletRequest) {
if (null != contentType && contentType.contains("multipart/form-data")) {
return;
}else {
requestWrapper = new ParameterRequestWrapper().parameterRequest((HttpServletRequest) request, response);
}
}
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
5.web.xml配置过滤器
<!-- xss字符参数过滤器 -->
<filter>
<filter-name>ParamsFilter</filter-name>
<filter-class>com.yxtech.business.filter.RepeatableFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ParamsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>