改善されたセキュリティとシリアル化のための新しいJava


2020年12月に、私は、Javaがカスタムシリアル化実装を持っている問題について、記事242479152に書きました.いくつかの実装が重要であることを知っているJavaの中でシリアライズフレームワークはとても深く埋め込まれています.不安定な
あなたのクラスパスクラスからガジェットチェーンが作成されるならば、逆シリアル化は任意のコード実行につながることができます.
最近、Java 17 --新しいLTS版--リリースされました.しかし、どのように新しい機能がこの問題に影響を与えますか.
ブログ記事では、3つの主要なJava 17機能を見ます.
  • の記録
  • Javaフライトレコーダー(JFR)の改善

  • Serialization and deserialization in Java: explaining the Java deserialize
    vulnerability
    (Java強化提案)コンテキスト固有の逆シリアル化フィルタ.
  • JEP 415
    1 .レコード
    は、プレビュー機能としてJava 14で導入されて、Java 16で完全にリリースされた特徴になりました.しかし、多くの開発者は、LTSのバージョンのみにアップグレードすることを好むので、それは、現在のJava 17が完全にリリースされているシリアル化のコンテキストでレコードを議論する意味があります.
    レコードを逆シリアル化するときに通常のPOJOとは異なり、コンストラクタがオブジェクトを再利用するために使用されます.通常のJavaオブジェクトでは、これはそうではなく、フレームワークはリフレクションに大きく依存します.つまり、コンストラクタによってトリガされたロジックは、通常の
    Javaオブジェクト.詳細はRecords .記録のために、逆シリアル化するとき、オブジェクトを再現するとき、関与する魔法がありません.何らかの理由で、検証ロジックをレコードのコンストラクタに入れたなら、我々は知っています
    この意志論理を適用する.
    それにもかかわらず、私たちはすべてのロジックを記録する必要がある場合は議論することができます.そうすることによって、逆シリアル化ガジェットチェーンで役割を果たすことができるガジェットを作成できます.さらに重要なことは、我々はまだreadObject()関数を使用して
    逆シリアル化.これは、レコードに関係なく、我々はまだ正常なPOJOでガジェットチェーンに脆弱であることを意味します.
    Read my previous blog post
    2 . Javaでの逆シリアル化フィルタ
    Javaでの逆シリアル化脆弱性に対処するために、シリアル化フィルタを設定することが可能です.これはJava 569で の実装で導入されました.あなたは、配列のサイズ、グラフの深さ、合計参照とストリームサイズに制限を配置することができます.また、ブロックを作成することができますし、パターンに基づいてリストを許可するクラスを制限する
    逆シリアル化される.
    このようなフィルタをグローバルJVMフィルタとして設定することもできます.グローバルフィルターでは、JVM引数を設定するか、コードで設定できます.下記のmypackageからすべてのクラスを許可し、他のすべてをブロックするフィルタを作成しました.
    JVM引数
    -Djdk.serialFilter=nl.brianvermeer.example.*;!*
    
    コード
    ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("nl.brianvermeer.example.*;!*");
    ObjectInputFilter.Config.setSerialFilter(filter);
    
    また、以下のような特定のストリームのフィルタを設定することもできます.
    ObjectInputStream in = new ObjectInputStream(fileIn);
    ObjectInputFilter filesOnlyFilter = ObjectInputFilter.Config.createFilter("nl.brianvermeer.example2.Object;!*");
    in.setObjectInputFilter(filesOnlyFilter);
    
    Java 17では、特定のストリームにフィルタを設定すると、グローバルフィルターがそのストリームに対してオーバーライドされます.グローバルフィルタをストリーム固有のフィルタと全く結合しません.これは非常に柔軟な作業方法ではありません.
    さらに、あなたのグローバルフィルタがあなたのためのライブラリを使用していない場合には問題が発生します.
    JEP 290
    Java 17でのコンテキスト固有の逆シリアル化フィルタ
    Java 17はJEP 415の実装により を拡張した.
    あなたが今できる最も重要なことの一つはSerialFilterFactoryへのObjectInputFilter.Config.この工場はBinaryOperatorである必要があります、そして、特定のフィルタが特定のストリームに加えられるとき、何をするべきかについて説明します.
    以下の例では、既存のフィルタを新しいフィルタでマージするために、デフォルトのマージメソッドを使用するConfigに非常に基本的なファクトリを設定します.このツールを使えば、ifとfilterをマージするかどうかを決めることができます.この
    さて、前に議論した問題を解決してください.
    ObjectInputFilter.Config.setSerialFilterFactory((f1, f2) -> ObjectInputFilter.merge(f2,f1));
    
    フィルタ工場の横には、Java 17も簡単にフィルタの作成のためのいくつかの便利な方法を提供します.allowFilter()rejectFilter()ObjectInputFilterのような機能は、私の意見では、より明確で可読なフィルタの作成方法です.
    下記のJava 17コードの例では、これらの新機能を使用しています.この例のSideIrializeメソッドでは、ガジェットクラスを特に拒否します.
    ガジェットクラスとTwoValueレコードは、同じパッケージの一部です.ガジェットは現在拒否されます、そして、このパッケージのすべての他のクラスはフィルタによって許されます.
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    var filename = "file.ser";
    var value = new TwoValue("one", "two");
    //var value = new Gadget(new Command("ls -l")); //This will not be deserialized
    
    
    var filter1 = ObjectInputFilter.allowFilter(cl -> cl.getPackageName().contentEquals("nl.brianvermeer.example.serialize.records"), ObjectInputFilter.Status.REJECTED);
    ObjectInputFilter.Config.setSerialFilter(filter1);
    ObjectInputFilter.Config.setSerialFilterFactory((f1, f2) -> ObjectInputFilter.merge(f2,f1));
    
    serialize(value, filename);
    deserialize(filename);
    }
    
    public static void serialize(Object value, String filename) throws IOException {
    System.out.println("---serialize");
    FileOutputStream fileOut = new FileOutputStream(filename);
    ObjectOutputStream out = new ObjectOutputStream(fileOut);
    out.writeObject(value);
    out.close();
    fileOut.close();
    }
    
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
    System.out.println("---deserialize");
    FileInputStream fileIn = new FileInputStream(filename);
    ObjectInputStream in = new ObjectInputStream(fileIn);
    ObjectInputFilter intFilter = ObjectInputFilter.rejectFilter(cl -> cl.equals(Gadget.class), ObjectInputFilter.Status.UNDECIDED);
    in.setObjectInputFilter(intFilter);
    TwoValue tv = (TwoValue) in.readObject();
    System.out.println(tv);
    
    deserialization filter
    Javaフライトレコーダ逆シリアル化イベント
    Java 17のリリースも、 への良い版で、あなたを卑劣化悪用に対するあなたの十字軍のお手伝いをするようにします.この新しいJavaバージョンは、SideIrializationを監視する特定のイベントをサポートします.逆シリアル化イベントは、ストリーム内のすべてのオブジェクトのために作成され、すべての種類のような興味深いものを記録します:実際のタイプ、フィルタがあった場合は、オブジェクトのフィルタリング、オブジェクトの深さ、参照の数など.
    すべての情報は、あなたのアプリケーションのどこかに逆シリアル化があるかどうか、そして、あなたのプロセスが動いている間、実際に何が逆シリアル化されているかを見るのに便利です.ただし、このイベントを有効にする必要があります.デフォルトでキャプチャされませんので、このような設定を行う必要があります.
    ​​<?xml version="1.0" encoding="UTF-8"?>
    <configuration version="2.0" description="test">
       <event name="jdk.Deserialization">
          <setting name="enabled">true</setting>
          <setting name="stackTrace">false</setting>
       </event>
    </configuration>
    
    前のコード例を使用しますが、現在ガジェットクラスを逆シリアル化します.私は、Javaフライトレコーダーとカスタム設定で、私のIntelliJアイデアでコードを実行するとき、次の結果を得ます.
    Java Flight Recorder (JFR)
    このイベントは、逆シリアル化されたときに実際のオブジェクト型をキャプチャし、フィルターがこのオブジェクトを拒否することを確認します.JFRのためにこの特定のSideialisationイベントを使用するより詳細な方法を知りたい場合は、ブログ投稿を見てください

    アップグレードの安全性に対してより強力なツールのためのJava
    Java 17 LTSリリースでは、Javaアプリケーションで悪意のある逆シリアル化を防ぐために重要な改善をもたらします.したがって、あなたがこれらの習慣を採用したいならば、17のようなより新しいバージョンへのアップグレードは重要です.
    それでも、Javaのカスタムシリアライズに関しては、私の意見ではそれを避けるのがより良いです.ただし、これを実行するか、Javaのカスタム逆シリアル化を実行したライブラリに依存する場合は、保護する方法を知っている
    あなた自身.
    また、既知の逆シリアル化ガジェットチェーンを含むライブラリをインポートしたり、他の逆シリアル化セキュリティ問題を持っていないことに注意してください.