信頼できないデータから復元処理(deserialize)を行うと、クラスパス上に存在する任意のクラスのオブジェクトを、攻撃者が指定するままに生成してしまう可能性がある。このようにオブジェクトが生成されるとき、さらに追加のコードを実行させることが可能なクラスもある。詳しくは「SER13-J. デシリアライズするデータは悪質なものという前提で処理する」を参照。クラスの設計によっては、攻撃者が引数を与えて Runtime.exec()
などのような任意のメソッドを呼び出すことが可能になることもある。そのような危険性を考慮すると、信頼できないデータをデシリアライズする場合には、信頼できるクラスを特定するホワイトリストを用意して、データに含まれるクラスが信頼できるもののみであることを確認すべきである。これは、java.io.ObjectInputStream
クラスの resolveClass()
メソッドをオーバーライドすることで実装できる。あるいは、任意のメソッド呼出しを阻止できるように、jva.lang.SecurityManager
で権限を下げた状態でデシリアライズ処理を行う方法も考えられる。
違反コード
この違反コード例では、どんなクラスが生成されるかを確認せずにデシリアライズ処理を行っている。デシリアライズされるオブジェクトのクラスが持っている readObject()
メソッドが適切に実装されていない場合、任意のコードを実行させる攻撃に悪用される可能性がある。
import java.io.*;
class DeserializeExample {
public static Object deserialize(byte[] buffer)
throws IOException, ClassNotFoundException {
Object ret = null;
try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) {
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
ret = ois.readObject();
}
}
return ret;
}
}
適合コード
この適合コードでは、http://www.ibm.com/developerworks/library/se-lookahead/ で解説されている手法にもとづき、デシリアライズするオブジェクトのクラスを、readObject()
メソッドを実行する前に確認する。デシリアライズするオブジェクト(とそこから参照されているすべてのオブジェクト)について、GoodClass1
クラスあるいは GoodClass2
クラスのオブジェクトであることを確認し、もし違っていた場合には例外 InvalidClassException
をスローする。次に示す WhitelistedObjectInputStream
クラスは、「SER13-J. デシリアライズするデータは悪質なものという前提で処理する」の適合コードの実装方法と対になるものである。
import java.io.*;
import java.util.*;
class WhitelistedObjectInputStream extends ObjectInputStream {
public Set whitelist;
public WhitelistedObjectInputStream(InputStream inputStream, Set wl)
throws IOException {
super(inputStream);
whitelist = wl;
}
@Override
protected Class<?> resolveClass(ObjectStreamClass cls)
throws IOException, ClassNotFoundException {
if (!whitelist.contains(cls.getName())) {
throw new InvalidClassException("Unexpected serialized class",
cls.getName());
}
return super.resolveClass(cls);
}
}
class DeserializeExample {
private static Object deserialize(byte[] buffer)
throws IOException, ClassNotFoundException {
Object ret = null;
Set whitelist = new HashSet<String>(Arrays.asList(new String[]{"GoodClass1","GoodClass2"}));
try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) {
try (WhitelistedObjectInputStream ois = new WhitelistedObjectInputStream(bais, whitelist)) {
ret = ois.readObject();
}
}
return ret;
}
}
ここに挙げた適合コードは「OBJ09-J. クラス名を比較するのではなくクラスを比較する」に違反しているように見えるかもしれない。しかし、OBJ09-J で解決しようとしている問題は、異なるクラスローダによってロードされたかもしれない複数のオブジェクトのクラス比較を行う場合のみに当てはまるものである。そのような状況では、オブジェクト x について x.getClass()==Class.forName(x.getClass().getName())
が必ず成り立つと考えることはできないのであった。ここに示した適合コードのクラス名比較は WhitelistedObjectInputStream.resolveClass
で行っており、返り値となるクラス ret
に対して ret == Class.forName(ret.getName())
を満たすかどうかの確認を追加することもできるが、この条件式は常に成り立ってしまうので追加することは無意味である。
(なお、ObjectInputStream.resolveClass()
では、シリアライズデータが持っている serialVersionUID
と返り値となる Class
オブジェクトの serialVersionUID
との比較を行っている。この2つの値が異なっていれば、例外をスローすることになる。)
例外
SER12-EX0: シリアライズデータを受け取る入力源を信頼する、という前提について明確にドキュメントされている場合には、シリアライズデータの検証を行わなくてもよい。例えばライブラリのコード検査において、ライブラリ中の関数のドキュメントに、引数として渡すデータやデータの入力源が信頼できるものであることを確認してから呼び出すこと、と明記されている場合があるかもしれない。
リスク評価
このルールに違反しているコードを攻撃できるかどうかは、JVM のクラスパス上にどのようなクラスが存在しているかに依存する。(これは実行環境の問題であって、コード自体の問題ではない。) 最悪の場合、任意のコードを実行させられる可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
SER12-J |
高 |
高 | 高 | P9 | L2 |
自動検出
ツール
|
バージョン
|
チェッカー
|
説明
|
---|---|---|---|
このルールに違反しているかどうかを確認する攻撃コードを作成する際の参考になる |
resolveClass()
をオーバーライドしてホワイトリストによるチェックを実装しているかどうかを、静的解析ツールで確認することは可能だろう。
関連ガイドライン
CWE-502, Deserialization of Untrusted Data |
参考文献
[API 2014] |
|
|
|
|
翻訳元
これは以下のページを翻訳したものです。
SER12-J. Prevent deserialization of untrusted classes (revision 27)