springboot中使用過濾器,jsoup過濾XSS腳本詳解
springboot使用過濾器,jsoup過濾XSS腳本
- 背景:略
- 目標(biāo):完成request請求中的腳本過濾
- 技術(shù):filter,jsoup,requestWapper
1.把可能包含腳本的參數(shù)位置分析一下
post/put/delete: 請求的參數(shù)中,有可能是表單提交、也有可能是使用了@requestBody注解,那么參數(shù)就是json格式,位于request的流中。get/options等:可能存在于url參數(shù)中,也有可能是表單提交的預(yù)請求中,所以一般在能想到的位置都有可能存在,包括header中。
2.分析實現(xiàn)過程
2.1首先要從request請求中將各個需要過濾位置的參數(shù)取出來
2.2然后將參數(shù)取出來進(jìn)行過濾
2.3將過濾后的參數(shù)重新包裝成request傳遞下去
2.4在這期間,
- 需要準(zhǔn)備好jsoup過濾腳本的工具類;
- 需要自定義一個過濾器,并且在過濾器中添加匹配條件,如:那些url不需要過濾,那些請求方式必須進(jìn)行過濾;
- 對過濾器進(jìn)行配置,是否開啟,設(shè)置在整個過濾器鏈中的位置,設(shè)置過濾的白名單或者黑名單
- 所以就很清晰了我們過濾需要哪些類,哪些配置了
一個filter
一個requestWapper
一個jsoup工具類
一個filter的配置類
2.5進(jìn)行數(shù)據(jù)測試
3.代碼實現(xiàn)過程
3.1.jsoup依賴:
<!--screen xss --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.9.2</version> </dependency>
3.2jsoup工具類:JsoupUtil
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* @Auther: qianshanmuxue
* @Date: 2019/2/27 19:32
* @Description: xss Illegal label filtering
*/
public class JsoupUtil {
private static final Whitelist whitelist = Whitelist.simpleText();//jsoup白名單種類,有四種,每一種針對的標(biāo)簽類型不一樣,具體的可以ctrl+左鍵點擊simpleText,在jsoup源碼中有響應(yīng)的注釋和標(biāo)簽名單
//add myself white list label
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
static {
whitelist.addAttributes(":all", "style").addTags("p").addTags("strong");//將自定義標(biāo)簽添加進(jìn)白名單,除開白名單之外的標(biāo)簽都會被過濾
whitelist.preserveRelativeLinks(true);//這個配置的意思的過濾如果找不到成對的標(biāo)簽,就只過濾單個標(biāo)簽,而不用把后面所有的文本都進(jìn)行過濾。
//(之前在這個問題上折騰了很久,當(dāng)<script>標(biāo)簽只有一個時,會<script>標(biāo)簽后面的數(shù)據(jù)全部過濾)
}
public static String clean(String content) { //過濾方法
return Jsoup.clean(content, "", whitelist, outputSettings);
}
//test main
public static void main(String[] args) throws FileNotFoundException, IOException {
String text = "<a href=\"http://www.baidu.com/a\" onclick=\"alert(1);\"><strong><p>sss</p></strong></a><script>alert(0);</script>sss";
System.out.println(clean(text));
}
}
3.3request包裝類XssHttpServletRequestWrapper
import java.io.*;
import java.util.*;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.xxx.utils.JsoupUtil;
import org.jsoup.nodes.Document;
import org.springframework.util.StringUtils;
/**
* @Auther: qianshanmuxue
* @Date: 2019/2/27 16:24
* @Description:request wapper use to get request parameter and request bdoy data and wapper another request
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { //因為我們需要獲取request中的數(shù)據(jù),所以需要繼承java底層中HttpServletRequestWrapper這個類,重寫父類中的某些方法,獲取相應(yīng)位置的參數(shù)
private HttpServletRequest orgRequest = null;
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
@Override
public ServletInputStream getInputStream() throws IOException {//get
BufferedReader br = new BufferedReader(new InputStreamReader(orgRequest.getInputStream()));
String line = br.readLine();
String result = "";
if (line != null) {
result += clean(line);
}
return new WrappedServletInputStream(new ByteArrayInputStream(result.getBytes()));
}
@Override
public String getParameter(String name) {
if (("content".equals(name) || name.endsWith("WithHtml"))) {
return super.getParameter(name);
}
name = clean(name);
String value = super.getParameter(name);
if (!StringUtils.isEmpty(value)) {
value = clean(value);
}
return value;
}
@Override
public Map getParameterMap() {
Map map = super.getParameterMap();
// 返回值Map
Map<String, String> returnMap = new HashMap<String, String>();
Iterator entries = map.entrySet().iterator();
Map.Entry entry;
String name = "";
String value = "";
while (entries.hasNext()) {
entry = (Map.Entry) entries.next();
name = (String) entry.getKey();
Object valueObj = entry.getValue();
if (null == valueObj) {
value = "";
} else if (valueObj instanceof String[]) {
String[] values = (String[]) valueObj;
for (int i = 0; i < values.length; i++) {
value = values[i] + ",";
}
value = value.substring(0, value.length() - 1);
} else {
value = valueObj.toString();
}
returnMap.put(name, clean(value).trim());
}
return returnMap;
}
@Override
public String[] getParameterValues(String name) {
String[] arr = super.getParameterValues(name);
if (arr != null) {
for (int i = 0; i < arr.length; i++) {
arr[i] = clean(arr[i]);
}
}
return arr;
}
/**
* get org request
*
* @return
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* wapper request
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).getOrgRequest();
}
return req;
}
public String clean(String content) {
String result = JsoupUtil.clean(content);
return result;
}
private class WrappedServletInputStream extends ServletInputStream {
public void setStream(InputStream stream) {
this.stream = stream;
}
private InputStream stream;
public WrappedServletInputStream(InputStream stream) {
this.stream = stream;
}
@Override
public int read() throws IOException {
return stream.read();
}
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
}
}
3.4filter-XssFilter
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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;
import javax.servlet.http.HttpServletResponse;
/**
* @Auther: qianshanmuxue
* @Date: 2019/2/27 16:25
* @Description:
*/
//@WebFilter
//@Component 在這里可以不用這個注解,以為后面我們會在config中去配置這個filter,在這里只需要實現(xiàn) Filter 接口實現(xiàn)相應(yīng)的方法就ok
public class XssFilter implements Filter {
private static boolean IS_INCLUDE_RICH_TEXT = false;//用于接收配置中的參數(shù),決定這個過濾器是否開啟
public List<String> excludes = new ArrayList<String>();//用于接收配置中的參數(shù),決定哪些是不需要過濾的url(在這里,也可以修改handleExcludeURL()方法中相應(yīng)的代碼,使其變更為只需要過濾的url)
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (handleExcludeURL(req, resp)) {
chain.doFilter(request, response);
return;
}
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
/**
*此方法是決定對當(dāng)前url是否執(zhí)行過濾,
*在這里沒有使用請求方法(post/put)來匹配,因為在本項目中使用url匹配更適合(因為get和其他請求方式也需要進(jìn)行過濾),如果你有興趣可以把這個方法更改為匹配請求方法進(jìn)行過濾
**/
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
if ((excludes == null || excludes.isEmpty())&&IS_INCLUDE_RICH_TEXT) {
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;
}
/**
*過濾器初始化,從配置類中獲取參數(shù),用于初始化兩個參數(shù)(是否開啟,排除指定的url list)
*
*/
@Override
public void init(FilterConfig arg0) throws ServletException {
String isIncludeRichText = arg0.getInitParameter("isIncludeRichText");
if (StringUtils.isNotBlank(isIncludeRichText)) {
IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
}
String temp = arg0.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.5filter的配置類:XssConfig
import com.xxx.filter.XssFilter;
import com.google.common.collect.Maps;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* @Auther: qianshanmuxue
* @Date: 2019/2/27 16:49
* @Description: xss filter config
*/
@Configuration
public class XssConfig {
@Bean
public FilterRegistrationBean xssFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new XssFilter());
filterRegistrationBean.setOrder(1);//filter order ,set it first
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*"); //set filter all url mapping
Map<String, String> initParameters = Maps.newHashMap();
initParameters.put("excludes", "/oauth/token");///white list url
initParameters.put("isIncludeRichText", "true");//enable or disable
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}
}
調(diào)試截圖:
請求:

程序截圖:

運行結(jié)果:

可以看到body中 的腳本已經(jīng)被過濾了,
然后其他的截圖我就不發(fā)了,還有一種思路就是在過濾器中把字符轉(zhuǎn)義。
感謝luckpet大佬的提示
1 BufferedReader 使用完需要關(guān)閉 ;
2 對于一些拿postman等工具的朋友,拼接json的話會有換行 這里result += clean(line); 需要改成: while((line = br.readLine()) != null){ if (line != null) { result += line; } }
使用jsoup防止XSS攻擊
前陣子項目國測后,打開一個項目頁面,莫名其妙彈出xss,搜了全局也沒找到alert("xss"),問了一下項目經(jīng)理,原來是國測做防注入的時候,在添加數(shù)據(jù)的時候做的,一臉懵逼。
查了一下資料,以前做項目的時候都沒想到這個問題,如果保存一段script腳本,查數(shù)據(jù)的時候,這段腳本就會被執(zhí)行,這東西后果挺嚴(yán)重啊,如果是在桌面外彈框,執(zhí)行個挖礦腳本,這玩意不得了啊,厲害,長知識了。。。
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version> </dependency>
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
教你快速搭建sona服務(wù)及idea使用sona的方法
Sonar 是一個用于代碼質(zhì)量管理的開放平臺。通過插件機制,Sonar 可以集成不同的測試工具,代碼分析工具,以及持續(xù)集成工具,本文給大家分享搭建sona服務(wù)及idea使用sona的方法,感興趣的朋友一起看看吧2021-06-06
詳解SpringCloud Finchley Gateway 統(tǒng)一異常處理
這篇文章主要介紹了詳解SpringCloud Finchley Gateway 統(tǒng)一異常處理,非常具有實用價值,需要的朋友可以參考下2018-10-10
SpringMvc3+extjs4實現(xiàn)上傳與下載功能
這篇文章主要為大家詳細(xì)介紹了SpringMvc3+extjs4實現(xiàn)上傳與下載功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06
SpringBoot實現(xiàn)動態(tài)增刪啟停定時任務(wù)的方式
在spring?boot中,可以通過@EnableScheduling注解和@Scheduled注解實現(xiàn)定時任務(wù),也可以通過SchedulingConfigurer接口來實現(xiàn)定時任務(wù),但是這兩種方式不能動態(tài)添加、刪除、啟動、停止任務(wù),本文給大家介紹SpringBoot實現(xiàn)動態(tài)增刪啟停定時任務(wù)的方式,感興趣的朋友一起看看吧2024-03-03
java中 IO 常用IO操作類繼承結(jié)構(gòu)分析
本篇文章小編為大家介紹,java中 IO 常用IO操作類繼承結(jié)構(gòu)分析。需要的朋友參考下2013-04-04

