防止存储型 XSS 不能只靠输入端过滤特殊字符,最可靠的做法是在数据输出到页面时进行上下文相关的编码处理。标题虽提及“过滤”,但工程实践中“输出编码”才是防御核心,输入过滤仅作为辅助手段。
先说结论:输入过滤只能作为辅助手段,核心防御必须在输出阶段根据 HTML、JS、URL 等不同上下文进行编码。
- 先判断:确认数据输出位置是 HTML body、属性还是 JavaScript 变量
- 优先做:引入 OWASP Java Encoder 或启用 Spring Security 默认转义
- 再验证:使用 XSS 测试 payload 确认脚本未被执行
核心原则:为什么输入过滤不够
存储型 XSS 的本质是浏览器信任了服务端返回的内容。攻击者将恶意脚本存入数据库,当其他用户访问页面时,服务端从数据库取出数据直接拼接到 HTML 中返回,浏览器将其当作正常代码执行。
单纯在输入端过滤特殊字符(如禁用 <script>)往往不够,因为攻击者可以利用事件 handler(如 onerror)、JavaScript 协议头或编码绕过。只有在输出端将特殊字符转换为 HTML 实体(如 < 转为 <),浏览器才会将其视为纯文本显示而非代码执行。
Spring Boot 安全配置实战
如果你使用 Spring Boot 2.7+ 或 3.x,请使用 Java Config 配置安全头。注意:X-XSS-Protection 头已被现代浏览器弃用,不建议再配置,应优先使用 Content-Security-Policy (CSP)。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers(headers -> headers
// 配置 CSP,限制脚本执行来源
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'; script-src 'self'"))
// 防止点击劫持
.frameOptions(HeadersConfigurer.FrameOptionsConfig::deny)
// 防止 MIME 类型嗅探
.contentTypeOptions(Customizer.withDefaults())
);
// 注意:不要启用 xssProtection(),该功能已废弃
return http.build();
}配置完成后,确保项目运行在 HTTPS 环境下,否则部分安全头(如 HSTS)可能无法生效或被中间人篡改。
手动编码实战(OWASP Encoder)
对于直接在 Servlet 输出流、复杂拼接场景或框架默认转义未覆盖的地方,建议使用 OWASP Java Encoder 项目。它比传统的 HTML 转义更安全,能区分 HTML、JS、URL 等不同上下文。
1. 引入依赖
<!-- Maven 依赖,建议检查 Maven Central 获取最新版本 -->
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder</artifactId>
<version>1.2.3</version>
</dependency>2. Controller 层调用示例
在数据返回给视图前进行编码,而不是在存入数据库时编码(避免破坏数据)。
@GetMapping("/profile")
public String profile(Model model, @RequestParam String userInput) {
// 根据输出上下文选择编码方法
String safeHtml = Encode.forHtml(userInput);
String safeJs = Encode.forJavaScript(userInput);
model.addAttribute("safeInput", safeHtml);
return "user/profile";
}怎么验证是否生效
1. 页面源码检查
在浏览器右键查看页面源代码,搜索你输入的测试字符。如果看到 <script> 变成了 <script>,说明 HTML 实体编码已生效。
2. 控制台报错观察
尝试输入 <img src=x onerror=alert(1)>。如果编码生效,图片不会加载,控制台也不会弹出 alert 窗口。如果弹出窗口,说明防御失败。
3. 自动化扫描
使用 OWASP ZAP 或 Burp Suite 的 XSS 扫描插件对提交接口进行测试,查看是否有高危漏洞报告。
常见坑
1. JSON 接口未处理
如果是前后端分离架构,后端返回 JSON 数据,前端使用 innerHTML 渲染,后端转义可能无效。需确保前端使用 textContent 或框架提供的安全绑定方式(如 Vue 的 {{ }} 而非 v-html)。
2. 富文本编辑器场景
如果业务允许用户发布含格式的文章(如博客),不能简单转义所有标签。需使用白名单过滤库(如 OWASP Java HTML Sanitizer),只保留安全的 HTML 标签和属性。
3. 双重编码问题
避免对同一数据进行多次编码,否则页面会显示乱码(如 &lt;)。确认框架是否已经自动转义,不要手动重复处理。
4. 安全头依赖 HTTPS
配置了 CSP 或 HSTS 但未强制 HTTPS,攻击者仍可通过 HTTP 劫持响应头。建议在网关或负载均衡层强制跳转 HTTPS。
参考来源
- OWASP Cross Site Scripting Prevention Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- OWASP Java Encoder Project, https://owasp.org/www-project-java-encoder/
- Spring Security Documentation, https://docs.spring.io/spring-security/reference/servlet/headers.html