SpringBoot2.x 整合 AntiSamy防御XSS攻擊的簡單總結(jié)
AntiSamy是OWASP的一個開源項目,通過對用戶輸入的HTML、CSS、JavaScript等內(nèi)容進行檢驗和清理,確保輸入符合應(yīng)用規(guī)范。AntiSamy被廣泛應(yīng)用于Web服務(wù)對存儲型和反射型XSS的防御中。
XSS攻擊全稱為跨站腳本攻擊(Cross Site Scripting),是一種在web應(yīng)用中的計算機安全漏洞,它允許用戶將惡意代碼(如script腳本)植入到Web頁面中,為了不和層疊樣式表(Cascading Style Sheets, CSS)混淆,一般縮寫為XSS。XSS分為以下兩種類型:
- 存儲型XSS:服務(wù)端對用戶輸入的惡意腳本沒有經(jīng)過驗證就存入數(shù)據(jù)庫,每次調(diào)用數(shù)據(jù)庫都會將其渲染在瀏覽器上。則可能為存儲型XSS。
- 反射型XSS:通過get或者post等方式,向服務(wù)端輸入數(shù)據(jù)。如果服務(wù)端不進行過濾,驗證或編碼,直接將用戶信息呈現(xiàn)出來,可能會造成反射型XSS。
本文主要對SpringBoot2.x集成AntiSamy防御XSS攻擊進行簡單總結(jié),其中SpringBoot使用的2.4.5
版本。
一、引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- AntiSamy依賴 --> <dependency> <groupId>org.owasp.antisamy</groupId> <artifactId>antisamy</artifactId> <version>1.6.2</version> </dependency> <!-- lombok插件 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.9</version> </dependency>
二、策略文件
Antisamy對惡意代碼的過濾依賴于策略文件,策略文件為xml格式,規(guī)定了AntiSamy對各個標簽、屬性的處理方法。策略文件定義的嚴格與否,決定了AntiSamy對Xss的防御效果。在AntiSamy的jar包中,已經(jīng)包含了幾個常用的策略文件:
本文使用antisamy-ebay.xml
作為策略文件,該策略相對安全,適用于電商網(wǎng)站。將antisamy-ebay.xml
和antisamy.xsd
復(fù)制到resouces
目錄下。對于策略文件的具體內(nèi)容這里不進行深入了解,只需了解下對標簽的處理規(guī)則<tag-rules>
,共有remove、truncate、validate三種處理方式,其中remove為直接刪除,truncate為縮短標簽,只保留標簽和值,validate為驗證標簽屬性:
上圖截取了<tag-rules>
的一部分,可知對script
標簽的處理策略是remove。
三、實體類和Controller
用戶實體類:
package com.rtxtitanv.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author rtxtitanv * @version 1.0.0 * @name com.rtxtitanv.model.User * @description 用戶實體類 * @date 2021/8/23 14:54 */ @AllArgsConstructor @NoArgsConstructor @Data public class User { private Long id; private String username; private String password; }
Controller:
package com.rtxtitanv.controller; import com.rtxtitanv.model.User; import org.springframework.web.bind.annotation.*; /** * @author rtxtitanv * @version 1.0.0 * @name com.rtxtitanv.controller.UserController * @description UserController * @date 2021/8/23 14:54 */ @RequestMapping("/user") @RestController public class UserController { @PostMapping("/save") public User saveUser(User user) { return user; } @GetMapping("/get") public User getUserById(@RequestParam(value = "id") Long id) { return new User(id, "ZhaoYun", "123456"); } @PutMapping("/update") public User updateUser(@RequestBody User user) { return user; } }
四、創(chuàng)建過濾器
package com.rtxtitanv.filter; import com.rtxtitanv.wrapper.XssRequestWrapper; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @author rtxtitanv * @version 1.0.0 * @name com.rtxtitanv.filter.XssFilter * @description XSS過濾器 * @date 2021/8/23 15:01 */ public class XssFilter implements Filter { private FilterConfig filterConfig; @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 攔截請求,處理XSS過濾 chain.doFilter(new XssRequestWrapper((HttpServletRequest)request), response); } @Override public void destroy() { this.filterConfig = null; } }
注意:在過濾器中并沒有直接對請求參數(shù)進行過濾清洗,而是在XssRequestWrapper
類中進行的。XssRequestWrapper
類將當(dāng)前的request
對象進行了包裝,在過濾器放行時會自動調(diào)用XssRequestWrapper
中的方法對請求參數(shù)進行清洗。
五、創(chuàng)建XssRequestWrapper類
package com.rtxtitanv.wrapper; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; import org.owasp.validator.html.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Map; import java.util.Objects; /** * @author rtxtitanv * @version 1.0.0 * @name com.rtxtitanv.wrapper.XssRequestWrapper * @description 裝飾器模式加強對request的處理,基于AntiSamy進行XSS防御 * @date 2021/8/23 15:01 */ public class XssRequestWrapper extends HttpServletRequestWrapper { private static final Logger LOGGER = LoggerFactory.getLogger(XssRequestWrapper.class); private static Policy policy = null; static { try { // 獲取策略文件路徑,策略文件需要放到項目的classpath下 String antiSamyPath = Objects .requireNonNull(XssRequestWrapper.class.getClassLoader().getResource("antisamy-ebay.xml")).getFile(); LOGGER.info(antiSamyPath); // 獲取的文件路徑中有空格時,空格會被替換為%20,在new一個File對象時會出現(xiàn)找不到路徑的錯誤 // 對路徑進行解碼以解決該問題 antiSamyPath = URLDecoder.decode(antiSamyPath, "utf-8"); LOGGER.info(antiSamyPath); // 指定策略文件 policy = Policy.getInstance(antiSamyPath); } catch (UnsupportedEncodingException | PolicyException e) { e.printStackTrace(); } } public XssRequestWrapper(HttpServletRequest request) { super(request); } /** * 過濾請求頭 * * @param name 參數(shù)名 * @return 參數(shù)值 */ @Override public String getHeader(String name) { String header = super.getHeader(name); // 如果Header為空,則直接返回,否則進行清洗 return StringUtils.isBlank(header) ? header : xssClean(header); } /** * 過濾請求參數(shù) * * @param name 參數(shù)名 * @return 參數(shù)值 */ @Override public String getParameter(String name) { String parameter = super.getParameter(name); // 如果Parameter為空,則直接返回,否則進行清洗 return StringUtils.isBlank(parameter) ? parameter : xssClean(parameter); } /** * 過濾請求參數(shù)(一個參數(shù)可以有多個值) * * @param name 參數(shù)名 * @return 參數(shù)值數(shù)組 */ @Override public String[] getParameterValues(String name) { String[] parameterValues = super.getParameterValues(name); if (parameterValues != null) { int length = parameterValues.length; String[] newParameterValues = new String[length]; for (int i = 0; i < length; i++) { LOGGER.info("AntiSamy清理之前的參數(shù)值:" + parameterValues[i]); // 清洗參數(shù) newParameterValues[i] = xssClean(parameterValues[i]); LOGGER.info("AntiSamy清理之后的參數(shù)值:" + newParameterValues[i]); } return newParameterValues; } return super.getParameterValues(name); } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> requestMap = super.getParameterMap(); requestMap.forEach((key, value) -> { for (int i = 0; i < value.length; i++) { LOGGER.info(value[i]); value[i] = xssClean(value[i]); LOGGER.info(value[i]); } }); return requestMap; } /** * 使用AntiSamy清洗數(shù)據(jù) * * @param value 需要清洗的數(shù)據(jù) * @return 清洗后的數(shù)據(jù) */ private String xssClean(String value) { try { AntiSamy antiSamy = new AntiSamy(); // 使用AntiSamy清洗數(shù)據(jù) final CleanResults cleanResults = antiSamy.scan(value, policy); // 獲得安全的HTML輸出 value = cleanResults.getCleanHTML(); // 對轉(zhuǎn)義的HTML特殊字符(<、>、"等)進行反轉(zhuǎn)義,因為AntiSamy調(diào)用scan方法時會將特殊字符轉(zhuǎn)義 return StringEscapeUtils.unescapeHtml4(value); } catch (ScanException | PolicyException e) { e.printStackTrace(); } return value; } /** * 通過修改Json序列化的方式來完成Json格式的XSS過濾 */ public static class XssStringJsonSerializer extends JsonSerializer<String> { @Override public Class<String> handledType() { return String.class; } @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (!StringUtils.isBlank(value)) { try { AntiSamy antiSamy = new AntiSamy(); final CleanResults cleanResults = antiSamy.scan(value, XssRequestWrapper.policy); gen.writeString(StringEscapeUtils.unescapeHtml4(cleanResults.getCleanHTML())); } catch (ScanException | PolicyException e) { e.printStackTrace(); } } } } }
六、創(chuàng)建配置類
package com.rtxtitanv.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.rtxtitanv.filter.XssFilter; import com.rtxtitanv.wrapper.XssRequestWrapper; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import javax.servlet.Filter; /** * @author rtxtitanv * @version 1.0.0 * @name com.rtxtitanv.config.AntiSamyConfig * @description AntiSamy配置類 * @date 2021/8/23 15:05 */ @Configuration public class AntiSamyConfig { /** * 配置XSS過濾器 * * @return FilterRegistrationBean */ @Bean public FilterRegistrationBean<Filter> filterRegistrationBean() { FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(new XssFilter()); filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.setOrder(1); return filterRegistrationBean; } /** * 用于過濾Json類型數(shù)據(jù)的解析器 * * @param builder Jackson2ObjectMapperBuilder * @return ObjectMapper */ @Bean public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) { // 創(chuàng)建解析器 ObjectMapper objectMapper = builder.createXmlMapper(false).build(); // 注冊解析器 SimpleModule simpleModule = new SimpleModule("XssStringJsonSerializer"); simpleModule.addSerializer(new XssRequestWrapper.XssStringJsonSerializer()); objectMapper.registerModule(simpleModule); return objectMapper; } }
七、測試
啟動項目,發(fā)送如下POST請求,請求地址為http://localhost:8080/user/save
,可見表單參數(shù)中的<script>
標簽內(nèi)容被成功過濾:
發(fā)送如下GET請求,請求地址為http://localhost:8080/user/get?id=1<script>alert("XSS");</script>0
,可見Query參數(shù)中的<script>
標簽內(nèi)容被成功過濾:
發(fā)送如下PUT請求,請求地址為http://localhost:8080/user/update
,可見Json類型參數(shù)中的<script>
標簽內(nèi)容被成功過濾:
代碼示例
到此這篇關(guān)于SpringBoot2.x 整合 AntiSamy防御XSS攻擊的簡單總結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot2.x防御XSS攻擊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java環(huán)境中MyBatis與Spring或Spring MVC框架的集成方法
和MyBatis類似,Spring或者Spring MVC框架在Web應(yīng)用程序的運作中同樣主要負責(zé)處理數(shù)據(jù)庫事務(wù),這里我們就來看一下Java環(huán)境中MyBatis與Spring或Spring MVC框架的集成方法2016-06-06JDK8時間相關(guān)類超詳細總結(jié)(含多個實例)
jdk1.8的一些新特性簡化了代碼的寫法,減少了部分開發(fā)量,下面這篇文章主要給大家介紹了關(guān)于JDK8時間相關(guān)類超詳細總結(jié),文中包含了多個實例代碼,需要的朋友可以參考下2023-01-01