シリアライズ(serialization)と復元(deserialization)の仕組みを悪用することで、セキュリティマネージャによるチェックをバイパスすることができる。セキュリティマネージャによるチェックは、たとえば信頼できないコードによってクラスの内部状態が改変されるのを阻止するために、コンストラクタに組み込まれている場合がある。セキュリティマネージャによるそのようなチェックは、クラスのインスタンスが構築されるすべての個所で行われなくてはならない。たとえば、セキュリティチェックが行われることを条件に、センシティブな情報であるクラスの内部状態を呼出し元が取得できるような場合、同様のセキュリティチェックを復元時にも行わなくてはならない。こうすることで、攻撃者は、オブジェクトを復元してセンシティブな情報を取り出すことができなくなる。
違反コード
以下の違反コード例では、セキュリティマネージャによるチェックは、コンストラクタの中では行われているが、シリアライズと復元の過程で利用される writeObject() メソッドや readObject() メソッドの中では行われていない。したがって、信頼できないコードはクラスのインスタンスを作成できてしまう。
public final class Hometown implements Serializable { // private な内部状態 private String town; private static final String UNKNOWN = "UNKNOWN"; void performSecurityManagerCheck() throws AccessDeniedException { // ... } void validateInput(String newCC) throws InvalidInputException { // ... } public Hometown() { performSecurityManagerCheck(); // town をデフォルトの値に初期化 town = UNKNOWN; } // 呼出し元が内部状態を取得することができる String getValue() { performSecurityManagerCheck(); return town; } // 呼出し元が private な内部状態を変更できてしまう public void changeTown(String newTown) { if (town.equals(newTown)) { // 変更無し return; } else { performSecurityManagerCheck(); validateInput(newTown); town = newTown; } } private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(town); } private void readObject(ObjectInputStream in) throws IOException { in.defaultReadObject(); // 復元された値が通常のインスタンス構築時につくられる値と // 一致しない場合, 再度検証する if (!UNKNOWN.equals(town)) { validateInput(town); } } }
セキュリティマネージャによるチェックは存在するが、このコード例におけるデータはセンシティブなデータではない。暗号化されていないセンシティブなデータをシリアライズすると「SER03-J. 暗号化されていないセンシティブなデータをシリアライズしない」の違反となる。
AccessDeniedException と InvalidInputException は、throws 宣言する必要なくすべてのメソッドがスローできるセキュリティ例外である。
適合コード
以下の適合コードでは、クラスの内部状態の変更や取得を行うすべてのコンストラクタとメソッドの中で、必要とされるセキュリティマネージャによるチェックを実装している。そのため、攻撃者は、改変したオブジェクトを復元によって作成させたり、シリアライズされたバイトストリームを読み取ることはできない。
public final class Hometown implements Serializable { // ... 以下を除くメソッドには変更なし // writeObject() はシリアライズ時に正しくセキュリティチェックを行う private void writeObject(ObjectOutputStream out) throws IOException { performSecurityManagerCheck(); out.writeObject(town); } // readObject() は復元時に正しくセキュリティチェックを行う private void readObject(ObjectInputStream in) throws IOException { in.defaultReadObject(); // 復元された値が通常のインスタンス構築時につくられる値と // 一致しない場合は再度検証する if (!UNKNOWN.equals(town)) { performSecurityManagerCheck(); validateInput(town); } } }
performSecurityManagerCheck() メソッドの実装方法については、「SEC04-J. センシティブな処理はセキュリティマネージャによるチェックで保護する」を参照。このメソッドはファイナライザ攻撃対策に有効である。
ObjectInputStream.defaultReadObject() は、入力ストリームから取得したデータを使ってオブジェクトのフィールドに値を設定する。各フィールドは再帰的に復元されるため、this 参照は復元ルーチンによる制御から逃れることができてしまう。これが起こるのは、参照されるオブジェクトが、コンストラクタやフィールドの初期化子において this 参照を公開する場合である。詳細は「TSM01-J. オブジェクトの構築時にthis参照を逸出させない」を参照。本ルールに適合するためには、再帰的に復元されるサブオブジェクトは this 参照を公開してはならない。
リスク評価
シリアライズと復元において、セキュリティマネージャのチェックをバイパスされると、必要とされるセキュリティチェックを行わずにクラスを構築されてしまう。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SER04-J | 高 | 中 | 高 | P6 | L2 |
関連ガイドライン
Secure Coding Guidelines for the Java Programming Language, Version 3.0 | Guideline 5-4. Duplicate the SecurityManager checks enforced in a class during serialization and deserialization |
参考文献
[Long 2005] | Section 2.4, Serialization |
翻訳元
これは以下のページを翻訳したものです。
SER04-J. Do not allow serialization and deserialization to bypass the security manager (revision 84)