From c7606ea1ccabe106c233012633bbe6a7d707a5cc Mon Sep 17 00:00:00 2001 From: Alban D Date: Sun, 26 Feb 2012 02:30:22 +0000 Subject: [PATCH] Add support for STARTTLS over XMPP. See --starttls and --xmpp_to. Update issue 8 Add support for STARTTLS over XMPP. See --starttls and --xmpp_to. --- discover_targets.py | 51 +++++++++++++++++++----- parse_command_line.py | 18 +++++++-- utils/STARTTLS.py | 73 +++++++++++++++++++++++++++++++++-- utils/SharedSettingsHelper.py | 17 ++++++-- 4 files changed, 139 insertions(+), 20 deletions(-) diff --git a/discover_targets.py b/discover_targets.py index 0efd181..1c7619e 100644 --- a/discover_targets.py +++ b/discover_targets.py @@ -97,9 +97,15 @@ def discover_targets(args_target_list, args_command_list, available_commands, ma if args_command_list.starttls == 'smtp': default_port = 25 test_stattls = test_starttls_smtp + test_starttls_args = () + elif args_command_list.starttls == 'xmpp': + default_port = 5222 + test_stattls = test_starttls_xmpp + test_starttls_args = args_command_list.xmpp_to else: default_port = 443 test_stattls = None + test_starttls_args = () for target in args_target_list: try: @@ -116,7 +122,8 @@ def discover_targets(args_target_list, args_command_list, available_commands, ma (host,port), socket_timeout, result_queue, - test_stattls)) + test_stattls, + test_starttls_args)) worker.start() thread_list.append(worker) @@ -147,7 +154,7 @@ def discover_targets(args_target_list, args_command_list, available_commands, ma return alive_target_list -def _test_connect(target, timeout, out_q=None, test_starttls=None): +def _test_connect(target, timeout, out_q=None, test_starttls=None, test_starttls_args=()): """ Try to connect to the given target=(host,port) and put the result in out_q. """ @@ -161,6 +168,8 @@ def _test_connect(target, timeout, out_q=None, test_starttls=None): s.connect((host, port)) # Host is up => keep the IP address we actually connected to ip_addr = s.getpeername() + if test_starttls: + error_text = test_starttls(s, host, test_starttls_args) except socket.timeout: # Host is down error_text = 'WARNING: Could not connect (timeout), discarding corresponding tasks.' @@ -168,10 +177,6 @@ def _test_connect(target, timeout, out_q=None, test_starttls=None): error_text = 'WARNING: Could not connect, discarding corresponding tasks.' except socket.error: # Connection Refused error_text = 'WARNING: Connection rejected, discarding corresponding tasks.' - - else: - if test_starttls: - error_text = test_starttls(s) finally: s.close() @@ -181,22 +186,48 @@ def _test_connect(target, timeout, out_q=None, test_starttls=None): return ip_addr -def test_starttls_smtp(s): +def test_starttls_smtp(s, host): """ Using a socket already connected to an SMTP server, try to initiate a STARTLS handshake. """ error_text = '' # Send a EHLO and wait for the 250 status - smtp_resp = s.recv(2048) + s.recv(2048) s.send('EHLO sslyze.scan\r\n') - smtp_resp = s.recv(2048) + if '250 ' not in s.recv(2048): + return 'WARNING: SMTP EHLO was rejected, discarding corresponding tasks.' # Semd a STARTTLS s.send('STARTTLS\r\n') smtp_resp = s.recv(2048) if 'Ready to start TLS' not in smtp_resp: - error_text = 'WARNING: STARTTLS not supported, discarding corresponding tasks.' + error_text = 'WARNING: SMTP STARTTLS not supported, discarding corresponding tasks.' + + return error_text + + +def test_starttls_xmpp(sock, host, xmpp_to): + """ + Using a socket already connected to an XMPP server, try to initiate a + STARTLS handshake. + """ + xmpp_open_stream = "" + xmpp_starttls = "" + + if xmpp_to is None: + xmpp_to = host + + error_text = '' + # Open an XMPP stream + sock.send(xmpp_open_stream.format(xmpp_to)) + if '' in sock.recv(2048): + return 'WARNING: Error opening XMPP stream, discarding corresponding tasks. Consider using --xmpp_to ?' + + # Send a STARTTLS + sock.send(xmpp_starttls) + if 'proceed' not in sock.recv(2048): + error_text = 'WARNING: XMPP STARTTLS not supported, discarding corresponding tasks.' return error_text diff --git a/parse_command_line.py b/parse_command_line.py index 72e552d..8032f36 100644 --- a/parse_command_line.py +++ b/parse_command_line.py @@ -93,11 +93,20 @@ def create_command_line_parser(available_plugins, prog_version, timeout): parser.add_option( '--starttls', help= ( - 'Uses STARTTLS to scan an SMTP server.' - ' STARTTLS should be \'smtp\'. '), + 'Uses STARTTLS to scan a server.' + ' STARTTLS should be \'smtp\' or \'xmpp\'.'), dest='starttls', default=None) + parser.add_option( + '--xmpp_to', + help= ( + 'Optional setting for STARTTLS XMPP. ' + ' XMPP_TO should be the hostname to be put in the \'to\' attribute ' + 'of the XMPP stream. Default is the server\'s hostname.'), + dest='xmpp_to', + default=None) + # Add plugin options to the parser for plugin_class in available_plugins: pluginoptiongroup = plugin_class.get_commands() @@ -215,11 +224,12 @@ def process_parsing_results(args_command_list): shared_settings['https_tunnel_port'] = None # STARTTLS - if args_command_list.starttls not in [None,'smtp']: - print PARSING_ERROR_FORMAT.format('--starttls should be \'smtp\'.') + if args_command_list.starttls not in [None,'smtp','xmpp']: + print PARSING_ERROR_FORMAT.format('--starttls should be \'smtp\' or \'xmpp\'.') return else: shared_settings['starttls'] = args_command_list.starttls + shared_settings['xmpp_to'] = args_command_list.xmpp_to if args_command_list.starttls and args_command_list.https_tunnel: print PARSING_ERROR_FORMAT.format( diff --git a/utils/STARTTLS.py b/utils/STARTTLS.py index 6f0f33a..70976de 100644 --- a/utils/STARTTLS.py +++ b/utils/STARTTLS.py @@ -1,7 +1,7 @@ #!/usr/bin/env python #------------------------------------------------------------------------------- # Name: STARTTLS.py -# Purpose: ctSSL-based STARTTLS support for SMTP. +# Purpose: Quick and dirty ctSSL-based STARTTLS support for SMTP and XMPP. # # Author: alban # @@ -63,7 +63,7 @@ def connect(self): # Get the SMTP banner - smtp_resp = sock.recv(2048) + sock.recv(2048) # Send a EHLO and wait for the 250 status sock.send('EHLO sslyze.scan\r\n') @@ -71,7 +71,7 @@ def connect(self): if '250 ' not in smtp_resp: raise SSLHandshakeError('SMTP EHLO was rejected ?') - # Semd a STARTTLS + # Send a STARTTLS sock.send('STARTTLS\r\n') smtp_resp = sock.recv(2048) if 'Ready to start TLS' not in smtp_resp: @@ -92,4 +92,71 @@ def connect(self): def close(self): self.sock.close() + + + +class XMPPConnection(): + + default_port = 5222 + xmpp_open_stream = "" + xmpp_starttls = "" + + def __init__(self, host, port=default_port, ssl=None, ssl_ctx=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, xmpp_to=None): + + self.ssl_ctx = ssl_ctx + self.ssl = ssl + self.host = host + self.port = port + self.timeout = timeout + self.sock = None + if xmpp_to is None: + self.xmpp_to = host + else: + self.xmpp_to = xmpp_to + + if self.ssl_ctx is None: + self.ssl_ctx = SSL_CTX.SSL_CTX() + # Can't verify certs by default + self.ssl_ctx.set_verify(constants.SSL_VERIFY_NONE) + + if self.ssl is None: + self.ssl = SSL.SSL(self.ssl_ctx) + + + def connect(self): + """ + Connect to a host on a given (SSL) port, send a STARTTLS command, + and perform the SSL handshake. + """ + + sock = socket.create_connection((self.host, self.port), + self.timeout) + self.sock = sock + + # Open an XMPP stream + sock.send(self.xmpp_open_stream.format(self.xmpp_to)) + sock.recv(2048) + + # Send a STARTTLS + sock.send(self.xmpp_starttls) + xmpp_resp = sock.recv(2048) + if 'proceed' not in xmpp_resp: + raise SSLHandshakeError('XMPP STARTTLS not supported ?') + + # Do the SSL handshake + self.ssl.set_socket(sock) + ssl_sock = SSLSocket(self.ssl) + + + try: + ssl_sock.do_handshake() + except Exception as e: + filter_handshake_exceptions(e) + + self.sock = ssl_sock + + + def close(self): + self.sock.close() \ No newline at end of file diff --git a/utils/SharedSettingsHelper.py b/utils/SharedSettingsHelper.py index bd8aa98..d658b0d 100644 --- a/utils/SharedSettingsHelper.py +++ b/utils/SharedSettingsHelper.py @@ -36,10 +36,19 @@ def create_ssl_connection(target, shared_settings, ssl=None, ssl_ctx=None): timeout = shared_settings['timeout'] (host, ip_addr, port) = target - if shared_settings['starttls']: + if shared_settings['starttls'] == 'smtp': ssl_connection = STARTTLS.SMTPConnection(ip_addr, port, ssl, ssl_ctx, timeout=timeout) - + elif shared_settings['starttls'] == 'xmpp': + if shared_settings['xmpp_to']: + xmpp_to = shared_settings['xmpp_to'] + else: + xmpp_to = host + + ssl_connection = \ + STARTTLS.XMPPConnection(ip_addr, port, ssl, ssl_ctx, + timeout=timeout, xmpp_to=xmpp_to) + elif shared_settings['https_tunnel_host']: # Using an HTTP CONNECT proxy to tunnel SSL traffic tunnel_host = shared_settings['https_tunnel_host'] @@ -86,12 +95,14 @@ def check_ssl_connection_is_alive(ssl_connection, shared_settings): """ result = 'N/A' - if shared_settings['starttls']: + if shared_settings['starttls'] == 'smtp': try: ssl_connection.sock.send('NOOP\r\n') result = ssl_connection.sock.read(2048).strip() except socket.timeout: result = 'Timeout on SMTP NOOP' + elif shared_settings['starttls'] == 'xmpp': + result = 'OK' else: try: # Send an HTTP GET to the server and store the HTTP Status Code