Hitcon 2017 《Baby^H Master PHP 2017》这题在比赛中并没有人能做出来,我当初看到题目以后以为是一道二进制相关的Web题,没想到并不是,还有学到了新姿势,我们来分析一下这道题的几个考点。
#奇技淫巧#
拿到源码(
My-CTF-Web-Challenges/index.php at master · orange... )会发现,关键代码在Admin类的__destruct函数里,那么很容易想到通过反序列化能够构造出Admin对象,即可触发析构函数了。
源代码中的确存在一处反序列化,但在执行前会检查Cookie中的签名,如果要构造自己的反序列化字符串,必须构造这个字符串的哈希签名。哈希用到了hash_hmac,基本上没法绕过了,应该及时舍弃这个思路。
那么,还有没有其他地方可以触发反序列化操作呢?
这就是本题最大的考点:PHP在解析phar文件的Metadata的时候,可能会触发反序列化操作。这一点可以从官方文档看到(图1):
PHP: Phar::getMetadata - Manual
再加上,phar默认会注册成一个协议 phar://,这样,在用phar://协议读取文件的时候会自动解析成phar对象,同时反序列化其中存储的Metadata信息。
所以,一个可控协议的文件读取的操作,将会触发反序列化操作(文件读取漏洞 => 反序列化漏洞)。到这里思路就比较明确了,我们通过upload函数(图2)可以写入任意文件,并再次通过upload函数读取之。
不过upload函数中检查了文件头是否是"GIF89a",这也好说,加上即可,因为phar在读取的时候这个头会被忽略掉。最后,编写如下POC:
<?php
class Admin {
public $avatar = 'orz';
}
$p = new Phar(__DIR__ . '/avatar.phar', 0);
$p['file.php'] = '<?php ?>';
$p->setMetadata(new Admin());
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
rename(__DIR__ . '/avatar.phar', __DIR__ . '/avatar.gif');
?>
即可生成包含序列化的Admin对象的avatar.gif。
本题第二个考点,是考察匿名函数真正的名字。我们现在构造好了反序列化对象,已经可以进入__destruct方法了,这个方法中执行了 $_GET["lucky"]();
这很类似于我以前出过的一个题
安全箱子的秘密 | 离别歌 ,那个题目当时是调用了get_defined_functions(),找到了可以利用的函数。
但这道题,获取flag的函数是一个匿名函数:$FLAG = create_function("", 'die("flagflagflag");'); ,是通过create_function创建的,所以用get_defined_functions()没法获得其真实名字。
这就涉及到另一个知识:PHP中匿名函数其实是有名字的,名字是"\x00lambda_%d",这个%d是一个1开始的递增序列,表示这是当前进程中第%d个匿名函数。
所以,如果一个崭新的PHP进程,其第一的第1个匿名函数就是"\x00lambda_1",所以我只需要传入lucky=%00lambda_1即可。
但是实战中,因为有很多人做题,每个人访问一次网页,就会使%d加1,所以我们并不知道自己访问时的这个%d是多少。
这就涉及到第三个考点,我们通过大量请求,使目标Apache难以同时处理这么多请求,所以以Pre-fork模式启动的Apache会启动新进程来处理这个请求。那么,新进程下%d就是以1重新开始的,所以就可以预测了。