Android WebView 远程代码执行漏洞

来源:互联网 发布:c语言数据类型怎么用 编辑:程序博客网 时间:2024/06/06 09:02

这个周末我们新开发的app超嘀折交给乌云测试检测,其中WebView 远程代码执行漏洞被反复提出,当时就蒙了,这是个什么东西,在网上查了很多资料,在这里做出记录。

一、 漏洞描述

Android的SDK中提供了的WebView组件,用于在应用中嵌入一个浏览器来进行网页浏览。 WebView组件中的addJavascriptInterface方法可以用于实现本地Java和JavaScript的交互。但是这个方法存在远程代码执行漏洞,远程攻击者利用此漏洞能实现本地java和js的交互,可对Android移动终端进行网页挂马从而控制受影响设备。

简单地说,就是用addJavascriptInterface可能导致不安全,因为JS可能包含恶意代码。当JS包含恶意代码时,它可以干任何事情:访问当前设备的SD卡上面的任何东西,甚至是联系人信息,短信等。

二、 漏洞检测

常用检测漏洞的方法是用webView打开下面的页面,如果当前 app 存在漏洞,将会在页面中输出存在漏洞的接口方便程序员做出修改:

<!DOCTYPE html><html><head>    <meta charset="UTF-8" />    <title>WebView 漏洞检测</title>    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"></head><body><p>    <b>如果当前 app 存在漏洞,将会在页面中输出存在漏洞的接口方便程序员做出修改:</b></p><script type="text/javascript">    function check()    {        for (var obj in window)        {            try {                if ("getClass" in window[obj]) {                    try{                        window[obj].getClass();                        document.write('<span style="color:red">'+obj+'</span>');                              document.write('<br />');                    }catch(e){                    }                }            } catch(e) {            }        }    } check();</script></body></html>

check()方法遍历所有window的对象,然后找到包含getClass方法的对象。如果getClass方法能够获取对象,那么就可以利用这个对象的类进行入侵操作。

三、解决方案

1、Android 4.2以上

Android4.2 开始,对于JavaScript代码通过addJavascriptInterface 添加的java 代码的调用做出了限制,只有public并且声明了@JavascriptInterface 的方法才可以被JavaScript代码调用。

 @SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled"})     public void showSalesActivity(String url) {       webView.loadUrl(url);    }

2、Android 4.2以下

Android 4.2一下版本就比较不容易解决。主要的思路是动态生成一段声明Javascript方法的JS脚本,通过loadUrl来加载它,从而注册到html页面中,这个js中调用prompt方法,通过prompt把JS中的信息传递给java(这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等),在onJsPrompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到Java对象的方法。

``` pythonpackage com.heshidai.javatojs;import android.annotation.SuppressLint;import android.annotation.TargetApi;import android.graphics.Bitmap;import android.os.Build;import android.os.Bundle;import android.support.design.widget.FloatingActionButton;import android.support.design.widget.Snackbar;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.Toolbar;import android.view.View;import android.webkit.JsPromptResult;import android.webkit.WebChromeClient;import android.webkit.WebSettings;import android.webkit.WebView;import android.webkit.WebViewClient;import org.json.JSONObject;public class MainActivity extends AppCompatActivity {    private WebView webView;    private JSCallManager jsCallManager;    @TargetApi(Build.VERSION_CODES.HONEYCOMB)    @SuppressLint({"JavascriptInterface"})    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);        fab.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)                        .setAction("Action", null).show();            }        });        webView = (WebView) findViewById(R.id.webView);        WebSettings webSettings = webView.getSettings();        webSettings.setJavaScriptEnabled(true);        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);        webSettings.setAllowFileAccess(true);//允许访问文件        webSettings.setSupportZoom(true);//设置支持缩放        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);//设置缓存模式(不使用缓存,只从网络获取数据.)        webSettings.setBuiltInZoomControls(true);//启动内置缩放        webSettings.setLoadsImagesAutomatically(true);//自动加载图片        webSettings.setDefaultTextEncodingName("UTF-8");        webSettings.setLoadWithOverviewMode(true);//自适应屏幕        webView.setWebViewClient(new MyWebViewClient());        webView.setWebChromeClient(new MyWebChromeClient());        webView.loadUrl("www.baidu.com");        //JSCallManager本地处理js消息的对象        if (Build.VERSION.SDK_INT >= 17) {            // 在sdk4.2以上的系统上继续使用addJavascriptInterface            webView.addJavascriptInterface(new JSCallManager(this), "Native");        } else {            //4.2之前 addJavascriptInterface有安全泄漏风险            //移除js中的searchBoxJavaBridge_对象,在Android 3.0以下,系统自己添加了一个叫            //searchBoxJavaBridge_的Js接口,要解决这个安全问题,我们也需要把这个接口删除            jsCallManager = new JSCallManager(this);            webView.removeJavascriptInterface("searchBoxJavaBridge_");            // 在 h5开始加载时动态给js注入Native对象和call方法,模拟addJavascriptInterface            //接口给js注入Native对象            //动态注入的好处就是不影响线上的h5数据,不影响ios使用            //在onPageStarted方法中注入是因为在h5的onload方法中有与本地交互的处理            //prompt()方法是js弹出的可输入的提示框            webView.loadUrl("javascript:if(window.Native == undefined){window.Native=\n"+  "{call:function(arg0,arg1){prompt('{\\\"methodName\\\":' + arg0 + ',\\\"jsonValue\\\":' + \n" +  "arg1 + '}')}}};\"");        }    }    class MyWebViewClient extends WebViewClient {        //WebViewClient的方法 h5开始加载的回调        public void onPageStarted(WebView view, String url, Bitmap favicon) {            super.onPageStarted(view, url, favicon);            if (Build.VERSION.SDK_INT < 17) {                // 在 h5开始加载时动态给js注入Native对象和call方法,模拟addJavascriptInterface                //接口给js注入Native对象                //动态注入的好处就是不影响线上的h5数据,不影响ios使用                //在onPageStarted方法中注入是因为在h5的onload方法中有与本地交互的处理                //prompt()方法是js弹出的可输入的提示框                view.loadUrl("javascript:if(window.Native == undefined){" +                        "window.Native=\n" +                        "{" +                        "onButtonClick:function(arg0,arg1){" +                        "prompt('{\\\"methodName\\\":' + javaMethod + ',\\\"jsonValue\\\":' + \n" + "jsonValue + '}')" +                        "}" +                        "}" +                        "};");            }        }    }    class MyWebChromeClient extends WebChromeClient {        //当js调用prompt方法时会触发onJsPrompt        //message就是js传给本地的信息        //result.confirm是回传给js的信息        @Override        public boolean onJsPrompt(WebView view, String url, final String message, String defaultValue, JsPromptResult result) {            if (Build.VERSION.SDK_INT < 17) {                new Thread(new Runnable() {                    @Override                    public void run() {                        try {                            //message的格式是json                            //jsCallManager.call方法是原来处理js响应事件的方法,methodName是指要处理的js事件名称,jsonValue是指要处理的js事件的参数                            JSONObject jsonObject = new JSONObject(message);                            String methodName = jsonObject.getString("methodName");                            String jsonValue = jsonObject.getString("jsonValue");                            jsCallManager.call(methodName, jsonValue);                        } catch (Exception e) {                        }                    }                }).start();            }            result.confirm("callBackMessage");//返回给js的去处理的数据            return super.onJsPrompt(view, url, message, defaultValue, result);        }    }}

需要注意的是:
1】刚开始时在当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、removeJavascriptInterface

当系统辅助功能中的任意一项服务被开启后,所有由系统提供的WebView都会被加入两个JS objects,分别为是”accessibility” 和 “accessibilityTraversal”。如果APP使用了系统的WebView,并且设置了setJavaScriptEnabled(),那么恶意攻击者就可以使用”accessibility” 和 “accessibilityTraversal” 这两个Java Bridge来执行远程攻击代码。
不同的Android 系统版本的可被攻击性也是不一样的。从API Level 17 (含)也就是Android4.2 开始,对于JavaScript代码通过addJavascriptInterface 添加的java 代码的调用做出了限制,只有public并且声明了@JavascriptInterface 的方法才可以被JavaScript代码调用。所以理论上在Android4.1(含)之前的版本上此漏洞会更容易被利用且有更高的危害。但是经过测试发现,有些手机即使系统版本是4.2,仍然可以利用此漏洞,在JavaScript中调用getClass() 方法。
我们需要通过removeJavascriptInterface(该方法在API 11中才有的,需要做判断)显示的删除accessibilityTraversal、accessibility。同时在Android 3.0以下,系统自己添加了一个叫searchBoxJavaBridge_的Js接口,要解决这个安全问题,我们也需要把这个接口删除。

 if (Build.VERSION.SDK_INT >= 11) {            webView.removeJavascriptInterface("searchBoxJavaBridge_");            webView.removeJavascriptInterface("accessibility");            webView.removeJavascriptInterface("accessibilityTraversal");        }

4、不在js中调用java方法

即不使用addJavascriptInterface 也不使用@SuppressLint({“JavascriptInterface”})。通过url拦截的形式去触发java中的方法

 webView.setWebViewClient(new WebViewClient() {            @Override            public boolean shouldOverrideUrlLoading(WebView view, String url) {//此处能拦截超链接的url,即拦截href请求的内容.                if (url.contains("first_access_share")) {//如果包含“first_access_share”拦截跳转                    firstAccessShare();//调用对应的方法                    return true;                }                view.loadUrl(url);                return true;            }        });

总结

1、WebView 远程代码执行漏洞 在Android4.2之后得到了一定的修复(有的系统中4.2上还是存在),只有public并且声明了@JavascriptInterface 的方法才可以被JavaScript代码调用。
2、Android4.2以下如果需要在JavaScript代码调用,就需要比较复杂的方法。主要的思路是动态生成一段声明Javascript方法的JS脚本,通过loadUrl来加载它,从而注册到html页面中,这个js中调用prompt方法,通过prompt把JS中的信息传递给java(这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等),在onJsPrompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到Java对象的方法。
3、上述两种情况还是有漏洞需要显示的用 webView.removeJavascriptInterface去掉一些方法searchBoxJavaBridge_、accessibility、accessibilityTraversal
4、最后的方法就是不要在JavaScript中调用java方法,使用url拦截的形式去触发(我的项目中使用的这种方法)

本文参考了一下文章
http://blog.csdn.net/leehong2005/article/details/11808557
http://seclab.safe.baidu.com/2014-10/android-webview-cve-2014-7224.html
http://blog.csdn.net/zhouyongyang621/article/details/47000041

2 0
原创粉丝点击