def parse_port_list(ports): """ Parse ports string like '80;443;1-1024' into port list. Returns: A list consist of port, which type is int. Raises: SwarmParseException: An error occurred when parse ports. """ try: ret = [] ports = ports.split(',') for curport in ports: midindex = curport.find('-') if midindex != -1: min = int(curport[:midindex], 10) max = int(curport[midindex + 1:], 10) if min < 0 or min > 65535 or max < 0 or max > 65535: raise SwarmUseException('invalid ports') for x in range(min, max + 1): ret.append(x) continue curport = int(curport, 10) if curport < 0 or curport > 65535: raise SwarmUseException('invalid ports') ret.append(curport) # remove duplicate element ret = {}.fromkeys(ret).keys() ret.sort() return ret except Exception as e: raise SwarmParseException('ports')
def generate_subtasks(self): if self._done: return [] # check wether exists option in nmap option group 'PORT SPECIFICATION' or # 'TARGET SPECIFICATION' legall = [ '-sL', '-sn', '-Pn', '-PS', '-PA', '-PU', '-PY', '-PE', '-PP', '-PM', '-PO', '-n', '-R', '--dns-servers', '--system-dns', '--traceroute', '-sS', '-sT', '-sA', '-sW', '-sM', '-sU', '-sN', '-sF', '-sX', '--scanflags', '-sI', '-sY', '-sZ', '-sO', '-b', '-sV', '--version-intensity', '--version-light', '--version-all', '--version-trace', '-sC', '--script', '--script-args', '--script-args-file', '--script-trace', '--script-updatedb', '--script-help', '-O', '--osscan-limit', '--osscan-guess', '-T', '--min-hostgroup', '--max-hostgroup', '--min-parallelism', '--max-parallelism', '--min-rtt-timeout', '--max-rtt-timeout', '--initial-rtt-timeout', '--max-retries', '--host-timeout', '--scan-delay', '--max-scan-delay', '--min-rate', '--max-rate', '-f', '--mtu', '-D', '-S', '-e', '-g', '--source-port', '--proxies', '--data', '--data-string', '--data-length', '--ip-options', '--ttl', '--spoof-mac', '--badsum', '-6', '-A', '--datadir', '--send-eth', '--send-ip', '--privileged', '--unprivileged' ] for cur in self._args.nmap_options: resultl = [cur.startswith(x) for x in legall] if not (True in resultl): raise SwarmUseException("not supported nmap option") subtaskl = [] # generate subtask if self._args.nmap_top_ports != 0: nmap_top_ports = '--top-ports ' + str(self._args.nmap_top_ports) for curhost in self._args.target_list: subtaskl.append(curhost + '|' + nmap_top_ports) elif self._args.nmap_ports != '': # maybe it need to be decomposited try: portl = parse_port_list(self._args.nmap_ports) for curhost in self._args.target_list: index = 0 while index < len(portl): subtaskl.append(curhost + '|' + '-p ' + ','.join( merge_ports(portl[index:index + self._args.task_granularity * 500]))) index += self._args.task_granularity * 500 except SwarmParseException as e: raise SwarmUseException('illeagal port format in nmap_ports') else: raise SwarmUseException('at least one port need to be provided') # mark self._done = True return subtaskl
def __init__(self, args): super(Master, self).__init__() self._args = args # parse crawler seeds if self._args.map_seed == '': seedl = [ '/', ] else: seedl = ['/' + x for x in self._args.map_seed.split(',')] # check cookies format if self._args.map_cookies != '': cookiel = self._args.map_cookies.split(',') for cur in cookiel: if len(cur.split(':')) != 2: raise SwarmUseException('cookies format error') # parse port list http_portl = [] https_portl = [] # just used for check validity here try: if self._args.map_http_port != '': http_portl = parse_port_list(self._args.map_http_port) if self._args.map_https_port != '': https_portl = parse_port_list(self._args.map_https_port) except SwarmParseException as e: raise SwarmUseException( 'invalid http or https port argument in sitemap crawler') # generate initial domain for final result report # it should prepare a list contains valid domain seed like: # ['http://github.com:81','http://XX.com:80','https://github.com:9090'] self._doml = [] for curhost in self._args.target_list: for curport in http_portl: self._doml.append('http://' + curhost + ':' + str(curport)) for curport in https_portl: self._doml.append('https://' + curhost + ':' + str(curport)) # generate inital url list wait to be crawled self._waitl = [] for curdom in self._doml: for curseed in seedl: self._waitl.append(curdom + curseed + ',get,,')
def __init__(self, args): super(Master, self).__init__() self._args = args self._domain_list = removeip(self._args.target_list) # copy it for quick query self._domain_list_orig = copy.copy(self._domain_list) # do some check if len(self._domain_list) == 0: raise SwarmUseException('domain name must be provided') if self._args.domain_maxlevel <= 0: raise SwarmUseException( 'subdomain name max level must be positive') # initial the collection self._args.coll.insert({'root': self._domain_list}) # record current subdomain name level self._curlevel = 0
def configfile_parse(args): try: conf_parser=ConfigParser.ConfigParser() conf_parser.read('/etc/swarm/swarm.conf') # output options args.logfile=conf_parser.get('Output','logfile') args.verbose=conf_parser.getboolean('Output','verbose') args.disable_col=conf_parser.getboolean('Output','disable_col') # target options args.target=conf_parser.get('Target','target') args.target_file=conf_parser.get('Target','target_file') args.target=args.target.split() # swarm options args.swarm=conf_parser.get('Swarm','swarm') args.swarm_file=conf_parser.get('Swarm','swarm_file') args.timeout=conf_parser.getfloat('Swarm','timeout') args.waken_cmd=conf_parser.get('Swarm','waken_cmd') args.m_addr=conf_parser.get('Swarm','m_addr') args.m_port=conf_parser.getint('Swarm','m_port') args.s_port=conf_parser.getint('Swarm','s_port') args.authkey=conf_parser.get('Swarm','authkey') args.sync_data=conf_parser.getboolean('Swarm','sync_data') args.swarm=args.swarm.split() # database options args.db_addr=conf_parser.get('Database','db_addr') args.db_port=conf_parser.getint('Database','db_port') # common options args.process_num=conf_parser.getint('Common','process_num') args.thread_num=conf_parser.getint('Common','thread_num') args.task_granularity=conf_parser.getint('Common','task_granularity') # parse arguments of modules in confiuration file try: for curmod in args.modules: module=importlib.import_module('modules.'+curmod+'.'+curmod) conf_parser=ConfigParser.ConfigParser() conf_parser.read('/etc/swarm/'+curmod+'.conf') module.parse_conf(args,conf_parser) except ImportError as e: # print repr(e) raise SwarmModuleException('an error occured when try to import module: '+curmod+ ' info: '+repr(e)) except Exception as e: raise SwarmModuleException('an error occured when parse configuration file of module:'+ curmod+' info: '+repr(e)) except SwarmModuleException as e: raise e except Exception as e: raise SwarmUseException('parse config file error: '+repr(e))
def __init__(self, args): self._args = args self._swarm_num = 0 try: LOG.info('begin to parse target list') # parse target list self._args.target_list = getlist(args.target, args.target_file) LOG.log(REPORT, 'target list parse completed') except SwarmBaseException as e: raise SwarmUseException('parse target error: ' + str(e)) try: LOG.info('begin to parse swarm list') # parse swarm list if args.waken_cmd != '': self._swarm_list, self._swarm_port_list = getswarmlist( args.swarm, args.swarm_file) else: self._swarm_list = getlist(args.swarm, args.swarm_file) LOG.log(REPORT, 'swarm list parse completed') except SwarmBaseException as e: raise SwarmUseException('parse swarm error: ' + str(e))
def generate_subtasks(self): """ Decomposition domain name scan task and distribute tasks, get result from swarm. Task granularity should not be too small or too huge. """ self._curlevel += 1 # if it is out of range, end this task if self._curlevel > self._args.domain_maxlevel: return [] # begin to discomposition if self._args.domain_compbrute == True: try: # generate list of subtasks subtasklist = generate_compbrute_subtask( self._domain_list, self._args.domain_levellen, self._args.domain_charset, self._args.task_granularity) except SwarmParseException as e: raise SwarmUseException('invalid subdomain name ' + str(e) + ', or format error') # use dictionary else: if self._args.domain_dict == '': raise SwarmUseException( 'domain name dictionary need to be provided') try: # generate list of subtasks subtasklist = generate_dictbrute_subtask( self._domain_list, self._args.domain_dict, self._args.task_granularity) except IOError as e: raise SwarmUseException( 'can not open dictionary for domain scan, path:%s' % (dict)) # clear domain list for next iteration self._domain_list = [] return subtasklist
def do_task(self, task): """ TODO: multiple ip address for same domain name """ try: taskl = task.split('|') nmap_task = NmapProcess( taskl[0], taskl[1] + ' ' + ' '.join(self._args.nmap_options)) rc = nmap_task.run() if rc != 0: return '|'.join( [taskl[0], 'f', str(rc) + ' ' + nmap_task.stderr]) return '|'.join([taskl[0], 's', nmap_task.stdout]) # if there is no nmap on current host, quit quietly except EnvironmentError, e: raise SwarmUseException(str(e))
def configfile_parse(args,config_file='swarm.conf'): try: conf_parser=ConfigParser() conf_parser.read(config_file) # output options args.logfile=conf_parser.get("Output","logfile") args.verbose=conf_parser.getboolean("Output","verbose") args.disable_col=conf_parser.getboolean("Output","disable_col") # target options args.target=conf_parser.get("Target","target") args.target_file=conf_parser.get("Target","target_file") if args.target!='': args.target=args.target.split() # swarm options args.swarm=conf_parser.get("Swarm","swarm") args.swarm_file=conf_parser.get("Swarm","swarm_file") args.timeout=conf_parser.getfloat("Swarm","timeout") args.waken_cmd=conf_parser.get("Swarm","waken_cmd") args.m_addr=conf_parser.get("Swarm","m_addr") args.m_port=conf_parser.getint("Swarm","m_port") args.s_port=conf_parser.getint("Swarm","s_port") args.authkey=conf_parser.get("Swarm","authkey") args.sync_data=conf_parser.getboolean("Swarm","sync_data") if args.swarm!='': args.swarm=args.swarm.split() # common options args.process_num=conf_parser.getint("Common","process_num") args.thread_num=conf_parser.getint("Common","thread_num") # domain scan options args.enable_domain_scan=conf_parser.getboolean("Domain Scan","enable_domain_scan") args.domain_compbrute=conf_parser.getboolean("Domain Scan","domain_compbrute") args.domain_dict=conf_parser.get("Domain Scan","domain_dict") args.domain_maxlevel=conf_parser.getint("Domain Scan","domain_maxlevel") args.domain_charset=conf_parser.get("Domain Scan","domain_charset") args.domain_levellen=conf_parser.get("Domain Scan","domain_levellen") args.domain_timeout=conf_parser.getfloat("Domain Scan","domain_timeout") except Exception,e: print 'parse config file error' raise SwarmUseException('parse config file error')
def parse_distribute_task(self): # do some common check here if self._args.task_granularity < 0 or self._args.task_granularity > 3: raise SwarmUseException( 'invalid task granularity, it should be one number of 1-3') if self._args.process_num < 0: raise SwarmUseException('process number can not be negative') if self._args.thread_num <= 0: raise SwarmUseException('thread number should be positive') # connect to db server LOG.info('try to connect to db server: %s:%d' % (self._args.db_addr, self._args.db_port)) self._args.db, self._args.coll = init_db(self._args.db_addr, self._args.db_port, self._args.mod) LOG.info('Connection to db server completed') # start the manager self._manager = MSwarmManager(self._args.timeout, address=('', self._args.m_port), authkey=self._args.authkey) try: module = importlib.import_module('modules.' + self._args.mod + '.' + self._args.mod) except ImportError as e: raise SwarmModuleException('an error occured when load module:' + self._args.mod) LOG.info('load module: ' + self._args.mod) LOG.info('begin to decompose task...') mod_master = getattr(module, 'Master')(self._args) # begin first round of tasks decomposition and distribution roundn = 0 self._manager.init_task_statistics() while True: subtaskl = mod_master.generate_subtasks() taskn = len(subtaskl) if taskn == 0: break roundn += 1 LOG.log(REPORT, 'begin round %d' % roundn) LOG.info('round %d: put task into queue...' % roundn) for cur in subtaskl: self._manager.put_task(self._args.mod, cur) LOG.log( REPORT, 'round %d: %d tasks have been put into queue' % (roundn, taskn)) LOG.info('round %d: get result from swarm...' % roundn) # get result confirmedn = 0 self._manager.prepare_get_result() while True: try: result = self._manager.get_result() if result == '': break confirmedn += 1 LOG.log( REPORT, 'round %d: %d/%d tasks have been completed' % (roundn, confirmedn, taskn)) mod_master.handle_result(result) except Queue.Empty as e: # check number of slave host, if someone has lost response, reorganize tasks # in queue. LOG.info('try to detect swarm status') r = self._send2swarm_r('ack') if len(r) < self._swarm_num: LOG.warning( '%d of swarm has lost response. now total swarm:%d' % (self._swarm_num - len(r), len(r))) self._swarm_num = len(r) # if no swarm left if self._swarm_num == 0: raise SwarmSlaveException( 'no swarm left. task failed') LOG.log(REPORT, 'reorganize tasks in queue...') self._manager.reorganize_tasks() LOG.log(REPORT, 'reorganization completed') else: LOG.log( REPORT, 'all swarm works fine. now num: %d' % self._swarm_num) # continue LOG.log(REPORT, 'round %d over' % roundn) LOG.log(REPORT, 'all tasks have been comfirmed') # do final report now mod_master.report() self._shutdown()
def __init__(self, args): super(Master, self).__init__() self._args = args self._symnum = 0 # common check and parse args first if len(args.int_target)==0: raise SwarmUseException('at least one target need to be provided for intruder') for t in args.int_target: if not t.startswith(('https://','http://')): raise SwarmUseException('unsupported scheme: '+t) if args.int_method.lower() not in ['get','post','head','delete','options','trace', 'put','connect']: raise SwarmUseException('illegal http method') else: self._args.int_method=args.int_method.lower() # check headers format try: if args.int_headers!="": self._args.int_headers=json.loads(input2json(args.int_headers)) except Exception as e: raise SwarmUseException('broken json data used in customized http headers') # check cookies format if args.int_cookies!='': cookiel=args.int_cookies.split(',') for cur in cookiel: if len(cur.split(':'))!=2: raise SwarmUseException('cookies format error') # count attack points number in url and payload for cur_n in xrange(999): if args.int_body.find('@'+str(cur_n)+'@')==-1: find=[True if t.find('@'+str(cur_n)+'@')!=-1 else False for t in args.int_target] if not any(find): break self._symnum=cur_n+1 if self._symnum==0: raise SwarmUseException('at least one attack point need to be specified') # in case too many attack points if args.int_body.find('@999@')!=-1: raise SwarmUseException('too many attack points') for t in args.int_target: if t.find('@999@')!=-1: raise SwarmUseException('too many attack points') # parse and count attack points in payload if args.int_payload=='': raise SwarmUseException('you need to specify your payload') else: self._args.int_payload=args.int_payload.split(',') if len(self._args.int_payload)!=self._symnum: raise SwarmUseException('attack points mismatch between ones marked ' 'and ones specified in payload') for index,cursym in enumerate(self._args.int_payload): pair=cursym.split(':') if pair[0]!='@'+str(index)+'@': raise SwarmUseException('the number in indicator symbol should ' 'increase continuously') if len(pair)==2: try: with open(pair[1]) as fp: pass except IOError as e: raise SwarmUseException('can not open dictionary: '+pair[1]) elif len(pair)==3: try: pair[1]=parse_charset(pair[1]) pair[2]=parse_digital_interval(pair[2]) except SwarmParseException as e: raise SwarmUseException('invalid '+str(e)+'in "'+ self._args.int_payload[index]+'"') else: raise SwarmUseException('payload format error, it should be like: ' '"@0@:PATH,@1@:NUM-NUM:CHARSET"') self._args.int_payload[index]=pair self._done=False
def cli_parse(args): parser = argparse.ArgumentParser() parser.add_argument('-m', dest='mod', metavar='MODULE', required=True, help='Use module name in ./modules/ to enable it') # output option output = parser.add_argument_group( 'Output', 'These option can be used to control output') output.add_argument('-v', dest='verbose', action='store_true', help='Output more verbose') output.add_argument('-c', dest='disable_col', action='store_true', help='Disable colorful log output') output.add_argument('-o', dest='logfile', metavar='PATH', help='Record log in target file') # target option target = parser.add_argument_group( 'Target', 'At least one of these options has to be provided to define target unless there is ' 'another special option for defining target in the module') target.add_argument( '-t', dest='target', metavar='TARGET', nargs='*', help='Separated by blank (eg: github.com 127.0.0.0/24 192.168.1.5)') target.add_argument( '-T', dest='target_file', metavar='PATH', help='File that contains target list, one target per line') # swarm option swarm = parser.add_argument_group( 'Swarm', 'Use these options to customize swarm connection. At least one of slave host ' 'has to be provided.') swarm.add_argument( '-s', dest='swarm', metavar='SWARM', nargs='*', help='Address of slave hosts with port if you need waken them' ' (eg: 192.168.1.2:9090 192.18.1.3:9191). ' 'No port if swarm-s on slave host has already run') swarm.add_argument('-S', dest='swarm_file', metavar='PATH', help='File that contains slave list, one host per line') swarm.add_argument( '--waken', dest='waken_cmd', metavar='CMD', help= 'Command to waken up slave hosts, null if swarm-s on slave host has already run' ) swarm.add_argument( '--timeout', dest='timeout', metavar='TIME', type=float, help='Seconds to wait before request to swarm getting response') swarm.add_argument( '--m-addr', dest='m_addr', metavar='ADDR', help='Master address which is reachable by all slave hosts') swarm.add_argument('--m-port', dest='m_port', metavar='PORT', type=int, help='Listen port on master host to distribute task') swarm.add_argument( '--s-port', dest='s_port', metavar='PORT', type=int, help='Listen port on slave host to receive command from master') swarm.add_argument('--authkey', dest='authkey', metavar='KEY', help='Auth key between master and slave hosts') # swarm.add_argument('--sync',dest='sync_data',action='store_true',default=False, # help='Synchronize data like dictionary and vulnerability database etc') # database option database = parser.add_argument_group( 'Database', 'These option can be used to access MongoDB server') database.add_argument('--db-addr', dest='db_addr', metavar='ADDR', help='Address of MongoDB server') database.add_argument('--db-port', dest='db_port', metavar='PORT', type=int, help='Listening port of MongoDB server') # common option common = parser.add_argument_group( 'Common', 'These option can be used to customize common configuration of slave host' ) common.add_argument('--process', dest='process_num', metavar='NUM', type=int, help='Max number of concurrent process on slave host') common.add_argument('--thread', dest='thread_num', metavar='NUM', type=int, help='Max number of concurrent threads on slave host') common.add_argument('--taskg', dest='task_granularity', metavar='NUM', type=int, help='Granularity of subtasks from 1 to 3') try: for curmod in args.modules: module = importlib.import_module('modules.' + curmod + '.' + curmod) module.add_cli_args(parser) except ImportError as e: raise SwarmModuleException( 'an error occurred when try to import module: ' + curmod + ' info: ' + repr(e)) except Exception as e: raise SwarmModuleException( 'an error occurred when add cli parse option of module:' + curmod + ' info: ' + repr(e)) parser.parse_args(namespace=args) if not any((args.target, args.target_file)): raise SwarmUseException('At least one target need to be provided') if not any((args.swarm, args.swarm_file)): raise SwarmUseException('At least one swarm need to be provided')
def scan_domain(self): """ Decomposition domain name scan task and distribute tasks, get result from swarm. Task granularity should not be too small or too huge Task format: __doms__:task_index:domain name:dict:dict_path:start_line:scan_lines __doms__:task_index:domain name:comp:charset:begin_str:end_str Result format: __doms__:task_index:result Example: put task: __doms__:26:github.com:dict:2000:3000 __doms__:980:github.com:comp:ABCDEFGHIJKLMN8980:DAAA:D000 get result: __doms__:980:gist.github.com;XX.github.com __doms__:980:no subdomain """ scan_result = [] domain_list = removeip(self._target_list) if len(domain_list) == 0: LOG.critical('domain name must be provided') raise SwarmUseException('domain name must be provided') if self._args.domain_maxlevel <= 0: LOG.critical('subdomain name max level must be positive') raise SwarmUseException( 'subdomain name max level must be positive') # begin to discomposition for curlevel in range(self._args.domain_maxlevel): self._init_task_statistics() if self._args.domain_compbrute == True: # parse interval of subdomain name length try: midindex = self._args.domain_levellen.find('-') minlen = int(self._args.domain_levellen[:midindex], 10) maxlen = int(self._args.domain_levellen[midindex + 1:], 10) except Exception, e: LOG.critical( 'invalid subdomain name length interval, or format error' ) raise SwarmUseException( 'invalid subdomain name length interval, or format error' ) # parse char set charset = self._parse_charset() task_granularity = 4 for cur_target in domain_list: begin_str = '' if maxlen < task_granularity: begin_str = minlen * charset[0] end_str = maxlen * charset[-1] task = ':'.join( [cur_target, 'comp', charset, begin_str, end_str]) self._put_task('__doms__', task) continue if minlen < task_granularity: begin_str = minlen * charset[0] end_str = (task_granularity - 1) * charset[-1] task = ':'.join( [cur_target, 'comp', charset, begin_str, end_str]) self._put_task('__doms__', task) bflist = generate_bflist(charset, charset[0], (maxlen - task_granularity + 1) * charset[-1]) for pre_str in bflist: begin_str = pre_str + (task_granularity - 1) * charset[0] end_str = pre_str + (task_granularity - 1) * charset[-1] task = ':'.join( [cur_target, 'comp', charset, begin_str, end_str]) self._put_task('__doms__', task) # use dictionary else: if self._args.domain_dict == '': LOG.critical('domain name dictionary need to be provided') raise SwarmUseException( 'domain name dictionary need to be provided') try: # get total number of dictionary lines with open(self._args.domain_dict) as fp: sumline = sum(1 for i in fp) except IOError, e: LOG.critical( 'can not open dictionary for domain scan, path:%s' % (self._args.domain_dict)) raise SwarmUseException( 'can not open dictionary for domain scan, path:%s' % (self._args.domain_dict)) task_granularity = 1000 for cur_target in domain_list: # begin from fisrt line cur_line = 1 while True: # after next calculation, cur_line point to next start line if (cur_line + task_granularity) > sumline: lines = sumline - cur_line + 1 else: lines = task_granularity task = ':'.join([ cur_target, 'dict', self._args.domain_dict, str(cur_line), str(lines) ]) self._put_task('__doms__', task) # update current line cur_line += task_granularity if cur_line > sumline: break
if target_file!='': f=open(target_file,'r') targets=f.read() iplist.extend(targets.splitlines()) f.close() # parse network segment and check iplist=_unite_list(iplist) LOG.info('parse completed') return iplist except socket.timeout, e: LOG.critical('time out when parsing target') raise SwarmNetException('time out when parsing target') except IOError, e: LOG.critical('can not open target file') raise SwarmUseException('can not open target file') except Exception, e: LOG.critical('invalid target or swarm address, or format error') raise SwarmUseException('invalid target or swarm address, or format error') def getswarmlist(swarm='',swarm_file=''): """ Return integrated ip and domain name list with port list from swarm list and file like (['127.0.0.1','127.0.0.2','github.com'],[80,90,90]). """ try: LOG.info('begin to parse swarm list') rawlist=[] iplist=[] portlist=[] if swarm!='':