From 8c4569bfac1bd9e4632e92139ea97d224f32e292 Mon Sep 17 00:00:00 2001 From: Lanjelot Date: Wed, 30 Nov 2011 19:06:05 +0100 Subject: [PATCH] new smtp_login module bugs fixed in smtp modules better trace output in http_fuzz --- patator.py | 147 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 55 deletions(-) diff --git a/patator.py b/patator.py index 102433a..2a9fa77 100755 --- a/patator.py +++ b/patator.py @@ -32,6 +32,7 @@ Currently it supports the following modules: - ftp_login : Brute-force FTP - ssh_login : Brute-force SSH - telnet_login : Brute-force Telnet + - smtp_login : Brute-force SMTP - smtp_vrfy : Enumerate valid users using the SMTP 'VRFY' command - smtp_rcpt : Enumerate valid users using the SMTP 'RCPT TO' command - http_fuzz : Brute-force HTTP/HTTPS @@ -55,7 +56,6 @@ Future modules to be implemented: - rdp_login - vmware_login (902/tcp) - pop3_login - - smtp_login The name "Patator" comes from http://www.youtube.com/watch?v=xoBkBvnTTjo "Whatever the payload to fire, always use the same launch tube" @@ -314,9 +314,14 @@ smtp_vrfy host=10.0.0.1 user=FILE0 0=logins.txt -x ignore:fgrep='User unknown in * Use the RCPT TO command in case the VRFY command was disabled. --------- -smtp_rcpt host=10.0.0.1 user=FILE0@localhost 0=logins.txt helo='EHLO mx.fb.com' mail_from=root +smtp_rcpt host=10.0.0.1 user=FILE0@localhost 0=logins.txt helo='ehlo mx.fb.com' mail_from=root +* Brute-force authentication. + (a) Send a fake hostname (by default the real hostname is sent) +------------ (a) +smtp_login host=10.0.0.1 helo='ehlo its.me.com' user=FILE0@dom.com password=FILE1 0=logins.txt 1=passwords.txt + }}} {{{ HTTP @@ -566,6 +571,7 @@ from datetime import timedelta, datetime from struct import unpack import socket import subprocess +import hashlib warnings = [] try: @@ -613,6 +619,9 @@ def create_time_dir(top_path, desc): def pprint_seconds(seconds, fmt): return fmt % reduce(lambda x,y: divmod(x[0], y) + x[1:], [(seconds,),60,60]) +def md5hex(plain): + return hashlib.md5(plain).hexdigest() + # }}} # Controller {{{ @@ -637,6 +646,7 @@ class Controller: available_encodings = { 'hex': (hexlify, 'encode in hexadecimal'), 'b64': (b64encode, 'encode in base64'), + 'md5': (md5hex, 'hash in md5'), } def expand_key(self, arg): @@ -1468,82 +1478,107 @@ class Telnet_login(TCP_Cache): # }}} # SMTP {{{ -from smtplib import SMTP -class SMTP_vrfy(TCP_Cache): - '''Enumerate valid users using SMTP VRFY''' - - usage_hints = ( - '''%prog host=10.0.0.1 user=FILE0 0=logins.txt [helo='EHLO blah.example.com']''' - ''' -x ignore:fgrep='User unknown' -x ignore,reset,retry:code=421''', - ) +from smtplib import SMTP, SMTPAuthenticationError, SMTPHeloError, SMTPException +class SMTP_Base(TCP_Cache): - available_options = ( + available_options = TCP_Cache.available_options + available_options += ( ('host', 'hostnames or subnets to target'), ('port', 'ports to target [25]'), + ('helo', 'first command to send after connect [None]'), ('user', 'usernames to test'), - ('helo', 'first command to send after connect'), ) - available_options += TCP_Cache.available_options Response = Response_Base - def new_tcp(self, host, port): + cache_keys = ('host', 'port', 'helo') + + def new_tcp(self, host, port, helo): fp = SMTP() resp = fp.connect(host, int(port or 25)) + + if helo: + cmd, name = helo.split(' ', 1) + + if cmd.lower() == 'ehlo': + resp = fp.ehlo(name) + else: + resp = fp.helo(name) + return fp, resp + - def execute(self, host, port=None, helo=None, user=None, persistent='1'): - fp, resp = self.get_tcp(persistent, host=host, port=port) +class SMTP_vrfy(SMTP_Base): + '''Enumerate valid users using SMTP VRFY''' - if helo is not None and resp == '': - resp = fp.docmd(helo) + usage_hints = ( + '''%prog host=10.0.0.1 user=FILE0 0=logins.txt [helo='ehlo its.me.com']''' + ''' -x ignore:fgrep='User unknown' -x ignore,reset,retry:code=421''', + ) + + def execute(self, host, port=None, helo=None, user=None, persistent='1'): + fp, resp = self.get_tcp(persistent, host=host, port=port, helo=helo) if user is not None: - resp = fp.docmd('VRFY ' + user) + resp = fp.verify(user) code, mesg = resp return self.Response(code, mesg) -class SMTP_rcpt(TCP_Cache): - """Enumerate valid users using SMTP RCPT TO""" + +class SMTP_rcpt(SMTP_Base): + '''Enumerate valid users using SMTP RCPT TO''' usage_hints = ( - '''%prog host=10.0.0.1 user=FILE0@localhost 0=logins.txt [helo='EHLO blah.example.com']''' - ''' [mail_from=foo@bar.org] -x ignore:fgrep='User unknown' -x ignore,reset,retry:code=421''', + '''%prog host=10.0.0.1 user=FILE0@localhost 0=logins.txt [helo='ehlo its.me.com']''' + ''' [mail_from=bar@example.com] -x ignore:fgrep='User unknown' -x ignore,reset,retry:code=421''', ) - available_options = ( - ('host', 'hostnames or subnets to target'), - ('port', 'ports to target [25]'), - ('user', 'usernames to test'), + available_options = SMTP_Base.available_options + available_options += ( ('mail_from', 'sender email [test@example.org]'), - ('helo', 'first command to send after connect'), ) - available_options += TCP_Cache.available_options - Response = Response_Base + def execute(self, host, port=None, helo=None, mail_from='test@example.org', user=None, persistent='1'): + fp, resp = self.get_tcp(persistent, host=host, port=port, helo=helo) - def new_tcp(self, host, port): - fp = SMTP() - resp = fp.connect(host, int(port or 25)) - return fp, resp + if mail_from: + resp = fp.mail(mail_from) - def execute(self, host, port=None, helo=None, mail_from='test@example.org', user=None, persistent='1'): - fp, resp = self.get_tcp(persistent, host=host, port=port) + if user: + resp = fp.rcpt(user) - if helo is not None and resp == '': - resp = fp.docmd(helo) + fp.rset() - if mail_from: - resp = fp.docmd('MAIL FROM: ' + mail_from) + code, mesg = resp + return self.Response(code, mesg) - if rcpt_to: - resp = fp.docmd('RCPT TO: ' + user) - fp.docmd('RSET') +class SMTP_login(SMTP_Base): + '''Brute-force SMTP authentication''' + + usage_hints = ( + '''%prog host=10.0.0.1 user=f.bar@dom.com password=FILE0 0=passwords.txt [helo='ehlo its.me.com']''', + ''' -x ignore:fgrep='Authentication failed' -x ignore,reset,retry:code=421''', + ) + + available_options = SMTP_Base.available_options + available_options += ( + ('password', 'passwords to test'), + ) + + def execute(self, host, port=None, helo=None, user='', password='', persistent='1'): + fp, resp = self.get_tcp(persistent, host=host, port=port, helo=helo) + + try: + resp = fp.login(user, password) + + except (SMTPHeloError,SMTPAuthenticationError,SMTPException) as resp: + logger.debug('SMTPError: %s' % resp) code, mesg = resp return self.Response(code, mesg) + # }}} # LDAP {{{ @@ -1984,11 +2019,10 @@ class Controller_HTTP(Controller): class Response_HTTP(Response_Base): - def __init__(self, code, content_length, response, request): - self.code, self.content_length, \ - self.response, self.request = code, content_length, response, request + def __init__(self, code, content_length, response, trace): + self.code, self.content_length = code, content_length + self.response, self.trace = response, trace self.size = len(self.response) - self.trace = '%s\n%s\n\n%s' % (self.request, '='*80, self.response) def compact(self): return '%s %s' % (self.code, '%d:%d' % (self.size, self.content_length)) @@ -2082,16 +2116,18 @@ class HTTP_fuzz(TCP_Cache): fp.setopt(pycurl.WRITEFUNCTION, noop) def debug_func(t, s): + if max_mem > 0 and trace.tell() > max_mem: + return 0 + if t in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN): - if max_mem < 0 or response.tell() < max_mem: - response.write(s) + response.write(s) - elif t in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT): - if max_mem < 0 or response.tell() < max_mem: - request.write(s) + if t != pycurl.INFOTYPE_TEXT: + trace.write(s) max_mem = int(max_mem) - response, request = StringIO(), StringIO(), + response, trace = StringIO(), StringIO() + fp.setopt(pycurl.DEBUGFUNCTION, debug_func) fp.setopt(pycurl.VERBOSE, 1) @@ -2158,7 +2194,7 @@ class HTTP_fuzz(TCP_Cache): http_code = fp.getinfo(pycurl.HTTP_CODE) content_length = fp.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD) - return self.Response(http_code, content_length, response.getvalue(), request.getvalue()) + return self.Response(http_code, content_length, response.getvalue(), trace.getvalue()) # }}} @@ -2715,6 +2751,7 @@ modules = ( 'ftp_login', (Controller, FTP_login), 'ssh_login', (Controller, SSH_login), 'telnet_login', (Controller, Telnet_login), + 'smtp_login', (Controller, SMTP_login), 'smtp_vrfy', (Controller, SMTP_vrfy), 'smtp_rcpt', (Controller, SMTP_rcpt), 'http_fuzz', (Controller_HTTP, HTTP_fuzz), @@ -2753,7 +2790,7 @@ or Available modules: %s''' % '\n'.join(' + %-13s : %s' % (k, v[1].__doc__) for k, v in modules)) if warnings: - print('\nWARNING missing dependencies (see README inside)') + print('\nWARNING missing dependencies (see README inside for the supported versions)') for w in warnings: print('- %s' % w) exit(2)