diff --git a/httpgrep.py b/httpgrep.py index 81025ba..72891d6 100755 --- a/httpgrep.py +++ b/httpgrep.py @@ -31,7 +31,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed __author__ = 'noptrix' -__version__ = '2.1' +__version__ = '2.2' __copyright__ = 'santa clause' __license__ = 'MIT' @@ -63,30 +63,31 @@ HELP = BOLD + '''usage''' + NORM + ''' ''' + BOLD + '''opts''' + NORM + ''' - -h - single host or host-range/cidr-range or file containing + -h - single host/url or host-/cidr-range or file containing hosts or file containing URLs, e.g.: foobar.net, - 192.168.0.1-192.168.0.254, - 192.168.0.0/24, /tmp/hosts.txt + 192.168.0.1-192.168.0.254, 192.168.0.0/24, /tmp/hosts.txt NOTE: hosts can also contain ':' on cmdline or in file. -p - port to connect to (default: 80 if hosts were given) -t - use TLS/SSL to connect to service -u - URI to search given strings in, e.g.: /foobar/, /foo.html - (default /) - -s - a single string or multile strings in a file to find in - given URIs and HTTP response headers, e.g.: 'tomcat 8', - '/tmp/igot0daysforthese.txt' + (default: /) + -s - a single string/regex or multile strings/regex in a file + to find in given URIs and HTTP response headers, + e.g.: 'tomcat 8', '/tmp/igot0daysforthese.txt' + -S - search strings in given places (default: headers,body) -X - specify HTTP request method to use (default: get). use '?' to list available methods. - -U - set custom user-agent (default: firefox, rv84, windows) - -S - search strings in given places (default: headers,body) + -a - http auth credentials (format: 'user:pass') + -U - set custom User-Agent (default: firefox, rv84, windows) -b - num bytes to read from response. offset == response[0]. (default: 64) - -x - num threads for concurrent checks (default: 80) + -x - num threads for concurrent scans and checks (default: 80) -c - num seconds for socket timeout (default: 3.0) -i - use case-insensitive search -r - perform reverse dns lookup for given IPv4 addresses - -l - log urls and found strings to file + NOTE: this will slow down the scanz + -l - log found matches to file -v - verbose mode (default: quiet) ''' + BOLD + '''misc''' + NORM + ''' @@ -102,9 +103,10 @@ opts = { 'ssl': False, 'uri': '/', 'searchstr': '', - 'method': 'get', - 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0', 'where': ('headers', 'body'), + 'method': 'get', + 'auth': False, + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0', 'bytes': 64, 'threads': 80, 'timeout': 3.0, @@ -174,14 +176,15 @@ def get_strings(strings): def http_req(url): m = getattr(requests, opts['method']) r = m(url, timeout=opts['timeout'], headers={'User-Agent': opts['ua']}, - verify=False) + verify=False, auth=opts['auth']) return r def scan(url): if opts['verbose']: - log(f'scanning {url}', 'verbose') + log(f'scanning {url}' + ' ' * 20, 'verbose', esc='\r') + sys.stdout.flush() r = http_req(url) @@ -189,17 +192,21 @@ def scan(url): if re.search(opts['searchstr'], r.text, opts['case_in']): idx = r.text.index(opts['searchstr']) res = repr(r.text[idx:idx+opts['bytes']]) - log(f'{url} => body => {res}', 'good') + log(f'{url} | body | {res}', 'good') if opts['logfile']: - log(f'{url} => body => {res}', 'file') + log(f'{url} | body | {res}', 'file') if 'headers' in opts['where']: for k, v in r.headers.items(): if re.search(opts['searchstr'], k, opts['case_in']) or \ re.search(opts['searchstr'], v, opts['case_in']): - log(f"{url} => header => {k}: {v}", 'good') + log(f"{url} | header | {k}: {v}", 'good') if opts['logfile']: - log(f"{url} => header => {k}: {v}", 'file') + log(f"{url} | header | {k}: {v}", 'file') + + if opts['verbose']: + sys.stdout.flush() + log('\n') return @@ -214,9 +221,6 @@ def build_url(host): url = f'{scheme}://{host}:{opts["port"]}{opts["uri"]}' - if opts['port'] == '80' or opts['port'] == '443': - url = f'{scheme}://{host}{opts["uri"]}' - return url @@ -233,11 +237,11 @@ def get_hosts(hosts): for i in range(int(start), int(end) + 1): ipaddr = str(ipaddress.IPv4Address(i)) yield rptr(str(ipaddr)) - elif '/' in hosts: + elif '/' in hosts and 'http' not in hosts: for ipaddr in ipaddress.IPv4Network(hosts).hosts(): yield rptr(str(ipaddr)) else: - yield hosts #single host + yield hosts # single host or url except Exception as err: log(err.args[0].lower(), 'error') @@ -265,6 +269,14 @@ def check_http_method(): return +def check_auth(): + if opts['auth']: + if len(opts['auth']) != 2: + log(f'wrong user:pass supplied', 'error') + + return + + def check_argv(cmdline): needed = ['-h', '-s', '-V', '-H'] @@ -279,7 +291,7 @@ def parse_cmdline(cmdline): global opts try: - _opts, _args = getopt.getopt(sys.argv[1:], 'h:p:tu:s:X:U:S:b:x:c:irl:vVH') + _opts, _args = getopt.getopt(sys.argv[1:], 'h:p:tu:s:S:X:a:U:b:x:c:irl:vVH') for o, a in _opts: if o == '-h': opts['hosts'] = a @@ -291,12 +303,14 @@ def parse_cmdline(cmdline): opts['uri'] = a if o == '-s': opts['searchstr'] = a - if o == '-X': - opts['method'] = a - if o == '-U': - opts['ua'] = a if o == '-S': opts['where'] = a.split(',') + if o == '-X': + opts['method'] = a + if o == '-a': + opts['auth'] = tuple(a.split(':', 1)) + if o == '-U': + opts['ua'] = a if o == '-b': opts['bytes'] = int(a) if o == '-x': @@ -337,10 +351,13 @@ def main(cmdline): check_argv(cmdline) check_http_method() check_search_place() + check_auth() with ThreadPoolExecutor(opts['threads']) as exe: log('w00t w00t, game started', 'info') log('wait bitch, scanning', 'info') + if opts['verbose']: + log('\n') for host in get_hosts(opts['hosts']): url = host if 'http' not in host: @@ -348,6 +365,8 @@ def main(cmdline): for string in get_strings(opts['searchstr']): exe.submit(scan, url) + if opts['verbose']: + log('\n\n') log('n00b n00b, game over', 'info') return @@ -358,6 +377,7 @@ if __name__ == '__main__': try: main(sys.argv[1:]) except KeyboardInterrupt: + log('\n') log('you aborted me', 'warn') os._exit(SUCCESS)