springboot后台xss防御过滤器配置示例
2021-06-25 本文已影响0人
haiyong6
XSS攻击主要是对前端插入可执行脚本来获取用户的敏感信息,对账户安全危害较大。
下面这篇博客对XSS的概念以及攻击类型做了很详细的解释,可以学习参考下。
https://www.cnblogs.com/tugenhua0707/p/10909284.html
XSS对于后台来说,可以在比如上传文件的时候拦截上传请求,把文件地址更改成可执行的javaScript脚本语句插入数据库,这样在页面上查询列表时查出来的可点击的地址链接便是可执行的前端脚本,比如这个接口参数:
{"filePath":"javascript:alert(/xss/)"}
如果地址被修改了成了这样,那点击链接之后前端会执行这段javascript脚本语句。
针对于这种情况,最简单的可以在后台接收参数的时候校验下filePath字段的格式是不是URL就行了。可以用实体类字段里@URL注解
import org.hibernate.validator.constraints.URL;
也可以在过滤器里写一个根据业务的统一的过滤逻辑。
新建一个过滤器类,比如:XssFilter.java
package com.ly.mp.project.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class XssFilter implements Filter {
FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper(
(HttpServletRequest) request), response);
}
}
新建XssHttpServletRequestWrapper继承HttpServletRequestWrapper
package com.ly.mp.project.filter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.web.util.HtmlUtils;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.ly.mp.project.util.BusinessUtil;
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
private String body;
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
body = BusinessUtil.getBodyString(request);
System.out.println("body==" + body);
}
@Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null)
return null;
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = cleanXSS(values[i]);
}
return encodedValues;
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null)
return null;
return cleanXSS(value);
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if (value == null)
return null;
return cleanXSS(value);
}
@Override
public ServletInputStream getInputStream() throws IOException {
ServletInputStream inputStream = null;
if (StringUtils.isNotEmpty(body)) {
body = cleanXSS(body);
//new ServletInputStream()
//InputStream is = new ByteArrayInputStream(body.getBytes("UTF-8"));
// inputStream = (ServletInputStream) is;
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes("UTF-8"));
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 listener) {
}
};
}
return inputStream;
}
private String cleanXSS(String value) {
value = value.replace("script:", "script:");
StringBuilder buffer = new StringBuilder(value.length() + 16);
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
switch (c) {
// case '&':
// buffer.append("&");
// break;
// case '<':
// buffer.append("<");
// break;
// case '>':
// buffer.append(">");
// break;
// case '"':
// buffer.append(""");
// break;
// case '\'':
// buffer.append("'");
// break;
// case '/':
// buffer.append("/");
// break;
// default:
// buffer.append(c);
// break;
case '>':
// 全角大于号
buffer.append('>');
break;
case '<':
// 全角小于号
buffer.append('<');
break;
case '&':
// 全角&符号
buffer.append('&');
break;
case '\\':
// 全角斜线
buffer.append('\');
break;
case '#':
// 全角井号
buffer.append('#');
break;
// < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c
case '%':
processUrlEncoder(buffer, value, i);
break;
default:
buffer.append(c);
break;
}
}
return buffer.toString();
}
public static void processUrlEncoder(StringBuilder sb, String s, int index) {
if (s.length() >= index + 2) {
// %3c, %3C
if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'c' || s.charAt(index + 2) == 'C')) {
sb.append('<');
return;
}
// %3c (0x3c=60)
if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '0') {
sb.append('<');
return;
}
// %3e, %3E
if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'e' || s.charAt(index + 2) == 'E')) {
sb.append('>');
return;
}
// %3e (0x3e=62)
if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '2') {
sb.append('>');
return;
}
}
sb.append(s.charAt(index));
}
/**
* 获取最原始的request
*
* @return
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取最原始的request的静态方法
*
* @return
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).getOrgRequest();
}
return req;
}
}
这个类中重写了getParameterValues、getParameter、getHeader、getInputStream等方法,在业务层获取参数的时候无非都是调用这几个方法获取,在这几个方法里参数都被cleanXSS这个自定义的方法过滤了,里面的过滤逻辑可根据业务需求自定义,这里做演示替换了一部分符号(不太合理)。
最后把过滤器注入到spring使之生效即可
在启动类里注入或者新建配置类注入
@Bean
public FilterRegistrationBean myFilterRegistration() {
FilterRegistrationBean regist = new FilterRegistrationBean(new XssFilter());
// 过滤全部请求
regist.addUrlPatterns("/*");//过滤url
regist.setOrder(1);//过滤器顺序
return regist;
}
参考:
https://www.cnblogs.com/tugenhua0707/p/10909284.html
https://www.cnblogs.com/kinome/p/12468421.html