

有安全研究者混入了PHP 8.0开发组!
source link: https://mp.weixin.qq.com/s/0HSAPYY2PjbwEN3MhI4SkA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

经历了近半年的alpha版本测试后,PHP在2020年11月26号正式发布了8.0版本: https://www.php.net/releases/8.0/en.php
今天我们就来浏览一下PHP 8.0中出现的主要特性,以及它给我们安全研究人员带来的挑战。
命名参数 Named Arguments
PHP 8 以前,如果我们需要给一个函数的第N个参数传参,那么这个参数前面的所有参数,我们都需要传参。但是实际上有些参数是具有默认值的,这样做显得多此一举。
比如,我们要给 htmlspecialchars
的第4个参数传递 false
,在PHP 8 以前需要传入4个参数:
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
在8.0以后增加了命名参数,我们只需要传递必需的参数和命名参数即可,方便了很多:
htmlspecialchars($string, double_encode: false);
属性注释 Attributes
属性注释是我自己取得名字,在英文原文中是单词 「Attributes」 (在C++、C#、Rust里也是相同的单词,但翻译有些差别)。这个新语法有点类似Python里的修饰器,以及Java里的Annotation。
但是,PHP里Attributes的作用还是更偏向于替换以前的doc-block,用于给一个类或函数增加元信息,而不是类似Python的修饰器那样,可以动态地劫持函数的输入与输出。
属性注释的简单例子:
#[ListensTo('error')]
function onerror() {
// do something
}
上面这个例子实际测试你会发现,属性注释里的东西也真的只是一个注释,执行上述的代码也不会去调用ListensTo类。这也印证了上面所说的,Attributes只是对以前doc-block的一个接纳,而非创造了一种HOOK函数的方式。
如果你需要执行Attributes里面的代码,仍然需要通过反射来做到,比如:
#[Attribute]
class ListensTo {
public string $event;
function __construct($event)
{
$this->event = $event;
}
}
#[ListensTo('error')]
function onerror()
{
// do something
}
$listeners = [];
$f = new ReflectionFunction('onerror');
foreach($f->getAttributes() as $attribute) {
$listener = $attribute->newInstance();
$listeners[$listener->event] = $f;
}
我模拟了一个设计模式中监听模式的事件处理方法注册过程,相比于以前解析Doc-Block的过程,这个流程要更加简单。
相比于其他的新特性,框架或IDE的设计者可能会研究的更深,普通开发者只需要按照框架的文档简单使用这个语法即可。
构造器属性提升 Constructor property promotion
这是一个利国利民的好特性,可以延长键盘的寿命……PHP 8以前,我们定义一个类时,可能会从构造函数里接收大量参数并赋值给类属性,如:
class Point {
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
实际上这已经形成了一种范式,我们要不厌其烦地进行定义->传递->赋值的过程。PHP 8以后给出了一种更加简单的语法:
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
直接在构造函数的参数列表位置完成了类属性的定义与赋值的过程,减少了大概三分之二的代码量。
另外提一句,这个RFC的作者是Nikita Popov,也就是著名的开源项目PHP-Parser的作者,做PHP代码分析的同学应该经常和这个项目打交道。他今年去了PHPStorm团队,相信这个老牌IDE在Nikita的加持下会变得更加好用。
联合类型 Union types
PHP 8 以前的Type Hinting,只支持使用一个具体的Type,比如:
function sample(array $data) {
var_dump($data);
}
这个功能鸡肋的一点是,有些地方接受参数类型可能有多个类型,或者支持传入null。
在7.1时解决了null的问题:
function sample(?array $data) {
var_dump($data);
}
但是仍然无法指定多个类型hint。
PHP 8 中总算支持了Union types,我们可以通过 |
来指定多个类型Hint了:
function sample(array|string|null $data) {
var_dump($data);
}
Match 语法
这是一个新的关键字 match
,这也是一个利国利民的好特性,又一次延长了键盘的寿命……
在PHP 8.0以前,我们要根据一个名字来获取一个值,通常需要借助switch或者数组,比如:
switch ($extension) {
case 'gif':
$content_type = "image/gif";
break;
case 'jpg':
$content_type = "image/jpeg";
break;
case 'png':
$content_type = "image/png";
break;
}
echo $content_type;
现在可以简化成一个 「表达式」 :
echo match ($extension) {
'gif' => "image/gif",
'jpg' => "image/jpeg",
'png' => "image/png"
};
Null安全的操作符 Nullsafe operator
这又又又是一个利国利民的好特性,又又又一次延长了键盘的寿命……
在PHP 8以前,如果封装的较多,我们经常出现一种情况:一个函数接受X对象,但又可能是null,此时我在使用X对象属性前,就需要对null进行判断,以免出现错误。
在对象较多时,容易出现多层嵌套判断的情况,比如:
$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
PHP 8 以后增加了一个新语法: ?->
,非常类似于PHP7里引入的 ??
。就是在取属性前,PHP会对对象进行判断,如果对象是null,那么就直接返回null了,不再取其属性:
$country = $session?->user?->getAddress()?->country;
字符串数字弱类型比较优化
这一个改动可能会对安全漏洞挖掘的影响较大。PHP 8 以前,在使用 ==
比较或任何有弱类型转换的情况时,字符串都会先转换成数字,再和数字进行比较。
比如,这个代码在PHP 8以前的结果是true和0,在PHP 8以后得到的则是false和1:
var_dump('a' == 0);
switch ('a') {
case 0:
echo 0;
break;
default:
echo 1;
break;
}
老的弱类型可能会有什么安全问题呢?我曾经挖掘到的一个真实案例,大概代码是这样:
$type = $_REQUEST['type'];
switch ($type) {
case 1:
$sql = "SELECT * FROM `type_one` WHERE `type` = {$type}";
break;
case 2:
$sql = "SELECT * FROM `type_two` WHERE `type` = {$type}";
break;
default:
$sql = "SELECT * FROM `type_default`";
break;
}
开发者认为 $type
是1和2的时候才会进入SQL语句拼接中,但实际我们传入 1 and 1=2
即可进入 case 1
,导致SQL注入漏洞。
PHP 8以后彻底杜绝了这种漏洞的产生。
内部函数严格参数检查
在PHP 8 以前,如果我们使用内部函数时传入的参数有误(比如,参数类型错误,参数取值错误等),有时会抛出一个异常,有时是一个错误,有时只是一个警告。在PHP 8 以后,所有这类错误都将是一个异常,并且导致解释器停止运行,比如:
strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given
array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0
这个改动可能会影响一些安全漏洞的利用,有一些我们之前通过弱类型等tricks构造的POC,在老版本PHP中只是一个警告,不会影响解释器的执行,但8.0之后将会导致错误,也就中断了执行。
JIT(Just-In-Time)被鸟哥称为PHP 8 中最重要的改动,我来简单介绍一下PHP 8 的JIT。
PHP 8 的JIT附加在opcache这个扩展中,opcache本身就是对PHP解释器的优化。没有使用opcache时,PHP解释器是在运行PHP脚本的时候进行“编译->Zend虚拟机执行”的过程。而opcache的出现实际上就是节省了编译的时间,代码在第一次运行时会编译成opcache能识别的缓存(opcode),之后运行时就免除了编译的过程,直接执行这段opcode。
而JIT的出现再次优化了这个过程,JIT会将一些opcode直接翻译成机器码。这样PHP解释器在执行时,如果发现缓存中保存的是机器码,就会直接交给CPU来执行,又减少了Zend虚拟机执行opcode的时间。
普通开发者可能对JIT比较无感,毕竟大家的性能瓶颈多半出现在IO等问题中,但对于性能要求极高的人或企业来说,JIT的确是对PHP的重要改进。
其他可能和安全相关的改动
作为安全研究者,我会更关注的是和安全相关的改动。除了前面提到了弱类型方面的改动外,PHP 8还进行了如下一些和安全相关的改动:
-
assert()
不再支持执行代码,少了一个执行任意代码的函数,这个影响还是挺大的。 -
create_function()
函数被彻底移除了,我们又少了一个可以执行任意代码的函数。 -
libxml依赖最低2.9.0起,也就是说,XXE漏洞彻底消失在PHP里了。
-
继
preg_replace()
中的e模式被移除后,mb_ereg_replace()
中的e模式也被彻底移除,再次少了一个执行任意代码的函数。 -
Phar中的元信息不再自动进行反序列化了,
phar://
触发反序列化的姿势也告别了。 -
parse_str()
必须传入第二个参数了,少了一种全局变量覆盖的方法。 -
php://filter
中的string.strip_tags
被移除了,我在文章《谈一谈php://filter的妙用》中提到的去除死亡exit的方法之一也就失效了。 -
strpos()
等函数中的参数必须要传入字符串了,以前通过传入数组进行弱类型利用的方法也失效了。
这些改动,改的我心拔凉拔凉的……我一度认为PHP核心团队里混入了安全研究者,为什么我们常用的小trick都被改没了呢?
总结一下PHP 8,我只有两个感想:
-
我不用担心键盘的寿命了,但是我的头顶变凉了
-
比头顶更凉的是我的心,安全真是越来越难做了
好在,现在很多人慢慢转战Java,Java可以吃的饭应该还有很多。
参考链接:
-
https://www.php.net/releases/8.0/en.php
-
https://wiki.php.net/rfc/attributes_v2
-
https://wiki.php.net/rfc/shorter_attribute_syntax
-
https://stitcher.io/blog/attributes-in-php-8
-
https://www.laruence.com/2020/06/27/5963.html
Recommend
-
112
PyPI 官方库被发现混入了名字相似的恶意模块 pigsrollaroundinthem
-
59
NPM中混入了包含恶意后门的包
-
53
本文来自 南方都市报 近年来,网贷平台迅猛发展。它给很多人提供了低门槛、低成本的借贷渠道,同时也衍生出了大量催收欠账的业务需求...
-
43
距离BCH11月15日的硬分叉升级已经不足一月,开发者们和全节点运行人员都在做着最后的准备。开发者方面,BCH的主要开发团队们已全部推出了兼容升级内容的新版本,开发组主要包括:Bitcoin ABC、Bitcoin Unlimited和BCHD。与此同时,BCH网络...
-
31
-
3
如何“混入”饭圈?我们在这些品牌动作里找到了参考答案 最近第一次...
-
5
V2EX › 程序员 有没有 Vivo 开发组的大佬 有问题求教 xunmiqun · 14 小时 17 分...
-
7
Vue3混入、axios - YaPiBlog混入 (mixins)定义了一部分可复用的方法或者计算属性。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。 当组件和混入对象含有同名选项时,这些选项将以恰当的方式混合。...
-
3
Firefox良心!开发组考虑继续延续Win 7/8.1版本支持 2022-11-02 10:16 出处/作者:快科技 整合编辑:佚名 0
-
7
美少女三消游戏 《Mirror 2: Project X》开发组宣布破产解散 2023-03-09 21:16 出处/作者:快科技 整合编辑:佚名 0
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK