Androidアプリセキュリティ 〜Webサイト閲覧でroot権限を取得されてしまう脆弱性(1)〜

Androidのブラウザ(図中ではWebkitと記載)の脆弱性を利用して相手の端末の管理者権限を取得されてしまうのはどういった仕組みで起きるのかについて解説します。
「○○○の脆弱性により任意のコードが実行される可能性があります」といった文章を一度は見たことがあると思います。 しかし、実際にどのような流れで任意のコードが実行されるのか?読者の皆さんはイメージできるでしょうか?

今回説明するのは、Androidのブラウザ(図中ではWebkitと記載)の脆弱性を利用して相手の端末の管理者権限を取得する、という少し過激な内容です。 読者の皆様、特にAndroidアプリケーション開発技術者の方々に、今回の記事を通して少しでもセキュリティに興味を持っていただき、セキュアなアプリケーション開発のヒントになればと思います。

1.全体の流れ

遠回りになっていまいますが、まずは、「Android端末からWebサイト閲覧で管理者権限を取得されてしまう」までの全体の流れから説明します。下の図を見てください。

Android端末からWebサイト閲覧で管理者権限を取得

Step1は相手(図中ではターゲットと記載)の端末に悪意のあるサーバへのリンクをクリックさせるところから始まります。リンクをクリックしたターゲット端末は悪意のあるサーバに接続しに行きます。 それを受けて悪意のあるサーバでは、Androidのブラウザの脆弱性を利用して任意のコードを実行可能になるようなExploitコード(JavaScript)を送り込みます。これにより、送り込まれたターゲット端末では脆弱性が発動し、任意のコードが実行可能な状態になります。

しかし、この段階ではWebkitの脆弱性を利用して任意のコードを実行しているに過ぎません。Webkit権限で任意のコードが実行できるようになっただけです。これでは大したことはできません。 悪意のあるユーザが相手から様々な情報を奪取するには管理者権限が必要になります。ではどうやって管理者権限を奪うのでしょうか?それがStep2になります。

Step2ではWebkit権限で任意のコードが実行可能になっている状態を利用し、GingerbreakのようにAndroid端末の管理者権限を取得できるようなツールを悪意のあるサーバからダウンロードします。 この第3者によってユーザの気づかないうちにソフトウエアがダウンロードされることを「ドライブバイダウンロード」といいます。その後、Gingerbreakをターゲット端末で実行することによってWebkit権限から管理者権限に昇格することができます。

このように、Webサイト閲覧時に1clickした後、たった2stepで、管理者権限が取得されてしまう可能性があります。 それでは、次の章でWebkitのどのような脆弱性を利用して任意のコードが実行可能になるのか?について見ていきましょう。

2.Webkitの脆弱性

今回説明する脆弱性はWebkitの脆弱性です。この脆弱性は、JavaScriptでNormalize関数を使用する場合に発生する可能性があるuse after freeと呼ばれる「ポインタを解放した後で、プログラムがそのポインタを使用し続ける場合に発生するエラー」が原因となります。

では、どのように発生するのか見ていきます。下のコードを見てください。

Description : A use after free issue exists in WebKit’s handling of the Node.normalize method.

<p>This test passes if it does not crash.</p>
<div id="test1"></div>
<script>
if (window.layoutTestController)
    layoutTestController.dumpAsText();

var elem = document.getElementById("test1");

function go()
{
    var str = "c";
    for (var i = 0; i < 0x10000; i++)
        var b = str + str;
}

function handler()
{
    elem.removeAttribute("b");  //(1)
    go();
}

elem.setAttribute("b", "a");
elem.attributes[0].appendChild(document.createTextNode("hi"));
elem.attributes[0].addEventListener("DOMSubtreeModified", handler,  false); //(2)
elem.normalize();                               //(2)
</script>

入手元:http://trac.webkit.org/browser/trunk/LayoutTests/fast/dom/Element/normalize-crash.html?rev=59109

上記のコードはWebkitの脆弱性を発現させるためのJavaScriptです。まず、(2)でaddEventListener関数を呼び出してhandler関数を登録しています。 これによりその下行にある、normalize関数が呼び出された際にhandler関数が呼ばれることになります。次に、(1)のhandler関数内ではremoveAttribute関数を呼び出しています。 ここでremoveAttribute関数を呼び出して属性bを削除してしまうと、normalize関数で処理されるはずだったオブジェクトが解放されてしまっている状態となり、use after freeが発生します。

次にnormalize関数の中を見てみましょう。下のコードを見てください。

void Node::normalize()
{
    //Go through the subtree beneath us, normalizing all nodes. This means that
    //any two adjacent text nodes are merged and empty text nodes are removed.

    RefPtr<Node> node = this;
    while (Node * firstChild = node ->firstChild()) //(1)
        node = firstChild;
    while(node ){
        NodeType type = node -> nodeType(); //(1)
        if(type == ELEMENT_NODE)
            static_cast<Element*>(node.get()) ->normalizeAttributes();
    }
}

Android2.2のソースコード中の、/external/webkit/WebCore/dom/Node.cpp

入手元:Android Open Source Project (http://source.android.com/)

上記のコードはJavaScriptでnormalize関数を呼び出したときに呼ばれるnormalize関数そのものです。normalize関数では下の(1)のようにfirstChild変数からnodeType関数を呼び出しています。 ですが、先ほど説明したように、normalize関数が呼び出される時点で属性bは削除されてしまっている為、firstChild変数には不定値が入ります。 また、その影響を受けてfirstChild変数から参照されているnodeType関数のアドレスも同様に不定値です。このようにuse after freeが発生した後では、不定なアドレスにジャンプしてしまう可能性があります。

しかし、もしもnodeType関数のアドレスを「任意のアドレスに置き換える事ができる」としたらどうでしょうか?ここにuse after freeを利用して「任意のコードが実行される可能性」についてのヒントが隠されています。下記図を見てください。

任意のコードが実行される可能性

上記図はHeapメモリを表したイメージ図です。黄色枠のelem.bは、すでに説明済みのnodeType関数アドレスを示しています。 仮にこのnodeType関数アドレスを「0×00560056」という値に書き換えたいとします。しかし、JavaScriptにおいてnodeType関数アドレスはHeapメモリ上のどこに存在するのかアドレスを知ることはできません。そこでHeapメモリ上に変数を生成し数十万回程度「0×00560056」で書き込みを行います。 その結果、偶然nodeType関数アドレスの領域に「0×00560056」という値が上書きされる可能性があります。この「偶然」によってnodeType関数のアドレスは任意のアドレスに置き換えることができます。

ここまでで、nodeType関数のアドレスに任意のアドレスを書き込める可能性について説明しましたが、実はまだ問題があります。 それは、仮に「0×00560056」にジャンプさせることができたとしても、ジャンプ先のアドレス「0×00560056」に、必ずしも関数(図中では任意のコードと記載)が存在するとは限らないということです。

では、任意のコードにたどり着くにはどうしたらよいのでしょうか?

図中の緑枠の部分に注目してください。「nop」と記載されています。nopとはARMの命令コードで「何もしない命令」を意味します。 そのnop命令と任意のコードを1setにしてHeapメモリ上に複数展開します。そうすることによって、多少おかしなアドレスに(今回の例ではアドレス「0×00560056」)ジャンプしてきたとしても、nop命令を実行して関数の先頭にたどり着き、任意のコードを実行することができます。このようなテクニックを使って脆弱性を使った任意のコード実行は実現されます。 なお、nop命令を実行して関数の先頭にたどり着くテクニックをNopSlide、Heap領域に悪意のある関数を複数展開するテクニックをHeapSprayといいます。

ここまで、Webkitの脆弱性を利用して任意のコードを実行する方法を説明しましたが、前述したように、現時点では、Webkit権限で任意のコードが実行できるようになっただけです。 この状態から管理者権限を取得するにはどうしたらよいのでしょうか?それを次回「Android端末からWebサイト閲覧でroot権限を取得されてしまう脆弱性(2)」で説明します。

執筆者プロフィール 片沼 怜、安部 剛

NTT-CERTメンバとして、セキュリティインシデントの対応支援、再発防止策の検討、トレーニングプログラムの開発およびセ キュリティ関連情報の提供など を実施中。