CTF圈里有句老话:信息收集做得好,漏洞利用不用愁。我刷 CTFHUB 的时候,信息泄露这个模块最初是被我跳过的——总觉得"备份文件下载不就是下载个文件嘛,能有什么技术含量"。直到后来在一次线上赛里,一道最简单的签到题卡了我半小时,目标目录里明晃晃躺着一个.DS_Store,我压根没认出来,最后只能看着别人几分钟拿 flag。从那以后我才老老实实回头,把"备份文件下载"这组题目完整过了一遍。今天这篇文章就把这个模块彻底拆开聊:网站源码、bak文件、vim缓存、.DS_Store 这四类备份文件分别是什么、为什么会存在、做题时怎么发现,以及从开发运维角度怎么避免这类问题。既适合刚开始接触 CTF 的选手,也适合想自查站点有没有备份残留的开发和运维朋友。
1. 信息泄露为什么被放在技能树最前面
1.1 信息收集是所有漏洞利用的前置条件
在 CTF 的 Web 方向,不管题目多难,第一步永远是"看"。看响应头、看页面源码、看目录结构、看请求参数。信息泄露题就是把这件"看"的训练单独放大。备份文件下载这个考点,核心训练目标有两个:一个是目录探测能力,一个是对文件痕迹的敏感度。
目录探测说白了就是猜路径。很多人觉得"猜"这个动作不专业——好像高端渗透都是用 0day 打进去的。但实际不管是 CTF 还是授权测试,路径枚举永远是最基础也最有效的手段。区别只在于:有经验的人会从站点返回的状态码、内容长度、报错信息里快速判断下一步该猜什么;没经验的人只会盲目跑完一个字典然后不知所措。
CTFHUB 把这四类备份文件单独做成一个模块,本质上就是在帮你训练这种"看到文件名就能想到它背后可能有什么"的能力。你探测到一个/public/目录存在,能不能立刻想到里面可能有/public/backup.zip?你看到页面是index.php,能不能想到去访问index.php.bak或者.index.php.swp?这些不是玄学,完全可以系统训练出来。
1.2 四类文件泄露背后的共同逻辑
网站源码、bak文件、vim缓存、.DS_Store 这四个考点,背后对应的是四种完全不同的"产生源":
- 网站源码压缩包:开发或运维人员的备份行为
- bak文件:编辑器或开发者手动备份的习惯
- vim缓存:vim 编辑器崩溃或异常退出留下的临时文件
- .DS_Store:macOS Finder 自动生成的文件
虽然题目模块叫"备份文件下载",但考题重点并不是"下载"这个动作,而是"发现"这一步。前面两类相对容易用字典扫出来,后面两类则更考验知识面——你不知道 vim 交换文件的命名规则,或者不知道 .DS_Store 的存在,那就算文件真的放在眼前也发现不了。这正是这类题目设计得有意思的地方。
另外得说一句,这组题不只是用来刷分的。真实世界里,因为一个备份文件导致站点泄露的案例非常多。很多中小型网站的维护者习惯把整站打包放根目录,文件名就叫backup.zip或者www.zip,传上去就忘了删。攻击者只要扫一下常见路径,整站源码甚至数据库配置就可能直接到手。理解这些泄露路径,不管站在攻击队思路还是防守队视角,都是必修课。
2. 网站源码:压缩包泄露,范围最大也最直白
2.1 压缩包备份为什么屡禁不止
网站源码这道题,考的是最直白的一类泄露:整个项目被人打成压缩包,放在了 Web 目录下。常见格式有 zip、tar.gz、rar、7z,文件名虽然五花八门,但核心命名规律就那几种:
- 按关键词命名:www、web、site、html、code、src、backup、bak、temp、old、test、wwwroot
- 带版本号或日期:www_2020.zip、site_v1.2.tar.gz、backup_20240101.zip
- 直接用数字:1.zip、2.zip、666.zip,或者拼接成 web1.zip
- 与目录同名:目录名是 admin 就可能存在 admin.zip,域名是 example.com 也可能有 example.com.zip
为什么这类泄露屡禁不止?因为"把代码打成压缩包"这个操作本身太顺手了。从服务器迁移、下载本地备份、发给同事调试,随手 zip 一下放在网站目录然后忘了删,是很多非专业运维都干过的事。CTF 题目不过是把这个常见的现实问题浓缩成了一道题。
2.2 做题时的探测流程
CTFHUB"网站源码"这道题,直接访问根目录时页面内容非常简单,正常浏览没有任何提示。这时候要做的是:
- 先手工试最可能的几个路径:
/www.zip、/web.zip、/backup.zip、/域名.zip - 手工不行就上字典工具,dirsearch、7kbscan、御剑都可以,也可以写个简单的爆破脚本只跑文件名字典
- 重点关注 HTTP 状态码和响应头里的 Content-Length。如果访问
/backup.zip返回 200,且 Content-Length 明显比普通页面大一个数量级,那基本就是它了
这里有个细节值得注意:不要只扫根目录。有些题目会把压缩包放在某个子目录下,比如/upload/backup.zip或者/admin/www.zip。所以扫描时记得带上递归深度,或者根据页面里其他线索去猜子目录。
2.3 下载解压之后别急着找 flag
压缩包拿到手,第一件事不是急着解压找 flag,而是先看压缩包里的目录结构。我见过不少人,拿到压缩包立刻全部解压,然后漫无目的地翻,效率其实很低。正确做法是:
- 用
unzip -l或tar -tf先列一遍压缩包里有哪些文件 - 重点关注:SQL 文件、配置文件(config.php、settings.py、application.properties)、README、隐藏文件
- 解压后全局搜索,Linux 下直接
grep -r "flag" .,或者用find把全部文件列出来人工扫一遍
有些题目还会把 flag 藏在文件名里,或者藏在被注释掉的代码里,所以别只看 PHP 文件,CSS、JS、HTML 都要扫一眼。我做题时发现,flag 直接写在模板注释里的出题率还挺高,这个习惯值得保留。
3. bak文件:一个后缀名暴露编辑习惯
3.1 bak文件到底是怎么来的
bak(backup)文件是开发过程中最容易留在服务器上的文件之一。它的产生场景通常有两种。
第一种是手动备份:开发人员改文件之前,担心改坏了,于是执行cp index.php index.php.bak,或者干脆复制一份改名成带 .bak 的文件。这种备份往往和原文件放在同一个目录里,一放就是几个月甚至几年。
第二种是编辑器自动生成:某些编辑器或者 IDE 在保存文件时,默认把上一次版本另存为后缀带 .bak 的文件。老版本的 EditPlus、UltraEdit 都干过这种事,部分 FTP 工具在下载覆盖前也会生成临时备份。
这里要理解一个关键技术点:当你访问/flag.php时,Web 服务器会通过 PHP 解析器执行它,你只能看到渲染后的 HTML 输出;但当你访问/flag.php.bak时,绝大多数服务器不认识 .bak 这个后缀,不会调用 PHP 解析器,而是直接把文件内容当普通文本返回。这样一来,原本只在服务端执行的代码就以源码形式交到了你手里。
3.2 经典路径组合与枚举方法
CTFHUB bak文件这道题,解法其实很固定:
- 先访问主页,判断它是用什么语言写的,推测可能存在哪些源文件
- 枚举常见备份名:index.php.bak、flag.php.bak、config.php.bak、admin.php.bak
- 如果页面里给了你某些线索——比如报错信息、注释里出现的路径,优先试对应的 .bak
这里整理了一些常见的备份组合:
| 原文件 | 备份文件 | 访问结果 |
|---|---|---|
| index.php | index.php.bak | 直接看到 PHP 源码 |
| flag.php | flag.php.bak | 获取 flag 逻辑 |
| config.php | config.php.bak | 数据库账号密码等配置 |
| function.php | function.php.bak | 后台业务逻辑 |
不要只试 php 后缀,txt、html、sql 也都值得考虑。备份文件的后缀不一定是 .bak,也可能有 .bak1、.old、.save 这类变体。做题时如果字典里没有这几个后缀,手动加上再跑一轮。
3.3 下载到bak之后怎么还原
拿到 .bak 文件后,如果内容是文本,直接用代码编辑器打开即可。注意编码问题,老项目经常出现 GB2312 乱码,这时候可以用iconv -f GBK -t UTF-8转一下再读。如果拿到的是二进制文件,先用file命令判断类型,再决定用对应工具处理。
还有一个我经常用的招:如果题目给的是flag.php.bak,下载后内容里有大量特殊字符,不要硬读。先跑strings提取可打印字符串,再正则匹配 flag 相关关键词。有时候整个文件被某种混淆手法处理过,直接盯着看反而容易漏掉关键信息。
顺带提一个真实环境里的例子。之前做授权测试时,我在目标站点的/editor/目录下发现了一个编辑器残留文件夹,顺手试了试/editor/connector.php.bak,结果直接拿到了连接器的源码。这个文件本身没泄露敏感数据,但通过阅读源码,我找到了它的上传校验逻辑,对后续构造请求帮助很大。所以说,bak 文件的价值往往不只是"直接读源码",它更像一个引子,能带你进入目标系统的内部逻辑。
4. vim缓存文件:藏在隐藏点号后面的编辑器现场
4.1 vim交换文件的生成机制
用 vim 编辑文件时,vim 会在同目录下创建一个隐藏的交换文件,用来保存尚未写入磁盘的修改内容,防止断电或断网导致数据丢失。文件名的规则是在原文件名前面加点,后面加.swp:
- 打开
index.php编辑,就会生成.index.php.swp - 如果交换文件已经存在(比如另一个 vim 会话正在编辑同一个文件),就会生成
.index.php.swo - 再往后依次是 .swn、.swm,后缀字母往下推
正常使用 vim 并退出时,交换文件会被自动删除。但 vim 进程被强制结束——ssh 掉线、终端被直接关闭、系统崩溃——交换文件就会残留在目录里。关键问题就在这里:残留的交换文件保存着上次编辑时缓冲区的内容,其中很可能包含完整的源码。
4.2 做题怎么发现 vim 交换文件
CTFHUB vim缓存这道题,最关键的技能就是"知道要去访问以点号开头的隐藏文件"。很多人扫目录时,字典里没有包含点号开头的文件,或者工具默认过滤了隐藏文件,导致这道题怎么都找不到入口。
正确思路:
- 先确认站点当前页面对应的文件,一般是
/index.php - 直接尝试访问
/.index.php.swp,返回 200 说明交换文件存在 - 如果 .swp 不行,就试 .swo、.swn;如果 index 不行,就试试 flag、admin、config 这些名字
还有一点值得注意:vim 的残留文件不只有 swp 后缀,某些配置下还会生成.viminfo,或叫index.php~的备份文件。做的时候多试几种后缀,不要在一棵树上吊死。事实上这也不是 CTF 里才有的问题。很多开发习惯直接在服务器上用 vim 改线上配置,改到一半终端会话超时断开,swap 文件就留在了 Web 目录。扫描器扫到之后下载下来,nginx 配置、PHP 源码、甚至密码哈希都可能被翻出来。所以这道题也是在提醒:别在生产环境直接用 vim 改代码,改完也要确认没有残留 swap 文件。
4.3 从交换文件恢复源码的实操
下载到 .swp 文件之后,恢复源码有两条路。
第一条是用 vim 自带的恢复功能。把文件放到本地,比如index.php.swp,然后执行:
vim -r index.php.swpvim 会提示恢复未保存的更改,确认后就能看到编辑器崩溃前缓冲区里的内容。如果这个交换文件是别人编辑时留下的,看到的往往是源文件完整内容或者未保存修改的版本。
第二条是用 strings 工具提取。在没有安装 vim 的机器上,直接执行:
strings .index.php.swp | grep -i flag很多 CTF 题的 flag 或关键代码就藏在交换文件的字符串碎片里,这种方法往往比vim -r更直接。
这里有个踩坑提示:vim -r恢复时要注意交换文件格式兼容性,新版 vim 打开旧版交换文件通常会提示版本不匹配,或者需要手动指定编码。这种情况下,用strings提取反而更加稳妥。
5. .DS_Store:macOS用户的"目录地图"
5.1 一个埋藏极深的系统文件
.DS_Store(Desktop Services Store)是 macOS Finder 自动生成的隐藏文件,作用是记录文件夹的显示设置——图标位置、窗口大小、背景图、排序方式等。只要你用 Finder 打开过一个文件夹,macOS 就会在里面生成一个 .DS_Store 文件。这个文件本身不是备份,但它有一个很危险的特性:它会记录该目录下所有文件和子目录的"文件名"。
换句话说,一个 .DS_Store 就相当于当前目录的一张地图。你不需要暴力枚举这个目录有哪些文件,只要解析出 .DS_Store 里的文件名列表,就能直接定位到目标。更棘手的是,很多开发者在 Mac 上打包上传整个项目文件夹时,会把目录里隐藏的 .DS_Store 一并传上去,于是这张"地图"就被带到了 Web 服务器上。
5.2 解析思路与工具选择
.DS_Store 是二进制格式,直接打开就是一堆乱码。解析的大致思路是:读取文件头部的 "Bud1" 标记,然后逐个解析后面的记录项。每个记录项里保存了文件名和坐标编码,我们要提取的就是这些文件名。
这里不需要从零写解析器,三种现成方案:
- 在 macOS 上直接看:用 Finder 打开目录,或者在终端用
ls -la查看隐藏文件,再配合第三方工具导出文件名列表 - 使用 Python 的 dsstore 库:
pip install dsstore之后几行代码就能解析 - 用 Hex 编辑器手动分析(不太推荐,效率太低)
我在做题时比较喜欢用 Python 脚本,因为可以批量处理多个 .DS_Store,把解析出的文件名列表直接存成字典,下一步再拿这个字典去请求服务器,效率很高。简单示例:
from dsstore import DSStore store = DSStore.open('.DS_Store') for entry in store: if entry.filename: print(entry.filename)5.3 拿到目录列表之后如何顺藤摸瓜
解析出文件名列表之后,题目才刚刚开始。常见情况是:.DS_Store 在站点根目录下,解析出来能看到 admin、upload、flag.php 之类的名字,但光有文件名还不够,你还得猜出对应的完整路径。所以下一步是把文件名列表作为字典,逐一请求服务器上的路径。
这里我建议多探测几层:根目录有 .DS_Store,admin 子目录下很可能也有一个 .DS_Store,层层解析、层层探测,能画出一棵完整的网站目录树。CTFHUB 的 .DS_Store 题目一般就是考到这个层面:解析根目录文件,找到隐藏的 flag 路径,访问并获取 flag。
有一个坑必须提醒:.DS_Store 文件可能不在根目录,也可能经过某种编码或截断,导致文件名列表不完整。做题时不要只下载一次就完事,多几个目录都探测一下。遇到解析不出来的,用strings硬核提取字符串,往往也有意外收获。
6. CTFHUB实战复盘:四道题的完整通关流程
6.1 网站源码题的标准操作流
CTFHUB"网站源码"这道题,整体流程非常清晰。我当时三步走:
第一步,确认目标站点技术栈。访问首页,看响应头里的 Server 字段、Set-Cookie、页面生成特征,判断是 PHP、Java 还是 Python。
第二步,扫描备份压缩包。我用 dirsearch 带上常见 zip、tar.gz 字典,扫到/website.zip之后,先看响应包的 Content-Length,确认是个压缩包,不是 404 跳转到自定义页面。
第三步,下载解压。解压后整个项目源码铺在眼前,直接全局搜索 flag 关键词,最后在一个配置文件里找到了明文 flag。整个过程没用到任何复杂漏洞,纯粹是信息收集做到位了。
6.2 bak文件题的细节与坑
CTFHUB"bak文件"这道题,入口其实非常直白:直接访问/index.php.bak就能下载源码。但有几个坑值得说一下。
第一个坑:下载后直接用浏览器打开,浏览器会把 PHP 代码当纯文本显示,部分内容可能被"折叠"或者显示不完整。正确做法是先用 curl 下载到本地,再用代码编辑器打开:
curl http://目标地址/index.php.bak -o index.php.bak第二个坑:很多人分不清访问/index.php和/index.php.bak的区别。访问前者时永远看不到源码,只有访问后者才能看到纯文本内容,因为服务器不会把 .bak 当 PHP 执行,而是直接返回文件本体。
第三个坑:编码问题。如果源文件里混着 BOM 头或者中文注释,直接看可能乱码。建议先用file index.php.bak确认格式,再用合适的编码打开。
6.3 vim缓存题:从 .swp 到 flag 的一步之遥
CTFHUB"vim缓存"这道题,关键动作是访问/.index.php.swp——注意带点号的隐藏文件。下载下来是个二进制文件。我当时先用vim -r恢复,因为交换文件版本兼容问题没成功,随后改用 strings 直接提取,在输出里看到了完整 PHP 代码以及一个注释掉的 flag 内容。
做题技巧:如果找到的交换文件后缀是 .swo 而不是 .swp,恢复方法完全一样。另外多试几个可能被编辑过的文件,比如 flag.php、config.php,不要只盯着 index.php。题目既然叫"vim缓存",说明出题人模拟的就是一个真实开发者在服务器上编辑代码后异常退出的场景,顺着这个思路去猜文件名,命中率会高很多。
6.4 .DS_Store题:解析后看到一张完整目录树
CTFHUB".DS_Store"这道题,下载根目录下的/.DS_Store文件,用 Python 解析之后,能看到里面记录了若干文件条目,其中就包括目标 flag 文件的文件名。顺着这个文件名去访问对应路径,flag 直接到手。
我当时犯过一个有意思的错误:第一次解析时程序输出乱码,还以为 .DS_Store 文件损坏了,后来才发现是自己的解析脚本没处理编码。换成库自带的编码处理逻辑之后,文件名列表就清爽了。这个坑提前帮你踩了。
6.5 四类文件联动思考
做完这四道题再回头看,能发现一个一致的内在逻辑:猜路径、用对工具、识别格式。猜路径靠经验和字典;用对工具靠的是知道什么场景配什么工具;识别格式靠的是知识面广度和对二进制文件的敏感度。这四个考点难度是递进的:压缩包考扫目录,bak 考猜后缀,vim 缓存考隐藏文件意识,.DS_Store 考系统生态知识。刷完这组题,再去看信息泄露模块里其他类型的题目,比如 Git 泄露、SVN 泄露,思路会顺畅很多,因为它们本质上都在做同一件事:从被忽略的文件痕迹里还原目标系统的结构。
7. 防守视角:如何从源头掐断备份文件泄露
7.1 服务器配置层面的拦截
做过几年运维之后,我强烈建议:不管你的站点规模多小,Web 服务器配置里都应该加一层针对备份文件的拦截规则。Nginx 可以这样加:
location ~* \.(bak|swp|swo|swn|old|save|orig|~)$ { deny all; } location ~ /\.DS_Store$ { deny all; }Apache 的 .htaccess 也类似:
<FilesMatch "\.(bak|swp|old|save|orig|~)$"> Require all denied </FilesMatch> <FilesMatch "^\.DS_Store$"> Require all denied </FilesMatch>加完之后,访问这些路径会直接返回 403。就算线上误留了备份文件,外部也看不到内容,算是最后一层兜底。但这只能算"止血",真正的根治还得靠流程。
7.2 开发与发布流程规范
比服务器拦截更有效的,是从流程上杜绝备份文件上线:
- 禁止在服务器上直接编辑代码,所有改动走测试环境验证后再通过 CI/CD 发布
- 备份不要放在 Web 目录下,统一放到对象存储或者内网备份机
- 每次上线前跑一个静态检查脚本,自动扫描 Web 目录下的常见备份文件名,发现后自动告警
- 给团队定好代码管理规范,源码必须进版本库,不要用 zip 互相传
我见过不少团队,开发规范写得像模像样,但生产服务器上依旧躺着几个旧 zip。大部分情况不是大家不想遵守,而是根本不知道有哪些文件被遗留下来了。所以放一个自动巡检脚本,比贴一百遍规定都管用。
7.3 定期巡检与检测思路
防守不能总是被动,得定期用"攻击者视角"去检查自己的站。思路和做题时完全一样:
- 维护一份常见备份文件名列表,定期用脚本请求自己的域名,比如
/www.zip、/backup.tar.gz、/index.php.bak、/.index.php.swp、/.DS_Store - 任何一个路径返回 200,立刻排查是谁上传的、什么时候上传的
- 对返回 200 的文件做内容安全检查,确认里面没有泄露敏感配置
这种巡检本质上就是在重复 CTF 题目的操作,只不过目标换成了自己家的站点。把你自己当成攻击者扫一遍,往往比任何安全报告都直观。
最后再分享一个做题之外的体会。刷 CTF 题很容易陷入"为了刷题而刷题"的状态,但信息泄露这个模块,我始终把它的价值看得很高:它培养的是一种"好奇心加验证力"的习惯。看到一个路径不直接略过,而是下意识想"后面还有没有东西",这种习惯在做开发、做测试、做运维时都会持续受益。如果你正准备按这个思路去挨个刷 CTFHUB 的备份文件下载题,建议过程中把每个路径的返回状态和判断记录一下,刷完你收获的绝对不只是四道题的分值。