JS与WebView交互存在的一些问题

144
作者 隔壁的李小宝
2015.06.09 19:30 字数 2896

一、背景概述

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以下的系统作修复。

程序人生