デシリアライズにおける問題
外部から受け取ったシリアライズデータを検証なしにデシリアライズすることは、以下のようなリスクを伴う。
1. 意図しないクラスのオブジェクトが生成される可能性がある
クラスパス上に存在する任意のクラスのオブジェクトを、攻撃者が指定するままに生成してしまう可能性がある。クラスパス上には、アプリケーション独自のクラスやJava標準ライブラリの他、ミドルウエアやフレームワークなど多数のライブラリが存在することが多い。それらのクラスはシリアライズ可能なケースが多く、デシリアライズ処理を悪用した攻撃が行われる可能性も考えられる。前回解説したInvokerTransformerやLazyMap、AnnotationInvocationHandlerクラスの悪用などがその例だ。
この問題を説明するための下記シンプルなサンプルコードを見てほしい。Class2はアプリケーションでデシリアライズすることを想定しているクラス、Class1はクラスパス上には存在するが、デシリアライズすることを想定していないクラスと仮定する。攻撃者がシリアライズデータとしてClass1をアプリケーションに渡した場合、デシリアライズ後のキャスト処理でエラーが発生するが、エラーになる前にClass1のreadObject()が実行され、OSコマンドが実行される。
class Class1 implements Serializable {
private String command = null;
Class1() {
command = "cmd /c mkdir class1";
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
try {
// 外部コマンド実行
Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Class2 implements Serializable {
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
}
}
public class Sample2 {
public static void main(String[] args) throws ClassNotFoundException, IOException {
ObjectInputStream ois = deserialize();
// 想定するクラスは Class2
Class2 class2 = (Class2) ois.readObject();
}
}
2. デシリアライズ処理の意図しない呼び出しやシリアライズデータの細工が行われる可能性がある
アプリケーションでデシリアライズすることを想定しているクラスであったとしても、検証なしにデシリアライズを行うことはリスクを伴う。何らかの方法(通信の盗聴や逆コンパイルなど)で攻撃者がシリアライズ対象クラスの情報を入手した場合、readObjectメソッド内などで攻撃に使えそうな処理(Dos攻撃や任意のコマンド実行につながるような処理)が実装されていれば、処理を悪用する方法を考えるかもしれない。例えば、readObject()で外部コマンドを起動するような実装になっている場合はデシリアライズ処理の意図しない呼び出しを受けた際に危険なことがお分かりいただけるだろう。
また、攻撃者によりシリアライズデータを細工された場合、デシリアライズしたオブジェクトのフィールドに意図しないデータを設定されるなどの危険性がある。意図しないデータが設定された場合、アプリケーションの挙動に影響を与えたり、クラッシュしたりするリスクも考えられる。
下記サンプルコード3は、サンプルコード2と異なり、Class1はアプリケーションでデシリアライズを想定しているクラスと仮定する。
public class Sample2 {
public static void main(String[] args) throws ClassNotFoundException, IOException {
ObjectInputStream ois = deserialize();
// 想定するクラスは Class1
Class1 class1= (Class1) ois.readObject();
}
}
Class1は実行するコマンドをフィールドに保持しているが、攻撃者がシリアライズデータに保存されているフィールド値を細工した場合、攻撃者が指定したコマンドが実行されてしまう危険性がある。
前述したデシリアライズの問題を悪用し、攻撃を行うための主な条件は以下だ。
- A)シリアライズデータを送受信しているサービスにアクセスできること。
- B)シリアライズデータを送受信している通信パケットのペイロードが特定できること。
- C)シリアライズデータのクラス情報を入手できること。
| 攻撃を行うための条件 | 攻撃への対策 | |||
|---|---|---|---|---|
| A)サービスへのアクセス | B)パケットのペイロード特定 | C)クラス情報入手 | ||
| 1. 悪意あるクラスのオブジェクト生成 | 要 | 要 | 不要 | デシリアライズ前の検証 |
| 2. 悪意あるデシリアライズ処理呼び出し、シリアライズデータの細工 | 要 | 要 | 要 | デシリアライズ時の検証 |
外部から受け取ったシリアライズデータをデシリアライズしている場合は、A)、B)、C)の条件に該当し、攻撃が可能な状態になっていないか確認することをお勧めする。
なお、デシリアライズ処理を行っているかは、以下のデシリアライズに関連するAPIを使っているかを検索することで確認できる。
- readObject():Serializableを実装するクラスで独自に定義でき、デシリアライズ時に実行される。
- readResolve():Serializableを実装するクラスで独自に定義でき、readObject()の実行後に呼び出され、デシリアライズするオブジェクトを置換できる。
- readExternal():Externalizableを実装するクラスで定義され、デシリアライズ時に実行される。
本記事で解説したデシリアライズに関する問題を踏まえて、次回はその対策について解説する。
