例外を伝播させる際に例外に含まれるセンシティブな情報を取り除かないと、攻撃者にさらなる攻撃の手がかりを与えるような情報漏えいを引き起こすことが多い。攻撃者はプログラムの内部構成や動作メカニズムを外部に晒すために、巧妙に細工した入力を作成することができる。例外メッセージおよび例外の種類は、内部の情報を漏えいしうる。たとえば、FileNotFoundException という例外メッセージはファイルシステムの構成に関する情報を、例外の種類は要求されたファイルが存在しないことを明らかにする。
このルールは、クライアントプログラムだけでなく、サーバサイドのアプリケーションにも当てはまる。攻撃者は脆弱なウェブサーバからだけでなく、脆弱なウェブブラウザを利用するユーザからもセンシティブな情報を集めることができる。Schönefeld は2004年に Opera v7.54 に対する攻撃手法を発見した。この攻撃手法は、アプレットに使われる sun.security.krb5.Credentials クラスを悪用し、「スローされた java.security.AccessControlException が提供する情報をもとに、現在ログイン中のユーザ名とそのユーザのホームディレクトリの場所を取得することを可能にした。」[Schönefeld 2004]
すべての例外は、攻撃者がシステムに対してサービス運用妨害(DoS)攻撃を行う手がかりとなる情報を漏えいする。したがって、信頼境界を越えて伝播する例外メッセージと例外の種類の両方を取り除かなくてはならない。
例外 | 情報漏えいや脅威の説明 |
---|---|
java.io.FileNotFoundException | ファイルシステムの構成、ユーザ名の列挙 |
java.sql.SQLException | データベースの構成、ユーザ名の列挙 |
java.net.BindException | 信頼できないクライアントがサーバのポートを選択できる場合、開いているポートを列挙 |
java.util.ConcurrentModificationException | スレッドセーフでないコードに関する情報 |
javax.naming.InsufficientResourcesException | 不十分なサーバーリソース (DoSにつながるかもしれない) |
java.util.MissingResourceException | リソースの列挙 |
java.util.jar.JarException | ファイルシステムの構成 |
java.security.acl.NotOwnerException | 所有者の列挙 |
java.lang.OutOfMemoryError | DoS |
java.lang.StackOverflowError | DoS |
スタックトレースの出力もプロセスの構成や状態に関する情報を意図せず攻撃者に漏えいすることにつながる。コンソールから実行されたJavaのプログラムがキャッチしていない例外が原因で終了すると、例外メッセージとスタックトレースがコンソールに出力される。その場合、スタックトレース自体がプログラムの内部構成に関するセンシティブな情報を漏えいしてしまうかもしれない。それゆえ、コマンドラインから実行されるプログラムはキャッチしない例外が原因で終了してはならない。
違反コード (例外メッセージと種類による情報漏えい)
以下の違反コード例では、プログラムはユーザが指定したファイルを読み取らなくてはならないが、ファイルシステムの構成やファイルシステムに保存されている内容はセンシティブな情報である。プログラムは入力引数としてファイル名を受け取るが、例外発生時に例外メッセージがユーザに表示されないように処理していない。
class ExceptionExample { public static void main(String[] args) throws FileNotFoundException { // Linux はユーザのホームディレクトリのパスを環境変数 $HOME に保存する // Windows の場合は %APPDATA% FileInputStream fis = new FileInputStream(System.getenv("APPDATA") + args[0]); } }
ユーザが指定したファイルが存在しない場合、FileInputStream コンストラクタは FileNotFoundException 例外をスローする。攻撃者はこれを悪用し、有りえるパス名を繰り返しプログラムに渡すことで、ファイルシステムの全貌を明らかにすることができる。
違反コード (センシティブな例外をラップして再度スローする)
以下の違反コード例では、発生した例外をログに保存し、それをより一般的な例外にラップしてから再度スローしている。
try { FileInputStream fis = new FileInputStream(System.getenv("APPDATA") + args[0]); } catch (FileNotFoundException e) { // 例外をログに保存する throw new IOException("Unable to retrieve file", e); }
ログに保存された例外がユーザにアクセスされない場合であっても、例外自身に有益な情報が含まれていることに変わりはなく、攻撃者はこの情報をもとに、ファイルシステムの構成に関するセンシティブな情報を探すことができる。このコード例はfinallyブロックで入力ストリームをクローズしておらず、「FIO04-J. 不要になったリソースは解放する」にも違反していることに注意。これ以降に示すコード例では、コードの簡素化のためにfinallyブロックを省略している。
違反コード (無害化された例外)
以下の違反コード例では、例外をログに保存し、FileNotFoundException をラップしない独自の例外をスローしている。
class SecurityIOException extends IOException {/* ... */}; try { FileInputStream fis = new FileInputStream(System.getenv("APPDATA") + args[0]); } catch (FileNotFoundException e) { // 例外をログに保存 throw new SecurityIOException(); }
前述のコード例に比べれば、有益な情報を攻撃者に漏えいする可能性は低いかもしれないが、依然として特定のファイルが読み取れないことを明らかにしている。より厳密に言えば、存在しないファイルパスと存在するファイルパスに対するプログラムの動作は異なるため、攻撃者はこの動作の違いをもとにファイルシステムに関するセンシティブな情報を推察することができるのである。ユーザからの入力を制限しないと、システムはブルートフォース攻撃を受けてしまう。攻撃者は、考えられるファイル名を網羅的に入力することで、システムに存在する有効なファイル名を見つけることができる。無害化した例外をプログラムが返す場合にはファイルが存在しないことを意味し、例外を返さない場合はファイルが存在するというわけである。
適合コード (セキュリティポリシー)
以下の適合コードでは、c:\homepath に置かれたファイルだけをオープンすることができ、このディレクトリの外にあるファイルについてユーザはまったく知り得ない、というポリシーを実装している。ファイルをオープンすることが出来なかったりファイルが適切なディレクトリに存在しない場合には、単純なエラーメッセージを出力するだけであり、c:\homepathの外にあるファイルに関する情報は隠されている。
この解決法では、File.getCanonicaFile() メソッドを使用してファイル名を正規化し、後で行うパス名の比較を簡素化している(詳しくは「IDS02-J. パス名は検証する前に正規化する」を参照。)
class ExceptionExample { public static void main(String[] args) { File file = null; try { file = new File(System.getenv("APPDATA") + args[0]).getCanonicalFile(); if (!file.getPath().startsWith("c:\\homepath")) { System.out.println("Invalid file"); return; } } catch (IOException x) { System.out.println("Invalid file"); return; } try { FileInputStream fis = new FileInputStream(file); } catch (FileNotFoundException x) { System.out.println("Invalid file"); return; } } }
適合コード (Restricted Input)
以下の適合コードは、ユーザがオープンすることができるファイルが c:\homepath\file1 と c:\homepath\file2 だけであるというポリシーに基づいて動作する。
このコードではまた、「ERR08-J. NullPointerException およびその親クラスの例外をキャッチしない」の例外で認められているように Throwableをキャッチしている。 また「ERR00-J. チェック例外を抑制あるいは無視しない」で解説した MyExceptionReporter クラスを使用し、例外メッセージからセンシティブな情報を取り除いている。
class ExceptionExample { public static void main(String[] args) { FileInputStream fis = null; try { switch(Integer.valueOf(args[0])) { case 1: fis = new FileInputStream("c:\\homepath\\file1"); break; case 2: fis = new FileInputStream("c:\\homepath\\file2"); break; //... default: System.out.println("Invalid option"); break; } } catch (Throwable t) { MyExceptionReporter.report(t); // 無害化 } } }
このルールに適合するコードでは、java.security.AccessControlException や java.lang.SecurityException といったセキュリティ例外をログに保存し、適切に無害化しなければならない。詳しくは「ERR02-J. ログ保存中の例外発生を防ぐ」を参照。 「ERR00-J. チェック例外を抑制あるいは無視しない」に示した MyExceptionReporter クラスは、このように例外をログに保存し無害化する方法について解説している。
プログラムの拡張性を考慮するならば、switch 文を使うのではなく、整数を正当なファイル名にマップしたり、有効なファイルを表す列挙型を使うべきである。
リスク評価
漏えいする情報を制限するよう注意しないと、例外によってセンシティブな情報を意図せず外に漏えいしてしまう恐れがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
ERR01-J | 中 | 中 | 高 | P4 | L3 |
関連する脆弱性
CVE-2009-2897 には、SpringSource Hyperic HQ の複数のバージョンに見つかったクロスサイトスクリプティングの脆弱性について記載されている。これらの脆弱性は、遠隔の攻撃者がプログラムの数値入力に不正な値を入力することで、任意のスクリプトやHTMLを注入することができるというものであった。ウェブインタフェースに不正な数値パラメータを入力することで発生する java.lang.NumberFormatException 例外がキャッチされないことから、この脆弱性の存在が明らかになった。
関連ガイドライン
C++ Secure Coding Standard | ERR12-CPP. Do not allow exceptions to transmit sensitive information |
MITRE CWE | CWE-209. Information exposure through an error message |
CWE-600. Uncaught exception in servlet | |
CWE-497. Exposure of system data to an unauthorized control sphere |
参考文献
[Gong 2003] | 9.1, Security Exceptions |
翻訳元
これは以下のページを翻訳したものです。
ERR01-J. Do not allow exceptions to expose sensitive information (revision 116)