Alamofire证书固定遇MITM攻击未告警,如何检测用户安装CA?
修复Alamofire证书校验器的MITM检测漏洞(用户安装CA场景)
原代码核心问题
- 默认信任用户安装的CA:调用
trust.af.performDefaultValidation会默认信任系统+用户安装的所有CA,攻击者预安装恶意CA后,中间人攻击的证书链会被默认通过,绕过后续校验。 - 证书固定逻辑无效:原
setupCertificatePinner返回无固定证书的CertificatePinner,等于未做任何证书固定校验。 - 验证顺序错误:先执行默认信任验证再检查证书固定,导致恶意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)) } } }
关键修改说明
- 有效配置证书固定:
setupCertificatePinner从Bundle加载对应主机的证书,生成真正有效的CertificatePinner,确保只有包含固定证书的服务器链才能通过校验。 - 限制信任系统根CA:通过
SecTrustSetAnchorCertificates和SecTrustSetAnchorCertificatesOnly(true),强制信任链只能以系统根CA为终点,直接拒绝用户安装的任何CA。 - 调整验证顺序:先检查证书固定,再做信任链验证,确保恶意证书链即使通过CA伪造,也会因不匹配固定证书被拦截。
- 手动执行信任评估:替代Alamofire的默认验证逻辑,完全控制信任策略,避免默认逻辑的安全漏洞。
测试与注意事项
- 将你的服务器证书(.cer格式)添加到项目Bundle中,修改代码中的证书文件名。
- 测试时,在设备上安装MITM工具(如Charles)的CA证书,尝试访问目标API,此时应触发
certificatePinningFailed或trustEvaluationFailed错误,说明漏洞已修复。 - 建议固定服务器的叶子证书或中间CA证书,避免根CA过期导致验证失败;若需支持多个主机,可扩展
setupCertificatePinner的主机匹配逻辑。
内容的提问来源于stack exchange,提问作者kisstajmi
相关产品推荐
相关产品推荐

