// 補充更新:據福布斯報導,國外安全研究人員發現,gotofail 安全漏洞不止影響 iOS、OS X 和 Safari(有流量被劫持風險),已有證據表明,這個漏洞還將影響 Mail、Twitter、iMessage,甚至是蘋果的軟體更新機制。
// 感謝@終曲_ 翻譯此文
2月21日蘋果向iOS使用者推送了一個安全更新,指出在iOS系統中SSL/TLS安全連線存在嚴重的bug,但並沒有給出更詳細的說明。對此問題的解答已經出現在Hacker News的頭條,我想大家都已經知道了這個漏洞,也不需要再胡亂猜測了。
以下就是導致這個bug的一段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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; } |
引自Apple’s published source code
注意其中有兩個連續的goto 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有相同問題的程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
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也沒有受到影響。