シリアライズすることで、オブジェクトの状態をバイト列として保存し、後にそれを復元することが可能になる。しかし、シリアライズしたデータを保護する手段までは提供されていない。シリアライズされたデータにアクセスできる攻撃者は、そのデータを元にセンシティブな情報を探し出したり、オブジェクトの実装詳細を知る可能性がある。さらに、悪意あるデータを復元させてシステムを乗っ取るために、シリアライズされたデータを改変するかもしれない。元のコードで private キーワードのような修飾子を使ってアクセス制限していたとしても、そのデータはシリアライズされることで外部にさらされ、アクセスされる可能性がある。また、セキュリティマネージャは復元されたデータの信頼性を保証することはできない。
シリアライズすべきでないセンシティブなデータとして、暗号化鍵、電子証明書、シリアライズ時にセンシティブなデータへの参照を保持しているクラスなどが挙げられる。
本ルールは、センシティブな情報の意図しないシリアライズを防ぐことを意図している。センシティブな情報を意図的にシリアライズする場合には「SER02-J. センシティブなオブジェクトは信頼境界を越えて送信する前に署名し暗号化する」が適用される。
違反コード
以下の違反コード例において、Point クラスの座標を表すデータメンバは private 宣言されている。座標がセンシティブなデータであると仮定すると、データストリーム中に存在するこれらのデータに対して、悪意ある改ざんが行われる可能性がある。
public class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } public Point() { // 引数を持たないコンストラクタ } } public class Coordinates extends Point implements Serializable { public static void main(String[] args) { FileOutputStream fout = null; try { Point p = new Point(5, 2); fout = new FileOutputStream("point.ser"); ObjectOutputStream oout = new ObjectOutputStream(fout); oout.writeObject(p); } catch (Throwable t) { // ハンドラに処理を移す } finally { if (fout != null) { try { fout.close(); } catch (IOException x) { // エラー処理 } } } } }
センシティブなデータを持たない場合、単純に java.io.Serializable インタフェースを実装することで、クラスをシリアライズ可能にすることができる。java.io.Serializable インタフェースを実装しているということは、そのオブジェクトをシリアライズしてもセキュリティ上の問題はないということを意味する。このインタフェースを継承するサブクラスもシリアライズ可能であることに注意。この手法はセンシティブなデータを持つクラスには不適切である。
適合コード
センシティブなデータを持つクラスをシリアライズする場合、シリアライズされたデータからセンシティブなデータが取り除かれていることをプログラムが保証しなくてはならない。つまり、センシティブなデータを含むデータメンバのシリアライズ、serializable でないオブジェクトへの参照のシリアライズ、センシティブなオブジェクトへの参照のシリアライズ、これら全てを防止するということである。
以下の適合コードでは、誤ったシリアライズが行われる可能性を排除し、センシティブなデータを含むメンバが間違ってシリアライズされることを防止している。該当するメンバを transient 宣言することで、デフォルトのシリアライズの仕組みではシリアライズされないようにしている。
public class Point { private transient double x; // transient宣言している private transient double y; // transient宣言している public Point(double x, double y) { this.x = x; this.y = y; } public Point() { // 引数を持たないコンストラクタ } } public class Coordinates extends Point implements Serializable { public static void main(String[] args) { try { Point p = new Point(5,2); FileOutputStream fout = new FileOutputStream("point.ser"); ObjectOutputStream oout = new ObjectOutputStream(fout); oout.writeObject(p); oout.close(); } catch (Exception e) { // ハンドラに処理を移す } finally { if (fout != null) { try { fout.close(); } catch (IOException x) { // エラー処理 } } } } }
適合コード
以下のような方法も考えられる。
- センシティブなフィールドをシリアライズしたストリームに書き出さないように、writeObject()、writeReplace()、writeExternal() メソッドを独自に実装する。
- serialPersistentFields を定義し、センシティブなフィールドがシリアライズされないようにする(「SER00-J. 開発中のクラスにおいてシリアライズの互換性を維持する」を参照)。
違反コード
シリアライズが悪用されると、たとえばシングルトンクラスの複数のインスタンスを返すようなことが可能になる。以下の違反コード例([Bloch 2005]の例に基づく)では、サブクラス SensitiveClass が意図せずシリアライズ可能になっている。なぜなら、このクラスは、Serializable インタフェースを実装する java.lang.Number クラスを拡張しているからである。
public class SensitiveClass extends Number { // .. Number.doubleValue() のような抽象メソッドを実装 private static final SensitiveClass INSTANCE = new SensitiveClass(); public static SensitiveClass getInstance() { return INSTANCE; } private SensitiveClass() { // セキュリティチェックと引数の検証を行う } protected int getBalance() { int balance = 1000; return balance; } } class Malicious { public static void main(String[] args) { SensitiveClass sc = (SensitiveClass) deepCopy(SensitiveClass.getInstance()); // falseが出力される。新規インスタンスであることを示している System.out.println(sc == SensitiveClass.getInstance()); System.out.println("Balance = " + sc.getBalance()); } // このメソッドは実運用には使用してはならない static public Object deepCopy(Object obj) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); new ObjectOutputStream(bos).writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); return new ObjectInputStream(bin).readObject(); } catch (Exception e) { throw new IllegalArgumentException(e); } } }
シングルトンクラスの詳細は「MSC07-J. シングルトンオブジェクトのインスタンスを複数作らない」を参照。
適合コード
Serializable を実装するクラスやインタフェースは、可能な限り拡張しないこと。たとえば、シリアライズ不可能なクラスで、シリアライズ可能なクラスのインスタンスを持ち、メソッド呼出しが行われたらその処理をシリアライズ可能なクラスに委任する方法がある。
シリアライズ可能なクラスを拡張してシリアライズ不可能なクラスを作らなければならない場合、NotSerializableExceptionをスローするように writeObject()、readObject()、readObjectNoData()を独自に定義することで、サブクラスの不適切なシリアライズを防ぐことができる。サブクラス化によりオーバーライドされないように、これら独自に定義するメソッドは private 宣言あるいは final 宣言すること。
class SensitiveClass extends Number { // ... protected final Object writeObject(java.io.ObjectOutputStream out) throws NotSerializableException { throw new NotSerializableException(); } protected final Object readObject(java.io.ObjectInputStream in) throws NotSerializableException { throw new NotSerializableException(); } protected final Object readObjectNoData(java.io.ObjectInputStream in) throws NotSerializableException { throw new NotSerializableException(); } }
依然として攻撃者は、NotSerializableExceptionをキャッチするか、ファイナライザ攻撃を行うことで、SensitiveClass の初期化されていないインスタンスを取得することができる。詳しくは「OBJ11-J. コンストラクタが例外をスローする場合には細心の注意を払う」を参照。シリアライズ可能なクラスを拡張してシリアライズ不可能なクラスをつくる場合、メソッドを実行する前に必ず自分自身を検証すること。
例外
SER03-EX0: センシティブなデータは適切に暗号化した上でシリアライズしてもよい。
リスク評価
センシティブなデータをシリアライズすると、安全でない通信経路を使って送信されたり、安全でない場所にデータが保存されたり、意図せずデータが公開されてしまう恐れがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SER03-J | 中 | 高 | 高 | P6 | L2 |
関連ガイドライン
MITRE CWE | CWE-499. Serializable class containing sensitive data |
CWE-502. Deserialization of untrusted data | |
Secure Coding Guidelines for the Java Programming Language, Version 3.0 | Guideline 5-2. Guard sensitive data during serialization |
参考文献
[Bloch 2005] | Puzzle 83. Dyslexic monotheism |
[Bloch 2001] | Item 1. Enforce the singleton property with a private constructor |
[Greanier 2000] | Discover the Secrets of the Java Serialization API |
[Harold 1999] | |
[JLS 2005] | Transient Modifier |
[Long 2005] | Section 2.4, Serialization |
[Sun 2006] | Serialization Specification, A.4, Preventing Serialization of Sensitive Data |
翻訳元
これは以下のページを翻訳したものです。
SER03-J. Do not serialize unencrypted, sensitive data (revision 103)