原文來自烏云
隨著安全的普及,https通信應用越發廣泛,但是由于對https不熟悉導致開發人員頻繁錯誤的使用https,例如最常見的是未校驗https證書從而導致“中間人攻擊”,并且由于修復方案也一直是個坑,導致修復這個問題時踩各種坑,故謹以此文簡單的介紹相關問題。
一、前言
本文第一節主要講述https的握手過程,第二節主要講述常見的“https中間人攻擊”場景,第三節主要介紹證書校驗修復方案,各位看官可根據自己口味瀏覽。
二、HTTPS握手過程
首先來看下https的工作原理,上圖大致介紹了https的握手流程,后續我們通過抓包看下每個握手包到底干了些什么神奇的事。
注:本文所有內容以TLS_RSA_WITH_AES_128_CBC_SHA加密組件作為基礎進行說明,其他加密組件以及TLS版本會存在一定差異,例如TLS1. 3 針對移動客戶端有了很大的改動,現在的ECDHE等密鑰交換算法與RSA作為密鑰交換算法也完全不一樣,所以有些地方和大家實際操作會存在一定出入。
1.TCP三次握手
我訪問的支付寶的官網www.alipay.com抓取的數據。
2.Client Hello
TLS的版本號和隨機數random_c:這個是用來生成最后加密密鑰的因子之一,它包含兩部分,時間戳和隨機數 session-id:用來標識會話,第一次握手時為空,如果以前建立過,可以直接帶過去從而避免完全握手 Cipher Suites加密組件列表:瀏覽器所支持的加密算法的清單客戶端支持的加密簽名算法的列表,讓服務器進行選擇 擴展字段:比如密碼交換算法的參數、請求主機的名字,用于單ip多域名的情況指定域名。
3.Sever Hello
隨機數rando_s,這個是用來生成最后加密密鑰的因子之一,包含兩部分,時間戳和隨機數 32 字節的SID,在我們想要重新連接到該站點的時候可以避免一整套握手過程。 在客戶端提供的加密組件中,服務器選擇了TLS_RSA_WITH_AES_128_CBC_SHA組件。
4.Certificate
證書是https里非常重要的主體,可用來識別對方是否可信,以及用其公鑰做密鑰交換。可以看見證書里面包含證書的頒發者,證書的使用者,證書的公鑰,頒發者的簽名等信息。其中Issuer Name是簽發此證書的CA名稱,用來指定簽發證書的CA的可識別的唯一名稱(DN, Distinguished Name),用于證書鏈的認證,這樣通過各級實體證書的驗證,逐漸上溯到鏈的終止點,即可信任的根CA,如果到達終點在自己的信任列表內未發現可信任的CA則認為此證書不可信。
驗證證書鏈的時候,用上一級的公鑰對證書里的簽名進行解密,還原對應的摘要值,再使用證書信息計算證書的摘要值,最后通過對比兩個摘要值是否相等,如果不相等則認為該證書不可信,如果相等則認為該級證書鏈正確,以此類推對整個證書鏈進行校驗。
二級機構的證書。
三、中間人攻擊
https握手過程的證書校驗環節就是為了識別證書的有效性唯一性等等,所以嚴格意義上來說https下不存在中間人攻擊,存在中間人攻擊的前提條件是沒有嚴格的對證書進行校驗,或者人為的信任偽造證書,下面一起看下幾種常見的https“中間人攻擊”場景。
1.證書未校驗
由于客戶端沒有做任何的證書校驗,所以此時隨意一張證書都可以進行中間人攻擊,可以使用burp里的這個模塊進行中間人攻擊。
通過瀏覽器查看實際的https證書,是一個自簽名的偽造證書。
2.部分校驗
做了部分校驗,例如在證書校驗過程中只做了證書域名是否匹配的校驗,可以使用burp的如下模塊生成任意域名的偽造證書進行中間人攻擊。
實際生成的證書效果,如果只做了域名、證書是否過期等校驗可輕松進行中間人攻擊(由于chrome是做了證書校驗的所以會提示證書不可信任)。
3.證書鏈校驗
如果客戶端對證書鏈做了校驗,那么攻擊難度就會上升一個層次,此時需要人為的信任偽造的證書或者安裝偽造的CA公鑰證書從而間接信任偽造的證書,可以使用burp的如下模塊進行中間人攻擊。
4.手機客戶端Https數據包抓取
上述第一、二種情況不多加贅述,第三種情況就是我們經常使用的抓手機應用https數據包的方法,即導入代理工具的公鑰證書到手機里,再進行https數據包的抓取。導入手機的公鑰證書在android平臺上稱之為受信任的憑據,在ios平臺上稱之為描述文件,可以通過openssl的命令直接查看我們導入到手機客戶端里的這個PortSwiggerCA.crt。
可以看見是Issuer和Subject一樣的自簽名CA公鑰證書,另外我們也可以通過證書類型就可以知道此為公鑰證書,crt、der格式的證書不支持存儲私鑰或證書路徑(有興趣的同學可查找證書相關信息)。導入CA公鑰證書之后,參考上文的證書校驗過程不難發現通過此方式能通過證書鏈校驗,從而形成中間人攻擊,客戶端使用代理工具的公鑰證書加密隨機數,代理工具使用私鑰解密并計算得到對稱加密密鑰,再對數據包進行解密即可抓取明文數據包。
5.中間人攻擊原理
一直在說中間人攻擊,那么中間人攻擊到底是怎么進行的呢,下面我們通過一個流行的MITM開源庫mitmproxy來分析中間人攻擊的原理。中間人攻擊的關鍵在于https握手過程的ClientKeyExchange,由于pre key交換的時候是使用服務器證書里的公鑰進行加密,如果用的偽造證書的公鑰,那么中間人就可以解開該密文得到pre_master_secret計算出用于對稱加密算法的master_key,從而獲取到客戶端發送的數據;然后中間人代理工具再使用其和服務端的master_key加密傳輸給服務端;同樣的服務器返回給客戶端的數據也是經過中間人解密再加密,于是完整的https中間人攻擊過程就形成了,一圖勝千言,來吧。
通過讀Mitmproxy的源碼發現mitmproxy生成偽造證書的函數如下:
通過上述函數一張完美偽造的證書就出現了,使用瀏覽器通過mitmproxy做代理看下實際偽造出來的證書。
可以看到實際的證書是由mimtproxy頒發的,其中的公鑰就是mimtproxy自己的公鑰,后續的加密數據就可以使用mimtproxy的私鑰進行解密了。如果導入了mitmproxy的公鑰證書到客戶端,那么該偽造的證書就可以完美的通過客戶端的證書校驗了。這就是平時為什么導入代理的CA證書到手機客戶端能抓取https的原因。
四、證書校驗
通過上文第一和第二部分的說明,相信大家已經對https有個大概的了解了,那么問題來了,怎樣才能防止這些“中間人攻擊”呢?
app證書校驗已經是一個老生常談的問題了,但是市場上還是有很多的app未做好證書校驗,有些只做了部分校驗,例如檢查證書域名是否匹配證書是否過期,更多數的是根本就不做校驗,于是就造成了中間人攻擊。做證書校驗需要做完全,只做一部分都會導致中間人攻擊,對于安全要求并不是特別高的app可使用如下校驗方式:
查看證書是否過期
服務器證書上的域名是否和服務器的實際域名相匹配
校驗證書鏈
可參考 http://drops.wooyun.org/tips/3296 此類校驗方式雖然在導入CA公鑰證書到客戶端之后會造成中間人攻擊,但是攻擊門檻已相對較高,所以對于安全要求不是特別高的app可采用此方法進行防御。對于安全有較高要求一些app(例如金融)上述方法或許還未達到要求,那么此時可以使用如下更安全的校驗方式,將服務端證書打包放到app里,再建立https鏈接時使用本地證書和網絡下發證書進行一致性校驗。
此類校驗即便導入CA公鑰證書也無法進行中間人攻擊,但是相應的維護成本會相對升高,例如服務器證書過期,證書更換時如果app不升級就無法使用,那么可以改一下,生成一對RSA的公私鑰,公鑰可硬編碼在app,私鑰放服務器。 https握手前可通過服務器下發證書信息,例如公鑰、辦法機構、簽名等,該下發的信息使用服務器里的私鑰進行簽名; 通過app里預置的公鑰驗簽得到證書信息并存在內容中供后續使用; 發起https連接獲取服務器的證書,通過對比兩個證書信息是否一致進行證書校驗。
這樣即可避免強升的問題,但是問題又來了,這樣效率是不是低太多了?答案是肯定的,所以對于安全要求一般的應用使用第一種方法即可,對于一些安全要求較高的例如金融企業可選擇第二種方法。
說了挺多,但是該來的問題還是會來啊!現在的app一般采用混合開發,會使用很多webveiw直接加載html 5 頁面,上面的方法只解決了java層證書校驗的問題,并沒有涉及到webview里面的證書校驗,對于這種情況怎么辦呢?既然問題來了那么就一起說說解決方案,對于webview加載html 5 進行證書校驗的方法如下:
webview創建實例加載網頁時通過onPageStart方法返回url地址; 將返回的地址轉發到java層使用上述的證書校驗代碼進行進行校驗; 如果證書校驗出錯則使用stoploading()方法停止網頁加載,證書校驗通過則正常加載。
支付寶官網簽名證書。
不僅僅進行證書鏈的校驗,此時還會進行另一個協議即Online Certificate Status Protocol, 該協議為證書狀態在線查詢協議,一個實時查詢證書是否吊銷的方式,客戶端發送證書的信息并請求查詢,服務器返回正常、吊銷或未知中的任何一個狀態,這個查詢地址會附在證書中供客戶端使用。
5.Server Hello Done
這是一個零字節信息,用于告訴客戶端整個server hello過程已經結束。
6.ClientKeyExchange
客戶端在驗證證書有效之后發送ClientKeyExchange消息,ClientKeyExchange消息中,會設置 48 字節的premaster secret(因為的TLS版本的原因,這里沒有顯示premaster),通過密鑰交換算法加密發送premaster secret的值,例如通過 RSA公鑰加密premaster secret的得到Encrypted PreMaster傳給服務端。PreMaster前兩個字節是TLS的版本號,該版本號字段是用來防止版本回退攻擊的。
從握手包到目前為止,已經出現了三個隨機數(客戶端的random_c,服務端的random_s,premaster secret),使用這三個隨機數以及一定的算法即可獲得對稱加密AES的加密主密鑰Master-key,主密鑰的生成非常的精妙。
7.Change Cipher Spec
發送一個不加密的信息,瀏覽器使用該信息通知服務器后續的通信都采用協商的通信密鑰和加密算法進行加密通信。
8.Encrypted Handshake Message
驗證加密算法的有效性,結合之前所有通信參數的 hash 值與其它相關信息生成一段數據,采用協商密鑰 session secret 與算法進行加密,然后發送給服務器用于數據與握手驗證,通過驗證說明加密算法有效。
9.Change_cipher_spec
Encrypted Handshake Message通過驗證之后,服務器同樣發送 change_cipher_spec 以通知客戶端后續的通信都采用協商的密鑰與算法進行加密通信。
10.Encrypted Handshake Message
同樣的,服務端也會發送一個Encrypted Handshake Message供客戶端驗證加密算法有效性。
11.Application Data
經過一大串的的計算之后,終于一切就緒,后續傳輸的數據可通過主密鑰master key進行加密傳輸,加密數據查看圖中的Encrypted Apploication Data字段數據,至此https的一次完整握手以及數據加密傳輸終于完成。
https里還有很多可優化并且很多精妙的設計,例如為了防止經常進行完整的https握手影響性能,于是通過sessionid來避免同一個客戶端重復完成握手,但是又由于sessionid消耗的內存性能比較大,于是又出現了new session ticket,如果客戶端表明它支持Session Ticket并且服務端也支持,那么在TLS握手的最后一步服務器將包含一個“New Session Ticket”信息,其中包含了一個加密通信所需要的信息,這些數據采用一個只有服務器知道的密鑰進行加密。這個Session Ticket由客戶端進行存儲,并可以在隨后的一次會話中添加到 ClientHello消息的SessionTicket擴展中。雖然所有的會話信息都只存儲在客戶端上,但是由于密鑰只有服務器知道,所以Session Ticket仍然是安全的,因此這不僅避免了性能消耗還保證了會話的安全性。
最后我們可以使用openssl命令來直觀的看下https握手的流程: