1. 程式人生 > >【轉】WebView的JavaScript與本地代碼三種交互方式

【轉】WebView的JavaScript與本地代碼三種交互方式

添加 lba 文件 abr cli 漏洞 大致 execution span

WebView的漏洞分析

漏洞產生的原因

最近在開發過程中遇到一個問題,就是 WebView 使用的時候,還是需要解決之前系統(4.2之前)導致的一個漏洞,雖然現在這個系統版本用戶很少了,但是也不能忽視,關於這個漏洞,這裏就不多做解釋了,可能有的同學早就了解了,本來想寫一篇文章詳細介紹一下,但是網上的知識太多了,而且都很詳細,就沒弄了,這裏大致簡單明了的說幾句: 漏洞產生的原因 這個漏洞導致的原因主要是因為Android中 WebView 中的 JS訪問本地方法 的方式存在缺陷,我們做過交互的都知道,Android中的交互方式是通過 WebView 的一個方法:

addJavascriptInterface(new JSObject(), "myObj");

第一個參數:Android本地對象;第二個參數:JS代碼中需要使用的對象。所以這裏看其實就相當於一個映射關系,把Android中的本地對象和JS中的對象關聯即可。

那麽這裏就存在這樣的一個問題了,在4.2系統之前,JS中通過對象調用的方法無需任何註解約束,那麽就意味著JS中拿到這個對象,就可以調用這個對象中所有的方法。

而我們知道Android中的對象有一個公共的方法 getClass() 方法,而這個方法可以獲取到當前類 類型Class,而這個類型有一個很關鍵的方法就是 Class.forName,這個方法可以加載一個類,這裏可以加載 Java.lang.Runtime 這個類,而這個類就可以執行本地命令了,那麽就會發生危險了,比如這裏可以執行命令獲取本地設備的SD卡中的文件等信息,非常危險的。 上面可能說的還是有些同學不太了解,下面就用一段簡單的JS代碼來了解一下:

技術分享

看到這段JS之後的同學應該好理解了,因為我們Android本地通過 WebView 進行了對象映射,那麽 WebView 加載頁面中如果包含這段JS代碼,那麽就會存在這個問題,這裏先遍歷window中所有的對象,然後查找這個對象是否有 getClass方法,有的話就在利用反射調用 Runtime類 執行具體命令即可。其實這個漏洞也得力於JS中的語法特性,這裏可以看到JS語法非常的靈活。

修復漏洞

當然對於 Android4.2 之後系統修復了這個漏洞,修復方法也很簡單,加上註解約束:@JavascriptInterface 就是只有加上這個註解的方法才會被JS調用,那麽我們知道getClass 是 Object類 中的,肯定沒有這個註解,那麽上面的JS代碼肯定執行不到了。就這樣解決了這個漏洞。

還有一個問題,就是Android系統默認的會給 WebView 添加一個JS映射對象:searchBoxJavaBridge_ 這個對象是 Android3.0 之後默認加上去的,也就是說通過這個對象也是可以操作的上面的代碼的。

JS代碼與本地代碼交互

說完了,這個漏洞,下面開始說今天的正題了,為什麽要先介紹這個漏洞呢?原因就是如果要在 4.2以下版本 中解決這個漏洞的話就需要借助今天介紹的內容了,首先來看看今天的內容主要是介紹 Android 中 WebView的JS 和 本地交互 的三種方式:

第一種方式:通過addJavascriptInterface方法進行添加對象映射

這種方式不多解釋了,也是Android中最常用的方式,但是這種方式會存在風險就是上面說到的漏洞問題。

技術分享

這裏定義一個簡單的本地對象,在需要被調用的方法加上約束註解。JS代碼也很簡單:

技術分享

這種方式的好處在於使用簡單明了,本地和JS的約定也很簡單,就是對象名稱和方法名稱約定好即可,缺點就是存在漏洞問題。

技術分享

第二種方式:利用WebViewClient接口回調方法攔截url

這種方式其實實現也很簡單,就是我們可以添加 WebViewClient 回調接口,在 shouldOverrideUrlLoading 回調方法中攔截url,然後解析這個url的協議,如果發現是我們約定好的協議就開始解析參數執行具體邏輯:

技術分享

代碼也很簡單,這個方法可以攔截 WebView 中加載url的過程,得到對應的url,我們就可以通過這個方法,和網頁JS約定好一個協議,看一下JS代碼:

技術分享

這個JS代碼執行之後,就會觸發本地的 shouldOverrideUrlLoading 方法,然後進行參數解析,調用指定方法。

這個方法是不會存在第一種方法的漏洞問題,但是細心的同學可以發現,這裏本地和JS之間的約定有點繁瑣,比如要約定好協議,參數名稱等信息,沒有第一種方式方便。而且最主要的問題是,這個只能主動的調用本地化方法,如果想得到方法的返回值,只能通過 WebView 的 loadUrl 方法去執行JS方法,把返回值傳遞回去:mWebView.loadUrl(“JavaScript:clicktworesult(“+res+”)”);

技術分享

看到這種方式是非常繁瑣的。在Android中也是不提倡使用的。

註意:在 iOS 中沒有像 Android 中的第一種方式,他也是為了安全考慮,所以 iOS 中的交互就是采用的第二種方式,通過 攔截url 來進行操作的。 第三種方式:利用WebChromeClient回調接口的三個方法攔截消息 這個方法的原理其實和第二種方式差不多,只是攔截的接口方法不一樣:

技術分享

Android中的 WebView 添加 WebChromeClient 接口,可以攔截JS中的幾個提示方法,也就是幾種樣式的對話框,在JS中有三個常用的對話框方法:

alert: 是彈出警告框,在文本裏面加入\n就可以換行。

confirm: 彈出確認框,會返回布爾值,通過這個值可以判斷點擊時確認還是取消。true表示點擊了確認,false表示點擊了取消。

prompt: 彈出輸入框,點擊確認返回輸入框中的值,點擊取消返回null。

那麽這三種對話框都是可以在本地攔截到的,那麽第三種方法的原理就是攔截這些方法,得到他們的消息內容,然後解析即可,比如這裏我們攔截了 prompt 方法內容:

技術分享

本地攔截的方法參數說明:

技術分享

為什麽要攔截 prompt 方法,因為這個方法可以返回想要的值,而對於 alert 是無法得到返回值的,confirm 只能得到兩個返回值。只有 prompt 方法可以返回各種類型的值,操作最方便。

然後在這個方法中和第二種方法一樣的原理解析消息內容即可。

執行結果 下面直接看看上面的三種方式的執行效果:

技術分享技術分享技術分享

其中html代碼如下:

技術分享

三種方式總結

好了,到這裏我們就介紹完了Android中WebView的JS和本地交互的三種方式:

第一種方式:是最普遍的用法,方便簡單,但是在4.2系統以下存在漏洞問題。

第二種方式:是通過攔截url,解析約定之後的協議調用本地方法,缺點是約束協議比較繁瑣,而且傳回執行之後的返回值比較麻煩。但是不會存在漏洞問題,而這種方式也是iOS中采用的方式。

第三種方式:其實和第二種方式差不多,只是攔截的方法變了,這裏攔截的是JS中的三種對話框方法,而這三種對話框方法的區別就在於返回值問題,alert 對話框方法沒有返回值,confirm 對話框方法只有兩種狀態的返回值,prompt 對話框方法可以返回任意類型的返回值。缺點和第二種方法一樣,協議約定比較繁瑣,但是不會存在漏洞問題。

@Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
//            result.confirm();//無法返回給js
//            result.cancel();//無法返回給js
        return super.onJsAlert(view, url, message, result);
    }

    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        result.confirm();//var ret=confirm("are you sure?"); ret為true
        result.cancel();//var ret=confirm("are you sure?"); ret為false
        return super.onJsConfirm(view, url, message, result);
    }

/**
     * @param view
     * @param url
     * @param message
     * @param defaultValue
     * @param result
     * @return 是否處理該js彈窗, 不處理則js負責彈窗, 處理則Android負責彈窗, 另外可以調用result的confirm方法,
     * 給js返回用戶輸入的結果, 一般應該是Android處理彈窗才需要調用
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
//            result.cancel();//var result = prompt("typein please","10"); result 為null
//            result.confirm();//var result = prompt("typein please","10"); 沒有返回值
        result.confirm("haha");//var result = prompt("typein please","10"); result 為haha
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }

4.2以下漏洞修復策略

最後在來介紹一下文章中開始介紹的漏洞問題,雖然google在4.2之後修復了這個漏洞,但是對於4.2之前的用戶該如何處理這個漏洞呢?這裏主要就是需要借助第三種方式了,攔截prompt 方法,修復步驟很簡單:

1 我們自己顯示一個 WebView 的包裝器,重寫他的 addJavascriptInterface 方法,然後在內部自己維護一個對象映射關系Map。

2 在 WebView 加載頁面的方法中構造一段本地JS代碼:

技術分享

關於這段JS代碼的含義:

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

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

當JS調用 onButtonClick 或 onImageClick 時,就會回調到Java層中的 onJsPrompt 方法,我們再解析出方法名,參數,對象名,再反射調用方法。

window.jsInterface 這表示在window上聲明了一個Js對象,聲明方法的形式是:方法名: function(參數1,參數2)。

而加載這段JS代碼的時機是什麽時候呢?

剛開始時在當WebView正常加載URL後去加載JS,但發現會存在問題,如果當 WebView 跳轉到下一個頁面時,之前加載的JS就可能無效了,所以需要再次加載。這個問題經過嘗試,需要在以下幾個方法中加載Js:

它們是 WebChromeClient 和 WebViewClient 的方法:onLoadResource,doUpdateVisitedHistory,onPageStarted,onPageFinished,onReceivedTitle,onProgressChanged。

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

關於返回值,可以通過 prompt 返回回去,這樣就可以把Java中方法的處理結果返回到JS中。通過這幾步,就可以簡單的修復漏洞問題,但是還是需要註意幾個問題:

需要過濾掉 Object類 的方法,由於通過反射的形式來得到指定對象的方法,他會把基類的方法也會得到,最頂層的基類就是Object,所以我們為了不把 getClass 方法註入到Js中,所以我們需要把Object的公有方法過濾掉。這裏嚴格說來,應該有一個需要過濾方法的列表。目前我的實現中,需要過濾的方法有: “getClass”,”hashCode”,”notify”,”notifyAll”,”equals”,”toString”,”wait”。

在Android 3.0以下,系統自己添加了一個叫 searchBoxJavaBridge_ 的Js接口,要解決這個安全問題,我們也需要把這個接口刪除,調用 removeJavascriptInterface 方法。這個 searchBoxJavaBridge_ 好像是跟google的搜索框相關的。

在實現過程中,我們需要判斷系統版本是否在4.2以下,因為在4.2以上,Android修復了這個安全問題。我們只是需要針對4.2以下的系統作修復。 項目案例下載地址: http://download.csdn.net/detail/jiangwei0910410003/9641825

Webview調用JavaScript後獲取返回值

前邊部分提到了javascript獲取java代碼執行的返回值,主要包括兩種方式:

(1)java代碼執行完畢後,主動調用JavaScript函數,把值傳遞給JavaScript;

(2)攔截prompt方法,通過JsPromptResult的confirm方法傳回返回值。

下邊討論java獲取JavaScript執行後的返回值:

一般java執行js的函數為:

/**
 * Loads the given URL.
 *
 * @param url the URL of the resource to load
 */
public void loadUrl(String url) {
    checkThread();
    mProvider.loadUrl(url);
}

可以看到這個函數是沒有返回值的,為了解決這個問題,Android在4.4以後引入了一個新的API:

 /**
 * Asynchronously evaluates JavaScript in the context of the currently displayed page.
 * If non-null, |resultCallback| will be invoked with any result returned from that
 * execution. This method must be called on the UI thread and the callback will
 * be made on the UI thread.
 *
 * @param script the JavaScript to execute.
 * @param resultCallback A callback to be invoked when the script execution
 *                       completes with the result of the execution (if any).
 *                       May be null if no notificaion of the result is required.
 */
public void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
    checkThread();
    mProvider.evaluateJavaScript(script, resultCallback);
}

調用該函數,通過傳入一個callback來接收返回值。那麽在4.4以下怎麽辦呢?

既然我們可以在java中調用js代碼,那就可以為所欲為了,分為兩步:

step 1 :向js中註入一個java對象:javaObj;

/**
 * Passed in addJavascriptInterface of WebView to allow web views‘s JS execute
 * Java code
 */
public class JavaScriptInterface {
private final CallJavaResultInterface mCallJavaResultInterface;

public JavaScriptInterface(CallJavaResultInterface callJavaResult) {
	mCallJavaResultInterface = callJavaResult;
}

@JavascriptInterface
public void returnResultToJava(String value, int callIndex) {
	mCallJavaResultInterface.jsCallFinished(value, callIndex);
}
}



final JavaScriptInterface jsInterface = new JavaScriptInterface(callJavaResult);
	mWebView.addJavascriptInterface(jsInterface, "javaObj");

step 2: 使用javaObj回調:

 mWebView.loadUrl("javaObj.returnResultToJava(eval(‘Math.sqrt(2 * 8);‘), 2);");

就可以在JavaScriptInterface中接收返回值了。

該方法實現可以參考開源庫:

https://github.com/evgenyneu/js-evaluator-for-android

和這個庫的實現原理是一樣的。

但是4.2以下又有安全問題,於是終極方案出來了:

通過js的prompt方法回傳參數,java中攔截prompt方法。

總結

在Android中WebView的作用還是舉足輕重的,加上現在很多應用都開始采用網頁版功能,那麽在這個過程中無法避免就是需要JS和本地交互,本文就詳細的介紹了現階段的三種交互方式,每種方式都有缺點和優點,當然最好的方式還是采用系統提供的也就是本文介紹的第一種方式,但是需要修復Android4.2以下存在的漏洞問題即可。

原文鏈接:http://yuanfentiank789.github.io/2016/10/09/webview_js/

【轉】WebView的JavaScript與本地代碼三種交互方式