iOS安全筆記

這一篇文章, 加上裏面連接的幾篇文章(一個百度的, 兩個阮一峯的), 全看完應該瞭解得差很少了html

如何打造一個安全的App?這是每個移動開發者必須面對的問題。在移動App開發領域,開發工程師對於安全方面的考慮廣泛比較欠缺,而因爲iOS平臺的封閉性,遭遇到的安全問題相比於Android來講要少得多,這就致使了許多iOS開發人員對於安全性方面沒有太多的深刻,但對於一個合格的軟件開發者來講,安全知識是必備知識之一。ios

對於未越獄的iOS設備來講,因爲強大的沙箱和受權機制,以及Apple本身掌控的App Store, 基本上杜絕了惡意軟件的入侵(非越獄)。但除系統安全以外,咱們仍是面臨不少的安全問題:網絡安全、數據安全等,每一項涉及也很是廣,安全是很是大的課題,本人並不是專業的安全專家,只是從開發者的角度,分析咱們常遇到的各項安全問題,並提出一般的解決方法,與各位同窗交流學習。git

每個軟件工程師都有義務保護用戶數據的隱私和安全。github



首先是網絡安全,OSI模型各層都會面臨相應的網絡安全問題,涉及寬廣,而網絡安全也是安全領域發展最爲繁榮的領域。本文咱們只是從移動應用開發角度,以儘可能簡單的方式,講解HTTPS核心概念知識,以及在iOS平臺上的實現。建議如今還在使用HTTP的應用都升級到HTTPS。objective-c

引讀:互聯網全站HTTPS的時代已經到來算法

 

1. HTTPS

其實HTTPS從最終的數據解析的角度,與HTTP沒有任何的區別,HTTPS就是將HTTP協議數據包放到SSL/TSL層加密後,在TCP/IP層組成IP數據報去傳輸,以此保證傳輸數據的安全;而對於接收端,在SSL/TSL將接收的數據包解密以後,將數據傳給HTTP協議層,就是普通的HTTP數據。HTTP和SSL/TSL都處於OSI模型的應用層。從HTTP切換到HTTPS是一個很是簡單的過程,在作具體的切換操做以前,咱們須要瞭解幾個概念:後端



SSL/TSL

關於SSL/TSL,阮一峯的兩篇博客文章作了很好的介紹:api

簡單的來講,SSL/TSL經過四次握手,主要交換三個信息:數組

  1. 數字證書:該證書包含了公鑰等信息,通常是由服務器發給客戶端,接收方經過驗證這個證書是否是由信賴的CA簽發,或者與本地的證書相對比,來判斷證書是否可信;假如須要雙向驗證,則服務器和客戶端都須要發送數字證書給對方驗證;
  2. 三個隨機數:這三個隨機數構成了後續通訊過程當中用來對數據進行對稱加密解密的「對話密鑰」瀏覽器

    首先客戶端先發第一個隨機數N1,而後服務器回了第二個隨機數N2(這個過程同時把以前提到的證書發給客戶端),這兩個隨機數都是明文的;而第三個隨機數N3(這個隨機數被稱爲Premaster secret),客戶端用數字證書的公鑰進行非對稱加密,發給服務器;而服務器用只有本身知道的私鑰來解密,獲取第三個隨機數。這樣,服務端和客戶端都有了三個隨機數N1+N2+N3,而後兩端就使用這三個隨機數來生成「對話密鑰」,在此以後的通訊都是使用這個「對話密鑰」來進行對稱加密解密。由於這個過程當中,服務端的私鑰只用來解密第三個隨機數,歷來沒有在網絡中傳輸過,這樣的話,只要私鑰沒有被泄露,那麼數據就是安全的。

  3. 加密通訊協議:就是雙方商量使用哪種加密方式,假如二者支持的加密方式不匹配,則沒法進行通訊;

有個常見的問題,關於隨機數爲何要三個?只最後一個隨機數N3不能夠麼?

這是因爲SSL/TLS設計,就假設服務器不相信全部的客戶端都可以提供徹底隨機數,假如某個客戶端提供的隨機數不隨機的話,就大大增長了「對話密鑰」被破解的風險,因此由三組隨機數組成最後的隨機數,保證了隨機數的隨機性,以此來保證每次生成的「對話密鑰」安全性。



數字證書

數字證書是一個電子文檔,其中包含了持有者的信息、公鑰以及證實該證書有效的數字簽名。而數字證書以及相關的公鑰管理和驗證等技術組成了PKI(公鑰基礎設施)規範體系。通常來講,數字證書是由數字證書認證機構(Certificate authority,即CA)來負責簽發和管理,並承擔PKI體系中公鑰合法性的檢驗責任;數字證書的類型有不少,而HTTPS使用的是SSL證書。

怎麼來驗證數字證書是由CA簽發的,而不是第三方僞造的呢? 在回答這個問題前,咱們須要先了解CA的組織結構。首先,CA組織結構中,最頂層的就是根CA,根CA下能夠受權給多個二級CA,而二級CA又能夠受權多個三級CA,因此CA的組織結構是一個樹結構。對於SSL證書市場來講,主要被Symantec(旗下有VeriSign和GeoTrust)、Comodo SSL、Go Daddy 和 GlobalSign 瓜分。 瞭解了CA的組織結構後,來看看數字證書的簽發流程:


數字證書的簽發流程

數字證書的簽發機構CA,在接收到申請者的資料後進行覈對並肯定信息的真實有效,而後就會製做一份符合X.509標準的文件。證書中的證書內容包含的持有者信息和公鑰等都是由申請者提供的,而數字簽名則是CA機構對證書內容進行hash加密後獲得的,而這個數字簽名就是咱們驗證證書是不是有可信CA簽發的數據。


數字證書的驗證流程

假設上圖證書是由證書籤發機構CA1簽發的。

1)接收端接到一份數字證書Cer1後,對證書的內容作Hash獲得H1;

2)從簽發該證書的機構CA1的數字證書中找到公鑰,對證書上數字簽名進行解密,獲得證書Cer1簽名的Hash摘要H2;

3)對比H1和H2,如相等,則表示證書沒有被篡改。

4)但這個時候仍是不知道CA是不是合法的,咱們看到上圖中有CA機構的數字證書,這個證書是公開的,全部人均可以獲取到。而這個證書中的數字簽名是上一級生成的,因此能夠這樣一直遞歸驗證下去,直到根CA。根CA是自驗證的,即他的數字簽名是由本身的私鑰來生成的。合法的根CA會被瀏覽器和操做系統加入到權威信任CA列表中,這樣就完成了最終的驗證。因此,必定要保護好本身環境(瀏覽器/操做系統)中根CA信任列表,信任了根CA就表示信任全部根CA下全部子級CA所簽發的證書,不要隨便添加根CA證書。

通常操做系統和瀏覽器只包含根CA機構的證書,而在配置Web服務器的HTTPS時,也會將配置整個證書鏈,因此整個校驗流程是從最後的葉子節點證書開始,用父節點校驗子節點,一層層校驗整個證書鏈的可信性。

打個比喻:父(根CA數字證書)-子(CA數字證書)-孫(數字證書)三代人,假設父沒有其餘兄弟(至關於根CA機構是惟一的),假如子與父進行DNA親子鑑定,檢測DNA位點(即證書籤名)相同,那就基本肯定子是由父所生;孫與子同樣。這樣就可以肯定孫確定是源於父一脈,是父(根CA數字證書)的合法繼承人。數字證書的驗證就是基於一樣的原理。



Basic Constraint校驗漏洞

那是否無論多少層均可以這樣一直信任下去呢?理論上是可行的,但會遇到一個問題。假設我從可信CA機構購買了一張證書,使用這張證書籤發的證書是否也會被操做系統和瀏覽器信任呢?明顯是不該該相信的,由於我並非CA機構,假如我簽發的證書也被信任的話,那我徹底能夠本身簽發任何域名的證書來進行僞造攻擊。這就是著名的Basic Constraint校驗漏洞,X.509證書中的Basic Constraint包含了這是否是一個CA機構,以及有效證書路徑的最大深度(即,這個CA還可否繼續簽發CA機構證書及其簽發子CA證書的路徑深度)。但在幾年前,包括微軟和Apple都爆出了沒有正確校驗這些信息的漏洞。

Basic Constraint信息請看下圖:


Google Internet Authority G2

上圖是Google Internet Authority G2的證書,該證書是個CA機構證書;路徑深度爲0,表示該證書沒法再簽發CA證書,只能簽發客戶證書(client certificate)。


google.com

上圖是google.com的證書,這是個客戶證書(client certificate),不可再簽發子證書,因此由該證書籤發的子證書是不會被信任的。

瞭解了上面關於SSL/TSL通訊加密策略以及數字證書的概念以後,對HTTPS的安全機制就有了個初步的瞭解,下面咱們看如何在iOS上實現對HTTPS的支持。



2. 實現支持HTTPS

首先,須要明確你使用HTTP/HTTPS的用途,由於OSX和iOS平臺提供了多種API,來支持不一樣的用途,官方文檔《Making HTTP and HTTPS Requests》有詳細的說明,而文檔《HTTPS Server Trust Evaluation》則詳細講解了HTTPS驗證相關知識,這裏就很少說了。本文主要講解咱們最經常使用的NSURLConnection支持HTTPS的實現(NSURLSession的實現方法相似,只是要求受權證實的回調不同而已),以及怎麼樣使用AFNetworking這個很是流行的第三方庫來支持HTTPS。本文假設你對HTTP以及NSURLConnection的接口有了足夠的瞭解。

 

驗證證書的API

相關的Api在Security Framework中,驗證流程以下:

1). 第一步,先獲取須要驗證的信任對象(Trust Object)。這個Trust Object在不一樣的應用場景下獲取的方式都不同,對於NSURLConnection來講,是從delegate方法-connection:willSendRequestForAuthenticationChallenge:回調回來的參數challenge中獲取([challenge.protectionSpace serverTrust])。

2). 使用系統默認驗證方式驗證Trust Object。SecTrustEvaluate會根據Trust Object的驗證策略,一級一級往上,驗證證書鏈上每一級數字簽名的有效性(上一部分有講解),從而評估證書的有效性。

3). 如第二步驗證經過了,通常的安全要求下,就能夠直接驗證經過,進入到下一步:使用Trust Object生成一份憑證([NSURLCredential credentialForTrust:serverTrust]),傳入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])處理,創建鏈接。

4). 假若有更強的安全要求,能夠繼續對Trust Object進行更嚴格的驗證。經常使用的方式是在本地導入證書,驗證Trust Object與導入的證書是否匹配。更多的方法能夠查看Enforcing Stricter Server Trust Evaluation,這一部分在講解AFNetworking源碼中會講解到。

5). 假如驗證失敗,取消這次Challenge-Response Authentication驗證流程,拒絕鏈接請求。

ps: 假如是自建證書的,則不使用第二步系統默認的驗證方式,由於自建證書的根CA的數字簽名未在操做系統的信任列表中。

iOS受權驗證的API和流程大概瞭解了,下面,咱們看看在NSURLConnection中的代碼實現:

 

使用NSURLConnection支持HTTPS的實現

 

// Now start the connection NSURL * httpsURL = [NSURL URLWithString:@"https://www.google.com"]; self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:httpsURL] delegate:self]; //回調 - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { //1)獲取trust object SecTrustRef trust = challenge.protectionSpace.serverTrust; SecTrustResultType result; //2)SecTrustEvaluate對trust進行驗證 OSStatus status = SecTrustEvaluate(trust, &result); if (status == errSecSuccess && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) { //3)驗證成功,生成NSURLCredential憑證cred,告知challenge的sender使用這個憑證來繼續鏈接 NSURLCredential *cred = [NSURLCredential credentialForTrust:trust]; [challenge.sender useCredential:cred forAuthenticationChallenge:challenge]; } else { //5)驗證失敗,取消此次驗證流程 [challenge.sender cancelAuthenticationChallenge:challenge]; } }

 

上面是代碼是經過系統默認驗證流程來驗證證書的。假如咱們是自建證書的呢?這樣Trust Object裏面服務器的證書由於不是可信任的CA簽發的,因此直接使用SecTrustEvaluate進行驗證是不會成功。又或者,即便服務器返回的證書是信任CA簽發的,又如何肯定這證書就是咱們想要的特定證書?這就須要先在本地導入證書,設置成須要參與驗證的Anchor Certificate(錨點證書,經過SecTrustSetAnchorCertificates設置了參與校驗錨點證書以後,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書自己,則信任該證書),再調用SecTrustEvaluate來驗證。代碼以下

 

//先導入證書 NSString * cerPath = ...; //證書的路徑 NSData * cerData = [NSData dataWithContentsOfFile:cerPath]; SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData)); self.trustedCertificates = @[CFBridgingRelease(certificate)]; //回調 - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { //1)獲取trust object SecTrustRef trust = challenge.protectionSpace.serverTrust; SecTrustResultType result; //注意:這裏將以前導入的證書設置成下面驗證的Trust Object的anchor certificate SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates); //2)SecTrustEvaluate會查找前面SecTrustSetAnchorCertificates設置的證書或者系統默認提供的證書,對trust進行驗證 OSStatus status = SecTrustEvaluate(trust, &result); if (status == errSecSuccess && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) { //3)驗證成功,生成NSURLCredential憑證cred,告知challenge的sender使用這個憑證來繼續鏈接 NSURLCredential *cred = [NSURLCredential credentialForTrust:trust]; [challenge.sender useCredential:cred forAuthenticationChallenge:challenge]; } else { //5)驗證失敗,取消此次驗證流程 [challenge.sender cancelAuthenticationChallenge:challenge]; } }

 

建議採用本地導入證書的方式驗證證書,來保證足夠的安全性。更多的驗證方法,請查看官方文檔《HTTPS Server Trust Evaluation》

 

使用AFNetworking來支持HTTPS

AFNetworking是iOS/OSX開發最流行的第三方開源庫之一,其做者是很是著名的iOS/OSX開發者Mattt Thompson,其博客NSHipster也是iOS/OSX開發者學習和開闊技術視野的好地方。AFNetworking已經將上面的邏輯代碼封裝好,甚至更完善,在AFSecurityPolicy文件中,有興趣能夠閱讀這個模塊的代碼;

AFNetworking上配置對HTTPS的支持很是簡單:

 

NSURL * url = [NSURL URLWithString:@"https://www.google.com"]; AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url]; dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue"); requestOperationManager.completionQueue = requestQueue; AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; //allowInvalidCertificates 是否容許無效證書(也就是自建的證書),默認爲NO //若是是須要驗證自建證書,須要設置爲YES securityPolicy.allowInvalidCertificates = YES; //validatesDomainName 是否須要驗證域名,默認爲YES; //假如證書的域名與你請求的域名不一致,需把該項設置爲NO;如設成NO的話,即服務器使用其餘可信任機構頒發的證書,也能夠創建鏈接,這個很是危險,建議打開。 //置爲NO,主要用於這種狀況:客戶端請求的是子域名,而證書上的是另一個域名。由於SSL證書上的域名是獨立的,假如證書上註冊的域名是www.google.com,那麼mail.google.com是沒法驗證經過的;固然,有錢能夠註冊通配符的域名*.google.com,但這個仍是比較貴的。 //如置爲NO,建議本身添加對應域名的校驗邏輯。 securityPolicy.validatesDomainName = YES; //validatesCertificateChain 是否驗證整個證書鏈,默認爲YES //設置爲YES,會將服務器返回的Trust Object上的證書鏈與本地導入的證書進行對比,這就意味着,假如你的證書鏈是這樣的: //GeoTrust Global CA // Google Internet Authority G2 // *.google.com //那麼,除了導入*.google.com以外,還須要導入證書鏈上全部的CA證書(GeoTrust Global CA, Google Internet Authority G2); //如是自建證書的時候,能夠設置爲YES,加強安全性;假如是信任的CA所簽發的證書,則建議關閉該驗證,由於整個證書鏈一一比對是徹底沒有必要(請查看源代碼); securityPolicy.validatesCertificateChain = NO; requestOperationManager.securityPolicy = securityPolicy;

 

這就是AFNetworking的支持HTTPS的主要配置說明,AFHTTPSessionManager與之基本一致,就不重複了。



3. 總結

雖然HTTPS相比於HTTP來講,會有必定的性能上的劣勢,但對於網絡飛速發展,移動設備的性能成倍增加的今天,安全才是咱們更應該去考慮的。全網HTTPS並非那麼遙遠。

下一篇準備講內存數據安全和持久化數據的安全,敬請期待。

 

======第二篇===

 

本文分爲如下五節:

  1. 中間人攻擊:介紹中間人攻擊常見方法,並模擬了一個簡單的中間人攻擊;
  2. 校驗證書的正確姿式:介紹校驗證書的一些誤區,並討論了正確校驗方式;
  3. ATS:討論下 iOS 9.0 新發布的的特性App Transport Security
  4. 調試SSL/TLS:討論使用Wireshark進行SSL/TLS調試的方法;
  5. 後記

其中第1節「中間人」是比較常見基礎的知識,網上也能夠找到相關的資料,若是對中間人攻擊已經有了足夠的瞭解,能夠跳過。後面幾節則是我的在iOS方面的實踐總結,除了一些與系統相關的特性外,大部分都是系統無關的通用知識,而且每一章節都比較獨立,因此能夠直接跳到感興趣的地方閱讀。



1. 中間人攻擊

關於HTTPS,我常常會提到的就是中間人攻擊,那究竟什麼是中間人攻擊呢?中間人攻擊,即所謂的Man-in-the-middle attack(MITM),顧名思義,就是攻擊者插入到本來直接通訊的雙方,讓雙方覺得還在直接跟對方通信,但實際上雙方的通訊對方已變成了中間人,信息已是被中間人獲取或篡改。


MITM

固然,本文並非科普性文章,本節就針對HTTPS攻擊,特別是HTTPS在App這一應用場景下的常見的攻擊手段進行分析討論。

由前文咱們知道,HTTPS在創建了TCP鏈接以後,會進行SSL握手(SSL Handshake)來校驗證書,協商加密協議和對稱加密的密鑰,以後就會使用協商好的密鑰來進行傳輸。因此HTTPS攻擊通常分爲SSL鏈接創建前的攻擊,以及HTTPS傳輸過程當中的攻擊;

常見的HTTPS中間人攻擊,首先須要結合ARP、DNS欺騙等技術,來對會話進行攔截,

 

1.1 SSL證書欺騙攻擊

此類攻擊較爲簡單常見。首先經過ARP欺騙、DNS劫持甚至網關劫持等等,將客戶端的訪問重定向到攻擊者的機器,讓客戶端機器與攻擊者機器創建HTTPS鏈接(使用僞造證書),而攻擊者機器再跟服務端鏈接。這樣用戶在客戶端看到的是相同域名的網站,但瀏覽器會提示證書不可信,用戶不點擊繼續瀏覽就能避免被劫持的。因此這是最簡單的攻擊方式,也是最容易識別的攻擊方式。

此類攻擊有個經典的工具:SSLSniff。SSLSniff是大神Moxie Marlinspike開發的工具,該工具一開始是設計用於上一篇文章中提到的Basic Constaints 漏洞的,這類系統級別的漏洞,基本上可讓你不知不覺;如今的操做系統和瀏覽器基本修復了這一漏洞。但也可使用SSLSniff來僞造證書實現釣魚攻擊。


MITM-Sniff

防範措施:

釣魚類攻擊,App直接調用系統API建立的HTTPS鏈接(NSURLConnection)通常不會受到影響,只使用默認的系統校驗,只要系統以前沒有信任相關的僞造證書,校驗就直接失敗,不會SSL握手成功;但若是是使用WebView瀏覽網頁,須要在UIWebView中加入較強的受權校驗,禁止用戶在校驗失敗的狀況下繼續訪問。

 

1.2 SSL剝離攻擊(SSLStrip

SSL剝離,即將HTTPS鏈接降級到HTTP鏈接。假如客戶端直接訪問HTTPS的URL,攻擊者是沒辦法直接進行降級的,由於HTTPS與HTTP雖然都是TCP鏈接,但HTTPS在傳輸HTTP數據以前,須要在進行了SSL握手,並協商傳輸密鑰用來後續的加密傳輸;假如客戶端與攻擊者進行SSL握手,而攻擊者沒法提供可信任的證書來讓客戶端驗證經過進行鏈接,因此客戶端的系統會判斷爲SSL握手失敗,斷開鏈接。

該攻擊方式主要是利用用戶並不會每次都直接在瀏覽器上輸入https://xxx.xxx.com來訪問網站,或者有些網站並不是全網HTTPS,而是隻在須要進行敏感數據傳輸時才使用HTTPS的漏洞。中間人攻擊者在劫持了客戶端與服務端的HTTP會話後,將HTTP頁面裏面全部的https://超連接都換成http://,用戶在點擊相應的連接時,是使用HTTP協議來進行訪問;這樣,就算服務器對相應的URL只支持HTTPS連接,但中間人同樣能夠和服務創建HTTPS鏈接以後,將數據使用HTTP協議轉發給客戶端,實現會話劫持。

這種攻擊手段更讓人難以提防,由於它使用HTTP,不會讓瀏覽器出現HTTPS證書不可信的警告,並且用戶不多會去看瀏覽器上的URL是https://仍是http://。特別是App的WebView中,應用通常會把URL隱藏掉,用戶根本沒法直接查看到URL出現異常。


MITM-Sniff

防範措施:

該種攻擊方式一樣沒法劫持App內的HTTPS鏈接會話,由於App中傳入請求的URL參數是固定帶有https://的;但在WebView中打開網頁一樣須要注意,在非全網HTTPS的網站,建議對WebView中打開的URL作檢查,檢查應該使用https://的URL是否被篡改成http://;也建議服務端在配置HTTPS服務時,加上「HTTP Strict Transport Security」配置項。

參考:【流量劫持】躲避HSTS的HTTPS劫持

 

1.3 針對SSL算法進行攻擊

上述兩種方式,技術含量較低,並且通常只能影響 WebApp,而很難攻擊到 Native App , 因此高階的 Hacker,會直接針對SSL算法相關漏洞進行攻擊,期間會使用不少的密碼學相關手段。因爲本人非專業安全相關人員,沒有多少相關實踐經驗,因此本節不會深刻講解相關的攻擊原理和手段,有興趣的同窗能夠查看如下拓展閱讀:

 

防範措施:

這類攻擊手段是利用SSL算法的相關漏洞,因此最好的防範措施就是對服務端 SSL/TLS 的配置進行升級:

  • 只支持儘可能高版本的TLS(最低TLSv1);
  • 禁用一些已爆出安全隱患的加密方法;
  • 使用2048位的數字證書;

 

1.4 模擬最簡單的攻擊

通過上述幾種攻擊方式的說明以後,咱們來模擬下最簡單的中間人攻擊。

中間人攻擊步驟方式的上文已經說過了,流量劫持相關操做不是本文重點,能夠參考流量劫持是如何產生的?, 本例直接使用Charles來作代理,對流量進行劫持。並使用SSL代理來模擬下對iPhone設備HTTPS請求的中間人攻擊,讓你們在思考理解中間人攻擊方式的同時,瞭解在開發中如何防範相似的攻擊。

 

1) Charles設置代理

在Charles中開啓並設置HTTP代理和SSL代理,Menu -> Proxy -> Proxy Setting,設置如圖:

HTTP代理設置,注意記住端口號爲:8888

Charles HTTP Proxy

SSL代理設置,在Locations上能夠設置想要進行SSL代理的域名,這裏以百度的百付寶*.baifubao.com爲模擬對象。

Charles SSL Proxy

 

2) 在iPhone端設置HTTP代理

在Mac上獲取當前機器的IP地址:

ifconfig en0:

ifconfig

還有一個簡單的方法,按住option+點擊頂部菜單欄的WiFi網絡圖標:

ifconfig

能夠看到當前電腦的IP地址爲:192.168.199.249

將iPhone鏈接到與電腦相同的WiFi,在iPhone設置中:無線局域網 -> 已鏈接WiFi右邊的Info詳情圖標 -> HTTP代理 -> 手動 -> 設置HTTP代理:

ifconfig

設置完成以後,打開Safari隨便訪問一個網頁,初次設置代理的話,Charles會彈出一個iPhone請求代理的確認框,點擊Allow便可。而後在Charles上就能夠看到iPhone上的HTTP請求了。爲了不Mac上的請求過多影響對被代理iPhone上HTTP請求的查看和調試,能夠在Charles取消Mac的代理:Menu -> Proxy -> 取消勾選Mac OS X Proxy 便可。

假如你訪問的是被代理的目標 URL http://www.baifubao.com 則打不開網頁。由於iPhone的HTTPS請求已經被Charles攔截,但iPhone沒法信任Charles的證書,因此SSL Handshake失敗,沒法創建HTTPS鏈接。

SSLHandshake

 

3) 僞造證書欺騙

在被代理的iPhone上打開Safari,訪問http://www.charlesproxy.com/getssl,會彈出安裝描述符文件的界面,該描述文件包含了Charles根證書:

Charles Root Cer

注意:這個Charles證書是內置在Charles中的,能夠在菜單Help -> SSL Proxying能夠直接保存和安裝證書。安裝後的描述文件能夠在iPhone設備的設置 -> 通用 -> 描述文件進行查看和管理。

「安裝」完成以後,就會將Charles根證書加入系統可信任證書列表中,使用該證書籤發的子證書也會被系統信任。Charles會爲以前SSL代理設置中配置的域名生成對應的SSL證書,這樣僞造證書的證書就實現了欺騙。可使用Mac SSL代理查看下:

Baidu Cer

 

4) 結果驗證

下載百度App,而後登陸帳號,在我 -> 個人錢包,就會訪問百付寶:

Proxy Success

看到已成功獲取到HTTPS請求包的內容。從這裏,咱們能夠猜想出該App是使用系統默認的校驗方式:系統信任了這個中間人服務器返回的SSL證書,App就信任了這一校驗,SSL握手成功;而沒有對服務器證書進行本地對比校驗。這是當下很是多App存在的安全隱患。

這個簡單的SSL代理模擬了簡單釣魚式的中間人攻擊,你們應該都基本明白了這種攻擊方式的所針對的漏洞,以及防範這種攻擊方法的措施:

  • 不要隨意連入公共場合內的WiFi,或者使用未知代理服務器
  • 不要安裝不可信或忽然出現的描述文件,信任僞造的證書;
  • App內部需對服務器證書進行單獨的對比校驗,確認證書不是僞造的;



2. 校驗證書的正確姿式

上一節對中間人攻擊進行了簡單介紹,本節就上一節咱們遇到的安全隱患問題,來討論下在App中,應該怎麼校驗服務器返回的SSL證書,來保證HTTPS通訊的安全。上一篇文章《iOS安全系列之一:HTTPS》有對基本校驗過程相關代碼進行講解,本文不會贅述這些細節,而是主要討論校驗證書中幾個重要的點:

 

2.1 域名驗證

前不久,iOS上最知名的網絡開源庫AFNetworking爆出HTTPS校驗漏洞,該漏洞是由於其校驗策略模塊 AFSecurityPolicy 內的參數 validatesDomainName 默認爲NO,這會致使校驗證書的時候不會校驗這個證書對應的域名。即請求返回的服務器證書,只要是可信任CA機構簽發的,都會校驗經過,這是很是嚴重的漏洞。該漏洞已在v2.5.2版本中修復,對應Git版本號3e631b203dd95bb82dfbcc2c47a2d84b59d1eeb4

這個漏洞以及AFNetworking的相關源碼會讓不少人覺得系統的默認校驗是不校驗證書對應域名的,實際上並不是如此。這裏AFNetworking確有多此一舉之嫌。首先咱們查看下系統的默認校驗策略:

- (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); //... }

打印默認校驗策略信息:

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}

從打印信息來看,系統的默認校驗策略中已包含了域名校驗。而後再看AFSecurityPolicy中相關源碼:

- (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); //... }

這其實也是不少開發者在處理異常與默認邏輯分支時會犯的錯誤,這段邏輯推薦實現方式是:

//取代validatesDomainName,默認爲NO,就是系統默認行爲 @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); } //... }

從代碼上看,邏輯是否變得更清晰了?並且也代表系統默認的校驗方式是會驗證域名的。實際上調用SecTrustSetPolicies來從新設置校驗策略,主要是用於使用IP進行HTTPS請求,或者一個證書用於多個域名的場景;在這些場景下,服務器證書上的域名和請求域名(多是IP,也多是其餘域名)就會出現不一致,致使校驗不經過;這就須要從新設置下校驗策略,把這個證書對應的域名設置下。詳細說明請查看官方文檔:《Overriding TLS Chain Validation Correctly》

 

2.2 校驗證書鏈?

上一篇文章介紹系統驗證SSL證書的方法和流程時,不是已經說明了會對證書鏈進行層層校驗,以保證證書的可信麼?爲何還須要討論這一問題?其實本節要討論的是AFNetworkingvalidatesCertificateChain的問題。

先說明下結果:在AFNetworking最新發布的V2.6.0,已經將該特性去掉了。相關的討論:SSL Pinning: What Should Be Certificate Chain Validation Expected Behavior?#2744

AFNetworking中實現的驗證證書鏈,是將App本地打包好的證書與服務器返回的證書鏈進行數據上的一一對比,只有打包到App的證書中包含了服務器返回的證書鏈上的全部證書,校驗纔會經過。如google的SSL證書:

Google Cer Chain

開啓validatesCertificateChain後請求https://google.com,須要將GeoTrust Global CA、Google Internet Authority G2和google.com的證書都導入App中才能驗證經過。請回憶下上一篇文章關於證書鏈的可信任機制,會發現這是徹底沒有必要的;證書鏈的驗證,主要由三部分來保證證書的可信:葉子證書是對應HTTPS請求域名的證書,根證書是被系統信任的證書,以及這個證書鏈之間都是層層簽發可信任鏈;證書之因此能成立,本質是基於信任鏈,這樣任何一個節點證書加上域名校驗(CA機構不會爲不一樣的對不一樣的用戶簽發相同域名的證書),就肯定一條惟一可信證書鏈,因此不須要每一個節點都驗證。

 

2.3打包證書校驗

那是否就不須要在App中打包證書進行驗證了呢?

這時須要想一想爲何僞造證書是能夠實現中間人攻擊的?答案就在於用戶讓系統信任了不該該信任的證書。用戶設置系統信任的證書,會做爲錨點證書(Anchor Certificate)來驗證其餘證書,當返回的服務器證書是錨點證書或者是基於該證書籤發的證書(能夠是多個層級)都會被信任。這就是基於信任鏈校驗方式的最大弱點。咱們不能徹底相信系統的校驗,由於系統的校驗依賴的證書的源極可能被污染了。這就須要選取一個節點證書,打包到App中,做爲Anchor Certificate來保證證書鏈的惟一性和可信性。

因此仍是須要App本地打包證書,使用SecTrustSetAnchorCertificates(SecTrustRef trust, CFArrayRef anchorCertificates)來設置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.

也就是說,單純調用SecTrustSetAnchorCertificates方法後不調用SecTrustSetAnchorCertificatesOnly來驗證證書,則只會相信SecTrustSetAnchorCertificates傳入的證書,而不會信任其餘錨點證書。關於這一點,SecTrustSetAnchorCertificatesOnly方法參數講解中也有說明:

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.

只相信傳入的錨點證書,也就只會驗證經過由這些錨點證書籤發的證書。這樣就算被驗證的證書是由系統其餘信任的錨點證書籤發的,也沒法驗證經過。

最後一個問題:選擇證書鏈的哪一節點做爲錨點證書打包到App中?不少開發者會直接選擇葉子證書。其實對於自建證書來講,選擇哪一節點都是可行的。而對於由CA頒發的證書,則建議導入頒發該證書的CA機構證書或者是更上一級CA機構的證書,甚至能夠是根證書。這是由於:

1) 通常葉子證書的有效期都比較短,Google和Baidu官網證書的有效期也就幾個月;而App因爲是客戶端,須要必定的向後兼容,稍疏於檢查,今天發佈,過兩天證書就過時了。

2) 越往證書鏈的末端,證書越有可能變更;好比葉子證書由特定域名(aaa.bbb.com)改成通配域名(*.bbb.com)等等。短時間內的變更,從新部署後,有可能舊版本App更新不及時而出現沒法訪問的問題。

所以使用CA機構證書是比較合適的,至於哪一級CA機構證書,並無徹底的定論,你能夠本身評估選擇。



3. ATS

在本文發表的時間(2015-09-03),大部分的iOS開發同窗應該升級到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 進行網絡請求的行爲做了一系列的強制要求,反逼服務器配置,以提升網絡數據傳輸的安全性:

These are the App Transport Security requirements:

1) The server must support at least Transport Layer Security (TLS) protocol version 1.2.

2) Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)

3) 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;並且規定了支持的加密套件(Cipher Suite)和證書籤名的哈希算法;若是想要向前兼容的話,能夠經過設置Info.plist來下降校驗強度,具體能夠看這篇文章:Configuring App Transport Security Exceptions in iOS 9 and OSX 10.11

本人升級到iOS9 GM版,從App Store上下載了一些並無徹底支持ATS的應用,使用起來也徹底沒有問題,估計iOS系統對使用低於SDK9編譯的App作了兼容,這方面也是符合預期的,畢竟ATS的影響實在太大,基本上沒有任何的App可以倖免,好比圖片下載通常使用HTTP,而不會使用HTTPS。因此建議能夠暫時使用NSAllowsArbitraryLoads來取消ATS的限制,後續慢慢完善對ATS的支持。

日益複雜脆弱的網絡難以保證用戶的數據安全,所以Apple纔在iOS9上強推ATS,反向逼迫服務端升級,以提供更安全的網絡環境。建議開發者不要簡單地將ATS禁用,而應該升級服務器的配置支持ATS,爲用戶提供更安全的服務。

 

4. 調試SSL/TLS

開發一個新的App,一般終端和後端先協商好了具體業務邏輯的通訊協議,後端和終端按照協議實現邏輯以後,就進入聯調階段,第一次聯調每每會回到不少問題,包括數據格式不對,缺乏基礎字段等;假如是基於HTTPS的網絡請求,則極可能因爲後臺配置問題,致使遇到如CFNetwork SSLHandshake failed (-9824)這類握手失敗的錯誤。面對這類SSL錯誤,該如何來解決呢?根據本人經驗,主要是分兩步:

4.1 錯誤碼

這會不會太簡單了?其實最簡單的每每是最有效的。SSL相關錯誤碼能夠在<Security/SecureTransport.h>中找到。上面-9824的錯誤,對應的是errSSLPeerHandshakeFail = -9824, /* handshake failure */,其餘常見的錯誤碼還有:

//... /* 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 */ //...

但靠錯誤碼只能判斷大概的狀況,不少時候並不能明確知道究竟是什麼緣由致使的,因此最直觀的,仍是須要抓包分析。

4.2 抓包分析

在這一階段,使用Charles來抓包是沒有用的,由於Charles是做爲HTTP代理工做的,它會抓取代理的網絡報文,而後將報文組合成HTTP/HTTPS協議包,對於HTTP調試很是方便,但因爲細節的缺失,沒辦法使用它來分析SSL相關錯誤。因此咱們須要使用上古神器Wireshark。

關於Wireshark就再也不多介紹了,網上已經有不少相關介紹和抓包教程,如《Mac OS X上使用Wireshark抓包》等,基本上能夠很快上手。下面咱們就以適配iOS9的ATS爲例,來講下如何進行抓包分析,找出由於不支持ATS致使SSL握手失敗問題。

還記得SSL握手過程麼?不記得能夠重溫下這篇文章:圖解SSL/TLS協議。咱們也來看看Wireshark上抓取到的包來直觀學習正常的SSL握手流程:

Wireshark SSL Handshake

上圖是一個標準的HTTPS請求抓取的包:

1) 在TCP三次握手成功以後,客戶端發起SSL的Client Hello(No.68幀),傳遞隨機數(Random),和客戶端支持的加密套件(Cipher Suites)、壓縮方法、簽名算法等信息; 以下圖所示,這是Client Hello所攜帶的信息,能夠展開來看相關的詳情:

Client Hello

2) 服務器從Client Hello中匹配支持的加密套件(Cipher Suites)、壓縮算法和簽名算法,和服務器新生成的一個隨機數返回給客戶端,這就是Server Hello(No.70幀)。 下圖就是對1)中Client Hello的迴應,由圖能夠看出,服務端匹配的Cipher Suite是TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:

Server Hello

3) 服務器同時會將證書發給客戶端(No.73幀);有時候抓取的包只有Client HelloServer Hello,而沒有再發送證書的,這是SSL/TLS的Session重用了:因爲新創建一個SSL/TLS Session的成本過高,因此以前有創建SSL/TLS鏈接Session的話,客戶端會保存Session ID,在下一次請求時在Client Hello中帶上,服務端驗證有效以後,就會成功重用Sesssion。

注:關於重用TLS Session,在特定場景下會引起嚴重的問題:當App只針對了代碼中發起的HTTPS請求作了本地證書校驗,而WebView中發起的HTTPS請求並無作本地證書校驗,可能會出現App內代碼發起的請求直接重用WebView中創建的HTTPS連接,致使中間人能夠實現短暫的繞過攻擊。

拓展閱讀:


4) 客戶端確認證書有效,則會生產最後一個隨機數(Premaster secret),並使用證書的公鑰RSA加密這個隨機數,發回給服務端。爲了更高的安全性,會改成Diffie-Hellman算法(簡稱DH算法);採用DH算法,最後一個隨機數(Premaster secret)是不須要傳遞的,客戶端和服務端交換參數以後就能夠算出。Client Key Exchange(No. 75幀);

5) 接下來雙方都會發送Change Cipher Spec通知對方,接下來的全部消息都會使用簽名約定好的密鑰進行加密通訊。

6) 最後是雙方的Finished Message(即Encrypted Handshake Message, No. 7七、79幀),這個消息是最終的校驗,裏面包含了握手過程當中的Session Key等信息,若是對方可以解密這個消息則表示握手成功,結束整個SSL Handshake流程。

掌握了SSL/TLS握手流程以後,調試SSL/TLS就會變得很是簡單,只須要看在哪一個環節報錯(Alert),就能夠基本推斷出相關的錯誤。

相關SSL/TLS接口信息,請查看:RFC5246以及SSL/TLS in Detail

下面就列舉下調試適配ATS過程當中遇到的主要問題:


1) 加密套件(Cipher Suite)等參數沒法匹配:加密套件不匹配是最多見的握手失敗的例子。

在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

但每每不少服務器的HTTPS配置好久沒有升級,沒辦法支持這些Cipher Suite;客戶端發送Client Hello給服務端,帶上支持加密套件參數;服務端查看這些參數,發現一個都不支持,則直接返回Handshake Failure的信息。以下圖:

Handshake Failure

通常在接受到客戶端發送的Client Hello後返回Handshake Failure,都是由於服務端沒法匹配客戶端SSL握手參數。至因而不是加密套件這個參數匹配的問題,建議抓取取消ATS了的正常HTTPS請求包進行對比,找出具體不匹配的參數。


2) SSL/TLS版本太低,這個也很是常見,但通常會被上一個參數不匹配的錯誤所掩蓋。由於大多數SSL/TLS版本低的服務器HTTPS配置支持的加密套件等參數版本也比較低,而SSL/TLS版本是客戶端收到Server Hello以後才驗證的,但前面握手失敗就走不到這一步了。因此加密套件(Cipher Suite)等參數沒法匹配支持,通常也就意味着服務端SSL/TLS版本太低。


3) 證書鏈配置錯誤:在開發過程當中,本人遇到過證書鏈沒有按照順序進行配置的問題,也遇到過只配置了葉子證書的問題。對於這些問題,能夠直接查看SSL握手過程當中,服務端返回的Certificate包:

SSL Certificate error

上圖能夠看到證書鏈Certificates只有一個,這是典型的配置錯誤。

PS:使用Wireshark進行抓包的時候,有時候因爲一些HTTPS請求的SSL/TLS版本號過低,Wireshark沒辦法辨認其是SSL包,而是顯示爲TCP;此時能夠手動來Decode:選擇對應的TCP數據幀,右鍵 -》Decode As -》Transport 選擇SSL -》Apply既可。

Wireshark Decode



5. 後記

這個時代,安全重要麼?這是我曾常疑惑的。90%以上的大衆對安全沒有切實的概念,即便安全上了春晚,過了熱潮一切又重歸原樣。特別最近換工做到保險金融類公司,安全問題更是觸目驚心。一直相信,人如同一個圓,你知道的越多,學的越深,接觸的越廣,圓就越大,越知道本身的眇小,越懂得敬畏。

這世界永遠不會缺乏矛和盾,沒有「Mission Impossible」,不是麼?


版權全部,轉載請保留Jaminzzhang署名

相關文章
相關標籤/搜索