蘋果 SSL/TLS Bug的細節
字體: 小 中 大 | 上一篇 下一篇 | 打印 | 我要投稿 | 推薦標簽: Bug 蘋果 測試技術
近日,蘋果向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,那么前面的討論都不能說明什么了。(譯注:更新程序可能連接到仿冒主機。)
對此我構建了一個簡單的測試網站:https://www.imperialviolet.org:1266。注意端口號(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也沒有受到影響。