¿Es la implementación del servicio de notificaciones push de Apple vulnerable a un ataque MitM?

15

Recientemente ( más información ) He detectado un mensaje extraño en los registros de mi reciente instalación reciente de OS X Mavericks 10.9.2

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

Aparece cada 15 minutos más o menos. Lo he buscado en Google y hay muchos otros usuarios que pegaron sus registros (muchas veces ni siquiera relacionados con ese problema) en la red que tenían ese mensaje. Parece que está apareciendo durante al menos medio año.

apsd es un demonio del servicio de notificaciones push de Apple que se ejecuta constantemente

y se conecta a uno de los siguientes servidores de forma aleatoria:

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

en el puerto 5223 (personalizado, pero SSL )

cada cierto tiempo (para buscar actualizaciones, supongo)

Probar: enlace en el navegador, sin embargo, muestra que hay un problema relacionado con el certificado del servidor. A saber, "Este certificado no es válido (no coincide el nombre de host)" . Lo que supongo es porque Apple no creó un certificado que incluya un comodín en el nombre común. Sin embargo, al utilizar Wireshark veo que la comunicación entre apsd y el servidor de Apple continúa. Pensé que eso no puede ser correcto. ¿Está apsd ignorando la verificación de validez ?!

Luego traté de realizar un ataque de hombre en medio con un certificado autofirmado. Y esto es lo que obtuve en los registros:

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

También vale la pena señalar que he visto (al observar el tráfico real con Wireshark) que el servidor requiere que el cliente envíe su certificado. Y apsd envía:

Nombre del sujeto
Nombre común XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (UUID)

Nombre del emisor
País EE.UU.
Organización Apple Inc.
Unidad de organización Apple iPhone
Nombre común Apple iPhone Device CA
.
.
.

¿Es extraño que Mac OS X esté enviando lo que parece ser una clave pública que se presenta como iPhone? Tengo instalado Xcode, si está relacionado de alguna manera con la instalación de simuladores de iPhone. Pero recibo el mensaje de registro mencionado todas las veces, independientemente de si Xcode se está ejecutando o no. Y, evidentemente, otros también lo son, a juzgar por los registros en Internet.

Entonces, ¿qué está pasando?
¿De qué manera apsd puede descubrir mi certificado falso y aprobar el suyo? ¿Hay algún problema aquí?
Nota: La implementación de Mi MitM es bastante simple, con la falsificación de DNS le doy a apsd una respuesta que X-courier.push.apple.com es 127.0.0.1. Luego escucho con un certificado autofirmado que imita el certificado del servidor de Apple (país, organización, nombre común ...). PERO no implementé el requisito del certificado de cliente de apsd . Así que todavía no sé si eso está relacionado de alguna manera con mi MitM fallido. ¿Podría ser?

¿Están ustedes allí en Mac OS X recibiendo el mismo mensaje de registro en su consola (aparece en 'Todos los mensajes' justo cuando lo inicia, pero en realidad está en system.log), escriba 'hoja' en el cuadro de búsqueda ...

ACTUALIZACIÓN:
He implementado solicitando al cliente su certificado. Es lo mismo (SSLHandshake failure / Failed para evaluar la confianza). Así que apsd está revisando algo, pero no estoy muy seguro de que se haya implementado de la manera correcta. Esperemos que alguien con mucha más experiencia investigue esto ...

    
pregunta Ivan Kovacevic 27.04.2014 - 17:06
fuente

2 respuestas

6

No tengo mucho tiempo ahora y no lo he verificado a fondo, así que esto es solo lo que recuerdo al analizar esto hace unos meses.

Con OS X 10.9 y iOS 7, Apple introdujo algún tipo de identificación de certificado para el servicio de inserción, es decir, no permite que ninguna CA que el sistema sepa firme su certificado de servidor de inserción, sino solo una CA específica. La identificación de este certificado fue un poco extraña, ya que tenía algún tipo de fecha de caducidad del 1 de enero de 2014, pero no me interesé por lo que eso significa exactamente y no sé si las últimas versiones de OS X y iOS todavía incluyen esto.

  

Certificado de hoja no reconocido

Por lo que tengo entendido, este mensaje no significa que apsd no pueda verificar el certificado en absoluto, sino que podría significar que la identificación del certificado no pudo verificarlo (lo que significaría que la fijación del certificado es actualmente opcional, lo que a su vez podría tiene que ver con la fecha de caducidad).

Es posible que no tenga que preocuparse por esto directamente, ya que la validación normal del certificado SSL aún funciona (como lo confirma su prueba). Por otro lado, me encantaría saber por qué la fijación de certificados no funciona y qué significa la fecha de caducidad.

ACTUALIZACIÓN : aquí hay más información sobre por qué se muestra el mensaje de registro.

Puede mostrar el certificado del servidor utilizando openssl (parece que el mismo certificado se entrega en los puertos 443 y 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 partir de 2014-05-10, muestra la siguiente información:

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

El binario apsd contiene varios certificados X.509 utilizados para la fijación de certificados. Escribí una secuencia de comandos para buscar certificados en un binario . Aquí hay un extracto cuando lo ejecutamos en 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]

Como puede ver, el certificado anclado para courier.push.apple.com tiene un número de serie diferente al que recibe el servidor push de Apple . Entonces, ambos son certificados diferentes y, aparentemente, este es el motivo del mensaje de registro Unrecognized leaf certificate que ve. Como apsd aún se conecta al servidor, esto significa que apsd no requiere un certificado de hoja fija para que coincida.

Otra cosa que puede ver es el tercer certificado, un certificado de CA . El año pasado, cuando intenté que apsd se conectara a pushproxy , tuve que reemplace el certificado de CA para hacer apsd trust pushproxy. Esto significa que, mientras no se verifica el certificado de hoja, apsd solo puede permitir certificados de una determinada CA raíz. No verifiqué esto recientemente.

Para verificar esto a fondo, además de verificar si la fijación de certificados de CA para la conexión SSL sigue siendo efectiva, uno tendría que verificar otra cosa. Antes de conectarse al servidor push, apsd descarga una 'bolsa de configuración' . Esta bolsa se descarga a través de HTTP, pero se firma. La bolsa contiene el nombre de host apsd y luego intenta conectarse. Esta es una manera que estoy usando para que pushproxy redirija apsd a otro host. Puede encontrar más información sobre la bolsa en el pushproxy README y en script para generar tal bolsa .

    
respondido por el meeee 04.05.2014 - 21:06
fuente
4

Ya que nadie respondió aún, pensé que tal vez debería poner mi método de cómo probé esto y luego alguien podría señalar si eso está bien o no.

Escribí un servidor básico en python escuchando en el puerto 5223 de esta manera:

#!/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 línea comentada de server.socket al final es la versión con la que se solicita al cliente (apsd) el certificado. He creado los certificados llamando a openssl en la consola:

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

El expectedClient.pem que he extraído utilizando Wireshark del tráfico real donde se encuentra en el formato DER . Así que lo he convertido a PEM con este código:

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

Ahora necesitaba engañar a apsd de que xxx-courier.push.apple.com es en realidad 127.0.0.1 . Escribí un bouncer (proxy) básico de DNS que busca una consulta con dicha dirección y responde que la IP es 127.0.0.1.
Aquí está el código:

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

Debe iniciarlo con sudo , porque de lo contrario no querrá enlazar en el puerto 53. Y el último paso fue cambiar el servidor DNS en mis preferencias de red a 127.0. 0.1 para que la búsqueda de DNS en realidad pase por el bouncer y luego al ISP.

Eso es todo!

Ahora puedes probarlo tú mismo si quieres ...

    
respondido por el Ivan Kovacevic 01.05.2014 - 20:27
fuente

Lea otras preguntas en las etiquetas