上个月给一个内部业务系统做授权评估,我拿到目标域名后先按老规矩把 dirsearch 目录扫描跑了起来。本来只打算把信息收集流程走完,结果工具在十几分钟里翻出了一个备份压缩包和一个没有加访问控制的API文档目录,直接让这次评估的结论从"中风险"抬到了"高风险"。事后复盘时我一直在想一个问题:很多人不是不会用目录扫描工具,而是把扫描当成了一个"跑完交给漏洞扫描器"的机械步骤。目录扫描、字典爆破、敏感目录泄露挖掘这三件事其实是连在一起的,它们往往比单个漏洞更早决定了整个系统暴露面有多大。
这篇文章从我的实际使用经验出发,把 dirsearch 的安装、字典爆破的思路、敏感目录的验证方法,以及我在实战里踩过的坑一次性讲清楚。不管你是在做安全测试、网站运维,还是写业务代码的同时想自己检查一下系统,这篇内容都适合。同时也必须说清楚:文中涉及的操作只允许用于自己拥有权限的资产、本地搭建的靶场,或者已经拿到书面授权的测试项目。
1. 目录扫描在信息收集中的定位:为何总被忽视却又必不可少
1.1 一次授权评估给我的教训
那是一次常规授权范围内的评估,目标系统是一个内部后台,登录页上没有明显的注入点,漏洞扫描器跑下来也只报了一些低危信息项。按很多人的习惯,到这里就可以收工写报告了。但我多跑了一次目录扫描,用 dirsearch 配合一份稍微大一点的字典,结果在 /backup/ 目录下列表里看到一个月度数据库备份文件,文件名称里的日期比割接时间早一天。这意味着什么不言而喻:备份文件直接暴露在web目录下,谁拿到这个文件都能把整库拖走。
那次之后我养成了一个习惯:不管目标看起来多安全,只要允许,一定会先做目录枚举。原因很简单,目录扫描是在模拟一种最基础的攻击者行为——人类系统里总有开发者为了方便留下的"后门路径",你不可能靠漏洞扫描器发现这些,因为它们不是漏洞,是暴露面。而暴露面越大,出现实际问题的概率就越大。
1.2 目录扫描工具到底在做什么
目录扫描的原理一点都不玄乎:它按照字典里的一列候选路径,逐个向目标发送HTTP请求,然后根据响应的状态码、返回数据长度、页面标题等信息,判断这个路径是否真实存在。dirsearch 就是这个过程的自动化实现,它把低频、逐条、人工观察的操作变成了高并发的机器枚举。
很多人会把它和爬虫混淆。爬虫是从已知入口出发,顺着页面里的链接一条一条往下抓;目录扫描正好反过来,它不依赖页面里给出的链接,而是靠字典猜测"没有链接指向但物理上存在的资源"。这就解释了为什么漏洞扫描器和爬虫都扫不到的东西,目录扫描反而能挖出来——很多敏感文件根本不在页面上留入口,比如 /config.php.bak、/logs/error.log,这些路径只能靠字典碰。
1.3 使用边界:授权与合规是前提
我知道很多刚入行的人拿到工具后会忍不住对陌生站点来一下,这个我必须泼一盆冷水。目录扫描虽然只是发HTTP请求,但在没有授权的情况下,这种行为在法律上可能被认定为未经授权的访问尝试,轻则被封IP,重则承担法律责任。我自己每次操作前都会核对三件事:目标是否在授权范围内、是否写明了测试时间窗口、扫描行为是否可能对目标造成超出预期的负载。
合规不是套话,而是这个职业能长期做下去的前提。你可以在自己电脑上用 Docker 搭一个靶机,也可以申请一个测试域名,把这些命令和流程跑熟,再接手真实项目。做法完全一样,只是目标换成你能负责的系统而已。
2. dirsearch 的安装与环境准备:绕开那个常见的 apt 报错
2.1 最简单的方式:直接拉取仓库
在 Linux 环境里,很多人按照旧教程执行apt install dirsearch,结果收到提示unable to locate package dirsearch。我第一次遇到这个报错也愣了半天,还以为软件源出了问题。后来弄明白,Debian 和 Ubuntu 的官方软件源里根本没有打包这个工具,它并不是一个"缺源"的问题,而是官方源压根没有这个包。
最省事的办法就是直接从 GitHub 把项目拉下来,进入目录之后看一下依赖文件,安装 requests 等依赖,然后调用入口脚本。基本步骤是:
git clone https://github.com/Arachni/arachni.git等等,这里容易混乱,dirsearch 的仓库地址不是 Arachni,别记串了。正确的做法是去 GitHub 搜索 dirsearch 找到对应仓库,然后:
git clone https://github.com/.../dirsearch.git cd dirsearch pip3 install -r requirements.txt python3 dirsearch.py -u http://example.com -e php拉下来之后直接运行就能看到 banner 和使用帮助。整个过程不需要编译,也不需要额外配置环境变量,系统 Python3 版本在 3.7 以上基本都能跑。
2.2 安装依赖时遇到 unable to locate package dirsearch 怎么办
这个报错几乎天天有人问,我把排查思路完整捋一遍。首先确认你用的是什么发行版:Kali Linux 的软件源里确实有 dirsearch 的包,所以apt install dirsearch在 Kali 上通常没问题;Debian、Ubuntu 以及基于它们的发行版,源里没有这个包,所以才会报unable to locate package dirsearch。
不要在遇到这个报错后直接去改/etc/apt/sources.list添加杂七杂八的源。很多时候会把系统包管理搞坏,得不偿失。推荐的做法是:
- 先用
git clone拿到源码; - 查看
requirements.txt里有哪些依赖; - 用
pip3 install -r requirements.txt安装; - 如果提示 pip 外部管理环境限制,就加
--user参数或者创建虚拟环境。
如果你特别想用 apt 方式管理,也可以检查系统里是否启用了 Kali 源或者第三方安全工具源,但不建议在生产环境里这么干。直接源码运行不会影响系统其他部分,升级时git pull就行了,反而更干净。
2.3 在 Windows 和 macOS 下的可行方案
我用过很多环境,说到底 dirsearch 就是个 Python 脚本,跨平台能力很好。在 Windows 上,如果不想装虚拟机,最简单的办法是安装 Python3,然后用 Git Bash 或 PowerShell 进到项目目录里运行同样的命令。需要注意 Windows 下文件名大小写不敏感,有些字典里同时存在index.php和Index.php,去重的时候要考虑这个差异。
macOS 相比会顺一些,系统自带 Python3 的概率比较高,但也建议先安装 Homebrew,再用brew install python把 Python 环境补齐。各平台的使用方式没有本质区别。
| 环境 | 推荐方式 | 适合场景 |
|---|---|---|
| Kali Linux | apt 安装或直接用源码 | 渗透测试专用机,开箱即用 |
| Debian/Ubuntu | git clone + pip3 | 日常批量任务,环境干净 |
| Windows | 原生 Python3 + Git Bash | 临时调试,或没有虚拟机时 |
| macOS | Homebrew + git clone | 个人开发机测试 |
| Docker | 官方镜像或手动构建 | 需要快速隔离环境时 |
3. 字典爆破的核心逻辑:从"跑得快"到"命中准"
3.1 为什么同一个字典在不同目标上结果差异巨大
很多新手喜欢找一个"万能大字典"走到哪用到哪,结果在一套系统上扫出一大堆路径,换一套系统就什么都扫不到。问题多半不在工具,而在字典和目标的匹配度。不同技术栈、不同开发团队,习惯命名的路径差别非常大。
PHP 项目常见uploads、admin、include、backup;Java 项目常见/actuator、/WEB-INF/、/swagger-ui.html;前后端分离项目则集中在/api/下面。如果你拿一套以 PHP 字典为主的词表去扫 Java 系统,自然不会有什么收获。目录扫描的命中率直接取决于字典覆盖了目标可能存在的路径模式,而不是单纯看词条数量。
3.2 字典来源与自制思路
字典可以从几个地方积累。dirsearch 自带的db/dicc.txt是基础,SecLists 里的 Discovery/Web-Content 目录也有大量分类词典。但我不建议直接全量跑几百万条的巨型字典,耗时低效不说,还容易触发目标系统告警。正确做法是基于目标特征组装一份中等规模的字典。
我开始一个新项目时,通常先做三步:
- 用爬虫抓取目标首页和公开入口的 URL,把路径部分拆出来作为种子词条;
- 根据技术栈猜测可能存在的目录和常见文件名,比如
backup、test、old、tmp、sql、log; - 把种子词条和常见扩展名组合,生成候选路径。
扩展名是爆破里的重要维度。目录扫描的扩展名不需要太多,通常保留这几个就够用:html、php、asp、aspx、jsp、txt、zip、sql、bak、gz、tar、env、json、xml。其中备份类扩展名(zip、sql、bak、tar.gz)价值最高,一旦扫描命中,往往意味着可直接下载源码或数据库。也可以组合双后缀,比如index.php.bak、config.php.old,这类文件在真实项目中出现概率很高,字典里应当有专门的组合词条。
3.3 用 Burp Suite 处理自定义字典的小技巧
在这个话题里总有人提到 burpsuite 爆破字典,我要先说清楚:目录场景下的"爆破"不是指爆破后台密码,而是对 URL 路径做暴力枚举,即用字典逐个尝试候选路径。字典本身是可以跨工具复用的,我经常用 Burp Suite 来做 dirsearch 不好处理的场景。
比如目标系统要求特殊的认证 Cookie 或自定义请求头,而 dirsearch 的--cookie参数又没法灵活构造时,我会把 Burp 的 Intruder 作为补充验证工具。做法不复杂,在 Burp 里拦截一个请求,把 URL 路径部分标记为 payload 位置,然后导入字典文件,设置好线程和重放策略就可以手动验证。不过这更适合小批量验证,大规模扫描还是交给 dirsearch 更高效。
另一个技巧是用 Burp 的 Content Discovery 功能做一个快速预扫,然后把预扫结果里的路径导出,和现有字典合并去重,生成一份针对当前目标的定制字典。这样既结合了 Burp 的可视化特性,又能把最终批量任务交给效率更高的 dirsearch。
3.4 扫描参数与响应码判断
dirsearch 的常用参数需要熟练记忆,否则用起来很别扭。我给出一个最常用的命令组合:
python3 dirsearch.py -u http://example.com -e php,zip,bak,txt,env -w /path/to/dict.txt -t 20 --random-agent -x 404,403 -o result.json这条命令的含义是:扫描目标站点,扩展名指定为php,zip,bak,txt,env,使用指定的字典,线程数为 20,随机 User-Agent,排除 404 和 403 状态码,把结果写入 JSON 文件。
参数不是越多越好,这里有个关键取舍:线程数过高确实能加快扫描,但也容易导致目标服务响应延迟、日志刷屏、甚至被云防火墙或 WAF 拦截。国内很多站点前面有 WAF,高频请求会在几分钟内触发封禁,所以线程数控制在 20 到 50 之间比较稳妥,必要时还要加--min-rate和--max-rate限制请求速率。响应码的判断我会在后面的章节详细展开,因为这是最容易误判的地方。
| 参数 | 作用 | 我的建议 |
|---|---|---|
| -e | 指定扩展名 | 不要一次给太多,控制在 8~10 个 |
| -w | 指定字典文件 | 优先自制定制字典 |
| -t | 线程数 | 公网目标 20 起,内网可适当调大 |
| -x | 排除状态码 | 通常先排除 404,403 看情况 |
| --random-agent | 随机 User-Agent | 建议开启,降低被规则匹配的概率 |
| --recursive | 递归扫描 | 建议配合 --max-depth 使用 |
| -o | 输出结果 | 保存 JSON,便于后续分析 |
4. 敏感目录泄露挖掘:响应码判断、验证与典型场景
4.1 识别扫描结果中的真信号和假信号
拿到扫描结果之后第一件事不是高兴,而是警惕误报。dirsearch 是一个高效的"枚举器",它不知道目标返回的 200 页面是不是真的代表路径存在。不同框架对未知路径的处理方式不同:传统的 PHP 站点通常返回 404,但一些单页应用会为所有前端路由返回 200;自定义 404 页面如果返回 200,那么整张结果表看起来会像假阳性大杂烩。
我的判断顺序是这样的:先看状态码,再看返回长度,最后看标题和页面特征。如果批量结果里几乎每个路径的返回长度都完全相同,比如每个都是 4 万字,那大概率是软 404,也就是服务器把所有未知请求都重定向到了一个自定义错误页。这时候把这串内容保存下来作为基准,再用--exclude-status或--exclude-text排除匹配项,能让真实路径浮出水面。
| 状态码 | 可能含义 | 需要做什么 |
|---|---|---|
| 200 | 资源存在并可访问 | 打开页面手工确认,区分是否为软404 |
| 301/302 | 路径存在,可能被重定向 | 跟随重定向看跳转到哪,可能是后台入口 |
| 401/403 | 路径存在但访问受限 | 记录路径,可能是隐藏管理入口,禁止绕过 |
| 404 | 路径不存在 | 忽略,但注意是不是自定义404返回了404 |
| 500 | 服务端错误 | 可能是路径存在但执行出错,值得记录 |
4.2 典型的敏感目录/文件清单
经过多次实战,我把最值得注意的敏感目录和文件分成了几类。
第一类是版本控制相关:/.git/、/.svn/、/.hg/。这类目录一旦泄露,攻击者可以通过特定工具下载完整的源码历史,敏感信息可能藏在不同 commit 里。第二类是备份与压缩包:/backup/、/bak/、/db.sql、/www.zip、/site.tar.gz。这类文件名字越具体越危险。第三类是配置文件与密钥:/.env、/config.php、/config.inc.php、/web.config、/application.properties。.env文件现在特别常见,里面往往有数据库口令、第三方 API Key。第四类是管理入口和调试接口:/admin/、/manager/、/phpmyadmin/、/actuator/、/swagger-ui.html、/error。第五类是信息聚合页:/robots.txt、/sitemap.xml、/crossdomain.xml,它们本身不一定敏感,但会给攻击者提供路径线索。
遇到这些路径时,我只做存在性确认,不会去深入利用。比如看到.git/config,我会记录并报告,而不是把整仓拉下来翻历史。做授权测试也一样,点到为止才是专业的做法。
4.3 手工验证:curl 和浏览器不能少
扫描结果只是候选队列,手工验证是必经步骤。我习惯先用 curl 看一下响应头,比如:
curl -I http://example.com/.git/config如果返回200且 Content-Type 是text/plain或application/octet-stream,基本可以确认文件可读。再用完整请求看内容,但要注意:如果是数据库备份等包含敏感数据的文件,不要下载到本地,只需要确认大小和响应头即可,避免对数据造成二次扩散。
用浏览器验证时我会开无痕窗口,不携带任何 Cookie,尽量用和扫描时一致的环境访问,避免登录态干扰判断。同时打开开发者工具,看 Network 里真实请求的 Status Code 以及耗时,判断是不是某种清缓存的假象。有些系统会针对扫描器 UA 返回特殊页面,所以验证时用普通浏览器 UA 反而更接近真实用户视角。
5. 实战中容易踩的坑与调优经验
5.1 限速、随机UA与访问控制的平衡
目录扫描看起来只是发 HTTP 请求,但真正在公网环境跑过的人都知道,它比漏洞扫描器更容易被目标发现。原因在于请求节奏太规律,一旦命中 WAF 的 URL 频率阈值,整个过程会立刻被中断。我见过不少新手抱怨"一不小心把目标扫挂了",大多数情况下就是线程开太大、速率没限。
我现在的做法是:先用--random-agent打底,再通过--min-rate和--max-rate约定扫描速率范围,比如每秒 10 到 20 个请求。同时用--header附加一些业务请求头,模拟真实浏览器的会话特征。如果目标系统登录后才能看到大部分路径,我会在授权范围内,把认证 Cookie 通过--cookie传进去再扫。记住一个原则:扫描应尽量不干扰业务,宁可慢一点也不要造成服务不可用。
5.2 递归扫描的深度和范围控制
递归扫描是一个典型的"香但容易失控"的功能。dirsearch 允许你对发现的目录继续向下扫描,但这意味着请求次数会呈指数级增长。一个/api/目录下可能有几十个子路径,每个子路径下又有更多文件,不加限制地递归,可能几小时都跑不完,还容易把目标日志刷爆。
我通常会把递归深度控制在 2 到 3 层,并用--max-depth做硬限制;同时对较大目录单独开一个扫描任务,指定针对性字典。比如发现/api/之后,我不会全局递归,而是单独执行python3 dirsearch.py -u http://example.com/api/ -w api_dict.txt -e json,php -t 20,这样既保证覆盖,又不会让任务失控。
5.3 误报处理与结果留存
误报处理的核心是建立"该目标的 404 基线"。扫描开始前,先手工访问一个几乎不存在的随机路径,比如http://example.com/this-path-should-not-exist-12345,把响应状态码、长度、标题记下来。然后在扫描命令里排除这个特征,或者扫描结果出来后把特征一致的记录全部删掉。
扫描结果建议一律输出成 JSON 文件,里面包含 URL、Status、Content-Length、Redirect 等信息,方便后续检索和写入报告。我一般还会把命中路径分成"已确认""需进一步验证""误报"三类,在报告里只放已确认的内容。目录扫描的产出如果不做验证直接贴进报告,反而会降低整个报告的可信度。
6. 不只是扫描:从防御侧看目录泄露的修复建议
6.1 服务端配置层面的收敛
目录扫描能不能挖到东西,首先取决于目标服务端有没有做基本的收敛。很多泄露问题其实一眼就能在配置里发现:Apache 开启了目录列表、Nginx 里autoindex on、Web 根目录里摆着备份文件。修复方式也很直接:在 Apache 配置里去掉Options Indexes,在 Nginx 配置里把autoindex设为off,然后把备份文件从 Web 可访问目录中移走,放到内网存储或对象存储的私有桶里。
对于本来就敏感的目录,比如后台、API 文档、日志目录,服务端应该加访问白名单或身份认证,不要让它们直接裸奔在公网。即使路径被字典猜中,也要有一层访问控制兜底。
6.2 开发与发布流程中的自检
很多时候敏感目录泄露不是运维故意的,而是构建发布流程把关不严。.git目录忘记从发布产物里排除、.env文件被当作配置模板打进了包、测试数据库备份被复制到 web 目录当临时文件,这些场景我见过太多次。
我建议开发团队在 CI/CD 流程里加入一道检查:构建产物里禁止出现*.sql、*.bak、.env、*.zip、.git/、.svn/这些高风险文件,一旦检测到就让构建失败。同时也建议运维侧每隔一段时间用 dirsearch 做一次模拟扫描,特别是针对刚上线的新系统,快速确认没有明显的目录泄露。这个操作不复杂,但能省下后续处理数据泄露的大麻烦。
6.3 常见敏感路径快速自查清单
| 路径/文件 | 泄露风险 | 推荐处理 |
|---|---|---|
| /.git/ | 源码与历史版本泄露 | 生产环境彻底移除,禁止部署 |
| /.svn/ | 源码与版本信息泄露 | 从 Web 目录中删除 |
| /.env | 数据库口令、密钥泄露 | 移到 Web 根目录之外,使用环境变量注入 |
| /backup/ | 备份文件被下载 | 关闭目录列表,移至私有存储 |
| /actuator/ | 系统运行状态、配置泄露 | 不要暴露公网,启用认证或关闭敏感端点 |
| /swagger-ui.html | API 结构泄露 | 仅在开发环境开启,生产环境禁用或加白名单 |
| /phpinfo.php | 配置信息泄露 | 用完即删,不要在正式环境保留 |
| /admin/ | 未授权管理入口 | 加访问控制与二次认证 |
| /logs/ | 日志信息可能包含会话/参数 | 禁止放在 Web 可访问目录 |
目录扫描这个技能,说难不难,一个命令就能跑起来;说简单也不简单,真正拉开差距的地方在于对字典的理解、对误报的判断、对结果的验证。我现在的习惯是,每一次授权评估结束后,会把所有命中路径整理成一张表,连同验证结果一起发给开发团队,还会附上对应的修复建议。目录扫描真正有价值的地方不是某一次跑出多少条 URL,而是把系统的暴露面量化出来,让开发人员意识到一个.git备份文件或者一份没有权限控制的.env配置文件,可能比某个注入漏洞更先把系统暴露在风险里。以后你在自己的项目里跑通第一条 dirsearch 命令时,不妨多花十分钟看看结果里有没有那些不该出现的路径。