1.前言
在日常滲透過程中我們經(jīng)常會遇到瓶頸無處下手,這個時候如果攻擊者從APP進(jìn)行突破,往往會有很多驚喜。但是目前市場上的APP都會為防止別人惡意盜取和惡意篡改進(jìn)行一些保護(hù)措施,比如模擬器檢測、root檢測、APK加固、代碼混淆、代碼反調(diào)試、反脫殼、簽名校驗
等等對抗機(jī)制。
而測試人員對APP進(jìn)行滲透的首步操作通常就是上burp或者Charles這類抓包工具進(jìn)行抓包,查看請求記錄里的域名及鏈接地址是否可以進(jìn)一步利用,但是如果遇到一些APP出現(xiàn)證書報錯或者抓不到包的情況該怎么辦,讀過本篇文章之后,相信你會擁有一些新的解決方案和思考。
2.數(shù)字證書
我們都知道http協(xié)議傳輸?shù)氖敲魑男畔ⅲ强梢灾苯硬东@的,從而造成了數(shù)據(jù)泄露。為了防止中間人的攔截,出現(xiàn)了HTTPS加密機(jī)制。在HTTPS中,使用了證書+數(shù)字簽名
解決了這個問題。
此篇的重點在于如何應(yīng)對APP的抓包對抗。
總結(jié)的HTTPS加密機(jī)制如下:
- 數(shù)字簽名是發(fā)送方的明文經(jīng)歷了兩次加密得到的兩個東西組成,一個是hash ,一個是經(jīng)過私鑰加密。
- 數(shù)字證書就是明文+數(shù)字簽名。但是數(shù)字證書中的內(nèi)容遠(yuǎn)不止這倆,還包括了權(quán)威機(jī)構(gòu)的信息,服務(wù)器的域名,最重要的是有簽名的計算方法,不然用公鑰進(jìn)行解密之后的hash,如何與加密明文進(jìn)行對比呢,還有證書中還包括公鑰,公鑰用于發(fā)放給請求證書的客戶端。
- HTTPS就是使用SSL/TLS協(xié)議進(jìn)行加密傳輸,讓客戶端拿到服務(wù)器的公鑰,然后客戶端隨機(jī)生成一個對稱加密的秘鑰,使用公鑰加密,傳輸給服務(wù)端,后續(xù)的所有信息都通過該對稱秘鑰進(jìn)行加密解密,完成整個HTTPS的流程。
3.https抓包
【一一幫助安全學(xué)習(xí)一一】
①網(wǎng)絡(luò)安全學(xué)習(xí)路線
②20份滲透測試電子書
③安全攻防357頁筆記
④50份安全攻防面試指南
⑤安全紅隊滲透工具包
⑥網(wǎng)絡(luò)安全必備書籍
⑦100個漏洞實戰(zhàn)案例
⑧安全大廠內(nèi)部教程
導(dǎo)入用戶證書
在第一次使用burp時,都會有這么一步,將burp的證書導(dǎo)出,添加進(jìn)瀏覽器 【受信任的根證書頒發(fā)機(jī)構(gòu)】中去,這樣就會信任burp發(fā)來的請求包,也就可以請求數(shù)據(jù)進(jìn)行修改。我們對APP抓包,也同樣要將burp證書安裝到系統(tǒng)證書中去,一般從【SD卡安裝】的證書會存放在用戶信任的憑據(jù)下
但是,在Android 7.0以前,應(yīng)用默認(rèn)會信任系統(tǒng)證書和用戶證書,Android 7.0開始,默認(rèn)只信任系統(tǒng)證書。
所以如果你的手機(jī)是處于Android7.0以上版本的話,并且在沒有綁定SSL證書的情況下,也會抓不到包,從安卓開發(fā)的角度可以很清楚的看到這一點。
下圖是我將burp證書安裝到Android7.1.2的用戶證書下,使用okhttp對https://ttt.com
進(jìn)行請求的結(jié)果。由于ttt.com的SSL證書是自簽名證書,而自簽名證書是不被系統(tǒng)默認(rèn)信任的,所以需要先將ttt.com的自簽名證書添加到系統(tǒng)證書中才可以訪問。
自簽名證書的生成如下圖所示:
- 系統(tǒng)證書路徑:
/system/etc/security/cacerts/
- 用戶證書路徑:
/data/misc/user/0/cacerts-added/
移動到系統(tǒng)根證書路徑的方法:
1、導(dǎo)出burp.der
2、使用openssl更改證書格式,先將burp證書的der格式轉(zhuǎn)成pem,再獲取證書的hash
openssl x509 -inform DER -in burp.der -out burp.pem
openssl x509 -inform PEM -subject_hash_old -in burp.pem
3.移動到系統(tǒng)根證書目錄路徑下
Android根證書目錄都是以pem證書的hash值+.0格式,所以要將剛才生成的pem改名為xxxx.0
mv burp.pem a5ba575.0
由于系統(tǒng)讀寫權(quán)限問題,不一定能直接上傳到system目錄
adb push 9a5ba575.0 /sdcard
adb shell
mount -o remount,rw /system
cp /sdcard/9a5ba575.0 /system/etc/security/cacerts/
chmod 644 /system/etc/security/cacerts/9a5ba575.0
移動完成之后,再打開【設(shè)置】-【安全】-【信任的憑據(jù)】驗證一下
這時可以在Android7.0以上版本正常訪問https://ttt.com
了,其他抓包工具同理即可。
證書有效期過長
還有一種情況是,導(dǎo)入到系統(tǒng)證書仍抓不到包,并且瀏覽器會報NET::ERR_CERT_VALIDITY_TOO_LONG錯誤。
原因是chrome從2018年開始只信任有效期少于825天(27個月)的證書,而burp證書有效期過長。
解決方案是自己做一個低于27個月的root證書導(dǎo)入burp,再通過burp重新導(dǎo)出證書并放入到系統(tǒng)證書路徑下。
openssl genrsa -out key.pem 3072 -nodes
openssl req -new -x509 -key key.pem -sha256 -config openssl.cnf -out cert.pem -days 730 -subj "/C=JP/ST=/L=/O=m4bln/CN=MY CA"
openssl pkcs12 -export -inkey key.pem -in cert.pem -out cert_and_key.pfx
把cert_and_key.pfx導(dǎo)入burp
目前還沒遇到過這種情況,但是如果遇到了這種問題要知道怎么解決。
以上兩種方法都是僅依靠了系統(tǒng)校驗證書的方式進(jìn)行抓包,APP在整個請求HTTPS的請求過程時還并未進(jìn)行證書校驗,和在普通的瀏覽器中訪問并無區(qū)別,只是要將想要被信任的證書放入系統(tǒng)證書路徑內(nèi)。
4.SSLPinning
對于像ttt.com這種自簽名的免費證書,不需要CA權(quán)威認(rèn)證的證書,大多數(shù)APP開發(fā)商都會使用。那么如果在安卓開發(fā)的過程中,將證書的驗證邏輯放在APP內(nèi)部,與系統(tǒng)和瀏覽器毫無相關(guān),這時再想將burp證書導(dǎo)入系統(tǒng)受信任路徑下也于事無補(bǔ)了。
APP自己校驗證書,分為兩種,一種是將驗證邏輯也在代碼中,一種是寫在安卓7.0之后才有的network-security-config
中。
驗證是方式也有兩種,一種是驗證證書公鑰的hash值,一種是直接驗證證書的公鑰文件。
這種通過APP自身的驗證方式就叫做證書綁定(也叫Certificate Pinning
或SSL Pinning
)。
那么如何去判斷一個APP是否使用了證書綁定呢?首先拿到apk文件,用apktool工具進(jìn)行反編譯,查看敏感文件
apktool d -s <file.apk> -o <outdir>
開發(fā)人員經(jīng)常會將網(wǎng)絡(luò)配置的相關(guān)文件保存到指定位置,如下圖就指定在了xml目錄下。
所以在反編譯后的res/xml目錄下會有一個network_security_config.xml
文件,打開看到標(biāo)簽,說明使用了證書綁定機(jī)制。
在配置文件中檢驗的兩種方法
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<!--允許http訪問-->
<base-config cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration" />
<!--證書校驗-->
<domain-config>
<domain includeSubdomains="true">www.ttt.com</domain>
<trust-anchors>
<certificates src="@raw/ttt"/>
</trust-anchors>
</domain-config>
<!--公鑰校驗-->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">ttt.com</domain>
<!--利用xml校驗證書公鑰的hash值-->
<pin-set expiration="2099-01-01"
tools:ignore="MissingBackupPin">
<pin digest="SHA-256">7VMdvZE3PGbxb0Pgf1PlCp+MI8KZ2ZC5psM8TIylNDA=</pin>
</pin-set>
<!--利用xml校驗證書的公鑰文件-->
<trust-anchors>
<certificates src="@raw/ttt"/>
</trust-anchors>
</domain-config>
</network-security-config>
這兩種校驗機(jī)制出現(xiàn)一種即可,從代碼中可以看出,ttt.com就是安卓自己要校驗綁定的域名。
如果只是在這個文件進(jìn)行校驗,有兩種解決方案:一是直接將文件中校驗的部分或注釋掉,再重新打包和簽名即可,但是這過程又有些麻煩,并不是上上策,如果遇到了不能重打包的apk就尷尬了。。。二是最常用的也是最好用的frida來hook關(guān)鍵函數(shù)進(jìn)行繞過,后面會講解。當(dāng)然有些人會直接在真機(jī)或者模擬器上安裝xposed模塊,但是我個人覺得每次使用都要軟重啟,可能還會造成卡機(jī),所以感覺還是使用frida最方便。
在代碼中檢驗的兩種方法
1.利用代碼校驗證書的公鑰hash
String hostname = "www.ttt.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/7VMdvZE3PGbxb0Pgf1PlCp+MI8KZ2ZC5psM8TIylNDA=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}).build();
2.利用代碼校驗證書的公鑰證書文件
// 獲取證書輸入流
InputStream openRawResource = getApplicationContext().getResources().openRawResource(R.raw.ttt);
Certificate ca = CertificateFactory.getInstance("X.509").generateCertificate(openRawResource);
// 創(chuàng)建 Keystore 包含我們的證書
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// 創(chuàng)建一個 TrustManager 僅把 Keystore 中的證書 作為信任的錨點
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // 建議不要使用自己實現(xiàn)的X509TrustManager,而是使用默認(rèn)的X509TrustManager
trustManagerFactory.init(keyStore);
// 用 TrustManager 初始化一個 SSLContext
sslContext = SSLContext.getInstance("TLS"); //定義:public static SSLContext sslContext = null;
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(),
(X509TrustManager) trustManagerFactory.getTrustManagers()[0] )
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}).build();
通過frida進(jìn)行hook,這種繞過的腳本也很多,比較熟悉的有JustTrustMe和DroidSSLUnpinning
,他們的底層原理都是一樣的,通過hook關(guān)鍵的驗證函數(shù),進(jìn)行邏輯繞過。
frida的安裝過程就不詳細(xì)講解了,網(wǎng)上很多教程。這里我使用的是frida 12.8.0 + frida-tools=5.3.0
這里我使用的hook.js的腳本如下:
/* Android ssl certificate pinning bypass script for various methods
by Maurizio Siddu modify by Ch3nYe
Run with:
frida -U -f [APP_ID] -l frida_multiple_unpinning.js --no-pause
*/
setTimeout(function() {
Java.perform(function () {
console.log('');
console.log('======');
console.log('[#] Android Bypass for various Certificate Pinning methods [#]');
console.log('======');
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var SSLContext = Java.use('javax.net.ssl.SSLContext');
// TrustManager (Android < 7) //
var TrustManager = Java.registerClass({
// Implement a custom TrustManager
name: 'dev.asd.test.TrustManager',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function (chain, authType) {},
checkServerTrusted: function (chain, authType) {},
getAcceptedIssuers: function () {return []; }
}
});
// Prepare the TrustManager array to pass to SSLContext.init()
var TrustManagers = [TrustManager.$new()];
// Get a handle on the init() on the SSLContext class
var SSLContext_init = SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');
try {
// Override the init method, specifying the custom TrustManager
SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
console.log('[+] Bypassing Trustmanager (Android < 7) request');
SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
};
} catch (err) {
console.log('[-] TrustManager (Android < 7) pinner not found');
//console.log(err);
}
// OkHTTPv3 (quadruple bypass) //
/
try {
// Bypass OkHTTPv3 {1}
var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner');
okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function (a, b) {
console.log('[+] Bypassing OkHTTPv3 {1}: ' + a);
return true;
};
} catch (err) {
console.log('[-] OkHTTPv3 {1} pinner not found');
//console.log(err);
}
try {
// Bypass OkHTTPv3 {2}
// This method of CertificatePinner.check could be found in some old Android app
var okhttp3_Activity_2 = Java.use('okhttp3.CertificatePinner');
okhttp3_Activity_2.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function (a, b) {
console.log('[+] Bypassing OkHTTPv3 {2}: ' + a);
return true;
};
} catch (err) {
console.log('[-] OkHTTPv3 {2} pinner not found');
//console.log(err);
}
try {
// Bypass OkHTTPv3 {3}
var okhttp3_Activity_3 = Java.use('okhttp3.CertificatePinner');
okhttp3_Activity_3.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function (a, b) {
console.log('[+] Bypassing OkHTTPv3 {3}: ' + a);
return true;
};
} catch(err) {
console.log('[-] OkHTTPv3 {3} pinner not found');
//console.log(err);
}
try {
// Bypass OkHTTPv3 {4}
var okhttp3_Activity_4 = Java.use('okhttp3.CertificatePinner');
okhttp3_Activity_4[''].implementation = function (a, b) {
console.log('[+] Bypassing OkHTTPv3 {4}: ' + a);
};
} catch(err) {
console.log('[-] OkHTTPv3 {4} pinner not found');
//console.log(err);
}
// Trustkit (triple bypass) //
//
try {
// Bypass Trustkit {1}
var trustkit_Activity_1 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier');
trustkit_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) {
console.log('[+] Bypassing Trustkit {1}: ' + a);
return true;
};
} catch (err) {
console.log('[-] Trustkit {1} pinner not found');
//console.log(err);
}
try {
// Bypass Trustkit {2}
var trustkit_Activity_2 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier');
trustkit_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) {
console.log('[+] Bypassing Trustkit {2}: ' + a);
return true;
};
} catch (err) {
console.log('[-] Trustkit {2} pinner not found');
//console.log(err);
}
try {
// Bypass Trustkit {3}
var trustkit_PinningTrustManager = Java.use('com.datatheorem.android.trustkit.pinning.PinningTrustManager');
trustkit_PinningTrustManager.checkServerTrusted.implementation = function () {
console.log('[+] Bypassing Trustkit {3}');
};
} catch (err) {
console.log('[-] Trustkit {3} pinner not found');
//console.log(err);
}
// TrustManagerImpl (Android > 7) //
try {
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
console.log('[+] Bypassing TrustManagerImpl (Android > 7): ' + host);
return untrustedChain;
};
} catch (err) {
console.log('[-] TrustManagerImpl (Android > 7) pinner not found');
//console.log(err);
}
// Appcelerator Titanium //
///
try {
var appcelerator_PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');
appcelerator_PinningTrustManager.checkServerTrusted.implementation = function () {
console.log('[+] Bypassing Appcelerator PinningTrustManager');
};
} catch (err) {
console.log('[-] Appcelerator PinningTrustManager pinner not found');
//console.log(err);
}
// OpenSSLSocketImpl Conscrypt //
/
try {
var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certRefs, JavaObject, authMethod) {
console.log('[+] Bypassing OpenSSLSocketImpl Conscrypt');
};
} catch (err) {
console.log('[-] OpenSSLSocketImpl Conscrypt pinner not found');
//console.log(err);
}
// OpenSSLEngineSocketImpl Conscrypt //
///
try {
var OpenSSLEngineSocketImpl_Activity = Java.use('com.android.org.conscrypt.OpenSSLEngineSocketImpl');
OpenSSLSocketImpl_Activity.verifyCertificateChain.overload('[Ljava.lang.Long;', 'java.lang.String').implementation = function (a, b) {
console.log('[+] Bypassing OpenSSLEngineSocketImpl Conscrypt: ' + b);
};
} catch (err) {
console.log('[-] OpenSSLEngineSocketImpl Conscrypt pinner not found');
//console.log(err);
}
// OpenSSLSocketImpl Apache Harmony //
//
try {
var OpenSSLSocketImpl_Harmony = Java.use('org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl');
OpenSSLSocketImpl_Harmony.verifyCertificateChain.implementation = function (asn1DerEncodedCertificateChain, authMethod) {
console.log('[+] Bypassing OpenSSLSocketImpl Apache Harmony');
};
} catch (err) {
console.log('[-] OpenSSLSocketImpl Apache Harmony pinner not found');
//console.log(err);
}
// PhoneGap sslCertificateChecker (https://github.com/EddyVerbruggen/SSLCertificateChecker-PhoneGap-Plugin) //
//
try {
var phonegap_Activity = Java.use('nl.xservices.plugins.sslCertificateChecker');
phonegap_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function (a, b, c) {
console.log('[+] Bypassing PhoneGap sslCertificateChecker: ' + a);
return true;
};
} catch (err) {
console.log('[-] PhoneGap sslCertificateChecker pinner not found');
//console.log(err);
}
// IBM MobileFirst pinTrustedCertificatePublicKey (double bypass) //
try {
// Bypass IBM MobileFirst {1}
var WLClient_Activity_1 = Java.use('com.worklight.wlclient.api.WLClient');
WLClient_Activity_1.getInstance().pinTrustedCertificatePublicKey.overload('java.lang.String').implementation = function (cert) {
console.log('[+] Bypassing IBM MobileFirst pinTrustedCertificatePublicKey {1}: ' + cert);
return;
};
} catch (err) {
console.log('[-] IBM MobileFirst pinTrustedCertificatePublicKey {1} pinner not found');
//console.log(err);
}
try {
// Bypass IBM MobileFirst {2}
var WLClient_Activity_2 = Java.use('com.worklight.wlclient.api.WLClient');
WLClient_Activity_2.getInstance().pinTrustedCertificatePublicKey.overload('[Ljava.lang.String;').implementation = function (cert) {
console.log('[+] Bypassing IBM MobileFirst pinTrustedCertificatePublicKey {2}: ' + cert);
return;
};
} catch (err) {
console.log('[-] IBM MobileFirst pinTrustedCertificatePublicKey {2} pinner not found');
//console.log(err);
}
// IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass) //
///
try {
// Bypass IBM WorkLight {1}
var worklight_Activity_1 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
worklight_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSocket').implementation = function (a, b) {
console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {1}: ' + a);
return;
};
} catch (err) {
console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {1} pinner not found');
//console.log(err);
}
try {
// Bypass IBM WorkLight {2}
var worklight_Activity_2 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
worklight_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) {
console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {2}: ' + a);
return;
};
} catch (err) {
console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {2} pinner not found');
//console.log(err);
}
try {
// Bypass IBM WorkLight {3}
var worklight_Activity_3 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
worklight_Activity_3.verify.overload('java.lang.String', '[Ljava.lang.String;', '[Ljava.lang.String;').implementation = function (a, b) {
console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {3}: ' + a);
return;
};
} catch (err) {
console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {3} pinner not found');
//console.log(err);
}
try {
// Bypass IBM WorkLight {4}
var worklight_Activity_4 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning');
worklight_Activity_4.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) {
console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {4}: ' + a);
return true;
};
} catch (err) {
console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {4} pinner not found');
//console.log(err);
}
// Conscrypt CertPinManager //
//
try {
var conscrypt_CertPinManager_Activity = Java.use('com.android.org.conscrypt.CertPinManager');
conscrypt_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function (a, b) {
console.log('[+] Bypassing Conscrypt CertPinManager: ' + a);
return true;
};
} catch (err) {
console.log('[-] Conscrypt CertPinManager pinner not found');
//console.log(err);
}
// CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager //
///
try {
var cwac_CertPinManager_Activity = Java.use('com.commonsware.cwac.netsecurity.conscrypt.CertPinManager');
cwac_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function (a, b) {
console.log('[+] Bypassing CWAC-Netsecurity CertPinManager: ' + a);
return true;
};
} catch (err) {
console.log('[-] CWAC-Netsecurity CertPinManager pinner not found');
//console.log(err);
}
// Worklight Androidgap WLCertificatePinningPlugin //
/
try {
var androidgap_WLCertificatePinningPlugin_Activity = Java.use('com.worklight.androidgap.plugin.WLCertificatePinningPlugin');
androidgap_WLCertificatePinningPlugin_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function (a, b, c) {
console.log('[+] Bypassing Worklight Androidgap WLCertificatePinningPlugin: ' + a);
return true;
};
} catch (err) {
console.log('[-] Worklight Androidgap WLCertificatePinningPlugin pinner not found');
//console.log(err);
}
// Netty FingerprintTrustManagerFactory //
//
try {
var netty_FingerprintTrustManagerFactory = Java.use('io.netty.handler.ssl.util.FingerprintTrustManagerFactory');
//NOTE: sometimes this below implementation could be useful
//var netty_FingerprintTrustManagerFactory = Java.use('org.jboss.netty.handler.ssl.util.FingerprintTrustManagerFactory');
netty_FingerprintTrustManagerFactory.checkTrusted.implementation = function (type, chain) {
console.log('[+] Bypassing Netty FingerprintTrustManagerFactory');
};
} catch (err) {
console.log('[-] Netty FingerprintTrustManagerFactory pinner not found');
//console.log(err);
}
// Squareup CertificatePinner [OkHTTP<v3] (double bypass) //
try {
// Bypass Squareup CertificatePinner {1}
var Squareup_CertificatePinner_Activity_1 = Java.use('com.squareup.okhttp.CertificatePinner');
Squareup_CertificatePinner_Activity_1.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function (a, b) {
console.log('[+] Bypassing Squareup CertificatePinner {1}: ' + a);
return;
};
} catch (err) {
console.log('[-] Squareup CertificatePinner {1} pinner not found');
//console.log(err);
}
try {
// Bypass Squareup CertificatePinner {2}
var Squareup_CertificatePinner_Activity_2 = Java.use('com.squareup.okhttp.CertificatePinner');
Squareup_CertificatePinner_Activity_2.check.overload('java.lang.String', 'java.util.List').implementation = function (a, b) {
console.log('[+] Bypassing Squareup CertificatePinner {2}: ' + a);
return;
};
} catch (err) {
console.log('[-] Squareup CertificatePinner {2} pinner not found');
//console.log(err);
}
// Squareup OkHostnameVerifier [OkHTTP v3] (double bypass) //
/
try {
// Bypass Squareup OkHostnameVerifier {1}
var Squareup_OkHostnameVerifier_Activity_1 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier');
Squareup_OkHostnameVerifier_Activity_1.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) {
console.log('[+] Bypassing Squareup OkHostnameVerifier {1}: ' + a);
return true;
};
} catch (err) {
console.log('[-] Squareup OkHostnameVerifier pinner not found');
//console.log(err);
}
try {
// Bypass Squareup OkHostnameVerifier {2}
var Squareup_OkHostnameVerifier_Activity_2 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier');
Squareup_OkHostnameVerifier_Activity_2.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) {
console.log('[+] Bypassing Squareup OkHostnameVerifier {2}: ' + a);
return true;
};
} catch (err) {
console.log('[-] Squareup OkHostnameVerifier pinner not found');
//console.log(err);
}
// Android WebViewClient (double bypass) //
///
try {
// Bypass WebViewClient {1} (deprecated from Android 6)
var AndroidWebViewClient_Activity_1 = Java.use('android.webkit.WebViewClient');
AndroidWebViewClient_Activity_1.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function (obj1, obj2, obj3) {
console.log('[+] Bypassing Android WebViewClient {1}');
};
} catch (err) {
console.log('[-] Android WebViewClient {1} pinner not found');
//console.log(err)
}
try {
// Bypass WebViewClient {2}
var AndroidWebViewClient_Activity_2 = Java.use('android.webkit.WebViewClient');
AndroidWebViewClient_Activity_2.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function (obj1, obj2, obj3) {
console.log('[+] Bypassing Android WebViewClient {2}');
};
} catch (err) {
console.log('[-] Android WebViewClient {2} pinner not found');
//console.log(err)
}
// Apache Cordova WebViewClient //
//
try {
var CordovaWebViewClient_Activity = Java.use('org.apache.cordova.CordovaWebViewClient');
CordovaWebViewClient_Activity.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function (obj1, obj2, obj3) {
console.log('[+] Bypassing Apache Cordova WebViewClient');
obj3.proceed();
};
} catch (err) {
console.log('[-] Apache Cordova WebViewClient pinner not found');
//console.log(err);
}
// Boye AbstractVerifier //
///
try {
var boye_AbstractVerifier = Java.use('ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier');
boye_AbstractVerifier.verify.implementation = function (host, ssl) {
console.log('[+] Bypassing Boye AbstractVerifier: ' + host);
};
} catch (err) {
console.log('[-] Boye AbstractVerifier pinner not found');
//console.log(err);
}
});
}, 0);
啟動frida進(jìn)行hook指定APP的包名
frida -U -f com.example.safehttps -l hook.js --no-pause
可以看到開啟burp抓包成功。
5.開啟雙向校驗
雙向校驗顧名思義也就是服務(wù)器也要對客戶端進(jìn)行證書校驗,在剛才客戶端校驗服務(wù)端的基礎(chǔ)上添加一直被校驗的邏輯在里面。
首先ttt.com所在的nginx服務(wù)器要開啟雙向認(rèn)證
開啟客戶端的校驗后,在瀏覽器進(jìn)行訪問,會發(fā)現(xiàn)返回400,沒有被請求的SSL證書發(fā)送,是因為瀏覽器正常請求不會攜帶證書信息去請求ttt.com
那么如何攜帶客戶端的證書,就要利用burp來操作,將ttt.com的證書添加到TLS客戶證書
這時再訪問
在APP中進(jìn)行綁定客戶端的證書文件,一般是p12格式文件,會放在assets目錄下或者raw目錄下,client.p12會有一個密鑰內(nèi)置在代碼中,需要找到才能添加進(jìn)burp中。
這個在反編譯后的目錄下也能找到,通常在assets或者res/raw目錄下查找,但是再導(dǎo)入burp這一步是需要證書密碼的,比如上圖能明顯看到密碼是123456,但是找不到證書密碼怎么辦,可以看一下目錄下是否存在lib文件夾,如果存在的話極大可能是將密碼寫進(jìn)so層了,這就需要你會IDA反匯編獲取證書密鑰了,這部分在此不詳細(xì)闡述,感興趣的朋友可以先去研究一下。
APP雙向校驗驗證
服務(wù)器校驗客戶端的證書ClientSSLSocketFactory,服務(wù)端將客戶端的證書進(jìn)行綁定。
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
trustManager = (X509TrustManager) trustManagers[0];
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(Objects.requireNonNull(ClientSSLSocketFactory.getSocketFactory(getApplicationContext())), Objects.requireNonNull(trustManager))
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//強(qiáng)行返回true 即驗證成功
return true;
}
}).build();
public class ClientSSLSocketFactory {
private static final String KEY_STORE_PASSWORD = "123456"; // 證書密碼
private static InputStream client_input;
public static SSLSocketFactory getSocketFactory(Context context) {
try {
//客戶端證書
client_input = context.getResources().getAssets().open("client.p12");
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(client_input, KEY_STORE_PASSWORD.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
client_input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
雙向校驗時,要將SSLPinning與服務(wù)器校驗客戶端證書的模塊同時開啟。
對其就行繞過,需要先啟動Frida進(jìn)行hook,然后勾選上客戶端證書,才可以請求成功。如下圖所示:
沒勾選就會請求失敗,出現(xiàn)400 Bad Request,和瀏覽器無代理時請求的結(jié)果一樣,如下圖:
此時burp代理日志顯示如下,SSL請求失敗
WebView證書校驗
webview也是一種請求方式,相當(dāng)于頁面的跳轉(zhuǎn)或嵌入,請求的結(jié)果會顯示在主屏幕上。
對于webview證書校驗,有些可以找到的腳本不一定有繞過,所以在使用的過程中要查看是否含有webview的關(guān)鍵信息
這里我使用的是DroidSSLUnpinning
使用效果如下:
此時的APP綁定場景為:OKhttp請求為雙向校驗,webview為SSLPinning校驗。所以在圖中的上面一行是okhttp的請求結(jié)果,下面的是webview的請求結(jié)果,此腳本雙向校驗仍然可以繞過。
如果說webview再添加了客戶端校驗,那么在反編譯apk后,需要找到webview訪問域名的證書密鑰,再安裝進(jìn)burp中即可。
6.ssl_logger通殺
至此,我們可以繞過證書綁定,抓APP發(fā)出的https包了,然而上述的證書解綁hook工具僅僅是通過hook了幾種綁定證書的API,不適用于新出現(xiàn)或者非主流的證書綁定技術(shù)。這時,就需要神器ssl_logger
ssl_logger是用來解密SSL流量的工具,也是一款基于frida的hook工具,通過hook libssl庫中的SSL_read、SSL_write等函數(shù)來實現(xiàn)流量解密,由于底層的實現(xiàn)會調(diào)用這幾個函數(shù)來封裝,所以可以直接解出流量數(shù)據(jù)。
r0capture是r0ysue大佬在其基礎(chǔ)上進(jìn)行改進(jìn)的一款工具。
由于ssl_logger是適用于MAC和linux操作系統(tǒng),所以我選擇在kali上進(jìn)行hook實現(xiàn)雙向校驗的app
打開抓到的1.pcap包
從圖中可以看出成功的獲取到了信息,是不是感覺這個工具特別神,但是看pcap的包總歸不如看burp的一目了然,這個工具就這一點不太友好。所以用哪個看你心情
7.eBPF hook 免CA證書
ecapture:eBPF HOOK uprobe實現(xiàn)的各種用戶態(tài)進(jìn)程的數(shù)據(jù)捕獲,無需改動原程序。這個工具也是通過hook了libssl
庫中SSL_write
、SSL_read
這兩個關(guān)鍵的SSL加密函數(shù)的返回值,拿到明文信息,通過ebpf map傳遞給用戶進(jìn)程。在APP中,如果遇到場景是burp抓包時出現(xiàn)證書報錯,我覺得可以嘗試一下用這個工具直接curl訪問進(jìn)行抓包。eBPF hook也是最近才發(fā)現(xiàn)的hook方法,值得我們?nèi)ド钊胩剿鳌?/p>
我這里用的是作者v0.1.3版本發(fā)布的工具,使用效果如下:
8.總結(jié)
在依靠系統(tǒng)或默認(rèn)瀏覽器校驗證書的情況下,導(dǎo)入burp證書為用戶證書是可以抓https包的
當(dāng)app支持的最小API為24(Android 7.0)或以上時,默認(rèn)情況下app只信任系統(tǒng)級別的證書,需要把burp變?yōu)橄到y(tǒng)證書
自簽名證書作為系統(tǒng)證書時,有效期最長不超過825天,用戶證書則沒有限制
開啟證書校驗的APP在使用burp抓包時會報certtificate_unknown等錯誤
使用frida hook繞過雙向證書校驗時,必須要將客戶端的p12文件導(dǎo)入burp中
一些腳本仍繞不過可以使用ssl_logger或者逆向代碼進(jìn)行分析驗證邏輯,再有針對性的繞過
p12文件的密鑰如果在so層,需要會用IDA進(jìn)行靜態(tài)分析lib下的so文件獲取關(guān)鍵密鑰文章來源:http://www.zghlxwxcb.cn/news/detail-419900.html
看完本篇文章之后,相信你再面對抓不到包的APP或者是https請求也不會手足無措了。文章來源地址http://www.zghlxwxcb.cn/news/detail-419900.html
到了這里,關(guān)于解決APP抓包問題【網(wǎng)絡(luò)安全】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!