跨站腳本攻擊 (Cross Site Scripting),為了不和層疊樣式表(Cascading Style Sheets, CSS) 的縮寫混淆,故将跨站腳本攻擊縮寫為 XSS。惡意攻擊者往 Web 頁面裡插入惡意 Script 代碼,當使用者浏覽該頁之時,嵌入其中 Web 裡面的 Script 代碼會被執行,進而達到惡意攻擊使用者的目的。
使用 Jsoup 可以有效的過濾不安全的代碼。Jsoup 使用白名單的機制來預防 XSS 攻擊,比如白名單中規定隻允許
<span>
标簽的存在,那麼其他标簽都會被過濾掉。
常見的 XSS 攻擊
比如頁面的某個表單允許使用者輸入任意内容,當某個調皮的使用者輸入如下内容:
儲存後,你會發現頁面文字都變成了紅色!
或者輸入
<script>for(var i=0;i<10;i++){alert("fuck you");}</script>
,儲存後頁面将彈窗 10 次!
引入 Jsoup
使用 Maven 建構一個簡單的 Spring Boot 項目,在 pom 中引入:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>
JsoupUtil
建立一個 JsoupUtil 工具類:
import java.io.FileNotFoundException;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
/**
* Xss過濾工具
*
*/
public class JsoupUtil {
private static final Whitelist whitelist = Whitelist.basicWithImages();
/*
* 配置過濾化參數,不對代碼進行格式化
*/
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
static {
/*
* 富文本編輯時一些樣式是使用style來進行實作的 比如紅色字型 是以需要給所有标簽添加style屬性
*/
whitelist.addAttributes(":all", "style");
}
public static String clean(String content) {
return Jsoup.clean(content, "", whitelist, outputSettings);
}
}
這裡采用的白名單為
basicWithImages
,Jsoup 内置了幾種常見的白名單供我們選擇,如下表所示:
白名單對象 | 标簽 | 說明 |
---|---|---|
none | 無 | 隻保留标簽内文本内容 |
simpleText | b,em,i,strong,u | 簡單的文本标簽 |
basic | a,b,blockquote,br,cite,code,dd, dl,dt,em,i,li,ol,p,pre,q,small,span, strike,strong,sub,sup,u,ul | 基本使用的标簽 |
basicWithImages | basic 的基礎上添加了 img 标簽 及 img 标簽的 src,align,alt,height,width,title 屬性 | 基本使用的加上 img 标簽 |
relaxed | a,b,blockquote,br,caption,cite, code,col,colgroup,dd,div,dl,dt, em,h1,h2,h3,h4,h5,h6,i,img,li, ol,p,pre,q,small,span,strike,strong, sub,sup,table,tbody,td,tfoot,th,thead,tr,u,ul | 在 basicWithImages 的基礎上又增加了一部分部分标簽 |
XssHttpServletRequestWrapper
建立一個 XssHttpServletRequestWrapper,同過重寫
getParameter()
,
getParameterValues()
和
getHeader()
方法來過濾 HTTP 請求中參數包含的惡意字元:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang.StringUtils;
import cc.mrbird.common.util.JsoupUtil;
/**
* Jsoup過濾http請求,防止Xss攻擊
*
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
private boolean isIncludeRichText = false;
public XssHttpServletRequestWrapper(HttpServletRequest request, boolean isIncludeRichText) {
super(request);
orgRequest = request;
this.isIncludeRichText = isIncludeRichText;
}
/**
* 覆寫getParameter方法,将參數名和參數值都做xss過濾如果需要獲得原始的值,則通過super.getParameterValues(name)來擷取
* getParameterNames,getParameterValues和getParameterMap也可能需要覆寫
*/
@Override
public String getParameter(String name) {
if (("content".equals(name) || name.endsWith("WithHtml")) && !isIncludeRichText) {
return super.getParameter(name);
}
name = JsoupUtil.clean(name);
String value = super.getParameter(name);
if (StringUtils.isNotBlank(value)) {
value = JsoupUtil.clean(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] arr = super.getParameterValues(name);
if (arr != null) {
for (int i = 0; i < arr.length; i++) {
arr[i] = JsoupUtil.clean(arr[i]);
}
}
return arr;
}
/**
* 覆寫getHeader方法,将參數名和參數值都做xss過濾如果需要獲得原始的值,則通過super.getHeaders(name)來擷取
* getHeaderNames 也可能需要覆寫
*/
@Override
public String getHeader(String name) {
name = JsoupUtil.clean(name);
String value = super.getHeader(name);
if (StringUtils.isNotBlank(value)) {
value = JsoupUtil.clean(value);
}
return value;
}
/**
* 擷取原始的request
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 擷取原始的request的靜态方法
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).getOrgRequest();
}
return req;
}
}
XssFilter
建立 XssFilter,同過使用上面定義的 XssHttpServletRequestWrapper 類中的
getParameter()
等方法來保證參數得到了過濾:
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;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Xss攻擊攔截器
*
*/
public class XssFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(XssFilter.class);
// 是否過濾富文本内容
private static boolean IS_INCLUDE_RICH_TEXT = false;
public List<String> excludes = new ArrayList<String>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("------------ 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 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,
IS_INCLUDE_RICH_TEXT);
chain.doFilter(xssRequest, response);
}
@Override
public void destroy() {
}
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;
}
}
Spring Boot 中配置 XssFilter
使用 JavaConfig 的形式配置:
@Bean
public FilterRegistrationBean xssFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new XssFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
initParameters.put("isIncludeRichText", "true");
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}
參考文章:
- https://blog.csdn.net/u014411966/article/details/78164752
- https://www.jianshu.com/p/32abc12a175a?nomobile=yes