Javaでシリアライズを扱う機会があったので、Effective Java 第3版 12章を読みました。
まとめを記載します。
##シリアライズ/ディシリアライズ(Serialize/Deserialize)とは
オブジェクトをバイトストリームとして符号化する、また反対にオブジェクトを再構築する手法。
Javaのフレームワークでは、オブジェクトにSerializableインターフェースを実装することによって容易に実装できる。
例えば、以下の様にUserをシリアライズ化した場合、インスタンス化されたUserオブジェクトはObjectInputStream#readObject()
を呼び出せば、容易にディシリアライズされる。
public class User implements Serializable {
private static final long serialVersionUID = 199035831519635924L;
private String name;
private String cardNumber;
public User(String name, String cardNumber) {
this.name = name;
this.cardNumber = cardNumber;
}
}
シリアライズの危険性
シリアライズは便利だが、上記の例では、どこからでもUserインスタンスはディシリアライズできるということにもなる。
こうしたコードは、リモートコード実行(RCE)やDoS攻撃などの攻撃の弱点になってしまう。
さらに、RMI,JMX,JMSといった、よく使われているJavaのサブシステム、およびサードパーティーのライブラリにもシリアライズは使用されているため、攻撃対象領域は広くなってしまう傾向にある。
(こうしたライブラリや製品など様々な場所で使われているJavaのシリアライズ可能な型を持つメソッドをガジェットと呼ぶ。こうしたガジェットを組み合わせることで(ガジェットチェーン)、攻撃者はネイティブコードを呼び出せてしまう。)
また、ガジェットなしでも、シリアライズ技術を利用し、DoS攻撃が仕掛けられるで「ディシリアライズ爆弾」なども作ることができてしまう。
これは、インスタンスのディシリアライズをする場合、そのフィールドや要素のハッシュコードを計算する必要があることを悪用し、例えば深い入れ子構造になっているHashSetインスタンスなどを作成してディシリアライズさせることで、莫大な計算時間を掛けさせる攻撃手法である。
ディシリアライズ攻撃に対する対応策
最善策は、システム内で一切のディシリアライズを行わないこと。
世の中には、クロスプラットフォーム構造化データ表現(≠シリアライズシステム)があり、これらを利用することで、シリアライズ以外の方法で、オブジェクトとバイトシーケンス間の変換を安全に行うことができる。
こうした表現は、基本データ型と配列のみをサポートし、Javaのシリアライズよりもはるかに単純である。
主なものに、JSONとprotobufがあり、これらは言語中立である。
・JSON→Webでよく使われる軽量のデータ交換フォーマット。JavaScript用に開発され、テキストで人間が読める形式でデータ表現ができる。
・protobuf→Googleにより、C++様に開発された。バイナリ形式で、より効率的である。
次に、既存のJavaシステムを開発している場合などで、シリアライズの利用が避けられない場合の策としては、信頼していないバイトストリームのディシリアライズは、一切行わないこと。
(特に信頼していない元からのRMI要求を受け付けないこと)
→Javaのセキュアコーディングガイドラインにて、「信頼されないデータのディシリアライズは、本質的に危険であり、避けるべきである」という記載があり、ガイドライン全体で唯一大文字・太字・斜体・赤字で強調記載されている。
Secure Coding Guidelines for Java SE
シリアライズが避けられず、データの安全性を確保できない場合は、Java9で導入されたObjectInputFilter
を使用することも必要。
これにより、データストリームに適用されるフィルタ処理を、ディシリアライズ前に実行できる。
なお、フィルタ方法には以下の二種類がある。
ブラックリスト・・・デフォルトでクラスを受け入れ、潜在的に危険なクラスを拒否する方法
ホワイトリスト・・・デフォルトでクラスを拒否し、安全なクラスを受け入れるする方法
ブラックリストは、新たな脅威に対応できないため、ホワイトリストを選ぶこと
(Serial Whitelist Application Trainerとよばれるツールを使うと、ホワイトリストを自動準備し、過剰なメモリ使用や、深い構造のオブジェクトを防いでくれる。ただし、防ぐことができないディシリアライズ爆弾などの攻撃もある。)