近日,苹果向iOS用户推送了一个安全更新,指出在iOS系统中SSL/TLS安全连接存在严重的bug,但并没有给出更详细的说明。对此问题的解答已经出现在Hacker News的头条,我想大家都已经知道了这个漏洞,也不需要再胡乱猜测了。
以下就是导致这个bug的一段代码:
static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,
uint8_t *signature, UInt16 signatureLen)
{
OSStatus err;
...
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
...
fail:
SSLFreeBuffer(&signedHashes);
SSLFreeBuffer(&hashCtx);
return err;
}
注意其中有两个连续的go to fail语句,第一个会正确地在if判断为真时执行,但是第二个却在任意情况下都会执行,尽管它有着看似标准的语句缩进。于是当代码跳转至fail时,由于认证使用的final方法还未执行,而update方法执行成功,因此err会包含一个校验成功的信息,导致对签名的认证永远不会失败。
认证签名时将会检测ServerKeyExchange消息中的签名,它用于DHE和ECDHE加密套件(多种加密算法联合使用)在建立连接时获取会话密钥(ephemeral key,本次会话的临时密钥)。服务器告诉客户端:“这是给你的会话密钥和签名,通过我的证书,你可以确定密钥和签名是来自于我的。”而现在会话密钥和证书链之间的关联已经断裂,所有曾经安全的认证都不再有效。这意味着,服务器可以向客户端发送正确的证书链,但在连接握手的过程中使用错误的私钥进行签名甚至干脆不签名,因为我们无法确认这个服务器是否持有对应此证书的正确的私钥。
这个Bug出现在SecureTransport的代码中,它将影响iOS的某个早期版本直到7.0.6(其中7.0.4我已经确认过),同时也会影响OS X系统(在10.9.1上已经得到确认)。所有使用了SecureTransport的地方都会被波及到,也就等于是绝大部分苹果系统上的软件。Chrome和Firefox在SSL/TLS连接中使用的NSS,因此得以幸免。然而如果你的软件更新程序使用了SecureTransport,那么前面的讨论都不能说明什么了。(译注:更新程序可能连接到仿冒主机。)
对此我构建了一个简单的测试网站。注意端口号(1226是这个漏洞在CVE里的编号),443端口运行着一个正常的网站,而1226端口的网站将会发送使用错误私钥签名的证书。如果你使用https连接去访问,就能够重现这个bug。
即使证书链是正确的,由于它和连接握手之间的关联已经被破坏,我认为任何形式的证书锁定都无法阻止这种错误的认证。同时,这个bug不仅仅影响使用DHE或者ECDHE加密套件的网站,因为攻击者可以自行选择合适的加密套件。
在TLS 1.2的针对ServerKeyExchange消息的认证是使用的另一个方法,因此没有受到影响。但仍然有上面提到的问题,攻击者可以选择任何客户端能够使用的版本。当然如果客户端仅支持TLS 1.2,那就完全没有问题了。客户端也可以只使用明文-RSA加密套件,那么就不存在ServerKeyExchange消息,同样起到了防范的效果。(当然在这两种方法中,前一种更加可取。)
根据我的测试发现,iOS 7.0.6已经修复了这个问题,但是在OS X 10.9.1中仍然存在。(补充:好像这个bug在OS X系统中是在10.9版引入的,但是iOS6的某些版本中就早已出现了。iOS 6.1.6昨天修复了此bug。)这样潜伏在代码深处的bug简直就是一个噩梦。我相信这仅仅是个失误,但无论是谁手滑(手贱)把这样的代码写出来,我都为他感到深切的悲痛。
下面是一段和这个bug有相同问题的代码:
extern int f();
int g() {
int ret = 1;
goto out;
ret = f();
out:
return ret;
}
如果我编译时候加上参数-Wall(启用所有警告),在Xcode中无论是GCC 4.8.2还是Clang 3.3都没有对死代码发出警告,对此我非常惊讶。更好的编译警告本可以阻止这样的悲剧,又或许在实际编码中这类警告发生第一类错误(弃真错误)的概率太高?(感谢Peter Nelson指出在Clang可以使用-Wunreachable-code参数对这样的问题发出警告,而不是-Wall。)
看起来是允许if代码块不使用大括号才导致了这样的代码风格,但是有人在大括号里也可能使用错误的代码缩进,因此我也没觉得大括号带来了多少便利。
写一个测试用例本可以发现这个问题,但是由于它深嵌在连接握手的过程中,所以非常复杂。这个用例需要写一个完全独立的TLS栈,并且包含大量发送无效握手请求的配置。在Chromium中我们有个修改过的TLSLite工具可以做类似的测试,但是我不太记得我们的用例是否完全适用于这个bug的情况。(如果不行的话,听起来好像我已经知道周一早上要干嘛了)(译注:当然是把用例改到可以完全适用)
代码审查对发现这类bug非常有效。不仅仅是浏览审阅,而是审查每句新写的代码。我不知道苹果一般怎么做代码审查,但是我充分相信我的同事,Wan-Teh和Ryan Sleevi。如果我意外手滑,他们一定会及时发现。可惜不是每个人都有幸和这样的同事一起工作。
最近,针对苹果忽略对证书中主机的校验这个事情,有一系列讨论。的确在OS X中使用curl时,即使IP地址不在证书中,命令行也会接受和这个主机的连接。然而我没找到更多问题了,Safari也没有受到影响。
最新内容请见作者的GitHub页:http://qaseven.github.io/