一篇关于使用内容安全策略 (CSP) 和跨域资源共享 (CORS) 加强前端安全的综合指南,保护您的 Web 应用免受现代威胁。
前端安全加固:内容安全策略与跨域资源共享
在当今互联的数字环境中,前端安全至关重要。Web 应用越来越多地成为复杂攻击的目标,这使得强大的安全措施变得不可或缺。安全前端架构的两个关键组成部分是内容安全策略 (CSP) 和跨域资源共享 (CORS)。本综合指南深入探讨了这些技术,提供了实用的示例和可行的见解,帮助您加固 Web 应用以抵御现代威胁。
什么是内容安全策略 (CSP)?
内容安全策略 (CSP) 是一个额外的安全层,有助于检测和缓解某些类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击。CSP 的实现方式是 Web 服务器向浏览器发送一个 Content-Security-Policy HTTP 响应头。此标头定义了一个白名单,列出了浏览器允许从中加载资源的来源。通过限制浏览器可以加载的内容来源,CSP 极大地增加了攻击者向您的网站注入恶意代码的难度。
CSP 的工作原理
CSP 的工作原理是指示浏览器仅从批准的来源加载资源(例如,脚本、样式表、图像、字体)。这些来源在 CSP 标头中使用指令来指定。如果浏览器尝试从一个未明确允许的来源加载资源,它将阻止该请求并报告违规。
CSP 指令:综合概述
CSP 指令控制可以从特定来源加载的资源类型。以下是一些最重要指令的分解:
- default-src: 指定所有内容类型的默认来源。这是一个后备指令,在没有其他更具体的指令时适用。
- script-src: 指定可以从中加载脚本的来源。这对于防止 XSS 攻击至关重要。
- style-src: 指定可以从中加载样式表的来源。
- img-src: 指定可以从中加载图像的来源。
- font-src: 指定可以从中加载字体的来源。
- media-src: 指定可以从中加载音频和视频的来源。
- object-src: 指定可以从中加载插件(例如 Flash)的来源。由于其固有的安全风险,通常设置为 'none' 以完全禁用插件。
- frame-src: 指定可以从中加载框架(例如 <iframe>)的来源。
- connect-src: 指定用户代理可以通过脚本接口(如 XMLHttpRequest、WebSocket 和 EventSource)连接的 URL。
- base-uri: 指定可以在文档的 <base> 元素中使用的 URL。
- form-action: 指定可以向其发送表单提交的 URL。
- upgrade-insecure-requests: 指示用户代理自动将不安全的请求 (HTTP) 升级为安全请求 (HTTPS)。
- report-uri: 指定浏览器应将 CSP 违规报告发送到的 URL。该指令已弃用,推荐使用 `report-to`。
- report-to: 指定在 `Report-To` 标头中定义的报告组名称,浏览器应将 CSP 违规报告发送到该组。
CSP 来源列表关键字
在 CSP 指令中,您可以使用来源列表关键字来定义允许的来源。以下是一些常见的关键字:
- 'self': 允许来自与文档相同来源(协议和主机)的资源。
- 'none': 禁止来自所有来源的资源。
- 'unsafe-inline': 允许使用内联脚本和样式(例如 <script> 标签和样式属性)。请极其谨慎使用,因为它会显著削弱 CSP 对 XSS 的防护能力。
- 'unsafe-eval': 允许使用动态代码评估函数,如
eval()和Function()。请极其谨慎使用,因为它会引入重大的安全风险。 - 'unsafe-hashes': 允许与指定哈希值匹配的特定内联事件处理程序或 <style> 标签。需要浏览器支持。请谨慎使用。
- 'strict-dynamic': 指定通过附带 nonce 或哈希显式授予标记中脚本的信任,应传播到由该根脚本加载的所有脚本。
- data: 允许 data: URI(例如,编码为 base64 的内联图像)。请谨慎使用。
- https:: 允许通过 HTTPS 从任何域加载资源。
- [hostname]: 允许来自特定域的资源(例如,example.com)。您还可以指定端口号(例如,example.com:8080)。
- [scheme]://[hostname]:[port]: 一个完全限定的 URI,允许来自指定协议、主机和端口的资源。
CSP 实用示例
让我们看一些 CSP 标头的实用示例:
示例 1:使用 'self' 的基本 CSP
此策略仅允许来自同源的资源:
Content-Security-Policy: default-src 'self'
示例 2:允许来自特定域的脚本
此策略允许来自您自己的域和受信任的 CDN 的脚本:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
示例 3:禁用内联脚本和样式
此策略禁止内联脚本和样式,这是对抗 XSS 的强有力防御:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'
重要提示:禁用内联脚本需要重构您的 HTML,将内联脚本移至外部文件。
示例 4:对内联脚本使用 Nonce
如果您必须使用内联脚本,请使用 nonce(加密随机、一次性使用的令牌)来将特定的内联脚本块列入白名单。这比 'unsafe-inline' 更安全。服务器必须为每个请求生成一个唯一的 nonce,并将其包含在 CSP 标头和 <script> 标签中。
Content-Security-Policy: default-src 'self'; script-src 'nonce-r4nd0mN0nc3'; style-src 'self'
<script nonce="r4nd0mN0nc3"> console.log('Inline script'); </script>
注意:请记住为每个请求生成一个新的 nonce。不要重复使用 nonce!
示例 5:对内联样式使用哈希
与 nonce 类似,哈希可用于将特定的内联 <style> 块列入白名单。这是通过生成样式内容的 SHA256、SHA384 或 SHA512 哈希来实现的。
Content-Security-Policy: default-src 'self'; style-src 'sha256-HASHEDSTYLES'
<style sha256="HASHEDSTYLES"> body { background-color: #f0f0f0; } </style>
注意:哈希的灵活性不如 nonce,因为对样式内容的任何更改都会使哈希失效。
示例 6:报告 CSP 违规
要监控 CSP 违规,请使用 report-uri 或 report-to 指令:
Content-Security-Policy: default-src 'self'; report-to csp-endpoint;
您还需要配置 Report-To 标头。Report-To 标头定义了一个或多个报告组,指定报告应发送到何处以及如何发送。
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://example.com/csp-report"}]}
测试和部署 CSP
实施 CSP 需要仔细的规划和测试。从一个限制性策略开始,然后根据需要逐步放宽。使用 Content-Security-Policy-Report-Only 标头来测试您的策略而不会阻止资源。此标头会报告违规但不会强制执行策略,使您能够在生产环境中部署策略之前识别并修复问题。
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint;
分析浏览器生成的报告以识别任何违规,并相应地调整您的策略。一旦您确信策略工作正常,就使用 Content-Security-Policy 标头进行部署。
CSP 最佳实践
- 从 default-src 开始:始终定义一个
default-src来建立基线策略。 - 要具体:使用特定的指令和来源列表关键字来限制策略的范围。
- 避免 'unsafe-inline' 和 'unsafe-eval':这些关键字会显著削弱 CSP,应尽可能避免使用。
- 对内联脚本和样式使用 nonce 或哈希:如果必须使用内联脚本或样式,请使用 nonce 或哈希将特定的代码块列入白名单。
- 监控 CSP 违规:使用
report-uri或report-to指令来监控 CSP 违规,并相应地调整您的策略。 - 充分测试:在生产环境中部署策略之前,使用
Content-Security-Policy-Report-Only标头进行测试。 - 迭代和完善:CSP 不是一次性配置。持续监控和完善您的策略,以适应应用程序和威胁环境的变化。
什么是跨域资源共享 (CORS)?
跨域资源共享 (CORS) 是一种机制,允许来自一个源(域)的网页访问来自不同源的资源。默认情况下,浏览器会实施同源策略,该策略阻止脚本向与脚本来源不同的源发出请求。CORS 提供了一种选择性地放宽此限制的方法,允许合法的跨域请求,同时防范恶意攻击。
理解同源策略
同源策略是一项基本的安全机制,可防止一个网站上的恶意脚本访问另一个网站上的敏感数据。一个源由协议 (scheme)、主机 (domain) 和端口定义。两个 URL 只有在它们的协议、主机和端口都相同时,才具有相同的源。
例如:
https://www.example.com/app1/index.html和https://www.example.com/app2/index.html具有相同的源。https://www.example.com/index.html和http://www.example.com/index.html具有不同的源(协议不同)。https://www.example.com/index.html和https://sub.example.com/index.html具有不同的源(主机不同)。https://www.example.com:8080/index.html和https://www.example.com:80/index.html具有不同的源(端口不同)。
CORS 的工作原理
当网页发出跨域请求时,浏览器首先向服务器发送一个“预检”请求。预检请求使用 HTTP OPTIONS 方法,并包含指示实际请求将使用的 HTTP 方法和标头的头部。然后,服务器以指示是否允许跨域请求的标头进行响应。
如果服务器允许该请求,它会在响应中包含 Access-Control-Allow-Origin 标头。此标头指定允许访问该资源的源。然后浏览器继续进行实际请求。如果服务器不允许该请求,它将不包含 Access-Control-Allow-Origin 标头,浏览器将阻止该请求。
CORS 标头:详细解析
CORS 依赖 HTTP 标头在浏览器和服务器之间进行通信。以下是关键的 CORS 标头:
- Access-Control-Allow-Origin: 指定允许访问资源的源。此标头可以包含一个特定的源(例如,
https://www.example.com)、一个通配符 (*) 或null。使用*允许来自任何源的请求,出于安全原因通常不建议这样做。使用 `null` 仅适用于“不透明响应”,例如当资源使用 `file://` 协议或数据 URI 检索时。 - Access-Control-Allow-Methods: 指定跨域请求允许的 HTTP 方法(例如,
GET, POST, PUT, DELETE)。 - Access-Control-Allow-Headers: 指定跨域请求中允许的 HTTP 标头。这对于处理自定义标头很重要。
- Access-Control-Allow-Credentials: 指示浏览器是否应在跨域请求中包含凭据(例如,cookie、授权标头)。此标头必须设置为
true才允许凭据。 - Access-Control-Expose-Headers: 指定哪些标头可以暴露给客户端。默认情况下,只暴露有限的一组标头。
- Access-Control-Max-Age: 指定浏览器可以缓存预检请求的最长时间(以秒为单位)。
- Origin: 这是浏览器发送的请求标头,用于指示请求的来源。
- Vary: 一个通用的 HTTP 标头,但对 CORS 很重要。当 `Access-Control-Allow-Origin` 是动态生成时,应在响应中包含 `Vary: Origin` 标头,以指示缓存机制响应会根据 `Origin` 请求标头而变化。
CORS 实用示例
让我们看一些 CORS 配置的实用示例:
示例 1:允许来自特定源的请求
此配置仅允许来自 https://www.example.com 的请求:
Access-Control-Allow-Origin: https://www.example.com
示例 2:允许来自任何源的请求(不推荐)
此配置允许来自任何源的请求。请谨慎使用,因为它可能会引入安全风险:
Access-Control-Allow-Origin: *
示例 3:允许特定的方法和标头
此配置允许 GET、POST 和 PUT 方法,以及 Content-Type 和 Authorization 标头:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
示例 4:允许凭据
要允许凭据(例如,cookie),您需要将 Access-Control-Allow-Credentials 设置为 true 并指定一个特定的源(当允许凭据时不能使用 *):
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
您还需要在您的 JavaScript fetch/XMLHttpRequest 请求中设置 credentials: 'include'。
fetch('https://api.example.com/data', {
credentials: 'include'
})
CORS 预检请求
对于某些类型的跨域请求(例如,带有自定义标头或除 GET、HEAD 或 POST(Content-Type 为 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)之外的方法的请求),浏览器会使用 OPTIONS 方法发送预检请求。服务器必须响应预检请求并附上适当的 CORS 标头,以指示是否允许实际的请求。
以下是预检请求和响应的示例:
预检请求 (OPTIONS):
OPTIONS /data HTTP/1.1
Origin: https://www.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
预检响应 (200 OK):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Access-Control-Max-Age 标头指定浏览器可以缓存预检响应的时间,从而减少预检请求的数量。
CORS 和 JSONP
JSON with Padding (JSONP) 是一种绕过同源策略的旧技术。然而,JSONP 存在重大的安全风险,应避免使用,转而使用 CORS。JSONP 依赖于向页面注入 <script> 标签,这可以执行任意代码。CORS 提供了一种更安全、更灵活的方式来处理跨域请求。
CORS 最佳实践
- 避免使用 *: 避免在
Access-Control-Allow-Origin标头中使用通配符 (*),因为它允许来自任何源的请求。相反,应指定允许访问资源的特定源。 - 具体指定方法和标头:在
Access-Control-Allow-Methods和Access-Control-Allow-Headers标头中指定确切的 HTTP 方法和标头。 - 谨慎使用 Access-Control-Allow-Credentials:仅在需要在跨域请求中允许凭据(例如,cookie)时才启用
Access-Control-Allow-Credentials。请注意允许凭据的安全影响。 - 保护您的预检请求:确保您的服务器正确处理预检请求并返回正确的 CORS 标头。
- 使用 HTTPS:始终对源和您跨域访问的资源使用 HTTPS。这有助于防止中间人攻击。
- Vary: Origin: 如果您动态生成 `Access-Control-Allow-Origin` 标头,请务必包含 `Vary: Origin` 标头以防止缓存问题。
CSP 和 CORS 的实践:组合方法
虽然 CSP 和 CORS 都解决了安全问题,但它们在不同的层面上运作,并提供互补的保护。CSP 侧重于防止浏览器加载恶意内容,而 CORS 侧重于控制哪些源可以访问您服务器上的资源。
通过结合使用 CSP 和 CORS,您可以为您的 Web 应用程序创建更强大的安全态势。例如,您可以使用 CSP 限制可以加载脚本的来源,并使用 CORS 控制哪些源可以访问您的 API 端点。
示例:使用 CSP 和 CORS 保护 API
假设您有一个托管在 https://api.example.com 的 API,您希望它只能从 https://www.example.com 访问。您可以配置您的服务器返回以下标头:
API 响应标头 (https://api.example.com):
Access-Control-Allow-Origin: https://www.example.com
Content-Type: application/json
并且您可以配置您的网站 (https://www.example.com) 使用以下 CSP 标头:
网站 CSP 标头 (https://www.example.com):
Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self' https://api.example.com;
此 CSP 策略允许网站加载脚本并连接到 API,但阻止其加载脚本或连接到其他域。
结论
内容安全策略 (CSP) 和跨域资源共享 (CORS) 是加固前端应用程序安全的重要工具。通过仔细配置 CSP 和 CORS,您可以显著降低 XSS 攻击、数据注入攻击和其他安全漏洞的风险。请记住,从限制性策略开始,进行充分测试,并持续监控和完善您的配置,以适应您的应用程序和不断变化的威胁环境的变化。通过优先考虑前端安全,您可以在当今日益复杂的数字世界中保护您的用户并确保 Web 应用程序的完整性。