这是用户在 2024-4-21 10:52 为 https://www.writesoftwarewell.com/content-security-policy/ 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Overview of Content Security Policy (CSP)

Content Security Policy (CSP): What Every Web Developer Must Know
内容安全策略 (CSP):每个 Web 开发人员必须了解的内容

This is a comprehensive guide to Content Security Policy (CSP). If you build websites for a living, CSP is an important concept to know, understand, and implement to protect your users from Cross-Site Scripting (XSS) Injection attacks. This post covers (almost) everything you need to know about CSP.
这是内容安全策略 (CSP) 的综合指南。如果您以构建网站为生,CSP 是一个需要了解、理解和实施的重要概念,可以保护您的用户免受跨站脚本 (XSS) 注入攻击。这篇文章涵盖了(几乎)您需要了解的有关 CSP 的所有内容。

17 min read

In this article, we'll learn (almost) everything about content security policies (CSP). Even if you've never heard of CSP before, and have no idea what it is, I promise that by the end of the article you'll have a solid understanding and a concrete, step-by-step plan to secure your website and apps from Cross-Site Scripting (XSS) Injection Attacks.
在本文中,我们将了解(几乎)有关内容安全策略 (CSP) 的所有内容。即使您以前从未听说过 CSP,也不知道它是什么,我保证在本文结束时您将有一个扎实的理解和一个​​具体的、逐步的计划来保护您的网站和应用程序免受跨站脚本 (XSS) 注入攻击。

First, a disclaimer. I am not a security expert by any shape or form. To be honest, I had no idea what the term 'Content Security Policy' meant until a few days ago. But last week, I got a chance to implement CSPs at work, and found it fascinating. So I spent the last few days (including the weekend), reading up everything I could get my hands on, and wanted to write down everything I learned in this article before I forgot it.
首先,免责声明。我不是任何形式的安全专家。说实话,直到几天前我才知道“内容安全策略”这个词是什么意思。但上周,我有机会在工作中实施 CSP,并发现它很有趣。所以我花了最后几天(包括周末)阅读我能得到的所有内容,并想在我忘记之前写下我在这篇文章中学到的所有内容。

So, please do not read this article as if it was written by an expert. If you find any mistakes in this article, please leave a helpful comment or email me so I could improve this article for myself and for everyone.

With that out of the way, here's the plan for this guide.

  • I strongly believe that you won't truly understand the solution if you don't know the problem. So we'll start with a brief exploration into the XSS attack, the security vulnerability CSP is trying to protect us from.
    我坚信,如果你不知道问题所在,你就不会真正理解解决方案。因此,我们将首先简要探讨 XSS 攻击,这是 CSP 试图保护我们免受的安全漏洞。
  • After that, we'll cover the basics of content security policy, including what it is, how it works, and why it's so important. I'll also show you the practical changes and refactoring you need to make to implement CSP in your codebase.
    之后,我们将介绍内容安全策略的基础知识,包括它是什么、它如何工作以及为什么它如此重要。我还将向您展示在代码库中实现 CSP 所需的实际更改和重构。
  • You have to make some effort to refactor your code to follow CSP best practices. If you implement a strict policy without proper planning, the website might break, as CSPs prevent all inline and external scripts and styles. We'll cover the best practices, such as reporting before enforcing and making exceptions with nonce attribute, as well as provide a step-by-step plan for a smooth transition.
    您必须付出一些努力来重构代码以遵循 CSP 最佳实践。如果您在没有适当规划的情况下实施严格的策略,网站可能会崩溃,因为 CSP 会阻止所有内联和外部脚本和样式。我们将介绍最佳实践,例如在强制执行之前进行报告以及使用 nonce 属性进行例外处理,并提供用于顺利过渡的分步计划。
  • We'll wrap up with a reference of all the important CSP-related commands, headers, directives, and their values.
    最后,我们将引用所有重要的 CSP 相关命令、标头、指令及其值。

Sounds good? Let's get started.

This article explains Content Security Policy in a framework-agnostic way. If you want to learn how to implement CSP in the context of a Ruby on Rails application, check out my article: How to Implement Content Security Policy in Rails.
本文以与框架无关的方式解释内容安全策略。如果您想了解如何在 Ruby on Rails 应用程序上下文中实现 CSP,请查看我的文章:如何在 Rails 中实现内容安全策略。

The Problem: Cross-Site Script Injection

TL;DR: Most web applications fetch data from the database, insert it into an HTML template and send the final HTML to the browser. An attacker injects bad JavaScript into the database by submitting it via a form on the website. On the next request, the server sends the bad script to the browser, which executes that script thinking it came from your server. The script does bad things.
TL;DR:大多数 Web 应用程序从数据库中获取数据,将其插入到 HTML 模板中,然后将最终的 HTML 发送到浏览器。攻击者通过网站上的表单提交,将恶意 JavaScript 注入数据库。在下一个请求中,服务器将错误的脚本发送到浏览器,浏览器会执行该脚本,认为它来自您的服务器。脚本做了坏事。

Let's imagine for a moment that you're a hacker who wants to steal the credit card numbers of an e-commerce website's users.

How'd you go about this?

One thing you could do is write some JavaScript that records each keystroke on the payment page as the users enter their credit card details.
您可以做的一件事是编写一些 JavaScript,记录用户输入信用卡详细信息时支付页面上的每次击键。

Since browsers execute any JavaScript on the page, and JavaScript can read, write, or modify any part of a website, if you can find a way to insert your bad, bad JavaScript code on the e-commerce website, then the users' credit card numbers aren't safe anymore.
由于浏览器执行页面上的任何 JavaScript,而 JavaScript 可以读取、写入或修改网站的任何部分,如果你能找到一种方法,在电子商务网站上插入你的糟糕的 JavaScript 代码,那么用户的信用就会受到影响。卡号不再安全。

But for the browser to execute this JavaScript, it needs to be part of the website's code.
但为了让浏览器执行此 JavaScript,它需要成为网站代码的一部分。

How can you get this bad JavaScript code on the e-commerce website, so that the user's browser executes it while they are making the payment?
如何在电子商务网站上获取这些糟糕的 JavaScript 代码,以便用户的浏览器在付款时执行它?

You notice that there's a form on this e-commerce website that lets anyone add reviews about a product. So you create an account, and instead of a real review, you type the following code in the textarea.

After hitting the submit button, you see the alert window pop up, showing you the message you just entered.

How did that happen?  那是怎么发生的?

Well, after you submitted your review, the browser sent that fake review (which instead is JavaScript) to the e-commerce website's backend server, which saved it in the database as plain-text.
好吧,在您提交评论后,浏览器会将虚假评论(而是 JavaScript)发送到电子商务网站的后端服务器,该服务器将其以纯文本形式保存在数据库中。

Next, in the response for that request, the backend redirected you to the same product page, which refreshed and fetched the same fake review from the backend. The backend script promptly fetched the stored review from the database and sent it to the browser.

Upon receiving the HTML containing the fake review, the browser thought it was a script that you wanted to execute, and promptly executed it, showing you the alert box.
在收到包含虚假评论的 HTML 后,浏览器认为这是您想要执行的脚本,并立即执行它,并向您显示警报框。

What's more, since the reviews are public and visible to every user of the website, the same HTML containing the injected script is sent to every other user, greeting them with the same alert!!
更重要的是,由于评论是公开的并且对网站的每个用户都可见,因此包含注入脚本的相同 HTML 会发送给每个其他用户,向他们发出相同的警报!

Now that you know that the website is vulnerable to XSS attacks, you write an elaborate script that records the users' keystrokes and makes a fetch request to post all that data to your other website, which saves all this information for you to exploit later. Or you can hijack the user's session entirely by accessing their session information, which lets you login as that user remotely.
现在您知道该网站容易受到 XSS 攻击,您可以编写一个复杂的脚本来记录用户的击键并发出获取请求以将所有这些数据发布到您的其他网站,从而保存所有这些信息供您以后利用。或者,您可以通过访问用户的会话信息来完全劫持用户的会话,这使您可以以该用户的身份远程登录。

Then you insert it in the database in the same way as the alert script, by adding another fake review on the product page.

Congratulations, you've successfully implemented a cross-site scripting attack!

Any page that fetches user-submitted data from the database can be exploited by an attacker using cross-site scripting vulnerability. An attacker will inject bad JavaScript code into the database, which will be fetched by the website later and executed by the user's browser when they visit the site.
攻击者可以利用跨站点脚本漏洞来利用从数据库获取用户提交的数据的任何页面。攻击者会将不良 JavaScript 代码注入数据库,该代码稍后将由网站获取,并在用户访问该网站时由用户的浏览器执行。

XSS is one of the most common security vulnerabilities. An attacker can try to inject this script anywhere they can submit a form, such as the title or body of an article, product review, or comments. If the website doesn't prevent against XSS, they can get full access to the data on the website.
XSS 是最常见的安全漏洞之一。攻击者可以尝试在可以提交表单的任何位置注入此脚本,例如文章的标题或正文、产品评论或评论。如果网站不防范 XSS,他们就可以完全访问网站上的数据。

Now think from the e-commerce website's developer's point of view. How do you mitigate this attack?

Solution One: Escape HTML Characters
解决方案一:转义 HTML 字符

A simple fix to prevent cross-site scripting attacks, is to take all user-submitted text, and replace any special control characters in it like <, >, etc. with their corresponding entity encoding. This is called character escaping.
防止跨站点脚本攻击的一个简单修复方法是获取所有用户提交的文本,并将其中的任何特殊控制字符(如 <> 等)替换为其相应的实体编码。这称为字符转义。

For example, the HTML

<script>alert('hello world')</script>

is converted to  被转换为

&lt;script&gt;alert('hello world')&lt;/script&gt;

When the browser sees the escaped content, it recognizes and renders it just like the regular text, but it won't treat it like an HTML tag. In other words, it won't execute the code inside the escaped <script> tags, which is what we want.
当浏览器看到转义的内容时,它会像常规文本一样识别并呈现它,但不会将其视为 HTML 标记。换句话说,它不会执行转义的 <script> 标签内的代码,这正是我们想要的。

In general, you need to escape the following characters in the context of HTML, so they don't add any special behavior to the markup.
一般来说,您需要在 HTML 上下文中转义以下字符,这样它们就不会向标记添加任何特殊行为。

  1. less than symbol (<) with &lt;
    小于号 (<) 和 &lt;
  2. greater than symbol (>) with &gt;
    大于符号 (>) 和 &gt;
  3. double quotes (") with &quot;
    &quot; 的双引号 (")
  4. single quote (’) with &#39;
    &#39; 的单引号 (’)
  5. ampersand (&) with &amp; 与号 (&) 与 &amp;

Often, you don't need to worry about escaping special characters, as most modern web frameworks handle it for you. For example, in Ruby on Rails, the ERB templating engine escapes dynamic content by default.
通常,您无需担心转义特殊字符,因为大多数现代 Web 框架都会为您处理它。例如,在 Ruby on Rails 中,ERB 模板引擎默认转义动态内容。

  <%= review %>

If you do want to write raw, unescaped HTML, you have to instruct it explicitly to do so, e.g. using the raw function, or the <%== variable %> ERB format (note the double ==).
如果您确实想编写原始的、未转义的 HTML,则必须明确指示它这样做,例如使用 raw 函数或 <%== variable %> ERB 格式(注意双 == )。

In many cases, ensuring the user-submitted HTML content is properly escaped takes you a long way towards protecting your users from XSS attacks. If you need to go all the way, you've to combine escaping with a strict Content Security Policy.
在许多情况下,确保正确转义用户提交的 HTML 内容可以帮助您保护用户免受 XSS 攻击。如果您需要一路走下去,则必须将转义与严格的内容安全策略结合起来。

Solution Two: Content Security Policy

The basic idea behind CSP is to block inline script execution and provide the list of allowed sources of trusted content (scripts, stylesheets, fonts, plugins, etc.) to the browser. Even if an attacker gets to inject their bad script, the browser won't execute it since CSP prevents inline script execution.
CSP 背后的基本思想是阻止内联脚本执行并向浏览器提供允许的受信任内容源(脚本、样式表、字体、插件等)列表。即使攻击者注入了错误的脚本,浏览器也不会执行它,因为 CSP 会阻止内联脚本执行。

The root cause of XSS attacks is the browser's ability to execute any and all inline JavaScript included in the HTML. What if there was a way to tell the browser to not execute any inline JavaScript, as well as any scripts loaded from external domains?
XSS 攻击的根本原因是浏览器执行 HTML 中包含的所有内联 JavaScript 的能力。如果有一种方法告诉浏览器不要执行任何内联 JavaScript 以及从外部域加载的任何脚本怎么办?

By inline, I mean any code that's written between the <script> tags. That would only leave the safe JavaScript code included with the <script src="my-domain.com/safe.js">, i.e. with the src attribute.
内联是指在 <script> 标记之间编写的任何代码。这只会留下 <script src="my-domain.com/safe.js"> 中包含的安全 JavaScript 代码,即 src 属性。

You can achieve this using the Content-Security-Policy header on the HTTP response, which dictates the security policy for the content. In its essence, this header does two things:
您可以使用 HTTP 响应上的 Content-Security-Policy 标头来实现此目的,该标头规定了内容的安全策略。从本质上讲,这个标头做了两件事:

  1. Instructs the browser to block all inline script execution
  2. Provide a list of safe domains from where to load external resources, preventing all other sources.

Although most often it's used for scripts, it also controls other resources like stylesheets, fonts, plugins, etc.

Implementing a strict CSP prevents browsers from fetching scripts from any other locations. This makes it really, really hard for a bad actor to inject bad JavaScript code in your website. Even if they inject it somehow, the browser won't execute it since it's inline.
实施严格的 CSP 可防止浏览器从任何其他位置获取脚本。这使得不良行为者很难在您的网站中注入不良 JavaScript 代码。即使他们以某种方式注入它,浏览器也不会执行它,因为它是内联的。

All modern browsers support CSP. Since cross-site scripting attacks happen due to unwanted JavaScript execution on the browser, locking down all external and inline JavaScript goes a long way towards preventing XSS attacks.
所有现代浏览器都支持 CSP。由于跨站点脚本攻击是由于浏览器上不需要的 JavaScript 执行而发生的,因此锁定所有外部和内联 JavaScript 对防止 XSS 攻击大有帮助。

When your website implements a strict CSP, an attacker won't be able to run any bad JavaScript on the site.
当您的网站实施严格的 CSP 时,攻击者将无法在网站上运行任何不良 JavaScript。

So, how does it work?

Here's an example of a standard CSP header.
以下是标准 CSP 标头的示例。

Content-Security-Policy: script-src 'self' https://safe-external-site.com; style-src 'self'

In above header the term script-src and style-src are directives that specify valid sources for JavaScript and stylesheets, respectively.
在上面的标头中,术语 script-srcstyle-src 是分别指定 JavaScript 和样式表有效源的指令。

Both directives tell the browser not to execute any inline JavaScript or stylesheet. In addition,
这两个指令都告诉浏览器不要执行任何内联 JavaScript 或样式表。此外,

  1. The script policy tells the browser to only run scripts fetched from the same domain (self) or from the domain safe-external-site.com. If the website is hosted at mysite.com, the browser will prevent all scripts except the ones loaded from mysite.com and safe-external-site.com, since we explicitly allowed it.
    脚本策略告诉浏览器仅运行从同一域 ( self ) 或域 safe-external-site.com 获取的脚本。如果网站托管在 mysite.com,则浏览器将阻止除从 mysite.com 和 safe-external-site.com 加载的脚本之外的所有脚本,因为我们明确允许这样做。
  2. The style policy tells it to only allow stylesheets from the same domain. No external stylesheets will be permitted.
The browser will execute JavaScript only if it's imported from your own domain via a src attribute in the <script> element.
仅当 JavaScript 通过 <script> 元素中的 src 属性从您自己的域导入时,浏览器才会执行 JavaScript。

Instead of using an HTTP header, you could also provide the policy in the <meta> tag, which lives under the <head> element.
您还可以在 <meta> 标记(位于 <head> 元素下)中提供策略,而不是使用 HTTP 标头。

Here's a content security policy provided via the <meta> tag.
这是通过 <meta> 标签提供的内容安全策略。

<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://safe-external-site.com">

`unsafe-inline` lets you bypass the CSP
`unsafe-inline` 可让您绕过 CSP

If, for some reason, you have to permit inline JavaScript or styles, use the unsafe-inline value on the corresponding directive. For example, the following policy allows all inline JavaScript, and hence defeats the basic purpose of having a CSP.
如果由于某种原因,您必须允许内联 JavaScript 或样式,请在相应指令上使用 unsafe-inline 值。例如,以下策略允许所有内联 JavaScript,因此违背了 CSP 的基本目的。

Content-Security-Policy: script-src 'self' 'unsafe-inline' https://safe-external-site.com

If you do have to use inline scripts, using the unsafe-inline value is not recommended. As we'll see in the next section, there's a much better solution if you absolutely need to execute inline script.
如果您确实必须使用内联脚本,则不建议使用 unsafe-inline 值。正如我们将在下一节中看到的,如果您绝对需要执行内联脚本,那么有一个更好的解决方案。

Important CSP Directives and Values
重要的 CSP 指令和价值观

So far, we've seen the script-src and style-src, which specify valid sources for scripts and stylesheets. Here're a few other important ones. For a complete list, check out the MDN documentation for directives.
到目前为止,我们已经看到了 script-srcstyle-src ,它们指定了脚本和样式表的有效源。这里还有其他一些重要的内容。有关完整列表,请查看 MDN 文档中的指令。

  • default-src: Defines the default policy for fetching all resources. Whenever a more specific policy is absent, the browser will fallback to the default-src policy directive. Example: default-src 'self' trusted-domain.com
    default-src :定义获取所有资源的默认策略。每当缺少更具体的策略时,浏览器将回退到 default-src 策略指令。示例: default-src 'self' trusted-domain.com
  • img-src: Defines valid sources for images, e.g. img-src 'self' img.mydomain.com
    img-src :定义图像的有效来源,例如 img-src 'self' img.mydomain.com
  • font-src: Defines valid sources for font resources.
    font-src :定义字体资源的有效来源。
  • object-src: Defines valid sources for plugins and external content, like <object> or <embed> elements.
    object-src :定义插件和外部内容的有效源,例如 或 元素。
  • media-src: Defines valid sources for audio and video.
    media-src :定义音频和视频的有效源。

In addition to unsafe-inline, the source list also accepts the following values. Again, this is not a comprehensive list, refer to the MDN docs for more values.
除了 unsafe-inline 之外,源列表还接受以下值。同样,这不是一个完整的列表,请参阅 MDN 文档以获取更多值。

  • 'none' : Match nothing.  'none' :不匹配任何内容。
  • 'self': Match the current host domain (same origin, i.e. host and port). Do not match subdomains, though.
    'self' :匹配当前主机域(同源,即主机和端口)。但不要匹配子域。
  • 'unsafe-inline': Allow inline JavaScript and CSS.
    'unsafe-inline' :允许内联 JavaScript 和 CSS。
  • 'unsafe-eval': Allow dynamic text to JavaScript evaluations.
    'unsafe-eval' :允许动态文本进行 JavaScript 计算。
  • domain.example.com: Allow loading resource from the specified domain. To match any subdomain, use the * wildcard, e.g. *.example.com
    domain.example.com :允许从指定域加载资源。要匹配任何子域,请使用 * 通配符,例如*.example.com
  • https: or ws:: Allow loading resources only over HTTPS or WebSocket.
    https:ws: :允许仅通过 HTTPS 或 WebSocket 加载资源。
  • nonce-{token}: Allow an inline script or CSS containing the same nonce attribute value.
    nonce-{token} :允许内联脚本或CSS包含相同的nonce属性值。

Note: The quotes '' are required for the first four values.
注意:前四个值需要加引号 ''

When You Do Need Inline Scripts and Styles, Use nonce Attribute
当您确实需要内联脚本和样式时,请使用 nonce 属性

The nonce attribute is useful to allow specific inline script or style elements. It helps you to avoid using the CSP unsafe-inline directive, which allows all inline code.
nonce 属性对于允许特定的内联脚本或样式元素很有用。它可以帮助您避免使用 CSP unsafe-inline 指令,该指令允许所有内联代码。

The term nonce refers to a word or a phrase that's used only once.
术语 nonce 指的是仅使用一次的单词或短语。

In the context of CSP, nonce is an attribute on the <script> and <style> tags that lets you allow specific inline script and style elements, while still blocking all other inline scripts and styles. This lets you avoid the use of unsafe-inline directive, which allows all inline executions and is not safe.
在 CSP 上下文中, nonce<script><style> 标记上的一个属性,允许您允许特定的内联 scriptstyle 元素,同时仍然阻止所有其他内联脚本和样式。这可以让您避免使用 unsafe-inline 指令,该指令允许所有内联执行并且不安全。

Using the nonce attribute lets you keep the essential benefits of CSP while still allowing specific inline executions that you absolutely do need.
使用 nonce 属性可以让您保留 CSP 的基本优势,同时仍然允许您绝对需要的特定内联执行。

How does it work? 它是如何工作的?

  • For every request, the server creates a random base64-encoded string that cannot be guessed. This is the nonce value, e.g. dGhpcyBpcyBhIG5v==
    对于每个请求,服务器都会创建一个无法猜测的随机 Base64 编码字符串。这是随机数值,例如 dGhpcyBpcyBhIG5v==
  • The server inserts this nonce value in all the inline script and style elements that you want to allow, as the value for the nonce attribute.
    服务器将此随机数值插入到您想要允许的所有内联脚本和样式元素中,作为 nonce 属性的值。
<script nonce="dGhpcyBpcyBhIG5v==">.. ..</script>

<style nonce="dGhpcyBpcyBhIG5v==">.. ..</style>
  • The server also inserts the same nonce value in the Content-Security-Policy header for script-src and style-src directives.
    服务器还在 script-srcstyle-src 指令的 Content-Security-Policy 标头中插入相同的随机数值。
Content-Security-Policy: script-src 'nonce-dGhpcyBpcyBhIG5v=='; style-src 'nonce-dGhpcyBpcyBhIG5v=='
When the browser receives the HTTP response containing the headers, it sees the nonce value in the header, then scans the HTML containing inline scripts and styles, and only executes those scripts and styles that have their nonce attribute set to this value.
当浏览器收到包含标头的 HTTP 响应时,它会看到标头中的随机数值,然后扫描包含内联脚本和样式的 HTML,并仅执行那些将 nonce 属性设置为该值的脚本和样式。

Since the attacker can't guess the token, they can't inject bad JavaScript.
由于攻击者无法猜测令牌,因此他们无法注入不良 JavaScript。

To summarize, the presence of nonce attribute on specific script and style elements instructs the browser to allow inline execution in those elements, while still blocking all other inline scripts and styles that don't have the nonce attribute.
总而言之,特定脚本和样式元素上存在 nonce 属性指示浏览器允许在这些元素中内联执行,同时仍然阻止所有其他不具有 nonce 属性的内联脚本和样式。

It tells the browser that these elements were not injected by the hacker (since they couldn't guess the nonce value), but were intentionally inserted by the server, so they're safe to execute.

Adopting CSP: Start by Reporting, but Not Enforcing CSP Violations
采用 CSP:从报告开始,但不强制执行 CSP 违规行为

Use the Content-Security-Policy-Report-Only header to iteratively work on your content security policy. You observe how your site behaves, watching for violation reports, then choose the desired policy enforced by the Content-Security-Policy header. - MDN
使用 Content-Security-Policy-Report-Only 标头迭代地处理您的内容安全策略。您观察站点的行为方式,监视违规报告,然后选择 Content-Security-Policy 标头强制执行的所需策略。 -MDN

Implementing a strict CSP policy sounds great in theory, but the reality is not as simple. Many legacy websites abundantly use inline JavaScript or scripts hosted on external domains.
实施严格的光热发电政策理论上听起来不错,但现实并不那么简单。许多旧网站大量使用内联 JavaScript 或托管在外部域上的脚本。

What if you have a huge legacy codebase that's littered with inline scripts within PHP/HTML files everywhere? On top of that, you have no idea how many places you're using inline and external scripts. What to do?
如果您有一个巨大的遗留代码库,其中到处都是 PHP/HTML 文件中的内联脚本,该怎么办?最重要的是,您不知道有多少地方使用了内联和外部脚本。该怎么办?

Since CSP prevents all inline scripts and blocks all external sources by default, you'll either have to remove/refactor all inline JavaScript and external sources or explicitly allow them in the policy.
由于 CSP 默认情况下会阻止所有内联脚本并阻止所有外部源,因此您必须删除/重构所有内联 JavaScript 和外部源,或者在策略中明确允许它们。

Otherwise, if you immediately enable a strict policy preventing all inline or external scripts and stylesheets, you're in trouble. the browser will neither load external scripts/styles nor execute inline scripts, and the websites will crash and burn for your users.

You don't want that. 你不想要这样。

How should you go about it, then? You know it's going to take a long time to refactor the code to follow the best practices. And how do you find out where are you violating CSP?
那你该怎么办呢?您知道重构代码以遵循最佳实践需要很长时间。您如何找出哪里违反了 CSP?

There's an elegant solution for this dilemma. Instead of immediately setting a strict CSP and risking any crashes in production, you can use the report-only mode.
对于这个困境有一个优雅的解决方案。您可以使用仅报告模式,而不是立即设置严格的 CSP 并冒着生产中崩溃的风险。

Using the Cotent-Security-Policy-Report-Only header, you can tell the browser to only report, but not enforce the policies.
使用 Cotent-Security-Policy-Report-Only 标头,您可以告诉浏览器仅报告,但不强制执行策略。

So the browser will still load the external scripts and execute any inline JavaScript, but it will also report all violations to an endpoint defined in the policy using the report-to directive.
因此,浏览器仍将加载外部脚本并执行任何内联 JavaScript,但它还会使用 report-to 指令向策略中定义的端点报告所有违规行为。

Content-Security-Policy-Report-Only: script-src 'self'; report-to https://mysite.com/csp-violations

The endpoint can be a route in your application where you write the code to parse the violation data and save it in the database, or you can use a third-party service like report-uri.com which shows it nicely in a table in the browser, with support for filtering and ordering.
端点可以是应用程序中的一条路径,您可以在其中编写代码来解析违规数据并将其保存在数据库中,或者您可以使用第三方服务,例如 report-uri.com ,它可以在浏览器的表格中很好地显示它,支持过滤和排序。

In any case, you can see all the places where you need to make a change in the code (or in the policy) to follow the security policy.

Pretty cool.  很酷。

How to Implement a Strict CSP
如何实施严格的 CSP

If you're still reading, and convinced that you need to implement a strict content security policy for your website or app, you'll need to add the CSP header, change your HTML templates to add nonce attributes, explicitly allow external domains for the content sources, and test everything works and nothing's broken for your users.
如果您仍在阅读本文,并确信需要为网站或应用程序实施严格的内容安全策略,则需要添加 CSP 标头、更改 HTML 模板以添加 nonce 属性、明确允许外部域对于内容源,并测试一切正常,并且对用户来说没有任何问题。

Here's a step-by-step plan to start implementing content security policy on your website.

  1. Report, don't enforce. Start with the Content-Security-Policy-Report-Only header to iteratively work on your policy. Keep an eye on violation reports, and fix issues as they arise until there're no more violation reports.
    举报,不执行。从 Content-Security-Policy-Report-Only 标头开始迭代地处理您的策略。密切关注违规报告,并在出现问题时予以解决,直到不再有违规报告为止。
  2. Refactor your code to remove inline JavaScript and move it to JavaScript files loaded with the src attribute on the <script> element. Refactor the HTML containing inline event handlers like onclick and javascript: URIs.
    重构代码以删除内联 JavaScript 并将其移动到使用 <script> 元素上的 src 属性加载的 JavaScript 文件。重构包含内联事件处理程序(例如 onclickjavascript: URI)的 HTML。
  3. Make exceptions. For the places where you have to execute inline scripts or use inline CSS, generate a nonce token on the server, put it on the nonce attribute for the script and style elements, and pass it to the policy. If you need to load external scripts and styles, add those domains to the policy.
    例外。对于必须执行内联脚本或使用内联 CSS 的地方,请在服务器上生成 nonce 令牌,将其放在脚本和样式元素的 nonce 属性上,并将其传递给策略。如果您需要加载外部脚本和样式,请将这些域添加到策略中。
  4. Enforce the policy. Set the Content-Security-Policy header for your site. Most likely, your backend framework supports this already, either as part of configuration (Rails) or with a middleware (Laravel). If not, just set one manually.
    执行政策。为您的网站设置 Content-Security-Policy 标头。最有可能的是,您的后端框架已经支持这一点,无论是作为配置的一部分(Rails)还是通过中间件(Laravel)。如果没有,只需手动设置一项即可。
  5. Test thoroughly. Before deploying to production, let your testing team know about the CSP and ask them to watch out any CSP violation reports in the console. Finally, watch out for CSP violations in production on the users' browser in the endpoint logs or on the site report-uri.com.
    彻底测试。在部署到生产之前,让您的测试团队了解 CSP 并要求他们注意控制台中的任何 CSP 违规报告。最后,请注意端点日志中或网站 report-uri.com 上用户浏览器上生产中的 CSP 违规情况。
Separating the JavaScript from the HTML is a best practice anyway, since it cleans up the HTML and keeps the codebase organized. In a way, implementing a stricter CSP and banning any inline JavaScript code forces you to follow the best practices.
无论如何,将 JavaScript 与 HTML 分离是最佳实践,因为它可以清理 HTML 并保持代码库井井有条。在某种程度上,实施更严格的 CSP 并禁止任何内联 JavaScript 代码会迫使您遵循最佳实践。

Here're a few examples of the types of changes you'll be making.

  • Add nonces to <script> and <style> elements
    将随机数添加到 <script><style> 元素
<script src="my_script.js"></script>
<script>alert('hello world')</script>

  body {
      background-color: green;   

becomes 变成

<script nonce="token" src="my_script.js"></script>
<script nonce="token">alert('hello')</script>

<style nonce="token">
  body {
      background-color: green;   
  • Refactor inline JavaScript handlers and URIs
    重构内联 JavaScript 处理程序和 URI

Often, legacy websites can contain event handlers using the onclick or onerror callbacks. These are vulnerable to XSS attacks. Refactor this so the handlers are added from JavaScript block. Ideally, you should move it to a separate JavaScript file.
通常,旧网站可以包含使用 onclickonerror 回调的事件处理程序。这些很容易受到 XSS 攻击。重构它,以便从 JavaScript 块添加处理程序。理想情况下,您应该将其移动到单独的 JavaScript 文件中。

    function handle() {
      // click handler code

<button onclick="handle();">Click Me</button>

becomes 变成

<button id="submit-btn">Click Me</button>

<script nonce="token">
document.addEventListener('DOMContentLoaded', function () {
          .addEventListener('click', () => { 
            // click handler code

It's important to remember that the policy is defined per page. You need to send this header on every HTTP response that you want to protect. That said, the framework should take care of this for you. For example, Rails lets you define it for the whole application as well as on the controller-action level.
请务必记住,策略是按页面定义的。您需要在要保护的每个 HTTP 响应上发送此标头。也就是说,框架应该为您处理这个问题。例如,Rails 允许您为整个应用程序以及控制器操作级别定义它。

Conclusion 结论

In cross-site scripting injection attack, an attacker tries to inject bad code in the HTML of your website, tricking the user's browser into executing that code. To prevent this, you need to escape the user-submitted data you receive from forms. In addition, you need to set a strict content security policy instructing browsers to prevent all inline script execution and only loading external scripts from pre-approved domains.
在跨站点脚本注入攻击中,攻击者试图在网站的 HTML 中注入不良代码,欺骗用户的浏览器执行该代码。为了防止这种情况,您需要转义从表单收到的用户提交的数据。此外,您需要设置严格的内容安全策略,指示浏览器阻止所有内联脚本执行,并仅从预先批准的域加载外部脚本。

Once you start escaping user-submitted content and implement a strict CSP, you're well on your way to protecting your users. It's very difficult for an hacker to inject any random script in the page with a strict and tight security policy.
一旦您开始转义用户提交的内容并实施严格的 CSP,您就可以很好地保护您的用户。黑客很难在具有严格且严密的安全策略的页面中注入任何随机脚本。

That said, having a strict CSP doesn't fully guarantee that your site is full-proof from attacks. Combine it with other security best-practices and a manual security review to further decrease the chances of an attack.
也就是说,拥有严格的 CSP 并不能完全保证您的网站完全免受攻击。将其与其他安全最佳实践和手动安全审查相结合,以进一步减少攻击的机会。

Parting words: If you're running a static blog without any user-generated content, you probably don't need to worry about having a strict CSP. If on the other hand, you're running an application that manages sensitive, personally identifiable information in industries like healthcare and finance, it's crucial to have a strict content security policy.
结束语:如果您运行的静态博客没有任何用户生成的内容,您可能不需要担心严格的 CSP。另一方面,如果您运行的应用程序管理医疗保健和金融等行业中的敏感个人身份信息,则制定严格的内容安全策略至关重要。

References: To Learn More

To learn how to implement Content Security Policy in Ruby on Rails applications, check out this post.
要了解如何在 Ruby on Rails 应用程序中实施内容安全策略,请查看这篇文章。

How to Implement Content Security Policy in Rails
如何在 Rails 中实施内容安全策略
This article shows how to implement content security policy in your Rails applications to protect against cross-site scripting (XSS) vulnerability. We’ll also learn how you can report CSP violations without enforcing the policy and make exceptions for inline scripts with nonce attributes.
本文介绍如何在 Rails 应用程序中实施内容安全策略以防止跨站点脚本 (XSS) 漏洞。我们还将了解如何在不强制执行策略的情况下报告 CSP 违规行为,并为具有随机数属性的内联脚本设置例外。

If you liked this article, check out my post on Cross-Site Request Forgery attack.

Cross-Site Request Forgery (CSRF) Attack: What It Is, How It Works, and How to Prevent It
跨站请求伪造 (CSRF) 攻击:它是什么、如何工作以及如何预防
CSRF vulnerability tricks authenticated users of an application to perform a dangerous activity on that application, simply by clicking a link. This post explains how CSRF works with a practical example, and shows how to protect against it, both as a user and a developer building web applications.
CSRF 漏洞会欺骗应用程序的经过身份验证的用户,只需单击链接即可在该应用程序上执行危险活动。这篇文章通过一个实际示例解释了 CSRF 的工作原理,并展示了作为构建 Web 应用程序的用户和开发人员如何防范 CSRF。

Then, learn about how Rails prevents CSRF attacks with authenticity tokens.
然后,了解 Rails 如何使用真实性令牌防止 CSRF 攻击。

How Rails Authenticity Tokens Protect Against CSRF Vulnerability
Rails 真实性令牌如何防范 CSRF 漏洞
Rails protects your web application from CSRF attack by including an authenticity token in the HTML forms. This token is also stored in the user’s session. Upon receiving a request, Rails compares these two tokens to decide if the request is verified.
Rails 通过在 HTML 表单中包含真实性令牌来保护您的 Web 应用程序免受 CSRF 攻击。该令牌也存储在用户的会话中。收到请求后,Rails 会比较这两个令牌来决定请求是否经过验证。

That's a wrap. I hope you liked this article and you learned something new. If you're new to the blog, check out the full archive to see all the posts I've written so far or the favorites page for the most popular articles on this blog.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.

Drag to outliner or Upload
  • Yellow
  • Blue
  • Green
  • Pink