一、先来个简介#
什么是XSS?
百度百科的解释: XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。
它与SQL注入攻击类似,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,通过插入恶意脚本,实现对用户游览器的控制,获取用户的一些信息。
二、XSS分类#
xss攻击可以分成两种类型:
1.非持久型攻击
2.持久型攻击
非持久型xss攻击:顾名思义,非持久型xss攻击是一次性的,仅对当次的页面访问产生影响。非持久型xss攻击要求用户访问一个被攻击者篡改后的链接,用户访问该链接时,被植入的攻击脚本被用户游览器执行,从而达到攻击目的。
持久型xss攻击:持久型xss,会把攻击者的数据存储在服务器端,攻击行为将伴随着攻击数据一直存在。
也可以分成三类:
反射型:经过后端,不经过数据库
存储型:经过后端,经过数据库
三、代码走起
先加pom文件加上依赖
<!-- xss过滤组件 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.4</version>
</dependency>
1.首先是要写个过滤器的包装类,这也是实现XSS攻击过滤的核心代码
```java
package com.jgtl.smurfs.common.filter;
import com.alibaba.fastjson.JSON;
import com.jgtl.smurfs.common.util.IPUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* <code>{@link XssHttpServletRequestWrapper}</code>
*
* @author
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final Logger log = LoggerFactory.getLogger(getClass());
private static String key = "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|+|$|..";
private static Set<String> notAllowedKeyWords = new HashSet<String>(0);
private static String replacedString = "INVALID";
static {
String keyStr[] = key.split("\\|");
for (String str : keyStr) {
notAllowedKeyWords.add(str);
}
}
private String currentUrl;
private String currentIp;
/**
* @param request
*/
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
currentUrl = request.getRequestURI();
currentIp = IPUtils.getIpAddr(request);
}
/**
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
*/
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return cleanXSS(value);
}
@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 Map<String, String[]> getParameterMap() {
Map<String, String[]> values = super.getParameterMap();
if (values == null) {
return null;
}
Map<String, String[]> result = new HashMap<>();
for (String key : values.keySet()) {
String encodedKey = cleanXSS(key);
int count = values.get(key).length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = cleanXSS(values.get(key)[i]);
}
result.put(encodedKey, encodedValues);
}
return result;
}
/**
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。
* 如果需要获得原始的值,则通过super.getHeaders(name)来获取
* getHeaderNames 也可能需要覆盖
*/
@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 {
String str = getRequestBody(super.getInputStream());
try {
Map<String, Object> map = JSON.parseObject(str, Map.class);
Map<String, Object> resultMap = new HashMap<>(map.size());
for (String key : map.keySet()) {
Object val = map.get(key);
if (map.get(key) instanceof String) {
resultMap.put(key, cleanXSS(val.toString()));
} else {
resultMap.put(key, val);
}
}
str = JSON.toJSONString(resultMap);
} catch (Exception e) {
//e.printStackTrace();
}
final ByteArrayInputStream bais = new ByteArrayInputStream(str.getBytes());
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) {
}
};
}
private String getRequestBody(InputStream stream) {
String line = "";
StringBuilder body = new StringBuilder();
int counter = 0;
// 读取POST提交的数据内容
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.forName("UTF-8")));
try {
while ((line = reader.readLine()) != null) {
body.append(line);
counter++;
}
} catch (IOException e) {
e.printStackTrace();
}
return body.toString();
}
private String cleanXSS(String valueP) {
// You'll need to remove the spaces from the html entities below
String value = valueP.replaceAll("<", "<").replaceAll(">", ">");
value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
// value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
// value = value.replaceAll("'", "& #39;");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
value = cleanSqlKeyWords(value);
return value;
}
private String cleanSqlKeyWords(String value) {
String paramValue = value;
for (String keyword : notAllowedKeyWords) {
if (paramValue.length() >= keyword.length()
// && (paramValue.contains(" "+keyword)||paramValue.contains(keyword+" ")||paramValue.contains(" "+keyword+" "))) {
&& (paramValue.trim().contains(keyword))) {
paramValue = StringUtils.replace(paramValue, keyword, replacedString);
log.error("IP【" + this.currentIp + "】" + this.currentUrl + "已被过滤,因为参数中包含不允许sql的关键词(" + keyword
+ ")" + ";参数:" + value + ";过滤后的参数:" + paramValue);
}
}
return paramValue;
}
}
2.接下来的事情很简单写个过滤器
```java
package com.jgtl.smurfs.common.filter;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 拦截防止xss注入
* 通过Jsoup过滤请求参数内的特定字符
*
* @author yangwk
*/
public class XssFilter implements Filter {
private static final Logger logger = LogManager.getLogger();
/**
* 是否过滤富文本内容
*/
private static boolean IS_INCLUDE_RICH_TEXT = false;
public List<String> excludes = new ArrayList<>();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("xss filter is open");
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (handleExcludeURL(req, resp)) {
filterChain.doFilter(request, response);
return;
}
// filterChain.doFilter(request, response);
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
filterChain.doFilter(xssRequest, response);
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
if (excludes == null || excludes.isEmpty()) {
return false;
}
String url = request.getServletPath();
for (String pattern : excludes) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("xss filter init ====================");
}
String isIncludeRichText = filterConfig.getInitParameter("isIncludeRichText");
if (StringUtils.isNotBlank(isIncludeRichText)) {
IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
}
String temp = filterConfig.getInitParameter("excludes");
if (temp != null) {
String[] url = temp.split(",");
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}
}
}
@Override
public void destroy() {
}
}
3注入Bean
```java
package com.jgtl.smurfs.config;
import com.google.common.collect.Maps;
import com.jgtl.smurfs.common.filter.XssFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
@Configuration
public class XssConfig {
/**
* xss过滤拦截器
*/
@Bean
public FilterRegistrationBean xssFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new XssFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
Map<String, String> initParameters = Maps.newHashMap();
initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
initParameters.put("isIncludeRichText", "true");
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}
}
4.补工具类
package com.jgtl.smurfs.common.util;
import cn.hutool.core.util.StrUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <br/>=================================<br/>
* 功能描述:xss攻击字典数据工具类
* <br/>=================================<br/>
*/
public class XssDictUtils {
// 防止xss攻击过滤字典map
private static final Map<String, String> xssDicpMap = new HashMap<String, String>();
/**
*<br/>=================================<br/>
*方法描述:防止xss攻击正则表达式字典收集
*<br/>=================================<br/>
*修改功能:添加对于null、NULL、0x0d、0x0a的替换规则
*<br/>=================================<br/>
*修改功能:null、NULL替换为"",打开<、>、&替换规则
*<br/>=================================<br/>
*修改功能:屏蔽&替换规则
*<br/>=================================<br/>
*修改功能:新增关键字过滤 load、create、union、rename
*<br/>=================================<br/>
*修改功能:新增关键字过滤 eval、and、or
*<br/>=================================<br/>
*修改功能:去掉关键字过滤 &
*<br/>=================================<br/>
*/
static {
xssDicpMap.put("[s|S][c|C][r|R][i|I][p|P][t|T]", "");
xssDicpMap.put("[\\\"\\\'][\\s]*[j|J][a|A][v|V][a|A][s|S][c|C][r|R][i|I][p|P][t|T]:(.*)[\\\"\\\']", "");
xssDicpMap.put("[e|E][v|V][a|A][l|L]\\((.*)\\)", "");
xssDicpMap.put("[v|V][b|B][s|S][c|C][r|R][i|I][p|P][t|T]", "");
xssDicpMap.put("[s|S][o|O][u|U][r|R][c|C][e|E]", "");
// xssDicpMap.put("<(\"[^\"]*\"|\'[^\']*\'|[^\'\">])*>", "");
xssDicpMap.put("[e|E][x|X][p|P][r|R][e|E][s|S][s|S][i|I][o|O][n|N]", "");
// xssDicpMap.put("[w|W][i|I][n|N][d|D][o|O][w|W]", "");
xssDicpMap.put("[l|L][o|O][c|C][a|A][t|T][i|I][o|O][n|N]", "");
xssDicpMap.put("[d|D][o|O][c|C][u|U][m|M][e|E][n|N][t|T]", "");
xssDicpMap.put("[c|C][o|O][o|O][k|K][i|I][e|E]", "");
xssDicpMap.put("[a|A][l|L][e|E][r|R][t|T]", "");
xssDicpMap.put("[o|O][p|P][e|E][n|N]", "");
xssDicpMap.put("[s|S][h|H][o|O][w|W][d|D][i|I][a|A][l|L][o|O][g|G]", "");
xssDicpMap.put("[s|S][h|H][o|O][w|W][m|M][o|O][d|D][a|A][l|L][d|D][i|I][a|A][l|L][o|O][g|G]", "");
xssDicpMap.put("[s|S][h|H][o|O][w|W][m|M][o|O][d|D][e|E][l|L][e|E][s|S][s|S][d|D][i|I][a|A][l|L][o|O][g|G]", "");
// xssDicpMap.put("(window\\.location|window\\.|\\.location|document\\.cookie|document\\.|alert\\(.*?\\)|window\\.open\\()*", "");
xssDicpMap.put("<+\\s*\\w*\\s*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|"
+ "ondragleave|ondragover|ondragstart|ondrop|οnerrοr=|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|"
+ "onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|"
+ "onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|"
+ "onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|"
+ "onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|"
+ "onselectstart|onstart|onstop|onsubmit|onunload)+\\s*=+", "");
xssDicpMap.put("<", "<");
xssDicpMap.put(">", ">");
// xssDicpMap.put("&", "&");
xssDicpMap.put("[t|T][a|A][b|B][l|L][e|E]", "");
xssDicpMap.put("[d|D][a|A][t|T][a|A][b|B][a|A][s|S][e|E]", "");
xssDicpMap.put("[i|I][n|N][s|S][e|E][r|R][t|T]", "");
xssDicpMap.put("[u|U][p|P][d|D][a|A][t|T][e|E]", "");
xssDicpMap.put("[d|D][e|E][l|L][e|E][t|T][e|E]", "");
xssDicpMap.put("[t|T][r|R][u|U][n|N][c|C][a|A][t|T][e|E]", "");
xssDicpMap.put("[s|S][e|E][l|L][e|E][c|C][t|T]", "");
xssDicpMap.put("[a|A][l|L][t|T][e|E][r|R]", "");
xssDicpMap.put("[n|N][u|U][l|L][l|L]", "\"\"");
xssDicpMap.put("[d|D][e|E][s|S][c|C]", "\"\"");
xssDicpMap.put("0[x|X]0[d|D]", "");
xssDicpMap.put("[l|L][o|O][a|A][d|D]", "\"\"");
xssDicpMap.put("[r|R][e|E][n|N][a|A][m|M][e|E]", "");
//xssDicpMap.put("[c|C][r|R][e|E][a|A][t|T][e|E]", "");
xssDicpMap.put("[u|U][n|N][i|I][o|O][n|N]", "");
xssDicpMap.put("[e|E][x|X][i|I][s|S][t|T][s|S]", "");
xssDicpMap.put("[e|E][v|V][a|A][l|L]", "");
xssDicpMap.put("[a|A][n|N][d|D]", "");
xssDicpMap.put("[o|O][r|R] ", "");
}
public static String xssValid(String value) {
if (StrUtil.isEmpty(value)) {
return value;
}
for (String key : xssDicpMap.keySet()) {
Pattern p = Pattern.compile(key);
Matcher m = p.matcher(value);
value = m.replaceAll(xssDicpMap.get(key));
}
return value;
}
public static void main(String[] args) {
String temp = "asset_m_windows";
System.out.println(XssDictUtils.xssValid(temp));
}
}