目錄
一、背景
安卓安全測試中,其中一個維度測試就是服務端業務邏輯安全性測試,主要通過抓包實現。
其實說白了就是中間人攻擊,裝着要測試APP的安卓手機發包給我們電腦的burpsuite,我們可以使用bp截包改包的內容,再發給服務端,服務端收到請求後響應,把返回包發給電腦的bp,bp最後發給我們手機上的app。
隨着移動端安全逐漸加強,現在越來越多的app已經無法抓到包,或者提示網絡相關錯誤。其實根本原因在於客戶端發包時對於服務端的ssl證書進行了校驗。
二、客戶端證書處理邏輯分類
客戶端關於證書的處理邏輯,按照安全等級可做如下分類:
本文所述的使用frida繞過ssl pinning即通過hook注入方式篡改鎖定邏輯,可破解後三種情況。
三、ssl pinning原理
證書鎖定(SSL/TLS Pinning)即將服務器提供的SSL/TLS證書內置到APP客戶端中,當客戶端發請求時,通過比對內置的證書和服務器端證書的內容,以確定這個連接的合法性。
所以,ssl pinning需要開發人員將APP代碼內置僅接受指定CA或域名的證書,而不接受操作系統或瀏覽器內置的CA根證書對應的任何證書,通過這種授權方式,保障了APP與服務端通信的唯一性和安全性。但是CA簽發證書都存在有效期問題,所以缺點是在證書續期後需要將證書重新內置到APP中。
四、frida繞過安卓ssl pinning
那麼,假設一個APP使用了ssl pinning,不信任任何系統和用戶證書,整個通信過程就真的安全了嗎,安全測試人員就完全沒辦法使用bp抓包來測試服務端業務邏輯安全了嗎。
答案當然是否定的,我們可以使用VirtualXposed+JustTrustMe在安卓手機不需root條件下,禁用客戶端的SSL 證書檢查,但是這種方法現在對很多APP也不奏效了。
因此,本文推薦使用frida繞過安卓APP的ssl pinning機制。我們想要實現的效果就是繞過安卓APP的ssl pinning機制,把包發給我們電腦的bp(說白了就是要實現對APP的中間人攻擊),以進行服務端安全測試。
1. 繞過原理
- 從安卓手機中加載我們自己的 CA證書
- 創建包含我們信任CA證書的KeyStore
- 創建TrustManager,讓它信任我們KeyStore中的CA證書
當安卓APP初始化SSLContext時,我們使用frida劫持SSLContext.init方法,使用我們自己創建的TrustManager , 把它作爲實參傳入SSLContext.init方法的第二個參數( SSLContext.init(KeyManager,TrustManager,SecuRandom) 。這樣我們就使APP信任我們的CA了。
以上邏輯都是通過一個js注入腳本實現的。
2. 使用工具
Frida
Frida就是一個讓你可以注入腳本到APP中的工具,從而修改APP的行爲,並實時的進行動態測試。
github鏈接:https://github.com/frida/frida
本文基於python 3.7版本(推薦使用python3版本運行frida),frida 12.9.4版本。
root設備/模擬器
這邊推薦大家某寶買個谷歌nexus安卓手機,可以很方便刷機、root設備和進行安全測試,而且也不貴,就兩三百,由於需要注入腳本,所以手機必須是root過的。
模擬器可使用genymotion。
本文是基於nexus 6p安卓機,可root。
adb
adb工具對於移動端安全測試還是很重要的,建議大家裝個android studio安卓開發環境,裝完後自帶adb。
注入腳本
注入腳本我們使用某國外大神寫的基於js的注入腳本,地址在:https://techblog.mediaservice.net/wp-content/uploads/2017/07/frida-android-repinning_sa-1.js
3. 繞過過程
0x01 安裝Frida
Frida需要在手機上安裝服務端,電腦上安裝客戶端。
- 電腦安裝客戶端,使用阿里源加速
pip3 install Frida -i https://mirrors.aliyun.com/pypi/simple/
pip3 install frida-tools -i https://mirrors.aliyun.com/pypi/simple/
- 手機usb連接電腦,adb連接成功後輸入
adb shell getprop ro.product.cpu.abi
,查看手機arch 版本,然後去https://github.com/frida/frida/releases下載對應的frida 服務端版本到電腦上
- 在電腦上解壓.xz結尾的壓縮文件,得到可執行文件
- 通過adb命令將剛剛解壓可執行的文件push到安卓指定目錄:adb push C:\xxx\frida-server /data/local/tmp
adb shell chmod 777 /data/local/tmp/frida-server
給可執行文件權限
0x02 準備burpsuite證書
爲了能攔截流量,frida要訪問我們的Burpsuite CA證書,因此通過bp下載其證書,並將其重命名爲cert-der.crt
,使用adb push C:\xxx\cert-der.crt /data/local/tmp
命令同樣將其push到手機相同目錄
0x03 準備注入腳本
將以下代碼保存爲在電腦上,命名爲fridascript.js
/*
Android SSL Re-pinning frida script v0.2 030417-pier
$ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt
$ frida -U -f it.app.mobile -l frida-android-repinning.js --no-pause
https://techblog.mediaservice.net/2017/07/universal-android-ssl-pinning-bypass-with-frida/
UPDATE 20191605: Fixed undeclared var. Thanks to @oleavr and @ehsanpc9999 !
*/
setTimeout(function(){
Java.perform(function (){
console.log("");
console.log("[.] Cert Pinning Bypass/Re-Pinning");
var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
var FileInputStream = Java.use("java.io.FileInputStream");
var BufferedInputStream = Java.use("java.io.BufferedInputStream");
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var KeyStore = Java.use("java.security.KeyStore");
var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
// Load CAs from an InputStream
console.log("[+] Loading our CA...")
var cf = CertificateFactory.getInstance("X.509");
try {
var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");
}
catch(err) {
console.log("[o] " + err);
}
var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
var ca = cf.generateCertificate(bufferedInputStream);
bufferedInputStream.close();
var certInfo = Java.cast(ca, X509Certificate);
console.log("[o] Our CA Info: " + certInfo.getSubjectDN());
// Create a KeyStore containing our trusted CAs
console.log("[+] Creating a KeyStore for our CA...");
var keyStoreType = KeyStore.getDefaultType();
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
console.log("[+] Our TrustManager is ready...");
console.log("[+] Hijacking SSLContext methods now...")
console.log("[-] Waiting for the app to invoke SSLContext.init()...")
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {
console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
console.log("[+] SSLContext initialized with our custom TrustManager!");
}
});
},0);
0x04 利用腳本注入
- 在手機上運行Frida服務端
adb shell
su
/data/local/tmp/frida-server &
2. 通過frida-ps -U
命令找到你要注入的正在運行APP的package name,如果知道的話,也不用事先找
3. 最後運行frida -U -f com.xxx.xxx-l C:\xxx\fridascript.js --no-paus
命令,將腳本注入到原生應用程序中。手機別忘了設置代理,指向電腦的bp監聽端口,bp即可截取到通信過程的包
五、後記
- 是否能繞過所有APP的ssl pinning機制
安全其實是木桶原理,一些銀行類APP,安全防護機制比較健全,會進行注入的檢測,所以無法使用Frida進行注入。但是本文敘述的方法適用大部分APP。 - 是否能抓到所有通信包
需要看具體APP業務邏輯,我能確認的是現在有一些APP的重要接口或者新接口會走socket通信,目前對於socket通信的包,還沒有比較好的截取辦法。