Javaシリアライズ・フィルタリング・メカニズムを使用すると、デシリアライズの脆弱性の回避に役立ちます。パターン・ベースのフィルタを定義したり、カスタム・フィルタを作成できます。
信頼できないデータを受け入れ、デシリアライズするアプリケーションは、攻撃に対して脆弱です。デシリアライズされる前に、シリアライズ・オブジェクトの着信ストリームに対するフィルタを作成できます。
オブジェクトは、その状態がバイト・ストリームに変換されると、シリアライズされます。ストリームはファイル、データベースにまたはネットワーク上に送信されます。Javaオブジェクトは、そのクラスまたはそのスーパークラスのいずれかがjava.io.Serializableインタフェースまたはjava.io.Externalizableサブインタフェースのいずれかを実装している場合にシリアライズ可能になります。JDKでは、シリアライズは、Remote Method Invocation (RMI)、プロセス間通信(IPC)プロトコル(Spring HTTPインボーカなど)のカスタムRMI、Java Management Extensions (JMX)、Java Messaging Service (JMS)などの多くの領域で使用されます。
オブジェクトは、そのシリアライズされた形式がオブジェクトのコピーに変換されると、デシリアライズされます。この変換のセキュリティを確保することが重要です。デシリアライズではコードが実行されます。これは、デシリアライズされるクラスのreadObjectメソッドにカスタム・コードが含まれている可能性があるためです。ガジェット・クラスと呼ばれる、シリアライズ可能クラスでは、クラスの作成やメソッドの呼出しなどの任意の反映型アクションを実行できます。アプリケーションがクラスをデシリアライズする場合は、サービスまたはリモート・コード実行が拒否される可能性があります。
フィルタを作成するとき、アプリケーションで許容されるクラスおよび拒否されるクラスを指定できます。オブジェクト・グラフが妥当な限度を超えないように、デシリアライズ中にオブジェクト・グラフのサイズと複雑度を制御できます。フィルタは、プロパティとして構成するか、またはプログラム的に実装できます。
信頼できないデータをデシリアライズしません。
SSLを使用してアプリケーションの間の接続を暗号化および認証します。
readObjectメソッドを使用してオブジェクトの不変をチェックするなど、割当ての前にフィールドの値を検証します。
注意:
ビルトイン・フィルタは、RMIに用意されています。ただし、これらのビルトイン・フィルタは、開始点としてのみ使用してください。ブラックリストを構成するか、ホワイトリストを拡張して、RMIを使用するアプリケーション用に保護を追加します。ビルトイン・フィルタを参照してください。これらおよびその他の方針の詳細は、Java SEのセキュアなコーディングのガイドラインのシリアライズとデシリアライズを参照してください。
Javaシリアライズ・フィルタリング・メカニズムでは、セキュリティおよび堅牢性の向上に役立てるために、シリアライズ・オブジェクトの着信ストリームを検査します。フィルタは、デシリアライズされる前の着信クラスを検証できます。
デシリアライズできるクラスを適切なコンテキストの一連のクラスに絞り込む方法を提供します。
デシリアライズ中にグラフのサイズと複雑度のメトリックを提供して、フィルタ通常のグラフの動作を検証します。
RMIエクスポートされたオブジェクトで、呼出しで予期されるクラスを検証できるようにします。
次の方法でシリアライズ・フィルタを実装できます。
パターン・ベースのフィルタの場合は、アプリケーションを変更する必要がありません。プロパティ、構成ファイルまたはコマンド行で定義された一連のパターンで構成されます。パターン・ベースのフィルタは、特定のクラス、パッケージまたはモジュールを受け入れたり、拒否できます。配列サイズ、グラフ深度、合計参照およびストリーム・サイズに制限を設定できます。一般的なユースケースは、Javaランタイムを損なう可能性があるとして識別されたクラスをブラックリスト対象にすることです。パターン・ベースのフィルタは、1つのアプリケーションまたはプロセス内のすべてのアプリケーションで定義されます。
カスタム・フィルタは、ObjectInputFilter APIを使用して実装されます。カスタム・フィルタは各ObjectInputStreamに独自のものにできるため、アプリケーションで、パターン・ベースのフィルタよりもきめ細かい制御を統合できます。カスタム・フィルタは、個々の入力ストリーム上またはプロセス内のすべてのストリームに対して設定します。
フィルタ・メカニズムは、ストリーム内の新規オブジェクトごとに呼び出されます。複数のアクティブなフィルタ(プロセス全体のフィルタ、アプリケーション・フィルタまたはストリームに固有のフィルタ)が存在する場合は、最も限定的なフィルタのみが呼び出されます。
ほとんどの場合、カスタム・フィルタでプロセス全体のフィルタが設定されているかどうかをチェックする必要があります。存在する場合、ステータスがUNDECIDED
の場合を除き、カスタム・フィルタはそのフィルタを呼び出して、プロセス全体のフィルタの結果を使用する必要があります。
シリアライズ・フィルタのサポートは、JDK 9以降と、8u121、7u131および6u141以降のJava CPUリリースに含まれています。
ホワイトリストとブラックリストは、パターン・ベースのフィルタまたはカスタム・フィルタを使用して実装できます。これらのリストを使用すると、アプリケーションを保護するためにプロアクティブな防御的アプローチを実行できます。
プロアクティブなアプローチではホワイトリストを使用して、認識および信頼されているクラスのみを受け入れます。アプリケーションを開発する際にコードにホワイトリストを実装するか、後でパターン・ベースのフィルタを定義してホワイトリストを実装できます。アプリケーションで小規模なクラスのセットのみが処理される場合、このアプローチは十分に機能します。許可されたクラス、パッケージまたはモジュールを指定してホワイトリストを実装できます。
防御的なアプローチではブラックリストを使用して、信頼できないクラスを拒否します。通常、ブラックリストは、クラスが問題であることを示す攻撃の後に実装されます。パターン・ベースのフィルタを定義することにより、コードを変更しないで、ブラックリストにクラスを追加できます。
パターン・ベースのフィルタの定義には、アプリケーション・コードの変更は伴いません。プロパティ・ファイルでプロセス全体のフィルタを追加するか、またはjava
コマンド行でアプリケーションに固有のフィルタを追加します。
パターン・ベースのフィルタは、一連のパターンです。各パターンは、ストリームまたはリソース制限内のクラス名と照合されます。クラス・ベースおよびリソース制限のパターンは、各パターンがセミコロン(;)で区切られた1つのフィルタ文字列に結合できます。
パターン・ベースのフィルタ構文
パターンをセミコロンで区切ります。次に例を示します。
pattern1.*;pattern2.*
空白は重要であり、パターンの一部とみなされます。
最初に制限を文字列にします。これらは、文字列内の場所に関係なく最初に評価されるため、最初に配置することによって順序付けを強調します。それ以外の場合、パターンは左から右に評価されます。
!
のパターンに一致するクラスは拒否されます。!
がないパターンに一致するクラスは受け入れられます。次のフィルタは、pattern1.MyClass
を拒否しますが、pattern2.MyClass
を受け入れます。 !pattern1.*;pattern2.*
*
)を使用してパターン内の未指定クラスを表します。 すべてのクラスが一致するようにするには、*
を使用します
mypackage
内のすべてのクラスが一致するようにするには、mypackage.*
を使用します
mypackage
内のすべてのクラスおよびそのサブパッケージが一致するようにするには、mypackage.**
を使用します
先頭がtext
のすべてのクラスが一致するようにするには、text*
を使用します
このクラスがどのフィルタにも一致しない場合、そのクラスは受け入れられています。特定のクラスのみを受け入れる場合は、フィルタが一致市内すべてのクラスを拒否する必要があります。指定されたクラス以外のすべてのクラスを拒否するには、クラス・フィルタ内の最後のパターンとして!*
を含めます。
パターンの構文の詳細は、conf/security/java.security
ファイルまたはJEP 290を参照してください。
パターン・ベースのフィルタの制限事項
パターン・ベースのフィルタは、単純な受入れまたは拒否のために使用されます。これらのフィルタには、いくつかの制限事項があります。次に例を示します。
パターンでは、クラスに基づいて配列のサイズを変えることができません。
パターンは、クラスのスーパータイプまたはインタフェースに基づいたクラスと一致させることができません。
パターンには状態がなく、ストリーム内のデシリアライズされた以前のクラスに基づいて選択を行うことができません。
1つのアプリケーションのパターン・ベースのフィルタの定義
1つのアプリケーションのシステム・プロパティとしてパターン・ベースのフィルタを定義できます。システム・プロパティは、セキュリティ・プロパティの値よりも優先されます。
1つのアプリケーションおよびJavaの単一の呼出しにのみ適用されるフィルタを作成するには、コマンド行でjdk.serialFilter
システム・プロパティを定義します。
次の例では、個々のアプリケーションのリソース使用率を制限する方法を示します。
java -Djdk.serialFilter=maxarray=100000;maxdepth=20;maxrefs=500 com.example.test.Application
プロセス内のすべてのアプリケーションのパターン・ベースのフィルタの定義
プロセス内のすべてのアプリケーションのセキュリティ・プロパティとしてパターン・ベースのフィルタを定義できます。システム・プロパティは、セキュリティ・プロパティの値よりも優先されます。
java.security
プロパティ・ファイルを編集します。JDK 9以降: $JAVA_HOME/conf/security/java.security
JDK 8、7、6: $JAVA_HOME/lib/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
でカスタム・フィルタを設定できます。または、すべてのストリームに同じフィルタを適用するには、プロセス全体のフィルタを設定します。ObjectInputStream
にフィルタが定義されていない場合は、プロセス全体のフィルタが呼び出されます(ある場合)。
ストリームのデコード中に、次のアクションが発生します。
ストリーム内の新規オブジェクトごとに、このフィルタが呼び出された後、オブジェクトがインスタンス化され、デシリアライズされます。
ストリーム内のクラスごとに、フィルタが解決されたクラスとともに呼び出されます。これは、ストリーム内のスーパータイプおよびインタフェースごとに個別に呼び出されます。
フィルタは、作成されるオブジェクトのクラス、それらのクラスのスーパータイプ、そのインタフェースなど、ストリーム内で参照される各クラスを調査できます。
ストリームの配列ごとに、プリミティブ配列、文字配列またはオブジェクト配列のいずれであっても、配列クラスおよび配列長を指定したフィルタが呼び出されます。
ストリームから読み取られたオブジェクトへの参照ごとに、深さ、参照の数およびストリーム長をチェックできるフィルタが呼び出されます。深さは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; } }
プロセス全体のカスタム・フィルタの設定
特定のストリームでオーバーライドされないかぎり、ObjectInputStreamのすべての使用に適用されるプロセス全体のフィルタを設定できます。アプリケーション全体で必要とされるすべてのタイプと条件を識別できる場合、フィルタはこれらを受け入れ、残りを拒否できます。通常、プロセス全体のフィルタを使用して、特定のクラスまたはパッケージを拒否したり、配列サイズ、グラフ深度または合計グラフ・サイズを制限します。
プロセス全体のフィルタは、ObjectInputFilter.Configクラスのメソッドを使用して1回設定されます。フィルタは、クラスのインスタンス、ラムダ式、メソッド参照またはパターンにすることができます。
ObjectInputFilter filter = ... ObjectInputFilter.Config.setSerialFilter(filter);
次の例では、プロセス全体のフィルタがラムダ式を使用して設定されます。
ObjectInputFilter.Config.setSerialFilter(info -> info.depth() > 10 ? Status.REJECTED : Status.UNDECIDED);
次の例では、プロセス全体のフィルタがクラスのインスタンスを使用して設定されます。
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)) ? Status.ALLOWED : Status.REJECTED; } return Status.UNDECIDED; } }
この例では次のようになります。
checkInput
メソッドはObjectInputFilter.FilterInfo
オブジェクトを受け入れます。オブジェクトのメソッドは、チェック対象のクラス、配列サイズを、現在の深さ、既存のオブジェクトへの参照の数およびこれまでに読み取られたストリーム・サイズへのアクセスを提供します。
serialClass
がnullでない場合は、新しいオブジェクトが作成されていることを示しており、値がチェックされて、オブジェクトのクラスがNumber
であるかどうかが確認されます。該当する場合はこの値が受け入れられ、それ以外の場合は拒否されます。
引数のその他の組合せでは、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; } }
例: java.baseモジュール内のクラスのフィルタ
メソッドとしても実装されているこのカスタム・フィルタは、JDKのベース・モジュール内のクラスのみを許可します。この例は、JDK 9以降で機能します。
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; }
Java Remote Method Invocation (RMI)レジストリ、RMI分散ガベージ・コレクタおよびJava Management Extensions (JMX)には、すべてJDKに含まれているフィルタが備えられています。RMIレジストリとRMI分散ガベージ・コレクタ用に独自のフィルタを指定して、保護を追加する必要があります。
RMIレジストリのフィルタ
注意:
これらのビルトイン・フィルタは、開始点としてのみ使用します。sun.rmi.registry.registryFilter
システム・プロパティを編集して、ブラックリストを構成するか、ホワイトリストを拡張して、RMIレジストリに保護を追加します。アプリケーション全体を保護するため、パターンをjdk.serialFilter
グローバル・システム・プロパティに追加して、独自のカスタム・フィルタが設定されていない他のシリアライズ・ユーザーに対する保護を強化します。RMIレジストリには、オブジェクトをレジストリ内でバインドできるビルトイン・ホワイトリスト・フィルタが含まれています。これには、java.rmi.Remote
、java.lang.Number
、java.lang.reflect.Proxy
、java.rmi.server.UnicastRef
、java.rmi.activation.ActivationId
、java.rmi.server.UID
、java.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.ObjID
、java.rmi.server.UID
、java.rmi.dgc.VMID
およびjava.rmi.dgc.Lease
クラスのインスタンスが含まれています。
maxarray=1000000,maxdepth=20
パターンとともにsun.rmi.transport.dgcFilter
システム・プロパティを使用して、フィルタを定義し、ビルトイン・フィルタを破棄します。フィルタに渡されるクラスを受け入れるか、クラスまたはサイズを拒否する場合は、ビルトイン・フィルタが呼び出されません。差替えフィルタが何も受け入れたり、拒否しない場合は、ビルトイン・フィルタが呼び出されます。
JMXのフィルタ
RMIServer.newClientリモート呼出しの実行中およびRMIを介したサーバーへのデシリアライズ・パラメータの送信中に使用される、デシリアライズ・フィルタ・パターン文字列を指定できます。management.properties
ファイルを使用して、デフォルト・エージェントにフィルタ・パターン文字列を指定することもできます。ロギングをオンにして、シリアライズ・フィルタへの呼出しの初期化、却下および受入れを記録できます。ホワイトリストとブラックリストを構成するときに、ログ出力を診断ツールとして使用して、デシリアライズの内容を表示し、設定を確認します。
ロギングを有効にすると、フィルタ・アクションがjava.io.serializationロガーに記録されます。
シリアライズ・フィルタ・ロギングを有効にするには、$JDK_HOME/conf/logging.properties
ファイルを編集します。
java.io.serialization.level = FINER
java.io.serialization.level = FINEST