[Bug] Potential DoS via Unbounded zlib.decompress (Zip Bomb Vulnerability) in Document Parser

用户在使用 LlamaIndex 的 HWP 文档解析器( llama-index-readers-hwp )读取 HWP 文件时触发问题。攻击者可以通过提供包含超高压缩比内容的恶意文档(Zip Bomb),在解析过程中导致服务端内存耗尽(OOM),造成拒绝服务(DoS)。该问题通过静态代码分析发现

[Bug] Potential DoS via Unbounded zlib.decompress (Zip Bomb Vulnerability) in Document Parser

[Bug] Potential DoS via Unbounded zlib.decompress (Zip Bomb Vulnerability) in Document Parser

快速结论:该报错发生在 LlamaIndex 的 HWP 文档解析器中,当读取恶意构造的高压缩比文档时,zlib.decompress(data, -15) 无大小限制的解压操作会瞬间耗尽内存,导致 OOM 崩溃。优先排查是否使用了受影响的 HWP Reader,并在代码中添加解压大小上限。

问题场景

用户在使用 LlamaIndex 的 HWP 文档解析器(llama-index-readers-hwp)读取 HWP 文件时触发问题。攻击者可以通过提供包含超高压缩比内容的恶意文档(Zip Bomb),在解析过程中导致服务端内存耗尽(OOM),造成拒绝服务(DoS)。该问题通过静态代码分析发现,在 base.pyget_text_from_section 方法中。

报错原文

unpacked_data = (
    zlib.decompress(data, -15) if self.is_compressed(load_file) else data
)

# 实际运行时观察到的现象为:
# 内存瞬间飙升至全部可用内存,触发操作系统 OOM killer 杀死进程,无显式 Python 报错。

原因分析

zlib.decompress(data, -15) 调用在处理单个压缩块时,没有对解压后的数据大小设置任何限制,也没有检查压缩比或使用流式解压。攻击者可以构造一个体积很小的压缩文档,解压后膨胀数 GB,导致单次 zlib.decompress 调用直接分配巨大内存,引发 OOM 崩溃。这是一个经典的 Zip Bomb 漏洞。

环境排查

  • 确认是否使用了 llama-index-readers-hwp 集成包
  • 定位代码位置:llama-index-integrations/readers/llama-index-readers-hwp/llama_index/readers/hwp/base.py 第 89–95 行

解决步骤

  1. 定位受影响代码:打开 base.py 文件,找到 get_text_from_section 方法中的 zlib.decompress(data, -15) 调用。
  2. 添加安全解压函数(可优先尝试):在类中新增一个带最大解压大小限制的 _safe_decompress 方法。示例实现如下:
    MAX_DECOMPRESSED_SIZE = 100 * 1024 * 1024  # 100 MB 限制,可根据需求调整
    
    def _safe_decompress(self, data: bytes, max_size: int = MAX_DECOMPRESSED_SIZE) -> bytes:
        decompressor = zlib.decompressobj(-15)
        result = b""
        chunk = decompressor.decompress(data, max_size)
        result += chunk
        if decompressor.unconsumed_tail or not decompressor.eof:
            if len(result) >= max_size:
                raise ValueError(
                    f"Decompressed data exceeds maximum allowed size ({max_size} bytes). "
                    "Possible decompression bomb."
                )
        return result
  3. 替换原始调用:将原始行替换为:
    unpacked_data = (
        self._safe_decompress(data) if self.is_compressed(load_file) else data
    )

    注意:使用 zlib.decompressobj + 增量解压是推荐方案,但上述示例中 decompress(data, max_size) 仍可能一次性消耗大量内存。更安全的做法是分多次读取并累计大小检查。如果安全性要求更高,可参考以下流式方案:

    def _safe_decompress_stream(self, data: bytes, max_size: int = MAX_DECOMPRESSED_SIZE) -> bytes:
        decompressor = zlib.decompressobj(-15)
        result = b""
        # 逐块处理,假设 data 已经全部加载到内存
        chunk = decompressor.decompress(data, 8192)  # 每次最多解压 8KB
        while chunk:
            result += chunk
            if len(result) > max_size:
                raise ValueError(...)
            chunk = decompressor.decompress(data, 8192)  # 需要使用剩余数据,实际需用 unconsumed_tail
        return result

    注意:以上流式代码为示意,实际实现需要处理 unconsumed_tail,建议直接采用 Issue 中的 _safe_decompress 方案作为基线。

验证方法

构建一个压缩后很小(如 1KB)但解压后超过 200MB 的测试文件(可使用 python3 -c "import zlib; import sys; sys.stdout.buffer.write(zlib.compress(b'A'*300*1024*1024))" 生成),尝试用 HWP Reader 解析。如果代码抛出 ValueError: Decompressed data exceeds maximum allowed size,且程序未 OOM 崩溃,则修复生效。

参考来源

run-llama/llama_index #22101

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

celebrityanime
celebrityanime
文章: 14724

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注