网络安全

敏感目录扫描工具:多线程爆破Web隐藏路径

2026-07-05 #网络安全#python

本文仅供合法授权的安全研究与学习使用,请勿用于非法用途。读者应确保行为符合当地法律法规。

前言

在 Web 渗透测试中,敏感目录扫描是信息收集阶段的核心环节。一个看似普通的网站背后,往往隐藏着后台管理目录(/admin)、备份文件(/backup.zip)、配置文件(/.git/config)、测试接口(/test/debug)等敏感路径——这些路径一旦被未授权访问,就可能直接导致数据泄露甚至服务器被控制。

敏感目录扫描的原理并不复杂:基于字典文件,对目标 URL 逐个拼接路径并发起 HTTP 请求,根据响应状态码判断路径是否存在。但要在实战中高效、稳定地完成大规模扫描,就需要解决三个工程问题:字典选择、并发控制、反封禁对抗

本文基于真实的 Python 实现源码,剖析两套敏感目录扫描工具的实现——一套基于 threading + Queue 的传统多线程方案,一套基于 ThreadPoolExecutor 的现代线程池方案,并讲解代理、UA 轮换等实战对抗技巧。

技术背景

HTTP 状态码判断逻辑

扫描器主要依据状态码判断路径存在性:

状态码 含义 扫描器处理
200 路径存在且可访问 确认存在,重点标记
301/302 重定向 可能存在,需跟进确认
403 禁止访问 路径存在但无权限,仍是有效信息
404 不存在 跳过
500 服务器错误 路径可能存在但触发异常

实战中还需注意”万能响应”问题——有些站点对任意路径都返回 200(SPA 前端路由),需要结合响应体长度、标题等特征做二次判断。

并发模型对比

模型 实现 特点
threading.Thread + Queue 继承Thread类,从队列消费任务 传统写法,控制精细
ThreadPoolExecutor concurrent.futures 线程池 现代写法,API 简洁,内置 Future

反封禁对抗

大规模扫描容易触发 WAF/IPS 封禁,常用对抗手段:

  • User-Agent 轮换:模拟不同浏览器,避免单一 UA 被识别
  • 代理 IP 轮换:通过 SOCKS5/HTTP 代理分散请求来源
  • 请求速率控制:合理设置超时和并发数,避免过于激进

实现思路

整体架构如下:

1
2
3
4
5
6
7
8
9
字典文件读取 → 任务队列(Queue)

多线程消费/线程池提交

构造完整URL并发起请求

根据状态码判断路径是否存在

输出存在的敏感路径

源码包含三个层次的实现:

  1. B站视频版 dirPathScan.pythreading.Thread + Queue 基础版,含 UA 轮换
  2. 自定义改造 scan_directories.py:带 argparse 命令行参数的工程化封装
  3. 目录扫描 dirBurp.pyThreadPoolExecutor 线程池版,支持 SOCKS5 代理

核心代码解析

1. UA 轮换池

两个版本都采用了 UA 轮换策略,这里以基础版为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import random

# 定义 User-Agent 列表,用于模拟不同的浏览器请求
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
]
# 构造请求头部,随机选择一个User-Agent
headers = {
'User-Agent': random.choice(user_agents),
}

random.choice(user_agents) 在模块加载时随机选定一个 UA。注意这里有个小瑕疵——UA 是模块级随机一次性的,所有线程共用同一个 UA。更完善的实现应在每次请求时动态随机:

1
2
def get_headers():
return {'User-Agent': random.choice(user_agents)}

2. 多线程扫描核心类

基础版通过继承 threading.Thread 实现多线程消费:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import threading
from multiprocessing import Queue
import requests
from requests import RequestException

class DirPathScan(threading.Thread):
"""
敏感路径扫描类,继承自 threading.Thread 类
"""

def __init__(self, queue):
"""
初始化敏感路径扫描类
:param queue: 任务队列,用于存储待扫描的URL
"""
super().__init__()
threading.Thread.__init__(self)
self.queue = queue

def run(self):
"""
线程运行方法,从队列中获取URL并进行扫描
"""
# 获取队列中的Url
while not self.queue.empty():
url = self.queue.get()
try:
res = requests.get(url=url, headers=headers, timeout=2)

if res.status_code == 200:
print(f"{url} 存在敏感路径 状态码:{res.status_code}")
# if res.status_code == 403:
# print(f"{url} 存在敏感路径 状态码:{res.status_code}\n")
# if res.status_code == 404:
# print(f"{url} 存在敏感路径 状态码:{res.status_code}\n")

except RequestException as e:
print(f"请求 {url} 时发生错误: {e}")

关键点解析:

  • Queue 线程安全queue.get() 内部有锁保护,多线程并发取任务不会冲突。注意这里用的是 multiprocessing.Queue(兼容性好),实战中 queue.Queue(标准库)也完全可以
  • while not self.queue.empty():线程持续从队列消费任务直到清空,这是”工作窃取”模式的雏形——快的线程会多处理任务,实现负载均衡
  • timeout=2:超时设置很关键,扫描场景下宁可漏报也不要卡死
  • 状态码注释:源码中 403、404 的判断被注释掉了,实战中 403(禁止访问)往往意味着路径存在但无权限,是高价值信息,建议放开

3. 启动函数:字典加载与线程调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def start(url, count, filePath):
"""
敏感路径扫描的启动函数
:param url: 需要扫描的基础URL
:param count: 线程数量
:param filePath: 字典文件路径
"""
queue = Queue()
# 指定字典文件的完整路径
with open(filePath, "r") as file:
for i in file:
queue.put(url + i.rstrip('\n'))
# 多线程
threads = []
threading_count = int(count)
for i in range(threading_count):
threads.append(DirPathScan(queue))
for t in threads:
t.start()
for t in threads:
t.join()

流程清晰:读取字典文件逐行放入队列 → 创建 N 个扫描线程 → 全部启动 → 等待全部完成。rstrip('\n') 去掉换行符,避免拼接出 http://target.com/admin\n 这样的错误 URL。

4. 命令行参数封装

自定义改造版增加了 argparse 支持,让工具更易用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import argparse
import dirPathScan

def start(url, count, filePath):
queue = Queue()
with open(filePath, "r") as file:
for i in file:
queue.put(url + i.rstrip('\n'))
threads = []
threading_count = int(count)
for i in range(threading_count):
threads.append(dirPathScan.DirPathScan(queue))
for t in threads:
t.start()
for t in threads:
t.join()

# 创建 ArgumentParser 对象
parser = argparse.ArgumentParser(description='敏感目录扫描工具')
parser.add_argument('--url', type=str, required=True, help='目标URL')
parser.add_argument('--filePath', type=str, required=True, help='文件路径')
parser.add_argument('--count', type=int, required=True, help='计数值')

args = parser.parse_args()
url = args.url
filePath = args.filePath
count = args.count

start(url=url, count=count, filePath=filePath)

三个必填参数:--url 目标地址、--filePath 字典路径、--count 线程数。这样工具就可以直接命令行调用,不用改代码。

5. 线程池版 + SOCKS5 代理

dirBurp.py 采用了更现代的 ThreadPoolExecutor 写法,并加入了代理支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests

# 配置 SOCKS5 代理
proxies = {
'http': 'socks5://127.0.0.1:33333',
'https': 'socks5://127.0.0.1:33333'
}

# 目标网站的基础 URL
base_url = "http://192.168.1.11:9999"


# 读取目录字典文件
def load_directories(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return [line.strip() for line in f if line.strip()]


# 进行单个目录的请求
def request_directory(directory):
url = f"{base_url}/{directory}"
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code in [200]:
print(f"[+] 找到: {url} - 状态码: {response.status_code}")
except requests.exceptions.RequestException as e:
pass


# 进行目录爆破
def directory_brute_force(base_url, directories, max_workers=10):
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_directory = {executor.submit(request_directory, directory): directory for directory in directories}
for future in as_completed(future_to_directory):
directory = future_to_directory[future]
try:
future.result()
except Exception as e:
print(f"[-] 处理 {directory} 时发生异常: {e}")

相比 threading.Thread 方案,ThreadPoolExecutor 有几个优势:

  • API 更简洁submit + as_completed 替代手动管理线程生命周期
  • Future 机制:每个任务返回 Future 对象,可以获取返回值和异常
  • 上下文管理with 语句自动等待所有任务完成,无需手动 join

SOCKS5 代理的加入让这个版本更适合实战——当目标有 IP 频率限制时,通过代理池轮换 IP 可以持续扫描。注意使用 SOCKS 代理需要安装 pip install requests[socks]

主程序入口:

1
2
3
4
5
6
7
8
if __name__ == "__main__":
dir_file = "dir.txt"
directories = load_directories(dir_file)
if not directories:
print("[-] 目录列表为空或文件未找到。")
else:
print(f"[*] 已加载 {len(directories)} 个目录进行爆破。")
directory_brute_force(base_url, directories)

使用方法与运行效果

基础版(无参数)

1
2
3
4
5
if __name__ == '__main__':
url = 'https://www.example.com'
filePath = 'PHP.txt'
count = 32
start(url=url, count=count, filePath=filePath)

命令行版

1
python scan_directories.py --url https://www.example.com --filePath PHP.txt --count 32

代理版

1
2
python dirBurp.py
# 需要先在本地启动 SOCKS5 代理(如 proxychains、v2ray),监听 33333 端口

运行效果示例

1
2
3
4
5
6
[*] 已加载 3528 个目录进行爆破。
[+] 找到: http://192.168.1.11:9999/admin - 状态码: 200
[+] 找到: http://192.168.1.11:9999/login.php - 状态码: 200
[+] 找到: http://192.168.1.11:9999/.git/config - 状态码: 200
[+] 找到: http://192.168.1.11:9999/backup.zip - 状态码: 200
[+] 找到: http://192.168.1.11:9999/phpinfo.php - 状态码: 200

发现 .git/config 意味着可进一步用 GitHack 工具恢复源码;backup.zip 可能包含整站源码和数据库配置;phpinfo.php 泄露服务器完整环境信息——这些都是高危发现。

防御对策

蓝队可从以下维度防御敏感目录扫描:

  1. 删除/迁移敏感路径:上线前检查并删除 /admin/test/debug/.git/backup 等敏感路径,后台管理目录应改为非常规路径并限制 IP 访问。
  2. 关闭目录列表:Web 服务器关闭 autoindex(Nginx)/ Options +Indexes(Apache),避免目录结构泄露。
  3. 部署 WAF:配置 WAF 规则识别扫描行为(短时间大量 404 请求),自动封禁来源 IP。
  4. 统一错误响应:对不存在的路径统一返回自定义 404 页面(而非服务器默认 404),避免通过响应差异判断路径存在性。
  5. 速率限制:对单 IP 的请求频率做限制(如 Nginx limit_req),增加扫描成本。
  6. 敏感文件监控:部署文件完整性监控,及时发现 .git.svn.env 等敏感文件的意外暴露。

总结

敏感目录扫描是 Web 渗透的”敲门砖”——简单但不可或缺。本文剖析的两套实现代表了两种典型的 Python 并发编程范式:threading + Queue 适合需要精细控制的场景,ThreadPoolExecutor 适合快速开发。

从源码中可以提炼几个实战要点:一是字典质量决定扫描效果,PHP、Java、Python 不同技术栈要用不同字典;二是并发数并非越高越好,过高反而触发封禁导致漏报,一般 20-50 线程为宜;三是代理和 UA 轮换是应对封禁的基本功,实战中必不可少。

一个优秀的扫描器还需要在此基础上增加:响应体长度去重(对付万能 200)、递归扫描(发现目录后继续扫描子目录)、结果自动验证(对 200 结果截图或检查内容)等能力。这些扩展留待读者在实战中逐步完善。

评论
分享