L'implementazione del servizio di notifica push di Apple è vulnerabile a un attacco MitM

15

Recentemente ( maggiori informazioni ) ho notato uno strano messaggio in i log della mia installazione recente e recente di OS X Mavericks 10.9.2

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

Appare ogni 15 minuti circa. L'ho cercato su google e ci sono molti altri utenti che hanno incollato i loro log (molte volte non correlati a quel problema) sulla rete che conteneva quel messaggio. Sembra che appaia da almeno sei mesi.

apsd è un demone del servizio di notifica push Apple di sempre in esecuzione

e si connette casualmente a uno dei seguenti server:

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

sulla porta 5223 (personalizzato, ma SSL )

ogni tanto (per verificare se ci sono aggiornamenti credo)

Cercando: collegamento nel browser indica tuttavia che esiste un problema correlato al certificato del server. Vale a dire "Questo certificato non è valido (mancata corrispondenza del nome host)" . Quale suppongo è perché Apple non ha creato un certificato compreso un carattere jolly nel nome comune. Tuttavia usando Wireshark vedo che la comunicazione tra apsd e il server di Apple continua. Io però, non può essere giusto. apsd ignora il controllo di validità?!

Poi ho provato a eseguire un attacco uomo nel mezzo utilizzando un certificato autofirmato. E questo è quello che ho ottenuto nei log:

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

Vale anche la pena notare che ho notato (osservando il traffico reale con Wireshark) che il server richiede al client di inviare il suo certificato. E apsd invia:

Nome soggetto
Nome comune XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (UUID)

Nome emittente
Paese Stati Uniti
Organizzazione Apple Inc.
Unità organizzativa Apple iPhone
Nome comune Dispositivo CA Apple iPhone
.
.
.

È strano che Mac OS X stia inviando quella che sembra una chiave pubblica presentandola come iPhone? Ho installato Xcode, se è in qualche modo collegato all'installazione di simulatori iPhone. Ma sto ricevendo il messaggio di registro menzionato tutte le volte, indipendentemente dal fatto che Xcode sia in esecuzione o meno. E evidentemente lo sono anche gli altri, a giudicare dai log su internet.

Quindi cosa sta succedendo?
In che modo apsd è in grado di capire il mio certificato falso e passare il loro? C'è qualcosa di sbagliato qui?
Nota: l'implementazione del mio MitM è abbastanza semplice, con lo spoofing del DNS do a apsd una risposta che X-courier.push.apple.com è 127.0.0.1. Quindi ascolto con un certificato autofirmato che riproduce il certificato del server Apple (paese, organizzazione, nome comune ...). MA non ho implementato richiedendo il certificato client da apsd . Quindi non so ancora se questo è in qualche modo correlato al mio MitM fallito. Potrebbe essere?

Siete qui su Mac OS X ottenendo lo stesso messaggio di log nella vostra console (appare in "Tutti i messaggi" solo quando lo si avvia, ma in realtà è in system.log), digita "leaf" nella casella di ricerca ...

UPDATE:
Ho implementato la richiesta del client per il suo certificato. È lo stesso (errore SSLHandshake / Impossibile valutare la fiducia). Quindi apsd sta controllando qualcosa, non sono sicuro che sia implementato nel modo giusto. Speriamo che qualcuno con molta più esperienza esaminerà questo ...

    
posta Ivan Kovacevic 27.04.2014 - 17:06
fonte

2 risposte

6

Non ho molto tempo ora e non ho verificato questo a fondo, quindi questo è solo ciò che ricordo osservando questo alcuni mesi fa.

Con OS X 10.9 e iOS 7, Apple ha introdotto una sorta di blocco dei certificati per il servizio push, vale a dire che non consente a nessuna CA che il sistema sa di firmare il proprio certificato push server, ma solo una CA specifica. Questo pinning del certificato è stato un po 'strano dato che aveva una data di scadenza del 1 ° gennaio 2014, ma non ho esagerato su cosa significhi esattamente e non so se le ultime versioni di OS X e iOS includano ancora questo.

Unrecognized leaf certificate

Quindi, dal mio attuale intendimento, questo messaggio non significa affatto che apsd non possa verificare il certificato, ma potrebbe significare che il blocco del certificato non potrebbe verificarlo (il che significherebbe che il blocco del certificato è attualmente facoltativo, che a sua volta potrebbe avere a che fare con la data di scadenza).

Potrebbe non doversi preoccupare di ciò direttamente, poiché la normale convalida del certificato SSL funziona ancora (come conferma il test). D'altra parte mi piacerebbe sapere perché il pinning dei certificati apparentemente non funziona e cosa significa la data di scadenza.

UPDATE : ecco alcune ulteriori informazioni sul perché viene mostrato il messaggio di log.

Puoi visualizzare il certificato del server usando openssl (lo stesso certificato sembra essere pubblicato sulle porte 443 e 5223):

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

A partire dal 2014-05-10, questo mostra le seguenti informazioni:

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

Il binario apsd contiene diversi certificati X.509 utilizzati per il blocco dei certificati. Ho scritto uno script per trovare i certificati in un file binario . Ecco un estratto quando lo esegui su 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]

Come puoi vedere, il certificato aggiunto per courier.push.apple.com ha un numero di serie diverso da quello servito dal server push di Apple . Quindi entrambi sono certificati diversi e apparentemente questo è il motivo del messaggio di registro Unrecognized leaf certificate che vedi. Poiché apsd si connette ancora al server, significa che apsd non richiede la corrispondenza di un certificato foglia a spine.

Un'altra cosa che puoi vedere è il terzo certificato, un certificato CA . L'anno scorso, quando ho provato a rendere apsd connesso a pushproxy , ho dovuto sostituisci il certificato CA per creare apsd trustproxy. Ciò significa che, pur non controllando il certificato foglia, apsd potrebbe consentire solo certificati da una determinata CA radice. Non l'ho verificato di recente.

Per verificarlo a fondo, oltre a verificare se il certificato CA pinning per la connessione SSL è ancora efficace, si dovrebbe controllare un'altra cosa. Prima di connettersi al server push, apsd scarica un "bag di configurazione" . Questa borsa viene scaricata tramite HTTP, ma firmata. Il sacchetto contiene il nome host apsd , quindi tenta di connettersi a. Questo è un modo che sto usando per pushproxy per reindirizzare apsd a un altro host. Puoi trovare ulteriori informazioni sulla borsa nel README pushproxy e a script per generare una borsa di questo tipo .

    
risposta data 04.05.2014 - 21:06
fonte
4

Dal momento che nessuno ha ancora risposto, ho pensato che forse dovrei mettere il mio metodo su come ho provato questo e poi qualcuno potrebbe indicare se va bene o no.

Ho scritto un server di base in python in ascolto sulla porta 5223 come questo:

#!/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()

La riga server.socket commentata alla fine è la versione con richiesta al client (apsd) per il certificato. Ho creato i certificati chiamando openssl sulla console:

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

Il expectedClient.pem che ho estratto utilizzando Wireshark dal traffico effettivo in cui si trova nel formato DER . Quindi l'ho convertito in PEM con questo codice:

#!/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()

Ora dovevo ingannare apsd che xxx-courier.push.apple.com sia in realtà 127.0.0.1 . Ho scritto un bouncer DNS (proxy) di base che cerca una query con tale indirizzo e fa una risposta che l'IP è 127.0.0.1.
Ecco il codice:

#!/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()

Devi avviarlo con sudo , perché altrimenti non vorrà legare sulla porta 53. E l'ultimo passo è stato quello di cambiare i server DNS nelle mie preferenze di rete su 127.0. 0.1 in modo che la ricerca DNS passi effettivamente attraverso il buttafuori e poi verso l'ISP.

Questo è tutto!

Ora puoi testarlo da solo se vuoi ...

    
risposta data 01.05.2014 - 20:27
fonte

Leggi altre domande sui tag