写篇文章介绍些以前在 iOS 客户端实践 HTTPS 安全的经历。
不同工程师写代码的习惯不一样,有些喜欢边写边查,即使在接触陌生的知识域时,也要先写一些代码,遇到难题再去 google 或者 stackoverflow 找答案,另一些则习惯在动手之前,先储备足够多的理论知识,在建立整体认知之后再定框架填细节。我个人建议初学者采用第一种方式,而对于有一定工作年限且寻求专业度提升的工程师来说,后者是更合理的方式。
关于客户端如何实践 HTTPS,所涉及的知识点非常多,如果不预先建立相关知识框架,熟悉背后的理论体系,写出 bug,给队友埋坑可以说是个大概率事件。个人建议可以围绕 PKI 体系和 HTTPS 网络流量分析这两块着手,逐步完善知识细节。
HTTPS 防中间人攻击是 PKI 知识体系的一个应用场景,关键在于明白签名的意义,签名的目的在于身份认证,背后设计的算法理论虽然有些复杂,但整个流程却很直观清晰。
中间人攻击场景涉及三个角色,客户端,服务器,以及 CA(证书签发机构)。CA 主要用来解决 Client 和 Server 之间的信任问题,相当于一个背书的角色。CA 通过签发证书的方式,来确认 Client 和 Server 的身份,具体到 iOS 客户端,CA 一般向 Server 签发证书,告知 Client,持有某个证书的 Server,其身份是可以被信任的。那谁来确认 CA 所说的话是可以被信任的呢?操作系统会内置一些知名 CA 的公钥,这些知名 CA 在签发证书的时候会通过审核确认,确保 Server 的身份和其所宣称的一致。
所有围绕中间人攻击的场景都是根据 CA 来展开的。
一般场景下,iOS 客户端的证书校验逻辑会检查 CA 是否被信任,可以避免中间人攻击。只不过在一些特定场景下会让中间人攻击有机可乘,比如用户自己在 iPhone 上添加可被信任的 CA。之前我写过一篇使用 mitmproxy,实施中间人攻击知乎 iOS 客户端的文章,其原理就是利用用户自己添加 mitimproxy 为可信任 CA,这样 mitmproxy 可以在截获 https 流量之后,进行证书校验的时候,临时签发证书,欺骗证书的校验过程。所以,任何时刻都不要随意添加第三方 CA 信任,这是客户端安全的一道大门。
如果不乱添加第三方 CA,不随意使用网络代理,是否就可以避免中间人攻击呢?凡事无绝对,以前就出现过不少第三方 CA 爆安全漏洞的例子,让攻击者可以签发出来自知名 CA 的证书,这种例子虽然少,却不是没有。
另外,iOS 系统为了加强安全性,降低用户误操作所带来的安全隐患,从 iOS 10.3 开始,将添加证书和信任证书分开处理。即使在用户添加证书之后,还需要在另外一个位置手动开启信任,才能让第三方 CA 获得签发证书被信任的能力,具体位置是:Settings > General > About > Certificate Trust Settings。
对于 iOS 开发者来说,防中间人攻击可以从两方面着手。
第一是通讯内容本身加密,无论是走 http 还是 https,request 和 response 的内容本身都要先做一次加密,这样即使 https 的流量被破解,攻击者还需要再攻破一层加密算法。我们一般使用 AES 256 对内容做加密,这里 AES 密钥的管理也有两种方式,其一是在客户端使用固定的密钥,为了加大破解的难度,我们可以对密钥本身做多次加密处理,使用时再在内存里解密出来真正的密钥。其二是每次会话都使用不同的密钥,原理类似 Forward Secrecy,即使流量被记录,将来被暴力破解,也能极大的增加攻击者破解的时间成本。
第二种就是大家所熟知的 ssl pinning。在客户端进行代码层面的证书校验,校验方式也有两种,一是证书本身校验,而是公钥校验。这两种方式对应到 AFNetworking 中的代码如下:
enum {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
}
证书校验是文件级别的校验,客户端只信任若干个证书文件,这些证书文件是和客户端一起打包发布的,这种处理方式要面对的一个问题证书过期问题,为了避免证书过期导致的校验失败,客户端和服务器之间需要额外存在一个证书更新机制,其实做起来也比较简单,只需要服务器下发一个特定的错误码,触发一个客户端的新证书下载流程即可。
公钥校验模式可以免去上述的麻烦,公钥模式只校验证书中所包含的公钥是否匹配,即使证书过期了,只要服务器更新证书,保证公钥不变,依然能完成校验过程,但这个大前提是,服务器的公钥私钥对不能更换。
以上所述都是 https 安全相关的主要知识点,有时候还会遇到一些特殊场景,所以预先建立完整的知识体系十分重要。
比如,有些客户端做了 httpdns,http 请求里是 IP 地址而非域名,这样自然无法通过证书校验环节中的域名匹配,这种时候,我们需要干预证书校验的环节,比如 AFNetworking 允许我们设置 validatesDomainName,NSURLSession 也提供如下方法让我们对证书校验过程做一些特殊处理:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{}
所以,先储备好相关理论知识,无论在哪个平台,使用什么第三方库,就都能清晰的做代码层面的 https 安全实践了。
全文完。
欢迎关注公众号:MrPeakTech