SpringBoot项目XSS攻击过滤防御实现

tech2022-08-04  153

一、先来个简介# 什么是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("<", "&lt;").replaceAll(">", "&gt;"); 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("<", "&lt;"); xssDicpMap.put(">", "&gt;"); // xssDicpMap.put("&", "&amp;"); 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)); } }
最新回复(0)