SSL Pinning(SSL 固定)是一种安全机制:客户端应用把服务器预期的加密证书或公钥硬编码"固定"下来,当代理试图用另一份证书(即便有效)拦截流量时,连接便会失败。该机制通过阻止那些依赖出示受信任却出乎意料的证书的中间人(MITM)攻击来增强安全性。
SSL Pinning 如何工作
SSL Pinning 在客户端运行,扩展了标准的 TLS 握手验证流程。通常在 TLS 握手期间,客户端会将服务器证书与其信任库(一组受信任的根证书颁发机构集合)进行核对。如果信任库中的某个 CA 签发了服务器证书,连接便会继续。
启用 SSL Pinning 后,客户端应用会为预期服务器存储一个特定标识——完整证书或其公钥。当发起连接时:
1. 服务器出示其证书。
2. 客户端使用其 CA 信任库执行标准的信任链验证。
3. 此外,客户端还将服务器出示的证书或公钥与其预先固定的值进行比对。
4. 如果两项验证都通过(CA 受信任且固定值匹配),连接继续。
5. 如果固定值不匹配,即便证书由受信任的 CA 签发,连接也会立即以验证错误终止。
这一额外检查使得攻击者——哪怕已攻陷某个受信任的 CA——也大大更难拦截流量。
SSL Pinning 的类型
SSL Pinning 主要有两种类型:
- 证书固定(Certificate Pinning):客户端固定服务器确切的 X.509 证书。此方法针对性极强,但每当服务器证书续期或更换时都需要更新应用。
- 公钥固定(Public Key Pinning):客户端固定从服务器证书中提取的公钥。它比证书固定更灵活,因为即便证书续期,公钥通常也保持不变(只要密钥对不变)。这通常通过固定 Subject Public Key Info(SPKI)哈希来实现。
| 特性 | 证书固定 | 公钥固定 |
|---|---|---|
| 固定对象 | 完整的 X.509 证书 | 公钥(如 SPKI 哈希) |
| 灵活性 | 低;证书续期时需更新应用 | 较高;允许用同一密钥续期证书 |
| 续期影响 | 高;证书变更需更新应用 | 低;仅密钥对变更时需更新应用 |
| 针对性 | 极高 | 高 |
| 实现风险 | 若管理不善,使应用失效的风险较高 | 风险较低,对证书变更更稳健 |
示例:Android 概念性固定(Java)
import okhttp3.CertificatePinner;
import okhttp3.OkHttpClient;
import okhttp3.Request;
public class PinnedHttpClient {
public static void main(String[] args) throws Exception {
// 一个公钥的示例 SHA256 哈希。
// 在真实应用中,它应从目标服务器的证书派生。
String hostname = "api.example.com";
String sha256_publicKey_hash = "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; // 替换为实际哈希
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, sha256_publicKey_hash)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://" + hostname + "/data")
.build();
// 如果服务器公钥与固定的哈希不匹配,即便证书由受信任的 CA 签发,
// 此调用也会失败。
try {
client.newCall(request).execute();
System.out.println("Connection successful (or would be if executed)");
} catch (javax.net.ssl.SSLPeerUnverifiedException e) {
System.err.println("SSL Pinning failed: " + e.getMessage());
}
}
}
应用为何使用 SSL Pinning
各类应用,尤其是移动和物联网(IoT)应用,出于以下几项关键安全原因实施 SSL Pinning:
- 防范流氓证书颁发机构(CA):标准 TLS 依赖客户端信任库中所有 CA 的完整性。如果某个 CA 被攻陷,或为某个域名签发了伪造证书,攻击者便可借此实施 MITM 攻击。SSL Pinning 通过只显式信任某一特定证书或公钥、而不管其他受信任的 CA,绕开了这一漏洞。
- 缓解信任库被篡改:在用户可控的设备上,用户或恶意软件可能向设备信任库安装额外的根证书。虽然这常用于正当的调试,但也可能被利用。SSL Pinning 确保即便添加了恶意根 CA,只要出示的证书与固定值不匹配,应用仍会拒绝连接到其固定的域名。
- 为敏感数据提供更强安全:处理高度敏感数据(如银行、医疗、身份验证令牌)的应用使用固定来建立更高的信任度,并缩小网络拦截的攻击面。
SSL Pinning 如何影响代理
代理,尤其是那些为流量检查、调试或安全扫描而设计的代理,通过充当中间人(一个合法的 MITM)来工作。
当应用使用代理进行 HTTPS 连接时:
1. 客户端连接到代理。
2. 代理与目标服务器建立自己的连接。
3. 随后代理为目标服务器即时生成一份新证书,由其自有的根 CA 签发(该 CA 必须被客户端信任,代理才能工作)。
4. 代理把这份新生成的证书出示给客户端。
这一流程与 SSL Pinning 存在根本冲突。客户端应用期待的是原始服务器的证书或公钥。当代理出示其自己生成的证书时,即便该证书由操作系统信任的 CA 签发,客户端的 SSL Pinning 机制也会检测到与其硬编码固定值的不匹配。因此,客户端会以 SSLPeerUnverifiedException 或类似错误终止连接,从而阻止代理拦截并检查流量。
- 透明代理:这类代理在无需客户端显式配置的情况下拦截流量。如果客户端应用启用了 SSL Pinning,即便操作系统信任代理的根 CA,应用仍会检测到代理的证书并终止连接。
- 拦截代理(显式):当客户端被显式配置为使用代理时,代理的证书仍会与应用固定的证书冲突,导致连接失败。
对运营的影响
- 调试与开发:开发者常用代理(如 Burp Suite、Fiddler、Charles Proxy)来检查网络流量,以进行调试、API 开发和性能分析。SSL Pinning 会直接阻碍这一点,使人无法查看应用的网络请求和响应。
- 安全测试:渗透测试人员依赖代理来分析应用漏洞,包括 API 滥用、数据泄露和身份验证缺陷。SSL Pinning 会阻止这些工具正常工作,妨碍全面的安全评估。
- 监控与分析:一些企业环境使用代理进行网络监控、数据泄露防护(DLP)或分析。带 SSL Pinning 的应用会绕过或阻断这些监控能力。
绕过 SSL Pinning(用于合法目的)
绕过 SSL Pinning 通常用于安全测试、调试或逆向工程等合法目的,且一般在受控环境中进行。它需要修改客户端应用或其运行环境。
- 修改应用代码:如果源代码可用,开发者可在测试构建中移除或禁用固定逻辑。这是最可靠的方法。
- 运行时插桩框架:Frida 或 Xposed 等工具允许在运行时动态修改应用行为。这些框架可挂钩(hook)应用的 SSL/TLS 库调用并绕过固定检查。这通常需要已 root 或越狱的设备。
```javascript
// 绕过 Android SSL Pinning 的示例 Frida 脚本片段(概念性)
Java.perform(function () {
var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
var FileInputStream = Java.use("java.io.FileInputStream");
var BufferedInputStream = Java.use("java.io.BufferedInputStream");
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var KeyStore = Java.use("java.security.KeyStore");
var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
var SSLContext = Java.use("javax.net.ssl.SSLContext");// 通常以 TrustManagerImpl.checkTrustedRecursive 为目标 var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.checkTrustedRecursive.implementation = function (a, b, c, d, e, f) { // 跳过实际检查,等效于信任所有证书 return Java.array('java.security.cert.X509Certificate', []); }; // ... 针对各类 SSL 库(OkHttp、Apache 等)的其他 hook});
```
* 修改应用二进制文件:对于源代码不可用的应用,可使用逆向工程工具给编译后的二进制打补丁以禁用固定。这很复杂,需要相当的专业能力。
* 支持代理的客户端配置:某些应用可能提供配置选项,以信任自定义 CA 或在开发模式下禁用固定,但这在生产应用中很少见。
绕过 SSL Pinning 应以负责任且合乎道德的方式进行,主要用于安全研究、开发或经授权的测试。未经授权、出于恶意目的的绕过是违法且不道德的。
