目次 | 前の項目 | 次の項目 | Java オブジェクト直列化仕様 バージョン 5.0 |
オブジェクト直列化システムを使えば、オブジェクトグラフからバイトストリームを作成し、(ディスクに保存するか、ネットワークを介して) JavaTM 環境の外へ送り、それを使って、同じ状態を持つ同等な新しいオブジェクトセットを再作成することができます。
オブジェクトの状態がこの環境の外でどうなるかは、(定義により) JavaTM システムの制御外のことであり、したがって、システムで提供されるセキュリティの制御外のことです。ここで 1 つの疑問が生じます。いったんオブジェクトが直列化されると、結果のバイト配列が、ある意味その直列化復元を行う Java プログラムのセキュリティに悪影響を及ぼして検査および変更される可能性はないのでしょうか。ここでは、このようなセキュリティの問題に焦点を当てて説明します。
オブジェクト直列化の目標は、できるかぎりシンプルでありながら、既知のセキュリティの制約と一貫性を保つことです。システムがシンプルであるほど、安全である可能性はより高くなります。以下は、オブジェクト直列化で使用されているセキュリティ方式の概要です。
java.io.Serializable
または java.io.Externalizable
インタフェースを実装しているオブジェクトだけが直列化される。データの重要性に応じて特定のフィールドの直列化を回避する機構が提供されている
セキュリティを十分に確保しないでオブジェクトを直列化すると、悪意のあるユーザが、直列化バイトストリームにアクセスして機密データを読み込んだり、不正なまたは危険な状態のオブジェクトを作成したり、直列化復元されているオブジェクトの private フィールドへの参照を取得する可能性があります。セキュリティに関係する実装を行うときは、直列化するときに次の点を考慮する必要があります。
readExternal
メソッドを呼び出すことができる。この場合、そのオブジェクトが任意のストリームに渡されて値が読み込まれるため、ターゲットオブジェクトが初期化し直される。この問題を回避する方法については、「A.7 外部化可能オブジェクトの上書きの防止」を参照
機密性の高いデータを含むフィールドは、直列化すべきではありません。直列化すると、直列化ストリームにアクセスすることでそれらの値を相手方に公開することになります。フィールドの直列化を防ぐ方法をいくつか次に示します。
serialPersistentFields
フィールドを定義し、フィールド記述子のリストからそのフィールドを除外する
writeObject
や writeExternal
) を記述する。固有メソッドはフィールドを直列化ストリームに書き込まない (つまり ObjectOutputStream.defaultWriteObject
を呼び出さない)
直列化復元されたオブジェクトが、保証されるべきである不変式のセットを壊すような状態にならないように、クラスが独自の直列化と直列化復元のメソッドを定義することができます。あるクラスのデータメンバ間で維持する必要がある不変式のセットがある場合、これらの不変式について知ることができるのはそのクラスだけなので、これらの不変式を検査する直列化復元メソッドを提供するかどうかは、そのクラスの作成者次第です。
セキュリティを重視した実装を行う際には、直列化可能クラスの readObject
メソッドが実際に public コンストラクタでなければならず、そのように扱う必要があります。これは、readObject
メソッドが暗黙的である場合と明示的である場合のどちらにもあてはまります。readObject
メソッドに提供されたバイトストリームが、適切に構築された正しいタイプのオブジェクトを直列化することによって生成されると想定するのは危険です。より防御性に優れたプログラミングを行うには、構築中のオブジェクトに悪影響を及ぼそうとする悪意あるユーザによってバイトストリームが提供されることを想定する必要があります。
このことは、セキュリティを関知しない場合でも重要です。ディスクファイルが壊れ、直列化データが無効になることも起こり得ます。したがって、そのような不変式を検査することは、単にセキュリティのためだけではなく、有効性を確保する手段でもあります。しかし、これを行うことができるのは、特定クラスのコードの中だけです。これは、どの不変式を維持し、検査するかを直列化パッケージで判断する方法がないからです。
JavaTM 2 SDK, Standard Edition バージョン 1.4 では、クラス定義メソッド readObjectNoData
のサポートが追加されました (「3.5 readObjectNoData メソッド」を参照)。フィールドをデフォルト以外の値に初期化する final
以外の直列化可能クラスでは、サブクラスインスタンスが直列化復元されるときに、直列化ストリームがそのクラスを直列化復元されたオブジェクトのスーパークラスとしてリストしない場合に、一貫した状態を保証するために readObjectNoData
メソッドを定義する必要があります。これは、受け取り側が、送り側とは異なるバージョンの直列化復元されたインスタンスのクラスを使用し、受け取り側のバージョンが送り側のバージョンによって継承されないクラスを継承する場合に発生する可能性があります。また、直列化ストリームが改変された場合にも発生することがあります。したがって、readObjectNoData
は、「悪意のある」または不正なソースストリームであっても、直列化復元されたオブジェクトを正しく初期化するのに役立ちます。
クラスが private または package private のオブジェクト参照フィールドを保持し、かつそのクラスが、それらのオブジェクト参照はクラス (またはパッケージ) の外部では利用できないという事実に依存する場合は、防衛のために、直列化復元プロセスの一環として参照オブジェクトをコピーするか、または ObjectOutputStream.writeUnshared
メソッドと ObjectInputStream.readUnshared
メソッド (JavaTM 2 SDK, Standard Edition バージョン 1.4 で導入) を使って内部オブジェクトへの一意参照を保証する必要があります。
コピーする方法では、ストリームから直列化復元されるサブオブジェクトを、「信頼されない入力」として扱う必要があります。新たに作成したオブジェクトを初期化して、直列化復元されたサブオブジェクトと同じ値を保持させ、readObject
メソッドを使ってサブオブジェクトの代替とする必要があります。たとえば、オブジェクトが private バイト配列フィールド b を保持する場合、次に示すように、このフィールドを private のままにしなければなりません。
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); b = (byte[])b.clone(); if (<invariants are not satisfied>) throw new java.io.StreamCorruptedException(); }
この問題は、可変サブオブジェクトへの内部参照 (private) を含む不変オブジェクトの直列化を考慮する際に特に重要です。コンテナオブジェクトの直列化復元時に、サブオブジェクトをコピーするための特別な措置が何もとられない場合、直列化ストリームへの書き込み権限を持つ悪意のある第三者が、可変サブオブジェクトへの参照を偽造し、これらの参照を使ってコンテナオブジェクトの内部状態を変更することで、コンテナオブジェクトの不変性を侵害する場合があります。このような場合に備えて、不変のコンテナクラスがクラス固有の直列化復元メソッドを提供することがポイントです。直列化復元される各可変コンポーネントオブジェクトのプライベートなコピーの作成は、このメソッドを使って行います。不変性を維持するために、不変コンポーネントオブジェクトのコピーを作成する必要はないことに留意してください。
また、clone
の呼び出しが、常にサブオブジェクトを自己防衛的にコピーする正しい方法とは限らないことに留意することも重要です。独立したコピーを作成するため (およびコピーへの参照を「横取り」しないため) に clone
メソッドをあてにできない場合、別の方法でコピーを作成する必要があります。サブオブジェクトのクラスが final ではない場合、呼び出される clone
メソッドやヘルパーメソッドがサブクラスによってオーバーライドされる可能性があるため、常に別の方法でコピーを作成する必要があります。
JavaTM 2 SDK, Standard Edition バージョン 1.4 以降では、ObjectOutputStream.writeUnshared
メソッドと ObjectInputStream.readUnshared
メソッドを使って、直列化復元されたオブジェクトへの一意参照を保証することもできます。この方法では、防衛的にコピーする方法の複雑さ、パフォーマンスコスト、およびメモリオーバーヘッドの問題を回避できます。readUnshared
メソッドと writeUnshared
メソッドの詳細は、「3.1 ObjectInputStream クラス」および「2.1 ObjectOutputStream クラス」を参照してください。
Externalizable
インタフェースを実装するオブジェクトは、public の readExternal
メソッドを提供しなければなりません。このメソッドは public であるため、オブジェクトにアクセス権のあるユーザによって任意で呼び出されます。オブジェクトの内部状態を複数の (不正な) readExternal
の呼び出しによって上書きされないようにするには、実装の際にチェックを追加して、適切な場合にだけ内部の値が設定されるようにします。
public synchronized void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { if (! initialized) { initialized = true; // read in and set field values ... } else { throw new IllegalStateException(); } }
仮想マシンの外でバイトストリームを保護する別の方法として、直列化パッケージで作成したストリームの暗号化があります。バイトストリームを暗号化することにより、直列化オブジェクトの private 状態の復号化と読み込みができなくなり、ストリームの内容の不正な変更に対する保護となります。
暗号の使用可能なクラスが独自に直列化および直列化復元のメソッドを定義できるようにすることにより、または合成可能なストリームの抽象化に従うことにより、オブジェクトの直列化で暗号を使用できます。この合成可能なストリームの抽象化により、データを暗号化する別のフィルタストリームに送られる直列化ストリームの出力を許可します。
目次 | 前の項目 | 次の項目 | Java オブジェクト直列化仕様 バージョン 5.0 |
Copyright © 2004 Sun Microsystems, Inc. All rights reserved