Contents
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是什么
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需要做手动转化。