aidantwoods/secureheaders

一个PHP类,旨在使浏览器安全特性的使用更加容易。

v2.0.1 2017-08-28 15:41 UTC

This package is auto-updated.

Last update: 2024-09-14 03:30:54 UTC


README

一个PHP类,旨在使浏览器安全特性的使用更加容易。

完整文档请参阅 Wiki

还有一个带有示例配置的 演示

什么是“安全头部”?

安全头部,是一组配置浏览器安全特性的 头部。所有这些头部都可以在任何Web应用程序中使用,其中大多数可以部署而无需进行任何,或非常小的代码更改。然而,其中一些最有效的头部 确实 需要代码更改 - 尤其是有效地实现。

功能

  • 轻松添加/删除和管理头部
  • 构建内容安全策略,或将多个策略组合在一起
  • 内容安全策略分析
  • 与任意框架轻松集成(请参阅 HttpAdapter)
  • 保护设置错误的Cookie
  • 严格模式
  • 安全模式可以防止在使用HSTS或HPKP时意外地造成长期自我DOS攻击
  • 收到有关缺失或配置错误的网络安全头部的警告

方法和哲学

错误消息通常是程序告诉程序员出错了的好方法。无论是调用尚未分配的变量,还是因为耗尽内存分配限制而造成致命错误。

这两种情况通常都可以由程序员迅速修复。这样做所需的努力大大减少,因为程序在程序员引入错误后立即准确地传达了问题。SecureHeaders旨在将此概念应用于浏览器安全特性。

SecureHeaders将利用PHP配置中设置的错误报告级别,生成 E_USER_WARNINGE_USER_NOTICE 级别的错误消息,以通知程序员关于误配置或未配置的信息。

除了错误报告之外,SecureHeaders将对某些头部进行一些 安全 的主动更改,或者如果它们缺失,甚至添加新的头部。

安装

通过Composer

composer require aidantwoods/secureheaders

其他

下载 SecureHeaders.phar,然后

require_once('SecureHeaders.phar');

听起来不错,但让我们看看一些代码...

这是一个很好的实现示例

$headers = new SecureHeaders();
$headers->hsts();
$headers->csp('default', 'self');
$headers->csp('script', 'https://my.cdn.org');
$headers->apply();

这几行代码将使应用程序从F级提高到A级,在Scott Helme的 https://securityheaders.io/

哇,这太简单了!告诉我它做了什么...

让我们分析上面的示例。

'开箱即用',SecureHeaders将已经做很多事情(通过运行以下代码)

$headers = new SecureHeaders();
$headers->apply();

自动头部和错误

使用这样的代码,将发生以下情况

  • 将发出警告(E_USER_WARNING

    警告: 缺失安全头部:'Strict-Transport-Security'

    警告: 缺失安全头部:'Content-Security-Policy'

  • 将自动添加以下头部

    Expect-CT: max-age=0
    Referrer-Policy: no-referrer
    Referrer-Policy: strict-origin-when-cross-origin
    X-Content-Type-Options:nosniff
    X-Frame-Options:Deny
    X-Permitted-Cross-Domain-Policies: none
    X-XSS-Protection:1; mode=block
    
  • 以下头部也将被删除(SecureHeaders还将尝试删除Server头部,尽管这个头部不太可能处于PHP的管辖之下)

    X-Powered-By
    

Cookie

此外,如果在调用 ->apply() 之前设置了任何Cookie,例如。

setcookie('auth', 'supersecretauthenticationstring');

$headers = new SecureHeaders();
$headers->apply();

尽管在当前的PHP配置中,cookie标志SecureHTTPOnly默认不开启,而且尽管PHP不支持SameSitecookie属性,但Set-Cookie头的最终结果将是

Set-Cookie:auth=supersecretauthenticationstring; Secure; HttpOnly; SameSite=Lax

这些标志是由SecureHeaders插入的,因为cookie名称包含了子字符串auth。当然,如果这是一个错误的假设,你可以纠正SecureHeaders的行为,或者相反,你可以告诉SecureHeaders一些具有不那么明显名称的cookie(但可能需要保护以防意外遗漏标志)。

如果你启用了->strictMode(),那么SameSite设置将设置为严格(你也可以在不使用严格模式的情况下升级这个设置)。

严格模式

严格模式将启用你应该使用的设置。强烈建议调整你的应用程序以在启用严格模式下工作。

启用后,严格模式将

  • 自动启用1年有效期的HSTS,并设置includeSubDomainspreload标志。请注意,这个HSTS策略是一个头部建议,因此可以被删除或修改。

  • 源关键字'strict-dynamic'也将添加到以下存在的第一个指令中:script-srcdefault-src;只有当该指令还包含nonce或hash源值时,否则不会添加。

    这将禁用在CSP3合规浏览器中的script-src中的源白名单。在script-src中使用白名单被认为不是一种理想的做法,因为它们通常很容易绕过。

    如果你使用此选项,不要忘记手动提交你的域名到HSTS预加载列表。

  • 注入到->protectedCookie中的默认SameSite值将更改为SameSite=Strict。请参阅->auto的文档以启用/禁用SameSite的注入,以及->sameSiteCookies的文档以了解更多具体行为和如何手动明确定义此值以覆盖默认值。

  • 自动启用1年有效期的Expect-CT,并设置enforce标志。请注意,这个Expect-CT策略是一个头部建议,因此可以被删除或修改。

回到示例

让我们看看其他三行,第一行是

$headers->hsts();

这将在应用程序上启用HSTS(严格传输安全),持续时间为1年。

听起来可能破坏某些东西——我不想不小心启用它。

安全模式

好吧,SecureHeaders为你提供了保障——使用$headers->safeMode();来防止发送将产生持久影响的头部。

例如,如果以下代码被执行(安全模式可以在->apply()之前任何时刻调用以生效)

$headers->hsts();
$headers->safeMode();

HSTS仍然会被启用(如请求),但将限制为持续24小时。

SecureHeaders还会生成以下通知

通知:由于启用了安全模式,HSTS设置被覆盖。了解一些通过复制粘贴设置HSTS时的常见错误,并在使用此安全功能之前确保你理解细节和可能的副作用。

如果我是通过与其无关的方法设置的,SecureHeaders还能强制安全模式吗?

是的!SecureHeaders将独立于其内置的用于生成它们的函数,检查头名称和值。

例如,如果我用PHP的内置header函数设置HSTS为1年,适用于所有子域名,并表明同意将此规则预加载到主流浏览器中,然后(在设置该头部之前或之后)启用安全模式...

header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
$headers->safeMode();

将生成相同的上述通知,max-age将修改为1天,并将预加载和includesubdomains标志删除。

内容安全策略

下面是初始示例中要覆盖的最后两行:

$headers->csp('default', 'self');
$headers->csp('script', 'https://my.cdn.org');

这些告诉SecureHeaders构建一个CSP(内容安全策略),允许从当前域(self)加载默认资源,并允许从https://my.cdn.org加载脚本。

请注意,如果我们说的是http://my.cdn.org而不是这样,那么下面将会生成:

警告:内容安全策略在源值中包含不安全的协议HTTP http://my.cdn.org;这可以使任何人将受script-src指令覆盖的元素插入到页面中。

同样,如果包含如'unsafe-inline'https:*这样的通配符,SecureHeaders将生成警告来突出这些CSP绕过。

请注意,->csp函数可以接受的内容非常多样,要了解更多,请参阅使用CSP部分。

发送头部

为了应用通过SecureHeaders添加的内容,您需要调用->apply()。按照设计,SecureHeaders没有构造函数,所以调用->apply()之前的所有内容只是配置。然而,如果您不想记得调用此函数,您可以在任何时候调用->applyOnOutput()代替,这将利用PHP的ob_start()函数来启动输出缓冲。这可以让SecureHeaders附加到任何生成输出的代码的第一个实例,并在实际将输出发送给用户之前,通过为您调用->apply()来确保发送所有头部。

因为SecureHeaders没有构造函数,所以您可以通过简单的类扩展轻松实现自己的,例如:

class CustomSecureHeaders extends SecureHeaders{
    public function __construct()
    {
        $this->applyOnOutput();
        $this->hsts();
        $this->csp('default', 'self');
        $this->csp('script', 'https://my.cdn.org');
    }
}

上面的示例实现了上面讨论的示例,并且会自动应用于运行了一行代码的任何页面。

$headers = new CustomSecureHeaders();

当然,页面还可以添加额外的配置,并且只有在页面开始生成输出时才会应用头部。

另一个示例

如果创建以下CSP(注意这可能不是定义这么大的CSP的最佳方式,请参阅使用CSP部分中可用的数组语法)

$headers->csp('default', '*');
$headers->csp('script', 'unsafe-inline');
$headers->csp('script', 'http://insecure.cdn.org');
$headers->csp('style', 'https:');
$headers->csp('style', '*');
$headers->csp('report', 'https://valid-enforced-url.org');
$headers->cspro('report', 'whatisthis');
Content-Security-Policy:default-src *; script-src 'unsafe-inline'
http://insecure.cdn.org; style-src https: *; report-uri
https://valid-enforced-url.org;

Content-Security-Policy-Report-Only:report-uri whatisthis;

以下将就CSP发出以下消息:(级别E_USER_WARNING和级别E_USER_NOTICE

  • default-src指令包含通配符(因此是CSP绕过)

    警告:内容安全策略在default-src中包含通配符*作为源值;这可以使任何人将受default-src指令覆盖的元素插入到页面中。

  • script-src指令包含一个允许内联脚本的标志(因此是CSP绕过)

    警告:内容安全策略在script-src中包含'unsafe-inline'关键字,这阻止CSP保护页面免受任意代码注入。

  • script-src 指令包含一个不安全的资源作为源值(HTTP 响应可以被轻易地伪造——伪造允许绕过安全限制)

    警告:内容安全策略(Content Security Policy)中包含了不安全的协议 HTTP 在源值中 http://insecure.cdn.org;这可以让任何人将受 script-src 指令覆盖的元素插入到页面中。

  • style-src 指令包含两个通配符(因此是一个 CSP 绕过)——两个通配符都被列出

    警告:内容安全策略中包含以下通配符 https:* 作为 style-src 的源值;这可以让任何人将受 style-src 指令覆盖的元素插入到页面中。

  • 只发送了报告头,但没有/无效的报告地址——防止了只发送报告头在野外执行任何有用的操作

    通知:内容安全策略报告仅头发送了,但提供了无效的,或没有报告地址。此头不会强制执行违规,并且没有指定报告地址,浏览器只能在本地的控制台中报告它们。请考虑添加报告地址以充分利用此头。

使用 CSP

如果您对 Content-Security-Policy 不熟悉,那么运行您建议的策略通过 Google 的 CSP 检查器 可能是一个好主意。

让我们看看声明以下 CSP(或其部分)的几种方法。下面添加了换行和缩进以提高可读性

Content-Security-Policy:
    default-src 'self';
    script-src 'self' https://my.cdn.org https://scripts.cdn.net https://other.cdn.com;
    img-src https://images.cdn.xyz;
    style-src https://amazingstylesheets.cdn.pizza;
    base-uri 'self';
    form-action 'none';
    upgrade-insecure-requests;
    block-all-mixed-content;

CSP 作为数组

$myCSP = array(
    'default-src' => [
        "'self'"
    ],
    'script-src' => [
        'self',
        'https://my.cdn.org',
        'https://scripts.cdn.net',
        'https://other.cdn.com'
    ],
    'img-src' => ['https://images.cdn.xyz'],
    'style-src' => 'https://amazingstylesheets.cdn.pizza',
    'base' => 'self',
    'form' => 'none',
    'upgrade-insecure-requests' => null,
    'block-all-mixed-content'
);

$headers->csp($myCSP);

在上面的示例中,我们使用数组指定策略,使其意义最大(除了稍作变化以展示支持的语法)。然后我们将策略数组传递给 csp 函数。

在数组中,看看 default-src。这是完整的指令名称(数组的键),其源列表指定为一个包含源值的数组。在这种情况下,指令只有一个源值 'self',它被完整地拼写出来(注意字符串中的单引号)。

在这种情况下,我们实际上写了很多不必要的代码——与指令 base 进行比较。实际的 CSP 指令是 base-uri,但 base 是 SecureHeaders 支持的缩写。其次,我们完全省略了向下源列表的数组语法——我们只想声明一个有效的源,因此 SecureHeaders 支持在数组结构无用的前提下省略它。此外,我们还使用了源值中的缩写——省略了字符串值的单引号(即 self'self' 的缩写)。

此策略还包括两个 CSP '标志',即 upgrade-insecure-requestsblock-all-mixed-content。这些不包含任何源值(如果包含则不适用于 CSP)。您可以通过提供源值 null(如上所述,或仅包含 null 的数组)或完全不提及后代(如 block-all-mixed-content 所示)来指定这些标志。一旦设置了标志,就不能添加任何源。同样,一旦设置了指令,就不能将其变为标志。(这样做是为了防止意外丢失整个源列表)。

csp 函数还支持组合这些 CSP 数组,因此以下会组合在 $myCSP$myOtherCSP 中定义的 csp。您可以通过添加更多参数来组合任意数量的 csp 数组。

$headers->csp($myCSP, $myOtherCSP);

CSP 作为有序对

使用与上面相同的 csp 函数,您可以将源添加到指令中如下

$headers->csp('default', 'self');
$headers->csp('script', 'self');
$headers->csp('script', 'https://my.cdn.org');

或者如果您喜欢在单行中完成这个操作

$headers->csp('default', 'self', 'script', 'self', 'script', 'https://my.cdn.org');

请注意,这里指令和源被指定为有序对。

如果您想以这种方式添加CSP标志,只需使用以下之一。

$headers->csp('upgrade-insecure-requests');
$headers->csp('block-all-mixed-content', null);

请注意,如果嵌入有序对列表中,第二种方式是必要的——否则SecureHeaders无法判断什么是指令名称或源值。例如,这将设置block-all-mixed-content作为CSP标志,将https://my.cdn.org作为script-src源值。

$headers->csp('block-all-mixed-content', null, 'script', 'https://my.cdn.org');

但是csp函数也支持将这些有序对与数组结构混合,并且参数列表末尾的字符串如果没有源也将被视为标志。您可以用可能是一种记法上的滥用,使用以下方法设置两个CSP标志和包含在$csp数组结构中的策略。

$headers->csp('block-all-mixed-content', $csp, 'upgrade-insecure-requests');

CSP作为,嗯...

CSP函数旨在尽可能宽容,CSP应该能够以您最容易的方式传达。

话虽如此,请负责任地使用——以下内容很难阅读

$myCSP = array(
    'default-src' => [
        "'self'"
    ],
    'script-src' => [
        "'self'",
        'https://my.cdn.org'
    ],
    'script' => [
        'https://scripts.cdn.net'
    ],
);

$myotherCSP = array(
    'base' => 'self'
);

$whoopsIforgotThisCSP = array(
    'form' => 'none'
);

$headers->csp(
    $myCSP, 'script', 'https://other.cdn.com',
    ['block-all-mixed-content'], 'img',
    'https://images.cdn.xyz', $myotherCSP
);
$headers->csp(
    'style', 'https://amazingstylesheets.cdn.pizza',
    $whoopsIforgotThisCSP, 'upgrade-insecure-requests'
);

CSP头已经设置时的行为

header("Content-Security-Policy: default-src 'self'; script-src https://cdn.org 'self'");
$headers->csp('script', 'https://another.domain.example.com');

上述代码将合并已设置的CSP头和最后一行设置的附加script-src值。生成以下合并的CSP头

Content-Security-Policy: script-src https://another.domain.example.com https://cdn.org 'self'; default-src 'self'

Content-Security-Policy-Report-Only

所有上述内容以完全相同的方式适用于仅报告策略。要告诉SecureHeaders您正在创建一个仅报告策略,只需使用->cspro代替->csp

作为另一种方法,您还可以在常规->csp函数的参数列表中包含布尔值true或非零整数(松散地相当于true)。布尔值false或整数零将表示强制CSP(这是默认值)。最左边的这些布尔值或整数将被视为模式。因此,为了强制执行CSP(如果您不确定CSP参数列表中最终变量的类型),请使用->csp(false, arg1[, arg2[, ...]])等,或者用零代替false。同样,为了强制执行仅报告(如果您不确定CSP参数列表中最终变量的类型),您可以使用->cspro(arg1[, arg2[, ...]])->csp(true, arg1[, arg2[, ...]])

请注意,虽然->csp支持将其模式更改为仅报告,但->cspro不支持(因为它是->csp的别名,强制仅报告)。->csp->cspro在解释Content-Security-Policy可以传达的各种结构方面是相同的。

更多用法

有关完整文档,请参阅Wiki

版本控制

SecureHeaders项目将遵循语义版本控制2,以下为声明的公共API

任何带有@api phpdoc标签的方法。

大致来说

  • Aidantwoods\SecureHeaders\SecureHeaders中的每个公共方法(除了Aidantwoods\SecureHeaders\SecureHeaders::returnBuffer
  • Aidantwoods\SecureHeaders\Http中的每个公共方法
  • Aidantwoods\SecureHeaders\HeaderBag中的每个公共方法

这使得主SecureHeaders类可以按照semver的预期使用,同时也可以按照semver的预期使用HttpAdapter接口/实现(用于集成)。

因此,为了符合语义化版本控制(semver),其他所有方法和属性都是非公开的。这意味着,例如,不属于上述范围内的具有公开可见性的方法可能会在不增加主版本号的情况下以向后不兼容的方式发生变化。

变更日志

SecureHeaders项目将遵循维护变更日志(Keep a CHANGELOG)原则。

查看ChangeLogs/文件夹以查看这些内容。