脆弱性ってどういうものなの? - AFNetworkingにおけるSSL証明書検証不備の脆弱性 -

こんにちは。セキュリティ技術グループのはるぷです。

最近以前より少し広い所に引越しをしたのですが、うちの猫達が猛ダッシュして遊んでいます。引越し前よりご飯を多めに食べているにも拘らず、少しスリムになっていてかわいいです。

さて、今年4月末にiOSアプリにおいて広く利用されているAFNetworking (https://github.com/AFNetworking/AFNetworking) に存在するSSL証明書の検証不備の脆弱性が修正され、多くのアプリが影響を受けるということで、ニュース記事でも取り上げられました。

参考:「iOS」のネットワーキングライブラリに脆弱性--2万件を超えるアプリに影響か

今回は、このAFNetworkingの脆弱性はどういったものかを解説します。

 

 

AFNetworkingで修正されている脆弱性は、SSL証明書の検証段階で、発行先(ドメイン名など)の検証をスキップしてしまうという問題です。これによって、第三者によるHTTPS通信への介入を許してしまい、通信系路上でHTTPS通信の盗聴、改ざん(中間者攻撃と呼ばれる)が可能になってしまいます。

 

AFNetworkingの脆弱性の具体的な内容を解説する前に、SSL証明書の検証について触れておきます。WebサーバへのHTTPSの安全なアクセスを実現するために、通常は信頼できる発行機関から発行された証明書を利用し、ブラウザやアプリに接続先のサーバが正当なものであることを証明します。この際、通信の開始にあたって、提示された証明書の

・発行者(証明書を発行した人)
・発行先(アクセスしようとしているサーバのドメイン名)
・有効期間

をブラウザやアプリ側で検証することで、安全に通信を行う様になっています。

例えば、一般的に自己署名証明書と呼ばれるもの(オレオレ証明書とも呼ばれる)は、上記のうち「発行者」が不正になっているもの(発行者と発行先が同じで、発行者が信頼されていない発行機関)を指していて、発行者の検証でエラーとなります。

 

今回解説するAFNetworkingのSSL証明書検証不備の脆弱性では「発行先」の検証がスキップされてしまっている状態でした。

例えば、ブラウザでwww.example.orgのサーバへhttpsでアクセスをする場合、通常「https://www.example.org/」のようにアクセスを行うことで、下図のように証明書が正しい状態になっていることを確認することができます。

しかし、「https://93.184.216.34/」(www.example.orgのIPアドレス)のようにIPアドレスでアクセスした場合は下記のように証明書のエラーになり、発行先が正しくないものとして扱われます。

しかし、脆弱性のあるバージョンのAFNetworkingでは「https://93.184.216.34/」の形式のアクセスでエラーとならず、通信が正常なものとして完了してしまう実装になっていました。本来であれば、クライアントが指定している「93.184.216.34」とサーバ証明書の「www.example.org」が異なっているのでエラーとならなければいけません。これは、「www.example.org」の正規の証明書(発行元と有効期限が有効なもの)を持っていれば、接続先がIPアドレスや任意のホスト名(例えばdena.comなど)であってもエラーとならないということになります。つまり、下図の様に任意のホストへのアクセスを傍受できる状態になっていることになり、HTTPSによる通信を利用している意味をなしていない状態になっています。

それでは、実際にAFNetworkingのコードを見ていきましょう。今回の脆弱性の主な原因と考えられるコードは下記で変更されている部分でAFSecurityPolicy.h、AFSecurityPolicy.m内のvalidatesDomainName(発行先の検証要否を判断するフラグ)に関する記述です。

このvalidatesDomainNameが脆弱性となる形で利用されるようになったのが、下記の2.2.1のevaluateServerTrust内になります。

https://github.com/AFNetworking/AFNetworking/blob/2.2.1/AFNetworking/AFSecurityPolicy.m#L227

if (self.validatesDomainName) {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}

ここで利用される際に、validatesDomainNameはデフォルトで初期化されていない状態になっています。BOOLの値はデフォルトが”NO”のため、明示的に”YES”にしないと発行先の検証が有効にならない状態でした。

 

この脆弱性に対し2.5.3では、AFSecurityPolicy.mの中でvalidatesDomainNameの初期値を”YES”し、setSSLPinningMode(2.5.0より導入された)を削除するなどの変更がされています。

https://github.com/AFNetworking/AFNetworking/compare/2.5.2...2.5.3#diff-508d2e2e91b3a2789fb4bf053ec4b125L198

 

では、実際にこの変更が入る前は本当に脆弱なのか?という部分についてXcodeを利用して検証する方法を紹介します。実際の攻撃シナリオでは、中間者攻撃をする必要があるため、日常的に中間者攻撃を行っている人以外には若干ハードルが高く、また、脆弱性の検証を行う上では必ずしも攻撃を再現する必要はないためここでは紹介はしません。

証明書検証不備の脆弱性の検証

 

検証を行うために、AFNetworkingを利用できる様にするために環境を整えます。

1. Projectの作成

はじめに、Xcodeを立ち上げて「Create a new Xcode project」を選択します。「Single View Application」を選択し「Language」を「Objective-C」でProjectの作成を完了します。

ここでは、プロジェクト名を「AFNetworkingTest」としています。

 

2. AFNetworkingのインストール

作成したプロジェクトのパスへターミナルで移動します。

Podfileという名前で以下のファイルを作成します。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.3'
pod 'AFNetworking', '2.5.2'

Podfileを作成したら以下のコマンドを実行します。CocoaPodsがインストールされていない場合は、インストールしてから実行してください。

$ export LANG=en_US.UTF-8
$ pod install

インストールが完了したら、「AFNetworkingTest.xcworkspace」が作成されるので、開きます。

$ open AFNetworkingTest.xcworkspace

3. サーバへの接続テストコードを追加

ViewController.mを開き、下記のように「#import "AFNetworking.h"」とviewDidLoad内にテストコードを追記します。

※「dena.com -> 103.23.4.10」の部分は、自身の管理しているサーバに置き換えて利用してください。

#import "ViewController.h"
#import "AFNetworking.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
        AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    // dena.com -> 103.23.4.10
    [manager GET:@"https://103.23.4.10/" parameters:@"" success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"response: %@",  operation.responseString);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Error: %@", error);
    }];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

4. 実行

上記のコードを実行すると「https://103.23.4.10/」へのアクセスが行われます。本来はこのアクセスはエラーとなるべきですが、脆弱な場合は証明書の検証が行われずにアクセス結果のHTML等が取得できてしまうことがわかります。

実行例:

2015-04-27 12:23:10.992 AFNetworkingTest[20792:1046592] response: <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  
  <title>DeNA ディー・エヌ・エー | Delight and Impact the World</title>
 : (以降省略)
 

脆弱なバージョンの場合でも、例えば仮想マシン上に構築した「発行先」と「有効期限」が正しい自己署名証明書のサーバ(https://192.168.56.102/)へアクセスを行うと下記の様に正しくエラーになることがわかります。この挙動より、「発行元」については正しく検証がされていて、「発行先」が不正な場合に本脆弱性が該当することを確認できます。その為、「発行元」が正しく「発行先」が誤っているという気付きにくいケースで脆弱な挙動になっていたという状態です。

2015-04-27 12:22:08.005 AFNetworkingTest[20740:1044711] Error: Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x7ff96147abe0
(NSURLErrorDomain error -1012.)" UserInfo=0x7ff96147abe0 y=https://192.168.56.102/?(null)=}

脆弱性が修正されたバージョン(2.5.3)やパッチをあてた脆弱なバージョンで「https://103.23.4.10/」へのアクセスを行うと、正しく以下のようにエラーとなります。

2015-04-27 12:26:32.490 AFNetworkingTest[21028:1052539] Error: Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x7f89494b4550 {NSErrorFailingURLKey=https://103.23.4.10/?(null)=, NSErrorFailingURLStringKey=https://103.23.4.10/?(null)=}

このように、実際に中間者攻撃を行う環境を構築しなくとも、簡単なテストコードで証明書の検証が正しく行われているかを確認することが可能です。HTTPS関連の脆弱性は検証が難しいものが多いですが、上記のように簡単に確認ができるものもあるので、実際に動かしてみると理解が深まるかと思います。

 

また、開発用のサーバで自己署名証明書を利用している場合などでも、誤ってそのままの設定でアプリをリリースしてしまうと中間者攻撃の被害を受ける可能性があります。サーバへの通信方法が複数存在している場合(直接HTTPリクエストを発行しているAFNetworkingのようなライブラリとWebViewを併用しているなど)、一部見落としとかが発生するため、こういったミスを防ぐためにも、このような簡単なテストをリリース前に実施することでHTTPSを安全に利用できるため、是非試してみてください!

 

========

今回もちゃんとPower Pointで描いてます!