GHOST 脆弱性は如何様に使うのか


2015年 01月 30日

先日 GHOST と呼ばれる glibc の脆弱性が発表された。なんでも、「リモートから任意のコードを実行できる可能性がある」らしいではないか。しかも様々なプログラムで利用されているライブラリ部分の問題とあって、影響範囲がとても広い。なかなか厄介なことである。

はて、しかし一体全体どうやってリモートから任意のコードを実行しようというのだろう? 話を聞くに、たかが数バイトの情報を範囲外のメモリに書き込める可能性があるだけだという。実際それだけのことでサーバーの乗っ取りなどできるものなのだろうか。そんなわけで、その疑問に答えるべく、本記事では以下の URL で解説されている実際の攻撃方法を若干端折って紹介してみようと思う。

http://www.openwall.com/lists/oss-security/2015/01/27/9

なお、本記事はこの脆弱性そのものに対する緊急度などについて言及するものではないし、実際に攻撃を行うことを推奨するものでもない。くれぐれも攻撃に用いたりしないようご注意願いたい。また、脆弱性そのものに対する適切な対応などについては、専門機関や専門家の指示や勧告に従って欲しい。

GHOST 脆弱性とは何か

まず GHOST と呼ばれる脆弱性が何なのかについて確認しておく必要がある。これは gethostbyname シリーズの関数に特殊なホスト名を渡すと、ホスト名末尾数バイトの情報が溢れてバッファ外の領域に書き込まれる可能性があるというものだ。

コトの問題の一番大きなところは、必要なバイト数の計算が間違っていたというものらしい:

size_needed = (sizeof (*host_addr)
- + sizeof (*h_addr_ptrs) + strlen (name) + 1);
+ + sizeof (*h_addr_ptrs)
+ + sizeof (*h_alias_ptr) + strlen (name) + 1);

この通り sizeof (*h_alias_ptr) の分だけ、つまり「ポインタひとつ分」の情報だけ溢れてしまうのだ。これは 32-bit システムでは 4 バイト、64-bit システムでは 8 バイトにあたる。「たかがそれだけ」のことで一体どうやって「任意のコードを実行」するのか。Exim という SMTP サーバーを相手に攻撃する方法をみてみよう。

Exim の実行時設定メモリアドレスの取得

さて、GHOST 単体では最大でもたかが 8 バイトの情報しか書き込めないので、これだけでは「任意のコード」は実行できない。まずは、Exim が実行時設定を保持しているメモリアドレスを得よう。

GHOST 自体はたかが数バイトのオーバーフローでしかないが、C ライブラリの malloc はその管理情報を確保したバッファの前後に書き込むので、これが利用できる。gethostbyname がホスト名を書き込むために確保したメモリ領域のすぐ隣には、その後ろの空き領域がどれだけあるかなどを管理する値が書き込まれている。

参考 URL の図を拝借し、malloc 管理情報の細かい構造を省略するとおおむね下記のようになる。やることは図中 XXXX で示した malloc の管理情報部分に、本当の空きバイト数より大きな値を書き込むことだ。

GHOSTによるmalloc管理情報上書

まさに GHOST 脆弱性の出番である。これを使って Exim が持っている “503 sender not yet given” というメッセージが書き込まれた領域まで、この空きメモリバイト数を増やす。

malloc空きブロックをメッセージまで拡張

これで malloc は、エラーメッセージが書き込まれている領域まで「空きメモリ」だと認識するようになった。

ここでもう一度 gethostbynamemalloc を実行させれば、malloc はこの空き領域からメモリを確保し、確保した領域の直後に新たな管理情報を書き込む。

エラーメッセージにmalloc管理情報を上書

メッセージが格納されていた場所に malloc の管理情報が上書きされたのが分かるだろうか。さてこれを取り出そう。どうすればいい? 簡単だ、”503 sender not yet given” エラーを発生させればいいのだ。Exim はエラーメッセージが malloc の管理情報に書き換わっているなどつゆ知らず、この情報をクライアントに返す。そしてこの管理領域の情報から、実行時設定を保持しているメモリアドレスを割り出せる。これで ASLR (Address Space Layout Randomization) や PIE (Position Independent Executable) を回避して必要なメモリアドレスを取得できた。

「どこでもなんでもライター」

メモリアドレスを得ただけでは話は進まない。もっと自由にどこにでもなんでも書き込める仕組みを作ろう。先ほどはエラーメッセージの場所を狙ったが、今度は Exim が独自に持っているメモリ管理情報を狙う。

malloc空きブロックをExim管理メモリまで拡張

図は GHOST で先と同じように空きメモリ領域を拡張した状態だ。図の「次」が、Exim が管理しているメモリブロックの次の空きを示すポインタだ。「長」はそのブロック長を管理しているが、今回は使わない。狙うのはこの「次」ポインタだ。

さてもう一度 gethostbynamemalloc させよう。malloc は嘘を書き込まれた管理情報により、図のように Exim が管理している「次」ポインタ領域まで含めて確保するので、これで「次」ポインタを書き換えられることがわかる。

Exim管理メモリブロックのnextポインタ乗っ取り

この「次」ポインタの先には、クライアントから送る SMTP コマンドの内容そのものが書き込まれる。これで「どこでもなんでもライター」ができたことがわかるだろうか。「次」ポインタを差し替えてから、任意のバイト列を SMTP コマンドとして送ればよい。

コマンドの実行

「どこでもなんでもライター」ができれば、あとは x86 コードを書き込めば…。

いやいや、そうはいかない。 NX (No-eXecute) 保護機能により、実行可能なメモリ領域には書き込めないし、データ領域は実行可能権限がないから実行できないのだ。

ではどうするのか。そう、先ほど実行時設定のメモリアドレスを取得したではないか。これを使って Exim の ACL (Access Control List) を書き換え、Exim が持つ文字列展開機能を使えば良い。"${run{ }}" の形式で好きなコマンドを書き込んで展開させよう。これで Exim に任意のコマンドを実行させることができる。お疲れ様、もうこのサーバーは乗っ取られてしまったも同然だ。

まとめ

ごく小さくみえるセキュリティホールから、少しずつ穴を広げて、最終的にはサーバージャックまで可能になってしまったのがわかっただろうか。確かにこれを実際に成功させるのはそう簡単なことではないが、だからといって甘く見てはいけない。理論上可能なことではあるし、乗っ取られただけでおしまいというわけにもいかない。次の目標への踏み台とされ、穴は次から次へと広がっていくものなのだ。