Springboot 配置防xss攻击和sql注入,网上也有不少的教程,这里我们也采用filter的方式
定义XssHttpServletRequestWrapper
package com.zone.kinglims.config;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern;
/**
* @author zhangmy
* @date 2022/7/1 10:39
* @description 防xss攻击
*/
@Slf4j
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* post请求体
*/
private byte[] body;
/**
* 是否是文件上传
*/
private boolean fileUpload = false;
/**
* sql注入正则
*/
private static String badStrReg =
"\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
/**
* xss脚本正则
*/
private final static Pattern[] scriptPatterns = {
Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
};
public XssHttpServletRequestWrapper() {
super(null);
}
/**
* 构造函数 - 获取post请求体
* @param httpservletrequest
* @throws IOException
*/
public XssHttpServletRequestWrapper(HttpServletRequest httpservletrequest) throws IOException {
super(httpservletrequest);
String sessionStream = getBodyString(httpservletrequest);
body = sessionStream.getBytes(StandardCharsets.UTF_8);
}
/**
* 读取post请求体
* @param httpservletrequest
* @return
* @throws IOException
*/
private String getBodyString(HttpServletRequest httpservletrequest) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream ins = httpservletrequest.getInputStream();
boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpservletrequest);
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(httpservletrequest.getSession().getServletContext());
boolean isMultipart = commonsMultipartResolver.isMultipart(httpservletrequest);
if (isMultipartContent || isMultipart) {
fileUpload = true;
}
try (BufferedReader isr = new BufferedReader(new InputStreamReader(ins, StandardCharsets.UTF_8));) {
String line = "";
while ((line = isr.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw e;
}
return sb.toString();
}
/**
* 过滤springmvc中的 @RequestParam 注解中的参数
* @param s
* @return
*/
@Override
public String[] getParameterValues(String s) {
String[] str = super.getParameterValues(s);
if (str == null) {
return null;
}
int i = str.length;
String[] as1 = new String[i];
for (int j = 0; j < i; j++) {
as1[j] = cleanXSS(cleanSQLInject(str[j]));
}
log.info("XssHttpServletRequestWrapper净化后的请求为:========== {}", Arrays.toString(as1));
return as1;
}
/**
* 过滤request.getParameter的参数
* @param s
* @return
*/
@Override
public String getParameter(String s) {
String s1 = super.getParameter(s);
if (s1 == null) {
return null;
} else {
String s2 = cleanXSS(cleanSQLInject(s1));
log.info("XssHttpServletRequestWrapper净化后的请求为:========== {}", s2);
return s2;
}
}
/**
* 过滤请求体 json 格式的
* @return
* @throws IOException
*/
@Override
public ServletInputStream getInputStream() throws IOException {
// 非文件上传进行过滤
if (!fileUpload) {
// 获取body中的请求参数
JSONObject json = JSONObject.parseObject(new String(body));
// 校验并过滤xss攻击和sql注入
for (String k : json.keySet()) {
cleanSQLInject(cleanXSS(json.getString(k)));
}
}
// 将请求体参数流转 -- 流读取一次就会消失,所以我们事先读取之后就存在byte数组里边方便流转
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
/**
* 清除xss
* @param src 单个参数
* @return
*/
public String cleanXSS(String src) {
String temp = src;
// 校验xss脚本
for (Pattern pattern : scriptPatterns) {
temp = pattern.matcher(temp).replaceAll("");
}
// 校验xss特殊字符
temp = temp.replaceAll("\0|\n|\r", "");
temp = temp.replaceAll("<", "<").replaceAll(">", ">");
if (!temp.equals(src)) {
log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
log.error("原始输入信息-->" + temp);
throw new RuntimeException("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
}
return src;
}
/**
* 过滤sql注入 -- 需要增加通配,过滤大小写组合
* @param src 单个参数值
* @return
*/
public String cleanSQLInject(String src) {
// 非法sql注入正则
Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);
if (sqlPattern.matcher(src.toLowerCase()).find()) {
log.error("sql注入检查:输入信息存在SQL攻击!");
throw new RuntimeException("sql注入检查:参数含有非法攻击字符,已禁止继续访问!!");
}
return src;
}
}
上面的配置需要注意的是由于流读取一次之后就会关闭,所以我们在处理post请求获取请求体(流的方式获取)的时候,需要存起来,然后处理完xss和sql注入之后还要流转到正常业务参数中去,另外对于文件上传我们也可以跳过检查,参考上面的getInputStream()方法
覆写好了HttpServletRequestWrapper的方法之后,我们还需要顶一个filter去拦截操作
定义XssFilter
package com.zone.kinglims.config;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author zhangmy
* @date 2022/7/1 10:51
* @description
*/
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
public class XssFilter implements Filter {
/**
* 忽略权限检查的url地址
*/
private final String[] excludeUrls = new String[]{
"/system/config/upload",
"/system/manager/files/editor",
"/system/settings/session",
"/system/config/download",
"/system/manager/query/oss"
};
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
//获取请求你ip后的全部路径
String uri = req.getRequestURI();
//跳过不需要的Xss校验的地址
for (String str : excludeUrls) {
if (uri.contains(str)) {
arg2.doFilter(arg0, response);
return;
}
}
//注入xss过滤器实例
XssHttpServletRequestWrapper reqW = new XssHttpServletRequestWrapper(req);
//过滤
arg2.doFilter(reqW, response);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig1) throws ServletException {
}
}
这里我们配置了跳过检查的url,根据需要自行配置
最后@WebFilter要生效的话还需要在Springboot启动类上面加@ServletComponentScan注解
@EnableTransactionManagement
@SpringBootApplication
@EnableScheduling
@ServletComponentScan(basePackages = {"com.zone.kinglims.config"})
public class HugoApplication {
public static void main(String[] args) {
SpringApplication.run(HugoApplication.class, args);
}
/**
* 解决时区问题
*/
@PostConstruct
void setDefaultTimezone(){
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
}
//允许多请求地址多加斜杠 比如 /msg/list //msg/list
@Bean
public HttpFirewall httpFirewall() {
return new DefaultHttpFirewall();
}
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
// 单个数据大小
factory.setMaxFileSize(DataSize.of(204800000, DataUnit.KILOBYTES)); // KB,MB
/// 总上传数据大小
factory.setMaxRequestSize(DataSize.of(2048000000, DataUnit.KILOBYTES));
factory.setLocation(SystemStaticParameter.systemTempFilePath);
return factory.createMultipartConfig();
}
}
最后以上的sql注入配置是通过正则表达式匹配的,如果是参数中只有一个敏感字段,如下图
这样是不会有问题,如果是一个sql语句就会被拦截
评论区