記事の冒頭 前回の記事
バッファ上書きの脆弱性を検出することは非常に重要かつ価値があります。しかし、その除去には継続して認識し、安全なバッファの扱いを理解する必要があります。脆弱性を検出する一番現実的で簡単な方法は、こうした脆弱性を許さない言語を用いることです。例えばC言語は、メモリ空間へ直接アクセスできる上、厳密な型がないため、こうした脆弱性が発生しやすいです。一方、Java、Python、.NETなどの言語やプラットフォームは、特別なチェックや変更を必要とせず、オーバーフロー脆弱性のリスクが低くなっています。
もちろん、開発言語を丸ごと変更できない場合もあります。その場合、バッファを扱う際は安全な方法を実践する必要があります。ワイヤハンドリングの関数については、安全に使える方法と、使わないほうがよい方法について議論が多くあります。
安全でない機能に選択肢があれば全てが失われるわけではありません。コンパイルや実行時に、これらの脆弱性を検出するための進展が見られます。プログラム実行時、コンパイラはしばしば「カナリア」と呼ばれるランダムな値を生成し、各バッファの後にスタック上へ配置します。いわゆる炭鉱鳥のように、これらのカナリアの値が脅威となります。カナリアの値と元の値を比較することで、バッファオーバーフローが発生したかどうかを判断できます。もし値が変われば、プログラムは戻り先アドレスが変更されるのではなく、停止したりエラーモードに入る可能性があります。
今日の一部のOSは、実行不可領域やアドレス空間配置ランダム化(ASLR)といった追加の保護機能を提供しています。実行不可領域(すなわちDEP)は、スタックや場合によっては他の構造体を、コードが実行できない領域として指定します。これにより、攻撃者はスタック上のコードを悪用できず、正常に実行されることは期待できません。
開発、コンパイラ、またはOSレベルで対策が施されていても、脆弱性が見逃され攻撃対象となる場合があります。場合によっては、攻撃が成功したこと自体がバッファオーバーフローの最初の兆候となります。この場合、まず脆弱性を特定し、問題解決のためにコードの基盤を修正する必要があります。その上で、脆弱なコードを新たに強化されたバージョンに置換することを目指します。理想的には、インターネット接続されているすべてのシステムへ自動更新で配信されることが望まれます。
しかし、こうした更新が十分な範囲をカバーするとは限りません。ソフトは、インターネットアクセスが限られるシステムで組織や個人に利用されている可能性があります。その場合は手動更新が必要です。更新情報はソフトを使用する管理者に伝え、パッチがすぐにダウンロードできる状態にする必要があります。パッチの作成と配布は、脆弱性検出にできるだけ近いタイミングで行い、ユーザーやシステムの脆弱な時間を最小限に抑えます。
安全なバッファ管理機能、コンパイラのセキュリティ機能、そして適切なOSにより、バッファオーバーフローに対してしっかりと守ることができます。こうした対策があってもエラーを継続的に特定することは重要で、ソースコードの細かい検証は手間がかかり、人の目では見逃す可能性も残ります。
メモリ破損の脆弱性は、プログラムが意図したメモリ領域を越え、攻撃者が制御するデータを書き込むと発生します。これによりプログラムが破壊されるか、ましてやシステム全体の制御権が攻撃者に渡る恐れがあります。Apple、Google、Microsoftなど大手企業が解消に努めているにもかかわらず、メモリ破損の脆弱性は長年ソフトを悩ませています。
これらのエラーは検出が難しく、システム全体に影響を与えるため、セキュリティ専門家はソフトの悪用を防ぎ、万が一の際の被害を制限する仕組みを設けています。理想は、脆弱なコードが残ることで、開発者にコード修正や安全な言語への書き換えの猶予を与える仕組みですが、完璧なものはありません。それでも、ASLRは最も有効な対策のひとつとされています。
アドレス空間配置ランダム化 (ASLR) は、OS向けのメモリ保護機能で、実行可能なデータの配置をランダムに行うことでバッファオーバーフロー攻撃のリスクを下げます。ASLRにより、必要なコード(例としてROP機能やモジュール)の配置が分かりにくくなるため、脆弱性そのものをなくすのではなく、悪用を困難にしています。
多くのサイバー攻撃、特にゼロデイ攻撃の成功は、攻撃者がメモリ内のプロセスや関数の状態を把握できるかどうかに依存しています。ASLRはアドレス空間を予測不能な場所に配置し、攻撃者が誤った場所を狙えば対象アプリがクラッシュして攻撃が止まり、システムに警告が送られます。
Pax Projectは2001年にLinux向けパッチとしてASLRを開発し、2007年以降VistaからWindowsにも組み込まれました。ASLR導入以前は、ファイルやアプリのメモリ配置は容易に特定できました。
VistaでASLRが導入された際、アドレス空間の候補は256箇所に増え、攻撃者が正しい実行場所を見つける確率は1/256となりました。
ASLRは、実行時にプログラムやライブラリの配置に関する開発者の前提を覆すことで動作します。例えば、DEPを突破するために用いられるReturn Oriented Programming (ROP)で使われるモジュールの位置はその一例です。ASLRは、主要プログラム、動的ライブラリ、スタック、メモリにマッピングされたファイルなど、脆弱なプロセス全体のアドレス空間を混乱させます。そのため、攻撃のペイロードは被害プロセスのアドレス空間に合わせて調整する必要があります。攻撃者が無作為にメモリアドレスやエンコード済みのアドレスを全マシンに送ったとしても、ASLRが有効であれば選ばれるメモリアドレスは異なり、攻撃は成功しにくくなります。結果として、脆弱なプログラムは容易にクラッシュします。
アドレス空間の配置をランダムにすることで、DLLの初期アドレスも起動時のランダム性に依存します。実際、これは次回再起動時に基本ライブラリのアドレスが変更されることを意味し、攻撃者はメモリ漏洩や総当たり攻撃などと組み合わせて、この弱点を突く可能性があります。
アドレス空間のランダム化の目的は、攻撃者が狙ったメモリアドレスに確実に到達するのを防ぐことです。ASLRは攻撃の捕捉に注力するのではなく、攻撃の成立可能性を低くすることを狙っています。もしシェルがランダム化により誤ったアドレスにジャンプした場合、プログラムの動作は未定義となり、例外やクラッシュ、停止、または予期しない動作を引き起こす恐れがあります。
サイバー攻撃、悪用、シェルコードに関するフォレンジック情報は、本格的な調査に不可欠です。悪用されたプロセス、メモリ領域、呼び出しの記録から、攻撃の痕跡やフィンガープリント、タグ付けが可能となります。しかし、ASLR自身は攻撃が発生しているか、いつ停止したかを把握できないため、こうした情報は得られません。
Windowsにアドレス空間のランダム化が導入されてから、実際の被害は軽減されましたが、攻撃者は常にASLRを回避する新たな技術を開発しています。回避方法としては、ASLRが適用されないモジュールでのROPチェーンの利用(例:CVE 2013-1347)、JITやNOPスプレー(例:CVE-2013-3346)、メモリ情報の漏洩などが挙げられます。
バッファオーバーフローを悪用できる脆弱性を防ぐ最も簡単な方法は、プログラマーがコードを安全に保つことです。しかし、これは自動で行えるものではなく、コードの整合性を保証するための大変な作業を伴います。コード行数が増えるほど必要な時間も増加するため、現実的には他の保護手段が求められます。このため、MicrosoftはDEP(Data Execution Prevention)という機能を実装しました。
DEPは、ウイルスやその他の脅威からコンピュータを守るためのセキュリティ機能です。悪意あるプログラムは、OSや正規のプログラム用に予約されたシステムメモリからコードを実行しようとし、攻撃を試みます。こうした攻撃はプログラムやファイルに被害を与える可能性があります。
DEPは、プログラムがシステムメモリを安全に使用しているか監視し、もし不適切な使用が検出されればプログラムを終了し警告を発します。
DEPにはハードウェアベースとソフトウェアベースの両方の構成があります。
ハードウェアベースのDEPは、DEP実装の中で最も安全とされています。この方式では、明示的な実行コードがない限り、プロセッサがすべてのメモリ領域を「実行不可」としてマークします。DEPの目的は、実行不可領域でコードが実行されないようにすることです。
ただし、ハードウェアベースのDEPは対応できるプロセス数が限られている点が課題です。この機能は、AMDではNXサービス、IntelではXDサービスと呼ばれます。
ハードウェアベースのDEPが利用できない場合は、ソフトウェアベースのDEPを使用します。この形態のDEPはWindowsに組み込まれており、プログラムが発生させる例外を検出し、その例外が正当なものかどうか確認してから実行を続けさせます。
構造例外処理の上書き防止機能(SEHOP)は、典型的なバッファオーバーフロー攻撃に用いられる構造例外の上書き手法の悪用を防ぎます。SEHOPを改良する目的は、悪意ある者が構造例外処理(SEH)の悪用手法を利用するのを防ぐためです。この手法は2003年9月にNGS SoftwareのDavid Litchfieldによって広められ、それ以降、SEH防止手法は攻撃の定番となりました。最新版のMetasploitシステムの約20%がSEH関連の技術を利用しており、SEHの侵害調査はブラウザ経由の脆弱性特定にも役立っています。
SEHの脆弱性は、Windowsの32ビット例外伝送機能を悪用して任意のコードを実行するために突かれます。実際には、スタックベースのバッファオーバーフローを利用して、スレッドに記録された例外ハンドラ情報を上書きすることで悪用されることが多いです。
SEHOPの対策としては、一般的に2つの方法が考えられます。1つ目は、実行ファイルにプラットフォームが対策を施すためのメタデータを組み込むよう、コード構成を変更する方法です。Microsoftはこの方法を採用し、Visual Studio 2003でSAFESEHと呼ばれるフラグを導入しました。しかし、実行ファイルの再構築が必要であり、例外ハンドラが画像ファイル外を指す場合には完全には対応できないため、SafeSEHはあまり魅力的ではありません。
2つ目の方法は、バイナリのメタデータに頼らず動的な制御を例外処理に追加することです。SEHOPはこの方法を採用しており、登録された例外ハンドラを呼び出す前に、スレッドの例外ハンドラリストが安全であるかを確認することで、攻撃者がSEHを悪用できないようにしています。この対策は、SEH違反時の副作用を利用することで実現されています。ほとんどのスタックベースのバッファオーバーフローが発生する際、攻撃者は例外登録レコード内の次のポインタを暗黙的に記録し、その後に例外上書き機能によりポインタが上書きされます。結果として、次のポインタが破壊され例外ハンドラの連鎖が保たれなくなります。この考え方とASLRを組み合わせることで、SEHOPはSEH違反を効果的に緩和することが可能となります。
その他、バッファオーバーフロー攻撃や脆弱性を防ぐための手法には次のものが含まれます:
バッファオーバーフローが注目されるようになってから約20年が経過し、かつてほど効果的には機能しなくなった対策もあります。これには、スタックカナリア、アドレス空間配置ランダム化(ASLR)、コンパイラ警告、そしてスタック上でコードが実行されないようにするハードウェア変更などが含まれます。
まず、スタックベースのオーバーフロー攻撃に対する最善の保護は、安全なコーディングを実践することです。特に、攻撃者に無制限なメモリアクセスを許さないような機能の使用をやめ、隣接するメモリの値が変更されないようにすることが大切です。つまり、攻撃者が変更を試みる変数のメモリのみアクセスできれば、コード実行に予期しない影響を与えることはできません。
しかし、無制限なメモリアクセスを可能にする不安全な機能を実装しているプログラムは数多く存在し、それらすべてを安全なコーディングに則って修正することは困難です。OSメーカーは、こうした古いプログラムで悪いコーディングが任意のコード実行を招かないよう、さまざまな緩和策を講じています。とはいえ、ソフト開発の初期段階における保護こそが、バッファオーバーフロー攻撃を守る最善策です。
「バッファオーバーフロー」という用語は軽く使われがちですが、システムの安全性にとっては他のほとんどの脅威よりも深刻な問題です。最終的に、攻撃者はこの手口を用いてシステム運用に影響を及ぼす可能性があり、こうした攻撃は防御側が追いつくよりも数歩先を行っています。
最新情報を購読