Windowsにおけるバッファ・オーバーフロー


Cyber Security Management 2004年7月号


本文書は、Cyber Security Management誌に寄稿した記事の原稿を、CSM編集部殿の許可を得た上で掲載したものです。




ソフトウェアのセキュリティホールというと、まず最初に思い浮かぶのがバッファ・オーバーフローではないだろうか。バッファ・オーバーフローは、報告される件数も多く、また重大なセキュリティ侵害を引き起こす可能性が高いため、セキュリティホールの中でも非常に重要な位置を占めている。




多発するバッファ・オーバーフロー問題


今年の4月下旬、Sasserという名前のワームが出現し、多くのWindowsコンピュータに感染したことは記憶に新しい。SasserワームはWindowsのLSASS(Local Security Authority Subsystem Service)に存在するセキュリティホール(脆弱性)[1]を利用して感染するように作られていたが、このLSASSの脆弱性は、バッファ・オーバーフローにより不正なプログラムコードが実行するといったものであった。

また昨年の8月に発生した、同じくWindowsコンピュータに感染するBlasterワームも、WindowsのRPC/DCOMという機能に含まれていたバッファ・オーバーフローの不具合[2]を利用し、感染を広げていった。

このようにバッファ・オーバーフローに起因するセキュリティホールは、場合によっては任意の不正プログラムコードを実行させることが可能であるため、ワームや不正アクセスに利用されることで深刻な事態に発展する恐れがある。

Sasserワームが利用したLSASSのバッファ・オーバーフロー脆弱性は、パッチおよび詳細情報が公開された数日後に、いくつかの攻撃プログラムがインターネット上のメーリングリストに投稿された。図1は、その中の一つをテスト環境下で実際に動作させた結果である。


図1 LSASSのバッファ・オーバーフロー脆弱性を攻撃した例。攻撃が成功しTCP4444番ポートに接続することで、ターゲットマシンのコマンドプロンプトが表示されている


一般に公開されるバッファ・オーバーフローの攻撃プログラムは、多くの場合、ターゲットのOS(英語版や日本語版といった言語の違い、あるいはバージョンの違い、等)に応じて若干の修正を施す必要がある。しかし多少の知識さえあれば、公開された攻撃プログラムを目的とするOS向けに修正することは、さほど困難ではないと考えた方がよいだろう。

それでは、Windowsに関連するバッファ・オーバーフローの脆弱性は、果たしてどのくらいあるのだろうか。

表1は、今年になってから現時点までに報告されたマイクロソフト製品の脆弱性の中で、バッファ・オーバーフローに関するものをピックアップしたものである。


表1 今年になってから報告されたマイクロソフト製品の脆弱性のうち、バッファ・オーバーフローに関するもの


今年に入ってから今までに報告されているWindows関連の脆弱性の総数は33個あるのだが、そのうち表で示したとおり実に11個、つまり三分の一がバッファ・オーバーフローに関する脆弱性である(図2)。


図2 今年における現時点までのWindows関連脆弱性のうち、三分の一がバッファ・オーバーフロー


また図3は、現時点でCVEとして登録されている6,653個の脆弱性の中で、バッファ・オーバーフローによると思われるもの(1,540個)の割合を示している。ちなみにCVE(Common Vulnerabilities and Exposures)とは、一般に使用されているソフトウェア製品(オープンソースも含む)の脆弱性一つ一つを、一意に識別するための「名前」をつけて管理したものだ[3]。


図3 CVEに登録されているすべての脆弱性のうち、約23%がバッファ・オーバーフロー


これらはいずれも、脆弱性に関する説明文や属性に基づいてバッファ・オーバーフローかどうかを判断した結果であるため、必ずしも正確ではない。しかし、脆弱性全体の中でバッファ・オーバーフロー脆弱性が非常に大きな割合を占めていることはお分かりいただけると思う。




バッファ・オーバーフローの基本的な仕組み


そもそもバッファ・オーバーフロー(バッファ・オーバーランとも言う)とはどういった脆弱性なのだろうか。

「バッファ」とは、プログラムの実行中に一時的に確保される、コンピュータのメモリ中のある「領域」だと考えてもらえればよい。メモリ中に確保されるバッファは、通常、論理的に連続したメモリの中で、他の領域と隣接して存在する。プログラムの実行中に、この領域の大きさを超えるデータが入力された場合、時としてこの領域に納まりきれずにデータが溢れてしまい(つまり「オーバーフロー」する)、溢れたデータは隣接する他の領域へと書き込まれる。これがバッファ・オーバーフローという現象である(図4)。


図4 バッファ・オーバーフローにより、隣接するメモリ領域にデータが溢れこむ


このようなバッファ・オーバーフローは、基本的にプログラミング上のミスにより発生する。CやC++といったプログラミング言語は比較的自由にメモリを操作することができるのだが、反面、メモリ中に格納するデータの大きさのチェックなどをプログラマが怠った場合、確保した領域の大きさ以上のデータが入ってきてしまい、結果としてバッファ・オーバーフローが生じる

バッファ・オーバーフローが発生した際に何が起こるかは、隣接する領域が何であるか、またどのようなデータが溢れ込んだかによって、さまざまである。最も一般的な振る舞いとしては、バッファ・オーバーフローによるプログラムの異常終了があげられる。つまり隣接領域にある本来のデータが、溢れ込んだデータで書き換えられ、プログラムはどう処理してよいかわからず異常終了するのである。C言語でプログラムを書いたことのある人は、このような異常終了は少なからず経験があるだろう。

場合によっては、書き換えられる隣接領域の特性を利用し、巧妙に仕組んだデータをオーバーフローさせることで、プログラムに不正なコードを実行させることができる(図5)。


図5 リターンアドレスが書き換わることで、不正なプログラムコードが実行


この図の「リターンアドレス」と書かれている部分には、プログラムコードのアドレスが格納されている。リターンアドレスとは、関数(サブルーチン)の処理終了後に次に実行すべきプログラムコードのアドレスのことで、関数を呼び出す際にスタックと呼ばれるメモリ中に格納され、関数終了直後にプログラムの制御はそのアドレスへと移る。

図5ではスタックメモリ中の「領域A」を、不正なプログラムコードと偽のリターンアドレス(図中の「1234」という数値)でオーバーフローさせ、リターンアドレスを「1234」のように書き換えている。アドレス「1234」は、スタックメモリ上の不正プログラムコードの位置を指し示しているため、このプログラムは関数処理を終了すると、送り込んだ不正プログラムコードを自動的に実行する。これが典型的なバッファ・オーバーフローの仕組みである。




さまざまなバッファ・オーバーフロー


バッファ・オーバーフローは、どのメモリ領域で発生するかによって、次の二つに大きく分類することができる。

    1) スタック・バッファ・オーバーフロー

    スタック・バッファ・オーバーフローは、メモリ中のスタック領域におけるバッファ・オーバーフローである。スタック領域とは自動変数、関数の引数、戻り値、リターンアドレス等、プログラム処理中に一時的に使用するデータが格納されるメモリ領域を指す。図5で示したリターンアドレス書き換えによる不正プログラムコードの実行は、まさにスタック・バッファ・オーバーフローに相当する。

    スタック・バッファ・オーバーフローにおけるもう一つの重要な攻撃方法は、「例外ハンドラアドレス」の書き換えである。プログラム実行中に異常が発生した際、例外処理を実行するのが例外ハンドラだが、Windows環境では例外ハンドラが呼び出すプログラムコードのアドレスがスタックに格納されている。つまり、バッファ・オーバーフローでこのアドレスを書き換えた後、何らかのプログラム異常(メモリアクセス違反等)を発生させることで、送り込んだ不正プログラムコードに制御を移すことが可能になる。

    2) ヒープ・バッファ・オーバーフロー

    一方、メモリ中のヒープ領域で発生するバッファ・オーバーフローのことを、ヒープ・バッファ・オーバーフローと呼ぶ。ヒープ領域とは、プログラムが永続的に使用するメモリを動的に確保する際に用いられるメモリ領域である。スタック領域のデータは、それを使用する関数が終了した時に消えてしまうが、ヒープ領域のデータは、プログラムが明示的に開放しない限り存続する。C言語ではプログラム実行中に動的にメモリを確保したい場合、malloc()といった関数を使用するが、このようなメモリはヒープ領域に確保される。つまりmalloc()等を用いて取得したメモリがオーバーフローすることにより、ヒープ・バッファ・オーバーフローが発生する。

    ヒープ・バッファ・オーバーフローを利用して不正なプログラムコードを実行させることは、スタック・バッファ・オーバーフローに比較すると、一般的には困難である。バッファ領域に格納される、リターンアドレスや例外ハンドラアドレスのようなプログラムコードのアドレスが、ヒープ領域には通常存在しないからだ。

    しかし不可能なわけではない。ヒープ領域のメモリ管理方法を巧みに利用する方法で、任意のメモリ領域を任意のバイト列で書き換え、送り込んだ不正なコードに制御を移させるような攻撃手法が知られている。

バッファ・オーバーフロー攻撃の際に、ターゲットのコンピュータへ送り込んで実行させる不正プログラムコードのことを、一般的に「シェルコード」と呼ぶ。バッファ・オーバーフロー攻撃を成功させるためには、このシェルコードの存在が欠かせない。

シェルコードはCPUが直接実行するマシン言語の形態をとっており、ターゲットのOSやCPUに依存するため、作成するにはかなりのスキルが必要とされる。しかしWindowsを含む主要なOSに関するシェルコードは、インターネット上で多く出回っており、通常はそれを再利用すれば事足りる。また用途に合わせたシェルコードを自動生成するようなツールも存在する(図6)[4]。


図6 各種のシェルコードを自動生成してくれるWebサイト(Metasploit Project)





バッファ・オーバーフロー攻撃を防ぐには


このようなバッファ・オーバーフローを利用した攻撃からコンピュータを守るためには、次のような対策が有効とされる。

    1) セキュリティパッチを適用する

    バッファ・オーバーフローはプログラミング上のミス、つまりバグであるので、通常はソフトウェア開発者(Windows関連だとマイクロソフト)よりバグを修正したパッチが提供される。

    システム管理者は使用しているソフトウェアの脆弱性情報に日ごろから注意を払い、バッファ・オーバーフロー脆弱性が報告された際には、速やかにパッチの適用を検討すべきである。

    ただしパッチは緊急的に提供されるため、適用により不具合が発生するケースが少なくない。実運用環境に適用する前に、テスト環境下で十分にテストすることが求められる。

    2) 不要なネットワークアクセスを制限する

    ワームの侵入や不正アクセス等、ネットワーク経由でのバッファ・オーバーフロー攻撃は、非常に大きな脅威といえる。その一方で、特に組織内部のネットワーク上では厳密なネットワークアクセス制限が行われていない場合が多い。

    ネットワーク上、あるいはホスト上にファイアウォール機能を導入し、不要なネットワークアクセスを制限することにより、ネットワークから攻撃されるようなバッファ・オーバーフローをある程度は防ぐことができるだろう。

    この場合、インバウンド(内向き)のアクセスを制御することはもちろんだが、アウトバウンド(外向き)のアクセス制御も重要となる。バッファ・オーバーフロー攻撃を受けた場合、そのホストを踏み台にして他のコンピュータへ攻撃したり、あるいは不正プログラムをコピーするために別のコンピュータにアクセスしたりすることがあるからだ。

    3) 不要なサービスプログラムを停止する

    Windowsに限らず多くのOSでは、標準インストール状態で多くのサービスプログラムが起動するようになっている。しかしこれらのサービスすべてが、必ずしも必要であるとは限らない。

    例えばWindows NT/2000/XPでは「メッセンジャ」というサービスがデフォルトで起動する。このメッセンジャサービスは、Windowsマシン同士でメッセージを交換するためのものだが、一般的にはあまり使う必要のないサービスである。昨年の10月、メッセンジャサービスにバッファ・オーバーフローの脆弱性が報告された[5]が、このサービスを停止しておけば、メッセンジャサービスに対するバッファ・オーバーフロー攻撃を受けることはない。つまり、緊急にパッチを適用する必要もないわけだ。

    このように、不要なサービスプログラムを極力停止(あるいは削除)することで、仮にそのサービスプログラムに脆弱性が発生したとしても影響を受けずにすむ。

今まで述べてきたとおり、バッファ・オーバーフローは非常に危険な脆弱性である。しかもバッファ・オーバーフロー脆弱性の発生(発覚といった方がよいかもしれない)は、過去においても非常に多く、おそらく今後も一定の数は出てくるだろう。

ネットワークに対してサービスを提供しているプログラム(Webサービスなど)がバッファ・オーバーフローを起こすことにより、場合によってはネットワーク経由でそのコンピュータ上において不正なプログラムコードを実行することもできてしまう。

ネットワーク経由でアクセスできないプログラムであったとしても、システム権限等の高い権限で実行している場合、ローカルログオンした一般ユーザがバッファ・オーバーフローを発生させ、システム権限で不正なコードを実行させることも考えられる。

最も重要なのは、バッファ・オーバーフローが発生しないように正しくプログラムを開発することである。最近はセキュリティホールのないセキュアなプログラムを開発するための、セキュア・プログラミング手法に関するさまざまな文献、書籍が発表されている。その中でもIPA(独立行政法人 情報処理推進機構)がWebで公開している「セキュア・プログラミング講座[6]」は、非常によいコンテンツなので、プログラム開発に携わっている方にはぜひ一読をお薦めしたい。



[1]  Microsoft Windows のセキュリティ更新プログラム(MS04-011)
     http://www.microsoft.com/japan/technet/security/bulletin/MS04-011.asp

[2]  RPCインターフェイスのバッファオーバーランによりコードが実行される(MS03-026)
     http://www.microsoft.com/japan/technet/security/bulletin/MS03-026.asp

[3]  Common Vulnerabilities and Exposures
     http://www.cve.mitre.org

[4]  Metasploit Project
     http://metasploit.com/shellcode.html

[5]  メッセンジャサービスのバッファオーバーランにより、コードが実行される(MS03-043)
     http://www.microsoft.com/japan/technet/security/bulletin/MS03-043.asp

[6]  IPA セキュア・プログラミング講座
     http://www.ipa.go.jp/security/awareness/vendor/programming/index.html


2004年5月執筆
塩月 誠人
ネットワークセキュリティコンサルタント