如何绕过Android网络安全配置
idql5231
7年前
<p>Android Nougat(Android 7)引入了一种名叫网络安全配置(Network Security Configuration)的新型安全功能,这种新功能可以允许Android开发者们在无需修改App代码的情况下自定义他们的网络安全设置。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/dce269fd72dde85bf1a926ceaaf2e039.jpg"></p> <p>但是这种功能将有可能影响Android移动端应用的安全测评。如果需要拦截HTTPS流量,那么就必须安装代理证书,而且还必须安装在“用户证书”之中,而默认情况下这类证书是不被信任的。</p> <p>接下来,我们将给大家介绍这种新机制的运行模式,以及如何通过重新编译或运行时钩子机制来修改这种新型安全机制的默认行为。</p> <h2>开发者如何使用该功能</h2> <p>为了修改默认配置,我们需要在resources目录中创建一个XML文件来指定自定义配置信息。下面给出的是一份配置文件样本,代码给应用程序的所有HTTPS链接配置了用户证书:</p> <pre> <code class="language-java"><?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></code></pre> <p>除此之外,该文件还需要在AndroidManifest文件中进行引用,即在application标签中的android:networkSecurityConfig参数中指定:</p> <pre> <code class="language-java"><?xml version="1.0"encoding="utf-8"?> <manifest ... > <applicationandroid:networkSecurityConfig="@xml/network_security_config" ... > ... </application> </manifest></code></pre> <h2>渗透测试人员如何绕过该功能</h2> <h3>重新编译</h3> <p>如果待测试的应用程序运行在Android 7以及更高版本的Android平台中(或者targetSdkVersion键配置为版本24或更高),应用程序很可能使用的是默认配置。因此,用户证书(例如代理CA证书)将不会被应用程序所信任。</p> <p>一般来说,修改默认配置的方法是在插入了XML内容(激活证书容器)之后再对应用程序进行重新编译。那么接下来,我们就要使用apktool来对应用程序进行修改了。</p> <p>首先,我们要做的就是使用apktool来对应用程序进行反编译。完成之后,我们还需要在resources目录中创建一个XML文件并修改AndroidManifest.xml文件中的相关参数(指向网络安全配置文件)。此时,我们就可以再次使用apktool来对应用程序进行重新编译,然后使用jarsigner工具来对生成的APK文件签名。</p> <p>当我们使用任意证书完成对APK文件的重新签名后,我们就可以使用adb来将其安装到手机之中了。如果手机经过配置后可以通过中间代理(例如Burp Suite)来发送流量,那么只要手机系统中安装了CA证书,我们就可以拦截HTTPS流量了。</p> <h3>运行时钩子</h3> <p>但是在某些情况下,刚才所介绍的方法也许是不可行的。比如说,如果应用程序使用了shareId来跟其他应用程序共享同一ID,而我们又需要直接访问其数据的话,那么这两个应用程序必须使用同一份证书来进行签名。如果应用程序经过了重新编译和重新签名之后,那这个保护功能也就多余了,而且我们也不可能再使用开发者之前的初始证书来对修改后的APK进行签名。</p> <p>对于这种场景,我们就可以使用动态构造技术了,因为这种方法可以允许我们在运行时对程序的行为进行修改而无须修改应用程序的代码。为了实现这种操作,我们需要创建一个Frida脚本来调整应用程序(目标SDK版本>=24)网络安全配置的默认行为。</p> <p>android.security.net.config包实现了网络安全配置模块,其主类ManifestConfigSource可以加载XML文件中自定义的配置信息,如果resources文件不存在的话,它将会加载默认配置。相关代码如下所示:</p> <pre> <code class="language-java">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; } } . . . }</code></pre> <p>DefaultConfigSource类是ManifestConfigSource类中定义的一个私类,如果没有使用XML文件来修改配置信息的话,系统将会默认使用这个类:</p> <pre> <code class="language-java">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; } } }</code></pre> <p>请大家看看这个类的构造器,它可以接收三个参数,其中一个就是应用程序的目标SDK版本。这个值可以使用getDefaultBuilder()方法来构造NetworkSecurityConfig类。在最后一段代码中,如果targetSdkVersion的值小于或等于23(Android Marshmallow,即Android 6.0),代码将会加载用户证书。</p> <pre> <code class="language-java">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; } ...</code></pre> <p>接下来,我们需要使用一个Frida脚本来挂钩DefaultConfigSource类的构造器,并修改其中的targetSdkVersion值。除此之外,这个脚本还需要挂钩getDefaultBuilder()方法来确保这个值已经被成功修改了。</p> <pre> <code class="language-java">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); }; });</code></pre> <p>现在,在上面给出的Frida脚本的帮助下,我们可以使用类似Burp Suite之类的HTTP代理来拦截应用程序(所有目标SDK版本>=24的应用程序)的网络流量。</p> <pre> <code class="language-java">$ frida -U -l ntc.js -f<package_name> --no-pause</code></pre> <p>如果你对本文所介绍的内容有任何疑问或者反馈,你可以直接通过推ter与本文作者( <a href="/misc/goto?guid=4959755562082802685" rel="nofollow,noindex">@AdriVillaB</a> )直接联系。</p> <h2>参考资料</h2> <p>[1] <a href="/misc/goto?guid=4959755562174917751" rel="nofollow,noindex">https://developer.android.com/training/articles/security-config.html?hl=en</a></p> <p>[2] <a href="/misc/goto?guid=4959654904389388747" rel="nofollow,noindex">https://ibotpeaches.github.io/Apktool/</a></p> <p>[3] <a href="/misc/goto?guid=4959755562308353055" rel="nofollow,noindex">https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/security/net/config</a></p> <p> </p> <p> </p> <p>来自:http://www.freebuf.com/articles/system/154420.html</p> <p> </p>