网站建设、公众号开发、微网站、微商城、小程序就找牛创网络 !

7*24小时服务专线: 152-150-65-006 023-68263070 扫描二维码加我微信 在线QQ

无线安全团结互助,让我们共同进步!

当前位置:主页 > 技术资讯 > 网络安全 > 无线安全 >

我们的优势: 10年相关行业经验,专业设计师量身定制 设计师一对一服务模式,上百家客户案例! 企业保证,正规流程,正规合作 7*24小时在线服务,售后无忧

绕过Android SDK网络安全配置:Frida脚本利用,测试脚本分析脚本的运行机制

文章来源:重庆网络安全 发布时间:2020-03-11 03:32:02 围观次数:
分享到:

摘要:绕过Android SDK网络安全配置:Frida脚本利用,测试脚本分析脚本的运行机制。

 使用Frida脚本绕过Android的网络安全配置。这是一项绕过网络安全配置的新技术。此外还将演示如何在其他情况下测试脚本,并分析脚本运行机制。

 在先前的Android应用程序安全审核过程中,我们要做的第一件事是准备渗透测试环境并配置应用程序以绕过网络安全配置。 


  生成不同的测试用例,尝试选择了几款比较常见的:


 1.OKHttp


 2.HttpsURLConnection


 3.WebView

  接下来生成了三个具有不同网络安全配置的应用程序:


  1.使用默认NSC配置-BypassNSC的应用程序


  2.带有NSC文件的应用程序(仅使用系统证书)-BypassNSC2


  3.具有NSC文件的应用程序(强制性证书绑定)-BypassNSC3

  代码解析并验证Android SDK中的网络安全配置。 测试版本是24、25和26。


  脚本名称如下:


 network-security-config-bypass-1.js


 network-security-config-bypass-2.js


 network-security-config-bypass-3.js


 network-security-config-bypass-cr.js

  下图显示了每个脚本的分析测试结果:

blob.png


network-security-config-bypass-1.js


 该脚本修改了NetworkSecurityConfig.Builder类中的getEffectiveCertificatesEntryRefs方法,该方法返回有效证书的列表。 在标准的Android配置中,它返回的有效证书列表是目标系统中安装的有效证书。出乎意料的是,此脚本将直接返回用户安装的证书,因此从理论上讲它可以绕过前两个应用程序的网络安全配置,但是惊讶的是,它在第三个情况下也有效,这是证书绑定配置。我们可以使用以下方法来验证绑定证书:

 android.security.net.config.NetworkSecurityTrustManager.checkPins


 以下堆栈跟踪显示了checkPins函数的代码执行路径:

   at android.security.net.config.NetworkSecurityTrustManager.checkPins(Native Method)

   at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:95)

   at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)

   at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:178)

   at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:596)

   at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)

   at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)


 如果未执行补丁,则执行路径到达函数时会引发异常:

Caused by: java.security.cert.CertificateException: Pin verification failed

       at android.security.net.config.NetworkSecurityTrustManager.checkPins(NetworkSecurityTrustManager.java:148)

       at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:95)

       at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)

       at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:178)

       at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:596)

       at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)

       at com.android.org.


 让我们看一下该方法的实现代码(API 25):

private void checkPins(List<X509Certificate> chain) throws CertificateException {

       PinSet pinSet = mNetworkSecurityConfig.getPins();

       if (pinSet.pins.isEmpty()

               || System.currentTimeMillis() > pinSet.expirationTime

               || !isPinningEnforced(chain)) {

           return;

       }

       Set<String> pinAlgorithms = pinSet.getPinAlgorithms();

       Map<String, MessageDigest> digestMap = new ArrayMap<String, MessageDigest>(

               pinAlgorithms.size());

       for (int i = chain.size() - 1; i >= 0 ; i--) {

           X509Certificate cert = chain.get(i);

           byte[] encodedSPKI = cert.getPublicKey().getEncoded();

           for (String algorithm : pinAlgorithms) {

               MessageDigest md = digestMap.get(algorithm);

               if (md == null) {

                   try {

                       md = MessageDigest.getInstance(algorithm);

                   } catch (GeneralSecurityException e) {

                       throw new RuntimeException(e);

                   }

                   digestMap.put(algorithm, md);

               }

               if (pinSet.pins.contains(new Pin(algorithm, md.digest(encodedSPKI)))) {

                   return;

               }

           }

       }

       // TODO: Throw a subclass of CertificateException which indicates a pinning failure.

       throw new CertificateException("Pin verification failed");

   }


 此方法可以接收网站通信返回的证书列表。它要做的第一件事是条件检测:


  1.pinset为空


  2.Pinset已在验证过程中过期


  3.证书绑定不是强制性要求

  如果以上条件都不成立,则绑定验证将被忽略。如果必须实施身份验证,则应用程序将检查站点提供的任何证书是否与网络安全配置文件中定义的证书之一匹配,此时身份验证成功。如果这没有发生,则该方法将引发上一stacktrace中所示的异常。


  最初认为这个问题存在于用于分析每个证书的for循环中,因此在Frida脚本中添加了以下日志:

var Pin = Java.use("android.security.net.config.Pin");

Pin.$init.implementation = function (digestAlg, digest) {

var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());

console.log("\nBacktrace:\n" + bt);

console.log(digestAlg);

return this.$init(digestAlg,digest);

}


 它可以在验证过程中输出每个pin。当运行更改后的应用程序时发现它没有用。因此在日志中添加了对pinSet.getPinAlgorithms()的调用,并在for循环之前执行了它:

var PinSet = Java.use("android.security.net.config.PinSet");

PinSet.getPinAlgorithms.implementation = function () {

var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());

console.log("\nBacktrace:\n" + bt);

return this.getPinAlgorithms();

}


 这次没有打印任何内容,查看该函数的条件是否为真,因此向脚本中添加了以下代码:

   NetworkSecurityTrustManager.checkPins.implementation = function (pins) {

var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());

console.log("\nBacktrace:\n" + bt);

pinSet = this.mNetworkSecurityConfig.value.getPins();

console.log("pinSet.pins.value.isEmpty: " +pinSet.pins.value.isEmpty());

console.log("isPinningEnforced: " +this.isPinningEnforced(pins));

console.log("pins.isEmpty: " +pins.isEmpty());

console.log(System.currentTimeMillis())

console.log(pinSet.expirationTime.value);

console.log(System.currentTimeMillis() > pinSet.expirationTime.value);

this.checkPins(pins);

}


运行该应用程序后得到以下输出:

pinSet.pins.value.isEmpty: false

isPinningEnforced: false <-- this condition is the problematic one

pins.isEmpty: false

1562031248274

9223372036854775807

false


 我们可以看到isPinningEnforced为False,此时所有其他表达式为True。该方法的实现代码如下:

private boolean isPinningEnforced(List<X509Certificate> chain) throws CertificateException {

if (chain.isEmpty()) {

return false;

}

X509Certificate anchorCert = chain.get(chain.size() - 1);

TrustAnchor chainAnchor =

mNetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey(anchorCert);

if (chainAnchor == null) {

throw new CertificateException("Trusted chain does not end in a TrustAnchor");

}

return !chainAnchor.overridesPins;

}


 原来,问题出在findTrustAnchorBySubjectAndPublicKey,它是NetworkSecurityConfig类中的一种方法,可以返回chainAnchor:

public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {

for (CertificatesEntryRef ref : mCertificatesEntryRefs) {

TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);

if (anchor != null) {

return anchor;

}

}

return null;

}


 在配置过程中对创建的CertificatesEntryRef进行迭代,并返回与SubjectAndPublicKey匹配的第一个对象。在这种情况下,它将返回代理之一。在研究了源代码之后,找到了CertificatesEntryRef类并找到了唯一的构造函数:

public CertificatesEntryRef(CertificateSource source, boolean overridesPins) {

mSource = source;

mOverridesPins = overridesPins;

}


 如果再次查看Frida脚本,将会发现以以下方式创建了CertificatesEntryRef:

NetworkSecurityConfig_Builder.getEffectiveCertificatesEntryRefs.implementation = function(){

origin = this.getEffectiveCertificatesEntryRefs()

source = UserCertificateSource.getInstance()

userCert = CertificatesEntryRef.$new(source,true) <-- sets overridesPins in true

origin.add(userCert)

return origin

}

 这就是为什么该脚本可在所有情况下使用的原因。


network-security-config-bypass-2.js


  在这种情况下,唯一适用的是不包含网络安全配置文件的应用程序。


  由于parseNetworkSecurityConfig方法而导致补丁无法正常工作的原因:

  XmlUtils.beginDocument(parser, "network-security-config");

int outerDepth = parser.getDepth();

while (XmlUtils.nextElementWithin(parser, outerDepth)) {

//here it creates a NetworkSecurityconfig.Builder based on the xml structure.

...

}

...

NetworkSecurityConfig.Builder platformDefaultBuilder =

NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion); <-- this is the method changed with the script

addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);

//baseConfigBuilder is null only if the xml network-security-config is not defined in the AndroidManifest.xml

if (baseConfigBuilder != null) {

baseConfigBuilder.setParent(platformDefaultBuilder);

addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder);

} else {

baseConfigBuilder = platformDefaultBuilder;

}

...

mDefaultConfig = baseConfigBuilder.build();

mDomainMap = configs;

}

 构建方法生成NetworkSecurityConfig实体:

public NetworkSecurityConfig build() {

boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();

boolean hstsEnforced = getEffectiveHstsEnforced();

PinSet pinSet = getEffectivePinSet();

List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs();

return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs);

}


 在entryRefs变量中定义了一个有效的证书源,其构造方法如下:

private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() {

if (mCertificatesEntryRefs != null) {

return mCertificatesEntryRefs;

}

if (mParentBuilder != null) {

return mParentBuilder.getEffectiveCertificatesEntryRefs();

}

return Collections.<CertificatesEntryRef>emptyList();

}

 此时,mCertificatesEntryRefs不为空,并将返回标准SystemCertificateSource。因此,从不调用mParentBuilder。


  接下来,成功验证服务器证书后,应用程序将调用NetworkSecurityConfig.findTrustAnchorBySubjectAnd

PublicKey方法,该方法将过滤有效证书:

public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {

for (CertificatesEntryRef ref : mCertificatesEntryRefs) {

TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);

if (anchor != null) {

return anchor;

}

}

return null;

}

 并导致堆栈跟踪引发异常:

com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:375)

at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:304)

at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)

at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)

...


network-security-config-bypass-3.js


  该脚本可在三种情况中的两种情况下工作,因为该补丁是通过一种用于验证证书的方法执行的。但是,它不适用于第三种情况,因为证书绑定是通过其他方法执行的。有关详细信息,请参考堆栈跟踪中的错误信息:

at android.security.net.config.NetworkSecurityTrustManager.checkPins(NetworkSecurityTrustManager.java:148)

at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:95)

at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)

at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:203)

at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:592)

at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)

at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:351)

... 25 more

network-security-config-bypass-cr.js


  在这种情况下,修补的方法是getConfigSource,当代码解析network-security-config时将调用它。我们可以看到重写的方法将创建一个DefaultConfigSource并将其定义为Android 23中的参数。


本文由 重庆网络安全 整理发布,转载请保留出处,内容部分来自于互联网,如有侵权请联系我们删除。

相关热词搜索:Android SDK 网络安全配置 Frida脚本利用 测试脚本 重庆网络安全

上一篇:如何从内存加载动态链接库(DLL)
下一篇:流氓应用程序收集隐私:分析Apple设备剪贴板上泄漏GPS信息的潜在危险

热门资讯

鼠标向下滚动