有安全研究者混入了 PHP 8.0 开发组!


经历了近半年的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
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


更多精彩推荐
点分享 点点赞 点在看
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 《求是》发表习近平总书记重要文章 7904693
- 2 勇敢夺枪老人与妻子相拥倒在现场 7808567
- 3 中美卫星惊险“擦肩”距离仅200米 7713432
- 4 明年经济工作政策取向确立这八个字 7616222
- 5 “中国最冷小镇”最低温降至-39℃ 7522493
- 6 男子陪女友逛街买刮刮乐中80万元 7427172
- 7 30万级的玛莎拉蒂两天被一抢而空 7332480
- 8 扫地机器人鼻祖要破产了 7233963
- 9 男子买炒饼打包5头蒜被老板劝退 7140316
- 10 如何让你我的钱袋子鼓起来 7048646














CSDN
