分析 `Content Security Policy` (`CSP`) 的 `Strict-CSP` 和 `Trusted Types` 如何有效防御 `XSS` 攻击。

大家好,各位观众老爷们,今儿咱们来聊聊CSP和Trusted Types这对儿CP,看看它们是怎么联手把XSS这只烦人的小强拍死的。

先给大家倒杯茶,润润嗓子。咱们今天的内容可不少,得好好说道说道。XSS这玩意儿,大家都知道,搞不好就把你辛辛苦苦攒的家底儿给偷走了,所以防御XSS是每个前端工程师的必修课。

一、XSS,你这个磨人的小妖精!

XSS (Cross-Site Scripting) ,跨站脚本攻击,简单来说就是攻击者往你的网站里塞了一些恶意脚本,这些脚本在用户的浏览器里执行,然后攻击者就可以偷用户的信息,或者冒充用户干坏事。

XSS分三种:

  • 存储型 XSS (Stored XSS): 攻击者把恶意脚本存到你的数据库里,比如评论区,然后每个访问这个评论的用户都会被攻击。这种危害最大。
  • 反射型 XSS (Reflected XSS): 攻击者通过URL参数,或者POST请求,把恶意脚本传到服务器,服务器没做处理直接返回给用户,然后用户的浏览器就执行了这个恶意脚本。
  • DOM 型 XSS (DOM-based XSS): 攻击者通过修改页面的DOM结构来注入恶意脚本。这种攻击不需要经过服务器,完全在客户端发生。

二、Content Security Policy (CSP):给你的网站加一道安全锁

CSP,内容安全策略,就像给你的网站加了一道安全锁,告诉浏览器哪些来源的内容是可信的,哪些是不可信的。浏览器收到CSP指令后,就会严格按照这个指令执行,拒绝加载不可信来源的内容。

CSP 的原理:

CSP 通过 HTTP 响应头 Content-Security-Policy 或者 HTML 中的 <meta> 标签来设置。它定义了一系列的指令,每个指令指定了某种类型资源的来源白名单。

CSP 的语法:

Content-Security-Policy: <指令> <来源>; <指令> <来源>; ...
  • 指令 (Directive): 指定要控制哪种类型的资源,比如 script-src 控制脚本,style-src 控制样式,img-src 控制图片等等。
  • 来源 (Source): 指定允许加载资源的域名、协议或者特殊值,比如 'self' 表示同源,'unsafe-inline' 允许内联脚本,'unsafe-eval' 允许使用 eval() 函数等等。

CSP 的例子:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' https://cdn.example.com; img-src 'self' data:;

这个CSP策略表示:

  • default-src 'self': 默认只允许加载同源的资源。
  • script-src 'self' https://cdn.example.com: 只允许加载同源的脚本,以及来自 https://cdn.example.com 的脚本。
  • style-src 'self' https://cdn.example.com: 只允许加载同源的样式,以及来自 https://cdn.example.com 的样式。
  • img-src 'self' data:: 只允许加载同源的图片,以及使用 data URI 的图片。

CSP 的指令详解:

指令 描述
default-src 定义了所有其他未明确声明指令的默认策略。
script-src 定义了允许加载脚本的来源。
style-src 定义了允许加载样式的来源。
img-src 定义了允许加载图片的来源。
connect-src 定义了允许建立网络连接的来源 (例如,使用 XMLHttpRequest, WebSocket, EventSource)。
font-src 定义了允许加载字体的来源。
media-src 定义了允许加载媒体文件的来源 (例如,使用 <audio>, <video>, <source>)。
object-src 定义了允许加载插件的来源 (例如,使用 <object>, <embed>, <applet>)。
frame-src 定义了允许加载 frame 的来源 (例如,使用 <iframe>, <frame>)。
sandbox 为请求的资源启用沙盒机制。
report-uri 指定一个 URI,浏览器会将违反 CSP 策略的报告发送到该 URI。
report-to 指定一个或多个组名称,这些组在 Report-To HTTP 响应头中定义,浏览器会将违反 CSP 策略的报告发送到这些组。这是 report-uri 的替代方案,更现代且功能更强大。
worker-src 定义了允许作为 worker, shared worker 或 service worker 加载的 URL。
base-uri 指定文档可以使用哪些 URL 来解析基 URL。
form-action 指定可以提交表单的 URL。
frame-ancestors 指定允许嵌入当前资源的来源。这与 X-Frame-Options HTTP 响应头类似,但功能更强大。
upgrade-insecure-requests 指示浏览器自动将页面上的所有不安全 URL (HTTP) 升级为安全 URL (HTTPS)。这有助于防止混合内容问题。

CSP 的部署方式:

  1. HTTP 响应头: 这是推荐的方式,因为可以灵活地控制每个页面的 CSP 策略。

    HTTP/1.1 200 OK
    Content-Security-Policy: <CSP策略>
  2. HTML <meta> 标签: 可以在 HTML 文档的 <head> 标签中使用 <meta> 标签来设置 CSP 策略。

    <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Security-Policy" content="<CSP策略>">
    </head>
    <body>
        ...
    </body>
    </html>

CSP 的两种模式:

  • Enforce 模式: 这是默认模式,浏览器会严格按照 CSP 策略执行,阻止违反策略的资源加载和脚本执行。
  • Report-Only 模式: 浏览器不会阻止违反策略的资源加载和脚本执行,但是会将违反策略的报告发送到 report-uri 或者 report-to 指令指定的 URI。 这种模式可以用来测试 CSP 策略,在不影响用户体验的情况下,发现潜在的安全问题。

    Content-Security-Policy-Report-Only: <CSP策略>

三、Strict-CSP:更严格的安全守护

Strict-CSP 是指一个配置非常严格的 CSP 策略,它旨在最大限度地减少 XSS 攻击的风险。 通常,这意味着要避免使用像 'unsafe-inline''unsafe-eval' 这样的不安全来源,并尽可能使用 noncehash 来允许特定的内联脚本和样式。

为什么需要 Strict-CSP?

普通的 CSP 策略可能会因为配置不当而留下安全漏洞。 例如,如果使用了 'unsafe-inline',那么攻击者仍然可以注入内联脚本来执行恶意代码。 Strict-CSP 通过禁用这些不安全的特性,来提高网站的安全性。

Strict-CSP 的关键特性:

  1. 禁用 unsafe-inlineunsafe-eval 这是 Strict-CSP 的核心原则。 这两个来源允许执行内联脚本和使用 eval() 函数,这是 XSS 攻击的主要入口。

  2. 使用 noncehash 来允许特定的内联脚本和样式: 如果必须使用内联脚本或样式,可以使用 noncehash 来指定允许执行的特定代码块。

    • Nonce (Number used once): 一个随机字符串,在每次页面加载时生成,并添加到 CSP 策略和允许执行的内联脚本或样式的 <script><style> 标签中。

      <script nonce="rAnd0mNumb3r">
          // Your inline script
      </script>
      Content-Security-Policy: script-src 'nonce-rAnd0mNumb3r';
    • Hash: 内联脚本或样式的 SHA256, SHA384 或 SHA512 哈希值。

      <script>
          // Your inline script
          console.log('Hello, world!');
      </script>
      Content-Security-Policy: script-src 'sha256-yourScriptHash';
  3. 限制 default-src 尽可能将 default-src 设置为 'none',然后根据需要显式地允许特定类型的资源。

  4. 使用 upgrade-insecure-requests 指示浏览器自动将页面上的所有不安全 URL (HTTP) 升级为安全 URL (HTTPS)。

一个 Strict-CSP 的例子:

Content-Security-Policy:
    default-src 'none';
    script-src 'self' 'nonce-rAnd0mNumb3r';
    style-src 'self' 'nonce-rAnd0mNumb3r';
    img-src 'self' data:;
    font-src 'self';
    connect-src 'self';
    upgrade-insecure-requests;

部署 Strict-CSP 的步骤:

  1. 分析你的网站: 确定你的网站需要哪些资源,以及这些资源的来源。
  2. 生成 nonce 在服务器端生成一个随机的 nonce 值,并将其添加到 CSP 策略和允许执行的内联脚本和样式中。
  3. 配置你的服务器: 将 CSP 策略添加到 HTTP 响应头中。
  4. 测试你的 CSP 策略: 使用 Report-Only 模式来测试你的 CSP 策略,确保它不会阻止正常的网站功能。
  5. 切换到 Enforce 模式: 一旦你确定你的 CSP 策略是正确的,就可以切换到 Enforce 模式。

代码示例(Python Flask):

from flask import Flask, render_template, make_response
import secrets

app = Flask(__name__)

@app.route('/')
def index():
    nonce = secrets.token_urlsafe(16)
    csp = f"""
        default-src 'none';
        script-src 'self' 'nonce-{nonce}';
        style-src 'self' 'nonce-{nonce}';
        img-src 'self' data:;
        font-src 'self';
        connect-src 'self';
        upgrade-insecure-requests;
    """
    resp = make_response(render_template('index.html', nonce=nonce))
    resp.headers['Content-Security-Policy'] = csp
    return resp

if __name__ == '__main__':
    app.run(debug=True)
<!DOCTYPE html>
<html>
<head>
    <title>Strict CSP Example</title>
</head>
<body>
    <h1>Hello, world!</h1>
    <script nonce="{{ nonce }}">
        console.log('This is an inline script!');
    </script>
    <style nonce="{{ nonce }}">
        body {
            background-color: #f0f0f0;
        }
    </style>
</body>
</html>

四、Trusted Types:让你的DOM操作更安全

Trusted Types 是一种新的 Web API,旨在防止 DOM 型 XSS 攻击。 它通过要求所有将数据插入 DOM 的操作都必须使用经过 类型转换 的值来实现这一点。 换句话说,你不能直接将字符串插入 DOM,而是需要先将它转换为 TrustedHTML, TrustedScriptTrustedScriptURL 类型。

Trusted Types 的原理:

Trusted Types 通过引入一个新的类型系统来限制 DOM 操作。 它定义了三种新的类型:

  • TrustedHTML: 表示安全的 HTML 片段。
  • TrustedScript: 表示安全的 JavaScript 代码。
  • TrustedScriptURL: 表示安全的 JavaScript URL。

要将字符串插入 DOM,你需要先使用一个 策略 来将它转换为这些类型之一。 策略是一个 JavaScript 函数,它接收一个字符串,并返回一个 TrustedHTML, TrustedScriptTrustedScriptURL 对象。 策略可以对字符串进行验证和清理,以确保它不包含恶意代码。

Trusted Types 的例子:

// 创建一个策略
const policy = trustedTypes.createPolicy('myPolicy', {
    createHTML: (input) => {
        // 对输入进行验证和清理
        const cleanInput = DOMPurify.sanitize(input); // 使用 DOMPurify 进行 HTML 清理
        return cleanInput; // 假设 DOMPurify 返回一个 TrustedHTML 对象
    },
    createScriptURL: (input) => {
        // 验证输入是否是一个安全的 URL
        if (input.startsWith('https://example.com/')) {
            return input; // 假设返回一个 TrustedScriptURL 对象
        }
        throw new Error('Invalid script URL');
    },
    createScript: (input) => {
        // 验证输入是否是一个安全的 JavaScript 代码
        if (input.indexOf('alert(') === -1) {
            return input; // 假设返回一个 TrustedScript 对象
        }
        throw new Error('Invalid script');
    }
});

// 使用策略来创建 TrustedHTML 对象
const trustedHTML = policy.createHTML('<p>Hello, world!</p><img src=x onerror=alert(1)>');

// 将 TrustedHTML 对象插入 DOM
document.getElementById('container').innerHTML = trustedHTML;

// 使用策略来创建 TrustedScriptURL 对象
const trustedScriptURL = policy.createScriptURL('https://example.com/script.js');

// 将 TrustedScriptURL 对象插入 DOM
const script = document.createElement('script');
script.src = trustedScriptURL;
document.body.appendChild(script);

Trusted Types 的步骤:

  1. 启用 Trusted Types: 通过设置 CSP 策略来启用 Trusted Types。

    Content-Security-Policy: require-trusted-types-for 'script';
  2. 创建策略: 使用 trustedTypes.createPolicy() 函数来创建策略。

  3. 使用策略来创建 Trusted Types 对象: 使用策略的 createHTML(), createScriptURL()createScript() 方法来将字符串转换为 TrustedHTML, TrustedScriptTrustedScriptURL 对象。

  4. 将 Trusted Types 对象插入 DOM: 使用 innerHTML, src 等属性来将 Trusted Types 对象插入 DOM。

Trusted Types 的兼容性:

Trusted Types 的兼容性目前还不是很好,需要使用 polyfill 来支持旧版本的浏览器。

Trusted Types 的优势:

  • 防止 DOM 型 XSS 攻击: 通过要求所有 DOM 操作都使用经过类型转换的值,可以有效地防止 DOM 型 XSS 攻击。
  • 提高代码的可维护性: Trusted Types 可以帮助你更好地理解和控制你的代码中的 DOM 操作。
  • 提供更好的安全保障: Trusted Types 可以提供比传统的 XSS 防御方法更好的安全保障。

五、CSP + Trusted Types:双剑合璧,天下无敌?

CSP 和 Trusted Types 可以一起使用,以提供更强大的 XSS 防御。 CSP 可以限制资源的来源,防止加载恶意脚本,而 Trusted Types 可以防止 DOM 型 XSS 攻击。

如何一起使用 CSP 和 Trusted Types:

  1. 启用 Trusted Types: 通过设置 CSP 策略来启用 Trusted Types。

    Content-Security-Policy: require-trusted-types-for 'script';
  2. 配置 CSP 策略: 配置 CSP 策略,限制资源的来源,并允许加载 Trusted Types polyfill。

    Content-Security-Policy:
        require-trusted-types-for 'script';
        script-src 'self' https://polyfill.io/v3/polyfill.min.js;
  3. 创建策略: 使用 trustedTypes.createPolicy() 函数来创建策略。

  4. 使用策略来创建 Trusted Types 对象: 使用策略的 createHTML(), createScriptURL()createScript() 方法来将字符串转换为 TrustedHTML, TrustedScriptTrustedScriptURL 对象。

  5. 将 Trusted Types 对象插入 DOM: 使用 innerHTML, src 等属性来将 Trusted Types 对象插入 DOM。

总结:

CSP 和 Trusted Types 都是强大的 XSS 防御工具。 CSP 可以限制资源的来源,防止加载恶意脚本,而 Trusted Types 可以防止 DOM 型 XSS 攻击。 通过一起使用 CSP 和 Trusted Types,可以提供更强大的 XSS 防御。

注意事项:

  • CSP 和 Trusted Types 的配置需要仔细考虑,避免因为配置不当而留下安全漏洞。
  • Trusted Types 的兼容性目前还不是很好,需要使用 polyfill 来支持旧版本的浏览器。
  • XSS 防御是一个持续的过程,需要不断地学习和更新你的知识。

好了,今天的讲座就到这里。 希望大家都能学会如何使用 CSP 和 Trusted Types 来保护你的网站,远离 XSS 攻击! 下次有机会再跟大家分享更多的安全知识。 拜拜!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注