關於Https安全性問題、雙向驗證防止中間人攻擊問題

lionzl發表於2016-11-02

關於Https安全性問題、雙向驗證防止中間人攻擊問題

 

關於Https安全性問題、雙向驗證防止中間人攻擊問題

最近專案中遇到一個問題,安全測試時反饋出,利用fiddler擷取到了客戶端與後臺的https介面的明文內容,這是一個可怕的問題,那麼接下來我從下面幾點做一些概述和程式碼舉例。

1、Https比Http安全性? 
是的,Https是以安全為目標的Http版本,在Http上使用SSL層,進行加密傳輸(對稱和非對稱加密),還具備身份驗證的功能(下面會重點說HTTPS的雙向驗證)。當然,Https是需要配置CA證書的,一般要從某些機構購買。

2、為何Https依然會被擷取? 
目前的一些抓包工具,fiddler、charles使用了叫做man-in-the-middle(中間人)機制:

著作權歸作者所有。 商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 
作者:連山歸藏 
來源:知乎 
第一步, fiddler向伺服器傳送請求進行握手, 獲取到伺服器的CA證書, 用根證書公鑰進行解密, 驗證伺服器資料簽名,獲取到伺服器CA證書公鑰。 
第二步, fiddler偽造自己的CA證書, 冒充伺服器證書傳遞給客戶端瀏覽器,客戶端瀏覽器做跟fiddler一樣的事。 
第三步, 客戶端瀏覽器生成https通訊用的對稱金鑰,用fiddler偽造的證書公鑰加密後傳遞給伺服器, 被fiddler截獲。 
第四步, fiddler將截獲的密文用自己偽造證書的私鑰解開,獲得https通訊用的對稱金鑰。 
第五步, fiddler將對稱金鑰用伺服器證書公鑰加密傳遞給伺服器, 伺服器用私鑰解開後建立信任,握手完成, 用對稱金鑰加密訊息, 開始通訊。 
第六步, fiddler接收到伺服器傳送的密文, 用對稱金鑰解開,獲得伺服器傳送的明文。再次加密, 傳送給客戶端瀏覽器。 
第七步, 客戶端向伺服器傳送訊息, 用對稱金鑰加密, 被fidller截獲後,解密獲得明文。 
由於fiddler一直擁有通訊用對稱金鑰, 所以在整個https通訊過程中資訊對其透明。也就是說fiddler&charles向伺服器冒充是客戶端,向客戶端又謊稱自己是伺服器。

這就解釋了問什麼擷取到了Https的資料,並且,Https在傳輸過程加密,所以到了客戶端已經不是Https傳輸加密的範圍了,這個時候其實很多人都是這麼解決的,對Https資料先自己進行加密(Ex:AES、RSA),再Https傳輸,這樣即使擷取到Https資料,也看不到明文。那麼有沒有方式可以不讓fiddler&charles這種傢伙擷取到Https呢?有,就是Https的雙向驗證;

3、Https雙向驗證 
伺服器端對請求它的客戶端要進行身份驗證,客戶端對自己所請求的伺服器也會做身份驗證。服務端一旦驗證到請求自己的客戶端為不可信任的,服務端就拒絕繼續通訊。客戶端如果發現服務端為不可信任的,那麼也中止通訊。具體怎麼做呢?

首先我們需要伺服器端到處證書給我們,那我公司舉例,後臺給我一個.cer檔案,我用它匯出一個.p12檔案,將這兩個匯入Xcode工程resource目錄下: 
這裡寫圖片描述 
接下來,有大致三種方式來進行雙向認證: 
(1)用NSURLConnection,需新增倆delegate

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{
  return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    static CFArrayRef certs;
        if (!certs) {
            NSData*certData =[NSData dataWithContentsOfFile:[[NSBundle mainBundle]
                                            pathForResource:@"srca" ofType:@"cer"]];
            SecCertificateRef rootcert
            =SecCertificateCreateWithData(kCFAllocatorDefault,CFBridgingRetain(certData));
            const void *array[1] = { rootcert };
            certs = CFArrayCreate(NULL, array, 1, &kCFTypeArrayCallBacks);
            CFRelease(rootcert);    // for completeness, really does not matter
        }

        SecTrustRef trust = [[challenge protectionSpace] serverTrust];
        int err;
        SecTrustResultType trustResult = 0;
        err = SecTrustSetAnchorCertificates(trust, certs);
        if (err == noErr) {
            err = SecTrustEvaluate(trust,&trustResult);
        }
        CFRelease(trust);
        BOOL trusted = (err == noErr) 
                       && ((trustResult == kSecTrustResultProceed)
                       ||(trustResult == kSecTrustResultConfirm) 
                       || (trustResult == kSecTrustResultUnspecified));

        if (trusted) {
            [challenge.sender useCredential:[NSURLCredential
                         credentialForTrust:challenge.protectionSpace.serverTrust]
                 forAuthenticationChallenge:challenge];
        }else{
            [challenge.sender cancelAuthenticationChallenge:challenge];
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36


(2)使用AFNetworking

- (void)getNBString:(void(^)(BOOL))completion
{
  SecIdentityRef identity = NULL;
  SecTrustRef trust = NULL;
  NSString *p12 = [[NSBundle mainBundle] pathForResource:@"cc"
                                                  ofType:@"p12"];
  NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];

  [self extractIdentity:&identity 
           andTrust:&trust
         fromPKCS12Data:PKCS12Data];
  NSString *url = [NSString stringWithFormat:@"%@/service/GetAPPValue", kDefaultHost];

  AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
  [manager.requestSerializer setValue:@"1" 
           forHTTPHeaderField:@"Platform"];
  [manager.requestSerializer setValue:kAppId 
           forHTTPHeaderField:@"AppId"];
  [manager.requestSerializer setValue:AppVersion()
                   forHTTPHeaderField:@"AppVersion"];
  [manager.requestSerializer setValue:@"application/json" 
           forHTTPHeaderField:@"Accept"];
  [manager.requestSerializer setValue:@"application/json" 
           forHTTPHeaderField:@"Content-Type"];
  manager.responseSerializer.acceptableContentTypes 
    = [NSSet setWithArray:@[@"application/json", @"text/html"]];
  manager.securityPolicy = [self customSecurityPolicy];

  [manager GET:url parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {

  } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    [HardCodingKeyManager getInstance].nbString = responseObject[@"Data"];
    completion(YES);
  } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"%@", error);
    completion(NO);
  }];

}

- (BOOL)extractIdentity:(SecIdentityRef *)outIdentity
               andTrust:(SecTrustRef*)outTrust
         fromPKCS12Data:(NSData *)inPKCS12Data {
  OSStatus securityError = errSecSuccess;

  // 證書金鑰
  NSDictionary *optionsDictionary = @{@"testHttps": (__bridge id)kSecImportExportPassphrase};
  CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
  securityError = SecPKCS12Import(
    (__bridge CFDataRef)inPKCS12Data,
    (__bridge CFDictionaryRef)optionsDictionary, &items);

  if (securityError == 0) {
    CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
    const void *tempIdentity = NULL;
    tempIdentity = 
       CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
    *outIdentity = (SecIdentityRef)tempIdentity;
    const void *tempTrust = NULL;
    tempTrust = 
       CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
    *outTrust = (SecTrustRef)tempTrust;
  } else {
    NSLog(@"Failed with error code %d",(int)securityError);
    return NO;
  }
  return YES;
}

// 設定manager的AFSecurityPolicy屬性
- (AFSecurityPolicy*)customSecurityPolicy {
  NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ClientC" 
                              ofType:@"cer"];
  NSData *certData = [NSData dataWithContentsOfFile:cerPath];
  AFSecurityPolicy *securityPolicy = 
     [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
  [securityPolicy setAllowInvalidCertificates:NO]; // 記得設定為NO
  NSSet *set = [NSSet setWithArray:@[certData]];
  [securityPolicy setPinnedCertificates:set];
  /**** SSL Pinning ****/
  return securityPolicy;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82


(3)使用ASIHttpRequest 
我們的專案老框架都是這種方式,苦逼的是我試了很多方法,都沒有雙向認證生效,大家有好方法,可以分享,這裡我就不貼了。

當我們配置完畢,使用charles&fiddler檢測時,https的介面都不會connect,提示證書不受信任,可能是偽伺服器之類的提示,成功。

親測,即便我們的.p12被人拿到,匯入charles&fiddler,作為它們的root certificater,只要[securityPolicy setAllowInvalidCertificates:NO]; // 記得設定為NO,依然無法connect。

相關文章