压缩包文件名欺骗漏洞详解:CVE-2023-39137技术剖析与防御

本文深入解析CVE-2023-39137漏洞,该漏洞影响Flutter的Archive包,允许攻击者通过篡改ZIP文件的局部文件头和中央目录条目中的不同文件名进行欺骗,可能导致安全机制失效。文章详细介绍了ZIP文件结构、漏洞原理、PoC利用代码及修复方案。

CVE-2023-39137:压缩包文件名欺骗漏洞技术分析

漏洞概述

CVE-2023-39137 是一个存在于 Archive 包(用于 Flutter/Dart 的压缩文件处理库)v3.3.7 及更早版本中的安全漏洞。该漏洞被评定为 高危 级别,CVSS 3.1 基础评分为 7.8

该漏洞的核心是 ZIP文件名欺骗。攻击者可以精心构造一个恶意ZIP文件,使其在局部文件头和中央目录条目中存储不同的文件名。由于Archive包在解析时仅从局部文件头读取文件名,而大多数其他ZIP解析器(如系统工具zipinfo)则优先使用中央目录条目中的文件名,这种不一致性可被利用,导致文件在提取前后的“身份”发生改变。

技术细节:ZIP文件结构与漏洞原理

ZIP文件基本结构

一个标准的ZIP文件主要包含三部分:

  1. 局部文件头:位于每个被压缩文件的数据之前,包含该文件的即时信息,如文件名、压缩方法、CRC校验和等。
  2. 中央目录:位于ZIP文件的末尾附近,作为整个存档的“索引”,其中每个“中央目录文件头”也包含文件名、大小等信息。
  3. 中央目录结束记录:标记中央目录的结束。

漏洞成因

在正常情况下,同一文件在局部文件头中央目录条目中的文件名应该一致。然而,ZIP文件格式规范并未强制要求两者必须相同。

Archive包(v3.3.7)的 ZipFileHeader 构造函数在解析文件头时,仅从输入流中读取了中央目录条目中的文件名(对应于代码中的filename字段),但在实际提取文件时,其内部逻辑可能依赖于其他部分或未正确处理这种不一致性,导致了文件名解析的歧义。

关键代码片段(展示解析逻辑):

1
2
3
4
5
6
// 在 ZipFileHeader 构造函数中
final fnameLen = input.readUint16();
// ... 读取其他字段 ...
if (fnameLen > 0) {
    filename = input.readString(size: fnameLen); // 此处读取的是中央目录中的文件名
}

漏洞利用与影响

攻击场景(Proof of Concept)

攻击者可以制作一个ZIP文件,其中某个条目的局部文件头中的文件名为evil.apk,而中央目录中记录的文件名却是evil.txt

  • 当使用系统命令zipinfo查看此ZIP包时,显示的文件名为evil.txt(取自中央目录)。
  • 当使用存在漏洞的Archive包(extractFileToDisk函数)解压此ZIP包时,提取出的文件名称却是evil.apk

这可能导致多种危害:

  1. 绕过安全检查:安全扫描工具或用户肉眼检查zipinfo输出时看到的是无害的.txt文件,但实际释放出的却是恶意的.apk可执行文件。
  2. 钓鱼攻击:将可执行文件伪装成文档、图片等。
  3. 引发后续攻击:与路径遍历等漏洞结合,扩大影响。

概念验证代码(Python)

以下Python脚本演示了如何构造一个文件名不一致的ZIP文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import zipfile

def generate_spoofed_zip(original_filename, spoofed_filename):
    # 1. 创建一个正常ZIP文件
    with zipfile.ZipFile('payload.zip', 'w') as zipf:
        zipf.writestr(original_filename, "Malicious Payload Content")

    # 2. 篡改局部文件头中的文件名
    with open('payload.zip', 'rb') as f:
        data = f.read()
    # 简单替换第一个匹配的文件名字符串(此处仅为原理演示,实际构造需精确定位字节偏移)
    spoofed_data = data.replace(bytes(original_filename, 'utf-8'), bytes(spoofed_filename, 'utf-8'), 1)

    with open('payload.zip', 'wb') as f:
        f.write(spoofed_data)

# 注意:此方法要求两个文件名长度相等,否则会破坏ZIP结构。
# 实际攻击中会使用更精确的二进制编辑来保持文件结构完整。
original_filename = 'evil.txt'
spoofed_filename = 'evil.apk'
if len(original_filename) != len(spoofed_filename):
    raise ValueError("Filenames lengths must be equal for this simple PoC")

generate_spoofed_zip(original_filename, spoofed_filename)

关联漏洞与同类威胁

该漏洞并非孤立现象,它是ZIP文件名欺骗这一类安全问题的具体实例。早在2009年,类似漏洞(如影响WinRAR的漏洞)已被公开披露并用于实际攻击。

同一安全研究(Ostorlab)在审计中还发现了Archive包中与之相关的另一个高危漏洞:CVE-2023-39139(ZIP符号链接路径遍历)。该漏洞允许ZIP包中的符号链接指向提取目录之外的目标文件,结合文件名欺骗,可构成更复杂的攻击链。

修复方案与缓解措施

  1. 官方修复:Archive包在 v3.3.8 版本中已修复此漏洞。用户应立即升级到该版本或更高版本。
  2. 修复逻辑:修复方案的核心是确保文件名解析的一致性。补丁可能强制要求在解压时,验证或统一使用单一可信来源(如中央目录)的文件名,并忽略或拒绝局部文件头中不一致的命名。
  3. 最佳实践
    • 在解压ZIP文件前,使用可信的库进行完整性检查和结构验证。
    • 对解压出的文件实施严格的命名白名单策略,特别是对可执行文件。
    • 在安全敏感的环境中使用解压功能时,将文件提取到隔离的沙箱环境中。

总结

CVE-2023-39137揭示了文件格式解析器中一个看似细微却危害巨大的逻辑缺陷——对同一数据实体的多源信息缺乏一致性校验。对于处理复杂、历史悠久的文件格式(如ZIP)的库,开发人员必须深入理解其规范的所有细节和潜在歧义,并采取防御性编程策略。用户则应及时关注依赖库的安全更新,避免将自身暴露于已知风险之中。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计