绕过Android SDK网络安全配置:Frida脚本利用,测试脚本分析脚本的运行机制
摘要:绕过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
下图显示了每个脚本的分析测试结果:
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信息的潜在危险
人机验证(Captcha)绕过方法:使用Chrome开发者工具在目标网站登录页面上执行简单的元素编辑,以实现Captcha绕过
牛创网络: " 人机身份验证(Captcha)通常显示在网站的注册,登录名和密码重置页面上。 以下是目标网站在登录页面中排列的验证码机制。 从上图可以
2020-01-26 12:44:09 )8872( 亮了
自动发现IDOR(越权)漏洞的方法:使用BurpSuite中的Autozie和Autorepeater插件来检测和识别IDOR漏洞,而无需手动更改每个请求的参数
牛创网络: "自动发现IDOR(越权)漏洞的方法:使用BurpSuite中的Autozie和Autorepeater插件来检测和识别IDOR漏洞,而无需手动更改每个请求的参数
2020-01-30 14:04:47 )6288( 亮了
Grafana CVE-2020-13379漏洞分析:重定向和URL参数注入漏洞的综合利用可以在任何Grafana产品实例中实现未经授权的服务器端请求伪造攻击SSRF
牛创网络: "在Grafana产品实例中,综合利用重定向和URL参数注入漏洞可以实现未经授权的服务器端请求伪造攻击(SSRF)。该漏洞影响Grafana 3 0 1至7 0 1版本。
2020-08-12 14:26:44 )4301( 亮了
Nginx反向代理配置及反向代理泛目录,目录,全站方法
牛创网络: "使用nginx代理dan(sui)是http响应消息写入服务地址或Web绝对路径的情况。 写一个死的服务地址是很少见的,但它偶尔也会发生。 最棘手的是写入web绝对路径,特别是如果绝对路径没有公共前缀
2019-06-17 10:08:58 )3858( 亮了
fortify sca自定义代码安全扫描工具扫描规则(源代码编写、规则定义和扫描结果展示)
牛创网络: "一般安全问题(例如代码注入漏洞),当前fortify sca规则具有很多误报,可通过规则优化来减少误报。自带的扫描规则不能检测到这些问题。 需要自定义扫描规则,合规性角度展示安全风险。
2020-02-12 10:49:07 )3505( 亮了
整理几款2020年流行的漏洞扫描工具
牛创网络: "漏洞扫描器就是确保可以及时准确地检测信息平台基础架构的安全性,确保业务的平稳发展,业务的高效快速发展以及公司,企业和国家 地区的所有信息资产的维护安全。
2020-08-05 14:36:26 )2536( 亮了
微擎安装使用技巧-微擎安装的时候页面显示空白是怎么回事?
牛创网络: "我们在公众号开发中,有时候会用到微擎,那我们来看一下微擎安装的时候页面显示空白是怎么回事吧
2019-06-08 15:34:16 )2261( 亮了
渗透测试:利用前端断点拦截和JS脚本替换对前端加密数据的修改
牛创网络: " 本文介绍的两种方法,虽然断点调试比JS脚本代码替换更容易,但是JS脚本代码替换方法可以实现更强大的功能,测试人员可以根据实际需要选择适当的测试方法
2020-01-07 09:34:42 )1995( 亮了
从工业界到学界盘点SAS与R优缺点比较
牛创网络: "虽然它在业界仍然由SAS主导,但R在学术界广泛使用,因为它的免费开源属性允许用户编写和共享他们自己的应用程序 然而,由于缺乏SAS经验,许多获得数据分析学位的学生很难找到工作。
2019-07-13 22:25:29 )1842( 亮了
41款APP侵犯用户隐私权:QQ,小米,搜狐,新浪,人人均被通报
牛创网络: "随着互联网的不断发展,我们进入了一个时代,每个人都离不开手机。 但是,APP越来越侵犯了用户隐私权。12月19日,工业和信息化部发布了《关于侵犯用户权益的APP(第一批)》的通知。
2019-12-20 11:28:14 )1775( 亮了