我最近了解到一个浏览器功能,如果您提供一个特殊的 HTTP 标头,它会自动将任何非 HTTPS 内容的报告发布到某个 URL。例如,在将网站迁移到 HTTPS 时,执行此操作将是一件很棒的事情,以消除任何混合内容警告。在本文中,我们将通过一个小的 WordPress 插件实现此功能。
什么是混合内容?
“混合内容”是指您正在通过 HTTPS 页面加载页面,但该页面上的某些资源(图像、视频、CSS、脚本、脚本调用的脚本等)是通过普通 HTTP 加载的。

我假设我们都太熟悉此警告了,并请读者参考 此优秀的入门指南 以获取有关混合内容的更多背景信息。
什么是内容安全策略?
内容安全策略 (CSP) 是一种浏览器功能,它为我们提供了一种方法来指示浏览器如何处理混合内容错误。通过在我们的页面中包含特殊的 HTTP 标头,我们可以告诉浏览器阻止、升级或报告混合内容。本文重点介绍报告,因为它为我们提供了一个简单且有用的切入点来了解 CSP 本身。
CSP 的名称有点不透明。不要让它吓到你,因为它非常易于使用。根据 caniuse,它似乎具有极佳的支持。以下是 Chrome 中传出报告的形状
{
"csp-report": {
"document-uri":"http://localhost/wp/2017/03/21/godaddys-micro-dollars/",
"referrer":"http://localhost/wp/",
"violated-directive":"style-src",
"effective-directive":"style-src",
"original-policy":"default-src https: 'unsafe-inline' 'unsafe-eval'; report-uri http://localhost/wp/wp-json/csst_consecpol/v1/incidents",
"disposition":"report",
"blocked-uri":"http://localhost/wp/wp-includes/css/dashicons.min.css?ver=4.8.2",
"status-code":200,
"script-sample":""
}
}
以下是它在其自然栖息地中的样子

我该如何处理?
您需要做的是告诉浏览器将该报告发送到哪个 URL,然后在您的服务器上有一些逻辑来侦听它。从那里,您可以让它写入日志文件、数据库表、电子邮件,等等。请注意,您可能会生成大量报告。务必警惕自我拒绝服务攻击!
可以给我看一个示例吗?
当然可以!我创建了一个 小的 WordPress 插件 来向您展示。该插件没有 UI,只需激活它即可。您可以剥离其中的大部分内容并在非 WordPress 环境中直接使用它,并且本文不假设任何特定的 WordPress 知识,除了激活插件和稍微浏览文件系统之外。我们将在本文的其余部分深入研究该插件。
发送标头
我们的第一步将是将我们的内容安全策略作为 HTTP 标头包含在内。查看插件中的 此文件。它非常短,我相信您会很高兴看到它有多简单。
相关部分是这行代码
header( "Content-Security-Policy-Report-Only: default-src https: 'unsafe-inline' 'unsafe-eval'; report-uri $rest_url" );
我们可以在这里玩很多参数。
- 使用
Content-Security-Policy-Report-Only
参数,我们表示我们希望获得违反我们策略的资源的报告,但我们不想实际阻止或以其他方式影响它们。 - 使用
default-src
参数,我们表示我们正在寻找所有类型的资源,而不是仅限于图像、字体或脚本等。 - 使用
https
参数,我们表示我们的策略是仅批准通过 https 加载的资源。 - 使用
unsafe-inline
和unsafe-eval
参数,我们表示我们关心内联资源(如普通图像标签)和从字符串构建代码的各种方法,例如 JavaScript 的eval()
函数。 - 最后,最有趣的是,使用
report-uri $rest_url
参数,我们为浏览器提供了一个 URL,浏览器应该将报告发送到该 URL。
如果您想了解有关参数的更多详细信息,Mozilla.org 上有一个优秀的文档 。 还值得注意的是,我们可以改为将我们的 CSP 作为 元标记 发送,尽管我发现语法很笨拙,并且 Google 指出这不是他们的首选方法。
本文将仅使用 HTTP 标头技术,您会注意到在我的标头中,我正在进行一些工作来构建报告 URL。它恰好是一个 WP API URL。我们接下来将深入研究它。
注册端点
您可能熟悉 WP API。在拥有 WP API 之前的旧时代,当需要某个任意 URL 来侦听表单提交时,我通常会创建一个页面或一个自定义帖子类型的帖子。这很烦人和脆弱,因为很容易在 wp-admin 中删除页面而不意识到它的用途。使用 WP API,我们有了一种更稳定的注册侦听器的方法,我在 此类 中执行此操作。此类中有三个关注点。
在第一个函数中,在确保我的日志不会变得太大后,我调用了register_rest_route()
,这是一个 WordPress 核心函数,用于注册侦听器
function rest_api_init() {
$check_log_file_size = $this -> check_log_file_size();
if( ! $check_log_file_size ) { return FALSE; }
...
register_rest_route(
CSST_CONSECPOL . '/' . $rest_version,
'/' . $rest_ep . '/',
array(
'methods' => 'POST',
'callback' => array( $this, 'cb' ),
)
);
}
该函数还允许我注册一个回调函数,该函数处理发布的 CSP 报告
function cb( \WP_REST_Request $request ) {
$body = $request -> get_body();
$body = json_decode( $body, TRUE );
$csp_report = $body['csp-report'];
...
$record = new Record( $args );
$out = $record -> get_log_entry();
}
在该函数中,我将报告的原始格式转换为我的日志记录类将处理的 PHP 数组。
创建日志文件
在 此类 中,我在 wp-content 文件夹中创建了一个目录,我的日志文件将存储在此目录中。我不太喜欢在每个页面加载时都检查此类内容,因此请注意,此函数首先检查自插件更新以来是否为第一次页面加载,然后再费心创建目录。
function make_directory() {
$out = NULL;
$update = new Update;
if( ! $update -> get_is_update() ) { return FALSE; }
$log_dir_path = $this -> meta -> get_log_dir_path();
$file_exists = file_exists( $log_dir_path );
if( $file_exists ) { return FALSE; }
$out = mkdir( $log_dir_path, 0775, TRUE );
return $out;
}
该更新逻辑在 另一个类 中,对于很多事情都非常有用,但对于本文而言,它并不是特别重要。
记录混合内容
现在我们已经开始发布 CSP 报告,并且我们有一个目录可以将其记录到,让我们看看如何将报告实际转换为日志条目,
在 此类 中,我有一个函数用于将新记录添加到我们的日志文件中。有趣的是,大部分繁重的工作仅仅是为fopen()
函数提供a
参数的问题
function add_row( $array ) {
// Open for writing only; place the file pointer at the end of the file. If the file does not exist, attempt to create it.
$mode = 'a';
// Open the file.
$path = $this -> meta -> get_log_file_path();
$handle = fopen( $path, $mode );
// Add the row to the spreadsheet.
fputcsv( $handle, $array );
// Close the file.
fclose( $handle );
return TRUE;
}
这里没有特定于 WordPress 的内容,只是一个以正常 PHP 方式向 csv 添加行的人。同样,如果您不喜欢日志文件的想法,您可以让它发送电子邮件或写入数据库,或者任何看起来最好的方法。
注意事项
至此,我们已经介绍了我的插件中的所有有趣亮点,我建议提供一些需要注意的陷阱。
首先,请注意,CSP 报告与任何浏览器功能一样,都存在跨浏览器差异。请查看这份令人震惊且详尽的 报告,了解这些差异。
其次,请注意,如果您拥有阻止请求混合内容的服务器配置,那么浏览器将永远无法有机会报告它。在这种情况下,CSP 报告更适用于准备迁移到 https,而不是监控 https 符合性。此配置的一个示例是 Cloudflare 的“始终使用 HTTPS”。
最后,**自我拒绝服务问题需要重复强调**。可以合理地假设一个受欢迎的网站每月会积累数百万份报告。因此,与其在您自己的服务器或数据库上跟踪这些报告,不如考虑将其外包给像 httpschecker.net 这样的服务。
后续步骤
针对 WordPress 的一些后续步骤是添加一个用于下载报告文件的 UI。您还可以将报告存储在数据库中而不是文件中。这将使您能够经济高效地,例如,在添加新记录之前确定它是否已存在,从而避免重复。
更一般地,我鼓励好奇的读者尝试 CSP 标头中的众多可能的参数。如此简洁的语法中蕴含着如此强大的功能,令人印象深刻。可以根据资源类型、域名、协议处理请求——几乎可以想象的任何组合。
Scott 老兄,好文章……强制 SSL 插件怎么样?
https://wordpress.org/plugins/force-https-littlebizzy/
功能一样吗?谢谢回复。
谢谢!
那可能是一个很棒的插件,我不太清楚。坦白说,我宁愿花时间学习 CloudFlare 在类似方面能做什么。
不错的文章,写得很好,也很容易理解。:) 有几点值得补充
CSP 远不止一种管理“混合内容”警告的方式;这更像是一种“副作用”益处。CSP 实际上是一个非常强大的 Web 安全功能:它是针对几种恶意攻击(尤其是跨站点脚本)的强大防御。
正确配置 CSP 有点棘手。为了获得全部好处,阻止内联脚本等内容非常重要,但这对于许多网站(尤其是 WordPress 网站)来说可能不切实际。
Mozilla Observatory 是一个有用的资源,可以检查 CSP 的配置情况,以及其他一些重要的 SSL(TLS)问题。
正如你在文章中提到的,CSP 报告可能非常嘈杂,甚至存在压垮服务器的风险。编写良好的 CSP 日志服务也很难。你最好使用 ReportURI。虽然现在这是一项付费服务,但它有一个非常棒的免费层级,对于大多数网站来说应该绰绰有余。
你完全正确。希望在这里为世界各地的深度潜水员(原谅我这个混合的比喻 :D)提供一个立足点。