SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Javaのデシリアライズに潜むセキュリティ問題

Javaのデシリアライズに関する問題への対策

Javaのデシリアライズに潜むセキュリティ問題(後編)

1. 意図しないクラスのデシリアライズは行わない

 セキュアコーディングスタンダードSER12-Jに該当する対策である。アプリケーションでデシリアライズすることを意図しているクラスのホワイトリストを定義しておき、デシリアライズしようとしているクラスがホワイトリストに含まれるか検証を行う。具体的には、ObjectInputStreamクラスのresolveClassメソッドをオーバーライドして、デシリアライズしようとしているクラスがホワイトリストに含まれる場合のみデシリアライズ処理を継続するようにする。第2回で解説したとおり、resolveClassメソッドはデシリアライズ対象のクラスのreadObjectメソッドの前に実行されるため、意図しないクラスのオブジェクトがデシリアライズされるのを防ぐことができる。

図1 resolveClass()によるクラスの検証
図1 resolveClass()によるクラスの検証

 SER12-Jに記載されている適合コードでは、ObjectStreamClass.getName()を使用し、シリアライズデータに含まれるクラス名が意図したものか検証を行っている(resolveClassメソッドではオブジェクト情報は読みだされないため、クラス名のみの検証で問題ない)。

SER12-Jの適合コードより抜粋
class WhitelistedObjectInputStream extends ObjectInputStream {
    // ...
    @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);
    }
}

 以下、適合コードに基づいて実装したアプリケーションが、第1回で解説した細工したクラス(AnnotationInvocationHandler)のオブジェクトをデシリアライズした結果だ。resolveClassメソッドで例外がスローされ、意図しないクラスがデシリアライズされるのを防ぐことができる。

resolveClass()でデシリアライズクラスの検証を行った場合の実行結果例
java.io.InvalidClassException: Unexpected serialized class; sun.reflect.annotation.AnnotationInvocationHandler
at WhitelistedObjectInputStream.resolveClass(DeserializeExample.java:15)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
図2 resolveClass()で検証を行うことにより意図しないクラスのデシリアライズを防ぐ
図2 resolveClass()で検証を行うことにより意図しないクラスのデシリアライズを防ぐ

 なお、resolveClassメソッドをオーバーライドしない場合、Java標準APIによってserialVersionUIDの比較が行われるが、公開されているクラスは互換性維持などの目的でserialVersionUIDが一定であるケースが多いため、意図したクラス名かどうかを検証することが重要である。

2. 意図しないデータのデシリアライズは行わない

 対策1は、デシリアライズする側の処理において、意図しないクラスのデシリアライズを防ぐアプローチであった。本対策では、デシリアライズされる側のクラスにおいて、以下の観点で意図しないデシリアライズが行われていないかを確認する。

意図しないデシリアライズ処理の呼び出しが行われていないかの検証:

 →セキュアコーディングスタンダードSER13-Jに該当する対策。

デシリアライズしたオブジェクトの状態の検証:

 →セキュアコーディングスタンダードSER07-Jに該当する対策。

 SER13-Jは、デシリアライズ対象クラスのreadObjectメソッド内で、アプリケーションが意図しないデシリアライズ処理の呼び出しが行われていないかの検証を行うアプローチだ。サンプルコードでは、readObjectメソッド内でファイルオープンを行っており、意図しない呼び出し(アプリケーションが想定していない処理でデシリアライズされるなど)を受けた場合、多数ファイルがオープンされるなどシステムリソースが枯渇するリスクがある。この例ではDoS攻撃相当の被害が想定されるが、リフレクションの使用や、外部プロセスの起動を行っている場合などに悪意あるコード実行が行われる可能性も考えられる。

 このようなリスクがある処理はデシリアライズ完了後に行うのが望ましいが、デシリアライズ時に行う必要がある場合、対策1を実施した上で、デシリアライズ対象クラスのreadObjectメソッド内で、ホワイトリストによって許可されたデシリアライズ処理かどうかを検証することをお勧めする。

 SER13-Jの適合コードでは、シリアライズ対象クラスのreadObjectメソッド内の処理で、ObjectInputStreamがホワイトリストを保持しているか検証を行っている。

SER13-Jの適合コードより抜粋
class OpenedFile implements Serializable {
    // ...
    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException {
        boolean hasWhitelist = false;
        try {
            in.getClass().getDeclaredField("whitelist");
            hasWhitelist = true;
        } catch (ReflectiveOperationException e) {}
        if (!hasWhitelist) {
            throw new SecurityException("Deserialization without a whitelist is disallowed for class " +
                this.getClass().getName() + ".");
        }
        // ...
    }
}

 適合コードに基づいて実装したクラスを、ホワイトリストを持たないJava標準のObjectInputStreamを使ってデシリアライズすると、例外がスローされる。

 なお、SER13-Jには「SER12-JとSER13-Jのどちらかが実現されていればよい」との記載があるが、意図しないクラスのオブジェクトが生成される問題に対してはSER12-Jが必須のため、1の対策を併せて実施してほしい。

readObject()によるホワイトリストの検証を行った場合の実行結果例
Exception in thread "main" java.lang.SecurityException: Deserialization without a whitelist is disallowed for class OpenedFile.
    at OpenedFile.readObject(OpenedFile.java:40)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1896)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)

 また、アプリケーションが意図したデシリアライズ処理の呼び出しであっても、シリアライズデータが細工されるリスクも考慮する必要がある。

 SER07-Jは、デシリアライズしたオブジェクトに対して、オブジェクトの状態があらかじめ定められた制約を満たしていることを検証することにより、意図しないオブジェクトがデシリアライズされるのを防ぐアプローチだ。デシリアライズ時にはコンストラクタが呼ばれないため、readObjectメソッドなどデシリアライズ時に実行されるメソッド内で検証を行う必要があることに注意してほしい。

 SER07-Jの違反コード(Lotteryクラス)では、コンストラクタでフィールドの値に制約を設けているが、readObjectメソッドで検証を行っていないため、攻撃者がシリアライズデータのフィールド値を書き換えるなどにより、制約外のフィールド値を持ったオブジェクトをデシリアライズさせることが可能となる。適合コードでは、readObjectメソッドでフィールド値の検証を行っており、意図しないデータを持ったオブジェクトが生成されるのを防ぐことができる。

SER07-Jの適合コードより抜粋
public final class Lottery implements Serializable { 
    // ...
    private synchronized void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField fields = s.readFields();
        int ticket = fields.get("ticket", 0);
        if (ticket > 20000 || ticket <= 0) {
            throw new InvalidObjectException("Not in range!");
        }
        // ...
    }
}

次のページ
おわりに

この記事は参考になりましたか?

Javaのデシリアライズに潜むセキュリティ問題連載記事一覧

もっと読む

この記事の著者

藤本 万里子(JPCERT コーディネーションセンター)(フジモト マリコ)

一般社団法人JPCERTコーディネーションセンター 早期警戒グループ 情報セキュリティアナリスト。前職では、国内企業においてソフトウエアの開発や内部統制のためのシステム開発等を担当。 2015年4月、JPCERTコーディネーションセンター早期警戒グループに情報セキュリティアナリストとして着任。主に国...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/9176 2016/01/26 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー