第一种和第二种情况不再详细描述,第三种情况是我们常用的抓取手机应用程序的https数据包的方法。这种方法涉及将代理工具的公钥证书导入到手机中,然后进行https数据包的抓取。在Android平台上,将公钥证书导入到手机中称为”受信任的凭据”,而在iOS平台上称为”描述文件”。我们可以使用openssl命令直接查看导入到手机客户端的PortSwiggerCA.crt证书。可以看到,这是一个Issuer和Subject相同的自签名CA公钥证书。此外,通过证书类型,我们可以知道这是一个公钥证书。crt和der格式的证书不支持存储私钥或证书路径(对此感兴趣的同学可以查找有关证书的更多信息)。导入CA公钥证书后,参考前文中的证书验证过程,我们可以发现通过这种方式可以通过证书链验证,从而实现中间人攻击。客户端使用代理工具的公钥证书加密随机数,代理工具使用私钥解密并计算得到对称加密密钥,然后对数据包进行解密,从而可以捕获明文数据包。
5.中间人攻击原理
一直在谈论中间人攻击,那么中间人攻击到底是如何进行的呢?下面我们通过一个流行的MITM开源库mitmproxy来分析中间人攻击的原理。中间人攻击的关键在于https握手过程中的ClientKeyExchange。由于pre key交换时使用服务器证书中的公钥进行加密,如果使用伪造证书的公钥,那么中间人就可以解密该密文,得到premastersecret并计算出用于对称加密算法的masterkey,从而获取客户端发送的数据。然后,中间人代理工具再使用自己和服务端的masterkey加密传输给服务端。同样,服务器返回给客户端的数据也经过中间人解密再加密。这样就形成了完整的https中间人攻击过程。通过阅读Mitmproxy的源代码,我们发现mitmproxy生成伪造证书的函数如下所示。通过这个函数,我们可以生成一个完美的伪造证书。使用浏览器通过mitmproxy作为代理,我们可以查看实际伪造出的证书。可以看到,实际的证书是由mitmproxy颁发的,其中的公钥就是mitmproxy自己的公钥。后续的加密数据可以使用mitmproxy的私钥进行解密。如果将mitmproxy的公钥证书导入到客户端,那么该伪造的证书就可以完美地通过客户端的证书验证。这就是为什么导入代理的CA证书到手机客户端可以抓取https的原因。
四、证书验证
通过前文的说明,相信大家对https有了一个大致的了解。那么问题来了,如何防止这些”中间人攻击”呢?对于app证书验证,这已经是一个老生常谈的问题了。但是市场上仍然有很多app没有做好证书验证,有些只做了部分验证,例如检查证书域名是否匹配和证书是否过期,更多的则根本不进行验证,从而导致中间人攻击的发生。进行证书验证需要全面进行,只做一部分会导致中间人攻击。对于安全要求不是特别高的app,可以使用以下验证方式:
- 检查证书是否过期。
- 检查服务器证书上的域名是否与服务器的实际域名匹配。
- 进行证书链验证。
可以参考上述验证方式。虽然在导入CA公钥证书到客户端后,这种验证方式会导致中间人攻击,但攻击门槛相对较高。因此,对于安全要求不是特别高的app,可以采用此方法进行防御。对于安全要求较高的app(例如金融应用),上述方法可能还不够,此时可以采用以下更安全的验证方式:将服务器证书打包放入app中,在建立https连接时,使用本地证书和网络下发的证书进行一致性验证。这种验证方式即使导入CA公钥证书也无法进行中间人攻击,但相应的维护成本会相对较高。例如,服务器证书过期或更换时,如果app不升级,就无法使用。因此,可以进行一些改进,生成一对RSA公私钥,将公钥硬编码到app中,私钥放在服务器上。在https握手之前,可以通过服务器下发证书信息,例如公钥、颁发机构、签名等。该下发的信息使用服务器的私钥进行签名。通过app中预置的公钥进行验签,得到证书信息并存储在内容中供后续使用。发起https连接时,获取服务器的证书,并通过比较两个证书信息的一致性进行证书验证。这样可以避免强制升级的问题。然而,这种验证方式的效率是否会降低呢?答案是肯定的。因此,对于安全要求一般的应用,可以使用第一种方法;对于一些安全要求较高的应用,例如金融企业,可以选择第二种方法。
说了这么多,但问题还是会出现!现在的应用通常采用混合开发,会使用许多webview直接加载HTML5页面。上述方法只解决了Java层证书验证的问题,并没有涉及webview内部的证书验证。对于这种情况,我们该怎么办呢?既然问题出现了,那么我们一起来看看解决方案。对于webview加载HTML5进行证书验证的方法如下:
- webview创建实例并加载网页,通过onPageStart方法返回URL地址。
- 将返回的地址转发到Java层,使用上述的证书验证代码进行验证。
- 如果证书验证出错,则使用stopLoading()方法停止网页加载;如果证书验证通过,则正常加载。
支付宝官网签名证书。除了进行证书链验证外,还会进行另一个协议,即在线证书状态协议(Online Certificate Status Protocol)。该协议用于实时查询证书是否被吊销,客户端发送证书信息并请求查询,服务器返回正常、吊销或未知状态中的任何一个。查询地址将附在证书中供客户端使用。
- Server Hello Done
这是一个零字节的信息,用于告知客户端整个Server Hello过程已经结束。
- ClientKeyExchange
在验证证书有效后,客户端发送ClientKeyExchange消息。在ClientKeyExchange消息中,会设置48字节的premaster secret。通过密钥交换算法加密并发送premaster secret的值。例如,通过RSA公钥加密premaster secret,得到Encrypted PreMaster,并传递给服务端。premaster secret的前两个字节是TLS的版本号,该字段用于防止版本回退攻击。
到目前为止,握手包中已经出现了三个随机数(客户端的randomc,服务端的randoms,premaster secret)。使用这三个随机数以及一定的算法,可以获得对称加密AES的加密主密钥Master-key。主密钥的生成非常巧妙。
- Change Cipher Spec
发送一个未加密的信息,浏览器使用该信息通知服务器后续的通信都将采用协商的通信密钥和加密算法进行加密通信。
- Encrypted Handshake Message
验证加密算法的有效性,结合之前所有通信参数的哈希值和其他相关信息,生成一段数据。使用协商密钥session secret和算法进行加密,然后发送给服务器,用于数据和握手验证。通过验证,说明加密算法有效。
- Changecipherspec
在Encrypted Handshake Message验证通过后,服务器同样发送changecipherspec,以通知客户端后续的通信都将采用协商的密钥和算法进行加密通信。
- Encrypted Handshake Message
同样地,服务器也会发送一个Encrypted Handshake Message,供客户端验证加密算法的有效性。
- Application Data
经过一系列的计算,一切准备就绪,后续传输的数据可以通过主密钥master key进行加密传输。加密数据可以在图中的Encrypted Application Data字段中查看。至此,https的完整握手和数据加密传输完成。
在https中还有许多可以优化的地方,还有许多精妙的设计。例如,为了避免频繁进行完整的https握手对性能产生影响,可以通过sessionid来避免同一个客户端重复完成握手。然而,由于sessionid消耗的内存性能较大,因此出现了new session ticket。如果客户端表明支持Session Ticket,并且服务器也支持,那么在TLS握手的最后一步,服务器将包含一个”New Session Ticket”信息,其中包含了加密通信所需的信息。这些数据使用只有服务器知道的密钥进行加密。客户端存储这个Session Ticket,并可以在随后的会话中将其添加到ClientHello消息的SessionTicket扩展中。尽管所有会话信息都只存储在客户端上,但由于密钥只有服务器知道,因此Session Ticket仍然是安全的。这样既避免了性能消耗,又保证了会话的安全性。
最后,我们可以使用openssl命令直观地查看https握手的过程。