Android 网络通信¶
概述¶
几乎每个 Android 应用都充当一个或多个远程服务的客户端。由于这种网络通信通常发生在不受信任的网络(如公共 Wi-Fi)上,因此传统的基于网络的攻击成为一个潜在的问题。
大多数现代移动应用使用基于 HTTP 的 Web 服务的变体,因为这些协议有完善的文档和支持。
Android 网络安全配置¶
从 Android 7.0(API 级别 24)开始,Android 应用可以使用所谓的 网络安全配置 功能来定制其网络安全设置,该功能提供以下关键功能
- 明文流量:保护应用免受意外使用明文流量的影响(或启用明文流量)。
- 自定义信任锚点:自定义哪些证书颁发机构 (CA) 被信任用于应用的安全连接。例如,信任特定的自签名证书或限制应用信任的公共 CA 集合。
- 证书锁定:将应用的安全连接限制为特定的证书。
- 仅调试覆盖:安全地调试应用中的安全连接,而不会增加已安装基础的风险。
如果应用定义了自定义网络安全配置,您可以通过在 AndroidManifest.xml 文件中搜索 android:networkSecurityConfig
来获取其位置。
<application android:networkSecurityConfig="@xml/network_security_config"
在这种情况下,该文件位于 @xml
(相当于 /res/xml)并且名称为“network_security_config”(可能会有所不同)。您应该能够将其找到为“res/xml/network_security_config.xml”。如果存在配置,则以下事件应在系统日志中可见( 监控系统日志)
D/NetworkSecurityConfig: Using Network Security Config from resource network_security_config
网络安全配置是 基于 XML 的,可用于配置应用范围和特定于域的设置
base-config
适用于应用尝试建立的所有连接。domain-config
覆盖特定域的base-config
(它可以包含多个domain
条目)。
例如,以下配置使用 base-config
来防止所有域的明文流量。但它使用 domain-config
覆盖该规则,显式允许 localhost
的明文流量。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
<domain-config cleartextTrafficPermitted="true">
<domain>localhost</domain>
</domain-config>
</network-security-config>
了解更多
默认配置¶
针对 Android 9(API 级别 28)及更高版本的应用的默认配置如下
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
针对 Android 7.0(API 级别 24)到 Android 8.1(API 级别 27)的应用的默认配置如下
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
针对 Android 6.0(API 级别 23)及更低版本的应用的默认配置如下
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
证书锁定¶
证书锁定 可用于 Android 应用中,以防止中间人 (MITM) 攻击,方法是确保应用仅与具有特定身份的远程端点通信。
虽然在正确实施时有效,但不安全的实施可能会使攻击者能够读取和修改所有通信。有关锁定的更多一般详细信息,请参阅 不安全的身份锁定。
存在多种证书锁定方法,具体取决于应用的 API 级别和使用的库。下面,我们重点介绍最常见的方法。有关特定实施的更深入信息,请参阅 "深入了解 Android 上的证书锁定"。
重要注意事项
证书锁定是一种加固实践,但并非万无一失。攻击者可以通过多种方式绕过它,例如
- 修改应用
TrustManager
中的证书验证逻辑。 - 替换存储在资源目录(
res/raw/
、assets/
)中的锁定的证书。 - 更改或删除网络安全配置中的锁定。
任何此类修改都会使 APK 签名无效,需要攻击者重新打包并重新签名 APK。为了减轻这些风险,可能需要额外的保护措施,例如完整性检查、运行时验证和模糊处理。有关特定技术的更多信息,请参阅 绕过证书锁定。
通过网络安全配置锁定 (API 24+)¶
网络安全配置 (NSC) 是在 Android 中实施证书锁定的首选和推荐方法,因为它提供了一种声明式的、可维护的和安全的方法,而无需更改代码。它适用于 Android 框架在应用中管理的所有网络流量,包括基于 HttpsURLConnection
的连接和 WebView
请求(除非使用自定义 TrustManager
)。对于来自原生代码的通信,NSC 不适用,需要考虑其他机制。
尝试建立与远程端点的连接时,系统将
- 获取并验证传入的证书。
- 提取公钥。
- 计算提取的公钥上的摘要。
- 将摘要与本地锁定集进行比较。
如果至少有一个锁定的摘要匹配,则证书链将被视为有效,连接将继续。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<!-- Use certificate pinning for OWASP website access including sub domains -->
<domain includeSubdomains="true">owasp.org</domain>
<pin-set expiration="2028-12-31">
<!-- Hash of the public key (SubjectPublicKeyInfo of the X.509 certificate) of
the Intermediate CA of the OWASP website server certificate -->
<pin digest="SHA-256">YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=</pin>
<!-- Hash of the public key (SubjectPublicKeyInfo of the X.509 certificate) of
the Root CA of the OWASP website server certificate -->
<pin digest="SHA-256">Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=</pin>
</pin-set>
</domain-config>
</network-security-config>
重要注意事项
- 备份锁定: 始终包含备份锁定,以在主证书意外更改时保持连接。
- 到期日期: 设置合适的 到期日期 并确保及时更新,以防止应用在日期过后绕过锁定。
- 应用范围: 请注意,此配置仅适用于使用
HttpsURLConnection
或依赖它的库建立的连接。其他网络库或框架可能需要单独的锁定实施。
使用自定义 TrustManagers 进行锁定¶
在网络安全配置可用之前,实施证书锁定的推荐方法是创建自定义 TrustManager
(使用 javax.net.ssl
API)并覆盖默认证书验证。您仍然可以在现代 Android 版本上使用此方法以获得灵活性,或者当您需要更多直接控制时。
此方法涉及
- 将服务器的证书加载到
KeyStore
中。 - 创建一个自定义
TrustManager
,该管理器仅信任KeyStore
中的证书。 - 使用自定义
TrustManager
初始化SSLContext
。 - 将自定义
SSLContext
应用为网络连接的套接字工厂(例如,HttpsURLConnection
)。
重要提示: 如果不小心完成此操作,这是一种 低级方法并且容易出错。一些关键考虑因素包括
SSLSocket
不会自动验证主机名,因此您必须使用具有安全实施的HostnameVerifier
手动处理此问题(这包括显式检查HostnameVerifier.verify()
的返回值)。更多信息可以在 Android 文档 中找到。- 不要 包括 "信任所有"
TrustManager
,该管理器会静默接受所有证书。这为攻击者打开了大门,他们可以拦截和修改用户数据,而只需付出最小的努力。
使用第三方库进行锁定¶
一些第三方库提供对证书锁定的内置支持,在某些情况下简化了实施过程。这些库通常使用自定义 TrustManager
方法,提供更高级别的抽象和其他功能。值得注意的例子包括
例如,OkHttp 的 CertificatePinner
中提供了锁定。在底层,它使用自定义 TrustManager
来强制执行锁定规则。
在 WebViews 中锁定¶
对于 Android 上的应用内 WebView
流量,最简单的方法是依赖 网络安全配置。由于 Android 会自动将 NSC 规则应用于同一应用程序中的 WebView 流量,因此您在 network_security_config.xml
中设置的任何锁定规则也将应用于在该 WebView 中加载的资源。
如果您需要的自定义设置超出了 NSC 提供的范围,您可以拦截 WebView 级别的请求(例如,使用 shouldInterceptRequest
和 自定义 TrustManager
)来实施锁定,但在大多数情况下,内置支持就足够且更简单。
在原生代码中锁定¶
也可以在 原生代码 (C/C++/Rust) 中实施锁定。通过在编译的原生库(.so
文件)中嵌入或动态验证证书,您可以增加通过典型的 APK 逆向工程绕过或修改锁定检查的难度。
也就是说,这种方法需要大量的安全专业知识和谨慎的设计,以在原生空间中管理证书或公钥哈希。维护和调试通常也会变得更加复杂。
在跨平台框架中锁定¶
Flutter、React Native、Cordova 和 Xamarin 等跨平台框架通常需要对证书锁定进行特殊考虑,因为它们可能不使用与原生应用相同的网络堆栈。例如,Flutter 依赖于它自己的 Dart HttpClient
(带有 BoringSSL),而不是平台的网络堆栈,而 Cordova 通过 WebView 中的 JavaScript 发出网络请求。因此,锁定行为会有所不同——一些框架提供内置配置选项,另一些框架依赖第三方插件,还有一些框架不提供直接支持,但允许通过 API 手动实施。了解框架如何处理网络对于确保正确执行锁定至关重要。
安全提供程序¶
Android 依赖于 安全提供程序 来提供基于 SSL/TLS 的连接。这种安全提供程序(一个例子是 OpenSSL)存在的问题是,它通常存在错误和/或漏洞。
为了避免已知的漏洞,开发人员需要确保应用程序将安装适当的安全提供程序。自 2016 年 7 月 11 日起,Google 一直在拒绝使用易受攻击版本的 OpenSSL 的 Play 商店应用程序提交(包括新应用程序和更新)。