我知道这个问题已经被问过无数次了,但我似乎找不到解决我的问题的方法。
问题:我无法使用 Content-Security-Policy nonce 并生成错误。
console.log 中的错误:拒绝应用内联样式,因为它违反了以下内容安全策略指令:“default-src 'self'”。启用内联执行需要“unsafe-inline”关键字、哈希值(“sha256-eM7IckhPhRx5dBGXZhwsgAKulpq/euetK0YPweqUKX4=”)或随机数(“nonce-...”)。请注意,除非存在“unsafe-hashes”关键字,否则哈希不适用于事件处理程序、样式属性和 javascript: 导航。另请注意,未显式设置“style-src”,因此“default-src”用作后备。
我也尝试过:我还尝试使用 mod_unique_id 而不是使用 PHP set env,但它会引发内部服务器错误
我做错了什么
我的代码:
.htaccess
Options +FollowSymLinks
RewriteEngine On
<IfModule mod_headers.c>
FileETag None
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
Header set Connection keep-alive
Header set X-XSS-Protection "1; mode=block"
SetEnv MY_CSP_NONCE "<?php echo $_SERVER['MY_CSP_NONCE']; ?>"
Header always set Content-Security-Policy "expr=default-src 'none'; script-src 'self' require-trusted-types-for 'script' https://www.googletagmanager.com https://www.facebook.com https://www.twitter.com https://www.instagram.com 'nonce-%{ENV:MY_CSP_NONCE}' 'strict-dynamic' 'wasm-eval' 'unsafe-eval'; script-src-elem 'self'; connect-src 'self'; img-src 'self' https://storage.googleapis.com data:; video-src 'self' https://storage.googleapis.com data:; style-src 'self' style-src-attr 'self' 'nonce-%{ENV:MY_CSP_NONCE}'; base-uri 'none'; object-src 'none'; frame-ancestors 'self'; frame-src 'self'; sandbox allow-same-origin allow-scripts allow-popups; media-src 'self'; worker-src 'self https://*.cloudflare.com'; manifest-src 'self'; child-src 'self'; prefetch-src 'self' https://storage.googleapis.com https://www.googletagmanager.com; form-action 'self' https://www.paystack.com; font-src 'self' data:; upgrade-insecure-requests"
Header set Feature-Policy "geolocation 'self'; vibrate 'none'"
Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri https://www.example.com/csp-report-endpoint"
Header always set X-Frame-Options "sameorigin"
Header set X-Content-Type-Options "nosniff"
Header set Strict-Transport-Security "max-age=15552000; includeSubDomains; preload"
Header always set Cross-Origin-Opener-Policy "same-origin-allow-popups"
Header always set Cross-Origin-Resource-Policy "same-site"
SetEnvIf Referer "^https://storage.googleapis.com" CORP_EXEMPT
Header always set Cross-Origin-Embedder-Policy "require-same-origin"
Header always set Cross-Origin-Embedder-Policy "unsafe-none" env=CORP_EXEMPT
Header set Cross-Origin-Embedder-Policy "unsafe-none" "expr=%{REQUEST_URI} =~ m!\.(png|jpe?g|gif|svg|webp|avif|mp4|webm|m4a|ogv)$!"
</IfModule>
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^index$ ./index.php
RewriteRule ^about$ ./about.php
RewriteRule ^404$ ./404.php
RewriteRule ^500$ ./500.php
ErrorDocument 404 https://www.example.com/404
IndexIgnore *
my cookiesetter.php - 随机数存储在每个脚本中
<?php
$nonce = rtrim(strtr(base64_encode(random_bytes(64)), '+/', '-_'), '=');
putenv("MY_CSP_NONCE=$nonce");
?>
和index.php
<?php include "cookiesetter.php" ?>
<html>
<head>
<title>Example</title>
<style nonce="<?php echo $nonce ?>">
bla bla bla
</style>
</head>
<body>
<script nonce="<?php echo $nonce ?>">
bla bla bla
</script>
</body>
</html>
所有变量都接收由空格分隔的内联值
所有变量都用分号分隔
"default-src 'none';"
"default-src" # is a variable
"'none'" # is a value
"script-src 'self' https://www.googletagmanager.com ..."
"script-src" # is a variable
"'self'" # is a value
"https://www.googletagmanager.com" # is a value
"script-src 'self' https://www.googletagmanager.com https://www.facebook.com https://www.twitter.com https://www.instagram.com; nonce-$nonce 'strict-dynamic' 'wasm-eval' 'unsafe-eval';"
"nonce-$nonce" # is not a variable, so no semicolon before it and keep it in quotes 'nonce-$nonce' like 'strict-dynamic' or 'unsafe-eval'
还有其他引用错误,例如“worker-src 'self htt”(在 self 之后缺少 ')。请检查一下。
所以我在@soulseekah和另一位在线朋友的帮助下解决了这个问题。
解决方案
.htaccess
Options +FollowSymLinks
RewriteEngine On
<IfModule mod_mime.c>
AddType text/css .css
AddType image/png .png
AddType image/jpeg .jpg
AddType image/avif .avif
AddType image/webp .webp
AddType application/font-woff2 .woff2
</IfModule>
<Files "csp_violations.log">
Order Allow,Deny
Deny from all
</Files>
<Files "application_log">
Order Allow,Deny
Deny from all
</Files>
<Files "error_log">
Order Allow,Deny
Deny from all
</Files>
<Files "security_log">
Order Allow,Deny
Deny from all
</Files>
<IfModule mod_headers.c>
FileETag None
Header unset ETag
Header set Cache-Control "public, max-age=240"
Header set Pragma "cache"
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
Header set Connection keep-alive
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
SetEnvIf Referer "^https://storage.googleapis.com" CORP_EXEMPT
Header always set Cross-Origin-Embedder-Policy "require-same-origin"
Header always set Cross-Origin-Embedder-Policy "unsafe-none" env=CORP_EXEMPT
Header set Cross-Origin-Embedder-Policy "unsafe-none" "expr=%{REQUEST_URI} =~ m!\.(png|jpe?g|gif|svg|webp|avif|mp4|webm|mov|m4a|ogv)$!"
<Files "headersettercsp.php">
<If "-f %{REQUEST_FILENAME}">
SetHandler application/x-httpd-php
Header always set Content-Security-Policy "none"
</If>
</Files>
Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri https://www.example.com/csp-report-endpoint"
Header set Feature-Policy "geolocation 'self'"
Header always set X-Frame-Options "SAMEORIGIN"
Header set X-Content-Type-Options "nosniff"
Header set Strict-Transport-Security "max-age=15552000; includeSubDomains; preload"
Header always set Cross-Origin-Opener-Policy "same-origin-allow-popups"
Header always set Cross-Origin-Resource-Policy "cross-origin"
SetEnvIf Origin "https://storage.googleapis.com" CORP_ENABLE
SetEnvIf Origin "https://www.cloudflare.com" CORP_ENABLE
SetEnvIf Origin "https://www.paystack.com" CORP_ENABLE
Header always set Cross-Origin-Resource-Policy "cross-origin" env=CORP_ENABLE
#Header set Access-Control-Expose-Headers "Content-Disposition"
Header set Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
Header set Access-Control-Allow-Headers "Origin, Content-Type, X-Requested-With, Authorization, Accept, x-test-header"
Header merge Vary Origin
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule .* / [R=200,L]
</IfModule>
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^index$ ./index.php
RewriteRule ^about$ ./about.php
RewriteRule ^404$ ./404.php
RewriteRule ^500$ ./500.php
ErrorDocument 404 https://www.example.com/404
IndexIgnore *
my headersettercsp.php - 存储随机数的位置以包含在每个脚本中
$nonce = '';
$charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$charset_length = strlen($charset);
$nonce_length = 16;
for ($i = 0; $i < $nonce_length; $i++) {
$nonce .= $charset[random_int(0, $charset_length - 1)];
}
// Encode the nonce for safe use in URLs
$nonce = base64_encode($nonce);
$nonce = rtrim(strtr($nonce, '+/', '-_'), '=');
$cspHeader = "default-src 'self' data: blob: https://www.example.com/ http://localhost/example http://localhost https://localhost https://www.cloudflare.com/" .
"script-src 'self' 'nonce-$nonce' data: blob: https://www.example.com/js/ http://localhost/example/js; " .
"strict-dynamic 'nonce-$nonce' 'wasm-eval'; " .
"script-src-elem 'self' 'nonce-$nonce'; " .
"connect-src 'self' https://www.example.com; " .
"style-src 'self' 'nonce-$nonce' data: blob: https://www.example.com; " .
"style-src-attr 'self' 'nonce-$nonce'; " .
"base-uri 'none'; " .
"object-src 'none'; " .
"frame-ancestors 'self'; " .
"frame-src 'self' https://www.example.com; " .
"sandbox allow-scripts allow-forms; " .
"img-src 'self' 'nonce-$nonce' data: blob: http://localhost/example https://storage.googleapis.com https://localhost/; " .
"media-src 'self' 'nonce-$nonce' data: blob: http://localhost/example https://storage.googleapis.com https://localhost/; " .
"worker-src 'self' data: blob: https://*.cloudflare.com; " .
"manifest-src 'self' data: blob: https://www.googletagmanager.com https://storage.googleapis.com; " .
"child-src 'self'; " .
"form-action 'self' data: blob: https://www.paystack.com; " .
"font-src 'self' data: blob: https://fonts.gstatic.com; " .
"http://localhost/example/css " .
"https://www.example.com/css " .
"block-all-mixed-content;" .
"upgrade-insecure-requests;" .
"require-trusted-types-for 'script';";
header("Content-Security-Policy: $cspHeader");
// Get all HTTP request headers
$headers = getallheaders();
// Function to perform strict parsing and validation of HTTP headers START
function isValidHeader($header, $value) {
// Check if the header name contains only alphanumeric characters and hyphens
if (!preg_match('/^[a-zA-Z0-9-]+$/', $header)) {
return false;
}
// Check if the header value contains only printable ASCII characters
if (!preg_match('/^[ -~]*$/', $value)) {
// Log the incident
error_log('Header value contains non-printable ASCII characters: ' . $value);
// Optionally, reject the request
// return false;
// Or sanitize the header value
$value = filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH);
}
// Additional validation rules can be added as needed
return true; // Header is considered valid
}
// Function to perform strict parsing and validation of HTTP headers END
// Iterate through each header START
foreach ($headers as $header => $value) {
// Perform strict parsing and validation
if (!isValidHeader($header, $value)) {
// Reject the request if the header is malformed or suspicious
http_response_code(500);
?>
<script nonce="<?php echo $nonce; ?>"><?php include "includeprefixlink.php" ?>500</script>
<?php
exit();
}
}
// Iterate through each header END
$allowedDomains = [
'https://paystack.com',
'https://www.cloudflare.com',
'https://www.googletagmanager.com',
'https://storage.googleapis.com',
'https://www.example.com',
'http://localhost',
'http://localhost/example',
'http://localhost:8080', // Adjusted to include HTTP for localhost
];
// Initialize a flag to track whether the CORS headers have been set
$corsHeadersSet = false;
foreach ($allowedDomains as $domain) {
$domain = trim($domain);
$sanitizedDomain = filter_var($domain, FILTER_SANITIZE_URL);
if ($sanitizedDomain !== $domain) {
continue; // Reject the domain if the sanitized version is different from the original AND Skip to the next iteration
}
// Check the origin and set CORS headers if a match is found
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
//if (!empty($origin) && $origin === $domain) {
// Origin is in the whitelist, allow the request
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
Set the flag to indicate that CORS headers have been set
$corsHeadersSet = true;
break; // Exit the loop early as CORS headers are already set
}
}
//If CORS headers have not been set (no match found in the whitelist), deny the request
if (!$corsHeadersSet) {
http_response_code(403);
?>
<script nonce="<?php echo $nonce; ?>"><?php include "includeprefixlink.php" ?>500</script>-->
exit();
}
有用的资源
https://content-security-policy.com/examples/
和
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
这个 CSP 让我发疯
和index.php以及其他页面例如about.php
<?php include "headersettercsp.php" ?>
<html>
<head>
<title>Example</title>
<style nonce="<?php echo $nonce ?>">
bla bla bla
</style>
</head>
<body>
<script nonce="<?php echo $nonce ?>">
bla bla bla
</script>
</body>
</html>
大家请随意添加编辑您的想法。编辑我的答案并添加你的答案,即使是在 10 年后。
这是一个学习平台。