HTTPS proxy tunneling with the ssl module

后端 未结 5 1121
耶瑟儿~
耶瑟儿~ 2021-01-02 01:51

I\'d like to manually (using the socket and ssl modules) make an HTTPS request through a proxy which itself uses HTTPS.

I can perform the i

相关标签:
5条回答
  • 2021-01-02 02:14

    This should work if the CONNECT string is rewritten as follows:

    CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
    

    Not sure why this works, but maybe it has something to do with the proxy I'm using. Here's an example code:

    from OpenSSL import SSL
    import socket
    
    def verify_cb(conn, cert, errun, depth, ok):
            return True
    
    server = 'mail.google.com'
    port = 443
    PROXY_ADDR = ("proxy.example.com", 3128)
    CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(PROXY_ADDR)
    s.send(CONNECT)
    print s.recv(4096)      
    
    ctx = SSL.Context(SSL.SSLv23_METHOD)
    ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
    ss = SSL.Connection(ctx, s)
    
    ss.set_connect_state()
    ss.do_handshake()
    cert = ss.get_peer_certificate()
    print cert.get_subject()
    ss.shutdown()
    ss.close()
    

    Note how the socket is first opened and then open socket placed in SSL context. Then I manually initialize SSL handshake. And output:

    HTTP/1.1 200 Connection established

    <X509Name object '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com'>

    It's based on pyOpenSSL because I needed to fetch invalid certificates too and Python built-in ssl module will always try to verify the certificate if it's received.

    0 讨论(0)
  • 2021-01-02 02:14

    Judging from the API of the OpenSSL and GnuTLS library, stacking a SSLSocket onto a SSLSocket is actually not straightforwardly possible as they provide special read/write functions to implement the encryption, which they are not able to use themselves when wrapping a pre-existing SSLSocket.

    The error is therefore caused by the inner SSLSocket directly reading from the system socket and not from the outer SSLSocket. This ends in sending data not belonging to the outer SSL session, which ends badly and for sure never returns a valid ServerHello.

    Concluding from that, I would say there is no simple way to implement what you (and actually myself) would like to accomplish.

    0 讨论(0)
  • 2021-01-02 02:17

    It doesn't sound like there's anything wrong with what you're doing; it's certainly possible to call wrap_socket() on an existing SSLSocket.

    The 'unknown protocol' error can occur (amongst other reasons) if there's extra data waiting to be read on the socket at the point you call wrap_socket(), for instance an extra \r\n or an HTTP error (due to a missing cert on the server end, for instance). Are you certain you've read everything available at that point?

    If you can force the first SSL channel to use a "plain" RSA cipher (i.e. non-Diffie-Hellman) then you may be able to use Wireshark to decrypt the stream to see what's going on.

    0 讨论(0)
  • 2021-01-02 02:22

    Building on @kravietz answer. Here is a version that works in Python3 through a Squid proxy:

    from OpenSSL import SSL
    import socket
    
    def verify_cb(conn, cert, errun, depth, ok):
            return True
    
    server = 'mail.google.com'
    port = 443
    PROXY_ADDR = ("<proxy_server>", 3128)
    CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(PROXY_ADDR)
    s.send(str.encode(CONNECT))
    s.recv(4096)
    
    ctx = SSL.Context(SSL.SSLv23_METHOD)
    ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
    ss = SSL.Connection(ctx, s)
    
    ss.set_connect_state()
    ss.do_handshake()
    cert = ss.get_peer_certificate()
    print(cert.get_subject())
    ss.shutdown()
    ss.close()
    

    This works in Python 2 also.

    0 讨论(0)
  • 2021-01-02 02:25

    Finally I got somewhere expanding on @kravietz and @02strich answers.

    Here's the code

    import threading
    import select
    import socket
    import ssl
    
    server = 'mail.google.com'
    port = 443
    PROXY = ("localhost", 4433)
    CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
    
    
    class ForwardedSocket(threading.Thread):
        def __init__(self, s, **kwargs):
            threading.Thread.__init__(self)
            self.dest = s
            self.oursraw, self.theirsraw = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
            self.theirs = socket.socket(_sock=self.theirsraw)
            self.start()
            self.ours = ssl.wrap_socket(socket.socket(_sock=self.oursraw), **kwargs)
    
        def run(self):
            rl, wl, xl = select.select([self.dest, self.theirs], [], [], 1)
            print rl, wl, xl
            # FIXME write may block
            if self.theirs in rl:
                self.dest.send(self.theirs.recv(4096))
            if self.dest in rl:
                self.theirs.send(self.dest.recv(4096))
    
        def recv(self, *args):
            return self.ours.recv(*args)
    
        def send(self, *args):
            return self.outs.recv(*args)
    
    
    def test():
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(PROXY)
        s = ssl.wrap_socket(s, ciphers="ALL:aNULL:eNULL")
        s.send(CONNECT)
        resp = s.read(4096)
        print (resp, )
    
        fs = ForwardedSocket(s, ciphers="ALL:aNULL:eNULL")
        fs.send("foobar")
    

    Don't mind custom cihpers=, that only because I didn't want to deal with certificates.

    And there's depth-1 ssl output, showing CONNECT, my response to it ssagd and depth-2 ssl negotiation and binary rubbish:

    [dima@bmg ~]$ openssl s_server  -nocert -cipher "ALL:aNULL:eNULL"
    Using default temp DH parameters
    Using default temp ECDH parameters
    ACCEPT
    -----BEGIN SSL SESSION PARAMETERS-----
    MHUCAQECAgMDBALAGQQgmn6XfJt8ru+edj6BXljltJf43Sz6AmacYM/dSmrhgl4E
    MOztEauhPoixCwS84DL29MD/OxuxuvG5tnkN59ikoqtfrnCKsk8Y9JtUU9zuaDFV
    ZaEGAgRSnJ81ogQCAgEspAYEBAEAAAA=
    -----END SSL SESSION PARAMETERS-----
    Shared ciphers: [snipped]
    CIPHER is AECDH-AES256-SHA
    Secure Renegotiation IS supported
    CONNECT mail.google.com:443 HTTP/1.0
    Connection: close
    
    sagq
    �u\�0�,�(�$��
    �"�!��kj98���� �m:��2�.�*�&���=5�����
    ��/�+�'�#��     ����g@32��ED���l4�F�1�-�)�%���</�A������
                                                            ��      ������
                                                                          �;��A��q�J&O��y�l
    
    0 讨论(0)
提交回复
热议问题