今天是2026年7月27日

今天学习了PHP反序列化,具体学习情况如下:

class定义类
序列化和反序列化:
序列化就是把内存中的数据(对象、属性。。。)转换成一段可以保存或传输的字符串(方便数据传输的字符串),反序列化就是把这段字符串还原成原来的数据结构。
它们都是为了方便数据传输

危险点:如果服务器对用户可控内容执行unserialize()

数组:echo serialize({"user" => "alice", "admin" => false});
结果:a:2{s:4"user";s:5"alice";s:5:"admin";b:0;}

对象:
class User {
public $name = "alice";
public $role = "user";
}
echo serialize(new User()); #实体化后序列化

O:object(对象)

public、protected、private属性
a
\0*\0b
\0类名\0b

PHP魔术方法:


$this -> username #指向


魔术方法第一类:

魔术方法

触发时机

典型用途

__construct()

“出生”——你用 new 创建对象时,自动执行。优先级最高,构造函数

初始化属性值,比如给机器人起名字。

__destruct()

“死亡”——对象被销毁(脚本结束或没人用了)时,自动执行。

(反序列化高危) 收尾工作,比如关掉数据库连接,或执行恶意代码。

__clone()

“克隆”——当你用 clone 复制一个对象时,自动执行。

克隆后调整属性,确保复制品和原件不完全一样。

魔术方法第二类:

魔术方法

触发时机

典型用途

__sleep()

“打包前”——当你执行 serialize() 时,先自动触发它。

告诉 PHP:哪些属性需要打包,以及清理不需要保存的临时数据(比如数据库连接资源)。

__wakeup()

“拆包后”——当你执行 unserialize() 时,自动触发。

(反序列化高危) 重新连接数据库、恢复丢失的属性。

__serialize()

PHP 7.4+ 新增,执行 serialize() 时触发(优先级高于 __sleep)。

更高级的打包,可以自定义打包成数组。

__unserialize()

PHP 7.4+ 新增,执行 unserialize() 时触发(优先级高于 __wakeup)。

更高级的拆包,接收数组并还原对象属性。

魔术方法第三类:

魔术方法

触发时机

典型用途

__get($name)

“取不到东西时”——当你读取一个不存在或私有的属性时,自动执行。

返回一个默认值,不让程序报错。

__set($name, $value)

“存不进去时”——当你给一个不存在或私有的属性赋值时,自动执行。

拦截赋值,把值存进一个数组里兜底。

__isset($name)

“判断有没有时”——当你在外部用 isset() 或 empty() 判断一个不存在的属性时,自动执行。

自定义判断逻辑。

__unset($name)

“销毁属性时”——当你在外部用 unset() 销毁一个不存在的属性时,自动执行。

自定义销毁逻辑。

__call($name, $args)

“找不到方法时”——当你调用一个不存在或私有的普通方法时,自动执行。

动态处理请求,比如提示“您调用的功能不存在”。

__callStatic($name, $args)

“找不到静态方法时”——当你调用一个不存在的静态方法时,自动执行。

同上,专供静态方法使用。

魔术方法第四类:

魔术方法

触发时机

典型用途

__toString()

“当字符串用”——当你 echo 一个对象,或者把对象拼接到字符串里时,自动执行。

(反序列化高危) 必须返回一个字符串,比如把对象转换成 JSON 或特定文本格式。

__invoke()

“当函数用”——当你把对象当作函数来调用时(比如 $obj()),自动执行。

让对象变成“可执行”的,设计模式中常用。

__set_state()

“导出时”——当用 var_export() 导出类时,自动执行。

用于恢复对象,但用得极少。

__debugInfo()

“调试时”——当用 var_dump() 打印对象时,自动执行。

自定义打印出来的信息,隐藏敏感数据(比如密码)。

今日练习:

1.

进入靶场给了这样一段代码:

代码逻辑是让账号密码与$u、$p相等就能拿到flag,直接传参:

拿到flag

2.

进入靶场

代码逻辑和上一题差不多,但是多了一个反序列化,$user会接收被传入参数的cookie再被反序列化后的值,所以要在cookie中加入参数user,并为其赋值,既然是反序列化,那我就传个序列化的值:

为了保证数据原封不动的传输给PHP,这里是进行了url编码:

Cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

构造payload:

https://d5396e49-da1a-40a6-9188-6b2f2590a9c2.challenge.ctf.show/?username=xxxxxx&password=xxxxxx
Cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

拿到flag:ctfshow{4159fe9e-6f37-481a-bddc-9fe3f4a7363c}

3.

进入靶场

直接构造payload:

把序列化结果给nss传参拿到flag:NSSCTF{6e0a1bac-f5f7-473f-8cbe-c1f6e0772304}

今日作业

进入靶场

发现没给题目,在源码中也没找到,用dirsearch扫一下发现两个目录:

/flag.php中没有任何数据,在/robots.txt中找到了题目入口:

进入/c145.php目录,看到真正的题目:

题目的代码逻辑大概就是让我把if判断的admin的值从user改成admin,把passwd从123456改成ctf就能拿到flag,并给了一个可传参的参数p,p再经过反序列化。于是根据代码,我想到如下payload:

这段代码的意思是让admin的值为admin,passwd的值为ctf,刚好满足题目要求,然后再经过序列化、url编码(保持原始数据),得到最终payload:

O%3A4%3A%22wllm%22%3A2%3A%7Bs%3A5%3A%22admin%22%3Bs%3A5%3A%22admin%22%3Bs%3A6%3A%22passwd%22%3Bs%3A3%3A%22ctf%22%3B%7D

成功拿到flag:NSSCTF{68024802-bfc3-4054-b015-1c7695bc3087}