From 49b541d2035942bf9a890837fb699f9ea1ff7cbc Mon Sep 17 00:00:00 2001 From: lanjelot Date: Thu, 2 Aug 2018 15:19:35 +1000 Subject: [PATCH 01/74] Work in progress for #67 --- patator.py | 78 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/patator.py b/patator.py index 5120c59..fa5754d 100755 --- a/patator.py +++ b/patator.py @@ -3592,6 +3592,29 @@ def connect(self, host, port, scheme): return TCP_Connection(fp) + @staticmethod + def perform_fp(fp, method, url, header='', body=''): + #logger.debug('perform: %s' % url) + fp.setopt(pycurl.URL, url) + + if method == 'GET': + fp.setopt(pycurl.HTTPGET, 1) + + elif method == 'POST': + fp.setopt(pycurl.POST, 1) + fp.setopt(pycurl.POSTFIELDS, body) + + elif method == 'HEAD': + fp.setopt(pycurl.NOBODY, 1) + + else: + fp.setopt(pycurl.CUSTOMREQUEST, method) + + headers = [h.strip('\r') for h in header.split('\n') if h] + fp.setopt(pycurl.HTTPHEADER, headers) + + fp.perform() + def execute(self, url=None, host=None, port='', scheme='http', path='/', params='', query='', fragment='', body='', header='', method='GET', auto_urlencode='1', user_pass='', auth_type='basic', follow='0', max_follow='5', accept_cookie='0', proxy='', proxy_type='http', resolve='', ssl_cert='', timeout_tcp='10', timeout='20', persistent='1', @@ -3674,31 +3697,9 @@ def debug_func(t, s): # produce requests with more than one Cookie: header # and the server will process only one of them (eg. Apache only reads the last one) - def perform_fp(fp, method, url, header='', body=''): - #logger.debug('perform: %s' % url) - fp.setopt(pycurl.URL, url) - - if method == 'GET': - fp.setopt(pycurl.HTTPGET, 1) - - elif method == 'POST': - fp.setopt(pycurl.POST, 1) - fp.setopt(pycurl.POSTFIELDS, body) - - elif method == 'HEAD': - fp.setopt(pycurl.NOBODY, 1) - - else: - fp.setopt(pycurl.CUSTOMREQUEST, method) - - headers = [h.strip('\r') for h in header.split('\n') if h] - fp.setopt(pycurl.HTTPHEADER, headers) - - fp.perform() - if before_urls: for before_url in before_urls.split(','): - perform_fp(fp, 'GET', before_url, before_header) + self.perform_fp(fp, 'GET', before_url, before_header) if before_egrep: for be in before_egrep.split('|'): @@ -3718,7 +3719,7 @@ def perform_fp(fp, method, url, header='', body=''): host = '%s:%s' % (host, port) url = urlunparse((scheme, host, path, params, query, fragment)) - perform_fp(fp, method, url, header, body) + self.perform_fp(fp, method, url, header, body) target = {} target['ip'] = fp.getinfo(pycurl.PRIMARY_IP) @@ -3734,7 +3735,7 @@ def perform_fp(fp, method, url, header='', body=''): if after_urls: for after_url in after_urls.split(','): - perform_fp(fp, 'GET', after_url) + self.perform_fp(fp, 'GET', after_url) http_code = fp.getinfo(pycurl.HTTP_CODE) content_length = fp.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD) @@ -3747,6 +3748,28 @@ def perform_fp(fp, method, url, header='', body=''): # }}} +# RDP Gateway {{{ +import uuid + +class RDP_gateway(HTTP_fuzz): + '''Brute-force RDP Gateway''' + + usage_hints = ( + '''%prog rdp_gateway url='https://example.com/remoteDesktopGateway/' user_pass=COMBO00:COMBO01 0=combos.txt -x ignore:code=401''', + ) + + @staticmethod + def perform_fp(fp, method, url, header='', body=''): + method = 'RDG_OUT_DATA' + header += '\nRDG-Connection-Id: {%s}' % uuid.uuid4() + + # if authentication is successful the gateway server hangs and won't send a body + fp.setopt(pycurl.NOBODY, 1) + + HTTP_fuzz.perform_fp(fp, method, url, header) + +# }}} + # AJP {{{ try: from ajpy.ajp import AjpForwardRequest @@ -4807,6 +4830,7 @@ def execute(self, data, data2='', delay='1'): ('smtp_rcpt', (Controller, SMTP_rcpt)), ('finger_lookup', (Controller_Finger, Finger_lookup)), ('http_fuzz', (Controller_HTTP, HTTP_fuzz)), + ('rdp_gateway', (Controller_HTTP, RDP_gateway)), ('ajp_fuzz', (Controller, AJP_fuzz)), ('pop_login', (Controller, POP_login)), ('pop_passd', (Controller, POP_passd)), @@ -4840,8 +4864,8 @@ def execute(self, data, data2='', delay='1'): dependencies = { 'paramiko': [('ssh_login',), 'http://www.paramiko.org/', '1.7.7.1'], - 'pycurl': [('http_fuzz',), 'http://pycurl.io/', '7.43.0'], - 'libcurl': [('http_fuzz',), 'https://curl.haxx.se/', '7.21.0'], + 'pycurl': [('http_fuzz', 'rdp_gateway'), 'http://pycurl.io/', '7.43.0'], + 'libcurl': [('http_fuzz', 'rdp_gateway'), 'https://curl.haxx.se/', '7.21.0'], 'ajpy': [('ajp_fuzz',), 'https://github.com/hypn0s/AJPy/', '0.0.1'], 'openldap': [('ldap_login',), 'http://www.openldap.org/', '2.4.24'], 'impacket': [('smb_login', 'smb_lookupsid', 'mssql_login'), 'https://github.com/CoreSecurity/impacket', '0.9.12'], From 1ca6a41f778a0af0d959eec94eee3a4550419b90 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sat, 4 Aug 2018 12:30:28 +1000 Subject: [PATCH 02/74] Fixes #67 --- README.md | 3 ++- patator.py | 3 ++- requirements.txt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 99b7f45..dd253e1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Currently it supports the following modules: * smtp_rcpt : Enumerate valid users using the SMTP RCPT TO command * finger_lookup : Enumerate valid users using Finger * http_fuzz : Brute-force HTTP/HTTPS +* rdp_gateway : Brute-force RDP Gateway * ajp_fuzz : Brute-force AJP * pop_login : Brute-force POP * pop_passd : Brute-force poppassd (not POP3) @@ -43,7 +44,7 @@ The name "Patator" comes from [this](https://www.youtube.com/watch?v=kU2yPJJdpag Patator is NOT script-kiddie friendly, please read the full README inside [patator.py](patator.py) before reporting. -And please donate if you like this project. +Please donate if you like this project! :) [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=SB36VJH4EM5WG&lc=AU&item_name=lanjelot&item_number=patator¤cy_code=AUD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) diff --git a/patator.py b/patator.py index fa5754d..24f7f4f 100755 --- a/patator.py +++ b/patator.py @@ -39,6 +39,7 @@ + smtp_rcpt : Enumerate valid users using SMTP RCPT TO + finger_lookup : Enumerate valid users using Finger + http_fuzz : Brute-force HTTP + + rdp_gateway : Brute-force RDP Gateway + ajp_fuzz : Brute-force AJP + pop_login : Brute-force POP3 + pop_passd : Brute-force poppassd (http://netwinsite.com/poppassd/) @@ -3755,7 +3756,7 @@ class RDP_gateway(HTTP_fuzz): '''Brute-force RDP Gateway''' usage_hints = ( - '''%prog rdp_gateway url='https://example.com/remoteDesktopGateway/' user_pass=COMBO00:COMBO01 0=combos.txt -x ignore:code=401''', + '''%prog url='https://example.com/remoteDesktopGateway/' user_pass=COMBO00:COMBO01 0=combos.txt -x ignore:code=401''', ) @staticmethod diff --git a/requirements.txt b/requirements.txt index 0e1ca73..bb8acd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ ajpy pyopenssl cx_Oracle mysqlclient -psycopg2 +psycopg2-binary pycrypto dnspython IPy From 53ace4f7e797d001b986c61174108ae5d6dc15e0 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Thu, 27 Jun 2019 16:11:22 +1000 Subject: [PATCH 03/74] Fixes #116 --- Vagrantfile | 1 + patator.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 504c88e..5ce4e48 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -16,6 +16,7 @@ apt-get install -y ldap-utils # ldapsearch apt-get install -y libmysqlclient-dev # mysqlclient-python apt-get install -y ike-scan unzip default-jdk apt-get install -y libsqlite3-dev libsqlcipher-dev # pysqlcipher +apt-get install -y libpq-dev # psycopg2 # xfreerdp apt-get install -y git-core cmake xsltproc libssl-dev libx11-dev libxext-dev libxinerama-dev libxcursor-dev libxdamage-dev libxv-dev libxkbfile-dev libasound2-dev libcups2-dev libxml2 libxml2-dev libxrandr-dev libxi-dev libgstreamer-plugins-base1.0-dev diff --git a/patator.py b/patator.py index 24f7f4f..ab210a1 100755 --- a/patator.py +++ b/patator.py @@ -1053,6 +1053,15 @@ def md5hex(plain): def sha1hex(plain): return hashlib.sha1(plain).hexdigest() +def html_unescape(s): + if PY3: + import html + return html.unescape(s) + else: + from HTMLParser import HTMLParser + h = HTMLParser() + return h.unescape(h) + # I rewrote itertools.product to avoid memory over-consumption when using large wordlists def product(xs, *rest): if len(rest) == 0: @@ -1456,6 +1465,7 @@ def __init__(self, module, argv): wlists = {} kargs = [] for arg in args: # ('host=NET0', '0=10.0.0.0/24', 'user=COMBO10', 'password=COMBO11', '1=combos.txt', 'name=google.MOD2', '2=TLD') + logger.debug('arg: %r' % arg) for k, v in self.expand_key(arg): logger.debug('k: %s, v: %s' % (k, v)) @@ -3707,6 +3717,10 @@ def debug_func(t, s): mark, regex = be.split(':', 1) val = re.search(regex, response.getvalue(), re.M).group(1) + if auto_urlencode == '1': + val = html_unescape(val) + val = quote(val) + header = header.replace(mark, val) query = query.replace(mark, val) body = body.replace(mark, val) @@ -4039,7 +4053,7 @@ class VNC_login: '''Brute-force VNC''' usage_hints = ( - '''%prog host=10.0.0.1 password=FILE0 0=passwords.txt -t 1 -x retry:fgrep!='Authentication failure' --max-retries -1 -x quit:code=0''', + '''%prog host=10.0.0.1 password=FILE0 0=passwords.txt -t 1 -x 'retry:fgrep!=Authentication failure' --max-retries -1 -x quit:code=0''', ) available_options = ( From 22e84d9c071cbdad6b6ea4eb7ea1b301085ce7fb Mon Sep 17 00:00:00 2001 From: lanjelot Date: Fri, 28 Jun 2019 16:54:51 +1000 Subject: [PATCH 04/74] Fixes #117 --- patator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patator.py b/patator.py index ab210a1..1f3bb66 100755 --- a/patator.py +++ b/patator.py @@ -1060,7 +1060,7 @@ def html_unescape(s): else: from HTMLParser import HTMLParser h = HTMLParser() - return h.unescape(h) + return h.unescape(s) # I rewrote itertools.product to avoid memory over-consumption when using large wordlists def product(xs, *rest): From 3c72f68554ecfe494c922ec81538d9496e44b230 Mon Sep 17 00:00:00 2001 From: ines Date: Sat, 27 Jul 2019 13:48:44 +0200 Subject: [PATCH 05/74] add ssl support for TCP_fuzz --- patator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/patator.py b/patator.py index 1f3bb66..eab7bba 100755 --- a/patator.py +++ b/patator.py @@ -4787,13 +4787,17 @@ class TCP_fuzz: ('host', 'target host'), ('port', 'target port'), ('timeout', 'seconds to wait for a response [10]'), + ('ssl', 'use SSL/TLS [0|1]'), ) available_actions = () Response = Response_Base - def execute(self, host, port, data='', timeout='2'): + def execute(self, host, port, data='', timeout='2',ssl='0'): fp = socket.create_connection((host, port), int(timeout)) + if ssl!='0': + from ssl import wrap_socket + fp = wrap_socket(fp) fp.send(data.decode('hex')) with Timing() as timing: resp = fp.recv(1024) From 988d21101499e037e9d2ddd5bbfc4fffbd481d12 Mon Sep 17 00:00:00 2001 From: ines Date: Sat, 27 Jul 2019 13:52:42 +0200 Subject: [PATCH 06/74] adding port in example command --- patator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patator.py b/patator.py index eab7bba..a3cfc21 100755 --- a/patator.py +++ b/patator.py @@ -4780,7 +4780,7 @@ class TCP_fuzz: '''Fuzz TCP services''' usage_hints = ( - '''%prog host=10.0.0.1 data=RANGE0 0=hex:0x00-0xffffff''', + '''%prog host=10.0.0.1 port=10000 data=RANGE0 0=hex:0x00-0xffffff''', ) available_options = ( From 748bb90eafa28a31956e721e7f00286a9773ab02 Mon Sep 17 00:00:00 2001 From: MrTchuss Date: Wed, 9 Oct 2019 14:40:01 +0200 Subject: [PATCH 07/74] [HTTP_FUZZ] cURL path-as-is support in command-line --- patator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/patator.py b/patator.py index 1f3bb66..531d7ff 100755 --- a/patator.py +++ b/patator.py @@ -3575,6 +3575,7 @@ class HTTP_fuzz(TCP_Cache): ('user_pass', 'username and password for HTTP authentication (user:pass)'), ('auth_type', 'type of HTTP authentication [basic | digest | ntlm]'), ('follow', 'follow any Location redirect [0|1]'), + ('pathasis', 'handle sequences of /../ or /./ [0|1]'), ('max_follow', 'redirection limit [5]'), ('accept_cookie', 'save received cookies to issue them in future requests [0|1]'), ('proxy', 'proxy to use (host:port)'), @@ -3628,7 +3629,7 @@ def perform_fp(fp, method, url, header='', body=''): def execute(self, url=None, host=None, port='', scheme='http', path='/', params='', query='', fragment='', body='', header='', method='GET', auto_urlencode='1', user_pass='', auth_type='basic', - follow='0', max_follow='5', accept_cookie='0', proxy='', proxy_type='http', resolve='', ssl_cert='', timeout_tcp='10', timeout='20', persistent='1', + follow='0', pathasis='0', max_follow='5', accept_cookie='0', proxy='', proxy_type='http', resolve='', ssl_cert='', timeout_tcp='10', timeout='20', persistent='1', before_urls='', before_header='', before_egrep='', after_urls='', max_mem='-1'): if url: @@ -3656,6 +3657,7 @@ def execute(self, url=None, host=None, port='', scheme='http', path='/', params= fp, _ = self.bind(host, port, scheme) fp.setopt(pycurl.FOLLOWLOCATION, int(follow)) + fp.setopt(pycurl.PATH_AS_IS, int(pathasis)) fp.setopt(pycurl.MAXREDIRS, int(max_follow)) fp.setopt(pycurl.CONNECTTIMEOUT, int(timeout_tcp)) fp.setopt(pycurl.TIMEOUT, int(timeout)) From a1db6bf842696364f0459c359e65b84e50b26792 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sun, 13 Oct 2019 16:55:30 +1000 Subject: [PATCH 08/74] Add pathasis option to http_fuzz --- patator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patator.py b/patator.py index 531d7ff..28ace63 100755 --- a/patator.py +++ b/patator.py @@ -3572,10 +3572,10 @@ class HTTP_fuzz(TCP_Cache): ('raw_request', 'load request from file'), ('scheme', 'scheme [http|https]'), ('auto_urlencode', 'automatically perform URL-encoding [1|0]'), + ('pathasis', 'retain sequences of /../ or /./ [0|1]'), ('user_pass', 'username and password for HTTP authentication (user:pass)'), ('auth_type', 'type of HTTP authentication [basic | digest | ntlm]'), ('follow', 'follow any Location redirect [0|1]'), - ('pathasis', 'handle sequences of /../ or /./ [0|1]'), ('max_follow', 'redirection limit [5]'), ('accept_cookie', 'save received cookies to issue them in future requests [0|1]'), ('proxy', 'proxy to use (host:port)'), @@ -3628,8 +3628,8 @@ def perform_fp(fp, method, url, header='', body=''): fp.perform() def execute(self, url=None, host=None, port='', scheme='http', path='/', params='', query='', fragment='', body='', - header='', method='GET', auto_urlencode='1', user_pass='', auth_type='basic', - follow='0', pathasis='0', max_follow='5', accept_cookie='0', proxy='', proxy_type='http', resolve='', ssl_cert='', timeout_tcp='10', timeout='20', persistent='1', + header='', method='GET', auto_urlencode='1', pathasis='0', user_pass='', auth_type='basic', + follow='0', max_follow='5', accept_cookie='0', proxy='', proxy_type='http', resolve='', ssl_cert='', timeout_tcp='10', timeout='20', persistent='1', before_urls='', before_header='', before_egrep='', after_urls='', max_mem='-1'): if url: @@ -3656,8 +3656,8 @@ def execute(self, url=None, host=None, port='', scheme='http', path='/', params= fp, _ = self.bind(host, port, scheme) - fp.setopt(pycurl.FOLLOWLOCATION, int(follow)) fp.setopt(pycurl.PATH_AS_IS, int(pathasis)) + fp.setopt(pycurl.FOLLOWLOCATION, int(follow)) fp.setopt(pycurl.MAXREDIRS, int(max_follow)) fp.setopt(pycurl.CONNECTTIMEOUT, int(timeout_tcp)) fp.setopt(pycurl.TIMEOUT, int(timeout)) From 03fdb3cce08d8cebf6a3ca49f4b2179bdd659ac2 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sun, 13 Oct 2019 17:12:46 +1000 Subject: [PATCH 09/74] Add ssl option to tcp_fuzz --- patator.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/patator.py b/patator.py index a3cfc21..5f8adc4 100755 --- a/patator.py +++ b/patator.py @@ -899,6 +899,7 @@ def process_logs(queue, indicatorsfmt, argv, log_dir): import ctypes import glob from xml.sax.saxutils import escape as xmlescape, quoteattr as xmlquoteattr +from ssl import wrap_socket try: # python3+ from queue import Empty, Full @@ -3112,8 +3113,6 @@ def execute(self, host, port='513', luser='root', user='', password=None, prompt # }}} # VMauthd {{{ -from ssl import wrap_socket - class LineReceiver_Error(Exception): pass @@ -4793,10 +4792,9 @@ class TCP_fuzz: Response = Response_Base - def execute(self, host, port, data='', timeout='2',ssl='0'): + def execute(self, host, port, data='', timeout='2', ssl='0'): fp = socket.create_connection((host, port), int(timeout)) - if ssl!='0': - from ssl import wrap_socket + if ssl != '0': fp = wrap_socket(fp) fp.send(data.decode('hex')) with Timing() as timing: From e539ab5271a10432d65f82d9332cf3f36546a53c Mon Sep 17 00:00:00 2001 From: lanjelot Date: Wed, 16 Oct 2019 17:10:36 +1000 Subject: [PATCH 10/74] Fixes #120 --- patator.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/patator.py b/patator.py index b87d170..b9f0842 100755 --- a/patator.py +++ b/patator.py @@ -745,7 +745,7 @@ def filter(self, record): else: return 1 -def process_logs(queue, indicatorsfmt, argv, log_dir): +def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file): ignore_ctrlc() @@ -765,14 +765,21 @@ def process_logs(queue, indicatorsfmt, argv, log_dir): names = [name for name, _ in indicatorsfmt] + ['candidate', 'num', 'mesg'] - if log_dir: - runtime_log = os.path.join(log_dir, 'RUNTIME.log') - results_csv = os.path.join(log_dir, 'RESULTS.csv') - results_xml = os.path.join(log_dir, 'RESULTS.xml') + if runtime_file: + runtime_log = os.path.join(log_dir if log_dir else './', runtime_file) with open(runtime_log, 'a') as f: f.write('$ %s\n' % ' '.join(argv)) + handler_log = logging.FileHandler(runtime_log) + handler_log.setFormatter(TXTFormatter(indicatorsfmt)) + + logger.addHandler(handler_log) + + if log_dir: + results_csv = os.path.join(log_dir, 'RESULTS.csv') + results_xml = os.path.join(log_dir, 'RESULTS.xml') + if not os.path.exists(results_csv): with open(results_csv, 'w') as f: f.write('time,level,%s\n' % ','.join(names)) @@ -815,18 +822,15 @@ def process_logs(queue, indicatorsfmt, argv, log_dir): f.seek(offset) f.truncate(f.tell()) - handler_log = logging.FileHandler(runtime_log) handler_csv = logging.FileHandler(results_csv) handler_xml = logging.FileHandler(results_xml) handler_csv.addFilter(MsgFilter()) handler_xml.addFilter(MsgFilter()) - handler_log.setFormatter(TXTFormatter(indicatorsfmt)) handler_csv.setFormatter(CSVFormatter(indicatorsfmt)) handler_xml.setFormatter(XMLFormatter(indicatorsfmt)) - logger.addHandler(handler_log) logger.addHandler(handler_csv) logger.addHandler(handler_xml) @@ -1396,6 +1400,7 @@ def format_usage(self, usage): log_grp = OptionGroup(parser, 'Logging') log_grp.add_option('-l', dest='log_dir', metavar='DIR', help="save output and response data into DIR ") log_grp.add_option('-L', dest='auto_log', metavar='SFX', help="automatically save into DIR/yyyy-mm-dd/hh:mm:ss_SFX (DIR defaults to '/tmp/patator')") + log_grp.add_option('-R', dest='runtime_file', metavar='FILE', help="save output to FILE") dbg_grp = OptionGroup(parser, 'Debugging') dbg_grp.add_option('-d', '--debug', dest='debug', action='store_true', default=False, help='enable debug messages') @@ -1451,7 +1456,7 @@ def __init__(self, module, argv): log_queue = multiprocessing.Queue() - logsvc = multiprocessing.Process(name='LogSvc', target=process_logs, args=(log_queue, module.Response.indicatorsfmt, argv, build_logdir(opts.log_dir, opts.auto_log))) + logsvc = multiprocessing.Process(name='LogSvc', target=process_logs, args=(log_queue, module.Response.indicatorsfmt, argv, build_logdir(opts.log_dir, opts.auto_log), opts.runtime_file)) logsvc.daemon = True logsvc.start() From 3135ea166658e5345d8bd3ae47c5b04c3a6b58c2 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Thu, 17 Oct 2019 10:24:17 +1000 Subject: [PATCH 11/74] Fixes #120 --- patator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patator.py b/patator.py index b9f0842..0b36c4c 100755 --- a/patator.py +++ b/patator.py @@ -765,8 +765,8 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file): names = [name for name, _ in indicatorsfmt] + ['candidate', 'num', 'mesg'] - if runtime_file: - runtime_log = os.path.join(log_dir if log_dir else './', runtime_file) + if runtime_file or log_dir: + runtime_log = os.path.join(log_dir or '', runtime_file or 'RUNTIME.log') with open(runtime_log, 'a') as f: f.write('$ %s\n' % ' '.join(argv)) From 1732fbbae7e0ad127007f03b0cee25695df1c1ad Mon Sep 17 00:00:00 2001 From: lanjelot Date: Thu, 17 Oct 2019 16:21:55 +1000 Subject: [PATCH 12/74] Fixes #118 --- patator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/patator.py b/patator.py index 0b36c4c..cb04b41 100755 --- a/patator.py +++ b/patator.py @@ -3731,6 +3731,8 @@ def debug_func(t, s): query = query.replace(mark, val) body = body.replace(mark, val) + response = StringIO() + if auto_urlencode == '1': path = quote(path) query = urlencode(parse_qsl(query, True)) From 22ba7337956fbc8cb9333cd282df15f66de3db69 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sun, 24 Nov 2019 13:57:02 +1000 Subject: [PATCH 13/74] Fixes #103 and more --- patator.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/patator.py b/patator.py index cb04b41..8bbc996 100755 --- a/patator.py +++ b/patator.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (C) 2012 Sebastien MACKE # @@ -2079,7 +2079,7 @@ def monitor_interaction(self): etc_time = etc_seconds.strftime('%H:%M:%S') logger.info('Progress: {0:>3}% ({1}/{2}) | Speed: {3:.0f} r/s | ETC: {4} ({5} remaining) {6}'.format( - total_count * 100/total_size, + total_count * 100 // total_size, total_count, total_size, speed_avg, @@ -2885,10 +2885,6 @@ def execute(self, host, port='139', user='', password='', sid=None, rid=None, pe # POP {{{ from poplib import POP3, POP3_SSL, error_proto as pop_error -class POP_Connection(TCP_Connection): - def close(self): - self.fp.quit() - class POP_login(TCP_Cache): '''Brute-force POP3''' @@ -2916,7 +2912,7 @@ def connect(self, host, port, ssl, timeout): if not port: port = 995 fp = POP3_SSL(host, int(port)) # timeout=int(timeout)) # no timeout option in python2 - return POP_Connection(fp, fp.welcome) + return TCP_Connection(fp, fp.welcome) def execute(self, host, port='', ssl='0', user=None, password=None, timeout='10', persistent='1'): @@ -2937,12 +2933,12 @@ def execute(self, host, port='', ssl='0', user=None, password=None, timeout='10' except pop_error as e: logger.debug('pop_error: %s' % e) - resp = str(e) + resp = e.args[0] if persistent == '0': self.reset() - code, mesg = resp.split(' ', 1) + code, mesg = B(resp).split(' ', 1) return self.Response(code, mesg, timing) class POP_passd: @@ -3676,6 +3672,9 @@ def debug_func(t, s): if max_mem > 0 and trace.tell() > max_mem: return 0 + if t not in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT, pycurl.INFOTYPE_TEXT, pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN): + return 0 + s = B(s) if t in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT): From 94cdfb19bbd8ac4f86b79a2d5e5c5788078145a8 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sun, 24 Nov 2019 14:23:41 +1000 Subject: [PATCH 14/74] Fixes #27 --- patator.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/patator.py b/patator.py index 8bbc996..aecc78a 100755 --- a/patator.py +++ b/patator.py @@ -745,7 +745,7 @@ def filter(self, record): else: return 1 -def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file): +def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xml_file): ignore_ctrlc() @@ -776,14 +776,22 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file): logger.addHandler(handler_log) - if log_dir: - results_csv = os.path.join(log_dir, 'RESULTS.csv') - results_xml = os.path.join(log_dir, 'RESULTS.xml') + if csv_file or log_dir: + results_csv = os.path.join(log_dir or '', csv_file or 'RESULTS.csv') if not os.path.exists(results_csv): with open(results_csv, 'w') as f: f.write('time,level,%s\n' % ','.join(names)) + handler_csv = logging.FileHandler(results_csv) + handler_csv.addFilter(MsgFilter()) + handler_csv.setFormatter(CSVFormatter(indicatorsfmt)) + + logger.addHandler(handler_csv) + + if xml_file or log_dir: + results_xml = os.path.join(log_dir or '', xml_file or 'RESULTS.xml') + if not os.path.exists(results_xml): with open(results_xml, 'w') as f: f.write('\n\n') @@ -822,16 +830,10 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file): f.seek(offset) f.truncate(f.tell()) - handler_csv = logging.FileHandler(results_csv) handler_xml = logging.FileHandler(results_xml) - - handler_csv.addFilter(MsgFilter()) handler_xml.addFilter(MsgFilter()) - - handler_csv.setFormatter(CSVFormatter(indicatorsfmt)) handler_xml.setFormatter(XMLFormatter(indicatorsfmt)) - logger.addHandler(handler_csv) logger.addHandler(handler_xml) while True: @@ -1401,6 +1403,8 @@ def format_usage(self, usage): log_grp.add_option('-l', dest='log_dir', metavar='DIR', help="save output and response data into DIR ") log_grp.add_option('-L', dest='auto_log', metavar='SFX', help="automatically save into DIR/yyyy-mm-dd/hh:mm:ss_SFX (DIR defaults to '/tmp/patator')") log_grp.add_option('-R', dest='runtime_file', metavar='FILE', help="save output to FILE") + log_grp.add_option('--csv', dest='csv_file', metavar='FILE', help="save CSV results to FILE") + log_grp.add_option('--xml', dest='xml_file', metavar='FILE', help="save XML results to FILE") dbg_grp = OptionGroup(parser, 'Debugging') dbg_grp.add_option('-d', '--debug', dest='debug', action='store_true', default=False, help='enable debug messages') @@ -1456,7 +1460,7 @@ def __init__(self, module, argv): log_queue = multiprocessing.Queue() - logsvc = multiprocessing.Process(name='LogSvc', target=process_logs, args=(log_queue, module.Response.indicatorsfmt, argv, build_logdir(opts.log_dir, opts.auto_log), opts.runtime_file)) + logsvc = multiprocessing.Process(name='LogSvc', target=process_logs, args=(log_queue, module.Response.indicatorsfmt, argv, build_logdir(opts.log_dir, opts.auto_log), opts.runtime_file, opts.csv_file, opts.xml_file)) logsvc.daemon = True logsvc.start() From a92c06f0d0205d92fcaf4f641b5481ccae8252bf Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sun, 24 Nov 2019 18:45:07 +1000 Subject: [PATCH 15/74] Fixes #89 --- patator.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/patator.py b/patator.py index aecc78a..9edffcf 100755 --- a/patator.py +++ b/patator.py @@ -664,8 +664,11 @@ def headers(self): def result(self, *args): self.send('result', *args) - def save(self, *args): - self.send('save', *args) + def save_response(self, *args): + self.send('save_response', *args) + + def save_hit(self, *args): + self.send('save_hit', *args) def setLevel(self, level): self.send('setLevel', level) @@ -745,7 +748,7 @@ def filter(self, record): else: return 1 -def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xml_file): +def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xml_file, hits_file): ignore_ctrlc() @@ -836,6 +839,10 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm logger.addHandler(handler_xml) + if hits_file: + if os.path.exists(hits_file): + os.rename(hits_file, hits_file + '.' + strftime("%Y%m%d%H%M%S", localtime())) + while True: pname, action, args = queue.get() @@ -864,7 +871,7 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm else: logger.info(None, extra=dict(results)) - elif action == 'save': + elif action == 'save_response': resp, num = args @@ -873,6 +880,11 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm with open('%s.txt' % os.path.join(log_dir, filename), 'w') as f: f.write(resp.dump()) + elif action == 'save_hit': + if hits_file: + with open(hits_file, 'a') as f: + f.write('%s\n' % ':'.join(args)) + elif action == 'setLevel': logger.setLevel(args[0]) @@ -923,7 +935,7 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm PY3 = sys.version_info >= (3,) if PY3: # http://python3porting.com/problems.html def b(x): - return x.encode('ISO-8859-1') + return x.encode('UTF-8') # 'ISO-8859-1') def B(x): return x.decode() else: @@ -1140,13 +1152,13 @@ def __init__(self, typ, rng, random=None): step = 1 elif typ == 'letters': - charset = [c for c in string.letters] + charset = [c for c in string.ascii_letters] elif typ in ('lower', 'lowercase'): - charset = [c for c in string.lowercase] + charset = [c for c in string.ascii_lowercase] elif typ in ('upper', 'uppercase'): - charset = [c for c in string.uppercase] + charset = [c for c in string.ascii_uppercase] def zrange(start, stop, step, fmt): x = start @@ -1405,6 +1417,7 @@ def format_usage(self, usage): log_grp.add_option('-R', dest='runtime_file', metavar='FILE', help="save output to FILE") log_grp.add_option('--csv', dest='csv_file', metavar='FILE', help="save CSV results to FILE") log_grp.add_option('--xml', dest='xml_file', metavar='FILE', help="save XML results to FILE") + log_grp.add_option('--hits', dest='hits_file', metavar='FILE', help="save found candidates to FILE") dbg_grp = OptionGroup(parser, 'Debugging') dbg_grp.add_option('-d', '--debug', dest='debug', action='store_true', default=False, help='enable debug messages') @@ -1460,7 +1473,7 @@ def __init__(self, module, argv): log_queue = multiprocessing.Queue() - logsvc = multiprocessing.Process(name='LogSvc', target=process_logs, args=(log_queue, module.Response.indicatorsfmt, argv, build_logdir(opts.log_dir, opts.auto_log), opts.runtime_file, opts.csv_file, opts.xml_file)) + logsvc = multiprocessing.Process(name='LogSvc', target=process_logs, args=(log_queue, module.Response.indicatorsfmt, argv, build_logdir(opts.log_dir, opts.auto_log), opts.runtime_file, opts.csv_file, opts.xml_file, opts.hits_file)) logsvc.daemon = True logsvc.start() @@ -1881,7 +1894,7 @@ def shutdown(): payload[k] = payload[k].replace('PROG%d' %i, prod[i]) for k, m, e in self.enc_keys: - payload[k] = re.sub(r'{0}(.+?){0}'.format(m), lambda m: e(m.group(1)), payload[k]) + payload[k] = re.sub(r'{0}(.+?){0}'.format(m), lambda m: e(b(m.group(1))), payload[k]) logger.debug('product: %s' % prod) pp_prod = ':'.join(prod) @@ -2003,7 +2016,8 @@ def report_progress(self): elif 'ignore' not in actions: p.hits_count += 1 - logger.save(resp, offset) + logger.save_response(resp, offset) + logger.save_hit(current) self.push_final(resp) From e975529cea83778c19f6d4442c426a7514044b3a Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sat, 30 Nov 2019 15:31:02 +1000 Subject: [PATCH 16/74] Fixes #107 --- patator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/patator.py b/patator.py index 9edffcf..aa84b9d 100755 --- a/patator.py +++ b/patator.py @@ -3524,8 +3524,12 @@ def __init__(self, fd): self.rfile = fd self.error = None self.body = None - self.raw_requestline = self.rfile.readline() + + command, path, version = B(self.rfile.readline()).split() + self.raw_requestline = b('%s %s %s' % (command, path, 'HTTP/1.1' if version.startswith('HTTP/2') else version)) + self.parse_request() + self.request_version = version if self.command == 'POST': self.body = B(self.rfile.read(-1)).rstrip('\r\n') From f5b161a38986b32f756e2e2bd6640d2a764fce15 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sat, 30 Nov 2019 15:33:27 +1000 Subject: [PATCH 17/74] Fix Python3 compat --- patator.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/patator.py b/patator.py index aa84b9d..c58e5d6 100755 --- a/patator.py +++ b/patator.py @@ -935,9 +935,9 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm PY3 = sys.version_info >= (3,) if PY3: # http://python3porting.com/problems.html def b(x): - return x.encode('UTF-8') # 'ISO-8859-1') + return x.encode('ISO-8859-1', errors='ignore') def B(x): - return x.decode() + return x.decode(errors='ignore') else: def b(x): return x @@ -3826,12 +3826,15 @@ def close(self): sock.close() class Response_AJP(Response_HTTP): - def __init__(self, code, response, status_msg='', timing=0, trace=None, content_length=-1, target={}): + def __init__(self, code, response, timing=0, trace=None, content_length=-1, target={}): Response_HTTP.__init__(self, code, response, timing, trace, content_length, target) - self.status_msg = status_msg def __str__(self): - return self.status_msg or self.mesg + lines = self.mesg.splitlines() + if lines: + return lines[0].rstrip('\r') + else: + return self.mesg def prepare_ajp_forward_request(target_host, req_uri, method): fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER) @@ -3878,7 +3881,7 @@ def connect(self, host, port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.connect((host, int(port))) - stream = sock.makefile("rb", bufsize=0) + stream = sock.makefile('rb', None if PY3 else 0) return AJP_Connection((sock, stream)) @@ -3893,7 +3896,7 @@ def execute(self, url=None, host=None, port='8009', path='/', params='', query=' req_uri = urlunparse(('', '', path, params, query, fragment)) fr = prepare_ajp_forward_request(host, req_uri, AjpForwardRequest.REQUEST_METHODS.get('GET')) - fr.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + b64encode(user_pass) + fr.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + B(b64encode(b(user_pass))) headers = [h.strip('\r') for h in header.split('\n') if h] for h in headers: @@ -3907,13 +3910,13 @@ def execute(self, url=None, host=None, port='8009', path='/', params='', query=' snd_hdrs_res = responses[0] http_code = snd_hdrs_res.http_status_code - http_status_msg = snd_hdrs_res.http_status_msg + http_status_msg = B(snd_hdrs_res.http_status_msg) content_length = int(snd_hdrs_res.response_headers.get('Content-Length', 0)) data_res = responses[1:-1] data = '' for dr in data_res: - data += dr.data + data += B(dr.data) target = {} target['ip'] = host @@ -3922,7 +3925,7 @@ def execute(self, url=None, host=None, port='8009', path='/', params='', query=' if persistent == '0': self.reset() - return self.Response(http_code, data, http_status_msg, timing, data, content_length, target) + return self.Response(http_code, http_status_msg + '\n' + data, timing, data, content_length, target) # }}} From 98bd95bd5e865cb238bab46d082065cacc121be1 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sat, 21 Mar 2020 15:01:58 +1000 Subject: [PATCH 18/74] Switch to bionic64 --- Vagrantfile | 4 ++-- patator.py | 8 +++++--- requirements.txt | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 5ce4e48..6124ec3 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -13,7 +13,7 @@ apt-get install -y tmux git wget build-essential vim # requirements.txt deps apt-get install -y libcurl4-openssl-dev python3-dev libssl-dev # pycurl apt-get install -y ldap-utils # ldapsearch -apt-get install -y libmysqlclient-dev # mysqlclient-python +apt-get install -y libmariadbclient-dev # mysqlclient-python apt-get install -y ike-scan unzip default-jdk apt-get install -y libsqlite3-dev libsqlcipher-dev # pysqlcipher apt-get install -y libpq-dev # psycopg2 @@ -33,7 +33,7 @@ pip install patator SCRIPT Vagrant.configure(2) do |config| - config.vm.box = "ubuntu/xenial64" + config.vm.box = "ubuntu/bionic64" config.vm.box_check_update = false # prevent TTY error messages diff --git a/patator.py b/patator.py index c58e5d6..34836ff 100755 --- a/patator.py +++ b/patator.py @@ -2522,10 +2522,12 @@ class SMTP_Base(TCP_Cache): def connect(self, host, port, ssl, helo, starttls, timeout): if ssl == '0': - if not port: port = 25 + if not port: + port = 25 fp = SMTP(timeout=int(timeout)) else: - if not port: port = 465 + if not port: + port = 465 fp = SMTP_SSL(timeout=int(timeout)) resp = fp.connect(host, int(port)) @@ -3235,7 +3237,7 @@ def execute(self, host, port='902', user=None, password=None, ssl='1', timeout=' # MySQL {{{ try: - import _mysql + from MySQLdb import _mysql except ImportError: notfound.append('mysqlclient') diff --git a/requirements.txt b/requirements.txt index bb8acd4..5c93b5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ paramiko pycurl ajpy -#impacket # no python3 compatibility +impacket pyopenssl cx_Oracle mysqlclient From 30abee9a36aa181320cbae9a845fec2b45e05e1a Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sun, 22 Mar 2020 12:08:56 +1000 Subject: [PATCH 19/74] Update versions --- Vagrantfile | 5 ++-- patator.py | 81 +++++++++++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 6124ec3..acac447 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -18,8 +18,9 @@ apt-get install -y ike-scan unzip default-jdk apt-get install -y libsqlite3-dev libsqlcipher-dev # pysqlcipher apt-get install -y libpq-dev # psycopg2 -# xfreerdp -apt-get install -y git-core cmake xsltproc libssl-dev libx11-dev libxext-dev libxinerama-dev libxcursor-dev libxdamage-dev libxv-dev libxkbfile-dev libasound2-dev libcups2-dev libxml2 libxml2-dev libxrandr-dev libxi-dev libgstreamer-plugins-base1.0-dev +# xfreerdp (see https://github.com/FreeRDP/FreeRDP/wiki/Compilation) +apt-get install ninja-build build-essential git-core debhelper cdbs dpkg-dev autotools-dev cmake pkg-config xmlto libssl-dev docbook-xsl xsltproc libxkbfile-dev libx11-dev libwayland-dev libxrandr-dev libxi-dev libxrender-dev libxext-dev libxinerama-dev libxfixes-dev libxcursor-dev libxv-dev libxdamage-dev libxtst-dev libcups2-dev libpcsclite-dev libasound2-dev libpulse-dev libjpeg-dev libgsm1-dev libusb-1.0-0-dev libudev-dev libdbus-glib-1-dev uuid-dev libxml2-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libfaad-dev libfaac-dev +apt-get install libavutil-dev libavcodec-dev libavresample-dev git clone https://github.com/FreeRDP/FreeRDP/ /tmp/FreeRDP && (cd /tmp/FreeRDP && cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_SSE2=ON . && make && sudo make install) SCRIPT diff --git a/patator.py b/patator.py index 34836ff..78d7691 100755 --- a/patator.py +++ b/patator.py @@ -11,14 +11,17 @@ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details (http://www.gnu.org/licenses/gpl.txt). +import sys + __author__ = 'Sebastien Macke' __email__ = 'patator@hsc.fr' __url__ = 'http://www.hsc.fr/ressources/outils/patator/' __git__ = 'https://github.com/lanjelot/patator' -__twitter__ = 'http://twitter.com/lanjelot' -__version__ = '0.7' +__twitter__ = 'https://twitter.com/lanjelot' +__version__ = '0.8' __license__ = 'GPLv2' -__banner__ = 'Patator v%s (%s)' % (__version__, __git__) +__pyver__ = '%d.%d.%d' % sys.version_info[0:3] +__banner__ = 'Patator %s (%s) with python-%s' % (__version__, __git__, __pyver__) # README {{{ @@ -126,37 +129,37 @@ | Required for | URL | Version | -------------------------------------------------------------------------------------------------- -paramiko | SSH | http://www.lag.net/paramiko/ | 1.7.7.1 | +paramiko | SSH | http://www.lag.net/paramiko/ | 2.7.1 | -------------------------------------------------------------------------------------------------- pycurl | HTTP | http://pycurl.sourceforge.net/ | 7.43.0 | -------------------------------------------------------------------------------------------------- -libcurl | HTTP | https://curl.haxx.se/ | 7.21.0 | +libcurl | HTTP | https://curl.haxx.se/ | 7.58.0 | -------------------------------------------------------------------------------------------------- -ajpy | AJP | https://github.com/hypn0s/AJPy/ | 0.0.1 | +ajpy | AJP | https://github.com/hypn0s/AJPy/ | 0.0.4 | -------------------------------------------------------------------------------------------------- -openldap | LDAP | http://www.openldap.org/ | 2.4.24 | +openldap | LDAP | http://www.openldap.org/ | 2.4.45 | -------------------------------------------------------------------------------------------------- -impacket | SMB, MSSQL | https://github.com/CoreSecurity/impacket | 0.9.12 | +impacket | SMB, MSSQL | https://github.com/CoreSecurity/impacket | 0.9.20 | -------------------------------------------------------------------------------------------------- -pyOpenSSL | impacket | https://pyopenssl.org/ | 17.5.0 | +pyOpenSSL | impacket | https://pyopenssl.org/ | 19.1.0 | -------------------------------------------------------------------------------------------------- -cx_Oracle | Oracle | http://cx-oracle.sourceforge.net/ | 5.1.1 | +cx_Oracle | Oracle | http://cx-oracle.sourceforge.net/ | 7.3.0 | -------------------------------------------------------------------------------------------------- -mysqlclient | MySQL | https://github.com/PyMySQL/mysqlclient-python | 1.3.12 | +mysqlclient | MySQL | https://github.com/PyMySQL/mysqlclient-python | 1.4.6 | -------------------------------------------------------------------------------------------------- xfreerdp | RDP (NLA) | https://github.com/FreeRDP/FreeRDP/ | 1.2.0 | -------------------------------------------------------------------------------------------------- -psycopg | PostgreSQL | http://initd.org/psycopg/ | 2.4.5 | +psycopg | PostgreSQL | http://initd.org/psycopg/ | 2.8.4 | -------------------------------------------------------------------------------------------------- pycrypto | VNC, impacket | http://www.dlitz.net/software/pycrypto/ | 2.6.1 | -------------------------------------------------------------------------------------------------- -dnspython | DNS | http://www.dnspython.org/ | 1.10.0 | +dnspython | DNS | http://www.dnspython.org/ | 1.16.0 | -------------------------------------------------------------------------------------------------- -IPy | NET keyword | https://github.com/haypo/python-ipy | 0.75 | +IPy | NET keyword | https://github.com/haypo/python-ipy | 1.0 | -------------------------------------------------------------------------------------------------- -pysnmp | SNMP | http://pysnmp.sourceforge.net/ | 4.2.1 | +pysnmp | SNMP | http://pysnmp.sourceforge.net/ | 4.4.12 | -------------------------------------------------------------------------------------------------- -pyasn1 | SNMP, impacket | http://sourceforge.net/projects/pyasn1/ | 0.1.2 | +pyasn1 | SNMP, impacket | http://sourceforge.net/projects/pyasn1/ | 0.4.8 | -------------------------------------------------------------------------------------------------- ike-scan | IKE | http://www.nta-monitor.com/tools-resources/ | 1.9 | -------------------------------------------------------------------------------------------------- @@ -166,7 +169,7 @@ -------------------------------------------------------------------------------------------------- pysqlcipher | SQLCipher | https://github.com/leapcode/pysqlcipher/ | 2.6.10 | -------------------------------------------------------------------------------------------------- -python | | http://www.python.org/ | 2.7 | +python | | http://www.python.org/ | 3.6 | -------------------------------------------------------------------------------------------------- * Shortcuts (optional) @@ -587,6 +590,12 @@ CHANGELOG --------- +* v0.8 2020/03/22 + - new switches (-R, --csv, --xml, --hits) + - new pathasis option for http_fuzz + - new rdp_gateway module + - fixed various issues reported on Github + * v0.7 2017/12/14 - added Python3 support - added Windows support @@ -1962,6 +1971,9 @@ def shutdown(): if 'fail' in actions: break + if 'quit' in actions: + return shutdown() + if 'retry' in actions: continue @@ -2000,6 +2012,9 @@ def report_progress(self): p.current = current p.seconds[p.done_count % len(p.seconds)] = seconds + if 'quit' in actions: + self.ns.quit_now = True + if 'fail' in actions: if not self.allow_ignore_failures or 'ignore' not in actions: logger.result('fail', resp, current, offset) @@ -2023,9 +2038,6 @@ def report_progress(self): p.done_count += 1 - if 'quit' in actions: - self.ns.quit_now = True - def monitor_interaction(self): @@ -3106,6 +3118,7 @@ def execute(self, host, port='513', luser='root', user='', password=None, prompt fp, _ = self.bind(host, port, timeout=int(timeout)) trace = b'' + prompt_re = b(prompt_re) timeout = int(timeout) with Timing() as timing: @@ -4915,27 +4928,27 @@ def execute(self, data, data2='', delay='1'): ] dependencies = { - 'paramiko': [('ssh_login',), 'http://www.paramiko.org/', '1.7.7.1'], + 'paramiko': [('ssh_login',), 'http://www.paramiko.org/', '2.7.1'], 'pycurl': [('http_fuzz', 'rdp_gateway'), 'http://pycurl.io/', '7.43.0'], - 'libcurl': [('http_fuzz', 'rdp_gateway'), 'https://curl.haxx.se/', '7.21.0'], - 'ajpy': [('ajp_fuzz',), 'https://github.com/hypn0s/AJPy/', '0.0.1'], - 'openldap': [('ldap_login',), 'http://www.openldap.org/', '2.4.24'], - 'impacket': [('smb_login', 'smb_lookupsid', 'mssql_login'), 'https://github.com/CoreSecurity/impacket', '0.9.12'], - 'pyopenssl': [('mssql_login',), 'https://pyopenssl.org/', '17.5.0'], - 'cx_Oracle': [('oracle_login',), 'http://cx-oracle.sourceforge.net/', '5.1.1'], - 'mysqlclient': [('mysql_login',), 'https://github.com/PyMySQL/mysqlclient-python', '1.3.12'], + 'libcurl': [('http_fuzz', 'rdp_gateway'), 'https://curl.haxx.se/', '7.58.0'], + 'ajpy': [('ajp_fuzz',), 'https://github.com/hypn0s/AJPy/', '0.0.4'], + 'openldap': [('ldap_login',), 'http://www.openldap.org/', '2.4.45'], + 'impacket': [('smb_login', 'smb_lookupsid', 'mssql_login'), 'https://github.com/CoreSecurity/impacket', '0.9.20'], + 'pyopenssl': [('mssql_login',), 'https://pyopenssl.org/', '19.1.0'], + 'cx_Oracle': [('oracle_login',), 'http://cx-oracle.sourceforge.net/', '7.3.0'], + 'mysqlclient': [('mysql_login',), 'https://github.com/PyMySQL/mysqlclient-python', '1.4.6'], 'xfreerdp': [('rdp_login',), 'https://github.com/FreeRDP/FreeRDP.git', '1.2.0-beta1'], - 'psycopg': [('pgsql_login',), 'http://initd.org/psycopg/', '2.4.5'], + 'psycopg': [('pgsql_login',), 'http://initd.org/psycopg/', '2.8.4'], 'pycrypto': [('smb_login', 'smb_lookupsid', 'mssql_login', 'vnc_login',), 'http://www.dlitz.net/software/pycrypto/', '2.6.1'], - 'dnspython': [('dns_reverse', 'dns_forward'), 'http://www.dnspython.org/', '1.10.0'], - 'IPy': [('dns_reverse', 'dns_forward'), 'https://github.com/haypo/python-ipy', '0.75'], - 'pysnmp': [('snmp_login',), 'http://pysnmp.sf.net/', '4.2.1'], - 'pyasn1': [('smb_login', 'smb_lookupsid', 'mssql_login', 'snmp_login'), 'http://sourceforge.net/projects/pyasn1/', '0.1.2'], + 'dnspython': [('dns_reverse', 'dns_forward'), 'http://www.dnspython.org/', '1.16.0'], + 'IPy': [('dns_reverse', 'dns_forward'), 'https://github.com/haypo/python-ipy', '1.0'], + 'pysnmp': [('snmp_login',), 'http://pysnmp.sf.net/', '4.4.12'], + 'pyasn1': [('smb_login', 'smb_lookupsid', 'mssql_login', 'snmp_login'), 'http://sourceforge.net/projects/pyasn1/', '0.4.8'], 'ike-scan': [('ike_enum',), 'http://www.nta-monitor.com/tools-resources/security-tools/ike-scan', '1.9'], 'unzip': [('unzip_pass',), 'http://www.info-zip.org/', '6.0'], 'java': [('keystore_pass',), 'http://www.oracle.com/technetwork/java/javase/', '6'], 'pysqlcipher': [('sqlcipher_pass',), 'https://github.com/leapcode/pysqlcipher/', '2.6.10'], - 'python': [('ftp_login',), 'Patator requires Python 2.7 or above. Some features may be unavailable otherwise, such as TLS support for FTP.'], + 'python': [('ftp_login',), 'Patator requires Python 3.6 or above and may still work on Python 2.'], } # }}} From 704dab811ff5020a82e68622ef446f875531148b Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sun, 22 Mar 2020 12:34:42 +1000 Subject: [PATCH 20/74] Release v0.8 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 03bdf17..5a94067 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def parse_requirements(file): setup( name="patator", - version="0.7", + version="0.8", description="multi-purpose brute-forcer", long_description=long_description, url="https://github.com/lanjelot/patator", From 426c2574e56a1ff9764672f85c494e8639f578b6 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Mon, 23 Mar 2020 15:51:34 +1000 Subject: [PATCH 21/74] Fixes #128 --- patator.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/patator.py b/patator.py index 78d7691..aaf4bf8 100755 --- a/patator.py +++ b/patator.py @@ -927,6 +927,7 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm import glob from xml.sax.saxutils import escape as xmlescape, quoteattr as xmlquoteattr from ssl import wrap_socket +from binascii import hexlify, unhexlify try: # python3+ from queue import Empty, Full @@ -1305,8 +1306,8 @@ class Controller: ) available_encodings = { - 'hex': (lambda s: s.encode('hex'), 'encode in hexadecimal'), - 'unhex': (lambda s: s.decode('hex'), 'decode from hexadecimal'), + 'hex': (lambda s: B(hexlify(s)), 'encode in hexadecimal'), + 'unhex': (lambda s: B(unhexlify(s)), 'decode from hexadecimal'), 'b64': (b64encode, 'encode in base64'), 'md5': (md5hex, 'hash in md5'), 'sha1': (sha1hex, 'hash in sha1'), @@ -4844,13 +4845,13 @@ def execute(self, host, port, data='', timeout='2', ssl='0'): fp = socket.create_connection((host, port), int(timeout)) if ssl != '0': fp = wrap_socket(fp) - fp.send(data.decode('hex')) + fp.send(unhexlify(data)) with Timing() as timing: resp = fp.recv(1024) fp.close() code = 0 - mesg = resp.encode('hex') + mesg = B(hexlify(resp)) return self.Response(code, mesg, timing) From 13033508937d66fe714969042a4fd9863843f2e5 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Thu, 26 Mar 2020 15:54:45 +1000 Subject: [PATCH 22/74] New --groups option (fixes #124) --- README.md | 2 +- patator.py | 204 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 146 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index dd253e1..b4d8b97 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Currently it supports the following modules: * umbraco_crack : Crack Umbraco HMAC-SHA1 password hashes ``` -The name "Patator" comes from [this](https://www.youtube.com/watch?v=kU2yPJJdpag). +The name "Patator" comes from [this](https://www.youtube.com/watch?v=9sF9fTALhVA). Patator is NOT script-kiddie friendly, please read the full README inside [patator.py](patator.py) before reporting. diff --git a/patator.py b/patator.py index aaf4bf8..85ebef3 100755 --- a/patator.py +++ b/patator.py @@ -18,7 +18,7 @@ __url__ = 'http://www.hsc.fr/ressources/outils/patator/' __git__ = 'https://github.com/lanjelot/patator' __twitter__ = 'https://twitter.com/lanjelot' -__version__ = '0.8' +__version__ = '0.8-dev' __license__ = 'GPLv2' __pyver__ = '%d.%d.%d' % sys.version_info[0:3] __banner__ = 'Patator %s (%s) with python-%s' % (__version__, __git__, __pyver__) @@ -76,7 +76,7 @@ Future modules to be implemented: - rdp_login w/no NLA -The name "Patator" comes from http://www.youtube.com/watch?v=xoBkBvnTTjo +The name "Patator" comes from https://www.youtube.com/watch?v=9sF9fTALhVA * Why ? @@ -226,6 +226,20 @@ 10.0.0.1 admin 123456 ... +By default Patator iterates over the cartesian product of all payload sets. Use +the --groups option to iterate over sets simultaneously instead. For example to +distribute all payloads among identical servers: +--------- +$ ./module name=FILE0.FILE1 resolver=FILE2 0=names.txt 1=domains.txt 2=ips.txt --groups 0,1:2 +ftp.abc.fr 8.8.8.8 +ftp.xyz.fr 8.8.4.4 +git.abc.fr 8.8.8.8 +git.xyz.fr 8.8.4.4 +www.abc.fr 8.8.8.8 +www.xyz.fr 8.8.4.4 + +The numbers of every keyword given on the command line must be specified. +Use ',' to iterate over the cartesian product of sets and use ':' to iterate over sets simultaneously. * Keywords @@ -909,8 +923,9 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm from time import localtime, gmtime, strftime, sleep, time from platform import system from functools import reduce +from operator import mul, itemgetter from select import select -from itertools import islice +from itertools import islice, cycle import string import random from decimal import Decimal @@ -928,6 +943,7 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm from xml.sax.saxutils import escape as xmlescape, quoteattr as xmlquoteattr from ssl import wrap_socket from binascii import hexlify, unhexlify +import mmap try: # python3+ from queue import Empty, Full @@ -1091,22 +1107,33 @@ def html_unescape(s): h = HTMLParser() return h.unescape(s) +def mapcount(filename): + lines = 0 + with open(filename, 'r+') as f: + buf = mmap.mmap(f.fileno(), 0) + readline = buf.readline + while readline(): + lines += 1 + return lines + # I rewrote itertools.product to avoid memory over-consumption when using large wordlists def product(xs, *rest): if len(rest) == 0: - for x in xs(): + for x in xs: yield [x] else: - for head in xs(): + for head in xs: for tail in product(*rest): yield [head] + tail -def chain(*iterables): - def xs(): - for iterable in iterables: +class chain: + def __init__(self, *iterables): + self.iterables = iterables + + def __iter__(self): + for iterable in self.iterables: for element in iterable: yield element - return xs class FileIter: def __init__(self, filename): @@ -1241,7 +1268,6 @@ def __len__(self): return self.size class ProgIter: - def __init__(self, prog): self.prog = prog @@ -1293,6 +1319,21 @@ def _run_server(cls, registry, address, authkey, serializer, writer, initializer ignore_ctrlc() super(MyManager, cls)._run_server(registry, address, authkey, serializer, writer) +def ppstr(s): + if isinstance(s, bytes): + s = B(s) + if not isinstance(s, str): + s = str(s) + return s.rstrip('\r\n') + +def flatten(l): + r = [] + for x in l: + if isinstance(x, (list, tuple)): + r.extend(map(ppstr, x)) + else: + r.append(ppstr(x)) + return r # }}} # Controller {{{ @@ -1407,19 +1448,20 @@ def format_usage(self, usage): exe_grp = OptionGroup(parser, 'Execution') exe_grp.add_option('-x', dest='actions', action='append', default=[], metavar='arg', help='actions and conditions, see Syntax below') - exe_grp.add_option('--start', dest='start', type='int', default=0, metavar='N', help='start from offset N in the wordlist product') + exe_grp.add_option('--start', dest='start', type='int', default=0, metavar='N', help='start from offset N in the product of all payload sets') exe_grp.add_option('--stop', dest='stop', type='int', default=None, metavar='N', help='stop at offset N') exe_grp.add_option('--resume', dest='resume', metavar='r1[,rN]*', help='resume previous run') exe_grp.add_option('-e', dest='encodings', action='append', default=[], metavar='arg', help='encode everything between two tags, see Syntax below') exe_grp.add_option('-C', dest='combo_delim', default=':', metavar='str', help="delimiter string in combo files (default is ':')") exe_grp.add_option('-X', dest='condition_delim', default=',', metavar='str', help="delimiter string in conditions (default is ',')") - exe_grp.add_option('--allow-ignore-failures', action='store_true', default=False, dest='allow_ignore_failures', help="failures cannot be ignored with -x (this is by design to avoid false negatives) this option overrides this behavior") + exe_grp.add_option('--allow-ignore-failures', action='store_true', default=False, dest='allow_ignore_failures', help="failures cannot be ignored with -x (this is by design to avoid false negatives) this option overrides this safeguard") opt_grp = OptionGroup(parser, 'Optimization') - opt_grp.add_option('--rate-limit', dest='rate_limit', type='float', default=0, metavar='N', help='wait N seconds between each test (default is 0)') + opt_grp.add_option('--rate-limit', dest='rate_limit', type='float', default=0, metavar='N', help='wait N seconds between each attempt (default is 0)') opt_grp.add_option('--timeout', dest='timeout', type='int', default=0, metavar='N', help='wait N seconds for a response before retrying payload (default is 0)') opt_grp.add_option('--max-retries', dest='max_retries', type='int', default=4, metavar='N', help='skip payload after N retries (default is 4) (-1 for unlimited)') opt_grp.add_option('-t', '--threads', dest='num_threads', type='int', default=10, metavar='N', help='number of threads (default is 10)') + opt_grp.add_option('--groups', dest='groups', default='', metavar='', help="default is to iterate over the cartesian product of all payload sets, use this option to iterate over sets simultaneously instead (aka pitchfork), see syntax inside (default is '0,1..n')") log_grp = OptionGroup(parser, 'Logging') log_grp.add_option('-l', dest='log_dir', metavar='DIR', help="save output and response data into DIR ") @@ -1453,6 +1495,7 @@ def __init__(self, module, argv): self.payload = {} self.iter_keys = {} + self.iter_groups = {} self.enc_keys = [] self.module = module @@ -1514,7 +1557,8 @@ def __init__(self, module, argv): kargs.append((k, v)) iter_vals = [v for k, v in sorted(wlists.items())] - logger.debug('kargs: %s' % kargs) # [('host', 'NET0'), ('user', 'COMBO10'), ('password', 'COMBO11'), ('domain', 'MOD2')] + + logger.debug('kargs: %s' % kargs) # [('host', 'NET0'), ('user', 'COMBO10'), ('password', 'COMBO11'), ('name', 'google.MOD2')] logger.debug('iter_vals: %s' % iter_vals) # ['10.0.0.0/24', 'combos.txt', 'TLD'] for k, v in kargs: @@ -1567,10 +1611,26 @@ def __init__(self, module, argv): else: self.payload[k] = v - logger.debug('iter_keys: %s' % self.iter_keys) # { 0: ('NET', '10.0.0.0/24', ['host']), 1: ('COMBO', 'combos.txt', [(0, 'user'), (1, 'password')]), 2: ('MOD', 'TLD', ['name']) + if self.iter_keys: + if not opts.groups: + # default is to iterate over the cartesian product of all payload sets + opts.groups = ','.join(map(str, self.iter_keys)) + + for i, g in enumerate(opts.groups.split(':')): + ks = list(map(int, g.split(','))) + for k in ks: + if k not in self.iter_keys: + raise ValueError('Unknown keyword number %r' % k) + + self.iter_groups[i] = sorted(ks) + + logger.debug('iter_groups: %s' % self.iter_groups) # {0: [0, 1], 1: [2]} + logger.debug('iter_keys: %s' % self.iter_keys) # [(0, ('NET', '10.0.0.0/24', ['host'])), (1, ('COMBO', 'combos.txt', [(0, 'user'), (1, 'password')])), (2, ('MOD', 'TLD', ['name']))] logger.debug('enc_keys: %s' % self.enc_keys) # [('password', 'ENC', hex), ('header', 'B64', b64encode), ... - logger.debug('payload: %s' % self.payload) + logger.debug('payload: %s' % self.payload) # {'host': 'NET0', 'user': 'COMBO10', 'password': 'COMBO11', 'name': 'google.MOD2'} + self.iter_groups = sorted(self.iter_groups.items()) + self.iter_keys = sorted(self.iter_keys.items()) self.available_actions = [k for k, _ in self.builtin_actions + self.module.available_actions] self.module_actions = [k for k, _ in self.module.available_actions] @@ -1722,58 +1782,44 @@ def produce(self, task_queues, log_queue): global logger logger = Logger(log_queue) - iterables = [] - total_size = 1 - def abort(msg): logger.warn(msg) self.ns.quit_now = True - for _, (t, v, _) in self.iter_keys.items(): + psets = {} + for k, (t, v, _) in self.iter_keys: - if t in ('FILE', 'COMBO'): - size = 0 - files = [] + pset= [] + size = 0 + if t in ('FILE', 'COMBO'): for name in v.split(','): for fpath in sorted(glob.iglob(expand_path(name))): if not os.path.isfile(fpath): return abort("No such file '%s'" % fpath) - with open(fpath) as f: - for _ in f: - size += 1 - - files.append(FileIter(fpath)) - - iterable = chain(*files) + pset.append(FileIter(fpath)) + size += mapcount(fpath) elif t == 'NET': - subnets = [IP(n, make_net=True) for n in v.split(',')] - size = sum(len(s) for s in subnets) - iterable = chain(*subnets) + pset = [IP(n, make_net=True) for n in v.split(',')] + size = sum(len(subnet) for subnet in pset) elif t == 'MOD': elements, size = self.module.available_keys[v]() - iterable = chain(elements) + pset = [elements] elif t == 'RANGE': - size = 0 - ranges = [] - for r in v.split(','): typ, opt = r.split(':', 1) try: - it = RangeIter(typ, opt) - size += len(it) + ri = RangeIter(typ, opt) + size += len(ri) + pset.append(ri) except ValueError as e: return abort("Invalid range '%s' of type '%s', %s" % (opt, typ, e)) - ranges.append(it) - - iterable = chain(*ranges) - elif t == 'PROG': m = re.match(r'(.+),(\d+)$', v) if m: @@ -1783,19 +1829,48 @@ def abort(msg): logger.debug('prog: %s, size: %s' % (prog, size)) - it = ProgIter(prog) - iterable, size = chain(it), int(size) + pset = [ProgIter(prog)] + size = int(size) else: return abort('Incorrect keyword %r' % t) - total_size *= size - iterables.append(iterable) + psets[k] = chain(*pset), size + + logger.debug('payload sets: %r' % psets) + + zipit = [] + if not psets: + total_size = 1 + zipit.append(['']) + + else: + group_sizes = {} + for i, ks in self.iter_groups: + group_sizes[i] = reduce(mul, (size for _, size in [psets[k] for k in ks])) + + logger.debug('group_sizes: %s' % group_sizes) + + total_size = max(group_sizes.values()) + biggest, _ = max(group_sizes.items(), key=itemgetter(1)) + + for i, ks in self.iter_groups: + r = [] + + for k in ks: + pset, _ = psets[k] + r.append(pset) - if not iterables: - iterables.append(chain([''])) + it = product(*r) + if i != biggest: + it = cycle(it) - if self.stop: + zipit.append(it) + + logger.debug('zipit: %s' % zipit) + logger.debug('total_size: %d' % total_size) + + if self.stop and total_size > self.stop: total_size = self.stop - self.start else: total_size -= self.start @@ -1809,13 +1884,18 @@ def abort(msg): logger.headers() count = 0 - for pp in islice(product(*iterables), self.start, self.stop): + for pp in islice(zip(*zipit), self.start, self.stop): if self.ns.quit_now: break - cid = count % self.num_threads - prod = [str(p).rstrip('\r\n') for p in pp] + pp = flatten(pp) + logger.debug('pp: %s' % pp) + + prod = [''] * len(pp) + for _, ks in self.iter_groups: + for k in ks: + prod[k] = pp.pop(0) if self.resume: idx = count % len(self.resume) @@ -1831,6 +1911,7 @@ def abort(msg): break try: + cid = count % self.num_threads task_queues[cid].put_nowait(prod) break except Full: @@ -1883,7 +1964,7 @@ def shutdown(): payload = self.payload.copy() - for i, (t, _, keys) in self.iter_keys.items(): + for i, (t, _, keys) in self.iter_keys: if t == 'FILE': for k in keys: payload[k] = payload[k].replace('FILE%d' % i, prod[i]) @@ -1907,10 +1988,10 @@ def shutdown(): payload[k] = re.sub(r'{0}(.+?){0}'.format(m), lambda m: e(b(m.group(1))), payload[k]) logger.debug('product: %s' % prod) - pp_prod = ':'.join(prod) + prod_str = ':'.join(prod) if self.check_free(payload): - report_queue.put(('skip', pp_prod, None, 0)) + report_queue.put(('skip', prod_str, None, 0)) continue try_count = 0 @@ -1959,7 +2040,7 @@ def shutdown(): actions = {'fail': None} actions.update(self.lookup_actions(resp)) - report_queue.put((actions, pp_prod, resp, time() - start_time)) + report_queue.put((actions, prod_str, resp, time() - start_time)) for name in self.module_actions: if name in actions: @@ -2039,7 +2120,6 @@ def report_progress(self): p.done_count += 1 - def monitor_interaction(self): if on_windows(): @@ -2127,7 +2207,6 @@ def monitor_interaction(self): total_count, total_size/num_threads, p.current)) - # }}} # Response_Base {{{ @@ -4858,6 +4937,9 @@ def execute(self, host, port, data='', timeout='2', ssl='0'): # }}} # Dummy Test {{{ +def generate_tst(): + return ['prd', 'dev'], 2 + class Dummy_test: '''Testing module''' @@ -4875,6 +4957,10 @@ class Dummy_test: ) available_actions = () + available_keys = { + 'TST': generate_tst, + } + Response = Response_Base def execute(self, data, data2='', delay='1'): From 2b78b342fc8d82cc0d6ff7e51ff30c97c0bd69db Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sat, 28 Mar 2020 07:14:07 +1000 Subject: [PATCH 23/74] Fixes #53 somewhat --- patator.py | 70 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/patator.py b/patator.py index 85ebef3..8524d99 100755 --- a/patator.py +++ b/patator.py @@ -24,7 +24,6 @@ __banner__ = 'Patator %s (%s) with python-%s' % (__version__, __git__, __pyver__) # README {{{ - ''' INTRODUCTION ------------ @@ -849,7 +848,7 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm f.write('\n') f.write('\n') - else: # remove "..." + else: # remove "..." with open(results_xml, 'r+b') as f: offset = f.read().find(b'') if offset != -1: @@ -911,7 +910,7 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm elif action == 'setLevel': logger.setLevel(args[0]) - else: # 'warn', 'info', 'debug' + else: # 'warn', 'info', 'debug' getattr(logger, action)(args[0], extra={'pname': pname}) # }}} @@ -931,7 +930,6 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm from decimal import Decimal from base64 import b64encode from datetime import timedelta, datetime -from struct import unpack import socket import subprocess import hashlib @@ -959,14 +957,16 @@ def process_logs(queue, indicatorsfmt, argv, log_dir, runtime_file, csv_file, xm from sys import maxint PY3 = sys.version_info >= (3,) -if PY3: # http://python3porting.com/problems.html +if PY3: # http://python3porting.com/problems.html def b(x): return x.encode('ISO-8859-1', errors='ignore') + def B(x): return x.decode(errors='ignore') else: def b(x): return x + def B(x): return x @@ -1017,7 +1017,8 @@ def __init__(self, *args, **kw): forking.Popen = _Popen from multiprocessing.managers import SyncManager -# imports }}} + +# }}} # utils {{{ def expand_path(s): @@ -1034,7 +1035,7 @@ def is_exe(fpath): return os.path.exists(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) - if on_windows() and fname[-4:] != '.exe' : + if on_windows() and fname[-4:] != '.exe': fname += '.exe' if fpath: @@ -1090,7 +1091,7 @@ def create_time_dir(top_path, desc): return time_path def pprint_seconds(seconds, fmt): - return fmt % reduce(lambda x,y: divmod(x[0], y) + x[1:], [(seconds,),60,60]) + return fmt % reduce(lambda x, y: divmod(x[0], y) + x[1:], [(seconds,), 60, 60]) def md5hex(plain): return hashlib.md5(plain).hexdigest() @@ -1434,7 +1435,7 @@ def format_usage(self, usage): encoding := "%s" %s''' % ('" | "'.join(k for k in self.available_encodings), - '\n'.join(' %-12s: %s' % (k, v) for k, (f, v) in self.available_encodings.items())) + '\n'.join(' %-12s: %s' % (k, v) for k, (f, v) in self.available_encodings.items())) epilog += ''' @@ -1789,7 +1790,7 @@ def abort(msg): psets = {} for k, (t, v, _) in self.iter_keys: - pset= [] + pset = [] size = 0 if t in ('FILE', 'COMBO'): @@ -1855,8 +1856,8 @@ def abort(msg): biggest, _ = max(group_sizes.items(), key=itemgetter(1)) for i, ks in self.iter_groups: - r = [] + r = [] for k in ks: pset, _ = psets[k] r.append(pset) @@ -1976,13 +1977,13 @@ def shutdown(): payload[k] = payload[k].replace('COMBO%d%d' % (i, j), prod[i].split(self.combo_delim)[j]) elif t == 'MOD': for k in keys: - payload[k] = payload[k].replace('MOD%d' %i, prod[i]) + payload[k] = payload[k].replace('MOD%d' % i, prod[i]) elif t == 'RANGE': for k in keys: - payload[k] = payload[k].replace('RANGE%d' %i, prod[i]) + payload[k] = payload[k].replace('RANGE%d' % i, prod[i]) elif t == 'PROG': for k in keys: - payload[k] = payload[k].replace('PROG%d' %i, prod[i]) + payload[k] = payload[k].replace('PROG%d' % i, prod[i]) for k, m, e in self.enc_keys: payload[k] = re.sub(r'{0}(.+?){0}'.format(m), lambda m: e(b(m.group(1))), payload[k]) @@ -2134,7 +2135,8 @@ def monitor_interaction(self): else: i, _, _ = select([sys.stdin], [], [], .1) - if not i: return + if not i: + return command = i[0].readline().strip() if command == 'h': @@ -2501,7 +2503,7 @@ def execute(self, host, port='22', user=None, password=None, auth_type='password fp.auth_password(user, password, fallback=False) elif auth_type == 'keyboard-interactive': - fp.auth_interactive(user, lambda a,b,c: [password] if len(c) == 1 else []) + fp.auth_interactive(user, lambda a, b, c: [password] if len(c) == 1 else []) elif auth_type == 'auto': fp.auth_password(user, password, fallback=True) @@ -3018,10 +3020,12 @@ class POP_login(TCP_Cache): def connect(self, host, port, ssl, timeout): if ssl == '0': - if not port: port = 110 + if not port: + port = 110 fp = POP3(host, int(port), timeout=int(timeout)) else: - if not port: port = 995 + if not port: + port = 995 fp = POP3_SSL(host, int(port)) # timeout=int(timeout)) # no timeout option in python2 return TCP_Connection(fp, fp.welcome) @@ -3128,10 +3132,12 @@ class IMAP_login: def execute(self, host, port='', ssl='0', user=None, password=None): if ssl == '0': - if not port: port = 143 + if not port: + port = 143 klass = IMAP4 else: - if not port: port = 993 + if not port: + port = 993 klass = IMAP4_SSL with Timing() as timing: @@ -3652,7 +3658,7 @@ def expand_key(self, arg): if r.path.startswith('http'): opts['url'] = r.path else: - _, _, opts['path'], opts['params'], opts['query'], opts['fragment'] = urlparse(r.path) + _, _, opts['path'], opts['params'], opts['query'], opts['fragment'] = urlparse(r.path) opts['host'] = r.headers['Host'] opts['header'] = str(r.headers) @@ -4160,7 +4166,6 @@ def login(self, password): else: raise VNC_Error('Unknown response: %r (code: %s)' % (resp, code)) - def gen_key(self, key): newkey = [] for ki in range(len(key)): @@ -4312,7 +4317,8 @@ def distro(): continue for line in open(f): match = re.match(r'([a-zA-Z0-9]+)\s', line) - if not match: continue + if not match: + continue for w in re.split(r'[^a-z0-9]', match.group(1).strip().lower()): ret.extend(['_%s.%s' % (w, i) for i in ('_tcp', '_udp')]) return ret @@ -4331,10 +4337,12 @@ def __str__(self): if self.name: line = ' '.join(self.name) if self.ip: - if line: line += ' / ' + if line: + line += ' / ' line += ' '.join(map(str, self.ip)) if self.alias: - if line: line += ' / ' + if line: + line += ' / ' line += ' '.join(self.alias) return line @@ -4434,10 +4442,11 @@ def show_final(self): else: i = 0 d = '.'.join(name.split('.')[i:]) - if d not in domains: domains[d] = 0 + if d not in domains: + domains[d] = 0 domains[d] += 1 - for domain, count in sorted(domains.items(), key=lambda a:a[0].split('.')[-1::-1]): + for domain, count in sorted(domains.items(), key=lambda a: a[0].split('.')[-1::-1]): print('%34s %d' % (domain, count)) print('Networks ' + '-'*41) @@ -4447,7 +4456,8 @@ def show_final(self): nets[ip] = [ip] else: n = ip.make_net('255.255.255.0') - if n not in nets: nets[n] = [] + if n not in nets: + nets[n] = [] nets[n].append(ip) for net, ips in sorted(nets.items()): @@ -4615,7 +4625,7 @@ def execute(self, host, port=None, version='2', community='public', user='myuser errorIndication, errorStatus, errorIndex, varBinds = cmdgen.CommandGenerator().getCmd( security_model, cmdgen.UdpTransportTarget((host, int(port or 161)), timeout=int(timeout), retries=int(retries)), - (1,3,6,1,2,1,1,1,0) + (1, 3, 6, 1, 2, 1, 1, 1, 0) ) code = '%d-%d' % (errorStatus, errorIndex) @@ -4648,7 +4658,7 @@ def execute(self, host, port=None, version='2', community='public', user='myuser def generate_transforms(): lists = list(map(lambda l: [i[0] for i in l], [IKE_ENC, IKE_HASH, IKE_AUTH, IKE_GROUP])) - return map(lambda p: ','.join(p), product(*[chain(l) for l in lists])), reduce(lambda x,y: x*y, map(len, lists)) + return map(lambda p: ','.join(p), product(*[chain(l) for l in lists])), reduce(lambda x, y: x*y, map(len, lists)) class Controller_IKE(Controller): From b25e85d0be236ee11c2f314a43e8d83c9d00b868 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Sat, 28 Mar 2020 08:12:13 +1000 Subject: [PATCH 24/74] Fix file line count function --- patator.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/patator.py b/patator.py index 8524d99..e6cc624 100755 --- a/patator.py +++ b/patator.py @@ -1108,14 +1108,18 @@ def html_unescape(s): h = HTMLParser() return h.unescape(s) -def mapcount(filename): - lines = 0 - with open(filename, 'r+') as f: - buf = mmap.mmap(f.fileno(), 0) - readline = buf.readline - while readline(): - lines += 1 - return lines +def count_lines(filename): + with open(filename) as f: + lines = 0 + buf_size = 1024 * 1024 + read_f = f.read + + buf = read_f(buf_size) + while buf: + lines += buf.count('\n') + buf = read_f(buf_size) + + return lines # I rewrote itertools.product to avoid memory over-consumption when using large wordlists def product(xs, *rest): @@ -1800,7 +1804,7 @@ def abort(msg): return abort("No such file '%s'" % fpath) pset.append(FileIter(fpath)) - size += mapcount(fpath) + size += count_lines(fpath) elif t == 'NET': pset = [IP(n, make_net=True) for n in v.split(',')] From fd84a310f1c5b06fdd8fc6517cecc3bd9fa5c68d Mon Sep 17 00:00:00 2001 From: lanjelot Date: Tue, 7 Apr 2020 15:18:06 +1000 Subject: [PATCH 25/74] Fixes #74 --- patator.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/patator.py b/patator.py index e6cc624..b27fc55 100755 --- a/patator.py +++ b/patator.py @@ -238,7 +238,12 @@ www.xyz.fr 8.8.4.4 The numbers of every keyword given on the command line must be specified. -Use ',' to iterate over the cartesian product of sets and use ':' to iterate over sets simultaneously. +Use ',' to iterate over the cartesian product of sets and use ':' to iterate +over sets simultaneously. + +If the value of a module option starts with the letter @, the rest should be a +filename. The contents of the file will be loaded into the option: +./module raw_request=@req.txt 0=vhosts.txt 1=uagents * Keywords From 13d7bf79812661e9ed42fe4696353615575beecf Mon Sep 17 00:00:00 2001 From: lanjelot Date: Tue, 7 Apr 2020 15:21:45 +1000 Subject: [PATCH 26/74] Fixes #74 typo --- patator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patator.py b/patator.py index b27fc55..9191ce5 100755 --- a/patator.py +++ b/patator.py @@ -243,7 +243,7 @@ If the value of a module option starts with the letter @, the rest should be a filename. The contents of the file will be loaded into the option: -./module raw_request=@req.txt 0=vhosts.txt 1=uagents +./module raw_request=@req.txt 0=vhosts.txt 1=uagents.txt * Keywords From b6316de22aa54646dae9e1611ae175ab0fdbe35a Mon Sep 17 00:00:00 2001 From: lanjelot Date: Wed, 29 Apr 2020 12:18:14 +1000 Subject: [PATCH 27/74] Fixes #132 --- README.md | 10 +++++----- Vagrantfile | 9 +++++---- patator.py | 18 ++++++------------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index b4d8b97..9f37fdb 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Many thanks! [@lanjelot](https://twitter.com/lanjelot) ## Usage Examples -* FTP : Enumerating users denied login in vsftpd/userlist +* FTP : Enumerating users denied login in `vsftpd/userlist` ``` $ ftp_login host=10.0.0.1 user=FILE0 0=logins.txt password=asdf -x ignore:mesg='Login incorrect.' -x ignore,reset,retry:code=500 @@ -68,7 +68,7 @@ $ ftp_login host=10.0.0.1 user=FILE0 0=logins.txt password=asdf -x ignore:mesg=' ... ``` -Tested against `vsftpd-3.0.2-9` on `CentOS 7.0-1406` +Tested against `vsftpd-3.0.2-9` on `CentOS 7.0-1406`. * SSH : Time-based user enumeration @@ -84,7 +84,7 @@ $ ssh_login host=10.0.0.1 user=FILE0 0=logins.txt password=$(perl -e "print 'A'x ... ``` -Tested against openssh-server 1:6.0p1-4+deb7u2 on Debian 7.8 +Tested against `openssh-server 1:6.0p1-4+deb7u2` on `Debian 7.8`. * HTTP : Brute-force phpMyAdmin logon @@ -108,7 +108,7 @@ $ grep AllowNoPassword /tmp/qsdf/72_200\:13215\:0\:0.351.txt ... class="icon ic_s_error" /> Login without a password is forbidden by configuration (see AllowNoPassword)