Littlesqx Blog
littlesqx's blog
https://littlesqx.com/
Mon, 16 Mar 2020 15:59:23 +0800
Mon, 16 Mar 2020 15:59:23 +0800
Jekyll v4.0.0
-
[WIP] PHP html 导出(PDF/图片)方案对比
<h3 id="前言">前言</h3>
<p>这个话题可以上升到怎么选择一个外部依赖/扩展包(往更大地说就是技术选型了),一般来说我们会考虑它的这些方面:</p>
<ul>
<li>
<p>质量。编码是否规范,API 设计是否合理(是否可扩展或二次开发);是否经过测试或验证(单元测试、生产验证);易用性/使用成本(扩展包本身是否还依赖其他扩展,环境要求等);安全性(代码是否有漏洞或包含其他有安全漏洞的环境或依赖);资源占用和性能等等。</p>
</li>
<li>
<p>生态和维护。是否形成社区;社区是否活跃;维护者是谁(一般认为官方 > 组织 > 个人)维护积极度(代码更新频率;issue 解答;版本控制是否合理)。</p>
</li>
<li>
<p>其他方面。还需要结合团队的技术栈和实际使用业务场景,甚至当时的开发状态。</p>
</li>
</ul>
<p>根据这个思路,本文会一起对比一下 PHP 社区上的几个比较优秀的 PDF 导出扩展/方案。</p>
<h3 id="正文">正文</h3>
<h4 id="入围的扩展方案">入围的扩展/方案</h4>
<p>先介绍一下这几个入围的扩展/方案:</p>
<ul>
<li>
<p>mPDF</p>
</li>
<li></li>
</ul>
Sun, 23 Feb 2020 16:23:01 +0800
https://littlesqx.com/2020/02/23/php-html-export/
https://littlesqx.com/2020/02/23/php-html-export/
PHP
chrome headless
PDF
-
[译] PHP language evolution overview proposal
<blockquote>
<p>这是针对处理向后不兼容更改的不同方法的概述提案。这不是一个具体的提案:它只是作为讨论的起点,我们可以决定要遵循的总体方向。—— Nikita Popov</p>
</blockquote>
<h3 id="目录">目录</h3>
<ul>
<li><a href="#简介">简介 Introduction</a></li>
<li><a href="#向后不兼容更改的示例">向后不兼容更改的示例 Examples of possible backwards-incompatible changes</a>
<ul>
<li><a href="#严格类型">严格类型 Strict types</a></li>
<li><a href="#显式引用传递">显式引用传递 Explicit pass-by-reference</a></li>
<li><a href="#禁用动态对象属性">禁用动态对象属性 Forbidding dynamic object properties</a></li>
<li><a href="#严格操作符等">严格操作符等 Strict operators and friends</a></li>
<li><a href="#名称解析更改">名称解析更改 Name resolution changes</a></li>
<li><a href="#字符串插值更改">字符串插值更改 String interpolation changes</a></li>
</ul>
</li>
<li><a href="#方法">方法 Approaches</a>
<ul>
<li><a href="#通用实现的新语言">通用实现的新语言(代号 P++)New language with common implementation (codename P++)</a></li>
<li><a href="#版本">版本 Editions</a></li>
<li><a href="#细粒度的声明">细粒度的声明 Fine-grained declares</a></li>
</ul>
</li>
<li><a href="#per-package 选项的技术实现">per-package 选项的技术实现 Technical realization of “per-package” options</a>
<ul>
<li><a href="#现状:在文件顶部声明">现状:在文件顶部声明 Status quo: Declares at top of file</a></li>
<li><a href="#新的开始标签">新的开始标签 New opening tag</a></li>
<li><a href="#命名空间范围的声明">命名空间范围的声明 Namespace-scoped declares</a></li>
<li><a href="#显式包声明">显式包声明 Explicit package declaration</a></li>
<li><a href="#基于文件系统的软件包">基于文件系统的软件包 Filesystem based packages</a></li>
</ul>
</li>
<li><a href="#一般的注意事项">一般的注意事项 General considerations</a>
<ul>
<li><a href="#维护负担和支持时间表">维护负担和支持时间表 Maintenance burden and support timeline</a></li>
<li><a href="#范围和合格变更">范围和合格变更 Scope and eligible changes</a></li>
<li><a href="#自动升级">自动升级 Automatic upgrades</a></li>
</ul>
</li>
<li><a href="#总结">总结 Conclusion</a></li>
</ul>
<h3 id="简介">简介</h3>
<p>近年来,PHP 社区在如何处理向后不兼容的语言更改方面上日益紧张。PHP 编程语言在某种程度上是随意发展的,并且表现出许多从现代角度不期望看到的行为。</p>
<p>解决这些问题可以使行为更一致,更可预测且更不易出错,从而有利于开发。反过来说,对 PHP 语言的每个向后不兼容的更改都可能促使对现有的数亿行代码进行调整。这延迟了向新 PHP 版本的迁移。</p>
<p>解决此问题的一般思路是允许不同的库和应用程序按照自己的步调跟上语言的变化,同时保持互操作性。有许多方法可以实现此总体目标,下面将对此进行讨论。</p>
<h3 id="向后不兼容更改的示例">向后不兼容更改的示例</h3>
<p>为使以下讨论以真实的提案为基础,本节提供了一些基于现有 RFC 的可能产生向后不兼容更改的示例。它们仅作为示例,本提案并不都持认可态度。</p>
<h4 id="严格类型">严格类型</h4>
<p><a href="https://wiki.php.net/rfc/scalar_type_hints_v5">标量类型声明 RFC</a> 引入了 <code class="highlighter-rouge">strict_types</code> 声明,它允许控制标量类型声明的行为。如果启用,则传递的类型必须与声明的类型完全匹配(取模细节)。如果禁用,则允许某些类型的强制转换。</p>
<p>尽管这是已经存在的功能,但在这个话题上值得拿来讨论一下,因为这是选择向后不兼容更改的现有示例。提供一些历史背景:当时,内部函数已经接受标量类型并根据强制语义对其进行了验证。为了保持与内部类型检查的一致性,用户区类型必须遵循相同的,通常是不受欢迎的语义。<code class="highlighter-rouge">strict_types</code> 声明允许保持用户态/内部态之间的行为一致,同时仍可以选择加入严格类型检查。</p>
<p>我认为,总的来说,将标量类型与 <code class="highlighter-rouge">strict_types</code> 指令一起引入效果很好。除了经常有抱怨必须在每个文件中都指定此选项(至少在早期)。人们遇到的主要技术问题还涉及回调的处理:</p>
<p>由内部函数(例如 <code class="highlighter-rouge">array_map</code>)调用的回调始终具有强制性参数语义,即使从严格类型的文件调用也是如此。相反,在严格类型文件中调用但来自弱类型文件的回调将使用严格参数语义。这是 <code class="highlighter-rouge">strict_types</code> 原始设计的一个缺点:回调应该是特殊情况,以便在声明端(而不是调用端)使用键入模式。在这里提到这个问题,主要是为了说明基于选择性加入使代码无缝互操作的想法在某些情况下可能并不总是完美的。</p>
<h4 id="显式引用传递">显式引用传递</h4>
<p><a href="https://wiki.php.net/rfc/explicit_send_by_ref">显式调用端引用传递 RFC</a> 提案允许在调用端使用 & 标记通过引用传递的变量,接近在声明端的现有标记。该提案的动机部分列出了为什么希望这样做的原因。</p>
<p>但是,仅允许在调用端使用 & 并不能给我们带来很多好处:必须使用调用端标记才能获得可读性、静态分析和性能的全部好处。</p>
<p>不幸的是,要求使用标记会导致最坏的向后兼容破坏类型:编写与 PHP 8(要求调用端标记)和 PHP 7(禁用调用端标记)兼容的代码变得非常困难。首先通过允许使用可选标记,并且仅在很多很多年后才要求它,才能在很长的时间内推出这种更改。这种改变最受选择机制的影响。</p>
<h4 id="禁用动态对象属性">禁用动态对象属性</h4>
<p>其他受到激励的案例提到了 <a href="https://wiki.php.net/rfc/namespace_scoped_declares">命名空间范围声明 RFC</a> 和 <a href="https://wiki.php.net/rfc/locked-classes">锁定类 RFC</a>(另一种不同的方式)。如今,大多数代码期望所有属性都在类中声明(除了诸如 <code class="highlighter-rouge">stdClass</code> 之类的特殊情况,当然诸如 <code class="highlighter-rouge">__set()</code> 之类的魔术方法也除外)。设置未声明的属性很可能是错字,而不是故意的行为。不幸的是,PHP 默许了它,并且没有禁用此行为的好方法(一种常见的解决方法是使用带有可检测异常的魔术方法 <code class="highlighter-rouge">trait</code>)。</p>
<p>拥有进行未声明的属性访问错误异常的选项,对于现代代码将是有益的。但是,还有许多旧的代码没有声明属性,因此需要选择加入。</p>
<h4 id="严格操作符等">严格操作符等</h4>
<p>尽管 <code class="highlighter-rouge">strict_types</code> 可用于禁用函数参数的类型强制,但目前尚无办法禁用基本语言构造函数(如算术运算符)的类型强制。<a href="https://wiki.php.net/rfc/strict_operators">严格运算符 RFC</a> 提出了 <code class="highlighter-rouge">strict_operators</code> 选项加入声明,以禁止大多数类型的强制转换。</p>
<p>该 RFC 很有趣,因为它不仅增加了新的错误,而且还在某些地方更改了行为。例如,<code class="highlighter-rouge">switch</code> 语句将使用严格比较(模数详细信息),而当前使用弱比较(==)。这是一个重要的区别:如果声明仅添加错误,则始终可以通过假定启用该选项来生成有效代码(无论该选项实际上是否启用)。如果声明进行了行为更改,那么是否启用该选项就非常重要。</p>
<h4 id="名称解析更改">名称解析更改</h4>
<p>提出的 <code class="highlighter-rouge">fuction_and_const_lookup ='global'</code> 声明最近被拒绝了。尽管拒绝了该提案,但我们仍希望考虑其他名称解析更改,以使函数/常量和类的规则保持一致。</p>
<h4 id="字符串插值更改">字符串插值更改</h4>
<p><a href="https://wiki.php.net/rfc/arbitrary_expression_interpolation">任意表达式插值 RFC</a> 建议引入 <code class="highlighter-rouge">foo#{1 + 1} bar</code> 语法来插值任意表达式。这造成了较小的向后兼容破坏,因为当前在字符串中允许使用 <code class="highlighter-rouge">#{}</code>,并且没有特殊含义。可以通过选择语法来避免向后兼容破坏。甚至可以使用 <code class="highlighter-rouge">foo {1 + 1} bar</code> 语法,当然这是另一个问题了。</p>
<p>这个示例很有趣,因为它涉及到 PHP 词法分析器的更改,而所有前面的示例都涉及到编译器或运行时的更改。</p>
<h3 id="方法">方法</h3>
<p>过去已经讨论了三种通用方法(相比于技术细节,更多是在理论上),在此进行了总结。</p>
<h4 id="通用实现的新语言">通用实现的新语言</h4>
<p>Zeev 已经提出了这种方法,<a href="https://wiki.php.net/pplusplus/faq">P++ FAQ</a> 和 <a href="https://wiki.php.net/pplusplus/concerns">P++ Concerns</a> 中对此已有一些讨论。PHP 内部的非正式民意检测一致反对这一想法。</p>
<p>P++ 的思想是引入一种新语言,该语言共享一个实现,并且可以与 PHP 互操作。 但是,P++ 在语法和行为上可能存在重大差异,并且可能追求不同的设计目标。顾名思义,这类似于 C 和 C++ 的情况,它们通常都由同一编译器支持,并且名义上可以互操作。</p>
<p>我认为这种方法存在许多问题,并且基本上都归结为 P++ 是 <strong>一个巨大的改动</strong>:</p>
<ul>
<li>
<p>仅有一次机会可以引入向后不兼容的更改。P++ 发布后,我们回到第一个问题。假设我们不会在第一次尝试中就创建出完美的语言,那么最好引入一种更具可持续性的机制。</p>
</li>
<li>
<p>一次性的重大更改会带来很高的升级负担。根据范围的不同,它可能比切换语言版本更类似于切换语言。这使得旧代码切换到 P++ 的可能性较小。</p>
</li>
<li>
<p>如果差异太大,则可能难以确保 PHP 和 P++ 之间的互操作性。例如,尽管 C 和 C++ 在名义上是兼容的,但实际上这需要通过与 C 兼容的 FFI 接口导出 C++ 代码。这使得在 C++ 中集成 C 相当容易,但反之则不然。假设,如果 P++ 引入了泛型但 PHP 没有引入泛型,则不清楚它们将如何互操作。</p>
</li>
<li>
<p>坦白说,我们只是没有开发资源来实现这一目标。这需要比现在更大的开发团队进行多年的一致努力。我们的资源最好投入在其他地方。</p>
</li>
</ul>
<p>从积极的一面来看,P++ 将使我们能够比下面讨论的方法做出更根本的改变。人们有时会提出类似的想法,例如 <em>从变量名中删除 $</em>,而一般情况下这只是出于疯狂而已,但是 P++ 至少基本上可以进行这样的更改。</p>
<h4 id="版本">版本</h4>
<p>版本(Editions)是 Rust 编程语言所普及的概念。有关更多信息,请参见 <a href="https://doc.rust-lang.org/edition-guide/editions/index.html">版本指南</a> 和 <a href="https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md">epoch RFC</a>。</p>
<h4 id="细粒度的声明">细粒度的声明</h4>
<p>“版本” 方法的替代方法是为单个更改引入更细粒度的声明指令。这是受现有的 strict_types 指令启发的,并且在前面的示例部分中的许多最新建议中都提到了这一点。</p>
<p>关于版本和细粒度声明之间差异的一些注意事项:</p>
<ul>
<li>
<p>“版本”的主要优点是更改是分组和分层的。这将在给定时间可用的语言“方言”的数量减少到了版本数。另一方面,细粒度声明创建 2^N 个不同的方言,其中 N 是布尔声明的数量。</p>
</li>
<li>
<p>细粒度声明的主要优点在于,它允许逐步更新代码(通过逐一处理更改),并允许退出部分代码库中的特定更改。例如,如果有一个假设的 <code class="highlighter-rouge">no_dynamic_properties</code> 声明,则可能希望对大多数代码启用它,但是在一个特定文件中将其禁用,在该文件中,该文件与需要使用动态属性的旧式库进行交互。</p>
</li>
<li>
<p>与此相关的是,细粒度的声明允许处理这样的情况:我们希望让人们可以选择是否要进行某些更改。“版本”强烈暗示应该使用新版本及其带来的更改。例如,是否将当前的 <code class="highlighter-rouge">strict_types</code> 选项作为新版本的一部分启用?</p>
</li>
</ul>
<h3 id="per-package-选项的技术实现">per-package 选项的技术实现</h3>
<p>与选择的“精细度”无关,在技术水平上也可以通过多种方式指定它们。这些将在下面讨论。</p>
<h4 id="现状在文件顶部声明">现状:在文件顶部声明</h4>
<p>我们已经在文件顶部使用了 <code class="highlighter-rouge">declare(strict_types = 1)</code> 来启用严格的类型,因此自然而然地继续依靠这种机制。</p>
<p>这种方法有两个很大的优点:首先,它已经有效,很熟悉并且不需要引入任何新的语言功能。其次,文件保持独立:无需查看其他文件即可确定使用的语言方言,这对工具尤其有用。</p>
<p>它还有一些缺点:首先,它无法扩展。此方法仅与“版本”方法兼容(在“版本”方法中,只有一个声明 <code class="highlighter-rouge">(edition = 2020)</code> 是必需的)。不能将其与“细粒度声明”方法一起使用。</p>
<p>其次,软件包很可能希望对整个软件包使用一种语言方言,而不是在不同文件之间混合使用。虽然名义上每个文件中的声明都更加明确,但实际上程序员会想到的是将其建模为“我正在开发 PHP 2020 项目”,并且每次打开新文件时都不会再次检查版本。如果文件忘记了偶然指定版本,则可能导致意外。</p>
<h4 id="新的开始标签">新的开始标签</h4>
<p>与前面的变体相比,有一个较小的变体:可以不使用声明,而可以引入新的开始标记。再一次,这仅适用于 P++ <code class="highlighter-rouge"><?p++</code> 或版本 <code class="highlighter-rouge"><span class="cp"><?php</span><span class="mi">2020</span></code>。这具有与每个文件声明相同的特征。它稍微紧凑一些,但是引入了新的语法。</p>
<p>在讨论过程中提出的建议是,也可以使用新的文件扩展名。这仅与 P++ 兼容,否则项目的所有文件在每次版本升级时都必须重命名。使用文件扩展名还具有其他缺点:在文件扩展名未知的情况下不起作用,例如,如果脚本通过管道传输到 PHP,从 stdin 读取或来自非文件系统流。一个新的扩展增加了在不知道新配置的服务器上进行代码泄漏的机会。</p>
<h4 id="命名空间范围的声明">命名空间范围的声明</h4>
<p>在 <a href="https://wiki.php.net/rfc/namespace_scoped_declares">命名空间范围的声明 RFC</a> 中将更详细地探讨了此变体。该提议允许为整个命名空间(包括子命名空间)指定声明:</p>
<pre><code class="language-PHP">namespace_declare('Vendor\Lib', [
'strict_types' => 1,
'no_dynamic_properties' => 1,
// ...
]);
</code></pre>
<p>目的是在 <code class="highlighter-rouge">composer.json</code> 中指定这些内容,并且 <code class="highlighter-rouge">Composer</code> 将负责用 <code class="highlighter-rouge">PHP</code> 注册声明。</p>
<p>这种方法避免了在每个文件中指定声明的弊端:它可以按任意数量的声明进行缩放,并且程序员可以假定声明对于整个项目都成立,除非明确覆盖了它们。</p>
<p>这种方法(以及下面讨论的所有其他其他“基于包”的方法)的一个缺点是,无法再通过查看单个文件来分辨所使用的语言方言。尽管这对人类来说不是问题(面向软件包的方法更有用),但对于工具来说可能是一个问题,因为在没有更大上下文的情况下可能无法正确处理文件。</p>
<p>与下面讨论的两种基于包的方法不同,命名空间范围的声明基于现有的,完善的和易于理解的“命名空间”功能。这既是一个优点(它没有引入任何新概念,也不需要其他代码)和也是一个缺点:</p>
<p>虽然名称空间通常直接映射到包,但并非总是如此。例如,主要的 <code class="highlighter-rouge">amphp</code> 软件包使用 <code class="highlighter-rouge">Amp\</code> 名称空间,而其他 <code class="highlighter-rouge">amphp</code> 软件包使用 <code class="highlighter-rouge">Amp\FooBar\</code>。在这里,<code class="highlighter-rouge">Amp\</code> 名称空间实际上不能被视为单个程序包。还有其他问题(例如,由于可以在单个文件中包含多个名称空间),但是可以解决这些问题,请参阅链接的 RFC,以获取更多详细讨论。</p>
<h4 id="显式包声明">显式包声明</h4>
<p>没有适用于此变体的 RFC,但是可以使用原型请求。这引入了与名称空间正交的新“包”概念。必须在名称空间旁边的每个文件中声明该包:</p>
<pre><code class="language-PHP"><?php
package "nikic/php-parser";
namespace PhpParser\Node;
// ...
</code></pre>
<p>然后可以将声明绑定到特定的程序包,而不是特定的名称空间。此外,相同的功能可以重用于其他基于包的功能,例如包专用符号。</p>
<p>这种方法的优势在于,它解决了为此使用名称空间的情况所带来的歧义。另一方面,它为语言引入了一个全新的概念,并可能导致其与名称空间的关系混乱。除非将此概念也用于其他目的,否则可能不值得引入它。</p>
<h4 id="基于文件系统的软件包">基于文件系统的软件包</h4>
<p>基于文件系统的软件包是显式软件包声明的替代方法。通过在目录中放置一个特殊文件(例如 <code class="highlighter-rouge">_package.php</code>)来定义软件包,该文件也可以包含每个软件包的配置。</p>
<p>相对于先前的变体,其优点是不需要在每个文件中都进行显式的包声明。此外,与命名空间范围的声明和显式包声明不同,它提供了一个定义良好的地方来查找与包相关的声明(<code class="highlighter-rouge">_package.php</code> 文件)。前两个变体将按约定将声明放置在 <code class="highlighter-rouge">composer.json</code> 中,但是 <code class="highlighter-rouge">namespace_declare()</code> 或 <code class="highlighter-rouge">package_declare()</code> 也可以从其他位置调用,这使得工具支持更加复杂。</p>
<p>这种方法的缺点是文件系统耦合,它在 PHP 中引入了许多问题:</p>
<ul>
<li>
<p>缓存失效:在一个请求中,PHP 大概会缓存哪些目录不包含 <code class="highlighter-rouge">_package.php</code> 文件,以及其中的内容。在请求期间,对其中任何一个的更改都将被忽略。更具问题的情况是它们将如何与 <code class="highlighter-rouge">opcache</code> 交互。如果启用 <code class="highlighter-rouge">validate_timestamps</code>,我们还必须检查所有目录(和父目录)中是否添加,删除或更改了 <code class="highlighter-rouge">_package.php</code> 文件,这可能会带来额外的性能损失。</p>
</li>
<li>
<p>路径规范化:为了确定文件是否是包的一部分,规范化路径需要可用。例如,必须解析符号链接,并且必须正确处理文件系统的大小写(不区分大小写)。尽管可以通过 <code class="highlighter-rouge">realpath()</code> 在文件系统层上使用此功能,但是对于一般的 PHP 流,当前不存在此功能(甚至没有phars可以正确地支持此功能)。使用其他流包装钩可以解决此问题。</p>
</li>
<li>
<p>目录遍历:为了找到包文件,我们需要在给定路径中“向上”查找包文件。再一次,流包装器不支持此操作。实际上,许多流包装器没有“目录”的有意义的概念。</p>
</li>
<li>
<p>通常,很难使此功能与任意流一起使用。我有一个替换文件流包装程序的包,以拦截所有包含的文件,并用存储在临时目录中的预处理文件替换它们。我不知道这将如何与基于目录的包系统交互。</p>
</li>
</ul>
<h3 id="一般的注意事项">一般的注意事项</h3>
<h4 id="维护负担和支持时间表">维护负担和支持时间表</h4>
<p>每个新版本/声明都会增加额外的维护负担,因为它要求在同一实现中支持两种不同的行为。在 Rust 中,版本的概念包括无限期支持它们的承诺(尽管在实践中,向后兼容的破坏有时确实会回溯到较早的版本,只是稍后)。</p>
<p>我们可能会或可能不想无限期地支持旧版本。即使一个版本只有有限的生存期,它仍然是管理版本迁移的有用工具,因为它消除了项目之间的依赖关系。每个项目都可以独立更新到新版本,而无需先更新其依赖项,也不必强制对其反向依赖项进行升级。</p>
<p>一个相关的问题是多长时间发布一次新版本。使用细粒度的声明方法,最好将它们添加到任何次要版本中。对于版本,即使更改很少,我们是否还要为每个次要版本创建一个新版本也不清楚。应该只为每个主要版本创建这些文件吗?还是应该在我们积累相关更改时按需创建它们?</p>
<h4 id="范围和合格变更">范围和合格变更</h4>
<p>应该强调的是,即使我们引入了“版本”之类的机制,也并不意味着所有向后不兼容的更改都将通过版本来处理,也不意味着任何向后兼容的破坏都可以,例如 只要基于版本。</p>
<p>这里有一些考虑因素:</p>
<ul>
<li>
<p>技术可行性:无法通过版本/声明进行某些更改,因为更改的影响不能包含在使用该版本的代码中。例如,从 HTTP 查询参数中删除名称修饰会影响所有代码中 <code class="highlighter-rouge">$_GET</code> 的内容,并且不能仅限于一个版本。 这种变化必须在全球范围内发生或根本不发生。</p>
</li>
<li>
<p>精神负担:虽然在新版本中引入新的错误条件总会很好,但是我们需要对行为的变化更加谨慎。如果仅引入错误,则程序员始终可以针对最新版本进行编程,即使实际使用的是旧版本,代码也可以正常工作。我不认为应该完全禁止改变行为,但应该为他们提供更高的标准。</p>
</li>
<li>
<p>维护开销:如前所述,由于必须支持两种不同的行为(以及它们与其他功能的交互),因此通过版本/声明机制引入的每项更改都会带来额外的维护负担。因此,不应将版本/声明机制用于较小的向后兼容性破坏。该机制应被视为做出我们之前无法进行的更改的手段:如果更改可以通过正常的弃用+删除周期来实现,则应该这样做。</p>
</li>
<li>
<p>库更改:在特定情况下,我认为版本/声明机制不适合标准库更改。 可以通过新功能或标志更好地处理这些问题。</p>
</li>
</ul>
<h4 id="自动升级">自动升级</h4>
<p>Rust 提供了大多数用于执行版本升级的自动化工具。由于其动态特性,在 PHP 中并不总是可能进行可靠的自动升级。但是,我们可能仍想为此提供官方的尽力而为工具。</p>
<h3 id="总结">总结</h3>
<p>我个人的结论是:应按文件进行声明的版本。</p>
<p>尽管具有细粒度的声明有其优势,并且是我最初提倡的选择,但我认为,对声明的泛滥和语言变体呈指数级增长的担忧非常真实。即使定期推出新版本,版本也可以大大减少语言方言的数量,从而减轻开发人员的心理负担并减少我们这方面的维护负担。</p>
<p>一旦使用了版本,投入用于包范围声明的新机制并不会带来很多好处,所有这些都有其自身的问题。每个文件的声明确实有其自身的优点,特别是它使内容保持独立,并且允许部分升级代码库。每个文件的声明的人机工程学当然会更糟,但是随着人们从本质上将使用 <code class="highlighter-rouge">declare(edition=2020)</code> 替换 <code class="highlighter-rouge">declare(strict_types=1)</code>,这并不比当前情况更糟。(此外,以后仍可以引入某种包范围的声明机制。)</p>
<p>我认为我们需要尽快优先考虑在 PHP 8 中引入“版本”,因此我们仍有时间利用这一机会。</p>
<p>原文地址:<a href="https://github.com/nikic/php-rfcs/blob/language-evolution/rfcs/0000-language-evolution.md">PHP RFC: language_evolution - Nikita Popov</a></p>
Fri, 21 Feb 2020 17:23:01 +0800
https://littlesqx.com/2020/02/21/language-evolution-overview-proposal/
https://littlesqx.com/2020/02/21/language-evolution-overview-proposal/
PHP
PHP RFC
翻译
-
[译] The 2018 Guide to Building Secure PHP Software
<blockquote>
<p>2018PHP应用程序安全设计指北!</p>
</blockquote>
<h3 id="目录">目录</h3>
<ul>
<li><a href="#前言">前言</a></li>
<li><a href="#php-版本">PHP 版本 / PHP Versions</a></li>
<li><a href="#依赖管理">Composer依赖管理 / Dependency Management with Composer</a>
<ul>
<li><a href="#推荐扩展">推荐扩展 / Recommended Packages</a></li>
</ul>
</li>
<li><a href="#https和浏览器安全">HTTPS和浏览器安全 / HTTPS and Browser Security</a>
<ul>
<li><a href="#安全头">安全头 / Security Headers</a></li>
<li><a href="#子资源完整性">子资源完整性 / Subresource Integrity</a></li>
<li><a href="#文档关系">文档关系 / Document Relationships</a></li>
</ul>
</li>
<li><a href="#开发安全的php程序">开发安全的PHP程序 / Developing Secure PHP Software</a>
<ul>
<li><a href="#数据库注入">数据库注入 / Database Interaction</a></li>
<li><a href="#文件上传">文件上传 / File Uploads</a></li>
<li><a href="#跨站脚本">跨站脚本 / Cross-Site Scripting (XSS)</a></li>
<li><a href="#跨站请求伪造">跨站请求伪造 / Cross-Site Request Forgery (CSRF)</a></li>
<li><a href="#xml-攻击-xxe-xpath-injection">XML 攻击 / XML attacks (XXE, XPath Injection)</a></li>
<li><a href="#反序列化和php对象注入">反序列化和PHP对象注入 / Deserialization and PHP Object Injection</a></li>
<li><a href="#密码散列">密码散列 / Password Hashing</a></li>
<li><a href="#通用加密">通用加密 / General-Purpose Cryptography</a></li>
<li><a href="#随机性">随机性 / Randomness</a></li>
<li><a href="#服务器端https请求">服务器端HTTPS请求 / Server-Side HTTPS Requests</a></li>
<li><a href="#避免的事情">避免的事情 / Things to Avoid</a></li>
</ul>
</li>
<li><a href="#专业用法">专业用法 / Specialized Use-Cases</a>
<ul>
<li><a href="#可搜索的加密">可搜索的加密 / Searchable Encryption</a></li>
<li><a href="#没有side-channels的基于令牌的身份验证">没有Side-Channels的基于令牌的身份验证 / Token-based Authentication without Side-Channels</a></li>
<li><a href="#开发安全的api">开发安全的API / Developing Secure APIs</a></li>
<li><a href="#使用chronicle记录安全事件">使用Chronicle记录安全事件 / Security Event Logging with Chronicle</a></li>
</ul>
</li>
<li><a href="#作者的一些话">作者的一些话 / A Word From the Author</a></li>
<li><a href="#资源">资源 / Resources</a></li>
</ul>
<h3 id="前言">前言</h3>
<p>2018年将至,一般程序员(特别是Web开发程序员)应当抛弃过去开发PHP程序的很多不好的习惯和观念了。虽然部分人不以为意,但是这确实是事实。</p>
<p>这个指南应该以重点部分作为<a href="http://www.phptherightway.com/">PHP: The Right Way</a>安全章节的补充,而不是以一般的PHP编程话题。</p>
<h3 id="正文">正文</h3>
<h4 id="php-版本">PHP 版本</h4>
<blockquote>
<p>请在2018年使用PHP 7.2, 并且计划2019年初切换到PHP 7.3。</p>
</blockquote>
<p>PHP 7.2 已于2017年11月30日发布。</p>
<p>写这篇文章的时候,只有7.1和7.2版本还在被PHP官方积极维护,而5.6和7.0只在大概1年内提供安全补丁更新。</p>
<p>对于其他官方不维护的PHP版本,虽然某些操作系统会提供长期支持和维护,但这其实通常是有害的。尤其是他们提供安全支持补丁却没有版本号,这使得很难解释系统的安全性(仅仅知道PHP版本)。</p>
<p>因此,无论其他供应商提出了什么承诺,如果可以,你就应该在任何时候都坚决地使用<a href="http://php.net/supported-versions.php">官方提供支持的PHP版本</a>。这样,尽管最终是一个短暂的安全版本,但一个不断致力于的升级版本,总会让你收获一些意外的惊喜。</p>
<h4 id="依赖管理">依赖管理</h4>
<blockquote>
<p>人生苦短,我用Composer</p>
</blockquote>
<p>在PHP生态中,<a href="https://getcomposer.org/">Composer</a>是最先进的依赖管理方案。我们推荐PHP: The Right Way 中关于<a href="http://www.phptherightway.com/#dependency_management">依赖管理</a>的完整章节。</p>
<p>如果你没有使用Composer来管理应用的依赖,最终(hopefully later but most likely sooner)会导致应用里某个依赖会严重过时,然后老旧版本中的漏洞会被利用于计算机犯罪。</p>
<p><strong>重要</strong>: 开发软件时,时常记得<a href="http://www.phptherightway.com/#updating-your-dependencies">保持依赖的更新</a>。幸运地,这只需一行命令:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>composer update
</code></pre></div></div>
<p>如果你正在使用某些专业的,需要使用PHP扩展(C语言编写),那你不能使用Composer管理,而需要PECL。</p>
<h5 id="推荐扩展">推荐扩展</h5>
<p>不管你正在编写什么,你总会受益于这些依赖。这是除了大多数PHP程序员的推荐(PHPUnit, PHP-CS-Fixer, …)外的补充。</p>
<p><strong>roave/security-advisories</strong></p>
<p><a href="https://github.com/Roave/SecurityAdvisories">Roave’s security-advisories</a>使用<a href="https://github.com/FriendsOfPHP/security-advisories">Friends of PHP repository</a>确保你的项目没有依赖一些已知易受攻击的依赖。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>composer require roave/security-advisories:dev-master
</code></pre></div></div>
<p>或者,你可以<a href="https://github.com/FriendsOfPHP/security-advisories#checking-for-vulnerabilities">上传你的<code class="highlighter-rouge">composer.lock</code>文件到Sensio Labs</a>,作为例行自动化漏洞评估工作流的一部分,以提醒发现任何过时的软件包。</p>
<p><strong>vimeo/psalm</strong></p>
<p><a href="https://github.com/vimeo/psalm">Psalm</a>是一个帮助你识别代码里可能存在bugs的静态分析工具。还有其他很好的静态分析工具(例如<a href="https://github.com/phan/phan">Phan</a>和<a href="https://github.com/phpstan/phpstan">PHPStan</a>都很棒),但当你发现你需要支持PHP 5,Psalms将是PHP 5.4+的首选。</p>
<p>使用Psalm挺简单:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Version 1 doesn't exist yet, but it will one day:</span>
composer require <span class="nt">--dev</span> vimeo/psalm:^0
<span class="c"># Only do this once:</span>
vendor/bin/psalm <span class="nt">--init</span>
<span class="c"># Do this as often as you need:</span>
vendor/bin/psalm
</code></pre></div></div>
<p>如果你是第一次在现有代码库运行,可能会看到很多红色错误。但除非你在构建像WordPress那么大的程序,否则努力通过所有测试绝不是艰巨的。</p>
<p>无论使用哪种静态分析工具,我们都推荐你能将他加入到<a href="https://zh.wikipedia.org/wiki/%E6%8C%81%E7%BA%8C%E6%95%B4%E5%90%88">持续集成工作流</a>(Continuous Integration workflow)中,以便在每次更改代码中运行。</p>
<h4 id="https和浏览器安全">HTTPS和浏览器安全</h4>
<blockquote>
<p>HTTPS, <a href="https://www.ssllabs.com/">which should be tested</a>, and <a href="https://securityheaders.io/">security headers</a> .</p>
</blockquote>
<p>2018年,不安全的HTTP网站将不再被接受。幸运的是,由于ACME 协议 和 <a href="https://letsencrypt.org/">Let’s Encrypt certificate authority</a>,免费的TLS证书成为了可能。</p>
<p>将 ACME 集成到你的服务器,小菜一碟。</p>
<ul>
<li><a href="https://caddyserver.com/">Caddy</a>: 自动加入。</li>
<li><a href="https://letsencrypt.org/2017/10/17/acme-support-in-apache-httpd.html">Apache</a>: 很快作为<code class="highlighter-rouge">mod_md</code>可用。在此之前,<a href="https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04">网上很多高质量教程</a>。</li>
<li><a href="https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/">Nginx</a>: 相对简单。</li>
</ul>
<p>你也许会想,“好,我已经有TLS证书了,为了网站变得安全和快速,得花些时间折腾配置信息。”</p>
<p><strong>不!</strong><a href="https://mozilla.github.io/server-side-tls/ssl-config-generator/">Mozilla做了好事情!</a>。你可以根据网站的目标受众,使用配置生成器生成<a href="https://wiki.mozilla.org/Security/Server_Side_TLS">推荐套件</a>。</p>
<p>如果你希望网站安全,HTTPS(HTTP over TLS)是<a href="https://stackoverflow.com/a/2336738/2224584">绝对不能妥协</a>的。使用HTTPS立刻就能消除多种攻击(中间人攻击、窃听、重放攻击以及若干允许用户模仿的会话形式的攻击)。</p>
<h5 id="安全头">安全头</h5>
<p>在服务器使用HTTPS确实为用户提供了许多安全性和性能方面的好处,但也还能通过利用某些浏览器的安全功能来进一步提升安全性。而这大部分会涉及到与返回内容一起返回的 Header 。</p>
<ul>
<li><code class="highlighter-rouge">Content-Security-Policy</code>
<ul>
<li>你需要该 Header ,因为它提供了对于浏览器是否允许加载内部和外部资源的细化控制,从而为跨域脚本攻击漏洞提供了有效防御层。</li>
<li>参阅<a href="https://github.com/paragonie/csp-builder">CSP-Builder</a>,以便快速简便地部署/管理内容安全策略(Content Security Policies)。</li>
<li>为了更加深入的分析, Scott Helme’s <a href="https://scotthelme.co.uk/content-security-policy-an-introduction/">introduction to Content-Security-Policy headers</a>,会是一个很好的引导。</li>
</ul>
</li>
<li><code class="highlighter-rouge">Expect-CT</code>
<ul>
<li>你需要该 Header ,因为它能通过强制某些不良行为者将其错误证书的证据颁发到可公开验证的仅可追加的数据结构,从而针对流氓/受损的证书颁发机构增加一层防护。</li>
<li>优先设置为<code class="highlighter-rouge">enforce,max-age=30</code>。只要你有足够的自信该 Header 不会造成服务中断,增加<code class="highlighter-rouge">max-age</code>吧。</li>
</ul>
</li>
<li><code class="highlighter-rouge">Referrer-Policy</code>
<ul>
<li>你需要该 Header ,因为它允许你控制用户的行为信息是否泄露给第三方。</li>
<li>同样地,Scott Helme提供了<a href="https://scotthelme.co.uk/a-new-security-header-referrer-policy/">一篇关于Referrer-Policy Header 介绍好文</a>。</li>
<li>除非有理由允许更加宽松的设置,否则请设置为<code class="highlighter-rouge">same-origin</code>或<code class="highlighter-rouge">no-referrer</code>。</li>
</ul>
</li>
<li><code class="highlighter-rouge">Strict-Transport-Security</code>
<ul>
<li>你需要该 Header ,因为它告诉浏览器通过HTTPS而不是不安全的HTTP,将future requests设为同源。</li>
<li>在第一次部署时,将其设置为<code class="highlighter-rouge">max-age = 30</code>,然后当你确信没有任何内容会中断时,将此值增加到某个较大的值(例如31536000)。</li>
</ul>
</li>
<li><code class="highlighter-rouge">X-Content-Type-Options</code>
<ul>
<li>你需要该 Header ,因为MIME类型的混淆可能会导致不可预知的结果,包括奇怪的允许XSS漏洞的边缘情况。这最好伴随着一个标准的Content-Type Header 。</li>
<li>除非需要默认的行为(例如文件的下载),否则请设置为<code class="highlighter-rouge">nosniff</code>。</li>
</ul>
</li>
<li><code class="highlighter-rouge">X-Frame-Options</code>
<ul>
<li>你需要该 Header ,因为它允许你防止点击劫持。</li>
<li>设置为<code class="highlighter-rouge">DENY</code> (或者<code class="highlighter-rouge">SAMEORIGIN</code>, 但仅仅当你使用<code class="highlighter-rouge"><frame></code>元素的时候)。</li>
</ul>
</li>
<li><code class="highlighter-rouge">X-XSS-Protection</code>
<ul>
<li>你需要该 Header ,因为它启用了一些默认情况下未启用的浏览器反XSS功能。</li>
<li>设置为<code class="highlighter-rouge">1; mode=block</code>。</li>
</ul>
</li>
</ul>
<p>同样,如果你使用PHP的内置会话管理功能(建议使用),则可能需要调用<code class="highlighter-rouge">session_start()</code></p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="nb">session_start</span><span class="p">([</span>
<span class="s1">'cookie_httponly'</span> <span class="o">=></span> <span class="kc">true</span><span class="p">,</span>
<span class="s1">'cookie_secure'</span> <span class="o">=></span> <span class="kc">true</span>
<span class="p">]);</span>
</code></pre></div></div>
<p>这会强制你的应用在发送会话标识符时使用HTTP-Only和Secure标志,从而防止XSS攻击窃取用户的Cookie,并强制它们分别通过HTTPS发送。 我们之前在2015年的博客文章中介绍了<a href="https://paragonie.com/blog/2015/04/fast-track-safe-and-secure-php-sessions">安全的PHP会话</a>。</p>
<h5 id="子资源完整性">子资源完整性</h5>
<p>在将来的某个时候,你也许会使用CDN来加载网站的公共JavaScript/CSS库。安全工程师已经遇见了这存在一个明显的风险,如果很多网站使用CDN提供内容,Hack和替换CDN(获得了CDN的控制权),则可以注入(恶意)代码到成千上万的网站。</p>
<p>查阅<a href="https://developer.mozilla.org/zh-CN/docs/Web/Security/%E5%AD%90%E8%B5%84%E6%BA%90%E5%AE%8C%E6%95%B4%E6%80%A7">子资源完整性</a>吧。</p>
<p>子资源完整性(SRI,Subresource integrity)允许你将希望CDN服务的文件的内容进行哈希处理。目前实行的SRI只允许使用安全的密码散列函数,这意味着攻击者不可能生成与原始文件哈希相同的恶意版本资源。</p>
<p>一个真实例子: <a href="https://v4-alpha.getbootstrap.com/">Bootstrap v4-alpha uses SRI in their CDN example snippet</a></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><link</span>
<span class="na">rel=</span><span class="s">"stylesheet"</span>
<span class="na">href=</span><span class="s">"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"</span>
<span class="na">integrity=</span><span class="s">"sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ"</span>
<span class="na">crossorigin=</span><span class="s">"anonymous"</span>
<span class="nt">/></span>
<span class="nt"><script
</span><span class="na">src=</span><span class="s">"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"</span>
<span class="na">integrity=</span><span class="s">"sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn"</span>
<span class="na">crossorigin=</span><span class="s">"anonymous"</span>
<span class="nt">></script></span>
</code></pre></div></div>
<h5 id="文档关系">文档关系</h5>
<p>Web开发人员经常在超链接上设置目标属性(例如,<code class="highlighter-rouge">target ="_ blank"</code>在新窗口中打开链接)。但是,如果你没有传递<code class="highlighter-rouge">rel ="noopener"</code>标签,则可以<a href="https://mathiasbynens.github.io/rel-noopener/">允许目标页面控制当前页面</a>。</p>
<p>不要这样做:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://example.com"</span> <span class="na">target=</span><span class="s">"_blank"</span><span class="nt">></span>Click here<span class="nt"></a></span>
</code></pre></div></div>
<p>这会让<code class="highlighter-rouge">http://example.com</code>页面能控制当前页面。</p>
<p>而应该这样做:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"https://example.com"</span> <span class="na">target=</span><span class="s">"_blank"</span> <span class="na">rel=</span><span class="s">"noopener noreferrer"</span><span class="nt">></span>Click here<span class="nt"></a></span>
</code></pre></div></div>
<p>通过这样在新窗口打开<code class="highlighter-rouge">https://example.com</code>,当前窗口的控制权也不会授予可能的恶意第三方。</p>
<p>可以更加<a href="https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever">深入研究</a>。</p>
<h4 id="开发安全的php程序">开发安全的PHP程序</h4>
<p>如果应用程序安全性对你来说是一个新话题,请从<a href="https://paragonie.com/blog/2015/08/gentle-introduction-application-security">应用程序安全性简介</a>开始吧。</p>
<p>大多数安全专家指出,开发者可以使用<a href="https://www.owasp.org/index.php/Top_10_2017-Top_10">OWASP Top 10</a>等资源开始着手。</p>
<p>但是,大多数常见的漏洞也可以是相同高等级的安全问题(例如代码和数据没有完全分离、逻辑不严谨和健全、操作环境不安全或是可破译的密码协议等)。</p>
<p>我们的假设是,应该授予安全新手知道一些更简单、基础的安全知识和问题,并如何解决这些问题,应该是一个更好的、长远的安全工程。</p>
<p>因此,<a href="https://paragonie.com/blog/2017/04/checklist-driven-security-considered-harmful">我们避免推荐十大或二十大安全清单</a>。</p>
<h5 id="数据库注入">数据库注入</h5>
<blockquote>
<p><a href="https://paragonie.com/blog/2015/05/preventing-sql-injection-in-php-applications-easy-and-definitive-guide">避免PHP程序存在SQL注入。</a></p>
</blockquote>
<p>如果你是自己编写SQL代码,请确保使用<code class="highlighter-rouge">prepared</code>语句,并且从网络或文件系统提供的信息都作为参数传递,而不是字符串拼接的形式。此外,确保你<a href="https://stackoverflow.com/a/12202218">没有使用模拟的prepared语句</a>。</p>
<p>为了达到好的效果,可以使用<a href="https://github.com/paragonie/easydb">EasyDB</a>。</p>
<p>不要这样做:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="cm">/* Insecure code: */</span>
<span class="nv">$query</span> <span class="o">=</span> <span class="nv">$pdo</span><span class="o">-></span><span class="na">query</span><span class="p">(</span><span class="s2">"SELECT * FROM users WHERE username = '"</span> <span class="o">.</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span> <span class="o">.</span> <span class="s2">"'"</span><span class="p">);</span>
</code></pre></div></div>
<p>应该这样做:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="cm">/* Secure against SQL injection: */</span>
<span class="nv">$results</span> <span class="o">=</span> <span class="nv">$easydb</span><span class="o">-></span><span class="na">row</span><span class="p">(</span><span class="s2">"SELECT * FROM users WHERE username = ?"</span><span class="p">,</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]);</span>
</code></pre></div></div>
<p>还有其他数据库抽象层提供了相同的安全性(EasyDB实际上是在使用PDO,但在实际的<code class="highlighter-rouge">prepare</code>语句前避免了<code class="highlighter-rouge">prepared</code>语句模拟)。 只要用户输入不会影响查询的结构,就很安全(包括存储过程)。</p>
<h5 id="文件上传">文件上传</h5>
<blockquote>
<p>深入:<a href="https://paragonie.com/blog/2015/10/how-securely-allow-users-upload-files">如何安全地允许用户上传文件?</a></p>
</blockquote>
<p>接受文件上传是一个冒险的提议,但只要采取一些基本的预防措施,是能保证安全的。也就是说,允许文件直接上传的话,这些文件可能会被意外的允许执行或解释。上传的文件应该是只读(read-only)或读写(read-write)的,永远不应该可执行(executable)。</p>
<p>如果你的网站根目录是<code class="highlighter-rouge">/var/www/example.com</code>,请不要保存上传文件在<code class="highlighter-rouge">/var/www/example.com/uploaded_files</code>。</p>
<p>而应该保存到一个不能直接访问的目录(例如:<code class="highlighter-rouge">/var/www/example.com-uploaded/</code>),以免意外地将其作为服务器端脚本执行,并获得执行远程代码的后门。</p>
<p>一个更加简洁的方法是将网站根目录往下移动一个层级(即:<code class="highlighter-rouge">/var/www/example.com/public</code>)。</p>
<p>如何安全地下载这些上传文件也是一个问题。</p>
<ul>
<li>直接访问SVG图像类型时,将在用户浏览器执行JavaScript代码。尽管<a href="https://github.com/w3c/svgwg/issues/266">它的MIME类型中的<code class="highlighter-rouge">image/</code>前缀具有误导性</a>,但是这是正确的。</li>
<li>正如前面提及的,MIME类型嗅探可能导致类型混淆攻击。请参阅<a href="#安全头">X-Content-Type-Options</a>。</li>
<li>如果你放弃前面关于如何安全地存储上传文件的建议,攻击者就会通过上传.php或.phtml文件,直接在浏览器中访问文件来执行任意代码,从而完全控制服务器。</li>
</ul>
<h5 id="跨站脚本">跨站脚本</h5>
<blockquote>
<p><a href="https://paragonie.com/blog/2015/06/preventing-xss-vulnerabilities-in-php-everything-you-need-know">关于PHP中的跨站脚本攻击,你想知道的都在这里</a></p>
</blockquote>
<p>同样地,预防XSS和SQL注入是一样简单的。我们有简单而易用的API来分离文档结构(structure of a document)和填充的数据。</p>
<p>然而,实际上还有很多Web开发程序员仍是通过生成一大串HTML代码作为响应的形式开发。并且,这不是PHP独有的现实,这是所有Web开发程序员都应该重视的。</p>
<p>减少XSS漏洞不失为一个好方法。总之,前面谈及的<a href="#浏览器安全">浏览器安全的章节</a>就显得十分相关了。简言之:</p>
<ul>
<li>尽量避免输出和输入(<code class="highlighter-rouge">Always escape on output, never on input</code>)。如果你把已清洗的数据(sanitized data)保存在数据库,然后在其它地方被发现了SQL注入漏洞,攻击者将通过恶意程序污染这些受信任的已清洗数据(trusted-to-be-sanitized record),从而绕开XSS保护。</li>
<li>如果你的框架有一个提供自动上下文过滤的模板引擎,那就使用它吧。这些工作可由框架安全地做到。</li>
<li><code class="highlighter-rouge">echo htmlentities($ string,ENT_QUOTES | ENT_HTML5,'UTF-8')</code> 是一种安全、有效的方法阻止UTF-8编码的网页上的所有XSS攻击,但不是任何HTML都有效。</li>
<li>如果你的环境要求你使用Markdown而不是HTML,那就不要使用HTML了。</li>
<li>如果你需要使用原生HTML(没有使用模板引擎),参阅第一点,并且使用<a href="http://htmlpurifier.org/">HTML Purifier</a>吧。HTML Purifier不适合转义为HTML属性上下文(HTML attribute context)。</li>
</ul>
<h5 id="跨站请求伪造">跨站请求伪造</h5>
<p>跨站请求伪造(CSRF)是一种混淆的代理攻击,通过诱导用户的浏览器代表攻击者执行恶意的HTTP请求(使用的是该用户的权限)。</p>
<p>这在一般情况下是很容易解决的,只需两步:</p>
<ul>
<li>使用HTTPS。这是先决条件。没有HTTPS的话,任何保护措施都是脆弱的,虽然HTTPS本身并不防御CSRF。</li>
<li>增加基本的Challenge-response authentication。
<ul>
<li>为每个表单添加一个隐藏的表单属性。</li>
<li>填充一个密码安全的随机值(称为令牌)。</li>
<li>验证是否提供了隐藏的表单属性,以及是否匹配上期望值。</li>
</ul>
</li>
</ul>
<p>我们写了一个名为<a href="https://github.com/paragonie/anti-csrf">Anti-CSRF</a>的库,并且:</p>
<ul>
<li>你可以使每个令牌只能使用一次,以防止重放攻击。
<ul>
<li>多个令牌存储在后端。</li>
<li>一旦令牌获取完,令牌会循环使用。</li>
</ul>
</li>
<li>每个令牌可以绑定特定的URL。
<ul>
<li>如果某个令牌泄露了,它不能在不同的上下文使用。</li>
</ul>
</li>
<li>令牌可以绑定特定的IP地址。</li>
<li>v2.1后,令牌可以重复使用(例如供Ajax使用)。</li>
</ul>
<p>如果你没有使用防止CSRF漏洞的框架,请将Anti-CSRF放在一边。在不久的将来,<a href="https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/">SameSite cookies将允许我们更简单地避免CSRF攻击</a>。</p>
<h5 id="xml-攻击-xxe-xpath-injection">XML 攻击 (XXE, XPath Injection)</h5>
<p>在处理大量XML的应用程序中存在两个主要的漏洞:</p>
<ul>
<li>XML External Entities (XXE)</li>
<li>XPath注入</li>
</ul>
<p><a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">除此之外</a>,XXE攻击可用作包含攻击代码的本地/远程文件的启动器。</p>
<p>早期版本的Google Docs被着名于XXE,但除了在很大程度上使用XML的商业应用程序之外,基本闻所未闻。</p>
<p>针对XXE袭击的主要缓解措施:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="nb">libxml_disable_entity_loader</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</code></pre></div></div>
<p>除XML文档外,<a href="https://www.owasp.org/index.php/XPATH_Injection">XPath注入</a>与SQL注入非常相似。</p>
<p>幸运的是,将用户输入传递给XPath查询的情况在PHP生态中非常罕见。</p>
<p>而不幸的是,这也意味着PHP生态中不存在可用的最佳避免措施(预编译和参数化XPath查询)。最好的办法是在任何涉及XPath查询的数据上设置允许使用的字符白名单。</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="k">declare</span><span class="p">(</span><span class="nx">strict_types</span><span class="o">=</span><span class="mi">1</span><span class="p">);</span>
<span class="kd">class</span> <span class="nc">SafeXPathEscaper</span>
<span class="p">{</span>
<span class="cd">/**
* @param string $input
* @return string
*/</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">allowAlphaNumeric</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$input</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nx">\preg_replace</span><span class="p">(</span><span class="s1">'#[^A-Za-z0-9]#'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$input</span><span class="p">);</span>
<span class="p">}</span>
<span class="cd">/**
* @param string $input
* @return string
*/</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">allowNumeric</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$input</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nx">\preg_replace</span><span class="p">(</span><span class="s1">'#[^0-9]#'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$input</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Usage:</span>
<span class="nv">$selected</span> <span class="o">=</span> <span class="nv">$xml</span><span class="o">-></span><span class="na">xpath</span><span class="p">(</span>
<span class="s2">"/user/username/"</span> <span class="o">.</span> <span class="nx">SafeXPathEscaper</span><span class="o">::</span><span class="na">allowAlphaNumeric</span><span class="p">(</span>
<span class="nv">$_GET</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span>
<span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>白名单总会比黑名单更安全。</p>
<h5 id="反序列化和php对象注入">反序列化和PHP对象注入</h5>
<blockquote>
<p>深入探究: <a href="https://paragonie.com/blog/2016/04/securely-implementing-de-serialization-in-php">在PHP中安全地实现(反)序列化</a></p>
</blockquote>
<p>如果你将不可信的数据传递给<code class="highlighter-rouge">unserialize()</code>,则通常是这两个结果之一:</p>
<ul>
<li>PHP对象注入,它能用于启动POP链(POP chain)并触发其他误用对象的漏洞。</li>
<li>PHP解释器本身的内存损坏。</li>
</ul>
<p>大多数开发人员更喜欢使用JSON序列化,这是对其软件安全状况的显著改进。但请记住,<a href="http://lukasmartinelli.ch/web/2014/11/17/php-dos-attack-revisited.html"><code class="highlighter-rouge">json_decode()</code>容易受到散列冲突拒绝服务(Hash-DoS)攻击</a>。不幸的是,<a href="https://bugs.php.net/bug.php?id=70644">PHP的Hash-DOS问题还没有得到彻底解决</a>。</p>
<p>从<code class="highlighter-rouge">djb33</code>迁移到<code class="highlighter-rouge">Siphash</code>,对于字符串输入,哈希输出的最高位设置为1,对于整数输入设置为0,使用<code class="highlighter-rouge">CSPRNG</code>提供的请求密钥将完全解决这些攻击。</p>
<p>不幸的是,PHP团队还没有准备好放弃他们已经在PHP 7系列中取得的性能提升,所以很难说服他们放弃djb33(这是非常快但不安全的) 赞成SipHash(这也是快速的,但不像djb33那么快,但更安全)。
如果性能受到重大影响,可能会阻碍未来版本的采用,但也影响了安全性。</p>
<p>因此,最好的办法是:</p>
<ul>
<li>使用<code class="highlighter-rouge">JSON</code>,因为它比<code class="highlighter-rouge">unserialize()</code>更安全。</li>
<li>在任何可能的地方,确保输入在反序列化之前被认证。
<ul>
<li>对于提供给用户的数据,通过一个只有服务器知道的秘钥使用<code class="highlighter-rouge">sodium_crypto_auth()</code>和<code class="highlighter-rouge">sodium_crypto_auth_verify()</code>验证。</li>
<li>对于第三方提供的数据,让他们使用<code class="highlighter-rouge">sodium_crypto_sign()</code>签名他们的JSON消息,然后使用<code class="highlighter-rouge">sodium_crypto_sign_open()</code>和第三方公钥验证消息。
<ul>
<li>如果你需要对传输的签名进行十六进制或Base64位编码,也可以使用分离的签名API。</li>
</ul>
</li>
</ul>
</li>
<li>如果你无法验证JSON字符串,请严格限制速度并阻止IP地址,以减轻重复的违规者。</li>
</ul>
<h5 id="密码散列">密码散列</h5>
<blockquote>
<p>深入探究:<a href="https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016">2016年,如何安全地保存用户密码</a></p>
</blockquote>
<p>安全的密码存储曾经是一个激烈争论的话题,但现在实现起来相当微不足道,特别是在PHP中:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="nv">$hash</span> <span class="o">=</span> <span class="nx">\password_hash</span><span class="p">(</span><span class="nv">$password</span><span class="p">,</span> <span class="nx">PASSWORD_DEFAULT</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">\password_verify</span><span class="p">(</span><span class="nv">$password</span><span class="p">,</span> <span class="nv">$hash</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// Authenticated.</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">\password_needs_rehash</span><span class="p">(</span><span class="nv">$hash</span><span class="p">,</span> <span class="nx">PASSWORD_DEFAULT</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// Rehash, update database.</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>你甚至不需要知道在后台使用什么算法,因为如果你使用最新版本的PHP,你也将使用当前最新的技术,用户的密码将会自动进行升级(只要有新的默认算法可用)。</p>
<p>无论你做什么,都<a href="https://paragonie.com/blog/2016/08/on-insecurity-popular-open-source-php-cms-platforms#wordpress-password-storage">不要做WordPress所做的事情</a>。</p>
<p>你可能会好奇,从PHP 5.5到7.2,默认算法是Bcrypt。在未来,它可能会切换到获得<a href="https://password-hashing.net/">密码哈希大赛冠军</a>的Argon2。</p>
<p>如果你以前没有使用<code class="highlighter-rouge">password_*</code> API,那需要迁移遗留哈希,请确保<a href="https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016#legacy-hashes">以这种方式进行</a>。
很多公司搞错了, 最有名的是<a href="https://www.theregister.co.uk/2016/12/15/yahoos_password_hash/">雅虎</a>。 最近,错误地实施传统哈希升级似乎导致了<a href="https://objective-see.com/blog/blog_0x24.html">苹果的iamroot错误</a>。</p>
<h5 id="通用加密">通用加密</h5>
<p>这是一些我们详细写了的话题:</p>
<ul>
<li><a href="https://paragonie.com/blog/2015/05/using-encryption-and-authentication-correctly">Using Encryption and Authentication Correctly</a> (2015)</li>
<li>Recommended: <a href="https://paragonie.com/blog/2015/11/choosing-right-cryptography-library-for-your-php-project-guide">Choosing the Right Cryptography Library for your PHP Project: A Guide</a> (2015)</li>
<li>Recommended: <a href="https://paragonie.com/blog/2015/08/you-wouldnt-base64-a-password-cryptography-decoded">You Wouldn’t Base64 a Password - Cryptography Decoded</a> (2015)</li>
<li><a href="https://paragonie.com/blog/2017/02/cryptographically-secure-php-development">Cryptographically Secure PHP Development</a> (2017)</li>
<li>Recommended: <a href="https://paragonie.com/blog/2017/06/libsodium-quick-reference-quick-comparison-similar-functions-and-which-one-use">Libsodium Quick Reference: Similarly-Named Functions and Their Use-Cases</a> (2017)</li>
</ul>
<p>一般来说,你总是希望使用Sodium cryptography library(libsodium)进行应用层加密
如果你需要支持早于7.2的PHP版本(像5.2.4),你可以使用<a href="https://github.com/paragonie/sodium_compat">sodium_compat</a>,基本上可以假设你的用户也是7.2。</p>
<p>在特定情况下,由于严格的算法选择和互操作性,你可能需要不同的库。
如有疑问,请咨询密码专家和密码工程师,了解密码选择是否安全(<a href="https://paragonie.com/services">这是我们提供的服务之一</a>)。</p>
<h5 id="随机性">随机性</h5>
<blockquote>
<p>深入探究:<a href="https://paragonie.com/blog/2015/07/how-safely-generate-random-strings-and-integers-in-php">如何在PHP中生成安全的整数和字符串?</a></p>
</blockquote>
<p>如果你需要随机数字,请使用<code class="highlighter-rouge">random_int()</code>。
如果你需要随机字节字符串,请使用<code class="highlighter-rouge">random_bytes()</code>。不要使用<code class="highlighter-rouge">mt_rand()</code>,<code class="highlighter-rouge">rand()</code>或<code class="highlighter-rouge">uniqid()</code>。</p>
<p>如果你需要从秘密种子生成伪随机数,而不是<code class="highlighter-rouge">srand()</code>或<code class="highlighter-rouge">mt_srand()</code>,请查阅<a href="https://github.com/paragonie/seedspring">SeedSpring</a>。</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="kn">use</span> <span class="nn">ParagonIE\SeedSpring\SeedSpring</span><span class="p">;</span>
<span class="nv">$seed</span> <span class="o">=</span> <span class="nb">random_bytes</span><span class="p">(</span><span class="mi">16</span><span class="p">);</span>
<span class="nv">$rng</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SeedSpring</span><span class="p">(</span><span class="nv">$seed</span><span class="p">);</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nv">$rng</span><span class="o">-></span><span class="na">getBytes</span><span class="p">(</span><span class="mi">1024</span><span class="p">);</span>
<span class="nv">$int</span> <span class="o">=</span> <span class="nv">$rng</span><span class="o">-></span><span class="na">getInt</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
</code></pre></div></div>
<h5 id="服务器端https请求">服务器端HTTPS请求</h5>
<blockquote>
<p>确保TLS证书验证没有被禁用</p>
</blockquote>
<p>随意使用你已经熟悉的任何兼容PSR-7的HTTP客户端。
我们喜欢Guzzle,有些人喜欢直接使用cURL。</p>
<p>无论你最终使用什么,请确保使用的确定性,以<a href="https://paragonie.com/blog/2017/10/certainty-automated-cacert-pem-management-for-php-software">确保始终可以拥有最新的CACert软件包</a>,
从而允许启用最严格的TLS证书验证设置并保护服务器的出站HTTPS请求。</p>
<p>安装Certainty很简单:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>composer require paragonie/certainty:^1
</code></pre></div></div>
<p>使用Certainty也很简单:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="kn">use</span> <span class="nn">ParagonIE\Certainty\RemoteFetch</span><span class="p">;</span>
<span class="nv">$latestCACertBundle</span> <span class="o">=</span> <span class="p">(</span><span class="k">new</span> <span class="nx">RemoteFetch</span><span class="p">())</span><span class="o">-></span><span class="na">getLatestBundle</span><span class="p">();</span>
<span class="c1"># cURL users:</span>
<span class="nv">$ch</span> <span class="o">=</span> <span class="nb">curl_init</span><span class="p">();</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$ch</span><span class="p">,</span> <span class="nx">CURLOPT_SSL_VERIFYHOST</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$ch</span><span class="p">,</span> <span class="nx">CURLOPT_SSL_VERIFYPEER</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$ch</span><span class="p">,</span> <span class="nx">CURLOPT_CAINFO</span><span class="p">,</span> <span class="nv">$latestCACertBundle</span><span class="o">-></span><span class="na">getFilePath</span><span class="p">());</span>
<span class="c1"># Guzzle users:</span>
<span class="cd">/** @var \GuzzleHttp\Client $http */</span>
<span class="nv">$repsonse</span> <span class="o">=</span> <span class="nv">$http</span><span class="o">-></span><span class="na">get</span><span class="p">(</span>
<span class="s1">'https://example.com'</span><span class="p">,</span>
<span class="p">[</span>
<span class="s1">'verify'</span> <span class="o">=></span> <span class="nv">$latestCACertBundle</span><span class="o">-></span><span class="na">getFilePath</span><span class="p">()</span>
<span class="p">]</span>
<span class="p">);</span>
</code></pre></div></div>
<p>这样可以保护你免受网络服务器与集成的任何第三方API之间的中间人攻击。</p>
<p><strong>我们真的需要Certainty吗?</strong></p>
<p>保护你的系统,Certainty并不是严格的要求。缺少它并不是什么漏洞。
但如果没有Certainty,开源软件必须猜测操作系统的CACert软件包的存在位置,如果猜测错误,它往往会失败并导致可用性问题。
从历史上看,这激励了许多开发人员只是禁用证书验证,以便他们的代码“正常工作”,却没有意识到他们只是将应用程序变成主动攻击。
Certainty通过将CACert捆绑在最新的可预测位置来消除这种激励。Certainty还为希望<a href="https://github.com/paragonie/certainty/blob/master/docs/features/LocalCACertBuilder.md">运行自己的内部CA</a>为企业提供大量的工具。</p>
<p><strong>谁禁用证书验证?</strong></p>
<p>流行的内容管理系统(WordPress,Magento等)的插件/扩展开发者!
这是我们试图在生态系统层面上解决的一个巨大的问题。
它不是孤立的任何特定的CMS,你会发现这些不安全的插件等都是类似的。</p>
<p>如果使用了类似的CMS,请在插件中搜索<code class="highlighter-rouge">CURLOPT_SSL_VERIFYPEER</code>和<code class="highlighter-rouge">CURLOPT_SSL_VERIFYHOST</code>,你可能会发现有几个将这些值设置为<code class="highlighter-rouge">FALSE</code>。</p>
<h5 id="避免的事情">避免的事情</h5>
<ul>
<li><a href="https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong">不要使用<code class="highlighter-rouge">mcrypt</code></a>。这是一个十多年来没有开发出来的密码学库。如果你遵循我们的PHP版本建议,这应该是一个容易避免的错误,因为<code class="highlighter-rouge">mcrypt</code>不再被PHP 7.2和更新的版本支持。</li>
<li><a href="https://paragonie.com/blog/2017/01/configuration-driven-php-security-advice-considered-harmful">配置驱动的安全建议</a>应该大部分地忽略。
如果你正在阅读PHP安全性指南,并告诉你更改php.ini设置而不是编写更好的代码,那么你可能正在阅读过时的建议。关闭窗口并转到一些和<code class="highlighter-rouge">register_globals</code>无关的文章上吧。</li>
<li><a href="https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid">不要使用JOSE(JWT,JWS,JWE)</a>,这是一套互联网标准,它编纂了一系列容易出错的密码设计。尽管由于某种原因,被写入了标准,也吸引了很多传道人。</li>
<li><a href="https://paragonie.com/blog/2015/09/comprehensive-guide-url-parameter-encryption-in-php">加密URL参数</a>是公司常用来模糊元数据的反模式(例如,我们有多少用户?)。
它带来了实施错误的高风险,也造成了错误的安全感。
我们在链接的文章中提出了一个更安全的选择。</li>
<li>除非迫不得已,否则<a href="https://paragonie.com/blog/2016/09/untangling-forget-me-knot-secure-account-recovery-made-simple">不要提供“我忘记了我的密码”的功能</a>。
不要讳言:密码重置功能是一个后门。
有一些方法可以实施以抵御合理的威胁模型,
但高风险用户应该不被考虑。</li>
<li><a href="https://paragonie.com/blog/2016/12/everything-you-know-about-public-key-encryption-in-php-is-wrong">避免使用RSA</a>,改用libsodium。如果你必须使用RSA,请确保指定OAEP填充。</li>
</ul>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cp"><?php</span>
<span class="nb">openssl_private_decrypt</span><span class="p">(</span>
<span class="nv">$ciphertext</span><span class="p">,</span>
<span class="nv">$decrypted</span><span class="p">,</span> <span class="c1">// Plaintext gets written to this variable upon success,</span>
<span class="nv">$privateKey</span><span class="p">,</span>
<span class="nx">OPENSSL_PKCS1_OAEP_PADDING</span> <span class="c1">// Important: DO NOT OMIT THIS!</span>
<span class="p">);</span>
</code></pre></div></div>
<p>如果你不得不使用PKCS#1 v1.5填充,那么无论你与哪个集成在一起,
几乎肯定会受到<a href="https://robotattack.org/">ROBOT</a>的影响,
请以允许明文泄露和签名伪造的漏洞将其报告给相应的供应商(或US-CERT)。</p>
<h4 id="专业用法">专业用法</h4>
<p>现在你已经掌握了在2018年及以后构建安全PHP应用程序的基础知识,接下来我们来看一些更专业的用法。</p>
<h5 id="可搜索的加密">可搜索的加密</h5>
<blockquote>
<p>深入探究:<a href="https://paragonie.com/blog/2017/05/building-searchable-encrypted-databases-with-php-and-sql">使用PHP和SQL构建可搜索的加密数据库</a></p>
</blockquote>
<p>可搜索的加密数据库是可取的,但被广泛认为是不太可能实现的。
上面链接的博客文章试图通过改进我们解决方案来实现,但本质上是这样的:</p>
<ul>
<li>设计你的架构,以便数据库(database compromise)不会让攻击者访问你的加密密钥。</li>
<li>用一个密钥加密数据。</li>
<li>基于HMAC或具有静态盐的安全KDF(secure KDF with a static salt)创建多个索引(具有自己独特的密钥)</li>
<li>可选:截断步骤3的输出,将其用作布隆过滤器(Bloom filter)</li>
<li>在SELECT查询中使用步骤3或4的输出</li>
<li>解密结果。</li>
</ul>
<p>在这个过程中的任何一步,你都可以根据实际使用情况进行不同的权衡。</p>
<h5 id="没有side-channels的基于令牌的身份验证">没有Side-Channels的基于令牌的身份验证</h5>
<blockquote>
<p>深入探究: <a href="https://paragonie.com/blog/2017/02/split-tokens-token-based-authentication-protocols-without-side-channels">Split Tokens: Token-Based Authentication Protocols without Side-Channels</a></p>
</blockquote>
<p>说到数据库(上一节),你是否知道SELECT查询理论上可能是定时信息泄漏的来源?</p>
<p>简单的缓解措施:</p>
<ul>
<li>把你的认证令牌分为两半</li>
<li>一半在SELECT查询中使用</li>
<li>后一半在恒定的时间(constant-time)验证
<ul>
<li>可以选择将后半部分的散列存储在数据库中。这对于只能使用一次的令牌是有意义的,例如 密码重置或“在此计算机上记住我”的令牌</li>
</ul>
</li>
</ul>
<p>即使可以使用定时泄漏来窃取一半的令牌,剩下的也需要暴力破解才能成功。</p>
<h5 id="开发安全的api">开发安全的API</h5>
<blockquote>
<p>深入探究: <a href="https://paragonie.com/blog/2017/06/hardening-your-php-powered-apis-with-sapient">Hardening Your PHP-Powered APIs with Sapient</a></p>
</blockquote>
<p>我们写了<a href="https://github.com/paragonie/sapient">SAPIENT</a>(the <strong>S</strong>ecure <strong>API</strong> <strong>EN</strong>gineering <strong>T</strong>oolkit),
让服务器到服务器验证的消息传递变得简单易行。
除了HTTPS提供的安全性之外,
<code class="highlighter-rouge">Sapient</code>允许你使用共享密钥或公钥来加密和验证消息。
这使得即使存在中间攻击者,并设有流氓证书颁发机构,
你也可以使用<code class="highlighter-rouge">Ed25519</code>对API请求和响应进行身份验证,
或者将消息加密到只能由接收方服务器的密钥解密的目标服务器。
由于每个HTTP消息体都通过安全密码进行身份验证,
所以可以安全地使用它来代替<code class="highlighter-rouge">stateful token juggling protocols</code>(例如 OAuth)。
但是,在密码学方面,在做任何不规范的事情之前,
总要确保他们的实现是由专家研究的。</p>
<p>所有<code class="highlighter-rouge">Sapient</code>使用的密码算法都由<code class="highlighter-rouge">Sodium cryptography library</code>提供。</p>
<p>进一步阅读:</p>
<ul>
<li><a href="https://github.com/paragonie/sapient/tree/master/docs">Sapient Documentation</a></li>
<li><a href="https://github.com/paragonie/sapient/blob/master/docs/Tutorial.md">Sapient Tutorial</a></li>
<li><a href="https://github.com/paragonie/sapient/blob/master/docs/Specification.md">Sapient Specification</a></li>
</ul>
<p><code class="highlighter-rouge">Paragon Initiative Enterprises</code>已经在其许多产品(包括许多开源软件项目)中使用了<code class="highlighter-rouge">Sapient</code>,
并将继续添加软件项目到<code class="highlighter-rouge">Sapient</code>用户群中。</p>
<h5 id="使用chronicle记录安全事件">使用Chronicle记录安全事件</h5>
<blockquote>
<p>深入探究: <a href="https://paragonie.com/blog/2017/07/chronicle-will-make-you-question-need-for-blockchain-technology">Chronicle Will Make You Question the Need for Blockchain Technology</a></p>
</blockquote>
<p><a href="https://github.com/paragonie/chronicle">Chronicle</a>是一个基于散列链数据结构的仅追加密码分类账(append-only cryptographic ledger),
具有很多吸引公司“区块链”技术的属性,而不会过分矫枉过正。</p>
<p>除了仅追加密码分类账(append-only cryptographic ledger)这个具有创造性的用例之外,
<code class="highlighter-rouge">Chronicle</code>集成到SIEM中时,也可以十分有亮点,
因为你可以将安全关键事件发送到私人<code class="highlighter-rouge">Chronicle</code>中,并且它们是不能被改变的。</p>
<p>如果你的<code class="highlighter-rouge">Chronicle</code>设置为将其摘要散列交叉签名到其他<code class="highlighter-rouge">Chronicle</code>实例,
或者如果有其他实例配置为复制你的<code class="highlighter-rouge">Chronicle</code>内容,攻击者就很难篡改你的安全事件日志。</p>
<p>在<code class="highlighter-rouge">Chronicle</code>的帮助下,你可以获得区块链所承诺的弹性特性(resilience),而没有任何隐私,性能或可伸缩性问题。</p>
<p>要将数据发布到本地<code class="highlighter-rouge">Chronicle</code>,你可以使用任何与<a href="https://paragonie.com/blog/2017/12/2018-guide-building-secure-php-software#secure-api-sapient">Sapient-compatible API</a>,
但最简单的解决方案称为<a href="https://github.com/paragonie/quill">Quill</a>。</p>
<h4 id="作者的一些话">作者的一些话</h4>
<p>一些聪明的读者可能注意到我们引用了很多我们自己的工作(博客文章和开源软件),当然也不仅仅引用了我们自己的工作。</p>
<p>这绝不是偶然的。</p>
<p>自从我们在2015年初成立以来,一直在编写安全库并参与提高PHP生态系统安全性的工作。
我们已经涉足了很多领域,而且我们的安全工程师(他们最近推动了更安全的加密技术加入PHP核心,就在最近的PHP 7.2中)
自我担保地说,并不擅长自我炒作,或是对已经做过的工作持续热情。
你很可能没有听说我们多年来开发的工具或库。对于这个,深感抱歉。</p>
<p>但是,我们也不可能成为各方面的先行者,所以我们尽可能地选择与重视公共利益而不是贪图小利的行业专家工作。
这也是为什么浏览器安全的许多章节都参考了<a href="https://scotthelme.co.uk/">Scott Helme</a>和公司的工作,
他们在为开发人员提供这些新的安全功能方面具有可访问性和可理解性。</p>
<p>本指南当然不会是详尽的。
编写不安全代码的方法几乎和编写代码的方法一样多。
<strong>安全是一种心态,而不是目的地。</strong>
随着上面所写的一切,以及后面涉及的资源,我们希望这将有助于全世界的开发人员,
从今天开始用PHP编写安全的软件。</p>
<h4 id="资源">资源</h4>
<p>如果你已经按照本页上的所有内容进行了操作,
并且需要更多内容,则可能会对我们策划的阅读列表感兴趣,以便学习应用程序安全性。</p>
<p>如果你认为自己编写的代码足够安全,并希望我们从安全工程师的角度对其进行评判,
这也是我们为客户提供的服务。</p>
<p>你如果为一家要进行合规性测试(PCI-DSS,ISO 27001等)的公司工作,可能还想聘请我们公司来审核你的源代码。
我们的流程比其他安全咨询公司更适合开发者。</p>
<p>接下来是PHP和信息安全社区提供的资源列表,这些资源帮助互联网更加安全。</p>
<ul>
<li>
<p><a href="http://www.phptherightway.com/">PHP: The Right Way</a>:现代PHP开发的实用指南,免费在线。</p>
</li>
<li>
<p><a href="https://mozilla.github.io/server-side-tls/ssl-config-generator/">Mozilla’s SSL Config Generator</a></p>
</li>
<li>
<p><a href="https://letsencrypt.org/">Let’s Encrypt</a>:证书颁发机构,通过提供免费TLS证书,为创建更安全的Internet做了很多。</p>
</li>
<li>
<p><a href="https://www.ssllabs.com/ssltest">Qualys SSL Labs</a>:为TLS配置提供了一个快速而简单的测试套件。
几乎每个人都使用这个来解决他们的密码组和证书问题,理由很充分:<strong>It does its job well.</strong></p>
</li>
<li>
<p><a href="https://securityheaders.io/">Security Headers</a>:可以检验你的网站在使用浏览器安全功能来保护用户方面的表现如何。</p>
</li>
<li>
<p><a href="https://report-uri.com/">Report-URI</a>:一个很好的免费资源,提供监控 CSP/HPKP 等安全策略的实时安全报告服务。
他们给你一个Report-URI,你可以传递给你的用户的浏览器,
如果有什么事情发生或有人发现XSS攻击媒介,他们会投诉Report-URI。 Report-URI会汇总这些错误,
并允许你更好地对这些报告进行疑难解答和分类。</p>
</li>
<li>
<p><a href="https://www.ripstech.com/php-security-calendar-2017">PHP Security Advent Calenda</a>:<a href="https://www.ripstech.com/">RIPSTech</a>旗下的团队负责。</p>
</li>
<li>
<p><a href="https://snuffleupagus.readthedocs.io/">Snuffleupagus</a>:一个面向安全的PHP模块(<a href="https://github.com/sektioneins/suhosin">Suhosin</a>的精神继承者,似乎在很大程度上会被放弃)</p>
</li>
<li>
<p><a href="https://phpdelusions.net/">PHP Delusions</a>:一个致力于更好地使用PHP的网站。
大部分的口吻是非常有见地的,
作者对技术的准确性和清晰度的奉献使得值得一读,特别是对于那些不太喜欢PDO功能的人来说。</p>
</li>
<li>
<p><a href="https://haveibeenpwned.com/">Have I Been Pwned?</a>:帮助用户发现他们的数据是否属于过时数据泄露。</p>
</li>
</ul>
<h4 id="结尾">结尾</h4>
<p>原文地址:<a href="https://paragonie.com/blog/2017/12/2018-guide-building-secure-php-software">The 2018 Guide to Building Secure PHP Software - P.I.E. Staff</a></p>
<p>为避免歧义,部分专业名词和语句保留了原文。翻译过程借助了Google和Google翻译,本人英文和相关专业水平有限,如有错误感谢指出修正。</p>
<p>PHP可以说是一门受争论的语言,有时候也羞愧于自己都大四了还纠结于该问题,迷茫不定。看了作者的努力,特别从<strong>作者的一些话</strong>中感受到,
原来有很多人在为改善PHP生态努力,有点感动,多了些坚定。</p>
<p>共勉吧!</p>
Sun, 24 Dec 2017 19:46:01 +0800
https://littlesqx.com/2017/12/24/the-2018-guide-to-building-secure-php-software/
https://littlesqx.com/2017/12/24/the-2018-guide-to-building-secure-php-software/
PHP
翻译
-
you know, for search - ElasticSearch 学习笔记
<h3 id="elasticsearch-是什么-">ElasticSearch 是什么 ?</h3>
<p><strong>怒抄一波介绍:</strong></p>
<blockquote>
<p>Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。
<br /><br />
不过,Elasticsearch不仅仅是Lucene和全文搜索,我们还能这样去描述它:</p>
<ul>
<li>分布式的实时文件存储,每个字段都被索引并可被搜索</li>
<li>分布式的实时分析搜索引擎</li>
<li>可以扩展到上百台服务器,处理PB级结构化或非结构化数据</li>
</ul>
<p>而且,所有的这些功能被集成到一个服务里面,你的应用可以通过简单的RESTful API、各种语言的客户端甚至命令行与之交互。</p>
</blockquote>
<p><strong>高大上的感觉,没关系,来个与传统关系型数据库的类比:</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">Relational DB</th>
<th style="text-align: left">ElasticSearch</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">Databases</td>
<td style="text-align: left">Indices</td>
</tr>
<tr>
<td style="text-align: left">Tables</td>
<td style="text-align: left">Types</td>
</tr>
<tr>
<td style="text-align: left">Rows</td>
<td style="text-align: left">Documents</td>
</tr>
<tr>
<td style="text-align: left">Columns</td>
<td style="text-align: left">Fields</td>
</tr>
<tr>
<td style="text-align: left">Schemas</td>
<td style="text-align: left">Mappings</td>
</tr>
<tr>
<td style="text-align: left">Index</td>
<td style="text-align: left">Everything is indexed</td>
</tr>
<tr>
<td style="text-align: left">SQL(DB Client)</td>
<td style="text-align: left">Query DSL (RESTful API / Java API)</td>
</tr>
</tbody>
</table>
<p><strong>这在比较多关于ElasticSearch的文章也提到过,甚至包括官方的文档。虽然很能帮助入门理解,然而,官方逐渐意识到了这是一个 bad analogy。</strong></p>
<blockquote>
<p>This was a bad analogy that led to incorrect assumptions. In an SQL database, tables are independent of each other. The columns in one table have no bearing on columns with the same name in another table. This is not the case for fields in a mapping type.
<br /><br />
In an Elasticsearch index, fields that have the same name in different mapping types are backed by the same Lucene field internally.</p>
</blockquote>
<p><strong>理解这段话之前,先理解ElasticSearch里的各种概念</strong></p>
<p><strong>1# 索引 (Index)</strong>
**
拥有相似特征的文档的集合。</p>
<p><strong>2# 类型 (Type)</strong></p>
<p><del>在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组相同字段的文档定义一个类型。</del> 删除线的原因后面补充。</p>
<p><strong>3# 文档 (Document)</strong></p>
<p>一个文档是一个可被索引的基础信息单元。文档以JSON格式来表示。</p>
<p><strong>4# 映射 (Mapping)</strong></p>
<p><del>映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型(string, number, booleans, date等)。此外,映射机制也可以给字段绑定使用的字段格式和分析器(analyzer)。</del> 删除原因同上。</p>
<p><strong>5# 近实时 (Near Real Time, NRT)</strong></p>
<p>Elasticsearch 是一个接近实时的搜索平台。从索引一个文档直到这个文档能够被搜索到有一个很小的延迟(通常是 1 秒)。</p>
<p><strong>6# 集群 (Cluster)</strong></p>
<p>一个集群就是由一个或多个节点组织在一起, 它们共同持有你全部的数据, 并一起提供索引和搜索功能。</p>
<p><strong>7# 节点 (Node)</strong></p>
<p>一个节点是你集群中的一个服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。</p>
<p><strong>8# 分片和复制 (Shards and Replicas)</strong></p>
<p>Elasticsearch提供了将索引划分成多片的能力,这些片叫做 <strong>分片</strong> 。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引” 可以被放置到集群中的任何节点上。</p>
<p>分片之所以重要,主要有两方面的原因:</p>
<ul>
<li>允许你水平分割/扩展你的内容容量</li>
<li>允许你在分片(位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量</li>
</ul>
<p>Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫 <strong>复制</strong> 。</p>
<p>复制之所以重要,有两个主要原因:</p>
<ul>
<li>在分片/节点失败的情况下,复制提供了高可用性。复制分片不与原/主要分片置于同一节点上是非常重要的。</li>
<li>因为搜索可以在所有的复制上并行运行,复制可以扩展你的搜索量/吞吐量</li>
</ul>
<p>总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(即没有复制) 或多次。一旦复制了,每个索引就有了主分片(作为复制源的分片)和复制分片(主分片的拷贝)。 分片和复制的数量可以在索引创建的时候指定。<strong>在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你不能再改变分片的数量。</strong></p>
<p><strong>补充删除线的原因</strong></p>
<p>官方文档 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/removal-of-types.html">Elasticsearch Reference [6.0] » Mapping » Removal of mapping types</a> 除了提到了类比传统关系型数据库是一个bad analogy,还提到了关于映射和类型的未来。甚至给出了一个移除时间表。一句话总结,其实自5.6版本后,行动就开始了,现在最新的6.x版本已经不支持一个索引里有多种类型,类型(Type)已经名存实亡,后面的版本将逐步移除映射和类型。</p>
<h3 id="为什么使用elasticsearch-">为什么使用ElasticSearch ?</h3>
<p>提这么一个小结,对于我这种萌新有点逞强。查阅了资料可以从他的优势和场景回答。</p>
<p>优势:部署和基本使用十分简单,查询快速,性能优越,可扩展,高可用…</p>
<p>场景:搜索、日志、统计、分析、监控…</p>
<p>为什么ElasticSearch那么快,可以参考 <a href="https://neway6655.github.io/elasticsearch/2015/09/11/elasticsearch-study-notes.html#reference">ElasticSearch学习笔记 - Neway</a></p>
<p>自己看完后不是很懂,大概了解到这几个关键点:</p>
<p><strong>Everything is indexed</strong>、<strong>倒排索引</strong>、<strong>利用内存计算</strong>、<strong>FST(Finite State Transducers)压缩技术</strong></p>
<h3 id="怎么使用elasticsearch-">怎么使用ElasticSearch ?</h3>
<p><strong>下载和安装</strong></p>
<p>请到<a href="https://www.elastic.co/">官方</a>下载。</p>
<p><strong>配置</strong></p>
<p>服务器配置,有几点需要注意:</p>
<p>1# 不能root账号执行,显然是出于安全考虑。解决办法是新建ElasticSearch用户组和用户。</p>
<p>2# Linux用户最大使用进程数。 参考 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/setting-system-settings.html">system-settings</a></p>
<p>3# vm-max-map-count。参考 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/vm-max-map-count.html">vm-max-map-count</a></p>
<p>4# jvm 内存限制。官方建议设置为机身最大内存的一半,但不超过32G。参考 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/heap-size.html">heap-size</a></p>
<p>重要的ElasticSearch配置。 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/important-settings.html">important-settings</a></p>
<p><strong>使用</strong></p>
<p>JAVA API 和 RESTful API。推荐后者,详细见官方文档。</p>
<h3 id="参考文章">参考文章</h3>
<ul>
<li>
<p><a href="https://endymecy.gitbooks.io/elasticsearch-guide-chinese/content/index.html">ElasticSearch中文开发指南</a></p>
</li>
<li>
<p><a href="https://www.gitbook.com/book/looly/elasticsearch-the-definitive-guide-cn/details">ElasticSearch权威指南</a></p>
</li>
<li>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/index.html">Elasticsearch Reference 6.0</a></p>
</li>
<li>
<p><a href="http://www.javajh.com/rest/article/297e9e7954952e6d0154f8b998090013.html">Elasticsearch 和 传统关系型数据库的对比</a></p>
</li>
<li>
<p><a href="https://neway6655.github.io/elasticsearch/2015/09/11/elasticsearch-study-notes.html#reference">ElasticSearch学习笔记 - Neway</a></p>
</li>
</ul>
Fri, 08 Dec 2017 17:05:01 +0800
https://littlesqx.com/2017/12/08/elasticsearch-learn-notes-1/
https://littlesqx.com/2017/12/08/elasticsearch-learn-notes-1/
ElasticSearch
-
Hello World!
<blockquote>
<p>keep!</p>
</blockquote>
Sat, 16 Sep 2017 18:58:01 +0800
https://littlesqx.com/2017/09/16/hello-world/
https://littlesqx.com/2017/09/16/hello-world/