一、背景概述
2013年Android平臺暴露出WebView漏洞。利用該漏洞,攻擊者可以通過存在風險的addJavascriptInterface接口函數提供的擴展穿透webkit執行本地Java代碼,造成惡意代碼在受害人的手機上執行,并可能進一步執行木馬。
目前,google公司僅對Android4.2及以上系統提供了規避方法,Android4.2以下所有版本尚無官方解決方案。
本文將就上述漏洞提出相應的解決方案。
Android API level 16以及之前的版本存在遠程代碼執行安全漏洞,該漏洞源于程序沒有正確限制使用WebView.addJavascriptInterface方法,遠程攻擊者可通過使用Java Reflection API利用該漏洞執行任意Java對象的方法,簡單的說就是通過addJavascriptInterface給WebView加入一個JavaScript橋接接口,JavaScript通過調用這個接口可以直接操作本地的JAVA接口。
該漏洞公布的近期,多款Android流行應用曾被曝出高危掛馬漏洞:點擊消息或朋友社區圈中的一條網址時,用戶手機然后就會自動執行被掛馬的代碼指令,從而導致被安裝惡意扣費軟件、向好友發送欺詐短信、通訊錄和短信被竊取以及被遠程控制等嚴重后果。在烏云漏洞平臺上,包括Android版的微信、QQ、騰訊微博、QQ瀏覽器、快播、百度瀏覽器、金山瀏覽器等大批TOP應用均被曝光同類型的漏洞。
不可避免,由于公司中,修舊并行的架構中,涉及很多的WebView和JS交互的功能實現點。安全部已經發布方案,需要我們修改。最近一直在做這方面的研究。
引用:【http://blog.csdn.net/leehong2005/article/details/11808557#】
使用WebView來展示一個網頁,現在很多應用為了做到服務端可控,很多結果頁都是網頁的,而不是本地實現,這樣做有很多好處,比如界面的改變不需要重新發布新版本,直接在Server端修改就行了。用網頁來展示界面,通常情況下都或多或少都與Java代碼有交互,比如點擊網頁上面的一個按鈕,我們需要知道這個按鈕點擊事件,或者我們要調用某個方法,讓頁面執行某種動作,為了實現這些交互,我們通常都是使用JS來實現,而WebView已經提供了這樣的方法,具體用法如下:
< mWebView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");>
我們向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.
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.
簡單地說,就是用addJavascriptInterface可能導致不安全,因為JS可能包含惡意代碼。今天我們要說的這個漏洞就是這個,當JS包含惡意代碼時,它可以干任何事情。
二、實現方案
1、Android 4.2以上的系統
Android 4.2以上系統,通過在Java的遠程方法上面聲明@JavascriptInterface可以解決WebView漏洞。如下面代碼:
class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}
2、Android 4.2以下的系統
若客戶端需要兼容支持Android 4.2以下的系統版本,建議使用本方案規避WebView漏洞。本方案分為兩個修改要點,建議對于https和http場景均使用下述方法規避風險。
修改點一:使用安全方法替代addJavascriptInterface
對于Android 4.2以下的系統Google公司官方沒有提供解決方案。為替代addJavascriptInterface方法,可以利用prompt方法傳參以完成java與js的交互。對應java中的onJsPrompt方法的聲明如下:
public boolean onJsPrompt( WebView view, String url, String message, String defaultValue, JsPromptResult result )
通過這個方法,JS能把信息(文本)傳遞到Java,而Java也能把信息(文本)傳遞到JS中。
具體實施方法如下:
1)讓JS調用一個Javascript方法,在這個方法中調用prompt方法,通過prompt把JS中的信息傳遞過來,這些信息應該是我們組合成的一段有意義的文本,可能包含:特定標識,參數等。在onJsPrompt方法中,我們去解析傳遞過來的文本,得到約定好的特定標識,參數等,再通過特定標識調用指定的java方法,并傳入參數。具體的Java代碼如下:
final class MyWebChromeClient extends WebChromeClient
{
public boolean onJsPrompt( WebView view, String url, String message, String defaultValue, JsPromptResult result )
{
if( message.equals("1") )
{
//解析參數defaultValue
//調用java方法并得到結果
}
//返回結果
result.confirm("result");
return true;
}
}
2)關于返回值,可以通過result返回回去,這樣就可以把Java中方法的處理結果返回到Js中。
3)在Javascript方法中,通過調用prompt方法傳入標識和參數(依次對應onJsPrompt方法中的message、defaultValue參數),以通知java需要使用的方法及對應參數。prompt方法中第一個參數可以傳送約定好的特定方法標識,prompt方法中第二個參數可以傳入對應的參數序列。具體的Javascript代碼如下:
function showHtmlcallJava()
{
var ret = prompt( "1", "param1;param2" );
//ret值即為java傳回的”result”
//根據返回內容作相應處理
}
修改點二:移除系統開放的JS接口
對于Android 3.0以上版本,Android系統開放了部分JS接口。因此在這個版本范圍下,盡管客戶端自身沒有使用addJavascriptInterface方法,黑客仍可以透過系統開放的JS接口實施惡意操作。
針對上述風險,客戶端可通過下面的方法移除風險接口。具體實施方法如下:
1)使用removeJavascriptInterface方法移除操作系統開放的"searchBoxJavaBridge_"、"accessibility"、"accessibilityTraversal"接口。由于removeJavascriptInterface方法只在Android API 11以上版本支持,因此若客戶端需要支持Android API 11以下版本,需要在使用該方法時聲明目標API。
@TargetApi(Build.VERSION_CODES.HONEYCOMB) private void dealJavascriptLeak() { mWebView.removeJavascriptInterface("searchBoxJavaBridge_"); mWebView.removeJavascriptInterface("accessibility"); mWebView.removeJavascriptInterface("accessibilityTraversal"); }
2)針對Android3.0以上系統版本移除問題接口。
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { dealJavascriptLeak(); }
三、總結
對于https和http協議訪問的頁面,開發過程中須注意棄用存在漏洞的addJavascriptInterface方法,通過使用prompt方法傳參,完成java與js的交互。同時客戶端須移除系統開放的JS接口,全面規避WebView漏洞風險。
詳細修步驟:
【1】讓JS調用一個Javascript方法,這個方法中是調用prompt方法,通過prompt把JS中的信息傳遞過來,這些信息應該是我們組合成的一段有意義的文本,可能包含:特定標識,方法名稱,參數等。在onJsPrompt方法中,我們去解析傳遞過來的文本,得到方法名,參數等,再通過反射機制,調用指定的方法,從而調用到Java對象的方法。
【2】關于返回值,可以通過prompt返回回去,這樣就可以把Java中方法的處理結果返回到Js中。
【3】我們需要動態生成一段聲明Javascript方法的JS腳本,通過loadUrl來加載它,從而注冊到html頁面中,具體的代碼如下:
javascript:(function JsAddJavascriptInterface_(){
if (typeof(window.jsInterface)!='undefined') {
console.log('window.jsInterface_js_interface_name is exist!!');}
else {
window.jsInterface = {
onButtonClick:function(arg0) {
return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));
},
onImageClick:function(arg0,arg1,arg2) { prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]}));
},
};
}
}
)()
說明:
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以下的系統作修復。