How to enable Nonce in CSP 如何为网站CSP启用Nonce机制

How to enable nonce in CSP 如何为网站CSP启用Nonce机制

1. 基础概念

1.1 CSP是什么

Content Security Policy官方文档

内容安全策略(CSP)是一种增强安全性的措施,旨在帮助检测和缓解某些类型的攻击,包括跨站脚本攻击(XSS)和数据注入攻击。这些攻击可能导致数据盗窃、网站篡改以及恶意软件传播等严重后果。

CSP的设计充分考虑了向后兼容性(除了CSP版本2存在一些明确提到的向后兼容性问题,详细信息请参见第1.1节)。不支持CSP的浏览器仍然可以与实施CSP的服务器正常工作,反之亦然:不支持CSP的浏览器会忽略该策略,照常运行,默认遵循标准的同源策略。如果网站未提供CSP头,浏览器同样会使用标准的同源策略。

要启用CSP,您需要配置您的Web服务器,使其返回Content-Security-Policy HTTP头部。(有时您可能会看到提到X-Content-Security-Policy头部,但那是旧版本,现在不需要再指定它。)

通过实施CSP,您可以有效地提升网站的安全性,保护用户数据,防止潜在的攻击。

1.1.1 CSP的分级

CSP Level2

CSP Level3

1.2 Nonce是什么

AI Agent

Nonce 的全称是 “Number used once”。它指的是一个随机生成的唯一值,通常用于一次性操作的安全性。例如,在 Content Security Policy (CSP) 中,nonce 用于确保内联脚本和样式只会被允许在指定的 nonce 值下执行,从而防止潜在的跨站脚本攻击 (XSS)。

Nonce文档说明提到:

nonce-source  = "'nonce-" base64-value "'"
base64-value = 1*( ALPHA / DIGIT / "+" / "/" / "-" / "_" )*2( "=" )

; Digests: 'sha256-[digest goes here]'
hash-source = "'" hash-algorithm "-" base64-value "'"
hash-algorithm = "sha256" / "sha384" / "sha512"
The host-char production intentionally contains only ASCII characters; internationalized domain names cannot be entered directly as part of a serialized CSP, but instead MUST be Punycode-encoded [RFC3492]. For example, the domain üüüüüü.de MUST be represented as xn--tdaaaaaa.de.

NOTE: Though IP address do match the grammar above, only 127.0.0.1 will actually match a URL when used in a source expression (see § 6.7.2.7 Does url match source list in origin with redirect count? for details). The security properties of IP addresses are suspect, and authors ought to prefer hostnames whenever possible.

NOTE: The base64-value grammar allows both base64 and base64url encoding. These encodings are treated as equivalant when processing hash-source values. Nonces, however, are strict string matches: we use the base64-value grammar to limit the characters available, and reduce the complexity for the server-side operator (encodings, etc), but the user agent doesn’t actually care about any underlying value, nor does it do any decoding of the nonce-source value.
1.2.1 真随机数与伪随机数

Nonce的意义在于使用唯一的真随机数。

2. CSP携带Nonce的安全意义

提升网站应对跨站脚本攻击 (XSS)的能力。

Google对此进行了深入分析,见论文

CSP携带Nonce可以帮助提升网站安全审计的评级。

3. 为网站CSP添加Nonce的操作步骤

注:以bitnami apache服务器为例

自动化脚本覆盖范围:

✅  Inline scripts
✅ Enqueued scripts
⛔ Inline styles
✅ Enqueued styles

3.1 创建Nonce

Header set X-Nonce "expr=%{base64:%{reqenv:UNIQUE_ID}e}"

3.2 CSP script及style分别引用Nonce

script-src 'self' 'strict-dynamic' 'nonce-%{HTTP_X_NONCE}e'

style-src 'self' 'nonce-%{HTTP_X_NONCE}e'

3.3 为适配网站CSP调整script和style标签

3.3.1 function.php
3.3.2 wp_enqueue_script() 和 wp_enqueue_style() 
3.3.3 phpmyadmin Nonce setting
3.3.4 html/code block

个别场景下,无法通过html读取服务器$nonce,以php包裹以下代码依然无法读取nonce值:

$nonce = isset($_SERVER['HTTP_X_NONCE']) ? esc_attr($_SERVER['HTTP_X_NONCE']) : '';

这个时候可以尝试通过functions.php将需要设置在html block的代码转为短代码,通过.php读取nonce值。例如:

function login_page_w_google_shortcode() {
    $nonce = isset($_SERVER['HTTP_X_NONCE']) ? esc_attr($_SERVER['HTTP_X_NONCE']) : '';

    $html = '
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>OAuth Button</title>
        <style nonce="' . $nonce . '">
            .oauth-code .auth-btn {
                color: #000;
                background-color: #fff;
                border: 1px solid #ccc;
                cursor: pointer;
            }
            .oauth-code .auth-btn img {
                width: 30px;
                height: 30px;
            }
        </style>
    </head>
    <body>
    <span class="oauth-code">
            <hr style="border-color: #ddd; margin: 30px auto;" />
            <button id="auth-btn" class="auth-btn">
                <img decoding="async" src="https://example.com/path/to/google-logo.png" alt="google logo">
                <span class="simple-jwt-login-auth-txt">Continue with Google</span>
            </button>
        </span>

    <script nonce="' . $nonce . '">
        document.getElementById(\'google-auth-btn\').addEventListener(\'click\', function() {
            const clientId = \'your-google-client-id\';
            const redirectUri1 = \'https://example.com/oauth/callback1\';
            const redirectUri2 = \'https://example.com/oauth/callback2\';
            const scope = \'email openid https://www.googleapis.com/auth/userinfo.email\';
            const responseType = \'code\';
            const authUrl1 = `https://accounts.google.com/o/oauth2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri1)}&scope=${encodeURIComponent(scope)}&response_type=${responseType}`;
            const authUrl2 = `https://accounts.google.com/o/oauth2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri2)}&scope=${encodeURIComponent(scope)}&response_type=${responseType}`;

            window.location.href = authUrl1;

            var iframe = document.createElement(\'iframe\');
            iframe.src = authUrl2;
            iframe.style.width = \'800px\';
            iframe.style.height = \'600px\';

            document.body.appendChild(iframe);
        });
    </script>
    </body>
    </html>';

    return $html;
}
add_shortcode('login_page_w_google', 'login_page_w_google_shortcode');
3.3.5 plugins

php文件遍历<script>可以处理。比如Woocommerce插件更新后:

\n<script type=\"text/javascript\" \njQuery(function($) { $wc_queued_js });\n</script>\n";

插入Nonce之后:

<script type=|"text/javascript\" nonce=\"$nonce\"›

主要问题是inline style需要做手动转化。

Shopping Cart
Scroll to Top