
刷CTF的人看到“Fakebook”这个名字多半会会心一笑又是一个谐音梗题目。但真正开始做才发现这道题跟社交平台没有半毛钱关系它把Web方向几个最经典的知识点串成了一条完整的利用链信息收集、源码泄露、SQL注入、PHP反序列化最后用SSRF把flag读出来。我在本地复现这道题的时候最大的感受是它没有特别难的单点技术难的是你怎么把每个环节的线索串起来。这篇文章就把我完整的复现思路和踩坑过程写出来适合刚接触CTF Web题、或者想系统梳理一遍“从黑盒到白盒再到利用”这条链路的朋友参考。先交代一下我准备的环境一个本地方便复现的靶场容器入口就是一个类似个人博客的站点整体接口风格很干净。整个解题过程我分成了五个阶段来聊每个阶段我都会说明“我是怎么想的”和“为什么这么想”而不是直接甩结论。1. 开局信息收集一个待挖掘的入口站拿到题目后第一件事永远不是急着找漏洞而是先把这个站点的“表面信息”看全。Fakebook这类题目尤其会在这里埋线索很多新手上来就扫目录、跑注入结果把最简单有效的入口给错过了。1.1 robots.txt与扫描器的联动我先打开页面把首页、注册、登录、个人主页几个功能都点了一遍确认了这个站点的大致构成有用户注册登录登录后可以发布自己的博客链接有一个展示用户信息的页面。表面上功能不多但功能点少不代表攻击面小。然后我直接访问了一次/robots.txt。这一步很多人会跳过但在CTF里这几乎是必查项出题人很喜欢把“下一关”的路径直接写在里面。果不其然robots.txt里躺着一个不是常规路径的条目指向了一个备份文件。这里有个经验robots.txt本身不是漏洞但它经常是出题人给的“指路牌”价值比扫描器跑半天还高。接着我再用目录扫描工具去叠一层覆盖。我用的是常见目录扫描字典把www.zip、backup.zip、1.php.bak、.git/、admin.php这类文件路径都包含了进去。扫描结果和robots.txt的提示对上了确认站点根目录下确实存在一个压缩备份文件。记得一定要同时做两件事手工看robots和页面源码中的注释以及用一个覆盖足够多备份文件命名的字典跑目录扫描。两者结合才能避免漏掉线索。1.2 备份文件泄露把黑盒变成白盒下载备份文件后解压整个站点的源码直接摊开在面前了。这一步的意义非常大我从“对着页面盲猜参数”的黑盒状态切换到了“直接看代码找危险点”的白盒状态。解压后目录结构很清晰一个小型PHP项目的标准布局入口页面、用户类文件、数据库操作文件、几个模板文件还有一个用于展示用户信息的页面。我按照经验先看几个东西项目里自己写的类文件、公共函数、数据库连接配置以及所有接收请求参数的页面。这里有一条重要的审计顺序先看有没有“危险函数”再顺着危险函数倒推“哪里有可控输入”。如果反过来从头读到尾效率很低而且在比赛里时间根本不够。后面我在源码里定位到反序列化相关调用时用的就是“先grep危险函数再回看调用链”这个思路。另外要提醒一句备份文件泄露看着是个小问题但在真实授权测试中它往往是击穿整个系统的第一步。源码只要到了测试者手里所有的过滤逻辑、隐藏接口、硬编码密钥都藏不住了。2. 在内容页找到注入参数从报错中还原数据库有了源码之后常规思维是先做代码审计找漏洞但我还是先回到功能页去测试了一遍参数。原因很简单源码里的数据库操作方式一眼就能看出有没有注入风险但手工触发一下报错可以顺带确认数据库类型和回显情况后面构造Payload会更稳。2.1 参数试探的方法这个站点展示用户信息的页面URL里带了一个数字参数形如?no1。这是很典型的注入候选点。我先做了三个最基础的测试# 正常请求 curl http://127.0.0.1:8080/index.php?no1 # 加单引号观察是否报错 curl http://127.0.0.1:8080/index.php?no1 # 用逻辑判断观察页面差异 curl http://127.0.0.1:8080/index.php?no1 and 11 curl http://127.0.0.1:8080/index.php?no1 and 12加单引号后页面直接报了数据库语法错误and 11正常回显数据and 12页面内容为空基本可以确认这里存在数字型注入。我坚持先手工试探而不是直接上sqlmap原因很直接报错信息里能看到数据库的语法细节、字段结构、甚至表名这些信息比工具自动跑出来的更直观。工具可以留到后面批量导数据时再用但判断“这个点有没有问题”手工永远比工具快。2.2 数据库指纹与数据表还原通过报错信息可以确定底层是MySQL。接下来就是常规的注入流程先用order by测出当前查询的列数再用union select判断回显位最后通过information_schema把表名、列名带出来。我实际拿到数据之后发现这个查询关联到的表里存的是用户信息包含用户名、展示内容、还有一串看起来非常奇怪的数据它的开头是O:4:User:这是一个很典型的PHP序列化对象特征。当时我就意识到这道题的核心攻击面很可能不是注入本身而是注入暴露出来的序列化数据。这里要展开说一下为什么“看到一个字段像序列化字符串”就值得警觉。在PHP应用里序列化对象被存进数据库并不罕见比如缓存用户信息、存储会话数据、保存临时对象。但一旦某个字段在读取后会被unserialize()还原成对象并且对象属性又和前端可控输入有关联那基本就是给攻击者递了一把钥匙。后面我进源码里验证这个猜测时整个利用链就慢慢浮出水面了。试探行为页面表现推断?no1正常显示用户信息无异常?no1数据库语法报错存在字符未过滤?no1 and 11正常显示逻辑判断可执行?no1 and 12无数据显示条件成立与否影响查询报错内容含MySQL关键字报错信息回显语句片段数据库类型为MySQL3. 密码字段里的序列化对象藏起来的反序列化点拿到注入结果之后我没有继续在SQL注入上恋战而是带着“这个序列化字段是什么”的问题回到了源码里。这种“数据异常反查代码”的思路比闷头读到尾高效很多。3.1 源码审计先圈出所有反序列化调用我在源码目录里直接搜索了所有包含unserialize的地方。搜索范围可以缩小到项目自有的类文件和业务逻辑中排除掉框架底层或第三方库的调用。搜索完发现站点里存在一个自定义User类当从数据库查出用户数据后某个字段会被直接拿去unserialize()还原成User对象。这意味着只要我能控制这个字段的内容就能控制反序列化后生成的对象形态。这里顺便解释一下PHP序列化字符串的格式新手看到O:4:User:2:{s:4:name;s:5:admin;s:4:blog;s:22:http://example.com/abc;}容易懵实际上拆开看就四部分O:4:User表示还原一个对象类名长度是4类名是User2表示对象有2个属性s:4:name表示第一个属性名是字符串长度4后面跟着属性对应的值也就是说序列化字符串本身是“自解释”的属性名、属性值、类型都写在里面。如果我构造一个博客地址为file:///etc/passwd的User对象再想办法让服务端把这个字段写进数据库那么后面一旦触发读取逻辑服务端就会按照我给的地址去取内容。3.2 可控属性分析从blog到SSRF接下来要看这个User类有哪些属性以及哪个属性可以被前端输入影响。审计后发现其中一个属性存的是用户的博客地址用户注册和编辑资料时都能填这个字段但它最终被保存进数据库前只是做了很弱的过滤。真正关键的是读取逻辑当访问某个用户的展示页时服务端会把数据库中该用户的序列化字符串还原成User对象然后取对象的博客地址属性在服务端发起请求去拉取这个地址的内容。那么问题就变成了如果我修改数据库里的序列化字符串把博客地址改成file:///var/www/html/flag或者file:///etc/passwd服务端就会在请求时把本地文件内容读出来。这里涉及一个SSRF的知识点。很多应用会给用户填“主页/博客”这样的外链然后后台去抓取这个链接做展示。如果后台没有限制请求协议那么PHP的file_get_contents、curl等都支持file协议攻击者就可以利用这个功能读取服务器本地的文件。打个比方网站相当于前台它拿着你给的名片去取资料你递的名片上写着“去隔壁公司拿文件”它照做了如果你把地址改成“去屋里档案柜拿文件”只要前台没有规定只能去隔壁公司它同样会照做。到这里整条攻击链已经在脑子里成型了通过SQL注入点拿到数据库的写入能力或者找到注册接口的写入点把一个改过的序列化字符串写进用户数据字段让博客地址属性指向file://协议的目标文件访问用户展示页触发服务端请求读取文件内容4. 把利用链串起来注入Payload与触发读取有了清晰的攻击链接下来就是动手构造和验证。这一步的细节决定成败很多新手在这里卡住不是思路不对而是Payload构造时被各种转义、编码、长度问题缠住了。4.1 利用SQL注入把序列化Payload写进数据库我先思考了一个问题怎么把改过的序列化字符串写进数据库在某些版本的题目里注册接口本身对博客地址的过滤很弱可以直接写入特殊字符在我复现的这份环境里则可以通过注入点执行更新语句来修改已有用户的字段。两种情况我都尝试了一下这里以后者为例来说明构造过程。原字段内容类似这样示意O:4:User:2:{s:4:name;s:5:admin;s:4:blog;s:22:http://example.com/abc;}我需要把s:22:http://example.com/abc这段改成s:29:file:///var/www/html/flagxxx之类的内容。注意PHP反序列化对字符串长度很敏感长度值必须和实际字符串长度完全一致多了少了都会导致反序列化失败。Payload里如果包含引号、反斜杠注入时还要考虑SQL语句里怎么转义。我的注入Payload大致思路是这样示意路径根据实际环境调整UPDATE 用户表 SET 存储序列化字段的列 O:4:User:2:{s:4:name;s:5:admin;s:4:blog;s:29:file:///var/www/html/flag;} WHERE 用户名 某个目标用户实际通过注入点执行更新时要把Payload做URL编码尤其是里面的单引号、花括号、引号在GET请求里直接传会被解析干扰。这里就有一个我踩过的坑URL里已经有参数使用了?no1如果我继续在参数里拼接包含特殊符号的Payload就必须把所有特殊字符都编码否则服务端解析参数时会截断。4.2 触发读取拿到flag写入成功之后我访问那个目标用户的展示页。页面上没有直接显示文件内容但通过回显可以判断服务端确实发起了请求并且把结果拼进了响应里。我用的验证方式是先从最简单、最通用的文件开始试尝试读取/etc/passwd。如果这一步成功说明file协议可用、权限足够、内容能回显到页面。确认之后再去读Web根目录下的flag文件。由于每个比赛的存放路径不一定一样我一般会先尝试读常见路径比如/var/www/html/flag、/var/www/html/flag.php、/flag再根据站点实际目录结构调整。# 触发读取的请求此时服务端会根据序列化对象中的地址发起请求 curl http://127.0.0.1:8080/index.php?no某个用户的ID整个链路走通之后flag就出现在响应内容里了。到这里这道题的核心就算解完了。需要额外强调一点所有这些操作都是在CTF靶场或本地容器里完成的属于合法授权的安全测试练习。真实环境下的任何未授权测试都是违规甚至违法的这套思路只能用来学习原理和参加比赛。尝试的文件路径预期作用结果file:///etc/passwd验证file协议可用性回显系统文件内容确认可用file:///var/www/html/flag读Web根目录flag回显flag内容题目打通5. 复盘与落地把一道题变成一套方法论打完一道题如果只是把flag提交了就结束那收获会少一半。我把这道题整体复盘了一遍又从这个例子里抽出了一些可以复用到其他Web题的通用经验。5.1 这类Web题值得沉淀的侦查清单我给自己整理了一份简单的链路清单以后遇到类似站点可以按这个顺序走打开站点把所有可见功能点记录清楚包括注册、登录、编辑、个人展示、上传每一个都可能成为入口访问robots.txt查看页面源码注释观察有无隐藏路径提示用包含常见备份文件名的字典做目录扫描重点看zip、bak、git、svn拿到源码后直接搜危险函数unserialize、include、eval、system、file_get_contents、curl_exec对每个危险函数反向追踪它的数据来源是用户输入、数据库读取、还是Cookie把“可控数据流向危险函数”的路径连起来就是一条基础利用链这个方法不仅适用于CTF在授权范围内的漏洞挖掘中同样有效。很多看似复杂的漏洞本质就是一条“用户可控数据 → 处理不当 → 触发敏感操作”的链路。5.2 开发者视角防御同一条攻击链站在开发者的角度这道题反映出来的问题其实很有代表性。第一备份文件不应该放在Web根目录。源码泄露是整条攻击链的起点一旦源码出去了后续的代码审计就是降维打击。生产环境里源码包、数据库备份、配置文件都应该放在Web目录之外并通过访问控制保护。第二数据库查询要用参数化查询。SQL注入之所以能走到“改数据”这一步根源在于查询语句直接拼接了用户输入。只要改用预编译语句注入点就不存在了后面整条链自然断掉。第三PHP反序列化不可信数据是非常危险的行为。从数据库读出的字段如果可能被外部影响就不能直接unserialize()。至少要做一层来源校验、字段白名单校验再考虑是否允许还原对象。第四服务端请求外部URL时要限制协议。不能允许file、gopher这类危险协议最好只允许http/https并且对请求目标做域名或IP白名单校验。5.3 我自己踩过的坑最后分享几个我在复现这道题时踩过的坑希望能帮你少走弯路。第一个坑是扫描字典太弱。我一开始用的目录字典里没有www.zip这类常见备份名结果扫描器跑完什么都没发现。后来手工看到robots.txt里给的提示才回去重新补了一个包含备份文件命名的字典。工具不是万能的字典选不好等于白扫。第二个坑是拿到注入数据后没有养成“先看整体结构”的习惯。我第一次把用户表数据拉出来时注意力全放在用户名和密码字段上差点把那串序列化对象当成普通缓存数据划过。后来回头复现时才意识到每一个非常规字段都可能是线索数据结构的任何一个异常都值得多问一句“为什么长这样”。第三个坑是构造Payload时忘了处理URL编码。序列化字符串里的花括号、引号在URL里直接传会被截断导致反序列化失败页面返回空。后来把所有特殊字符做了URL编码才正常触发。这个细节提醒我任何注入内容在进入HTTP请求前都要以“服务端接收到的最终形态”去考虑而不是以我自己在文本框里看的样子去考虑。第四个坑是复现过程中环境被我改坏了。用注入更新数据库字段时我把那列改成了非法的序列化字符串导致整个用户展示页白屏。当时没有备份数据库的习惯只能重新初始化容器再来一遍。CTF环境可以重置但如果是在授权测试中操作前做好记录和备份是非常必要的职业习惯。这道题解题本身可能只需要几十分钟但把每个环节的原理想透、把每条经验落到自己的方法论里才是刷题最大的收获。如果你也正在做Fakebook或者类似风格的Web题建议不要直接抄Payload而是按我上面说的顺序自己把信息收集、源码审计、数据流分析完整走一遍。等你独立打通了这条链再遇到“看似花哨实则套路清晰”的题目心里就有底了。