1. 从一次内部资产梳理说起:为什么敏感文件暴露总在事后才被发现
做过几年安全工作的朋友大概都有类似的经历:某次内部资产梳理或者攻防演练复盘时,翻看日志才发现,某个本不该对外暴露的配置文件、备份包或者调试页面,已经在公网上静静躺了好几个月。更让人后背发凉的是,这些文件的路径往往并不复杂,/backup.zip、/.git/config、/config.php.bak这类命名,几乎就是明摆着等人来拿。问题在于,日常巡检靠人工点链接,根本覆盖不了成千上万的路径组合,于是目录扫描工具就成了安全工程师和渗透测试人员手里最基础也最实用的一件家伙。
这篇内容我想聊的是dirsearch这个工具,以及围绕它展开的一整套敏感文件扫描与收集的实战思路。dirsearch 是一个用 Python 写的 Web 路径扫描器,核心能力就是拿着字典去逐个请求目标站点的候选路径,根据返回的状态码、响应长度、响应内容等特征,判断哪些路径真实存在、哪些值得进一步关注。它不算什么高深的东西,但胜在轻量、可定制、上手快,特别适合做资产梳理、暴露面排查和授权范围内的渗透测试。
需要先把话说在前面:这类工具只能用于你拥有明确授权的目标,比如自己公司的资产、客户书面授权的测试范围、或者专门搭建的靶场环境。未经授权对他人站点做扫描,性质上就是攻击行为,后果不用我多说。这篇内容面向的是安全工程师、渗透测试初学者、以及需要做自身资产暴露面排查的运维和开发同学,目标是让你理解 dirsearch 的工作逻辑,掌握一套可复现的扫描与结果分析方法,而不是教你如何去搞别人。
我下面会从整体设计思路讲起,再拆解核心参数和字典策略,然后给出一套完整的实操流程,最后把我踩过的坑和常见问题整理成速查表。内容偏实战,能直接抄作业的地方我会尽量写清楚。
2. 整体设计思路:扫描敏感文件到底在扫什么
2.1 敏感文件暴露的本质是“路径可猜”
很多人对目录扫描有个误解,以为它是靠什么漏洞探测技术。其实绝大多数情况下,它利用的就是一个朴素的事实:Web 服务器上真实存在的文件,只要路径能被猜中,就能被直接访问。这跟“猜密码”是一个道理,密码空间有限、规律明显,就能被枚举出来;路径命名有习惯、有规律,同样能被字典覆盖。
敏感文件之所以敏感,是因为它们往往包含不该公开的信息。常见的几类包括:
- 版本控制残留:
.git/、.svn/目录被误部署到生产环境,攻击者可以直接拉取完整源码。 - 备份与临时文件:
index.php.bak、www.zip、database.sql、config.old这类编辑器或运维留下的产物。 - 配置文件:
config.php、.env、settings.py、application.yml,里面常有数据库账号、密钥、第三方服务凭证。 - 调试与后台入口:
phpinfo.php、debug.log、admin/、test/、api/swagger。 - 日志与数据导出:
access.log、error.log、export.csv、users.xlsx。
这些文件的共同点是:命名有迹可循,且一旦存在通常就是直接可读。dirsearch 的价值就在于把“猜路径”这件事自动化、批量化,并且用一套判断逻辑帮你从海量响应里筛出真正存在的那些。
2.2 为什么选 dirsearch 而不是别的扫描器
市面上同类工具不少,有偏重量级的综合扫描平台,也有更轻的命令行工具。dirsearch 的定位很清晰:单文件、纯 Python、字典驱动、结果直观。我选它主要基于几个考量。
第一是依赖少、部署快。它基本只依赖 Python 标准库和少量第三方包,克隆下来装个依赖就能跑,不像某些工具需要一整套运行环境。第二是字典和参数高度可定制,你可以针对不同技术栈换不同字典,也可以精细控制并发、超时、递归深度。第三是结果输出友好,支持纯文本、JSON、HTML 等多种格式,方便后续做二次分析或者写进报告。
当然它也有短板,比如默认不做 JS 渲染,对纯前端路由的站点覆盖有限;再比如它本质是“盲扫”,不理解业务逻辑,所以结果里会有大量噪音需要人工判断。这些限制后面我会具体讲怎么应对。
2.3 一次完整扫描的四个阶段
我把用 dirsearch 做敏感文件排查的流程拆成四个阶段,后面所有内容都围绕这四个阶段展开:
- 信息收集与目标确认:搞清楚目标的技术栈、域名范围、是否有 CDN 或 WAF,确定授权边界。
- 字典与参数配置:根据技术栈选字典,设置并发、超时、状态码过滤、递归策略。
- 执行扫描与实时观察:跑起来之后盯住输出,及时调整,避免被封或跑偏。
- 结果分析与二次验证:从结果里筛出高价值路径,逐个验证,确认风险等级。
这四个阶段里,真正决定成败的往往不是扫描本身,而是前后的信息收集和结果分析。工具跑一遍只要几分钟,但判断哪些结果有意义、哪些是误报,才是体现经验的地方。
3. 核心细节解析:字典、状态码与过滤逻辑
3.1 字典是扫描的灵魂
dirsearch 自带了一份默认字典,位置在db/dictionary.txt附近,里面收录了常见的目录名和文件名。但说实话,默认字典只能算及格线,真正干活时几乎一定要换或补充。原因很简单:不同技术栈、不同框架、不同业务,暴露的文件命名习惯差别很大。
我一般会把字典分成几类来准备:
| 字典类型 | 典型内容 | 适用场景 |
|---|---|---|
| 通用目录字典 | admin、api、test、backup、upload | 任何站点的第一轮粗扫 |
| 技术栈字典 | 针对 PHP、Java、Node、Python 的特定路径 | 已知后端语言时精准打击 |
| 备份文件字典 | .bak、.old、.zip、.tar.gz、.sql | 排查运维遗留 |
| 版本控制字典 | .git、.svn、.hg 相关路径 | 排查源码泄露 |
| 配置文件字典 | .env、config、settings、credentials | 排查凭证泄露 |
| 编辑器残留字典 | .swp、~、.orig | 排查开发遗留 |
实际操作中,我会先用通用字典跑一轮,拿到基础目录结构,再根据响应特征决定第二轮用哪类字典。比如发现站点是 PHP 的,第二轮就重点上 PHP 相关的备份和配置字典。
提示:字典不是越大越好。几万条的字典跑起来又慢又容易被封,而且噪音极大。我通常把单轮字典控制在几千到一万条,分多轮、有针对性地上,效果比一次性怼大字典好得多。
3.2 状态码只是起点,不是终点
新手最容易犯的错,就是只看状态码。看到 200 就兴奋,看到 404 就跳过。实际上 dirsearch 的判断逻辑比这细腻得多,理解这些细节能帮你大幅减少误报。
- 200 OK:路径存在且返回内容。但要注意“软 404”——有些站点对不存在的路径也返回 200,只是内容是个统一的错误页。这种情况要靠响应长度和内容特征来区分。
- 301 / 302 重定向:路径存在,但被跳转了。可能是跳登录页,也可能是跳到了真实资源。要跟进去看跳转目标。
- 403 Forbidden:路径存在但禁止访问。这其实是个强信号,说明目录或文件真实存在,只是权限拦住了。很多敏感目录就是 403,值得记录。
- 401 Unauthorized:需要认证,同样说明资源存在。
- 500 Internal Server Error:路径存在但服务端处理出错,可能是参数缺失或代码 bug,值得进一步看。
dirsearch 默认会把 200、301、302、403、401、500 这些状态码都纳入结果,并且会显示响应长度。响应长度是区分软 404 的关键:如果一堆不同路径都返回同样的长度,那大概率是统一错误页;如果某个路径长度明显不同,就值得点进去看。
3.3 过滤与排除:把噪音挡在门外
扫描结果里噪音主要来自两类:一是软 404,二是 WAF 的拦截页。dirsearch 提供了几个参数来对付它们。
--exclude-status:直接排除指定状态码,比如把 404 排掉。--exclude-text:按响应内容排除,比如响应里包含“页面不存在”就跳过。--exclude-regex:用正则排除,适合处理有规律的错误页。--exclude-sizes:按响应长度排除,把那些固定长度的错误页过滤掉。
我个人的习惯是,第一轮先不加太多过滤,让结果全出来,观察一下错误页的特征(长度、关键词),第二轮再把这些特征写进排除参数,结果就干净很多。这个“先观察后过滤”的顺序很重要,一上来就乱排除,很可能把真实结果也误杀了。
3.4 并发、超时与请求头:别把目标打挂
dirsearch 默认的线程数不算激进,但很多人为了快会手动调高。这里有个经验:并发不是越高越好。线程太高,一是容易触发 WAF 的频率限制,二是会给目标服务器造成压力,三是结果里会出现大量超时和连接错误,反而干扰判断。
我的常用配置是线程控制在 20 到 30 之间,超时设 5 到 10 秒,并且加上--random-agents让 User-Agent 随机化,降低被识别为扫描器的概率。如果目标有 WAF,还要考虑加延迟(--delay),牺牲一点速度换稳定性。
请求头这块,有时候需要带上特定的 Cookie 或 Token 才能扫到登录后的路径。dirsearch 支持--header参数自定义请求头,这个在做授权测试时很有用,可以模拟已登录状态去扫后台功能。
4. 实操过程:从零跑通一次敏感文件扫描
4.1 环境准备与安装
先把环境搭起来。dirsearch 是 Python 项目,建议用 Python 3.7 以上版本。安装方式很简单,克隆仓库后装依赖即可。
git clone https://github.com/maurosoria/dirsearch.git cd dirsearch pip install -r requirements.txt装完之后可以用python3 dirsearch.py -h看一下帮助信息,确认能正常运行。如果提示缺某个模块,按提示补装就行。我建议单独建一个虚拟环境,避免和系统里的其他 Python 包冲突,尤其是做安全工具的时候,环境干净能省很多事。
注意:不同版本的 dirsearch 参数名可能有细微差别,比如早期版本用
-e指定扩展名,新版本可能调整了写法。跑之前先看-h输出,以你本地版本为准,别照搬网上过时的命令。
4.2 第一轮:通用字典粗扫
假设我们已经确认了授权目标,域名用target.example代指。第一轮我用通用字典做粗扫,目的是摸清目录结构。
python3 dirsearch.py -u https://target.example \ -e php,html,js,txt,zip,bak \ -t 25 \ --timeout=8 \ --random-agents \ -o report_round1.txt逐条解释一下这些参数:
-u:目标 URL,注意带上协议。-e:指定要尝试的扩展名。这里我加了 php、html、js、txt、zip、bak,覆盖常见的网页、脚本和备份类型。-t 25:25 个线程,比较温和。--timeout=8:单请求超时 8 秒,避免卡死。--random-agents:随机 User-Agent。-o:把结果输出到文件,方便后续分析。
跑完之后打开report_round1.txt,先别急着看具体路径,先扫一眼整体:有多少条结果,状态码分布如何,响应长度有没有明显的聚集。这一步是建立“这个站点的错误页长什么样”的基准。
4.3 第二轮:针对技术栈的精准扫描
第一轮结果里如果发现站点是 PHP 的(比如大量.php路径返回 200 或 403),第二轮就上 PHP 相关的敏感文件字典。
python3 dirsearch.py -u https://target.example \ -w /path/to/php-sensitive.txt \ -e php,bak,old,swp,sql,zip \ -t 20 \ --timeout=8 \ --exclude-status=404 \ --exclude-sizes=0B,1234B \ -o report_round2.json \ --format=json这一轮的关键变化:
-w:换成自定义的敏感文件字典。--exclude-status=404:直接排掉 404,减少噪音。--exclude-sizes:把第一轮观察到的错误页长度排掉(这里 1234B 是举例,实际用你观察到的值)。--format=json:输出 JSON,方便写脚本做二次处理。
JSON 格式的好处是结构化,你可以用 Python 或 jq 快速筛选出状态码为 200 且长度异常的条目,效率比肉眼翻文本高得多。
4.4 第三轮:递归与深度挖掘
如果前两轮发现了有价值的目录,比如/api/、/admin/、/backup/,就可以针对这些目录做递归扫描,挖更深层的文件。
python3 dirsearch.py -u https://target.example/api/ \ -w /path/to/api-dictionary.txt \ -t 20 \ --timeout=8 \ -r -R 3 \ --exclude-status=404 \ -o report_round3.txt-r:开启递归扫描。-R 3:递归深度限制为 3 层。
递归一定要设深度上限,否则可能无限往下钻,既慢又容易触发防护。我一般最多设 3 层,超过 3 层还没挖到东西,基本可以判断这个方向价值不大了。
4.5 结果分析:从一堆路径里挑出真金
扫描跑完只是开始,分析才是重头戏。我通常按下面的顺序过一遍结果:
- 先看 200 和 403:这两类最可能是真实存在的资源。403 尤其值得关注,因为它明确告诉你“东西在这,只是不让你看”。
- 看响应长度异常的条目:长度和错误页明显不同的,优先验证。
- 看路径命名:带 backup、config、admin、test、debug、old、bak 字样的,优先级拉满。
- 逐个手动验证:用浏览器或 curl 访问,确认内容,判断敏感程度。
验证的时候有个小技巧:不要只看能不能访问,还要看返回内容里有没有敏感信息。比如一个config.php返回 200,但内容是空的或者报错,那价值就有限;如果返回了数据库连接串,那就是高危。
curl -s -o /dev/null -w "%{http_code} %{size_download}\n" https://target.example/config.php.bak这条命令能快速拿到状态码和响应大小,适合批量初筛。
5. 常见问题与排查技巧实录
5.1 扫描结果全是 200,怎么判断真假
这是最典型的问题。很多站点配置了“友好错误页”,任何不存在的路径都返回 200,只是内容是个统一的提示页。判断方法:
- 对比响应长度:如果几百个路径长度都一样,基本是软 404。
- 看响应内容:抓一个明显不存在的路径(比如
/thisisnotexist12345),看它返回什么,把这个特征记下来。 - 用
--exclude-text或--exclude-sizes把这个特征排掉。
我一般会先手动请求一个随机路径,把它的状态码、长度、内容特征记下来,作为“基准错误页”,然后所有和基准一致的都视为不存在。
5.2 扫到一半被 WAF 拦了怎么办
WAF 拦截的表现通常是:突然大量返回 403、429,或者返回一个验证页面。应对策略:
- 降低线程数,加
--delay增加请求间隔。 - 换 User-Agent,用
--random-agents。 - 如果目标有 CDN,注意扫描可能打到 CDN 节点而不是源站,结果会失真。
- 分时段、分批次扫,别一次性怼完。
提示:被 WAF 拦不代表失败,反而说明你的扫描行为被识别了。这时候应该停下来分析拦截特征,而不是硬刚。硬刚的结果往往是 IP 被封,得不偿失。
5.3 字典里没有目标文件,怎么补
默认字典和通用字典覆盖不了所有情况。补充字典的思路:
- 从目标站点已知的路径反推命名规律,比如发现用
/v1/、/v2/做版本,就补版本相关路径。 - 参考同类技术栈的常见暴露路径。
- 从公开的漏洞报告和案例里收集路径样本(注意只用于授权测试)。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| 结果全是 200 | 软 404 错误页 | 找基准错误页,用 exclude 参数过滤 |
| 大量 403 | WAF 拦截或权限限制 | 降速、换 UA,区分是拦截还是真实权限 |
| 连接超时多 | 线程过高或网络问题 | 降线程、加超时、检查网络 |
| 结果为空 | 字典不合适或目标有防护 | 换字典、确认目标可达、检查是否被拦 |
| 递归跑不完 | 深度没限制 | 设-R上限 |
| 扫不到登录后路径 | 缺少认证信息 | 用--header带上 Cookie/Token |
5.5 几个我踩过的坑
第一个坑是忽略 robots.txt 和 sitemap。这两个文件经常直接告诉你站点有哪些目录,是免费的字典来源。扫描前先看一眼,能省不少事。
第二个坑是只看状态码不看内容。有次扫到一个 200 的路径,兴冲冲点进去发现是个空页面,白高兴一场。后来养成习惯,200 的条目一定看内容。
第三个坑是在没授权的情况下手痒。这个不用多说了,红线问题,任何情况下都不能碰。
第四个坑是结果不落盘。扫描结果一定要输出到文件,尤其是做资产梳理的时候,隔几天回头看,没记录就全忘了。JSON 格式最方便,能直接喂给脚本做统计和对比。
6. 把扫描变成常态化能力
单次扫描解决的是眼前的问题,但敏感文件暴露是个持续存在的风险。今天清理了.git目录,明天可能又有人误传一个备份包上去。所以真正有价值的做法,是把这套扫描能力沉淀成常态化机制。
我的做法是:把 dirsearch 的扫描命令封装成脚本,针对核心资产定期跑,结果自动落盘并和历史结果做 diff,一旦发现新增的可疑路径就告警。这样不用天天盯着,也能在暴露面扩大的第一时间知道。
脚本层面,核心就是把前面几轮的参数固化下来,用不同的字典跑不同资产,输出统一格式,再用一个简单的 Python 脚本做结果比对。diff 的逻辑很朴素:对比两次扫描的路径集合,新增的条目重点看。这个思路不复杂,但坚持做下来,对资产暴露面的掌控会强很多。
另外,扫描只是发现手段,真正的治理还得靠开发运维规范:版本控制目录不进生产、备份文件不放 Web 根目录、配置文件权限收紧、上线前做一次暴露面自查。工具能帮你发现问题,但解决问题还得靠流程。
最后分享一个我在实际使用中的小体会:扫描的价值不在于扫出多少条结果,而在于你能不能从结果里准确判断出哪几条是真风险。工具人人会用,但判断力是靠一次次实战喂出来的。多扫、多看、多复盘,比背参数有用得多。