目次 | 前の項目 | 次の項目 | Java オブジェクト直列化仕様 Version 6.0 |
付録 A |
オブジェクト直列化システムを使えば、オブジェクトグラフからバイトストリームを作成し、(ディスクに保存するか、ネットワークを介して) JavaTM 環境の外へ送り、それを使って、同じ状態を持つ同等な新しいオブジェクトセットを再作成することができます。オブジェクトの状態がこの環境の外でどうなるかは、(定義により) JavaTM システムの制御外のことであり、したがって、システムで提供されるセキュリティーの制御外のことです。ここで 1 つの疑問が生じます。いったんオブジェクトが直列化されると、結果のバイト配列が、ある意味その直列化復元を行う Java プログラムのセキュリティーに悪影響を及ぼして実行および変更される可能性はないのでしょうか。ここでは、このようなセキュリティーの問題に焦点を当てて説明します。
オブジェクト直列化の目標は、できる限りシンプルでありながら、既知のセキュリティーの制約と一貫性があることです。 システムがシンプルであるほど、安全である可能性はより高くなります。以下は、オブジェクト直列化で使用されているセキュリティー方式の概要です。
java.io.Serializable
またはjava.io.Externalizable
インタフェースを実装しているオブジェクトだけが直列化される。データの重要性に応じて特定のフィールドの直列化を回避する機構が提供されている- 直列化パッケージを使用して、オブジェクトの作成または初期化を再実行することはできない。バイトストリームを直列化復元すると、新しいオブジェクトが作成されるが、既存のオブジェクトの内容が上書きされたり変更されたりすることはない
- オブジェクトを直列化復元すると、リモートソースからコードのダウンロードが開始される。 ただし、ダウンロードされたコードは、JavaTM コードの通常の検査およびセキュリティー機構によって制限される。直列化復元の副作用によってロードされるクラスは、その他の方法でロードされる場合と同じように保護される
セキュリティーを十分に確保しないでオブジェクトを直列化すると、悪意のあるユーザーが、直列化バイトストリームにアクセスして機密データを読み込んだり、不正なまたは危険な状態のオブジェクトを作成したり、直列化復元されているオブジェクトの private フィールドへの参照を取得する可能性があります。セキュリティーに関係する実装を行うときは、直列化するときに次の点を考慮する必要があります。
- デフォルトでは、オブジェクトを直列化すると、そのオブジェクトのフィールドが public であるかどうかにかかわらず、すべてのフィールドの値が直列化ストリームに書き込まれる。悪意のあるユーザーは、オブジェクトが直列化可能な場合は、コードでオブジェクトの直列化およびそのバイトストリームの検査を行うことによって、その private フィールドの値を効果的に読み込むことができる。この問題を回避する方法については「A.4 機密データの直列化の防止」を参照
- 直列化復元を行うと、受信した直列化ストリームのデータを使用して、オブジェクトの作成および初期化が行われる。直列化復元を行う前にストリームが破損したり変更された場合は、直列化復元されたオブジェクトは予期しない状態または不正な状態になる。この問題を回避する方法については「A.5 クラス特有の直列化メソッドの作成」を参照
- 直列化バイトストリームにワイヤーハンドル参照を追加すると、直列化復元しているときに、ストリーム内で以前に発生したオブジェクトに参照が追加されることがある。この結果、開発者は、直列化復元中に取得した private オブジェクトへの参照が、一意である保証がなくなる。この問題に対処する方法については、「A.6 非共有の直列化復元されたオブジェクトの保護」を参照
- Externalizable インタフェースを実装しているオブジェクトは、readExternal メソッドが public であるため、上書きされる可能性がある。呼び出し側は、任意の時点に
readExternal
メソッドを呼び出すことができる。 この場合、そのオブジェクトが任意のストリームに渡されて値が読み込まれるため、ターゲットオブジェクトが初期化し直される。この問題を回避する方法については、「A.7 外部化可能オブジェクトの上書きの防止」を参照
機密性の高いデータを含むフィールドは、直列化すべきではありません。直列化すると、直列化ストリームにアクセスすることでそれらの値を相手方に公開することになります。フィールドの直列化を防ぐ方法をいくつか次に示します。
直列化復元されたオブジェクトが、保証されるべきである不変式のセットを壊すような状態にならないように、クラスが独自の直列化と直列化復元のメソッドを定義することができます。あるクラスのデータメンバー間で維持する必要がある不変式のセットがある場合、これらの不変式について知ることができるのはそのクラスだけなので、これらの不変式を検査する直列化復元メソッドを提供するかどうかは、そのクラスの作成者次第です。セキュリティーを重視した実装を行う際には、直列化可能クラスの
readObject
メソッドが実際に public コンストラクタでなければならず、そのように扱う必要があります。これは、readObject
メソッドが暗黙的である場合と明示的である場合のどちらにもあてはまります。readObject
メソッドに提供されたバイトストリームが、適切に構築された正しいタイプのオブジェクトを直列化することによって生成されると想定するのは危険です。より防御性に優れたプログラミングを行うには、構築中のオブジェクトに悪影響を及ぼそうとする悪意あるユーザーによってバイトストリームが提供されることを想定する必要があります。このことは、セキュリティーを関知しない場合でも重要です。ディスクファイルが壊れ、直列化データが無効になることも起こり得ます。したがって、そのような不変式を検査することは、単にセキュリティーのためだけではなく、有効性を確保する手段でもあります。しかし、これを行うことができるのは、特定クラスのコードの中だけです。これは、どの不変式を維持し、検査するかを直列化パッケージで判断する方法がないからです。
JavaTM 2 SDK, Standard Edition Version 1.4 では、クラス定義メソッド
readObjectNoData
のサポートが追加されました (「3.5 readObjectNoData メソッド」を参照)。フィールドをデフォルト以外の値に初期化するfinal
以外の直列化可能クラスでは、サブクラスインスタンスが直列化復元されるときに直列化ストリームがそのクラスを直列化復元されたオブジェクトのスーパークラスとしてリストしない場合に、一貫した状態を保証するためにreadObjectNoData
メソッドを定義する必要があります。これは、受け取り側が、送り側とは異なるバージョンの直列化復元されたインスタンスのクラスを使用し、受け取り側のバージョンが、送り側のバージョンによって継承されないクラスを継承する場合に発生する可能性があります。また、直列化ストリームが改変された場合にも発生することがあります。したがって、readObjectNoData
は、「悪意のある」または不正なソースストリームであっても、直列化復元されたオブジェクトを正しく初期化するのに役立ちます。
クラスが private または package private のオブジェクト参照フィールドを保持し、かつそのクラスが、それらのオブジェクト参照はクラス (またはパッケージ) の外部では利用できないという事実に依存する場合は、防衛のために、直列化復元プロセスの一環として参照オブジェクトをコピーするか、またはObjectOutputStream.writeUnshared
メソッドとObjectInputStream.readUnshared
メソッド (JavaTM 2 SDK, Standard Edition Version 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
の呼び出しが、常にサブオブジェクトを自己防衛的にコピーする正しい方法とは限らないことに留意することも重要です。独立したコピーを作成するため (およびコピーへの参照を「横取り (from stegword)」しないため) にclone
メソッドをあてにできない場合、別の方法でコピーを作成する必要があります。サブオブジェクトのクラスが final ではない場合、呼び出されるclone
メソッドやヘルパーメソッドがサブクラスによってオーバーライドされる可能性があるため、常に別の方法でコピーを作成する必要があります。JavaTM 2 SDK, Standard Edition Version 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 状態の復号化と読み込みができなくなり、ストリームの内容の不正な変更に対する保護となります。暗号の使用可能なクラスが独自に直列化および直列化復元のメソッドを定義できるようにすることにより、または合成可能なストリームの抽象化に従うことにより、オブジェクトの直列化で暗号を使用できます。 この合成可能なストリームの抽象化により、データを暗号化する別のフィルタストリームに送られる直列化ストリームの出力を許可します。