You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Alamofire证书固定遇MITM攻击未告警,如何检测用户安装CA?

修复Alamofire证书校验器的MITM检测漏洞(用户安装CA场景)

原代码核心问题

  1. 默认信任用户安装的CA:调用trust.af.performDefaultValidation会默认信任系统+用户安装的所有CA,攻击者预安装恶意CA后,中间人攻击的证书链会被默认通过,绕过后续校验。
  2. 证书固定逻辑无效:原setupCertificatePinner返回无固定证书的CertificatePinner,等于未做任何证书固定校验。
  3. 验证顺序错误:先执行默认信任验证再检查证书固定,导致恶意CA通过信任验证后,即使证书不匹配也可能被忽略。

修改后的完整代码

import Alamofire

public final class CertificatePinnerTrustEvaluator: ServerTrustEvaluating {
    
    public init() {}
    
    /// 初始化证书固定器,加载指定主机对应的固定证书
    /// - Parameter host: 目标主机
    /// - Returns: 配置好的CertificatePinner
    func setupCertificatePinner(host: String) -> CertificatePinner {
        // 根据主机加载对应的固定证书(示例:从主Bundle加载,替换为你的证书文件名)
        let certFileName: String
        switch host {
        case "api.yourdomain.com":
            certFileName = "your_server_cert"
        // 可添加更多主机的证书配置
        default:
            fatalError("No pinned certificate configured for host: \(host)")
        }
        
        guard let certURL = Bundle.main.url(forResource: certFileName, withExtension: "cer"),
              let certData = try? Data(contentsOf: certURL) else {
            fatalError("Failed to load pinned certificate for host: \(host)")
        }
        
        return CertificatePinner(host: host, certificates: [certData])
    }
    
    public func evaluate(_ trust: SecTrust, forHost host: String) throws {
        let pinner = setupCertificatePinner(host: host)
        
        // 1. 先校验服务器证书链是否包含固定证书
        let serverCertDataSet = Set(trust.af.certificateData)
        let pinnedCertDataSet = Set(pinner.certificates)
        guard !serverCertDataSet.isDisjoint(with: pinnedCertDataSet) else {
            throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(
                host: host,
                trust: trust,
                pinnedCertificates: pinner.certificates.af.certificates,
                serverCertificates: trust.af.certificates
            ))
        }
        
        // 2. 配置信任策略:仅信任系统根CA,拒绝用户安装的CA
        let sslPolicy = SecPolicyCreateSSL(true, host as CFString)
        SecTrustSetPolicies(trust, sslPolicy)
        
        // 获取系统根证书集合
        let systemRootCerts = SecCertificateCopySystemRootCertificates() as! [SecCertificate]
        // 设置信任锚点为系统根CA
        SecTrustSetAnchorCertificates(trust, systemRootCerts as CFArray)
        // 关键:强制仅信任指定的锚点证书,不允许使用其他信任源(如用户安装的CA)
        SecTrustSetAnchorCertificatesOnly(trust, true)
        
        // 3. 执行信任链验证
        var trustResult = SecTrustResultType.invalid
        let evalStatus = SecTrustEvaluate(trust, &trustResult)
        
        guard evalStatus == errSecSuccess,
              trustResult == .unspecified || trustResult == .proceed else {
            throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(trust: trust))
        }
        
        // 额外校验:确保根证书是系统根CA(可选,进一步增强安全性)
        guard let rootCert = SecTrustGetCertificateAtIndex(trust, SecTrustGetCertificateCount(trust) - 1),
              systemRootCerts.contains(rootCert) else {
            throw AFError.serverTrustEvaluationFailed(reason: .untrustedRootCertificate(certificate: rootCert))
        }
    }
}

关键修改说明

  1. 有效配置证书固定:setupCertificatePinner从Bundle加载对应主机的证书,生成真正有效的CertificatePinner,确保只有包含固定证书的服务器链才能通过校验。
  2. 限制信任系统根CA:通过SecTrustSetAnchorCertificates和SecTrustSetAnchorCertificatesOnly(true),强制信任链只能以系统根CA为终点,直接拒绝用户安装的任何CA。
  3. 调整验证顺序:先检查证书固定,再做信任链验证,确保恶意证书链即使通过CA伪造,也会因不匹配固定证书被拦截。
  4. 手动执行信任评估:替代Alamofire的默认验证逻辑,完全控制信任策略,避免默认逻辑的安全漏洞。

测试与注意事项

  • 将你的服务器证书(.cer格式)添加到项目Bundle中,修改代码中的证书文件名。
  • 测试时,在设备上安装MITM工具(如Charles)的CA证书,尝试访问目标API,此时应触发certificatePinningFailed或trustEvaluationFailed错误,说明漏洞已修复。
  • 建议固定服务器的叶子证书或中间CA证书,避免根CA过期导致验证失败;若需支持多个主机,可扩展setupCertificatePinner的主机匹配逻辑。

内容的提问来源于stack exchange,提问作者kisstajmi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 10:34:51