springboot中使用過濾器,jsoup過濾XSS指令碼詳解
阿新 • • 發佈:2021-12-20
目錄
- springboot使用過濾器,oup過濾XSS
- 1.把可能包含指令碼的引數位置分析一下
- 2.分析實現過程
- 3.程式碼實現過程
- 使用jsoup防止XSS攻擊
springboot使用過濾器,jsoup過濾XSS指令碼
- 背景:略
- 目標:完成request請求中的指令碼過濾
- 技術:filter,jsoup,requestWapper
1.把可能包含指令碼的引數位置分析一下
post
/put
/delete
: 請求的引數中,有可能是表單提交、也有可能是使用了@requestBody註解,那麼引數就是json格式,位於request的流中。get
/options
等:可能存在於url引數中,也有可能是表單提交的預請求中,所以一般在能想到的位置都有可能存在,包括header中。
2.分析實現過程
2.1首先要從request請求中將各個需要過濾位置的引數取出來
2.2然後將引數取出來進行過濾
2.3將過濾後的引數重新包裝成request傳遞下去
2.4在這期間,
- 需要準備好jsoup過濾指令碼的工具類;
- 需要自定義一個過濾器,並且在過濾器中新增匹配條件,如:那些url不需要過濾,那些請求方式必須進行過濾;
- 對過濾器進行配置,是否開啟,設定在整個過濾器鏈中的位置,設定過濾的白名單或者黑名單
- 所以就很清晰了我們過濾需要哪些類,哪些配置了
一個filter
一個requestWapper
一個jsoup工具類
一個filter的配置類
2.5進行資料測試
3.程式碼實現過程
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 .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.jzcCEkGXsimpleText();//jsoup白名單種類,有四種,每一種針對的標籤型別不一樣,具體的可以ctrl+左鍵點選www.cppcns.comsimpleText,在jsoup原始碼中有響應的註釋和標籤名單 //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");//將自定義標籤新增進白名單,除開白名單之外的標籤都會被過濾 whitelist.preserveRelativeLinks(true);//這個配置的意思的過濾如果找不到成對的標籤,就只過濾單個標籤,而不用把後面所有的文字都進行過濾。 //(之前在這個問題上折騰了很久,當<script>標籤只有一個時,會<script>標籤後面的資料全部過濾) } 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中的資料,所以需要繼承java底層中HttpServletRequestWrapper這個類,重寫父類中的某些方法,獲取相應位置的引數
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"))) {
http://www.cppcns.com 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,在這裡只需要實現 Filter 介面實現相應的方法就ok public class XssFilter implements Filter { private static boolean IS_INCLUDE_RICH_TEXT = false;//用於接收配置中的引數,決定這個過濾器是否開啟 public List<String> excludes = new ArrayList<String>();//用於接收配置中的引數,決定哪些是不需要過濾的url(在這裡,也可以修改handleExcludeURL()方法中相應的程式碼,使其變更為只需要過濾的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); } /** *此方法是決定對當前url是否執行過濾, *在這裡沒有使用請求方法(post/put)來匹配,因為在本專案中使用url匹配更適合(因為get和其他請求方式也需要進行過濾),如果你有興趣可以把這個方法更改為匹配請求方法進行過濾 **/ 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; } /** *過濾器初始化,從配置類中獲取引數,用於初始化兩個引數(是否開啟,排除指定的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; } }
除錯截圖:
請求:
程式截圖:
執行結果:
可以看到body中 的指令碼已經被過濾了,
然後其他的截圖我就不發了,還有一種思路就是在過濾器中把字元轉義。
感謝luckpet大佬的提示
1 BufferedReader 使用完需要關閉 ;
2 對於一些拿postman等工具的朋友,拼接json的話會有換行 這裡result += clean(line); 需要改成: while((line = br.readLine()) != null){ if (line != null) { result += line; } }
使用jsoup防止XSS攻擊
前陣子專案國測後,開啟一個專案頁面,莫名其妙彈出xss,搜了全域性也沒找到alert("xss"),問了一下專案經理,原來是國測做防注入的時候,在新增資料的時候做的,一臉懵逼。
查了一下資料,以前做專案的時候都沒想到這個問題,如果儲存一段script指令碼,查資料的時候,這段指令碼就會被執行,這東西后果挺嚴重啊,如果是在桌面外彈框,執行個挖礦指令碼,這玩意不得了啊,厲害,長知識了。。。
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version> </dependency>
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。