在2017年12月11日,VerSprite发布了如下关于Dolphin Browser(海豚浏览器)的漏洞报告。
一、前言
[VS-2017-001]安卓平台上的海豚浏览器在备份与还原功能处存在任意文件写入漏洞。
CVE ID
CVE-2017-17551
开发商
Mobotap
影响产品
安卓版本低于12.0.2的海豚浏览器
漏洞细节
Android 12.0.2版本的海豚浏览器(由Mobotap厂商开发)日前曝出存在任意文件写入漏洞。漏洞发生在备份与还原功能处。如果浏览器从一个恶意备份文件中还原配置,漏洞就会被触发。此漏洞允许攻击者使用准备好的恶意可执行文件覆盖掉浏览器特定目录下的可执行文件。每当浏览器启动时,都会尝试从磁盘中运行恶意文件,从而执行攻击者的代码。
在这篇文章中,我们将会讨论此漏洞的具体细节,以及如何利用海豚浏览器在备份与还原功能处的漏洞。
二、攻击范围分析
我一直对各种其他厂家开发的Android 浏览器十分感兴趣。因为他们开发了很多不同的功能。这些功能扩展了额外的攻击范围,甚至有可能导致高危漏洞的发生。比如说这次的主角–海豚浏览器,它有很多额外的功能可以去分析是否存在漏洞点。从某种意义上来说这给了攻击者更多的机会。
本次漏洞是在搜索ZipInputStream和ZipFile的API在这里是如何被使用的过程中发现的。在com.dolphin.browser.util.g类中包含了一个引用ZipInputStream类的方法。如下:
try {
v1_2 = new ZipInputStream(((InputStream)v3));
}
try {
File v0_6 = new File(this.b.getApplicationInfo().dataDir);
while(true) {
ZipEntry v2_1 = v1_2.getNextEntry();
if(v2_1 == null) {
break;
}
BufferedOutputStream v2_2 = new BufferedOutputStream(new FileOutputStream(new File(v0_6 + File.separator + v2_1.getName())));
IOUtilities.copy(((InputStream)v1_2), ((OutputStream)v2_2));
v2_2.flush();
v2_2.close();
v1_2.closeEntry();
}
}
对于这里处理的zip压缩文件,代码首先将其逐行读入,然后将app的data目录作为根目录,把zip文件写入到根目录下的一个新文件中。如果我们能够控制zip文件内容,这就可能会导致任意文件写入漏洞。现在,我们需要弄清楚这个功能是用来干什么的。在看完com.dolphin.util.g类后,我发现了其中一段更有趣的代码。
try {
label_129:
Log.e("BackupHelper", ((Throwable)v0));
throw new a(((Throwable)v0));
}
catch(Throwable v0_4) {
goto label_135;
}
这里浏览器在备份些什么?我马上跳转到AndroidManifest.xml并发现了以下Activity。
<activity android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:name="mobi.mgeek.TunnyBrowser.BackupRestoreActivity" android:theme="@android:style/Theme.NoTitleBar"/>
在浏览器的UI界面中搜索一阵之后,我找到了出现备份的地方。
好的,到现在为止我已经知道了一些有用的信息。在使用一遍这个备份与还原功能之后,我总结出了以下几点:
- 浏览器配置可以存储,也可以被还原。
- 备份文件存储的默认目录是在/storage/emulated/0。
- 备份文件有一个自定义的文件扩展名->.dbk。
- 备份可以不设置密码。
- 备份文件名格式为[年:月:日:具体时间]。
- 当你试着去还原备份的时候,浏览器总是会最先访问最近生成的备份文件。
我发现的最重要的信息就是:备份文件确实就是在上述未加任何过滤的代码中处理的zip压缩文件。这也就导致了任意文件写入漏洞的发生。
三、备份文件格式
3.1 加密
继续对备份文件进行逆向,很快发现备份文件是经过加密处理过的。当然了,加密必然有它的加密流程。浏览器加密备份文件分为以下俩种不同的情况:
- 设置密码
- 未设置密码
如果备份设置了密码,浏览器首先将密码与一个静态的密钥连接。将连接后的字符串进行哈希处理。最后,将哈希后的结果填充0x10个额外字节并传给SecretKeySpec()构造函数。如果没有设置密码,整个流程是一样的,除了在使用密码的地方使用了另一个静态密钥代替。
3.2 文件头部
备份文件还包含了一个自定义的文件头,它用来验证浏览器还原备份时所用的备份文件是否是由浏览器创建的合法备份。
0D00010F 01000000 00010000 [sig]
0D00010F是备份文件的标识符。文件头还包含一个在00010000后的签名。签名是使用和备份文件相同的加密过程生成的。
最后,我们用头部文件内容生成CRC校验和,并将它附在签名后。得到如下结构:
0D00010F 01000000 00010000 [sig] [checksum]
在浏览器还原备份时,它使用文件头部内容生成一个CRC校验和与头部签名后的校验和比较,如果它们不相等,则还原过程中止。
现在,我们已经知道如何去生成一个有效的备份文件。
生成过程:
- 创建一个存放恶意代码的zip压缩文件。
- 为zip文件添加文件头->D00010F 01000000 00010000 [sig]。
- 添加头部校验和。
- 加密压缩文件。
四、漏洞利用
在Android app中任意写入的文件与app包中包含的文件是等价的。当我想要写入文件并作为代码执行的时候,我通常会查找以下内容:
DEX
JAR
ELF
幸运的是,在海豚浏览器中找到了一个写入文件的好地方。
/data/data/mobi.mgeek.TunnyBrowser/files # ls -la
...
-rwxr-xr-x u0_a195 u0_a195 9496 2017-12-12 14:26 watch_server
$ file watch_server
watch_server: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, stripped
可以看到,这样一来,我们就可以很轻松地在备份文件中植入恶意代码,并覆盖掉watch_server,从而达到我们的目的。但是浏览器执行watch_server的方式是什么?又是什么时候执行呢?
4.1 watch_server
为了弄清楚watch_server在何时被执行,我需要调用Runtime.getRuntime().exec()在浏览器中搜索服务执行时间。通过这种方式我发现每次浏览器启动时,watch_server都会被执行。
4.2 POC
利用这次海豚浏览器中备份与还原功能处的漏洞的最大障碍就是用户交互需求。也就是说受害者必需还原一个我们所创建的恶意备份并写入SD卡。我还没有找到通过IPC(进程间通信)或者其他方法来触发还原进程。所以目前,唯一可以利用的就是控制选择哪一个备份文件。
- 备份文件名格式为[年:月:日:具体时间]。
- 当你试着还原备份的时候,浏览器总是默认访问最先生成的备份。
在还原进程结束后,浏览器会快速重启,我们的payload也得以执行。
./build.sh
[+] Building ... [!] //构建中…
[armeabi-v7a] Install : payload => libs/armeabi-v7a/payload
[+] Creating tmp.zip and injecting payload [!] //创建tmp.zip并注入payload
[+] Launching backup file format generation [!] //开始生成恶意备份文件
[+] Generating cipher [!] //开始密文生成
[+] key --> 95acde261f3e09d281498163958dd366 //密钥选择
[+] Building backup --> ./backup.dbk [!] //构建初始备份文件
[+] Encrypting and saving backup [!] //加密备份文件并保存
[+] Cleaning up [!]
[+] Pushing backup.dbk to the device [!] //将恶意备份文件传到目标设备
backup.dbk: 1 file pushed. 1.0 MB/s (13926 bytes in 0.014s)
[+] Waiting on restore process ... [!]
--------- beginning of system
--------- beginning of main
V/FlipperFlapper(14975): uid=10195(u0_a195) gid=10195(u0_a195) groups=1015(sdcard_rw),1028(sdcard_r),3003(inet),9997(everybody),50195(all_a195) context=u:r:untrusted_app:s0
https://github.com/VerSprite/research/tree/master/exploits/VS-2017-001