web-dev-qa-db-ja.com

Appleのプッシュ通知サービスの実装はMitM攻撃に対して脆弱ですか

最近( 詳細OS X Mavericks 10.9.2の最新インストールで、奇妙なメッセージがログに記録されました。

Apr 27 15:26:47 Ivans-MacBook-Pro.local apsd[194]: Unrecognized leaf certificate

15分ごとに表示されます。私はそれをグーグルで調べました、そしてそのメッセージを持っていたネットに彼らのログを(多くの場合その問題にさえ関係なく)貼り付けた他の多くのユーザーがいます。少なくとも半年は現われているようです。

apsdは常に実行中ですApple Push Notification service daemon

そして、ランダムに次のサーバーのいずれかに接続します。

1-courier.Push.Apple.com
2-courier.Push.Apple.com
3-courier.Push.Apple.com
4-courier.Push.Apple.com



200-courier.Push.Apple.com

ポート522(カスタム、ただし[〜#〜] ssl [〜#〜]

頻繁に(更新を確認するために)

しかし、ブラウザで https://1-courier.Push.Apple.com:522 を実行すると、サーバー証明書に関連する問題があることが示されます。つまり "この証明書は無効です(ホスト名が一致しません)"。 Appleは、一般名にワイルドカードを含む証明書を作成しなかったためです。しかし、Wiresharkを使用すると、 apsdとAppleのサーバーは続行しますが、それは正しくない可能性がありますapsdは妥当性チェックを無視しますか?!

次に、自己署名証明書を使用して中間者攻撃を実行しようとしました。そして、これは私がログで得たものです:

Apr 27 15:42:07 Ivans-MacBook-Pro.local apsd[194]: CFNetwork SSLHandshake failed (-9807)
Apr 27 15:42:07 Ivans-MacBook-Pro.local apsd[194]: Failed to evaluate trust: No error. (0), result=5; retrying with revocation checking optional
Apr 27 15:42:07 Ivans-MacBook-Pro.local apsd[194]: failed to evaluate trust: No error. (0), result=5; retrying with system roots
Apr 27 15:42:07 Ivans-MacBook-Pro.local apsd[194]: Failed to evaluate trust: No error. (0), result=5
Apr 27 15:42:07 Ivans-MacBook-Pro.local apsd[194]: Untrusted peer, closing connection immediately

また、Wiresharkで実際のトラフィックを監視しているときに、サーバーがクライアントに証明書の送信を要求していることを発見したことにも注意してください。そしてapsd送信:

件名
一般名 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX(UUID)

発行者名
米国
組織 Apple Inc.
組織部門 Apple iPhone
一般名 Apple iPhoneデバイスCA


Mac OS Xが公開鍵のように見えるものを送信し、それをiPhoneとして提示しているのは奇妙なことですか? Xcodeがインストールされていますが、iPhoneシミュレーターがインストールされていることに何らかの関係がある場合は。しかし、Xcodeが実行されているかどうかに関係なく、上記のログメッセージが常に表示されます。そして、インターネット上のログから判断すると、明らかに他の人もそうです。

それで、何が起こっているのですか?
どのようにapsd私の偽の証明書を見つけてそれらを渡すことができますか?ここで何か問題がありますか?
注:私のMitM実装は非常にシンプルで、DNSスプーフィングを使用していますapsd X-courier.Push.Apple.comが127.0.0.1であるという応答。次に、Appleのサーバー証明書(国、組織、一般名など)を模倣した自己署名証明書を聞きます。しかしapsdからのクライアント証明書の要求を実装しませんでした。そのため、それが何らかの形で失敗したMitMに関連しているかどうかはまだわかりません。それは可能性が?

Mac OS Xのコンソールに同じログメッセージが表示されますか(起動時に「すべてのメッセージ」に表示されますが、実際にはsystem.logにあります)、検索ボックスに「リーフ」と入力します...

更新:
クライアントに証明書を要求することを実装しました。同じです(SSLHandshake失敗/信頼の評価に失敗)。だからapsdは何かをチェックしています、私はそれが正しい方法で実装されていると確信しています。うまくいけば、はるかに経験豊富な誰かがこれを調べます...

15
Ivan Kovacevic

私は今あまり時間をかけていませんし、これを完全に検証していません。そのため、これは数か月前にこれを調べたときに覚えていることです。

OS X 10.9とiOS 7では、Appleにより、Pushサービスに証明書のピン留めが導入されました。つまり、システムが認識しているCAがPushサーバー証明書に署名することを許可せず、特定のCAのみを許可します。この証明書のピン留めは、2014年1月1日の有効期限のようなものだったので少し奇妙でしたが、正確にそれが何を意味するのかについてはあまり調べませんでした。また、OS XとiOSの最新バージョンにまだ含まれているかどうかはわかりませんこの。

認識されないリーフ証明書

したがって、私の現在の理解から、このメッセージはapsdが証明書をまったく検証できないことを意味するわけではありませんが、証明書のピン留めがそれを検証できなかったことを意味する可能性があります(つまり、証明書のピン留めは現在オプションであり、順番に有効期限付き)。

通常のSSL証明書の検証は引き続き機能するため(テストで確認されているとおり)、これを直接心配する必要はありません。一方、証明書のピン留めが機能しないように見える理由と有効期限の意味を知りたいです。

[〜#〜] update [〜#〜]:ログメッセージが表示される理由の詳細を次に示します。

Opensslを使用してサーバー証明書を表示できます(同じ証明書がポート443および5223で提供されているようです)。

openssl s_client -prexit -connect 1-courier.Push.Apple.com:5223  2>/dev/null | openssl x509 -noout -text |grep -E '(Subject.*CN|Serial)'

2014-05-10現在、次の情報が表示されています。

Serial Number: 1277288244 (0x4c21df34)
Subject: C=US, ST=California, L=Cupertino, O=Apple Inc., CN=courier.Push.Apple.com

apsdバイナリには、証明書のピン留めに使用されるいくつかのX.509証明書が含まれています。 バイナリで証明書を検索するためのスクリプト を作成しました。 apsdで実行した場合の抜粋を次に示します。

+ 469280 Found cert with CN "courier.sandbox.Push.Apple.com" and serial "1277027356"
+ 470416 Found cert with CN "courier.Push.Apple.com" and serial "1276925395"
+ 471584 Found cert with CN "Entrust.net Certification Authority (2048)" and serial "946059622"
[skipping some code signing certificates]

ご覧のとおり、courier.Push.Apple.comの固定された証明書は、Appleのプッシュサーバーが提供するものとは異なるシリアル番号を持っています。したがって、どちらも異なる証明書であり、明らかにこれがUnrecognized leaf certificateログメッセージが表示される理由です。 apsdは引き続きサーバーに接続するため、これはapsdが一致するためにピン留めされたリーフ証明書を必要としないことを意味します。

あなたが見ることができるもう一つは、3番目の証明書、CA証明書です。昨年、apsdpushproxy に接続しようとすると、apsdにpushproxyを信頼させるために CA証明書を置き換える を使用する必要がありました。つまり、apsdは、リーフ証明書をチェックしていなくても、特定のルートCAからの証明書しか許可しない可能性があります。私は最近これを確認していません。

これを徹底的にチェックするには、SSL接続のCA証明書のピン留めがまだ有効かどうかをチェックする以外に、別のことをチェックする必要があります。プッシュサーバーに接続する前に、apsd 'configuration bag' をダウンロードします。このバッグはHTTP経由でダウンロードされますが、署名されています。バッグにはホスト名apsdが含まれており、接続を試みます。これは、pushproxyがapsdを別のホストにリダイレクトするために使用している方法です。 pushproxy READMEこのようなバッグを生成するスクリプト でバッグの詳細情報を確認できます。

6
meeee

まだ誰も反応しなかったので、私はこれをテストする方法を自分の方法に入れるべきだと思いました。

私はpythonポートでリッスンする基本的なサーバーを作成しました522このように:

#!/usr/bin/python
import SocketServer
import ssl

class requestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        print self.client_address[0] + ' connected.'
        receivedData = self.request.recv(4096)
        print receivedData

        #self.request.sendall('test')
        return

server = SocketServer.TCPServer(('0.0.0.0', 5223), requestHandler)
server.socket = ssl.wrap_socket(server.socket, server_side=True, keyfile='privateKey.pem', certfile='selfSignedCert.pem')
#server.socket = ssl.wrap_socket(server.socket, server_side=True, keyfile='privateKey.pem', certfile='selfSignedCert.pem', cert_reqs=ssl.CERT_REQUIRED, ca_certs='expectedClient.pem')
print 'Listening on port 5223'
server.serve_forever()

最後のコメント化されたserver.socket行は、クライアント(apsd)に証明書を要求するバージョンです。コンソールでopensslを呼び出して証明書を作成しました。

openssl req -new -x509 -days 365 -nodes -out selfSignedCert.pem -keyout privateKey.pem

expectedClient.pem[〜にある実際のトラフィックから、Wiresharkを使用して抽出しました#〜] der [〜#〜]形式。だから私はそれを[〜#〜] pem [〜#〜]にこのコードで変換しました:

#!/usr/bin/python
import ssl

inFileHandle = open('clientCert.der', 'rb')
outFileHandle = open('clientCert.pem', 'wb')
outFileContent = ssl.DER_cert_to_PEM_cert(inFileHandle.read())
outFileHandle.write(outFileContent)
inFileHandle.close()
outFileHandle.close()

今私はapsdをだます必要がありましたxxx-courier.Push.Apple.comは実際には127.0.0.1です。このようなアドレスを持つクエリを探し、IPが127.0.0.1であるという応答を返す基本的なDNSバウンサー(プロキシ)を作成しました。
これがコードです:

#!/usr/bin/python
import SocketServer
import socket

DNS_server = '8.8.8.8' # Your ISP's DNS server(8.8.8.8 is Google Public DNS)
DNS_formatted_address_match = '-courier' + chr(4) + 'Push' + chr(5) + 'Apple' + chr(3) + 'com' + chr(0)

class requestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        queryData = self.request[0]
        incomingSocket = self.request[1]

        transactionID = queryData[:2] # First two bytes
        DNS_formatted_query_address = queryData[12:].split(chr(0))[0] + chr(0) # First 12 bytes are DNS header

        if DNS_formatted_address_match in queryData:
            # DNS protocol explained: http://technet.Microsoft.com/en-us/library/dd197470(v=ws.10).aspx
            forgedResponse = (transactionID + 
                            chr(129) + chr(128) + 
                            chr(0) + chr(1) + 
                            chr(0) + chr(1) + 
                            chr(0) + chr(0) + 
                            chr(0) + chr(0) + 
                            DNS_formatted_query_address + 
                            chr(0) + chr(1) + 
                            chr(0) + chr(1) + 
                            chr(192) + chr(12) + chr(0) + chr(1) + 
                            chr(0) + chr(1) + 
                            chr(0) + chr(0) + chr(0) + chr(60) + chr(0) + chr(4) + 
                            chr(127) + chr(0) + chr(0) + chr(1))

            incomingSocket.sendto(forgedResponse, self.client_address)
        else:
            outgoingSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            outgoingSocket.sendto(queryData, (DNS_server, 53))
            responseData = outgoingSocket.recv(4096) # DNS response should not be more than 512 bytes so this should be more than enough
            incomingSocket.sendto(responseData, self.client_address)

        return

server = SocketServer.UDPServer(('0.0.0.0', 53), requestHandler)
print 'Listening on port 53'
server.serve_forever()

Sudoで開始する必要があります。それ以外の場合は、ポート53にバインドする必要がないためです。最後のステップは、ネットワーク設定のDNSサーバーを127.0.0.1に変更して、 DNSルックアップは、実際にはバウンサーを通過してからISPに到達します。

それでおしまい!

これで、必要に応じて自分でテストできます...

4
Ivan Kovacevic