手写SpringBoot项目XSS攻击过滤器实现
Posted 金色的鱼儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写SpringBoot项目XSS攻击过滤器实现相关的知识,希望对你有一定的参考价值。
一、先来个简介
什么是XSS?
百度百科的解释: XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。
它与SQL注入攻击类似,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,通过插入恶意脚本,实现对用户游览器的控制,获取用户的一些信息。
二、XSS分类
xss攻击可以分成两种类型:
1.非持久型攻击
2.持久型攻击
非持久型xss攻击:顾名思义,非持久型xss攻击是一次性的,仅对当次的页面访问产生影响。非持久型xss攻击要求用户访问一个被攻击者篡改后的链接,用户访问该链接时,被植入的攻击脚本被用户游览器执行,从而达到攻击目的。
持久型xss攻击:持久型xss,会把攻击者的数据存储在服务器端,攻击行为将伴随着攻击数据一直存在。
也可以分成三类:
反射型:经过后端,不经过数据库
存储型:经过后端,经过数据库
三、代码走起
先加pom文件加上依赖
1 <dependency> 2 <groupId>org.apache.commons</groupId> 3 <artifactId>commons-text</artifactId> 4 <version>1.4</version> 5 </dependency>
1.首先是要写个过滤器的包装类,这也是实现XSS攻击过滤的核心代码。
这边因为业务需要,我拆分了两种的xss包装类,针对于不同的接口来使用。
第一中写法如下:
package com.hrt.zxxc.fxspg.xss;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
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;
/**
* @program: fxspg
* @description: XSS过滤具体核心代码(第一种)
* @author: liumingyu
* @date: 2020-01-10 14:28
**/
public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {
//声明sql注入的关键词key
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("\|");
//将key添加到Set集合中
for (String str : keyStr) {
notAllowedKeyWords.add(str);
}
}
/**
* @return
* @Author liumingyu
* @Description //TODO 构造函数,传入参数,执行超类
* @Date 2020/1/10 2:29 下午
* @Param [request]
**/
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
/**
* @return java.lang.String
* @Author liumingyu
* @Description //TODO 重写getParameter方法 ,getParameter方法是直接通过request获得querystring类型的入参调用的方法
* @Date 2020/1/10 2:31 下午
* @Param [name]
**/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (!StringUtils.isEmpty(value)) {
//调用Apache的工具类:StringEscapeUtils.escapeHtml4
value = StringEscapeUtils.escapeHtml4(value);
value = cleanSqlKeyWords(value);
}
return value;
}
/**
* @return java.lang.String[]
* @Author liumingyu
* @Description //TODO 重写getParameterValues
* @Date 2020/1/10 2:32 下午
* @Param [name]
**/
@Override
public String[] getParameterValues(String name) {
String[] parameterValues = super.getParameterValues(name);
if (parameterValues == null) {
return null;
}
for (int i = 0; i < parameterValues.length; i++) {
String value = parameterValues[i];
//调用Apache的工具类:StringEscapeUtils.escapeHtml4
parameterValues[i] = StringEscapeUtils.escapeHtml4(value);
parameterValues[i] = cleanSqlKeyWords(parameterValues[i]);
}
return parameterValues;
}
@Override
public String getHeader(String name) {
//过滤xss攻击
String value = StringEscapeUtils.escapeHtml4(super.getHeader(name));
if (value == null){
return null;
}
//过滤sql注入
return cleanSqlKeyWords(value);
}
@Override
public String getQueryString() {
return StringEscapeUtils.escapeHtml4(super.getQueryString());
}
/**
* @return javax.servlet.ServletInputStream
* @Author liumingyu
* @Description //TODO 过滤JSON数据中的XSS攻击
* @Date 2020/1/10 4:58 下午
* @Param []
**/
@Override
public ServletInputStream getInputStream() throws IOException {
//调用方法将流数据return为String
String str = getRequestBody(super.getInputStream());
//如果str为"",则返回0
if ("".equals(str)) {
return new ServletInputStream() {
@Override
public int read() throws IOException {
return 0;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
//将数据存放至map
Map<String, Object> map = JSON.parseObject(str, Map.class);
//声明个存放过滤后数据的hashMap
Map<String, Object> resultMap = new HashMap<>(map.size());
//开始遍历数据
for (String key : map.keySet()) {
Object val = map.get(key);
//如果key=富文本字段名,就不去过滤
if ("content".equals(key)) {
//不过滤
resultMap.put(key, val);
} else {
//不为富文本字段才会过滤
if (map.get(key) instanceof String) {
//通过escapeHtml4去过滤
resultMap.put(key, StringEscapeUtils.escapeHtml4(cleanSqlKeyWords(val.toString())));
} else {
//不过滤
resultMap.put(key, val);
}
}
}
str = JSON.toJSONString(resultMap);
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
/**
* @return java.lang.String
* @Author liumingyu
* @Description //TODO 获取JSON数据
* @Date 2020/1/10 4:58 下午
* @Param [stream]
**/
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();
}
if (body == null) {
return "";
}
//最后返回数据
return body.toString();
}
/**
* @Author liumingyu
* @Description //TODO 过滤可能造成sql注入的关键字
* @Date 2020/1/13 9:11 上午
* @Param [value]
* @return java.lang.String
**/
private String cleanSqlKeyWords(String value) {
String paramValue = value;
for (String keyword : notAllowedKeyWords) {
if (paramValue.length() > keyword.length() + 4
&& (paramValue.contains(" "+keyword)||paramValue.contains(keyword+" ")||paramValue.contains(" "+keyword+" "))) {
paramValue = StringUtils.replace(paramValue, keyword, replacedString);
}
}
return paramValue;
}
}
第二种如下:
package com.hrt.zxxc.fxspg.xss; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * @ClassName XssHttpServletRequestWrapper3 * @Description TODO xss过滤核心代码(第二种) * @Author liumingyu * @Date 2020/2/4 15:51 * @Version 1.0 **/ public class XssHttpServletRequestWrapper3 extends HttpServletRequestWrapper { 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 byte[] body; /** * @Author liumingyu * @Description //TODO 构造函数 * @Date 2020/2/5 19:58 * @Param [servletRequest] * @return **/ public XssHttpServletRequestWrapper3(HttpServletRequest servletRequest) throws IOException { super(servletRequest); currentUrl = servletRequest.getRequestURI(); //获取到json数据 this.body = StreamUtils.copyToByteArray(servletRequest.getInputStream()); } /** * @Author liumingyu * @Description //TODO 重写该方法过滤json数据 * @Date 2020/2/5 19:59 * @Param [] * @return javax.servlet.ServletInputStream **/ @Override public ServletInputStream getInputStream() throws IOException { ServletInputStream inputStream = null; String bodyStr = new String(body); if (!StringUtils.isEmpty(bodyStr)) { bodyStr = xssEncode(bodyStr, 1); bodyStr = cleanSqlKeyWords(bodyStr); final ByteArrayInputStream bais = new ByteArrayInputStream(bodyStr.getBytes()); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return bais.read(); } }; } return inputStream; } /** * @Author liumingyu * @Description //TODO 将容易引起xss漏洞的半角字符直接替换成全角字符 * @Date 2020/2/5 19:57 * @Param [s, type] * @return java.lang.String **/ private static String xssEncode(String s, int type) { 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); if (type == 0) { 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; // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c case ‘%‘: processUrlEncoder(sb, s, i); break; default: sb.append(c); break; } } else { switch (c) { case ‘>‘: // 全角大于号 sb.append(‘>‘); break; case ‘<‘: // 全角小于号 sb.append(‘<‘); break; case ‘&‘: // 全角&符号 sb.append(‘&‘); break; case ‘#‘: // 全角井号 sb.append(‘#‘); break; // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c case ‘%‘: 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) { // %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)); } /**覆盖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); } private String cleanXSS(String valueP) { 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() + 3 && (paramValue.contains(" "+keyword)||paramValue.contains(keyword+" ")||paramValue.contains(" "+keyword+" "))) { paramValue = StringUtils.replace(paramValue, keyword, replacedString); System.out.println(this.currentUrl + "已被过滤,因为参数中包含不允许sql的关键词(" + keyword + ")"+";参数:"+value+";过滤后的参数:"+paramValue); } } return paramValue; }
2.看到这里你就已经完成了一半了加油!接下来的事情很简单写个过滤器就over 。
package com.hrt.zxxc.fxspg.xss; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.context.annotation.Bean; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName XssFilter2 * @Description TODO * @Author liumingyu * @Date 2020/2/4 15:45 * @Version 1.0 **/ @WebFilter @Component public class XssFilter3 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //获取请求数据 HttpServletRequest req = (HttpServletRequest) servletRequest; //获取请求的url路径 String path = ((HttpServletRequest) servletRequest).getServletPath(); //声明要被忽略请求的数组 String[] exclusionsUrls = {".js", ".gif", ".jpg", ".jpeg", ".png", ".css", ".ico", "health", "uploadPic", "file", "savePrintTemp", "goods/create", "goods/edit", "goodsBatchImport", "goodsBatchExport","platformtestreport"}; //声明带有富文本的接口数组 String[] richTextUrls = {"addarticles", "editarticles", "advert"}; //第一种xss过滤 XssAndSqlHttpServletRequestWrapper XssAndSqlHttpServletRequestWrapper = new XssAndSqlHttpServletRequestWrapper(req); //遍历忽略的请求数组,若该接口url为忽略的就调用原本的过滤器,不走xss过滤 for (String str : exclusionsUrls) { if (path.contains(str)) { filterChain.doFilter(servletRequest, servletResponse); return; } } //若为带有富文本的接口,走第一种xss过滤 for (String rtu : richTextUrls) { if (path.contains(rtu)) { filterChain.doFilter(XssAndSqlHttpServletRequestWrapper, servletResponse); return; } } //将请求放入XSS请求包装器中,返回过滤后的值 XssHttpServletRequestWrapper3 xssRequestWrapper3 = new XssHttpServletRequestWrapper3(req); filterChain.doFilter(xssRequestWrapper3, servletResponse); } @Override public void destroy() { } }
以上是关于手写SpringBoot项目XSS攻击过滤器实现的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot 项目添加抵御跨站防御脚本(XSS)攻击功能
SpringBoot整合XssFilter,Jsoup等实现请求参数的过滤,处理Xss攻击及sql注入