springboot中使用過(guò)濾器,jsoup過(guò)濾XSS腳本詳解
springboot使用過(guò)濾器,jsoup過(guò)濾XSS腳本
- 背景:略
- 目標(biāo):完成request請(qǐng)求中的腳本過(guò)濾
- 技術(shù):filter,jsoup,requestWapper
1.把可能包含腳本的參數(shù)位置分析一下
post
/put
/delete
: 請(qǐng)求的參數(shù)中,有可能是表單提交、也有可能是使用了@requestBody注解,那么參數(shù)就是json格式,位于request的流中。get
/options
等:可能存在于url參數(shù)中,也有可能是表單提交的預(yù)請(qǐng)求中,所以一般在能想到的位置都有可能存在,包括header中。
2.分析實(shí)現(xiàn)過(guò)程
2.1首先要從request請(qǐng)求中將各個(gè)需要過(guò)濾位置的參數(shù)取出來(lái)
2.2然后將參數(shù)取出來(lái)進(jìn)行過(guò)濾
2.3將過(guò)濾后的參數(shù)重新包裝成request傳遞下去
2.4在這期間,
- 需要準(zhǔn)備好jsoup過(guò)濾腳本的工具類;
- 需要自定義一個(gè)過(guò)濾器,并且在過(guò)濾器中添加匹配條件,如:那些url不需要過(guò)濾,那些請(qǐng)求方式必須進(jìn)行過(guò)濾;
- 對(duì)過(guò)濾器進(jìn)行配置,是否開(kāi)啟,設(shè)置在整個(gè)過(guò)濾器鏈中的位置,設(shè)置過(guò)濾的白名單或者黑名單
- 所以就很清晰了我們過(guò)濾需要哪些類,哪些配置了
一個(gè)filter
一個(gè)requestWapper
一個(gè)jsoup工具類
一個(gè)filter的配置類
2.5進(jìn)行數(shù)據(jù)測(cè)試
3.代碼實(shí)現(xiàn)過(guò)程
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白名單種類,有四種,每一種針對(duì)的標(biāo)簽類型不一樣,具體的可以ctrl+左鍵點(diǎn)擊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)白名單,除開(kāi)白名單之外的標(biāo)簽都會(huì)被過(guò)濾 whitelist.preserveRelativeLinks(true);//這個(gè)配置的意思的過(guò)濾如果找不到成對(duì)的標(biāo)簽,就只過(guò)濾單個(gè)標(biāo)簽,而不用把后面所有的文本都進(jìn)行過(guò)濾。 //(之前在這個(gè)問(wèn)題上折騰了很久,當(dāng)<script>標(biāo)簽只有一個(gè)時(shí),會(huì)<script>標(biāo)簽后面的數(shù)據(jù)全部過(guò)濾) } public static String clean(String content) { //過(guò)濾方法 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 { //因?yàn)槲覀冃枰@取request中的數(shù)據(jù),所以需要繼承java底層中HttpServletRequestWrapper這個(gè)類,重寫(xiě)父類中的某些方法,獲取相應(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 在這里可以不用這個(gè)注解,以為后面我們會(huì)在config中去配置這個(gè)filter,在這里只需要實(shí)現(xiàn) Filter 接口實(shí)現(xiàn)相應(yīng)的方法就ok public class XssFilter implements Filter { private static boolean IS_INCLUDE_RICH_TEXT = false;//用于接收配置中的參數(shù),決定這個(gè)過(guò)濾器是否開(kāi)啟 public List<String> excludes = new ArrayList<String>();//用于接收配置中的參數(shù),決定哪些是不需要過(guò)濾的url(在這里,也可以修改handleExcludeURL()方法中相應(yīng)的代碼,使其變更為只需要過(guò)濾的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); } /** *此方法是決定對(duì)當(dāng)前url是否執(zhí)行過(guò)濾, *在這里沒(méi)有使用請(qǐng)求方法(post/put)來(lái)匹配,因?yàn)樵诒卷?xiàng)目中使用url匹配更適合(因?yàn)間et和其他請(qǐng)求方式也需要進(jìn)行過(guò)濾),如果你有興趣可以把這個(gè)方法更改為匹配請(qǐng)求方法進(jìn)行過(guò)濾 **/ 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; } /** *過(guò)濾器初始化,從配置類中獲取參數(shù),用于初始化兩個(gè)參數(shù)(是否開(kāi)啟,排除指定的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)試截圖:
請(qǐng)求:
程序截圖:
運(yùn)行結(jié)果:
可以看到body中 的腳本已經(jīng)被過(guò)濾了,
然后其他的截圖我就不發(fā)了,還有一種思路就是在過(guò)濾器中把字符轉(zhuǎn)義。
感謝luckpet大佬的提示
1 BufferedReader 使用完需要關(guān)閉 ;
2 對(duì)于一些拿postman等工具的朋友,拼接json的話會(huì)有換行 這里result += clean(line); 需要改成: while((line = br.readLine()) != null){ if (line != null) { result += line; } }
使用jsoup防止XSS攻擊
前陣子項(xiàng)目國(guó)測(cè)后,打開(kāi)一個(gè)項(xiàng)目頁(yè)面,莫名其妙彈出xss,搜了全局也沒(méi)找到alert("xss"),問(wèn)了一下項(xiàng)目經(jīng)理,原來(lái)是國(guó)測(cè)做防注入的時(shí)候,在添加數(shù)據(jù)的時(shí)候做的,一臉懵逼。
查了一下資料,以前做項(xiàng)目的時(shí)候都沒(méi)想到這個(gè)問(wèn)題,如果保存一段script腳本,查數(shù)據(jù)的時(shí)候,這段腳本就會(huì)被執(zhí)行,這東西后果挺嚴(yán)重啊,如果是在桌面外彈框,執(zhí)行個(gè)挖礦腳本,這玩意不得了啊,厲害,長(zhǎng)知識(shí)了。。。
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version> </dependency>
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
教你快速搭建sona服務(wù)及idea使用sona的方法
Sonar 是一個(gè)用于代碼質(zhì)量管理的開(kāi)放平臺(tái)。通過(guò)插件機(jī)制,Sonar 可以集成不同的測(cè)試工具,代碼分析工具,以及持續(xù)集成工具,本文給大家分享搭建sona服務(wù)及idea使用sona的方法,感興趣的朋友一起看看吧2021-06-06SpringBoot中shiro過(guò)濾器的重寫(xiě)與配置詳解
在前后端分離跨域訪問(wèn)的項(xiàng)目中shiro進(jìn)行權(quán)限攔截失效 (即使有正確權(quán)限的訪問(wèn)也會(huì)被攔截) 時(shí)造成302重定向錯(cuò)誤等問(wèn)題,為解決這個(gè)問(wèn)題,就需要進(jìn)行shiro過(guò)濾器的重寫(xiě)以及配置。本文詳細(xì)介紹了解決方法,需要的可以參考一下2022-04-04詳解SpringCloud Finchley Gateway 統(tǒng)一異常處理
這篇文章主要介紹了詳解SpringCloud Finchley Gateway 統(tǒng)一異常處理,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-10-10SpringAop實(shí)現(xiàn)原理及代理模式詳解
Spring的AOP就是通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)的,使用了兩個(gè)動(dòng)態(tài)代理,分別是JDK的動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理,本文重點(diǎn)給大家介紹下SpringAop實(shí)現(xiàn)原理及代理模式,感興趣的朋友一起看看吧2022-04-04SpringMvc3+extjs4實(shí)現(xiàn)上傳與下載功能
這篇文章主要為大家詳細(xì)介紹了SpringMvc3+extjs4實(shí)現(xiàn)上傳與下載功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06SpringBoot實(shí)現(xiàn)動(dòng)態(tài)增刪啟停定時(shí)任務(wù)的方式
在spring?boot中,可以通過(guò)@EnableScheduling注解和@Scheduled注解實(shí)現(xiàn)定時(shí)任務(wù),也可以通過(guò)SchedulingConfigurer接口來(lái)實(shí)現(xiàn)定時(shí)任務(wù),但是這兩種方式不能動(dòng)態(tài)添加、刪除、啟動(dòng)、停止任務(wù),本文給大家介紹SpringBoot實(shí)現(xiàn)動(dòng)態(tài)增刪啟停定時(shí)任務(wù)的方式,感興趣的朋友一起看看吧2024-03-03java中 IO 常用IO操作類繼承結(jié)構(gòu)分析
本篇文章小編為大家介紹,java中 IO 常用IO操作類繼承結(jié)構(gòu)分析。需要的朋友參考下2013-04-04Druid監(jiān)控分布式實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Druid監(jiān)控分布式實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11