简介:基于 Python 的目录扫描工具 Dirsearch-master 面向渗透测试与网站安全评估人员,用于自动化探测站点敏感目录、隐藏文件与子目录结构,支持 HTTP、HTTPS 及 Socket 协议,结合多线程与自定义字典能显著提升目录枚举效率。压缩包共 217 个文件、约 933KB,主体包含 92 个 pyc 编译文件、50 个 py 源码、57 个 txt 字典/说明文本,另附 markdown 文档、YAML 配置、Dockerfile 与 HTML 报告模板等,可支持本地运行、容器化部署及结果导出(文本、XML、JSON 等)。资源在 CSDN 已有 2186 人浏览学习。获取后可得到完整工具目录结构与默认配置文件,便于二次开发、定制漏洞扫描字典,也可参照 README 与变更日志快速掌握安装与调试方法,适合希望深入理解目录扫描实现原理的安全爱好者与初学者。
1. 目录扫描工具选型:为什么我把 dirsearch-master 留作默认项目
经常有人把目录扫描工具 dirsearch-master 当成一个“字典炸弹”,扔进去跑完、看个红色高亮就收工,其实这恰恰是最容易漏东西的用法。目录扫描解决的不是“能不能爆破出后台”,而是“目标到底有哪些业务边界值得往下挖”。在我经历的几次模拟授权测试里,真正让后续突破产生转机的一次,搜出来的并不是 admin 入口,而是一个没被任何导航页引用的 backup 路径。这份项目包的价值在于:线程可控、输出可清洗、支持递归和自定义字典,适合作为目录枚举的基础组件,也适合二次封装进自动化的安全测试流程。
2. 把 dirsearch-master 跑起来:安装、启动与第一份扫描报告
2.1 项目文件结构:入口、字典和依赖库
解压dirsearch-master.zip之后,不要急着双击运行,先看目录结构。这个项目包的核心分为三块:dirsearch.py是命令行入口,db/目录下放着携带的字典和扩展名列表,lib/里是实现请求、响应解析和控制器逻辑的模块。对不需要改源码的使用者来说,真正值得关心的就两个文件:db/dicc.txt是默认字典,db/extensions.txt是默认探测的扩展名集合。
我一般会把 zip 解压到独立目录里:
# 解压并进入项目目录 cd ~/tools unzip dirsearch-master.zip cd dirsearch-master # 用一个简单站点做一次冒烟测试 python3 dirsearch.py -u http://192.168.1.5:8080 -e php,txt,zip第一行命令是常规的解压和进入目录操作;第二行才是真正让项目跑起来的关键。python3指定解释器版本,新版本 dirsearch 需要 Python 3.7 以上,如果你遇到导入模块报错,大概率是 Python 版本不够旧。-u后面紧跟目标 URL,-e指定扩展名,多个后缀用逗号隔开,这样扫描器在拼接路径时会尝试admin.php、admin.txt、admin.zip,而不是只用默认的扩展名集合。
跑完第一遍后,你会看到类似下面的输出:
[12:12:01] 200 - 4KB - /admin/ [12:12:02] 301 - 0B - /api -> /api/ [12:12:02] 403 - 1KB - /server-status这里每行包含四个关键信息:时间、HTTP 状态码、响应大小、路径。301 后面的箭头表示跳转目标,/api被重定向到/api/,说明这是个带目录形式的接口服务。输出里的 200 和 301 条目最值得人工复核,403 也未必是“无权限”,有可能只是服务器对不存在路径的默认拒绝,需要结合响应体大小判断。
2.2 核心启动参数:从命令行到结果落盘
第一次跑完,大多数人会直接看控制台,然后关掉窗口,这是最容易损失证据的操作。控制台输出会被终端缓冲截断,尤其是目标站点路径非常多、结果上千条时,前面的记录很难翻回去看。所以从第二次扫描开始,我每次都会加结果保存参数。
# 推荐的基础命令:不输出大量控制器日志,只保存命中的高价值条目 python3 dirsearch.py \ -u http://192.168.1.5:8080 \ -e php,txt,conf \ -x 404 \ -o report-2024-06-01.txt \ --format plain \ --full-url这里的-x 404是把 404 状态码从结果里排除掉,减少无效噪音;-o指定输出文件路径;--format plain表示输出纯文本格式,方便用 grep 继续过滤;--full-url让结果里显示完整的 URL 而不是相对路径。命令末尾的反斜杠只是换行连接符,在 Windows 终端里可以去掉反斜杠写成一整行。
保存下来的结果文件,我通常会再跑一次grep -E "200|301" report-2024-06-01.txt,只保留真正能访问的路径。之所以不直接改-x排除 301,是因为 301 之后的跳转目标经常暴露新的接口前缀,比如/api跳到/api/v2/,这个版本信息在控制台里很容易被忽略。
3. 调参细节:线程、超时、字典与递归的取舍
3.1 并发、超时和延迟:速度与稳定性的平衡点
很多第一次用目录扫描工具的人都会把线程调得很高,因为看到并发 50、100 的扫描器觉得“很爽”。但在真实的授权测试里,线程数一高,请求频率就会挤压目标服务的正常带宽,导致三类翻车:一是大量 429 限流响应,把延迟拉到几十秒;二是被前置网关记录成恶意访问,直接封锁来源 IP;三是目标服务扛不住并发,请求超时比例升高,本来存在的目录反而没有被探测到。
我自己的习惯是,先从--threads 10开始,观察前 50 条请求的响应分布。如果 200/301 占比较高且超时很少,再逐步提高到 20;如果出现连续等待,说明服务响应慢,要么把线程降回来,要么把--timeout从默认的 7.5 秒改成 3 到 5 秒。目录扫描本质是并发请求同一个源站的多个路径,目标服务对并发处理的优先级、负载均衡策略都会影响结果,而不是越用力越好。
--delay参数控制在每个请求之间插入的空闲时间,单位是秒。这个参数有两个适用场景:一个是目标站点有请求频率限制,加 0.2 到 0.5 秒延迟可以绕开限速;另一个是对外网目标不想在网关日志里留下过于密集的访问记录。对内网测试环境,我一般不加延迟,因为内网延迟本身就很低,扫描频率并不高。
3.2 响应状态码的筛选逻辑:不是只有 200 才有意义
目录扫描的结果里,200 只是最有价值的响应码,但不是全部。301/302 能暴露接口的跳转关系和路由版本,403 可能意味着文件真实存在于服务器上但当前权限不允许访问,401/407 说明背后有一套认证体系。如果你用-x一次性排除太多状态码,很容易把关键信息丢掉。
比较稳的筛选策略是:把 404 和 500 排除掉,保留 200、301、302、401、403。然后针对 401 和 403 单独做一次手工探测,带上不同的路径变体、常见文件名后缀、大小写变体,确认这些响应到底是“目录存在但没有权限”,还是“默认的访问拒绝”。
从参数表里能看到这几个参数的实际作用:
| 参数 | 典型值 | 作用 | 适合场景 |
|---|---|---|---|
--threads | 10~20 | 并发请求数量 | 内网或响应较快的应用 |
--timeout | 0.5~5.0 | 单请求超时时间 | 慢速站点或弱网 |
--delay | 0.1~0.5 | 每个请求之间的间隔 | 被限流或内网演示环境 |
-x | 404,500 | 排除指定状态码 | 减少无效噪音 |
--max-runtime | 120~300 | 扫描最大运行秒数 | 现场测试或时间受限 |
--recursive | / | 递归扫描子目录 | 目录结构层级较深的目标 |
3.3 字典自定义:默认字典不够用时的替代方案
db/dicc.txt这份默认字典覆盖了常见的系统目录、备份路径、日志文件、配置文件、后台入口,但它的粒度偏通用。如果你的目标是一个企业级应用,可能会有以下特征:目录名带业务语义(如accountmanager、paycallback、fileservice),文件后缀是工程里特有的(如.do、.action、.axd)。这时候默认字典命中率会明显下降。
我不会说一定要生成一份完全不一样的字典,而是建议在默认字典之外做一个“追加列表”。常见做法是准备一个extra.txt,把在同类项目里反复出现的路径填进去,然后用下面的命令合并去重:
# 合并默认字典和自定义字典,并去掉重复项和空行 cat db/dicc.txt extra.txt | sed '/^$/d' | sort -u > merged_dict.txt # 用合并后的字典扫描并指定扩展名 python3 dirsearch.py -u http://192.168.1.5:8080 -w merged_dict.txt -e action,do,php第一行命令生成merged_dict.txt,sort -u的作用是去重同时让字典按字典序排列,这样扫描结果里能明显看到同类路径聚集在一起。第二行用-w指定新字典,-e action,do,php把目标常见的业务后缀也加进来。这个做法看起来只是加了几行命令,却能把命中率提升一截,尤其是针对老旧的 Java 项目和 .NET 平台的应用。
4. 预处理与插件化输出:socket 探活与结果联动
4.1 开跑前用 socket 确认目标协议,避免无效扫描
目录扫描跑得再快,也怕目标主机本身就不可达。很多时候明明看着端口开着,但协议不对、代理拦截、防火墙限流,导致扫描器请求大量超时,上万条字典项全部超时,最后什么有效结果都拿不到。最省时间的办法是在跑扫描前先用 socket 做一次轻量探活,确认 80/443/8080 端口是否真正开放,以及端口背后是不是 HTTP 协议。
我常用的探活脚本是几十行的小工具,核心逻辑如下:
import socket def check_port(host, port, timeout=2): # 控制连接超时,避免因单端口堵塞拖慢整个预检查 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(timeout) try: sock.connect((host, port)) return True except OSError: return False host = "192.168.1.5" for port in (80, 443, 8080): alive = check_port(host, port) print(f"{host}:{port} -> {'open' if alive else 'closed'}")socket.socket(socket.AF_INET, socket.SOCK_STREAM)创建的是标准的 TCP socket,settimeout(2)把单次连接超时限制在 2 秒,避免默认几十秒的内核超时拖慢探活。connect((host, port))是真正的 TCP 三次握手动作,握手成功就说明端口开放。这里不需要发送 HTTP 请求,因为目录扫描的第一步只要确认端口能够建立 TCP 连接。
如果端口探活显示 80 是开着的但 443 关闭,那扫描目标就应该用http://192.168.1.5,而不是https://192.168.1.5。后者会强制走 TLS 握手,握手失败后扫描器会反复重试,既浪费资源又让结果里堆满超时记录。
4.2 扫描结果导出为 JSON,再喂给其他处理插件
目录扫描的价值要从“扫描”延伸到“消费结果”,这也是我一直强调输出格式的原因。默认的 plain 文本适合人眼扫一遍,但要写程序做二次分析,JSON 格式会比纯文本稳定得多。dirsearch 支持--format json -o report.json,输出结构里每条结果包含状态码、URL、响应长度、跳转目标,机器可读性很好。
拿到 JSON 后,我会写一个很小的处理脚本,把 200 状态码的目录路径提取出来,再做一次存活确认:
# 用 jq 提取状态码为 200 的路径,生成待复核清单 cat report.json \ | jq -r '.[] | select(.status == 200) | .url' \ > direct_urls.txt # 逐个请求确认路径真实可访问 cat direct_urls.txt | xargs -n1 -P5 curl -s -o /dev/null -w "%{http_code} %{url}\n"第一行里的jq是命令行 JSON 解析工具,.[]表示遍历数组元素,select(.status == 200)是条件过滤,.url只取 URL 字段。第二行把 URL 列表交给xargs,-n1表示每次处理一个参数,-P5是并发 5 个进程,curl的-w参数定义输出格式,这段命令最后会生成一个状态码加 URL 的清单。看到200的 URL 再做下一步验证,尽量避免只在目录扫描工具的黑匣子里看结果就下结论。
5. 避坑清单:目录扫描过程中容易翻车的几个地方
5.1 现象:扫描结果全是 200,但浏览器里全是 404
扫描完看到满屏 200,感觉发现了大量“后台”,手一抖全存进报告,结果点开一半都是 404。原因:很多现代 Web 框架在路由层做了通配,比如单页应用、前后端分离系统,对任意路径都返回前端模板,但由前端脚本再动态渲染,后端实际看不到这些路径。解决:没有急着排除 200,而是把扫到 200 的路径用 curl 再看一眼Content-Length。如果大量路径的响应长度完全一致,那基本可以断定是同一个页面模板通配了所有路径,这批路径应该整体降权,只留少数能返回不同响应体的路径继续分析。
5.2 现象:线程开到 30,扫了十几条目录就被网关拦截
现象是扫描刚跑没多少,后续请求全部变成 429 或 403。原因:目标前置网关有并发连接限制,短时间内的密集请求触发了限流规则。解决:先停掉当前扫描,等连接恢复,然后降低线程数并增加请求间隔。我现在的做法是--threads 10 --delay 0.2,先跑 5 分钟看命中率,如果目录数量正常且没有限流,再以 5 的步进往上加,最高不超过 20。这个调整对一台测试服务器的资源占用也友好得多,不至于因为扫描太猛影响同网络里的其他任务。
5.3 现象:把 401/403 全部排除,结果漏掉关键接口
刚开始用扫描器时,我觉得 401/403 都是“访问不到”,直接-x 401,403把它们过滤掉。后来发现一个系统里有个/admin/config路径,扫描时返回 403,因为默认没有带管理员的角色信息;但如果带上登录后的会话 Cookie,这个路径能返回完整的配置内容。原因:某些访问控制只在中间层校验,目录是真实存在且有业务信息的,状态码并不能代表不存在。解决:不要默认排除 401/403,单独把它们拎出来,结合抓包得到认证信息,再针对性地看一次。
5.4 现象:递归扫描越跑越深,目录几千条但真正有用的没几个
递归扫描是个看起来很聪明、实际很费资源的功能。如果指定了--recursive --recursion-depth 3去扫一个深层次嵌套的目录,扫描时间会指数级增长。原因:递归会让每个子目录下所有字典项再跑一遍,路径一多,请求量瞬间膨胀。解决:先用--recursive-depth 1只递归一层,跑完看结果里哪些子目录确实值得深入,再对该子目录单独做一次-u <url>/subdir/扫描,本质上是把递归拆成两步,费一点人工但省很多时间。
5.5 现象:结果文件打开以后一头雾水,后缀分辨不出格式
部分版本 dirsearch 的输出格式由-o后面的文件名后缀推断,例如.json自动生成 JSON,.xml自动生成 XML;但如果你写-o report.txt --format json,那文件内容会变成“以.txt结尾的 JSON 数据”,记事本打开后是挤在一起的字符串。原因:--format和文件名后缀不一致,查看工具没有正确识别格式。解决:指定输出时让后缀和--format保持一致,我一般固定写--format json -o report.json或--format plain -o report.txt,并在文件名里加日期,方便后面按时间线对比。
6. 进阶技巧:把扫描结果做成可反复对比的资产记录
6.1 固定命令模板与结果归档
目录扫描的终点不是拿到一份结果文件,而是让结果沉淀成目标资产变化的记录。同一个授权系统可能每隔一两周就更新一次,新增了接口、下掉了旧服务,谁能快速发现这些变化,谁就有更多时间去做后续分析。我的做法是:为每个目标保存一份独立的命令模板,把 URL、端口、扩展名、字典路径都写成固定的 shell 脚本,每次扫描只需改时间标签。
#!/bin/bash # 固定脚本:对同一目标重复做目录扫描并归档结果 STAMP=$(date +%Y%m%d) python3 dirsearch.py \ -u http://192.168.1.5:8080 \ -w merged_dict.txt \ -e php,action,do \ -x 404,500 \ --format plain \ --output scan-${STAMP}.txt这里的STAMP=$(date +%Y%m%d)是在生成当天日期,--output scan-${STAMP}.txt把每次扫描结果写进带日期的文件,避免覆盖旧报告。运行一次就能得到一个按日期归档的目录快照,方便以后回溯任何一次扫描的原始输出。
6.2 用 diff 对比两次扫描,找出新增路径
扫完两次或多次后,真正有价值的是“哪些路径是新增的”。固定脚本之外,我会统一用sort把状态码字段去掉再排序,然后做对比:
# 提取路径并排序,让两次扫描结果可以直接对比 grep "200" scan-20240601.txt | awk '{print $2}' | sort -u > day1.txt grep "200" scan-20240615.txt | awk '{print $2}' | sort -u > day2.txt # 找出第二次新增的路径 diff day1.txt day2.txt | grep '^>'第一段命令里的awk '{print $2}'是从[12:12:01] 200 - 4KB - /admin/这行记录里切出 URL 路径字段,sort -u排序并去重。第二段diff用grep '^>'专门查看第二次比第一次多出来的行,也就是新增路径。这样的操作每周只要三五分钟,就能追踪目标系统的目录变化。
从那以后,我每次接新的授权项目,都会先做 socket 探活、跑固定扫描、归档结果,再定期 diff 一次新增路径,把目录扫描当作持续追踪手段,而不是一次性爆破。这个习惯帮我在好几个项目里提前发现了已经变更的接口路径,也省去了反复重扫的麻烦,希望帮到你。
本文还有配套的精品资源,点击获取