如何繞過Android網絡安全配置
Android Nougat(Android 7)引入了一種名叫網絡安全配置(Network Security Configuration)的新型安全功能,這種新功能可以允許Android開發者們在無需修改App代碼的情況下自定義他們的網絡安全設置。
但是這種功能將有可能影響Android移動端應用的安全測評。如果需要攔截HTTPS流量,那么就必須安裝代理證書,而且還必須安裝在“用戶證書”之中,而默認情況下這類證書是不被信任的。
接下來,我們將給大家介紹這種新機制的運行模式,以及如何通過重新編譯或運行時鉤子機制來修改這種新型安全機制的默認行為。
開發者如何使用該功能
為了修改默認配置,我們需要在resources目錄中創建一個XML文件來指定自定義配置信息。下面給出的是一份配置文件樣本,代碼給應用程序的所有HTTPS鏈接配置了用戶證書:
<?xml version="1.0"encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</base-config>
</network-security-config>
除此之外,該文件還需要在AndroidManifest文件中進行引用,即在application標簽中的android:networkSecurityConfig參數中指定:
<?xml version="1.0"encoding="utf-8"?>
<manifest ... >
<applicationandroid:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
滲透測試人員如何繞過該功能
重新編譯
如果待測試的應用程序運行在Android 7以及更高版本的Android平臺中(或者targetSdkVersion鍵配置為版本24或更高),應用程序很可能使用的是默認配置。因此,用戶證書(例如代理CA證書)將不會被應用程序所信任。
一般來說,修改默認配置的方法是在插入了XML內容(激活證書容器)之后再對應用程序進行重新編譯。那么接下來,我們就要使用apktool來對應用程序進行修改了。
首先,我們要做的就是使用apktool來對應用程序進行反編譯。完成之后,我們還需要在resources目錄中創建一個XML文件并修改AndroidManifest.xml文件中的相關參數(指向網絡安全配置文件)。此時,我們就可以再次使用apktool來對應用程序進行重新編譯,然后使用jarsigner工具來對生成的APK文件簽名。
當我們使用任意證書完成對APK文件的重新簽名后,我們就可以使用adb來將其安裝到手機之中了。如果手機經過配置后可以通過中間代理(例如Burp Suite)來發送流量,那么只要手機系統中安裝了CA證書,我們就可以攔截HTTPS流量了。
運行時鉤子
但是在某些情況下,剛才所介紹的方法也許是不可行的。比如說,如果應用程序使用了shareId來跟其他應用程序共享同一ID,而我們又需要直接訪問其數據的話,那么這兩個應用程序必須使用同一份證書來進行簽名。如果應用程序經過了重新編譯和重新簽名之后,那這個保護功能也就多余了,而且我們也不可能再使用開發者之前的初始證書來對修改后的APK進行簽名。
對于這種場景,我們就可以使用動態構造技術了,因為這種方法可以允許我們在運行時對程序的行為進行修改而無須修改應用程序的代碼。為了實現這種操作,我們需要創建一個Frida腳本來調整應用程序(目標SDK版本>=24)網絡安全配置的默認行為。
android.security.net.config包實現了網絡安全配置模塊,其主類ManifestConfigSource可以加載XML文件中自定義的配置信息,如果resources文件不存在的話,它將會加載默認配置。相關代碼如下所示:
package android.security.net.config;
public class ManifestConfigSourceimplements ConfigSource {
. . .
private ConfigSource getConfigSource() {
synchronized (mLock) {
. . .
if (mConfigResourceId != 0) {
. . .
source = newXmlConfigSource(mContext, mConfigResourceId, debugBuild, mTargetSdkVersion,mTargetSandboxVesrsion);
} else {
. . .
source = new DefaultConfigSource(usesCleartextTraffic,mTargetSdkVersion, mTargetSandboxVesrsion);
}
mConfigSource = source;
return mConfigSource;
}
}
. . .
}
DefaultConfigSource類是ManifestConfigSource類中定義的一個私類,如果沒有使用XML文件來修改配置信息的話,系統將會默認使用這個類:
package android.security.net.config;
public class ManifestConfigSourceimplements ConfigSource {
...
private static final class DefaultConfigSource implements ConfigSource {
private final NetworkSecurityConfig mDefaultConfig;
public DefaultConfigSource(boolean usesCleartextTraffic, inttargetSdkVersion,
int targetSandboxVesrsion) {
mDefaultConfig =NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion,
targetSandboxVesrsion)
.setCleartextTrafficPermitted(usesCleartextTraffic)
.build();
}
@Override
public NetworkSecurityConfig getDefaultConfig() {
return mDefaultConfig;
}
@Override
public Set<Pair<Domain, NetworkSecurityConfig>>getPerDomainConfigs() {
return null;
}
}
}
請大家看看這個類的構造器,它可以接收三個參數,其中一個就是應用程序的目標SDK版本。這個值可以使用getDefaultBuilder()方法來構造NetworkSecurityConfig類。在最后一段代碼中,如果targetSdkVersion的值小于或等于23(Android Marshmallow,即Android 6.0),代碼將會加載用戶證書。
package android.security.net.config;
public final class NetworkSecurityConfig {
...
public static final Builder getDefaultBuilder(int targetSdkVersion, inttargetSandboxVesrsion) {
Builder builder = new Builder()
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
// System certificatestore, does not bypass static pins.
.addCertificatesEntryRef(
newCertificatesEntryRef(SystemCertificateSource.getInstance(), false));
final booleancleartextTrafficPermitted = targetSandboxVesrsion < 2;
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N andabove must opt in into trusting the user added certificate
// store.
if (targetSdkVersion <=Build.VERSION_CODES.M) {
// User certificate store,does not bypass static pins.
builder.addCertificatesEntryRef(
newCertificatesEntryRef(UserCertificateSource.getInstance(), false));
}
return builder;
}
...
接下來,我們需要使用一個Frida腳本來掛鉤DefaultConfigSource類的構造器,并修改其中的targetSdkVersion值。除此之外,這個腳本還需要掛鉤getDefaultBuilder()方法來確保這個值已經被成功修改了。
Java.perform(function(){
var ANDROID_VERSION_M = 23;
var DefaultConfigSource =Java.use("android.security.net.config.ManifestConfigSource$DefaultConfigSource");
var NetworkSecurityConfig = Java.use("android.security.net.config.NetworkSecurityConfig");
DefaultConfigSource.$init.overload("boolean","int").implementation = function(usesCleartextTraffic,targetSdkVersion){
console.log("[+] Modifying DefaultConfigSource constructor");
return this.$init.overload("boolean","int").call(this, usesCleartextTraffic, ANDROID_VERSION_M);
};
DefaultConfigSource.$init.overload("boolean", "int","int").implementation = function(usesCleartextTraffic,targetSdkVersion, targetSandboxVersion){
console.log("[+]Modifying DefaultConfigSource constructor");
return this.$init.overload("boolean", "int","int").call(this, usesCleartextTraffic, ANDROID_VERSION_M,targetSandboxVersion);
};
NetworkSecurityConfig.getDefaultBuilder.overload("int").implementation= function(targetSdkVersion){
console.log("[+] getDefaultBuilder original targetSdkVersion =>" + targetSdkVersion.toString());
return this.getDefaultBuilder.overload("int").call(this, ANDROID_VERSION_M);
};
NetworkSecurityConfig.getDefaultBuilder.overload("int","int").implementation = function(targetSdkVersion,targetSandboxVersion){
console.log("[+] getDefaultBuilder original targetSdkVersion =>" + targetSdkVersion.toString());
return this.getDefaultBuilder.overload("int","int").call(this, ANDROID_VERSION_M, targetSandboxVersion);
};
});
現在,在上面給出的Frida腳本的幫助下,我們可以使用類似Burp Suite之類的HTTP代理來攔截應用程序(所有目標SDK版本>=24的應用程序)的網絡流量。
$ frida -U -l ntc.js -f<package_name> --no-pause
如果你對本文所介紹的內容有任何疑問或者反饋,你可以直接通過推ter與本文作者( @AdriVillaB )直接聯系。
參考資料
[1] https://developer.android.com/training/articles/security-config.html?hl=en
[2] https://ibotpeaches.github.io/Apktool/
來自:http://www.freebuf.com/articles/system/154420.html