跳过内容

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 版本上使用此方法以获得灵活性,或者当您需要更多直接控制时。

此方法涉及

  1. 将服务器的证书加载到 KeyStore 中。
  2. 创建一个自定义 TrustManager,该管理器仅信任 KeyStore 中的证书。
  3. 使用自定义 TrustManager 初始化 SSLContext
  4. 将自定义 SSLContext 应用为网络连接的套接字工厂(例如,HttpsURLConnection)。

重要提示: 如果不小心完成此操作,这是一种 低级方法并且容易出错。一些关键考虑因素包括

使用第三方库进行锁定

一些第三方库提供对证书锁定的内置支持,在某些情况下简化了实施过程。这些库通常使用自定义 TrustManager 方法,提供更高级别的抽象和其他功能。值得注意的例子包括

例如,OkHttpCertificatePinner 中提供了锁定。在底层,它使用自定义 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 商店应用程序提交(包括新应用程序和更新)。