1. 概要
このチュートリアルでは、攻撃者がJavaコードの逆シリアル化を使用してシステムを悪用する方法を探ります。
まず、攻撃者がシステムを悪用するために使用する可能性のあるいくつかの異なるアプローチを見ていきます。 次に、攻撃が成功した場合の影響を見ていきます。 最後に、これらのタイプの攻撃を回避するためのいくつかのベストプラクティスを見ていきます。
2. 逆シリアル化の脆弱性
Javaは、デシリアライズを広く使用して、入力ソースからオブジェクトを作成します。
これらの入力ソースはバイトストリームであり、さまざまな形式で提供されます(一部の標準形式にはJSONとXMLが含まれます)。 L 正当なシステム機能またはネットワーク全体の信頼できるソースとの通信は逆シリアル化を使用します。ただし、信頼できないまたは悪意のあるバイトストリームは脆弱な逆シリアル化コードを悪用する可能性があります。
Javaシリアル化に関する前回の記事では、シリアル化と逆シリアル化がどのように機能するかについて詳しく説明しています。
2.1. 攻撃ベクトル
攻撃者がデシリアライズを使用してシステムを悪用する方法について説明しましょう。
クラスをシリアライズ可能にするには、シリアライズ可能インターフェースに準拠している必要があります。 Serializable を実装するクラスは、メソッド readObjectおよびwriteObjectを使用します。これらのメソッドは、クラスのオブジェクトインスタンスをそれぞれ逆シリアル化およびシリアル化します。
これの典型的な実装は次のようになります。
public class Thing implements Serializable {
private static final long serialVersionUID = 0L;
// Class fields
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
// Custom attribute setting
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
// Custom attribute getting
}
}
一般的なフィールドまたは大まかに定義されたフィールドがあり、リフレクションを使用してこれらのフィールドに属性を設定すると、クラスは脆弱になります:
public class BadThing implements Serializable {
private static final long serialVersionUID = 0L;
Object looselyDefinedThing;
String methodName;
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
try {
Method method = looselyDefinedThing.getClass().getMethod(methodName);
method.invoke(looselyDefinedThing);
} catch (Exception e) {
// handle error...
}
}
// ...
}
上記を分解して、何が起こっているかを見てみましょう。
まず、クラス BadThing には、タイプObjectのフィールドlooselyDefinedThingがあります。これはあいまいであり、攻撃者がこのフィールドを任意のタイプにすることができます。これはクラスパスで利用できます。
次に、このクラスを脆弱にしているのは、 readObject メソッドに、looselyDefinedThingのメソッドを呼び出すカスタムコードが含まれていることです。 呼び出したいメソッドは、リフレクションを介してフィールドmethodName(攻撃者が制御することもできます)を使用します。
上記のコードは、クラス MyCustomAttackObject がシステムのクラスパス上にある場合、実行中の次のコードと同等です。
BadThing badThing = new BadThing();
badThing.looselyDefinedThing = new MyCustomAttackObject();
badThing.methodName = "methodThatTriggersAttack";
Method method = looselyDefinedThing.getClass().getMethod(methodName);
method.invoke(methodName);
public class MyCustomAttackObject implements Serializable {
public static void methodThatTriggersAttack() {
try {
Runtime.getRuntime().exec("echo \"Oh, no! I've been hacked\"");
} catch (IOException e) {
// handle error...
}
}
}
MyCustomAttackObject クラスを使用することにより、攻撃者はホストマシン上でコマンドを実行することができました。
この特定のコマンドは無害です。 ただし、この方法でカスタムコマンドを実行できた場合、攻撃者が達成できる可能性は無限にあります。
まだ残っている問題は、「そもそもなぜ誰かがクラスパスにそのようなクラスを持っているのか」ということです。
攻撃者が悪意のあるコードを実行できるようにするクラスは、多くのフレームワークやソフトウェアで使用されているオープンソースおよびサードパーティのライブラリ全体に広く存在します。これらは上記の例ほど単純ではありませんが、複数のクラスと同様の同類のコマンドを実行できるようにするためのリフレクション。
このように複数のクラスを使用することは、ガジェットチェーンと呼ばれることがよくあります。 オープンソースツールysoserialは、攻撃で使用できるガジェットチェーンのアクティブなリストを維持します。
2.2. 含意
攻撃者がリモートコマンド実行にアクセスする方法がわかったので、攻撃者がシステムで達成できる可能性のある影響について説明します。
JVMを実行しているユーザーのアクセスレベルによっては、攻撃者がすでにマシンに対する特権を高めている可能性があります。これにより、攻撃者はシステム全体のほとんどのファイルにアクセスして情報を盗むことができます。
一部の逆シリアル化エクスプロイトでは、攻撃者がカスタムJavaコードを実行して、サービス拒否攻撃、ユーザーセッションの盗用、またはリソースへの不正アクセスにつながる可能性があります。
各逆シリアル化の脆弱性は異なり、各システムセットアップも異なるため、攻撃者が達成できることは大きく異なります。 この理由から、脆弱性データベースは、逆シリアル化の脆弱性を高リスクと見なします。
3. 予防のためのベストプラクティス
システムがどのように悪用される可能性があるかについて説明したので、このタイプの攻撃を防ぎ、潜在的な悪用の範囲を制限するために従うことができるいくつかのベストプラクティスに触れます。
エクスプロイトの防止には特効薬はなく、このセクションはすべての防止策の完全なリストではないことに注意してください。
- オープンソースライブラリを最新の状態に保つ必要があります。 可能な場合は、最新バージョンのライブラリへの更新を優先します。
- National VulnerabilityDatabaseやCVEMitre (いくつか例を挙げます)などの脆弱性データベースを積極的にチェックして、新たに宣言された脆弱性を確認し、公開されていないことを確認します
- デシリアライズ用の入力バイトストリームのソースを確認します(安全な接続を使用し、ユーザーを確認するなど)
- 入力がユーザー入力フィールドからのものである場合は、デシリアライズする前に、これらのフィールドを検証し、ユーザーを承認してください。
- カスタムデシリアライズコードを作成するときは、owaspチートシートに従ってデシリアライズしてください。
- JVMがホストマシン上でアクセスできるものを制限して、攻撃者がシステムを悪用できる場合に攻撃者が実行できる範囲を縮小します
4. 結論
この記事では、攻撃者がデシリアライズを使用して脆弱なシステムを悪用する方法について説明しました。 さらに、Javaシステムで良好なセキュリティ衛生を維持するためのいくつかのプラクティスについても説明しました。
いつものように、ソースコードはGitHubでから入手できます。