内容安全策略:轻松防止混合内容

Avatar of Scott Fennell
Scott Fennell

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费积分!

我最近了解到一个浏览器功能,如果您提供一个特殊的 HTTP 标头,它会自动将任何非 HTTPS 内容的报告发布到某个 URL。例如,在将网站迁移到 HTTPS 时,执行此操作将是一件很棒的事情,以消除任何混合内容警告。在本文中,我们将通过一个小的 WordPress 插件实现此功能。

什么是混合内容?

“混合内容”是指您正在通过 HTTPS 页面加载页面,但该页面上的某些资源(图像、视频、CSS、脚本、脚本调用的脚本等)是通过普通 HTTP 加载的。

A browser pop up window of a security warning about unsecure content.
浏览器关于混合内容的警告。

我假设我们都太熟悉此警告了,并请读者参考 此优秀的入门指南 以获取有关混合内容的更多背景信息。

什么是内容安全策略?

内容安全策略 (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":""
    }
}

以下是它在其自然栖息地中的样子

Chrome 检查器的网络面板中的传出报告。

我该如何处理?

您需要做的是告诉浏览器将该报告发送到哪个 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-inlineunsafe-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 标头中的众多可能的参数。如此简洁的语法中蕴含着如此强大的功能,令人印象深刻。可以根据资源类型、域名、协议处理请求——几乎可以想象的任何组合。