11. 安全

未匹配的标注

我找到的关于PHP安全性的最佳资源是由 Paragon Initiative 公司编写的 2018年 PHP 系统安全构架指南

Web 应用程序安全

对于每个PHP开发人员来说,学习 web应用程序安全基础 是非常重要的,其内容可拆分为几个较大的主题:

  1. 代码与数据分离
    • 当数据可以当做代码执行时,你将遭遇SQL注入、跨站点脚本、本地/远程文件包含漏洞等攻击。
    • 当代码允许以数据形式打印时,会出现信息泄漏(例如泄露源代码,或者在C程序中,有足够的信息绕过 地址空间布局随机化(ASLR) 机制)
  2. 应用程序逻辑
    • 缺少身份验证或授权控制。
    • 输入校验。
  3. 操作环境
    • PHP 版本
    • 第三方类库
    • 操作系统
  4. 加密缺陷

攻击者无时无刻不在准备对你的 Web 应用程序进行攻击,采取必要的预防措施来加强 Web 应用程序的安全性是很重要的。幸运的是,来自 开放式 Web 应用程序安全项目 (OWASP) 的有心人已经整理了一份包含了已知安全问题和防御方式的详尽的清单。对于具有安全意识的开发者来说这是必读的资料。由 Padraic Brady 编写的 生存手册:PHP 安全 也是一份很不错的 PHP web 应用程序安全指南。

密码哈希

每个人最终都会构建一个依赖于用户登录的 PHP 应用。用户名和密码存储在数据库中,稍后用于在登录时对用户进行身份验证。

在存储密码之前,适当地对密码进行 哈希 处理至关重要。哈希和加密是经常混淆的 两个截然不同的

哈希是不可逆的单向函数。哈希会生成一个固定长度的字符串,该字符串不能回溯为原串。这意味着你可以比较两个哈希串,以此判断它们是不是来自同一个原串,但无法得到原串。如果未对密码进行哈希处理,并且数据库允许第三方未授权访问,那么所有的用户账户都面临危险。

与哈希不同,加密是可逆的(前提是你有密钥)。加密在其他领域很有用,但在密码存储上就有些捉襟见肘。

密码也应该分别 加盐,也就是在每个密码哈希处理之前拼接一个随机字符串。这可以防止使用「彩虹表」(常见密码的哈希反向列表)进行字典攻击。

哈希和加盐非常重要,因为用户经常在多个网站使用相同的密码,而且密码质量很差。

此外,你还应该使用 一个专用的密码哈希算法,而不是使用通用快速的哈希函数(例如 SHA256)。可以使用的密码哈希算法(截止2018年6月):

  • Argon2 (PHP 7.2 以及更新的版本中可用)
  • Scrypt
  • Bcrypt (PHP 提供了此算法,见下文)
  • 带有 HMAC-SHA256 或 HMAC-SHA512 的 PBKDF2

幸运的是,现在 PHP 将这些都变得更简单了。

使用 password_hash 来哈希密码

password_hash 函数在 PHP 5.5 时被引入。 此函数现在使用的是目前 PHP 所支持的最强大的加密算法 BCrypt 。 当然,此函数未来会支持更多的加密算法。 password_compat 库的出现是为了提供对 PHP >= 5.3.7 版本的支持。

在下面例子中,我们哈希一个字符串,然后和新的哈希值对比。因为我们使用的两个字符串是不同的('secret-password' 与 'bad-password'),所以登录失败。

<?php
require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // 密码正确
} else {
    // 密码错误
}

password_hash() 已经帮你进行了加盐处理。加进去的随机子串通过加密算法自动保存着,成为哈希的一部分。password_verify() 会从中提取随机子串,所以你不必使用另一个数据库来记录这些随机子串。

数据过滤

永远不要信任外部输入。请在使用外部输入前进行过滤和验证。filter_var() 和 filter_input() 函数可以过滤文本并对格式进行校验(例如 email 地址)。

外部输入可以是任何东西:$_GET 和 $_POST 等表单输入数据、$_SERVER 超全局变量中的某些值、还有通过 fopen('php://input', 'r') 得到的 HTTP 请求体。记住,外部输入的定义并不局限于用户通过表单提交的数据。上传和下载的文档,session 值,cookie 数据,还有来自第三方 web 服务的数据,这些都是外部输入。

虽然外部输入可以被存储、组合并在以后继续使用,但它依旧是外部输入。每次你处理、输出、连结或在代码中包含时,请提醒自己检查数据是否已经安全地完成了过滤。

数据可以根据不同的目的进行不同的 过滤 。比如,当未经过滤的外部输入被传入到了 HTML 页面的输出当中,它可以在你的站点上执行 HTML 和 JavaScript 脚本!这属于跨站脚本攻击(XSS),是一种很有杀伤力的攻击方式。一种避免 XSS 攻击的方法是在输出到页面前对所有用户生成的数据进行清理,使用 strip_tags() 函数来去除 HTML 标签或者使用 htmlentities() 或是 htmlspecialchars() 函数来对特殊字符分别进行转义从而得到各自的 HTML 实体。

另一个例子是传入能够在命令行中执行的选项。这是非常危险的(同时也是一个不好的做法),但是你可以使用自带的 escapeshellarg() 函数来过滤执行命令的参数。

最后的一个例子是接受外部输入来从文件系统中加载文件。这可以通过将文件名修改为文件路径来进行利用。你需要过滤掉"/""../"null 字符,或者其他文件路径的字符,以此来确保不会去加载隐藏、私有或者敏感的文件。

数据清理

数据清理是指删除(或转义)外部输入中的非法和不安全的字符。

例如,你需要在将外部输入包含在 HTML 中或者插入到原始的 SQL 请求之前对它进行过滤。当你使用 PDO 中的变量绑定功能时,它会自动为你完成过滤的工作。

有些时候你可能需要允许一些安全的 HTML 标签输入进来并被包含在输出的 HTML 页面中,但这实现起来并不容易。尽管有一些像 HTML Purifier 的白名单类库为了这个原因而出现,实际上更多的人通过使用其他更加严格的格式限制方式例如使用 Markdown 或 BBCode 来避免出现问题。

查看 Sanitization Filters

反序列化 Unserialization

使用 unserialize() 从用户或者其他不可信的渠道中提取数据是非常危险的事情。这样做会触发恶意实例化对象(包含用户定义的属性),即使对象没用被使用,也会触发运行对象的析构函数。所以你应该避免从不可信渠道反序列化数据。

如果你必须这样做,请你使用 PHP 7 的 allowed_classes 选项来限制反序列化的对象类型。

有效性验证

验证是来确保外部输入的是你所想要的内容。比如,你也许需要在处理注册申请时验证 email 地址、手机号码或者年龄等信息的有效性。

查看 Validation Filters

配置文件

当你在为你的应用程序创建配置文件时,最好的选择时参照以下的做法:

  • 推荐你将你的配置信息存储在无法被直接读取和上传的位置上。
  • 如果你一定要存储配置文件在根目录下,那么请使用 .php 的扩展名来进行命名。这将可以确保即使脚本被直接访问到,它也不会被以明文的形式输出出来。
  • 配置文件中的信息需要被针对性的保护起来,对其进行加密或者设置访问权限。
  • 建议不要把敏感信息如密码或者 API 令牌放到版本控制器中。

注册全局变量

注意: register_globals 设置在 PHP 5.4.0 中已经被删除,无法再使用了。这仅在任何升级旧版本程序的过程中作为警告显示。

启用后,register_globals 配置会在应用程序的全局范围内提供几种类型的变量(包括 $_POST$_GET 和 $_REQUEST)。这很容易导致安全问题,因为你的应用程序无法有效地判断数据来自哪里。

举个例子:$_GET['foo'] 可以通过 $foo 被访问到,也就是说会覆盖已经定义的变量。

如果你正在使用小于 5.4.0 的 PHP,请确保 register_globals关闭的

错误报告

错误日志在查找应用程序中的问题时十分有用,但它也会将应用程序的结构信息暴露给外部。为了有效地保护应用程序不受消息输出可能导致的问题的影响,你需要在开发和生产环境下对服务器进行不同的配置。

开发环境

想要在开发过程中显示每一个可能出现的问题,这样配置你的 php.ini

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

传入 -1 会显示所有可能的错误,甚至包括在未来的 PHP 版本中新增加的类型和参数。 从 PHP 5.4 开始, 使用 E_ALL 也是一样的。- php.net

E_STRICT类型的错误级别常量是在 5.3.0 中引入的,然而一开始并没有被包含在 E_ALL 中。直到 5.4.0 开始它才被包含进了 E_ALL 。这代表着什么呢?这意味着,在 5.3 中你需要使用 -1 或者 E_ALL | E_STRICT,才能显示所有的错误信息。

不同 PHP 版本下如何显示全部错误

  • < 5.3 -1 或 E_ALL
  •   5.3 -1 或 E_ALL | E_STRICT
  • > 5.3 -1 或 E_ALL

生产环境

要隐藏 生产 环境中的错误信息,请将php.ini配置为:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

在生产环境中使用这些配置时,错误仍会记录到 web 服务器的错误日志中,但不会显示给用户。更多关于配置的信息,请参考 PHP 手册:

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~