Android WebView 远程代码执行漏洞

这个周末我们新开发的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对象的方法。

``` python
package 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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值