1.5 安全指南

请在 2019 年使用 PHP 7.2, 并且计划 2019 中期切换到 PHP 7.3 到目前为止(2018-01-19),PHP 7.2 已经发布快两个月了。 7.1 以下版本已进入 LTS 养老末期,使用低版本的 PHP 意味着没有官方的安全维护, 也无法使用新特性和更快的速度(例如 PHP 内置加密操作 PASSWORD_HASH() 仅支持 PHP >= 5.5 , 和大多数软件一样,PHP 通常会提供 LTS 长期支持和维护, 但是因为无法知道安全补丁的版本号,不像 Windows 操作系统一样,安全补丁有据可查, 这会使得产品的安全性大大降低。 因此,如果你是新开的项目,请使用 PHP 最新版本,而过旧的项目也要尝试重构更新。

依赖管理

Composer 安装、配置及使用请参考本书 第二章:2.3 开发环境搭建

Composer: https://getcomposer.org/ 在 PHP 生态中是重要的一环,大部分的扩展包都是通过 Composer 进行管理。 如果你是手动下载扩展包并进行手动加载,那么在大型应用上,你无法检测某个框架是否过时, 也无法知晓某个框架的重大安全漏洞并及时更新到最新版本,过老的版本是非常不安全的。

HTTPS 和浏览器安全

HTTPS,一种通过计算机网络进行安全通信的传输协议 在 2018 年,所有现代安全的浏览器已经不在接受 HTTP,用户打开 HTTP 协议的网站时, 浏览器会警告该网站的连接不安全。 不过你可以从各大云厂商免费的申请一个 TLS 证书, 或者使用 Let's Encrypt certificate authority: https://letsencrypt.org/ .

开发安全的 PHP 程序

避免 PHP 程序存在 SQL 注入 如果你是自己编写 SQL 代码, 请确保使用 prepared 语句, 并且从网络或文件系统提供的信息都作为参数传递, 而不是字符串拼接的形式。 此外,确保你没有使用 模拟的 prepared 语句: https://stackoverflow.com/questions/134099 .

文件上传处理

接受未知来源的问题,意味着这些文件可能恶意攻击服务器, 请保证服务器文件权限不超过 775, 上传的新文件应该是 只读或读写, 不要在网站根目录保存文件, 同大多数 PHP 框架一样,不在根目录运行 index.php 目的是为恶意代码提供最少的执行环境, 例如:

/var/www/example.com-uploaded/

或者直接将文件往下移动一个层级

/var/www/example.com/public

跨站请求伪造

跨站请求伪造(CSRF)是一种混淆的代理攻击,通过诱导用户的浏览器代表攻击者执行恶意的 HTTP 请求(使用的是该用户的权限). 首先使用 HTTPS,没有 HTTPS 的话,任何保护都是脆弱的,因为所有数据都是明文传输。 如果你是用的是现代 PHP 框架,请在框架中开启 CSRF 验证 如果你是原生用户,只需要:

  • 增加基本的 Challenge-response authentication。

  • 为每个表单添加一个隐藏的表单属性。

  • 填充一个密码安全的随机值(称为令牌).

  • 验证是否提供了隐藏的表单属性,以及是否匹配上期望值。

密码散列

安全的密码存储曾经是一个激烈争论的话题,但现在实现起来相当微不足道

$hash = password_hash($password, PASSWORD_DEFAULT);
if (password_verify($password, $hash)) {
// Authenticated.
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
// Rehash, update database.
}
}

甚至不需要知道在后台使用什么算法,因为如果你使用最新版本的 PHP ,你也将使用当前最新的技术,用户的密码将会自动进行升级(只要有新的默认算法可用). 无论你做什么,都不要做 WordPress 所做的事情 WordPress 并不使用 bcrypt 来储存密码,而是使用 MD5, 要理解为什么,首先查看 这个代码片段 以及 这一个 如果 $this->portable_hashes 设置为TRUE,则会调用 $this->crypt_private() 因此,HashPassword 可以大大简化为以下片段:

function HashPassword($password)
{
if ( strlen( $password ) > 4096 ) {
return '*';
}
/* these steps are skipped */
$random = $this->get_random_bytes(6);
$hash =
$this->crypt_private($password,
$this->gensalt_private($random));
if (strlen($hash) == 34)
return $hash;
return '*';
}