オブジェクトのシリアライズ(serialization)時および復元(deserialization)時に特別な処理を行う必要のあるクラスは、特別なメソッドを正確に次のようなシグネチャで実装しなくてはならない。
private void writeObject(java.io.ObjectOutputStream out) throws IOException; private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException; private void readObjectNoData() throws ObjectStreamException;
シリアライズ可能ないかなるクラスにおいても、これらのメソッドは private 宣言しなければならない点に注意。シリアライズ可能なクラスではこれらに加えて、readResolve() メソッドと writeReplace() も実装することがあるだろう。シリアライズに関する仕様 [Sun 2006] では readResolve() メソッドと writeReplace() メソッドについて以下のように記述している。
Serializable インタフェースや Externalizable インタフェースを実装するクラスは、readResolve メソッドを使うことによって、ストリームから読み込んだオブジェクトを、呼び出し側に返される前に、置換または解釈処理できます。readResolve メソッドを実装することによって、クラスは、復元されるインスタンスの型およびインスタンスを直接制御できます。
Serializable インタフェースや Externalizable インタフェースを実装するクラスは、writeReplace メソッドを使うことによって、オブジェクトがストリームに書き込まれる前に、その置換を指定できます。writeReplace メソッドを実装することによって、クラスは、シリアライズされるインスタンスの型およびインスタンスを直接制御できます。
readResolve()メソッドとwriteReplace()メソッドには、あらゆるアクセス指定子を追加することが可能である。しかし、これらのメソッドを private 宣言すると、サブクラスにおいて、これらのメソッドを呼び出したり、オーバーライドすることができなくなる。同様に、これらのメソッドを static 宣言すると、サブクラスはメソッドをオーバーライドすることはできず、メソッドを隠蔽することができるだけになる。
これらのメソッドシグネチャから逸脱したメソッドは、オブジェクトのシリアライズ時や復元時に呼び出されなくなってしまう。特に public 宣言された場合には、信頼できないコードからアクセスされてしまうかもしれない。
大半のインタフェースとは異なり、Serializable インタフェースはこれらのメソッドのシグネチャを規定しない。インタフェースはpublicなフィールドやメソッドのみを規定するものだが、readObject(), readObjectNoData, writeObject() は private 宣言しなくてはならない。同様に Serializable インタフェースは、readResolve() メソッドや writeReplace() メソッドを static、public、private 宣言することを防げない。それゆえ、Javaのシリアライズメカニズムでは、これらのメソッドがメソッドシグネチャから逸脱していたとしても、コンパイラによる検出はできない。
違反コード (readObject()、writeObject())
以下の違反コード例に Ser クラスを示す。このクラスのコンストラクタは private 宣言されているので、クラスの外部にあるコードは、クラスのインスタンスを作成することができないはずである。しかし、Ser クラスは java.io.Serializable を実装し、readObject() と writeObject() をpublicメソッドとして定義している。したがって、信頼できないコードは、readObject() メソッドを使って再構築されたオブジェクトを取得したり、writeObject() を使ってストリームに書き込んだりすることができる。
public class Ser implements Serializable { private final long serialVersionUID = 123456789; private Ser() { // 初期化する } public static void writeObject(final ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); } public static void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); } }
また、static キーワードを取り除くだけでは、このコードを安全にすることはできない。JVMは2つのメソッドを検知することができず、独自のシリアライズ形式を使用できなくなる。
適合コード (readObject()、writeObject())
以下の適合コードでは、readObject() メソッドと writeObject() メソッドを static 宣言せずに private 宣言し、メソッドへのアクセスを制限している。
private void writeObject(final ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); } private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); }
メソッドをアクセス制限することで、これら2つのメソッドが悪意あるクラスによってオーバーライドされることも防いでいる。
違反コード (readResolve()、writeReplace())
以下の違反コード例では、readResolve() メソッドと writeReplace() メソッドを private 宣言している。
class Extendable implements Serializable { private Object readResolve() { // ... } private Object writeReplace() { // ... } }
違反コード (readResolve()、writeReplace())
以下の違反コード例では、readResolve() メソッドと writeReplace() メソッドを static 宣言している。
class Extendable implements Serializable { protected static Object readResolve() { // ... } protected static Object writeReplace() { // ... } }
適合コード (readResolve()、writeReplace())
以下の適合コードでは、2つのメソッドを protected 宣言する一方でキーワード static を取り除いており、サブクラスはこれらのメソッドを継承することができる。
class Extendable implements Serializable { protected Object readResolve() { // ... } protected Object writeReplace() { // ... } }
リスク評価
シリアライズ関連のメソッドを不適切なシグネチャで実装すると、予期せぬプログラムの動作を引き起こす可能性がある。readObject() メソッドと writeObject() メソッドを適切にアクセス制限しないと、信頼できないコードによる呼出しを可能にしてしまう。readResolve() メソッドと writeReplace() メソッドを static あるいは private 宣言すると、サブクラスはこれらのメソッドを無視することになる。また、誤って public 宣言すると、信頼できないコードからの呼出しを可能にしてしまう。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SER01-J | 高 | 高 | 低 | P27 | L1 |
関連ガイドライン
MITRE CWE | CWE-502. Deserialization of untrusted data |
参考文献
[API 2006] | Serializable |
[Sun 2006] | Serialization Specification (Java オブジェクト直列化仕様) |
[Ware 2008] |
翻訳元
これは以下のページを翻訳したものです。
SER01-J. Do not deviate from the proper signatures of serialization methods (revision 45)