2 シリアライズ・フィルタリング

Javaシリアライズ・フィルタリング・メカニズムを使用すると、デシリアライズの脆弱性の回避に役立ちます。パターン・ベースのフィルタを定義したり、カスタム・フィルタを作成できます。

デシリアライズの脆弱性への対処

信頼できないデータを受け入れ、デシリアライズするアプリケーションは、攻撃に対して脆弱です。デシリアライズされる前に、シリアライズ・オブジェクトの着信ストリームに対するフィルタを作成できます。

デシリアライズの本質的な危険性

信頼できないデータ(特に不明、信頼できない、または認証されていないクライアントからのデータ)のデシリアライズは、本質的に危険なアクティビティです。これは、受信データ・ストリームのコンテンツによって、作成されるオブジェクト、フィールドの値、およびそれらの間の参照が決定されるためです。ストリームを慎重に構築することで、攻撃者は悪意を持って任意のクラスのコードを実行できます。

たとえば、オブジェクトの構築に、状態を変更したり他のアクションを呼び出したりする副次的な影響がある場合、これらのアクションによって、アプリケーション・オブジェクト、ライブラリ・オブジェクト、さらにはJavaランタイムの整合性が損なわれる可能性があります。ガジェット・クラスは、クラスの作成やメソッドの呼出しなどの任意の反映型アクションを実行できますが、悪意を持ってデシリアライズされて、サービス拒否やリモート・コード実行を引き起こす可能性があります。

デシリアライズ攻撃を無効にするための鍵は、任意のクラスのインスタンスがデシリアライズされないようにし、そのメソッドの直接または間接的な実行を防ぐことです。これは、シリアライズ・フィルタを介して行うことができます。

Javaのシリアライズおよびデシリアライズの概要

オブジェクトは、その状態がバイト・ストリームに変換されると、シリアライズされます。ストリームはファイル、データベースにまたはネットワーク上に送信されます。Javaオブジェクトは、そのクラスまたはそのスーパークラスのいずれかがjava.io.Serializableインタフェースまたはjava.io.Externalizableサブインタフェースのいずれかを実装している場合にシリアライズ可能になります。JDKでは、シリアライズは、Remote Method Invocation (RMI)、プロセス間通信(IPC)プロトコル(Spring HTTPインボーカなど)のカスタムRMI、Java Management Extensions (JMX)などの多くの領域で使用されます。

オブジェクトは、そのシリアライズされた形式がオブジェクトのコピーに変換されると、デシリアライズされます。この変換のセキュリティを確保することが重要です。デシリアライズではコードが実行されます。これは、デシリアライズされるクラスのreadObjectメソッドにカスタム・コードが含まれている可能性があるためです。

シリアライズ・フィルタ

シリアライズ・フィルタを使用すると、アプリケーションで許容されるクラスおよび拒否されるクラスを指定できます。フィルタを使用すると、オブジェクト・グラフが妥当な限度を超えないように、デシリアライズ中にオブジェクト・グラフのサイズと複雑度を制御することもできます。フィルタは、プロパティとして構成することも、プログラム的に実装することもできます。

ノート:

シリアライズ・フィルタは、デフォルトでは有効化も構成もされていません。システム・プロパティまたはセキュリティ・プロパティでフィルタを指定するか、ObjectInputFilterクラスで設定しないかぎり、シリアライズ・フィルタリングは行われません。
デシリアライズの脆弱性の回避に役立てるために、フィルタを作成する以外に、次のアクションを実行できます。
  • 信頼できないデータをデシリアライズしません。
  • SSLを使用してアプリケーションの間の接続を暗号化および認証します。
  • readObjectメソッドを使用してオブジェクトの不変をチェックするなど、割当ての前にフィールドの値を検証します。

ノート:

ビルトイン・フィルタは、RMIに用意されています。ただし、これらのビルトイン・フィルタは、開始点としてのみ使用してください。拒否リストの構成または許可リストの拡張、あるいはその両方を行って、RMIを使用するアプリケーションの保護を追加します。ビルトイン・フィルタを参照してください。

これらおよびその他の方針の詳細は、Java SEのセキュアなコーディングのガイドラインシリアライズとデシリアライズを参照してください。

Javaシリアライズ・フィルタ

Javaシリアライズ・フィルタリング・メカニズムでは、セキュリティおよび堅牢性の向上に役立てるために、シリアライズ・オブジェクトの着信ストリームを検査します。フィルタは、デシリアライズされる前のクラスの着信インスタンスを検証できます。

JEP 290およびJEP 415で説明されているように、Javaシリアライズ・フィルタリング・メカニズムの目標は、次のとおりです:
  • デシリアライズできるクラスを適切なコンテキストの一連のクラスに絞り込む方法を提供します。

  • デシリアライズ中にグラフのサイズと複雑度のメトリックを提供して、フィルタ通常のグラフの動作を検証します。

  • RMIエクスポートされたオブジェクトで、呼出しで予期されるクラスを検証できるようにします。

フィルタには、次の2種類があります:

  • JVM全体のフィルタ: JVMのすべてのデシリアライズに適用されます。ただし、JVM全体のフィルタの、特定のデシリアライズにおけるクラスの検証の有無と方法は、他のフィルタとの組合せ方法によって異なります。
  • ストリーム固有のフィルタ: ある特定のObjectInputStreamからのクラスを検証します。

次の方法でシリアライズ・フィルタを実装できます:

  • jdk.serialFilterプロパティによるJVM全体のパターン・ベースのフィルタの指定: パターン・ベースのフィルタは、特定のクラス、パッケージまたはモジュールの名前を受け入れるか拒否できる一連のパターンで構成されます。配列サイズ、グラフ深度、合計参照およびストリーム・サイズに制限を設定できます。一般的なユースケースは、Javaランタイムを損なう可能性があるとして識別されたクラスを拒否リストに追加することです。jdk.serialFilterプロパティでパターン・ベースのフィルタを指定する場合、アプリケーションを変更する必要はありません。

  • ObjectInputFilter APIによるカスタムまたはパターン・ベースのストリーム固有のフィルタの実装: ObjectInputFilter APIを使用してフィルタを実装し、それをObjectInputStreamで設定できます。Config.createFilter(String)メソッドを呼び出すことで、ObjectInputFilter APIを使用してパターン・ベースのフィルタを作成できます。

ノート:

シリアライズ・フィルタは、デフォルトでは有効化も構成もされていません。システム・プロパティまたはセキュリティ・プロパティでフィルタを指定するか、ObjectInputFilterクラスで設定しないかぎり、シリアライズ・フィルタリングは行われません。

ストリーム内の新規オブジェクトごとに、フィルタ・メカニズムではフィルタが1つのみ適用されます。ただし、このフィルタはフィルタの組合せである可能性があります。

ほとんどの場合、特にフィルタ・ファクトリを指定していない場合は、ストリーム固有のフィルタはJVM全体のフィルタが設定されているかどうかを確認する必要があります。JVM全体のフィルタが存在する場合は、ストリーム固有のフィルタによってそれが起動され、ステータスがUNDECIDEDでないかぎり、JVM全体のフィルタの結果が使用されます。

フィルタ・ファクトリ

フィルタ・ファクトリは、ストリームに使用するフィルタを選択したり、単一のフィルタに結合します。指定すると、デシリアライズ操作で、初めてクラスが検出されたときにそのクラスを許可するかどうかが決定されます。(同じクラスの後続のインスタンスはフィルタされません。)BinaryOperator<ObjectInputFilter>として実装され、ObjectInputFilter.Config.setSerialFilterFactoryメソッドまたはシステムまたはセキュリティ・プロパティで指定されます。「フィルタ・ファクトリの設定」を参照してください。ObjectInputStreamが作成されるたびに、フィルタ・ファクトリはObjectInputFilterを選択します。ただし、ストリームの特性およびフィルタ・ファクトリが以前に作成したフィルタに基づいて、異なるフィルタを作成できます。

許可リストと拒否リスト

許可リストと拒否リストは、パターン・ベースのフィルタまたはカスタム・フィルタを使用して実装できます。これらのリストを使用すると、アプリケーションを保護するためにプロアクティブな防御的アプローチを実行できます。

プロアクティブなアプローチでは、許可リストを使用して、認識および信頼されているクラス名のみを許可し、他のすべてのクラス名を拒否します。アプリケーションを開発する際にコードに許可リストを実装するか、後でパターン・ベースのフィルタを定義して実装できます。アプリケーションで小規模なクラスのセットのみが処理される場合、このアプローチは十分に機能します。許可されたクラス、パッケージまたはモジュールの名前を指定して、許可リストを実装できます。

防御的なアプローチでは、拒否リストを使用して、信頼できないクラスのインスタンスを拒否します。通常、拒否リストは、クラスが問題であることを示す攻撃の後に実装されます。jdk.serialFilterプロパティで指定されているパターン・ベースのフィルタに追加することにより、コードを変更しないで、拒否リストにクラス名を追加できます。

パターン・ベースのフィルタの作成

パターン・ベースのフィルタの定義には、アプリケーション・コードの変更は伴いません。プロパティ・ファイルでJVM全体のフィルタを追加するか、またはjavaコマンド行でアプリケーションに固有のフィルタを追加します。

パターン・ベースのフィルタは、一連のパターンです。各パターンは、ストリームまたはリソース制限内のクラス名と照合されます。クラス・ベースおよびリソース制限のパターンは、各パターンがセミコロン(;)で区切られた1つのフィルタ文字列に結合できます。

パターン・ベースのフィルタ構文

パターンで構成されたフィルタを作成する場合は、次のガイドラインを使用します。
  • パターンをセミコロンで区切ります。たとえば:

    pattern1.*;pattern2.*
  • 空白は重要であり、パターンの一部とみなされます。

  • 最初に制限を文字列にします。これらは、文字列内の場所に関係なく最初に評価されるため、最初に配置することによって順序付けを強調します。それ以外の場合、パターンは左から右に評価されます。

  • 先頭が!のパターンに一致するクラス名は拒否されます。!がないパターンに一致するクラス名は許可されます。次のフィルタは、pattern1.MyClassを拒否しますが、pattern2.MyClassを許可します:
    !pattern1.*;pattern2.*
  • 次の例に示すように、ワイルドカード記号(*)を使用してパターン内の未指定クラス名を表します:
    • すべてのクラス名が一致するようにするには、*を使用します

    • mypackage内のすべてのクラス名が一致するようにするには、mypackage.*を使用します

    • mypackage内のすべてのクラス名およびそのサブパッケージが一致するようにするには、mypackage.**を使用します

    • 先頭がtextのすべてのクラス名が一致するようにするには、text*を使用します

クラス名がどのフィルタにも一致しない場合、そのクラス名は許可されています。特定のクラス名のみを許可する場合は、フィルタが一致しないものをすべて拒否する必要があります。指定されたクラス名以外のすべてのクラス名を拒否するには、クラス・フィルタ内の最後のパターンとして!*を含めます。

パターンの構文の詳細は、JEP 290を参照してください。

パターン・ベースのフィルタの制限事項

パターン・ベースのフィルタには、次のような制限があります:

  • パターンでは、クラス名に基づいて配列のサイズを変えることができません。

  • パターンは、クラス名のスーパータイプまたはインタフェースに基づいたクラスと一致させることができません。

  • パターンには状態がなく、ストリーム内の以前にデシリアライズされたクラス・インスタンスに基づいて選択を行うことができません。

ノート:

パターン・ベースのフィルタは、デシリアライズされるクラスによって実装されているインタフェースをチェックしません。このフィルタは、ストリーム内で明示的に参照されるインタフェースに対して呼び出されます。デシリアライズされるオブジェクトのクラスによって実装されるインタフェースに対しては呼び出されません。

1つのアプリケーションのパターン・ベースのフィルタの定義

1つのアプリケーションのシステム・プロパティとしてパターン・ベースのフィルタを定義できます。システム・プロパティは、セキュリティ・プロパティの値よりも優先されます。

1つのアプリケーションおよびJavaの単一の呼出しにのみ適用されるフィルタを作成するには、コマンド行でjdk.serialFilterシステム・プロパティを定義します。

次の例では、個々のアプリケーションのリソース使用率を制限する方法を示します。

java -Djdk.serialFilter=maxarray=100000;maxdepth=20;maxrefs=500 com.example.test.Application

すべてのアプリケーションのパターン・ベースのフィルタの定義

セキュリティ・プロパティとして指定することで、$JAVA_HOMEからJavaランタイムで実行されるすべてのアプリケーションに影響を与えるパターン・ベースのJVM全体のフィルタを定義できます。(システム・プロパティは、セキュリティ・プロパティの値よりも優先されます。)ファイル$JAVA_HOME/conf/security/java.securityを編集し、パターン・ベースのフィルタをjdk.serialFilterセキュリティ・プロパティに追加します。

クラス・フィルタの定義

グローバルに適用されるパターン・ベースのクラス・フィルタを作成できます。たとえば、パターンはワイルドカードを含むクラス名またはパッケージである可能性があります。

次の例では、フィルタはパッケージ(!example.somepackage.SomeClass)の1つのクラス名を拒否し、他のすべてのクラス名を許可します:
jdk.serialFilter=!example.somepackage.SomeClass;example.somepackage.*;
前述のフィルタ例では、example.somepackage.*のクラス名だけでなく、他のすべてのクラス名を許可します。他すべてのクラス名を拒否するには、!*を追加します:
jdk.serialFilter=!example.somepackage.SomeClass;example.somepackage.*;!*

リソース制限フィルタの定義

リソース・フィルタは、グラフの複雑度とサイズを制限します。次のパラメータのフィルタを作成して、各アプリケーションのリソース使用率を制御できます。
  • 許容最大配列サイズ。たとえば: maxarray=100000;

  • グラフの最大の深さ。たとえば: maxdepth=20;

  • オブジェクト間のグラフ内の最大参照。たとえば: maxrefs=500;

  • ストリーム内のバイトの最大値。たとえば: maxbytes=500000;

カスタム・フィルタの作成

カスタム・フィルタは、アプリケーション・コードで指定するフィルタです。カスタム・フィルタは、個々のストリーム上またはプロセス内のすべてのストリームで設定されます。カスタム・フィルタは、パターン、メソッド、ラムダ式またはクラスとして実装できます。

シリアライズ・オブジェクトのストリームの読取り

1つのObjectInputStreamでカスタム・フィルタを設定できます。または、すべてのストリームに同じフィルタを適用するには、JVM全体のフィルタを設定します。ObjectInputStreamにフィルタが定義されていない場合は、JVM全体のフィルタが呼び出されます(ある場合)。

ストリームのデコード中に、次のアクションが発生します。

  • ストリーム内の新規オブジェクトごとに、およびオブジェクトがインスタンス化され、デシリアライズされる前に、初めてクラスが検出されたときにフィルタが呼び出されます。(同じクラスの後続のインスタンスはフィルタされません。)
  • ストリーム内のクラスごとに、フィルタが解決されたクラスとともに呼び出されます。これは、ストリーム内のスーパータイプおよびインタフェースごとに個別に呼び出されます。
  • フィルタは、作成されるオブジェクトのクラス、それらのクラスのスーパータイプ、そのインタフェースなど、ストリーム内で参照される各クラスを調査できます。
  • ストリームの配列ごとに、プリミティブ配列、文字配列またはオブジェクト配列のいずれであっても、配列クラスおよび配列長を指定したフィルタが呼び出されます。
  • ストリームから読み取られたオブジェクトへの参照ごとに、深さ、参照の数およびストリーム長をチェックできるフィルタが呼び出されます。深さは1から始まり、ネスト・オブジェクトごとに増加し、ネストされた呼出しが戻されるたびに減少します。
  • フィルタは、ストリームで具体的にエンコードされているプリミティブ・インスタンスまたはjava.lang.Stringインスタンスの場合は呼び出されません。
  • フィルタは、受入れ、拒否または未決定のステータスを戻します。
  • ロギングが有効になっている場合は、フィルタ・アクションがログに記録されます。

フィルタによってオブジェクトが拒否されない場合、オブジェクトは受け入れられます。

個別のストリームのカスタム・フィルタの設定

ストリームへの入力が信頼されず、フィルタにクラスの限定セットがあったり、制約が施行される場合、個別のObjectInputStreamにフィルタを設定できます。たとえば、ストリームに数値、文字列およびその他のアプリケーションに固有のタイプのみが確実に含まれるようにできます。

setObjectInputFilterメソッドを使用して、カスタム・フィルタが設定されます。オブジェクトがストリームから読み取られる前にカスタム・フィルタを設定する必要があります。

次の例では、setObjectInputFilter メソッドがdateTimeFilterメソッドを使用して呼び出されます。このフィルタは、java.timeパッケージのクラスのみを受け入れます。dateTimeFilterメソッドは、メソッドとしてのカスタム・フィルタの設定のコード・サンプルで定義されています。

    LocalDateTime readDateTime(InputStream is) throws IOException {
        try (ObjectInputStream ois = new ObjectInputStream(is)) {
            ois.setObjectInputFilter(FilterClass::dateTimeFilter);
            return (LocalDateTime) ois.readObject();
        } catch (ClassNotFoundException ex) {
            IOException ioe = new StreamCorruptedException("class missing");
            ioe.initCause(ex);
            throw ioe;
        }
    }

JVM全体のカスタム・フィルタの設定

特定のストリームでオーバーライドされないかぎり、ObjectInputStreamのすべての使用に適用されるJVM全体のフィルタを設定できます。アプリケーション全体で必要とされるすべてのタイプと条件を識別できる場合、フィルタはこれらを受け入れ、残りを拒否できます。通常、JVM全体のフィルタを使用して、特定のクラスまたはパッケージを拒否したり、配列サイズ、グラフ深度または合計グラフ・サイズを制限します。

JVM全体のフィルタは、ObjectInputFilter.Configクラスのメソッドを使用して1回設定されます。フィルタは、クラスのインスタンス、ラムダ式、メソッド参照またはパターンにすることができます。

ObjectInputFilter filter = ...
ObjectInputFilter.Config.setSerialFilter(filter);

次の例では、JVM全体のフィルタがラムダ式を使用して設定されます。

ObjectInputFilter.Config.setSerialFilter(
    info -> info.depth() > 10 ? Status.REJECTED : Status.UNDECIDED);

次の例では、JVM全体のフィルタがメソッド参照を使用して設定されます:

ObjectInputFilter.Config.setSerialFilter(FilterClass::dateTimeFilter);

パターンを使用したカスタム・フィルタの設定

単純なケースに役立つパターン・ベースのカスタム・フィルタは、ObjectInputFilter.Config.createFilterメソッドを使用して作成できます。システム・プロパティまたはセキュリティ・プロパティとしてパターン・ベースのフィルタを作成できます。メソッドまたはラムダ式としてパターン・ベースのフィルタを実装すると、柔軟性が向上します。

フィルタ・パターンはクラス、パッケージおよびモジュールの特定の名前を受け入れたり、拒否でき、配列サイズ、グラフ深度、合計参照およびストリーム・サイズに制限を設定できます。パターンは、クラスのスーパータイプまたはインタフェースの名前と一致させることができません。

次の例では、フィルタはexample.Fileクラスを許可し、example.Directoryを拒否します。
ObjectInputFilter filesOnlyFilter =
    ObjectInputFilter.Config.createFilter("example.File;!example.Directory");
この例では、example.Fileのみを許可します。他のすべてのクラス名は拒否されます。
ObjectInputFilter filesOnlyFilter =
    ObjectInputFilter.Config.createFilter("example.File;!*");

クラスとしてのカスタム・フィルタの設定

カスタム・フィルタは、java.io.ObjectInputFilterインタフェースを実装するクラス、ラムダ式またはメソッドとして実装できます。

通常、フィルタはステートレスで、入力パラメータに対してのみチェックを実行します。ただし、たとえば、ストリーム内のアーティファクトをカウントするcheckInputメソッドへの呼出し間の状態を維持するフィルタを実装できます。

次の例では、FilterNumberクラスはNumberクラスのインスタンスであるオブジェクトを許可し、その他すべてを拒否します。

    class FilterNumber implements ObjectInputFilter {
        public Status checkInput(FilterInfo filterInfo) {
            Class<?> clazz = filterInfo.serialClass();
            if (clazz != null) {
                return (Number.class.isAssignableFrom(clazz))
                    ? ObjectInputFilter.Status.ALLOWED
                    : ObjectInputFilter.Status.REJECTED;
            }
            return ObjectInputFilter.Status.UNDECIDED;
        }
    }

この例では次のようになります。

  • checkInputメソッドはObjectInputFilter.FilterInfoオブジェクトを受け入れます。オブジェクトのメソッドは、チェック対象のクラス、配列サイズを、現在の深さ、既存のオブジェクトへの参照の数およびこれまでに読み取られたストリーム・サイズへのアクセスを提供します。
  • serialClassがnullでない場合は、値がチェックされて、オブジェクトのクラスがNumberであるかどうかが確認されます。その場合は、受け入れられ、ObjectInputFilter.Status.ALLOWEDが返されます。それ以外の場合は、拒否され、ObjectInputFilter.Status.REJECTEDが返されます。
  • 引数のその他の組合せでは、ObjectInputFilter.Status.UNDECIDEDが返されます。デシリアライズは続行され、オブジェクトが受け入れられるか拒否されるまで、残りのフィルタが実行されます。他のフィルタがない場合、オブジェクトは受け入れられています。

メソッドとしてのカスタム・フィルタの設定

カスタム・フィルタもメソッドとして実装できます。メソッド参照はインライン・ラムダ式のかわりに使用されます。

次の例で定義されるdateTimeFilterメソッドは、個別のストリームのカスタム・フィルタの設定のコード・サンプルで使用されています。

    public class FilterClass {
        static ObjectInputFilter.Status dateTimeFilter(ObjectInputFilter.FilterInfo info) {
            Class<?> serialClass = info.serialClass();
            if (serialClass != null) {
                return serialClass.getPackageName().equals("java.time")
                        ? ObjectInputFilter.Status.ALLOWED
                        : ObjectInputFilter.Status.REJECTED;
            }
            return ObjectInputFilter.Status.UNDECIDED;
        }
    }

このカスタム・フィルタは、JDKのベース・モジュールにあるクラスのみを許可します:

        static ObjectInputFilter.Status baseFilter(ObjectInputFilter.FilterInfo info) {
            Class<?> serialClass = info.serialClass();
            if (serialClass != null) {
                return serialClass.getModule().getName().equals("java.base")
                        ? ObjectInputFilter.Status.ALLOWED
                        : ObjectInputFilter.Status.REJECTED;
            }
            return ObjectInputFilter.Status.UNDECIDED;
       }

ObjectInputFilterメソッドを使用したフィルタの作成

ObjectInputFilterインタフェースには、フィルタを迅速に作成できる次の静的メソッドが含まれています:

  • allowFilter(Predicate<Class<?>>, ObjectInputFilter.Status)
  • rejectFilter(Predicate<Class<?>>, ObjectInputFilter.Status)
  • rejectUndecidedClass(ObjectInputFilter)
  • merge(ObjectInputFilter, ObjectInputFilter)

allowFilterメソッドは、引数としてClassを取得するPredicateに基づいてフィルタを作成します。述語がtrueの場合、作成されたフィルタはObjectInputFilter.Status.ALLOWEDを返します。それ以外の場合は、allowFilterメソッドの2番目の引数の値を返します。次に、Integerクラスを受け入れるフィルタを作成します。他のすべてのクラスは未決定とみなされます:

ObjectInputFilter intFilter = ObjectInputFilter.allowFilter(
    cl -> cl.equals(Integer.class), ObjectInputFilter.Status.UNDECIDED);

rejectFilterメソッドは、allowFilterの逆です。Classを引数として取るPredicateに基づいてフィルタを作成します。述語がtrueの場合、作成されたフィルタはObjectInputFilter.Status.REJECTEDを返します。それ以外の場合は、rejectFilterメソッドの2番目の引数の値を返します。次に、アプリケーション・クラス・ローダーからロードされたクラスを拒否するフィルタを作成します:

ObjectInputFilter f = ObjectInputFilter.rejectFilter(cl ->
    cl.getClassLoader() == ClassLoader.getSystemClassLoader(), Status.UNDECIDED);

rejectUndecidedClassメソッドは、既存のフィルタが未決定クラスとみなすクラスを拒否することで、既存のフィルタに基づいて新しいフィルタを作成します。次に、intFilterに基づいてフィルタを作成します。Integerクラスを受け入れますが、他のすべての(未決定の)クラスを拒否します:

ObjectInputFilter rejectUndecidedFilter =
    ObjectInputFilter.rejectUndecidedClass(intFilter);

mergeメソッドは、2つのフィルタをマージして新しいフィルタを作成します。次に、フィルタintFilterfをマージします。Integerクラスを受け入れますが、アプリケーション・クラス・ローダーからロードされたすべてのクラスを拒否します:

ObjectInputFilter mergedFilter = ObjectInputFilter.merge(intFilter, f);

マージされたフィルタは、クラスをフィルタする際に次のステップに従います:

  1. いずれかのフィルタがStatus.REJECTEDを返す場合は、Status.REJECTEDを返します。
  2. いずれかのフィルタがStatus.ACCEPTEDを返す場合は、Status.ACCEPTEDを返します。
  3. Status.UNDECIDEDを返します(両方のフィルタはStatus.UNDECIDEDを返します)。

mergeメソッドは、フィルタ・ファクトリで役立ちます。ストリームでフィルタが設定されるたびに、そのフィルタを、mergeメソッドを使用してフィルタ・ファクトリが作成するフィルタに追加できます。例については、ObjectInputFilter APIのドキュメントを参照してください。

ノート:

JVM全体のフィルタを、フィルタ・ファクトリでリクエストされたストリーム固有のフィルタにマージすることをお薦めします。リクエストされたフィルタを返すだけの場合は、JVM全体のフィルタを事実上無効にします。これにより、セキュリティ・ギャップが発生します。

フィルタ・ファクトリの設定

フィルタ・ファクトリはBinaryOperatorで、これはストリームのフィルタを選択する2つのオペランドの関数です。フィルタ・ファクトリを設定するには、メソッドObjectInputFilter.Config.setSerialFilterFactoryを呼び出すか、システムまたはセキュリティ・プロパティで指定します。

ノート:

フィルタ・ファクトリは、メソッドsetSerialFilterFactory、システム・プロパティjdk.serialFilterFactoryまたはセキュリティ・プロパティjdk.serialFilterFactoryで1回のみ設定できます。

setSerialFilterFactoryを使用したフィルタ・ファクトリの設定

メソッドObjectInputFilter.Config.setSerialFilterFactoryを呼び出してフィルタ・ファクトリを設定すると、ObjectInputStreamが構築されたとき、およびストリーム固有のフィルタがObjectInputStreamに設定されたときに、フィルタ・ファクトリのメソッドBinaryOperator<ObjectInputFilter>.apply(ObjectInputFilter t, ObjectInputFilter u)が呼び出されます。パラメータtは現在のフィルタで、uはリクエストされたフィルタです。applyが最初に呼び出されたとき、tはnullになります。JVM全体のフィルタが設定されている場合、applyが最初に呼び出されたとき、uはJVM全体のフィルタになります。それ以外の場合、uはnullになります。applyメソッド(自分で実装する必要がある)は、ストリームに使用されるフィルタを返します。applyが再度呼び出された場合、パラメータtはこの返されたフィルタになります。メソッドObjectInputStream.setObjectInputFilter(ObjectInputFilter)を使用してフィルタを設定すると、パラメータuがこのフィルタになります。

次の例では、applyメソッドが呼び出されるたびにObjectInputFilterパラメータを出力する単純なフィルタ・ファクトリを実装し、これらのパラメータを1つの結合フィルタにマージしてから、このマージ済フィルタを返します。

public class SimpleFilterFactory {

    static class MySimpleFilterFactory implements BinaryOperator<ObjectInputFilter> {
        public ObjectInputFilter apply(
            ObjectInputFilter curr, ObjectInputFilter next) {
            System.out.println("Current filter: " + curr);
            System.out.println("Requested filter: " + next);
            return ObjectInputFilter.merge(next, curr);
        }
    }            
   
    private static byte[] createSimpleStream(Object obj) {
        ByteArrayOutputStream boas = new ByteArrayOutputStream();
        try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
            ois.writeObject(obj);
            return boas.toByteArray();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        throw new RuntimeException();        
    }
            
    public static void main(String[] args) throws IOException {
        
        // Set a filter factory
        
        MySimpleFilterFactory contextFilterFactory = new MySimpleFilterFactory();
        ObjectInputFilter.Config.setSerialFilterFactory(contextFilterFactory);          
        
        // Set a stream-specific filter
        
        ObjectInputFilter filter1 =
            ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*");
        ObjectInputFilter.Config.setSerialFilter(filter1);

        // Create another filter        
        
        ObjectInputFilter intFilter = ObjectInputFilter.allowFilter(
          cl -> cl.equals(Integer.class), ObjectInputFilter.Status.UNDECIDED);        
          
        // Create input stream
          
        byte[] intByteStream = createSimpleStream(42);
        InputStream is = new ByteArrayInputStream(intByteStream);
        ObjectInputStream ois = new ObjectInputStream(is);
        ois.setObjectInputFilter(intFilter);
        
        try {
            Object obj = ois.readObject();
            System.out.println("Read obj: " + obj);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

この例では、次のような出力が表示されます(わかりやすくするために改行が追加されています):

Current filter: null
Requested filter: example.*;java.base/*;!*
Current filter: example.*;java.base/*;!*
Requested filter:
    merge(
        predicate(
            SimpleFilterFactory$$Lambda$8/0x0000000800c00c60@76ed5528,
            ifTrue: ALLOWED, ifFalse: UNDECIDED),
        predicate(
            SimpleFilterFactory$$Lambda$9/0x0000000800c01800@2c7b84de,
            ifTrue: REJECTED, ifFalse: UNDECIDED))
Read obj: 42

applyメソッドは2回呼び出されます。ObjectInputStream oisが作成されたとき、およびメソッドsetObjectInputFilterが呼び出されたときです。

ノート:

  • フィルタをObjectInputStreamに設定できるのは1回のみです。それ以外の場合は、IllegalStateExceptionがスローされます。
  • 予期しないデシリアライズから保護するには、セキュリティ・エキスパートは、フィルタ・ファクトリがフィルタを選択して組み合せる方法を徹底的に確認します。

システムまたはセキュリティ・プロパティでのフィルタ・ファクトリの指定

コマンド行のjdk.serialFilterFactoryシステム・プロパティで指定することで、1つのアプリケーションおよびJavaの単一の呼出しにのみ適用されるフィルタ・ファクトリを設定できます:

java -Djdk.serialFilterFactory=FilterFactoryClassName YourApplication

jdk.serialFilterFactoryの値は、最初のデシリアライズ前に設定されるフィルタ・ファクトリのクラス名です。クラスはパブリックであり、アプリケーション・クラス・ローダー(メソッドjava.lang.ClassLoader.getSystemClassLoader()が返す)からアクセスできる必要があります。

セキュリティ・プロパティで指定することで、$JAVA_HOMEからJavaランタイムで実行されるすべてのアプリケーションに影響を与えるJVM全体のフィルタ・ファクトリを設定できます。システム・プロパティは、セキュリティ・プロパティの値よりも優先されます。ファイル$JAVA_HOME/conf/security/java.securityを編集し、jdk.serialFilterFactoryセキュリティ・プロパティでフィルタ・ファクトリのクラス名を指定します。

ビルトイン・フィルタ

Java Remote Method Invocation (RMI)レジストリ、RMI分散ガベージ・コレクタおよびJava Management Extensions (JMX)には、すべてJDKに含まれているフィルタが備えられています。RMIレジストリとRMI分散ガベージ・コレクタ用に独自のフィルタを指定して、保護を追加する必要があります。

RMIレジストリのフィルタ

ノート:

これらのビルトイン・フィルタは、開始点としてのみ使用します。sun.rmi.registry.registryFilterシステム・プロパティを編集して、拒否リストを構成するか、許可リストを拡張してRMIレジストリに保護を追加するか、またはその両方を行います。アプリケーション全体を保護するため、パターンをjdk.serialFilterグローバル・システム・プロパティに追加して、独自のカスタム・フィルタが設定されていない他のシリアライズ・ユーザーに対する保護を強化します。

RMIレジストリには、オブジェクトをレジストリ内でバインドできるビルトイン許可リスト・フィルタが含まれています。これには、java.rmi.Remotejava.lang.Numberjava.lang.reflect.Proxyjava.rmi.server.UnicastRefjava.rmi.server.UIDjava.rmi.server.RMIClientSocketFactoryおよびjava.rmi.server.RMIServerSocketFactoryクラスのインスタンスが含まれています。

ビルトイン・フィルタには、サイズ制限が含まれています。
 maxarray=1000000;maxdepth=20

パターンとともにsun.rmi.registry.registryFilterシステム・プロパティを使用して、フィルタを定義し、ビルトイン・フィルタを破棄します。定義したフィルタがフィルタに渡されるクラスを受け入れるか、クラスまたはサイズを拒否する場合は、ビルトイン・フィルタが呼び出されません。フィルタが何も受け入れたり、拒否しない場合は、ビルトイン・フィルタが呼び出されます。

RMI分散ガベージ・コレクタのフィルタ

ノート:

これらのビルトイン・フィルタは、開始点としてのみ使用します。sun.rmi.transport.dgcFilterシステム・プロパティを編集して、拒否リストを構成するか、許可リストを拡張して分散ガベージ・コレクタに保護を追加するか、またはその両方を行います。アプリケーション全体を保護するため、パターンをjdk.serialFilterグローバル・システム・プロパティに追加して、独自のカスタム・フィルタが設定されていない他のシリアライズ・ユーザーに対する保護を強化します。

RMI分散ガベージ・コレクタには、限定されたクラスのセットを受け入れるビルトイン許可リスト・フィルタが含まれています。これには、java.rmi.server.ObjIDjava.rmi.server.UIDjava.rmi.dgc.VMIDおよびjava.rmi.dgc.Leaseクラスのインスタンスが含まれています。

ビルトイン・フィルタには、サイズ制限が含まれています。
maxarray=1000000;maxdepth=20

パターンとともにsun.rmi.transport.dgcFilterシステム・プロパティを使用して、フィルタを定義し、ビルトイン・フィルタを破棄します。フィルタに渡されるクラスを受け入れるか、クラスまたはサイズを拒否する場合は、ビルトイン・フィルタが呼び出されません。差替えフィルタが何も受け入れたり、拒否しない場合は、ビルトイン・フィルタが呼び出されます。

JMXのフィルタ

ノート:

これらのビルトイン・フィルタは、開始点としてのみ使用します。com.sun.management.jmxremote.serial.filter.pattern管理プロパティを編集して、拒否リストを構成するか、許可リストを拡張してJMXに保護を追加するか、またはその両方を行います。アプリケーション全体を保護するため、パターンをjdk.serialFilterグローバル・システム・プロパティに追加して、独自のカスタム・フィルタが設定されていない他のシリアライズ・ユーザーに対する保護を強化します。

JMXには、RMIを介してサーバーにデシリアライズ・パラメータとして送信できるクラスのセットを制限する組込みフィルタがあります。このフィルタは、デフォルトでは無効になっています。フィルタを有効にするには、パターンを使用してcom.sun.management.jmxremote.serial.filter.pattern管理プロパティを定義します。

パターンには、RMIを介してサーバーにパラメータとして送信できる型、およびそれらが依存するすべての型、さらにjavax.management.ObjectName型およびjava.rmi.MarshalledObject型を含める必要があります。たとえば、許可されるクラスのセットをOpen MBean型とそれらが依存する型に制限するには、次の行をmanagement.propertiesファイルに追加します:

com.sun.management.jmxremote.serial.filter.pattern=java.lang.*;java.math.BigInteger;java.math.BigDecimal;java.util.*;javax.management.openmbean.*;javax.management.ObjectName;java.rmi.MarshalledObject;!* 

フィルタ・アクションのロギング

ロギングをオンにして、シリアライズ・フィルタへの呼出しの初期化、却下および受入れを記録できます。ログ出力を診断ツールとして使用して、デシリアライズの内容を表示し、許可リストおよび拒否リストを構成するときに設定を確認します。

ロギングを有効にすると、フィルタ・アクションがjava.io.serializationロガーに記録されます。

シリアライズ・フィルタ・ロギングを有効にするには、$JDK_HOME/conf/logging.propertiesファイルを編集します。

拒否された呼出しをログに記録するには、次を追加します

java.io.serialization.level = FINE

すべてのフィルタ結果をログに記録するには、次を追加します

java.io.serialization.level = FINEST