上一篇《iOS安全系列之一:HTTPS》被CocoaChina轉(zhuǎn)載,還順便上了下頭條: 打造安全的App瑟慈!iOS安全系列之 HTTPS炒刁,但那篇文章只是介紹了比較偏應(yīng)用的初級知識,對于想要深入了解HTTPS的同學(xué)來說是遠遠不夠的水醋,剛好本人最近工作上也遇到并解決了一些HTTPS相關(guān)的問題,以此為契機彪置,決定寫這篇更深入介紹HTTPS的文章拄踪。
本文分為以下五節(jié):
- 中間人攻擊:介紹中間人攻擊常見方法,并模擬了一個簡單的中間人攻擊拳魁;
- 校驗證書的正確姿勢:介紹校驗證書的一些誤區(qū)惶桐,并討論了正確校驗方式;
-
ATS:討論下 iOS 9.0 新發(fā)布的的特性
App Transport Security
; - 調(diào)試SSL/TLS:討論使用Wireshark進行SSL/TLS調(diào)試的方法姚糊;
- 后記
其中第1節(jié)“中間人”是比較常見基礎(chǔ)的知識贿衍,網(wǎng)上也可以找到相關(guān)的資料,如果對中間人攻擊已經(jīng)有了足夠的了解叛拷,可以跳過舌厨。后面幾節(jié)則是個人在iOS方面的實踐總結(jié),除了一些與系統(tǒng)相關(guān)的特性外忿薇,大部分都是系統(tǒng)無關(guān)的通用知識裙椭,并且每一章節(jié)都比較獨立,所以可以直接跳到感興趣的地方閱讀署浩。
1. 中間人攻擊
關(guān)于HTTPS揉燃,我經(jīng)常會提到的就是中間人攻擊,那究竟什么是中間人攻擊呢筋栋?中間人攻擊炊汤,即所謂的Man-in-the-middle attack(MITM),顧名思義弊攘,就是攻擊者插入到原本直接通信的雙方抢腐,讓雙方以為還在直接跟對方通訊,但實際上雙方的通信對方已變成了中間人襟交,信息已經(jīng)是被中間人獲取或篡改迈倍。
當然,本文并不是科普性文章捣域,本節(jié)就針對HTTPS攻擊啼染,特別是HTTPS在App這一應(yīng)用場景下的常見的攻擊手段進行分析討論。
由前文我們知道焕梅,HTTPS在建立了TCP連接之后迹鹅,會進行SSL握手(SSL Handshake)來校驗證書,協(xié)商加密協(xié)議和對稱加密的密鑰贞言,之后就會使用協(xié)商好的密鑰來進行傳輸斜棚。所以HTTPS攻擊一般分為SSL連接建立前的攻擊,以及HTTPS傳輸過程中的攻擊该窗;
常見的HTTPS中間人攻擊打肝,首先需要結(jié)合ARP、DNS欺騙等技術(shù)挪捕,來對會話進行攔截粗梭,
1.1 SSL證書欺騙攻擊
此類攻擊較為簡單常見。首先通過ARP欺騙级零、DNS劫持甚至網(wǎng)關(guān)劫持等等断医,將客戶端的訪問重定向到攻擊者的機器滞乙,讓客戶端機器與攻擊者機器建立HTTPS連接(使用偽造證書),而攻擊者機器再跟服務(wù)端連接鉴嗤。這樣用戶在客戶端看到的是相同域名的網(wǎng)站斩启,但瀏覽器會提示證書不可信,用戶不點擊繼續(xù)瀏覽就能避免被劫持的醉锅。所以這是最簡單的攻擊方式兔簇,也是最容易識別的攻擊方式。
此類攻擊有個經(jīng)典的工具:SSLSniff硬耍。SSLSniff是大神Moxie Marlinspike開發(fā)的工具垄琐,該工具一開始是設(shè)計用于上一篇文章中提到的Basic Constaints 漏洞的,這類系統(tǒng)級別的漏洞经柴,基本上可以讓你不知不覺狸窘;現(xiàn)在的操作系統(tǒng)和瀏覽器基本修復(fù)了這一漏洞。但也可以使用SSLSniff來偽造證書實現(xiàn)釣魚攻擊坯认。
防范措施:
釣魚類攻擊翻擒,App直接調(diào)用系統(tǒng)API創(chuàng)建的HTTPS連接(NSURLConnection
)一般不會受到影響,只使用默認的系統(tǒng)校驗牛哺,只要系統(tǒng)之前沒有信任相關(guān)的偽造證書陋气,校驗就直接失敗,不會SSL握手成功引润;但如果是使用WebView瀏覽網(wǎng)頁恩伺,需要在UIWebView中加入較強的授權(quán)校驗,禁止用戶在校驗失敗的情況下繼續(xù)訪問椰拒。
1.2 SSL剝離攻擊(SSLStrip)
SSL剝離,即將HTTPS連接降級到HTTP連接凰荚。假如客戶端直接訪問HTTPS的URL燃观,攻擊者是沒辦法直接進行降級的,因為HTTPS與HTTP雖然都是TCP連接便瑟,但HTTPS在傳輸HTTP數(shù)據(jù)之前缆毁,需要在進行了SSL握手,并協(xié)商傳輸密鑰用來后續(xù)的加密傳輸到涂;假如客戶端與攻擊者進行SSL握手脊框,而攻擊者無法提供可信任的證書來讓客戶端驗證通過進行連接,所以客戶端的系統(tǒng)會判斷為SSL握手失敗践啄,斷開連接浇雹。
該攻擊方式主要是利用用戶并不會每次都直接在瀏覽器上輸入https://xxx.xxx.com來訪問網(wǎng)站,或者有些網(wǎng)站并非全網(wǎng)HTTPS屿讽,而是只在需要進行敏感數(shù)據(jù)傳輸時才使用HTTPS的漏洞昭灵。中間人攻擊者在劫持了客戶端與服務(wù)端的HTTP會話后,將HTTP頁面里面所有的https://
超鏈接都換成http://
,用戶在點擊相應(yīng)的鏈接時烂完,是使用HTTP協(xié)議來進行訪問试疙;這樣,就算服務(wù)器對相應(yīng)的URL只支持HTTPS鏈接抠蚣,但中間人一樣可以和服務(wù)建立HTTPS連接之后祝旷,將數(shù)據(jù)使用HTTP協(xié)議轉(zhuǎn)發(fā)給客戶端,實現(xiàn)會話劫持嘶窄。
這種攻擊手段更讓人難以提防怀跛,因為它使用HTTP,不會讓瀏覽器出現(xiàn)HTTPS證書不可信的警告护侮,而且用戶很少會去看瀏覽器上的URL是https://
還是http://
敌完。特別是App的WebView中,應(yīng)用一般會把URL隱藏掉羊初,用戶根本無法直接查看到URL出現(xiàn)異常滨溉。
防范措施:
該種攻擊方式同樣無法劫持App內(nèi)的HTTPS連接會話,因為App中傳入請求的URL參數(shù)是固定帶有https://
的长赞;但在WebView中打開網(wǎng)頁同樣需要注意晦攒,在非全網(wǎng)HTTPS的網(wǎng)站,建議對WebView中打開的URL做檢查得哆,檢查應(yīng)該使用https://
的URL是否被篡改為http://
脯颜;也建議服務(wù)端在配置HTTPS服務(wù)時,加上“HTTP Strict Transport Security”配置項贩据。
1.3 針對SSL算法進行攻擊
上述兩種方式栋操,技術(shù)含量較低,而且一般只能影響 WebApp饱亮,而很難攻擊到 Native App 矾芙, 所以高階的 Hacker,會直接針對SSL算法相關(guān)漏洞進行攻擊近上,期間會使用很多的密碼學(xué)相關(guān)手段剔宪。由于本人非專業(yè)安全相關(guān)人員,沒有多少相關(guān)實踐經(jīng)驗壹无,所以本節(jié)不會深入講解相關(guān)的攻擊原理和手段葱绒,有興趣的同學(xué)可以查看以下拓展閱讀:
防范措施:
這類攻擊手段是利用SSL算法的相關(guān)漏洞,所以最好的防范措施就是對服務(wù)端 SSL/TLS 的配置進行升級:
- 只支持盡量高版本的TLS(最低TLSv1)斗锭;
- 禁用一些已爆出安全隱患的加密方法地淀;
- 使用2048位的數(shù)字證書;
1.4 模擬最簡單的攻擊
經(jīng)過上述幾種攻擊方式的說明之后岖是,我們來模擬下最簡單的中間人攻擊骚秦。
中間人攻擊步驟方式的上文已經(jīng)說過了她倘,流量劫持相關(guān)操作不是本文重點,可以參考流量劫持是如何產(chǎn)生的作箍?硬梁, 本例直接使用Charles來做代理,對流量進行劫持胞得。并使用SSL代理來模擬下對iPhone設(shè)備HTTPS請求的中間人攻擊荧止,讓大家在思考理解中間人攻擊方式的同時,了解在開發(fā)中如何防范類似的攻擊阶剑。
1) Charles設(shè)置代理
在Charles中開啟并設(shè)置HTTP代理和SSL代理跃巡,Menu -> Proxy -> Proxy Setting,設(shè)置如圖:
HTTP代理設(shè)置牧愁,注意記住端口號為:8888
SSL代理設(shè)置素邪,在Locations上可以設(shè)置想要進行SSL代理的域名,這里以百度的百付寶*.baifubao.com
為模擬對象猪半。
2) 在iPhone端設(shè)置HTTP代理
在Mac上獲取當前機器的IP地址:
ifconfig en0
:
還有一個簡單的方法兔朦,按住option+點擊頂部菜單欄的WiFi網(wǎng)絡(luò)圖標:
可以看到當前電腦的IP地址為:192.168.199.249
。
將iPhone連接到與電腦相同的WiFi磨确,在iPhone設(shè)置中:無線局域網(wǎng) -> 已連接WiFi右邊的Info詳情圖標 -> HTTP代理 -> 手動 -> 設(shè)置HTTP代理:
設(shè)置完成之后沽甥,打開Safari隨便訪問一個網(wǎng)頁,初次設(shè)置代理的話乏奥,Charles會彈出一個iPhone請求代理的確認框摆舟,點擊Allow即可。然后在Charles上就可以看到iPhone上的HTTP請求了邓了。為了避免Mac上的請求過多影響對被代理iPhone上HTTP請求的查看和調(diào)試恨诱,可以在Charles取消Mac的代理:Menu -> Proxy -> 取消勾選Mac OS X Proxy 即可。
假如你訪問的是被代理的目標 URL http://www.baifubao.com 則打不開網(wǎng)頁骗炉。因為iPhone的HTTPS請求已經(jīng)被Charles攔截照宝,但iPhone無法信任Charles的證書,所以SSL Handshake失敗痕鳍,無法建立HTTPS連接。
3) 偽造證書欺騙
在被代理的iPhone上打開Safari龙巨,訪問http://www.charlesproxy.com/getssl笼呆,會彈出安裝描述符文件的界面,該描述文件包含了Charles根證書:
注意:這個Charles證書是內(nèi)置在Charles中的旨别,可以在菜單Help -> SSL Proxying可以直接保存和安裝證書诗赌。安裝后的描述文件可以在iPhone設(shè)備的設(shè)置 -> 通用 -> 描述文件進行查看和管理。
“安裝”完成之后秸弛,就會將Charles根證書加入系統(tǒng)可信任證書列表中铭若,使用該證書簽發(fā)的子證書也會被系統(tǒng)信任洪碳。Charles會為之前SSL代理設(shè)置中配置的域名生成對應(yīng)的SSL證書,這樣偽造證書的證書就實現(xiàn)了欺騙叼屠⊥纾可以使用Mac SSL代理查看下:
4) 結(jié)果驗證
下載百度App,然后登錄賬號镜雨,在我 -> 我的錢包嫂侍,就會訪問百付寶:
看到已成功獲取到HTTPS請求包的內(nèi)容。從這里荚坞,我們可以猜測出該App是使用系統(tǒng)默認的校驗方式:系統(tǒng)信任了這個中間人服務(wù)器返回的SSL證書挑宠,App就信任了這一校驗,SSL握手成功颓影;而沒有對服務(wù)器證書進行本地對比校驗各淀。這是當下非常多App存在的安全隱患。
這個簡單的SSL代理模擬了簡單釣魚式的中間人攻擊诡挂,大家應(yīng)該都基本明白了這種攻擊方式的所針對的漏洞碎浇,以及防范這種攻擊方法的措施:
- 不要隨意連入公共場合內(nèi)的WiFi,或者使用未知代理服務(wù)器
- 不要安裝不可信或突然出現(xiàn)的描述文件咆畏,信任偽造的證書南捂;
- App內(nèi)部需對服務(wù)器證書進行單獨的對比校驗,確認證書不是偽造的旧找;
2. 校驗證書的正確姿勢
上一節(jié)對中間人攻擊進行了簡單介紹溺健,本節(jié)就上一節(jié)我們遇到的安全隱患問題,來討論下在App中钮蛛,應(yīng)該怎么校驗服務(wù)器返回的SSL證書鞭缭,來保證HTTPS通信的安全。上一篇文章《iOS安全系列之一:HTTPS》有對基本校驗過程相關(guān)代碼進行講解魏颓,本文不會贅述這些細節(jié)岭辣,而是主要討論校驗證書中幾個重要的點:
2.1 域名驗證
前不久,iOS上最知名的網(wǎng)絡(luò)開源庫AFNetworking爆出HTTPS校驗漏洞甸饱,該漏洞是因為其校驗策略模塊 AFSecurityPolicy
內(nèi)的參數(shù) validatesDomainName
默認為NO沦童,這會導(dǎo)致校驗證書的時候不會校驗這個證書對應(yīng)的域名。即請求返回的服務(wù)器證書叹话,只要是可信任CA機構(gòu)簽發(fā)的偷遗,都會校驗通過,這是非常嚴重的漏洞驼壶。該漏洞已在v2.5.2版本中修復(fù)氏豌,對應(yīng)Git版本號3e631b203dd95bb82dfbcc2c47a2d84b59d1eeb4。
這個漏洞以及AFNetworking的相關(guān)源碼會讓很多人以為系統(tǒng)的默認校驗是不校驗證書對應(yīng)域名的热凹,實際上并非如此泵喘。這里AFNetworking確有畫蛇添足之嫌泪电。首先我們查看下系統(tǒng)的默認校驗策略:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//1)獲取trust object
SecTrustRef trust = challenge.protectionSpace.serverTrust;
//獲取默認的校驗策略
CFArrayRef defaultPolicies = NULL;
SecTrustCopyPolicies(serverTrust, &defaultPolicies);
NSLog(@"Default Trust Policies: %@", (__bridge id)defaultPolicies);
//...
}
</figure>
打印默認校驗策略信息:
5 : <CFString 0x197814dc0 [0x196ea5fa0]>{contents = "ValidRoot"} = <CFBoolean 0x196ea6340 [0x196ea5fa0]>{value = true}
6 : <CFString 0x197814b20 [0x196ea5fa0]>{contents = "SSLHostname"} = <CFString 0x170226b60 [0x196ea5fa0]>{contents = "xxx.xxx.com"}
8 : <CFString 0x197814da0 [0x196ea5fa0]>{contents = "ValidLeaf"} = <CFBoolean 0x196ea6340 [0x196ea5fa0]>{value = true}
從打印信息來看,系統(tǒng)的默認校驗策略中已包含了域名校驗纪铺。然后再看AFSecurityPolicy
中相關(guān)源碼:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
//...
}
</figure>
這其實也是很多開發(fā)者在處理異常與默認邏輯分支時會犯的錯誤相速,這段邏輯推薦實現(xiàn)方式是:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
//取代validatesDomainName,默認為NO霹陡,就是系統(tǒng)默認行為
@property (nonatomic, assign) BOOL skipDomainNameValidation;
//校驗
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (self.skipDomainNameValidation) {
NSMutableArray *policies = [NSMutableArray array];
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
}
//...
}
</figure>
從代碼上看和蚪,邏輯是否變得更清晰了?而且也表明系統(tǒng)默認的校驗方式是會驗證域名的烹棉。實際上調(diào)用SecTrustSetPolicies
來重新設(shè)置校驗策略攒霹,主要是用于使用IP進行HTTPS請求,或者一個證書用于多個域名的場景浆洗;在這些場景下催束,服務(wù)器證書上的域名和請求域名(可能是IP,也可能是其他域名)就會出現(xiàn)不一致伏社,導(dǎo)致校驗不通過抠刺;這就需要重新設(shè)置下校驗策略,把這個證書對應(yīng)的域名設(shè)置下摘昌。詳細說明請查看官方文檔:《Overriding TLS Chain Validation Correctly》
2.2 校驗證書鏈速妖?
上一篇文章介紹系統(tǒng)驗證SSL證書的方法和流程時,不是已經(jīng)說明了會對證書鏈進行層層校驗聪黎,以保證證書的可信么罕容?為什么還需要討論這一問題?其實本節(jié)要討論的是AFNetworking
中validatesCertificateChain
的問題稿饰。
先說明下結(jié)果:在AFNetworking
最新發(fā)布的V2.6.0锦秒,已經(jīng)將該特性去掉了。相關(guān)的討論:SSL Pinning: What Should Be Certificate Chain Validation Expected Behavior?#2744
AFNetworking
中實現(xiàn)的驗證證書鏈喉镰,是將App本地打包好的證書與服務(wù)器返回的證書鏈進行數(shù)據(jù)上的一一對比旅择,只有打包到App的證書中包含了服務(wù)器返回的證書鏈上的所有證書,校驗才會通過侣姆。如google的SSL證書:
開啟validatesCertificateChain
后請求https://google.com生真,需要將GeoTrust Global CA、Google Internet Authority G2和google.com的證書都導(dǎo)入App中才能驗證通過捺宗。請回憶下上一篇文章關(guān)于證書鏈的可信任機制柱蟀,會發(fā)現(xiàn)這是完全沒有必要的;證書鏈的驗證偿凭,主要由三部分來保證證書的可信:葉子證書是對應(yīng)HTTPS請求域名的證書产弹,根證書是被系統(tǒng)信任的證書派歌,以及這個證書鏈之間都是層層簽發(fā)可信任鏈弯囊;證書之所以能成立痰哨,本質(zhì)是基于信任鏈,這樣任何一個節(jié)點證書加上域名校驗(CA機構(gòu)不會為不同的對不同的用戶簽發(fā)相同域名的證書)匾嘱,就確定一條唯一可信證書鏈斤斧,所以不需要每個節(jié)點都驗證。
2.3打包證書校驗
那是否就不需要在App中打包證書進行驗證了呢霎烙?
這時需要想想為什么偽造證書是可以實現(xiàn)中間人攻擊的撬讽?答案就在于用戶讓系統(tǒng)信任了不應(yīng)該信任的證書。用戶設(shè)置系統(tǒng)信任的證書悬垃,會作為錨點證書(Anchor Certificate)來驗證其他證書游昼,當返回的服務(wù)器證書是錨點證書或者是基于該證書簽發(fā)的證書(可以是多個層級)都會被信任。這就是基于信任鏈校驗方式的最大弱點尝蠕。我們不能完全相信系統(tǒng)的校驗烘豌,因為系統(tǒng)的校驗依賴的證書的源很可能被污染了。這就需要選取一個節(jié)點證書看彼,打包到App中廊佩,作為Anchor Certificate來保證證書鏈的唯一性和可信性。
所以還是需要App本地打包證書靖榕,使用SecTrustSetAnchorCertificates(SecTrustRef trust, CFArrayRef anchorCertificates)
來設(shè)置Anchor Certificate進行校驗标锄。需要注意的是,官方文檔《Certificate, Key, and Trust Services Reference》針對傳入的 Anchor Certificates 有說明:
IMPORTANT
Calling this function without also calling SecTrustSetAnchorCertificatesOnly disables the trusting of any anchors other than the ones specified by this function call.
也就是說茁计,單純調(diào)用SecTrustSetAnchorCertificates
方法后不調(diào)用SecTrustSetAnchorCertificatesOnly
來驗證證書料皇,則只會相信SecTrustSetAnchorCertificates
傳入的證書,而不會信任其他錨點證書簸淀。關(guān)于這一點瓶蝴,SecTrustSetAnchorCertificatesOnly
方法參數(shù)講解中也有說明:
anchorCertificatesOnly:
If true, disables trusting any anchors other than the ones passed in with the SecTrustSetAnchorCertificates function. If false, the built-in anchor certificates are also trusted. If SecTrustSetAnchorCertificates is called and SecTrustSetAnchorCertificatesOnly is not called, only the anchors explicitly passed in are trusted.
只相信傳入的錨點證書,也就只會驗證通過由這些錨點證書簽發(fā)的證書租幕。這樣就算被驗證的證書是由系統(tǒng)其他信任的錨點證書簽發(fā)的舷手,也無法驗證通過。
最后一個問題:選擇證書鏈的哪一節(jié)點作為錨點證書打包到App中劲绪?很多開發(fā)者會直接選擇葉子證書男窟。其實對于自建證書來說,選擇哪一節(jié)點都是可行的贾富。而對于由CA頒發(fā)的證書歉眷,則建議導(dǎo)入頒發(fā)該證書的CA機構(gòu)證書或者是更上一級CA機構(gòu)的證書,甚至可以是根證書颤枪。這是因為:
一般葉子證書的有效期都比較短汗捡,Google和Baidu官網(wǎng)證書的有效期也就幾個月;而App由于是客戶端,需要一定的向后兼容扇住,稍疏于檢查春缕,今天發(fā)布,過兩天證書就過期了艘蹋。
越往證書鏈的末端锄贼,證書越有可能變動;比如葉子證書由特定域名(aaa.bbb.com)改為通配域名(*.bbb.com)等等女阀。短期內(nèi)的變動宅荤,重新部署后,有可能舊版本App更新不及時而出現(xiàn)無法訪問的問題浸策。
因此使用CA機構(gòu)證書是比較合適的冯键,至于哪一級CA機構(gòu)證書,并沒有完全的定論庸汗,你可以自己評估選擇琼了。
3. ATS
在本文發(fā)表的時間(2015-09-03),大部分的iOS開發(fā)同學(xué)應(yīng)該升級到iOS9了夫晌,在iOS9下進行HTTP/HTTPS請求時會遇到如下錯誤:
Request failed: Error Domain=NSURLErrorDomain Code=-1022 “The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.” UserInfo=0x7fbb4a158f00 {NSUnderlyingError=0x7fbb4a1141c0 “The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.”, NSErrorFailingURLStringKey=http://api.xxx.com/mobile, NSErrorFailingURLKey=http://api.xxx.com/mobile, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}
這是iOS9中一個重大的更新:App Transport Security雕薪,簡稱ATS。ATS對使用NSURLConnection, CFURL, 或NSURLSession 等 APIs 進行網(wǎng)絡(luò)請求的行為作了一系列的強制要求晓淀,反逼服務(wù)器配置所袁,以提高網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)陌踩裕?/p>
These are the App Transport Security requirements:
- The server must support at least Transport Layer Security (TLS) protocol version 1.2.
- Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
- Certificates must be signed using a SHA256 or better signature hash algorithm, with either a 2048 bit or greater RSA key or a 256 bit or greater Elliptic-Curve (ECC) key. Invalid certificates result in a hard failure and no connection.
ATS要求運行在iOS9的App,需將HTTP連接升級到HTTPS凶掰,并且TLS版本不得低于v1.2燥爷;而且規(guī)定了支持的加密套件(Cipher Suite)和證書簽名的哈希算法;如果想要向前兼容的話懦窘,可以通過設(shè)置Info.plist來降低校驗強度前翎,具體可以看這篇文章:Configuring App Transport Security Exceptions in iOS 9 and OSX 10.11。
本人升級到iOS9 GM版畅涂,從App Store上下載了一些并沒有完全支持ATS的應(yīng)用港华,使用起來也完全沒有問題,估計iOS系統(tǒng)對使用低于SDK9編譯的App做了兼容午衰,這方面也是符合預(yù)期的立宜,畢竟ATS的影響實在太大,基本上沒有任何的App能夠幸免臊岸,比如圖片下載一般使用HTTP橙数,而不會使用HTTPS。所以建議可以暫時使用NSAllowsArbitraryLoads
來取消ATS的限制帅戒,后續(xù)慢慢完善對ATS的支持灯帮。
日益復(fù)雜脆弱的網(wǎng)絡(luò)難以保證用戶的數(shù)據(jù)安全,因此Apple才在iOS9上強推ATS,反向逼迫服務(wù)端升級钟哥,以提供更安全的網(wǎng)絡(luò)環(huán)境响疚。建議開發(fā)者不要簡單地將ATS禁用,而應(yīng)該升級服務(wù)器的配置支持ATS瞪醋,為用戶提供更安全的服務(wù)。
4. 調(diào)試SSL/TLS
開發(fā)一個新的App装诡,通常終端和后端先協(xié)商好了具體業(yè)務(wù)邏輯的通信協(xié)議银受,后端和終端按照協(xié)議實現(xiàn)邏輯之后,就進入聯(lián)調(diào)階段鸦采,第一次聯(lián)調(diào)往往會回到很多問題宾巍,包括數(shù)據(jù)格式不對,缺少基礎(chǔ)字段等渔伯;假如是基于HTTPS的網(wǎng)絡(luò)請求顶霞,則很可能由于后臺配置問題,導(dǎo)致遇到如CFNetwork SSLHandshake failed (-9824)
這類握手失敗的錯誤锣吼。面對這類SSL錯誤选浑,該如何來解決呢?根據(jù)本人經(jīng)驗玄叠,主要是分兩步:
4.1 錯誤碼
這會不會太簡單了古徒?其實最簡單的往往是最有效的。SSL相關(guān)錯誤碼可以在<Security/SecureTransport.h>
中找到读恃。上面-9824
的錯誤百框,對應(yīng)的是errSSLPeerHandshakeFail = -9824, /* handshake failure */
肖粮,其他常見的錯誤碼還有:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
//...
/* fatal errors detected by peer */
errSSLPeerUnexpectedMsg = -9819, /* unexpected message received */
errSSLPeerBadRecordMac = -9820, /* bad MAC */
errSSLPeerDecryptionFail = -9821, /* decryption failed */
errSSLPeerRecordOverflow = -9822, /* record overflow */
errSSLPeerDecompressFail = -9823, /* decompression failure */
errSSLPeerHandshakeFail = -9824, /* handshake failure */
errSSLPeerBadCert = -9825, /* misc. bad certificate */
errSSLPeerUnsupportedCert = -9826, /* bad unsupported cert format */
errSSLPeerCertRevoked = -9827, /* certificate revoked */
errSSLPeerCertExpired = -9828, /* certificate expired */
errSSLPeerCertUnknown = -9829, /* unknown certificate */
errSSLIllegalParam = -9830, /* illegal parameter */
errSSLPeerUnknownCA = -9831, /* unknown Cert Authority */
errSSLPeerAccessDenied = -9832, /* access denied */
/* more errors detected by us */
errSSLHostNameMismatch = -9843, /* peer host name mismatch */
errSSLConnectionRefused = -9844, /* peer dropped connection before responding */
errSSLDecryptionFail = -9845, /* decryption failure */
errSSLBadRecordMac = -9846, /* bad MAC */
errSSLRecordOverflow = -9847, /* record overflow */
errSSLBadConfiguration = -9848, /* configuration error */
//...
</figure>
但靠錯誤碼只能判斷大概的情況,很多時候并不能明確知道到底是什么原因?qū)е碌模宰钪庇^的第煮,還是需要抓包分析。
4.2 抓包分析
在這一階段磺送,使用Charles來抓包是沒有用的代虾,因為Charles是作為HTTP代理工作的,它會抓取代理的網(wǎng)絡(luò)報文艇肴,然后將報文組合成HTTP/HTTPS協(xié)議包篡撵,對于HTTP調(diào)試非常方便,但由于細節(jié)的缺失豆挽,沒辦法使用它來分析SSL相關(guān)錯誤育谬。所以我們需要使用上古神器Wireshark。
關(guān)于Wireshark就不再多介紹了帮哈,網(wǎng)上已經(jīng)有很多相關(guān)介紹和抓包教程膛檀,如《Mac OS X上使用Wireshark抓包》等,基本上可以很快上手。下面我們就以適配iOS9的ATS為例咖刃,來說下如何進行抓包分析泳炉,找出因為不支持ATS導(dǎo)致SSL握手失敗問題。
還記得SSL握手過程么嚎杨?不記得可以重溫下這篇文章:圖解SSL/TLS協(xié)議花鹅。我們也來看看Wireshark上抓取到的包來直觀學(xué)習(xí)正常的SSL握手流程:
上圖是一個標準的HTTPS請求抓取的包:
- 在TCP三次握手成功之后,客戶端發(fā)起SSL的
Client Hello
(No.68幀)枫浙,傳遞隨機數(shù)(Random)刨肃,和客戶端支持的加密套件(Cipher Suites)、壓縮方法箩帚、簽名算法等信息真友; 如下圖所示,這是Client Hello
所攜帶的信息紧帕,可以展開來看相關(guān)的詳情:
- 服務(wù)器從
Client Hello
中匹配支持的加密套件(Cipher Suites)盔然、壓縮算法和簽名算法,和服務(wù)器新生成的一個隨機數(shù)返回給客戶端是嗜,這就是Server Hello
(No.70幀)愈案。 下圖就是對1)中Client Hello
的回應(yīng),由圖可以看出鹅搪,服務(wù)端匹配的Cipher Suite是TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
- 服務(wù)器同時會將證書發(fā)給客戶端(No.73幀)刻帚;有時候抓取的包只有
Client Hello
和Server Hello
,而沒有再發(fā)送證書的涩嚣,這是SSL/TLS的Session重用了:由于新建立一個SSL/TLS Session的成本太高崇众,所以之前有建立SSL/TLS連接Session的話,客戶端會保存Session ID航厚,在下一次請求時在Client Hello
中帶上顷歌,服務(wù)端驗證有效之后,就會成功重用Sesssion幔睬。
注:關(guān)于重用TLS Session眯漩,在特定場景下會引發(fā)嚴重的問題:當App只針對了代碼中發(fā)起的HTTPS請求做了本地證書校驗,而WebView中發(fā)起的HTTPS請求并沒有做本地證書校驗麻顶,可能會出現(xiàn)App內(nèi)代碼發(fā)起的請求直接重用WebView中建立的HTTPS鏈接赦抖,導(dǎo)致中間人可以實現(xiàn)短暫的繞過攻擊。
拓展閱讀:
- RFC5246#Handshake Protocol Overview查看Handshake的流程和相關(guān)信息辅肾。
- Apple官方開發(fā)文檔:TLS Session Cache
客戶端確認證書有效队萤,則會生產(chǎn)最后一個隨機數(shù)(Premaster secret),并使用證書的公鑰RSA加密這個隨機數(shù)矫钓,發(fā)回給服務(wù)端要尔。為了更高的安全性舍杜,會改為Diffie-Hellman算法(簡稱DH算法);采用DH算法赵辕,最后一個隨機數(shù)(Premaster secret)是不需要傳遞的既绩,客戶端和服務(wù)端交換參數(shù)之后就可以算出。
Client Key Exchange
(No. 75幀)还惠;接下來雙方都會發(fā)送
Change Cipher Spec
通知對方饲握,接下來的所有消息都會使用簽名約定好的密鑰進行加密通信。最后是雙方的
Finished Message
(即Encrypted Handshake Message
蚕键, No. 77救欧、79幀),這個消息是最終的校驗嚎幸,里面包含了握手過程中的Session Key等信息,如果對方能夠解密這個消息則表示握手成功寄猩,結(jié)束整個SSL Handshake流程嫉晶。
掌握了SSL/TLS握手流程之后,調(diào)試SSL/TLS就會變得非常簡單田篇,只需要看在哪個環(huán)節(jié)報錯(Alert)替废,就可以基本推斷出相關(guān)的錯誤。
相關(guān)SSL/TLS接口信息泊柬,請查看:RFC5246以及SSL/TLS in Detail
下面就列舉下調(diào)試適配ATS過程中遇到的主要問題:
- 加密套件(Cipher Suite)等參數(shù)無法匹配:加密套件不匹配是最常見的握手失敗的例子椎镣。
在ATS中,可接受的加密套件有包括:
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
但往往很多服務(wù)器的HTTPS配置很久沒有升級兽赁,沒辦法支持這些Cipher Suite状答;客戶端發(fā)送Client Hello
給服務(wù)端,帶上支持加密套件參數(shù)刀崖;服務(wù)端查看這些參數(shù)惊科,發(fā)現(xiàn)一個都不支持,則直接返回Handshake Failure
的信息亮钦。如下圖:
一般在接受到客戶端發(fā)送的Client Hello
后返回Handshake Failure
馆截,都是因為服務(wù)端無法匹配客戶端SSL握手參數(shù)。至于是不是加密套件這個參數(shù)匹配的問題蜂莉,建議抓取取消ATS了的正常HTTPS請求包進行對比蜡娶,找出具體不匹配的參數(shù)。
SSL/TLS版本過低映穗,這個也非常常見窖张,但一般會被上一個參數(shù)不匹配的錯誤所掩蓋。因為大多數(shù)SSL/TLS版本低的服務(wù)器HTTPS配置支持的加密套件等參數(shù)版本也比較低蚁滋,而SSL/TLS版本是客戶端收到
Server Hello
之后才驗證的荤堪,但前面握手失敗就走不到這一步了合陵。所以加密套件(Cipher Suite)等參數(shù)無法匹配支持,一般也就意味著服務(wù)端SSL/TLS版本過低澄阳。證書鏈配置錯誤:在開發(fā)過程中拥知,本人遇到過證書鏈沒有按照順序進行配置的問題,也遇到過只配置了葉子證書的問題碎赢。對于這些問題低剔,可以直接查看SSL握手過程中,服務(wù)端返回的
Certificate
包:
上圖可以看到證書鏈Certificates
只有一個肮塞,這是典型的配置錯誤襟齿。
PS:使用Wireshark進行抓包的時候,有時候由于一些HTTPS請求的SSL/TLS版本號太低枕赵,Wireshark沒辦法辨認其是SSL包猜欺,而是顯示為TCP;此時可以手動來Decode:選擇對應(yīng)的TCP數(shù)據(jù)幀拷窜,右鍵 -》Decode As -》Transport 選擇SSL -》Apply既可开皿。
5. 后記
這個時代,安全重要么篮昧?這是我曾常疑惑的赋荆。90%以上的大眾對安全沒有切實的概念,即使安全上了春晚懊昨,過了熱潮一切又重歸原樣窄潭。特別最近換工作到保險金融類公司,安全問題更是觸目驚心酵颁。一直相信嫉你,人如同一個圓,你知道的越多躏惋,學(xué)的越深均抽,接觸的越廣,圓就越大其掂,越知道自己的渺小油挥,越懂得敬畏。
這世界永遠不會缺少矛和盾款熬,沒有“Mission Impossible”深寥,不是么?