diff --git a/README.md b/README.md index a21c6ad..e8410ea 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,27 @@ httpscan是一个扫描指定CIDR网段的Web主机的小工具。和端口扫描器不一样,httpscan是以爬虫的方式进行Web主机发现,因此相对来说不容易被防火墙拦截。 httpscan会返回IP http状态码 Web容器版本 以及网站标题。 + ![demo][1] -**Usage**:`./httpscan IP/CIDR –t threads` +Usage: +``` +./httpscan.py IP/CIDR –t threads +``` + +Example: +``` +./httpscan.py 10.20.30.0/24 –t 10 +``` + +Load hosts from file: +``` +./httpscan.py hosts.txt -t 10 +``` -Example:`./httpscan.py 10.20.30.0/24 –t 10` +### Changelog +- 本地文件载入目标主机支持 +- 扫描进度状态 [1]: https://raw.githubusercontent.com/zer0h/httpscan/master/log/demo.png diff --git a/httpscan.py b/httpscan.py index d0276a6..d6f57dd 100644 --- a/httpscan.py +++ b/httpscan.py @@ -5,10 +5,14 @@ import re import sys import Queue -import threading +import codecs +import logging import optparse import requests +import threading from IPy import IP +from os.path import basename +# from urllib3.exceptions import ConnectTimeoutError printLock = threading.Semaphore(1) #lock Screen print TimeOut = 5 #request timeout @@ -18,63 +22,128 @@ class scan(): - def __init__(self,cidr,threads_num): - self.threads_num = threads_num - self.cidr = IP(cidr) - #build ip queue - self.IPs = Queue.Queue() - for ip in self.cidr: - ip = str(ip) - self.IPs.put(ip) - - def request(self): - with threading.Lock(): - while self.IPs.qsize() > 0: - ip = self.IPs.get() + def __init__(self,cidr,threads_num): + self.threads_num = threads_num + self.log_handler = None + + self.cidr, self.logfilename = self.load_target(cidr) + self.cur_position = 1 + self.total = len(self.cidr) + self.running = True + + #build ip queue + self.IPs = Queue.Queue() + self.msg_queue = Queue.Queue() + for ip in self.cidr: + self.IPs.put(ip) + + def load_target(self, target): + targets = [] + logfilename = '' try: - r = requests.Session().get('http://'+str(ip),headers=header,timeout=TimeOut) - status = r.status_code - title = re.search(r'(.*)', r.text) #get the title - if title: - title = title.group(1).strip().strip("\r").strip("\n")[:30] - else: - title = "None" - banner = '' - try: - banner += r.headers['Server'][:20] #get the server banner - except:pass - printLock.acquire() - print "|%-16s|%-6s|%-20s|%-30s|" % (ip,status,banner,title) - print "+----------------+------+--------------------+------------------------------+" - - #Save log - with open("./log/"+self.cidr.strNormal(3)+".log",'a') as f: - f.write(ip+"\n") - - except Exception,e: - printLock.acquire() - finally: - printLock.release() - - #Multi thread - def run(self): - for i in range(self.threads_num): - t = threading.Thread(target=self.request) - t.start() + # try to open as file + with open(target, 'r') as handle: + for line in handle: + if line.strip(): + targets.append(line.strip()) + logfilename = basename(target) + except IOError, e: + # treat as range of ip + ips = IP(target) + logfilename = ips.strNormal(3) + targets = map(lambda x: str(x), ips) + + return (targets, './log/{}.log'.format(logfilename)) + + def __record(self, log): + if self.log_handler: + self.log_handler.write(log + '\n') + print log + + def request(self, log_handler): + while self.IPs.qsize() > 0: + ip = self.IPs.get() + self.msg_queue.put('Current target: ' + ip) + + try: + r = requests.Session().get('http://' + ip,headers=header,timeout=TimeOut) + status = r.status_code + title = re.search(r'(.*)', r.text) #get the title + if title: + title = title.group(1).strip().strip("\r").strip("\n")[:30] + else: + title = "None" + banner = '' + try: + banner += r.headers['Server'][:20] #get the server banner + except:pass + + self.msg_queue.put("|%-16s|%-6s|%-20s|%-30s|" % (ip,status,banner,title)) + self.msg_queue.put("+----------------+------+--------------------+------------------------------+") + except Exception: + # timeout, nothing to do + pass + + def __print_log_message(self): + while self.running: + try: + msg = self.msg_queue.get(timeout=0.1) + except: + continue + + with printLock: + if msg[0] in ('|', '+'): + self.__record(msg) + else: + sys.stdout.write(' ' * 100 + '\r') + sys.stdout.flush() + + if msg[:7] == 'Current': + msg = '%s (%d/%d)' % (msg, self.cur_position, self.total) + if self.cur_position < self.total: + self.cur_position += 1 + + sys.stdout.write(msg + '\r') + sys.stdout.flush() + + #Multi thread + def run(self): + threads = [] + + with codecs.open(self.logfilename, 'w+', 'utf-8') as handle: + try: + self.log_handler = handle + log_thread = threading.Thread(target=self.__print_log_message) + log_thread.start() + + self.msg_queue.put("+----------------+------+--------------------+------------------------------+") + self.msg_queue.put("| IP |Status| Server | Title |") + self.msg_queue.put("+----------------+------+--------------------+------------------------------+") + + for i in range(self.threads_num): + threads.append(threading.Thread(target=self.request, args=(handle,))) + threads[i].setDaemon(True) + threads[i].start() + + for thread in threads: + thread.join(20000) + except KeyboardInterrupt: + self.msg_queue.put('[+] User aborted') + finally: + self.running = False + + self.msg_queue.put('done.') if __name__ == "__main__": - parser = optparse.OptionParser("Usage: %prog [options] target") - parser.add_option("-t", "--thread", dest = "threads_num", - default = 10, type = "int", - help = "[optional]number of theads,default=10") - (options, args) = parser.parse_args() - if len(args) < 1: - parser.print_help() - sys.exit(0) - - print "+----------------+------+--------------------+------------------------------+" - print "| IP |Status| Server | Title |" - print "+----------------+------+--------------------+------------------------------+" - - s = scan(cidr=args[0],threads_num=options.threads_num) - s.run() + parser = optparse.OptionParser("Usage: %prog [options] ") + parser.add_option("-t", "--thread", dest = "threads_num", + default = 10, type = "int", + help = "[optional]number of theads,default=10") + (options, args) = parser.parse_args() + + if len(args) < 1: + parser.print_help() + sys.exit(0) + + s = scan(cidr = args[0], threads_num = options.threads_num) + s.run()