跳到主要内容

GitLab Workhorse ExifTool RCE

没有任何一个单独缺陷看起来是高危漏洞——4 个缺陷各自都有缓解措施,但当它们被攻击者按特定顺序组合时,形成完整的远程代码执行。这是 2021 年公开的 HackerOne 报告中最值得借鉴的部分——不是某个具体漏洞,而是"白名单 + 第三方解析器"这类组合的结构性脆弱

利用链 4 层

缺陷 1:扩展名白名单与解析器识别错配

GitLab Workhorse 的逻辑:用户上传文件时,只对扩展名 jpg|jpeg|tiff 调用 ExifTool 去除 EXIF。

漏洞:ExifTool 不按扩展名识别文件类型,按文件内容魔数识别。这意味着攻击者可以把任何 ExifTool 支持的格式改名为 xxx.jpg,照样进入处理流程。白名单从 2 种格式(JPEG/TIFF)放大到 ExifTool 支持的全部 30+ 种格式

缺陷 2:选 DjVu 作为攻击面

ExifTool 支持的格式中,DjVu 模块存在代码执行缺陷。DjVu 是一种文档格式,ExifTool 在解析 DjVu 注释(annotation)时会处理 C escape sequences。攻击者不选 JPEG/TIFF(这两个是白名单原本允许的),而是利用扩展名-内容错配,让白名单允许的入口触发了白名单外的高危解析器

缺陷 3:DjVu 模块的 eval 反模式

DjVu 模块的"convert C escape sequences"逻辑,本质上是对用户控制的字符串做 eval——把 \n 转成换行符、\t 转成制表符等。用 eval 来"解释"用户数据中的转义字符是经典反模式。

缺陷 4:\<换行> 字符串延续

PoC 核心(DjVu 元数据):

(metadata
(Copyright "\
" . qx{echo vakzz >/tmp/vakzz} . \
" b ") )

执行顺序:

  1. ExifTool 解析 Copyright 字段,得到字符串:\<换行>" . qx{echo vakzz >/tmp/vakzz} . \<换行>" b
  2. 字符串的 \<换行> 是 C 的合法语法——反斜杠后跟换行符 = 字符串延续符(line continuation)。后续的换行符被"吃掉",引号被"延续"
  3. 字符串变成:<空>" . qx{echo vakzz >/tmp/vakzz} . <空>" b
  4. ExifTool 的 eval 把它当 Perl 代码执行——qx{...} 是 Perl 的反引号操作符(执行 shell 命令)
  5. RCE 完成/tmp/vakzz 文件被创建

第二个 PoC 把 qx{...} 替换为标准的 Perl 反向 shell,触发后攻击者从远程获得 GitLab 服务器 git 用户的 shell。

4 个缺陷的链式依赖

缺陷层漏洞本质单独存在的影响
1. 扩展名 vs 内容识别白名单逻辑与解析器逻辑错配攻击面放大,但不可利用
2. 选 DjVu 作为攻击面30+ 格式中有一个有缺陷攻击面巨大,但攻击入口缺失
3. eval 反模式eval 用户控制的数据严重漏洞,但需要触发
4. \<换行> 字符串延续字符级别绕过可绕过某些过滤,但需要上下文

没有这 4 个缺陷中的任何一个,整个利用链都不成立。这是教科书级的"多低危链式组合"——单个 CVSS 评分都不算严重,链式组合后就是 RCE。

类级教训

信任边界错配是"白名单"的常见死法

白名单防御的根本性局限是它假设白名单与实际处理能力一致。但实际处理能力往往是白名单的超集——当解析器按内容识别文件类型时,扩展名白名单失去防御意义。

教训:防御性白名单必须配上"白名单与处理能力对齐"——要么限制解析器只接受白名单格式(而非用白名单限制哪些文件可以进入解析器),要么在调用解析器前先用魔数检测验证内容。

eval 用户控制的数据是反模式

反模式的本质是"信任边界混淆"——把"数据"当作"代码"来执行。ExifTool 的 DjVu 模块用 eval 来"convert C escape sequences",本质上是对用户控制的字符串做执行。

教训

  • 处理用户数据时只做有限状态的状态机转换(如简单的字符串替换),不做 eval
  • 如果必须处理"转义序列",应该自己写解析器,明确指定允许的转义类型
  • 任何"看似数据实则代码"的输入边界都该被审计

多缺陷链式组合是真实风险

真实的利用链往往是多个低危缺陷的链式组合。这个案例中的每个缺陷单独看都"不是高危",但链式组合后就是 RCE。这意味着:

  • 漏洞评估应考虑链式可能性,而非只看单个缺陷的严重性
  • 防御时应在每一层都设置阻断条件——"任何一层断裂就阻止整条链"
  • 对于"白名单 + 第三方解析器"这类组合,默认假设它们错配,需要主动验证

与其他 RCE 案例的对比

案例攻击面触发方式
命令执行.md 中常见的 eval/assert 类 RCE字符串作代码单个 eval 调用
macOS 端 SourceTree/XcodeGhost 类应用漏洞直接利用单个应用
本案例4 层链式:扩展名 / 解析器 / eval / 字符级4 个低危缺陷链式组合

这个案例最值得借鉴的不是"如何被攻击",而是"如何审查白名单与处理器的信任边界"——很多类似系统都有相同的结构性缺陷。

防御清单

针对这个攻击模式,防御方应主动验证:

  • 上传处理管道中,白名单与解析器实际接受格式是否一致
  • 解析器是否有任意代码执行能力(如 eval、backtick、shell 调用)?
  • 解析器接受的转义序列是否有明确允许列表?是否有状态机实现?
  • 处理用户控制数据时,是否有字符串级攻击面测试(如反斜杠续行、Unicode 规范化)?
  • 魔数检测是否在白名单之前做(而非之后)?