这是一款专业级扫描器,旨在检测 Next.js 应用程序中的 CVE-2025-29927 中间件绕过漏洞。
X-Middleware-Subrequest 请求头测试内部路径,以绕过 Next.js 中间件pip install -r requirements.txt playwright install
### 运行扫描器```bash
python main.py --domain https://example.com --threads 10 --timeout 10 --save
python main.py --help
| Option | Description |
|----------------|--------------------------------------------|
| `--domain` | 目标站点基础 URL(必需) |
| `--user-agent` | 自定义 user-agent(默认:Chrome 字符串) |
| `--timeout` | 请求超时时间(默认:10 秒) |
| `--proxy` | 代理地址(可选) |
| `--save` | 将结果保存到 `results.txt` |
| `--threads` | 线程数(默认:10) |
| `--wordlist` | 字典包含常见路径 |
---
## 🐳 Docker 使用
### 构建 Docker 镜像```bash
docker build -t cve-scanner .
docker run -it --rm cve-scanner --domain https://example.com --save
---
## ⚙️ GitHub Actions
本项目包含一个 GitHub Actions 工作流,用于在推送时测试环境搭建。它:
- 安装依赖
- 安装 Playwright 浏览器
- 运行 `--help` 检查
参见 `.github/workflows/python.yml`。
---
## 🧱 结构```
.
├── main.py # Entry point
├── config.py # CLI parser
├── crawler.py # Playwright crawler
├── scanner.py # Multi-threaded vulnerability testing
├── requirements.txt
├── Dockerfile
└── .github/workflows
CVE-2025-29927 漏洞概述
CVE-2025-29927 是一个严重的 Next.js 安全漏洞,允许攻击者绕过基于中间件的身份验证和授权。通过在 HTTP 请求中包含一个特殊的内部请求头(X-Middleware-Subrequest),攻击者可以诱使 Next.js 服务器跳过中间件执行,从而访问受保护的路由。实际上,通常会被身份验证中间件阻止的请求(例如返回 401/403 或重定向到登录页)在存在此请求头时会被正常处理,从而有效绕过安全检查。该漏洞影响 Next.js 11.1.4 至 15.2.2 版本,强烈建议管理员进行修补或实施缓解措施(例如在代理层剥离此请求头)以保护其应用程序。
在 Web 应用程序中检测此漏洞需要发现内部端点,并使用恶意请求头对它们进行测试,以判断是否可能进行未授权访问。以下是高级 Python 脚本的设计方案,该脚本将爬取目标网站(完全支持 JavaScript)并扫描 CVE-2025-29927,满足所有指定的要求。
为了满足对包含 JavaScript 渲染内容的深度爬取要求,我们将使用 Playwright(因其速度和现代 API 而优于 Selenium)。Playwright 是一个强大的无头浏览器自动化库,可以处理动态 Web 应用程序和现代 JS 框架。与 Selenium 相比,Playwright 提供了更现代的 API(基于 Chrome DevTools Protocol 构建),并同时支持同步和异步操作,这可以为我们的使用场景带来更好的性能。主要库及其安装说明包括:
playwright – 用于无头浏览器自动化(加载 SPA 或需要 JS 的页面)。(安装:pip install playwright 并运行 playwright install 获取浏览器二进制文件)。
requests 或 httpx – 用于在扫描阶段发送 HTTP 请求。我们可以使用 requests 以简化操作,或使用 httpx/aiohttp 获得异步支持。(安装:pip install requests 或 pip install httpx)。
bs4 (BeautifulSoup) – 用于解析 HTML 并在需要时提取链接。Playwright 可以直接查询 DOM,但在页面的 HTML 内容上使用 BeautifulSoup 来查找锚点标签也很直接。(安装:pip install beautifulsoup4)。
concurrent.futures(内置)或 asyncio – 用于实现并发。对于多线程,将使用 Python 的 concurrent.futures.ThreadPoolExecutor(无需额外安装)。如果使用异步方案,可以将 Python 的 asyncio 与 httpx 结合用于并行请求。
(可选)argparse – 用于解析命令行参数,如果我们想要 CLI 界面而不是交互式菜单。(内置模块)
(可选)rich 或 colorama – 用于彩色或格式化控制台输出,以提高可读性。(安装:pip install rich 或 pip install colorama)。
理由:选择 Playwright 是因为它能够在不过度复杂的情况下抓取动态内容。“使用 Playwright,我们可以自动化无头浏览器……像人类一样浏览网页,这使其非常适合抓取由 JavaScript 驱动的动态网站”。这确保了我们的爬虫能够看到由脚本生成的链接或 UI 元素(而简单的基于 requests 的爬虫会错过这些)。
爬虫模块将使用 Playwright 的无头模式对目标站点进行深度爬取。目标是发现要测试的内部路径(端点),包括那些仅在 JS 执行后才会暴露的路径。爬虫的关键设计要点:
无头浏览器导航:通过 Playwright 以无头模式启动浏览器实例(例如 Chromium)。如果用户指定了自定义 User-Agent,则使用带该 User-Agent 的浏览器上下文(下一节将详细介绍)。例如,我们可以使用 browser.new_context(user_agent=<user_agent_string>) 创建上下文来模拟所选的 User-Agent。如果配置了代理,则在启动时应用它(Playwright 允许在启动浏览器或上下文时设置代理服务器)。
递归爬取策略:从给定的基础 URL(种子)开始。使用 page.goto(base_url, timeout=<T>) 加载页面(超时时间可配置)。等待网络空闲或短暂延迟,以便在必要时允许动态内容加载。然后提取链接。我们可以通过以下任一方式提取链接:
links = page.evaluate("Array.from(document.querySelectorAll('a[href]'), a => a.href)"),或<a href> 属性。链接过滤:过滤掉不在目标域名范围内的链接(以保持内部性)。同时忽略静态文件 URL,例如图片、CSS、JS 等。例如,跳过任何带有 .css、.js、.jpg、.png、.gif、.svg、.woff 等文件扩展名的 URL。一种实用的方法(灵感来自 ProjectDiscovery 模板)是忽略初始斜杠后包含“点”的任何路径。他们使用正则表达式模式 href=['"](https://github.com/houmanpashaei/cve-2025-29927/blob/main/%5C/%5B%5E.%5C%22%27%5D%2B)['"] 提取端点——这会捕获不包含句点的内部路径(从而跳过静态资源)。我们将在代码中实现类似的逻辑,以避免将静态资源或外部链接加入队列。
跟踪与深度控制:维护一个已访问 URL 的集合,以避免无限循环或重复。使用队列(FIFO)对站点的链接图进行 BFS 遍历。可选地,允许用户指定爬取深度限制或最大访问页面数,以防止在大型站点上无限运行。
JavaScript 渲染的内容:由于我们使用真实的浏览器,即使是脚本添加到 DOM 中的链接(例如,React 应用在获取数据后渲染菜单)也会被我们的爬虫看到。如有需要,我们应考虑点击或交互(例如,某些页面仅在用户操作后才加载)。然而,为了保持简单和快速,初始设计将专注于收集每个已加载页面上的 href 链接。如果目标应用程序有要求,我们可以在后续增强以处理无限滚动或点击后的内容等情况。
效率:Playwright 支持使用其异步 API 并行运行多个页面/标签页。我们可以使用 asyncio.gather 实例化多个页面以并发获取多个链接。对于初始实现,更简单的方法是顺序爬取(更容易实现),并依赖多线程扫描来提高性能。如果需要,高级优化可以涉及异步爬取(使用 async with async_playwright() 并等待多个 page.goto 调用)。但由于浏览器自动化对资源消耗较大,谨慎的做法是同时仅保留一个或少数几个浏览器页面,以避免使系统不堪重负。
脚本将在启动时显示一个用户友好的配置菜单,允许用户自定义扫描参数或接受默认值。这可以通过交互式控制台菜单(使用 input() 提示)或命令行参数(使用 argparse 以获得更专业的 CLI 体验)来完成。选项包括:
自定义 User-Agent:用户可以指定爬虫和扫描器使用的自定义 User-Agent 字符串。这将应用于 Playwright 浏览器上下文以及所有直接 HTTP 请求。使用非默认 User-Agent 有助于避免简单的机器人检测。(默认情况下,Playwright 可能会使用可识别的标识;我们可以如上所示轻松覆盖它。)例如,用户可以输入一个标识为 Windows 上 Chrome 的字符串,我们会将其传递给 Playwright 的上下文创建。
请求超时:用户可以为页面加载和 HTTP 请求设置超时时间(以秒为单位)。这可以防止扫描器在无响应的端点上挂起过长时间。我们将在爬取时将此设置应用于 page.goto(timeout=...),并在扫描时应用于请求(例如 requests.get(timeout=...))。
代理设置:如果用户希望通过代理路由流量(为了匿名或访问内部主机),他们可以输入代理 URL(以及需要的凭据)。脚本将在启动时配置 Playwright 浏览器使用此代理(例如 browser.launch(proxy={"server": "http://<proxy_host>:<port>", "username": "...", "password": "..."}),如示例所示)。类似地,对于 requests,我们将相应地设置 proxies 参数(或环境变量)。
输出到文件:菜单将询问用户是否要将结果保存到文件(例如 results.txt)。如果是,脚本除了打印到屏幕外,还会将发现的任何易受攻击的端点和详细信息写入该文件。如果不是,结果将仅打印到标准输出。(如果需要,我们仍可以将所有扫描路径记录到详细日志中,但该文件将根据用户偏好专门记录阳性结果或完整报告。)
其他选项:我们可以包含诸如“详细模式”之类的开关,用于调试日志,或在需要时包含“最大爬取深度/页面数”。这些可以帮助用户微调扫描。对于初始范围,上述四个主要选项就足够了。
菜单系统将在专门的配置/设置模块中实现。这可以简单地是一个打印提示并收集输入的函数,如果用户按 Enter 则使用合理的默认值(例如,默认 user-agent 为标准值,默认超时 = 10 秒,无代理,无文件输出)。这保持了交互的清晰性,并允许脚本也可以非交互式运行(如果我们稍后添加命令行参数,可以通过参数提供所有必要的配置来绕过交互式提示)。
对于扫描器来说,性能至关重要,尤其是在发现大量端点的情况下。脚本将采用并发来提高速度,可以通过多线程或 asyncio(或两者的组合):
多线程扫描:由于对已发现路径的扫描(发送带请求头的 HTTP 请求)是一项 I/O 密集型任务,我们可以安全地使用 Python 线程来并行化。I/O 操作会释放全局解释器锁(GIL),允许多个线程并发地推进网络请求。使用 concurrent.futures.ThreadPoolExecutor,我们可以有一个工作线程池,每个线程处理一部分扫描任务。这可以显著加快处理速度:例如,并行运行 5 个线程可以将扫描时间大致缩短 5 倍,正如其他 Web 抓取场景所示。我们将允许线程数可配置,或选择一个合理的默认值(例如 10 个线程)以在速度和服务器负载之间取得平衡。每个线程将从共享的待测端点队列中获取 URL。
Asyncio 替代方案:或者,可以采用异步方法,尤其是在异步模式下使用 Playwright 或使用 httpx 发送 HTTP 请求时。我们可以同时 await 多个请求。例如,httpx.AsyncClient 可以并发发送许多请求并收集结果。这种方法避免了线程开销,对于大量端点来说可能非常高效。然而,将 asyncio 与 Playwright(其本身可以异步使用)混合使用可能会使事情复杂化。一种务实的解决方案是对 HTTP 扫描阶段使用多线程(因为使用 Playwright 爬取在同步模式下可能更容易管理)。
并发爬取:如果站点较大,我们还应该考虑并行化爬取。Playwright 可以使用异步上下文一次打开多个页面。我们可能会为爬取实现有限的并发(例如一次 2-3 个页面)。例如,当我们提取新的 URL 时,如果使用 asyncio,我们可以为每个 URL 启动一个新的 Page。如果需要,这可以是一个高级优化。最初,单线程爬取更简单,对于中等规模的站点来说已经足够,但设计可以将其作为增强点。
线程安全:我们将确保共享数据的线程安全处理。为了简单起见,可以使用 ThreadPoolExecutor.map 处理要扫描的 URL 列表,或者我们可以使用线程安全队列(Python 的 queue.Queue)并让线程从中拉取直到为空。用于爬取的 visited 集合仅由爬虫访问(单线程,除非我们进行并发爬取)。扫描线程将只从其 URL 列表中读取(不修改共享结构,除了可能记录结果,我们可以用锁保护或仅在线程安全列表中收集)。
速率限制与礼貌性:由于这是一个安全测试工具,速度是优先考虑的因素,但我们仍然希望避免使目标过载。可以建议用户设置合理的线程数。如果需要,我们还可以实现微小的延迟或使用信号量来限制并发。例如,如果用户的网络或服务器可能不堪重负,我们可能不会一次启动所有线程。在高级场景中,异步方法可以使用信号量来允许一次 5 个并发请求。这些细节可以根据脚本性能测试进行调整。