def findLaunchableTools(): """ Try to find tools that matches all criteria. Returns: A tuple with two values: * A list of launchable tools as dictionary with values _id, name and priority * A dictionary of waiting tools with tool's names as keys and integer as value. """ toolsLaunchable = [] waiting = {} time_compatible_waves_id = Wave.searchForAddressCompatibleWithTime() for wave_id in time_compatible_waves_id: commandsLaunchableWave = Wave.getNotDoneTools(wave_id) for tool in commandsLaunchableWave: toolModel = Tool.fetchObject({"_id": tool}) try: waiting[str(toolModel)] += 1 except KeyError: waiting[str(toolModel)] = 1 command = toolModel.getCommand() if command is None: prio = 0 else: prio = int(command.get("priority", 0)) toolsLaunchable.append({ "_id": tool, "name": str(toolModel), "priority": prio }) return toolsLaunchable, waiting
def onTreeviewSelect(self, event=None): """Called when a line is selected on the treeview Open the selected object view on the view frame. Args: _event: not used but mandatory """ selection = self.selection() if len(selection) == 1: item = super().onTreeviewSelect(event) if isinstance(item, str): mongoInstance = MongoCalendar.getInstance() self.saveState(mongoInstance.calendarName) if str(item) == "waves": objView = WaveView(self, self.appli.viewframe, self.appli, WaveController(Wave())) objView.openInsertWindow() elif str(item) == "commands": objView = CommandView( self, self.appli.viewframe, self.appli, CommandController( Command({"indb": mongoInstance.calendarName}))) objView.openInsertWindow() elif str(item) == "ips": objView = MultipleIpView(self, self.appli.viewframe, self.appli, IpController(Ip())) objView.openInsertWindow() elif "intervals" in str(item): wave = Wave.fetchObject({ "_id": ObjectId(IntervalView.treeviewListIdToDb(item)) }) objView = IntervalView( self, self.appli.viewframe, self.appli, IntervalController(Interval().initialize(wave.wave))) objView.openInsertWindow() elif "scopes" in str(item): wave = Wave.fetchObject( {"_id": ObjectId(ScopeView.treeviewListIdToDb(item))}) objView = MultipleScopeView( self, self.appli.viewframe, self.appli, ScopeController(Scope().initialize(wave.wave))) objView.openInsertWindow() else: self.openModifyWindowOf(item) elif len(selection) > 1: # Multi select: multiView = MultiSelectionView(self, self.appli.viewframe, self.appli) for widget in self.appli.viewframe.winfo_children(): widget.destroy() multiView.form.clear() multiView.openModifyWindow()
def autoScanv2(databaseName, workerName): """ Search tools to launch within defined conditions and attempts to launch them this celery worker. Gives a visual feedback on stdout Args: databaseName: The database to search tools in endless: a boolean that indicates if the autoscan will be endless or if it will stop at the moment it does not found anymore launchable tools. useReprinter: a boolean that indicates if the array outpur will be entirely reprinted or if it will be overwritten. """ mongoInstance = MongoCalendar.getInstance() mongoInstance.connectToDb(databaseName) time_compatible_waves_id = Wave.searchForAddressCompatibleWithTime() worker = Worker(workerName) while True: # Extract commands with compatible time and not yet done launchableTools, waiting = findLaunchableToolsOnWorker( worker, databaseName) # Sort by command priority launchableTools.sort( key=lambda tup: (tup["errored"], int(tup["priority"]))) # print(str(launchableTools)) dispatchLaunchableToolsv2(launchableTools, worker) time.sleep(3)
def autoScan(databaseName, endless, useReprinter=False): """ Search tools to launch within defined conditions and attempts to launch them on celery workers. Gives a visual feedback on stdout Args: databaseName: The database to search tools in endless: a boolean that indicates if the autoscan will be endless or if it will stop at the moment it does not found anymore launchable tools. useReprinter: a boolean that indicates if the array outpur will be entirely reprinted or if it will be overwritten. """ mongoInstance = MongoCalendar.getInstance() mongoInstance.connectToDb(databaseName) my_monitor = Monitor(databaseName) Utils.resetUnfinishedTools() time_compatible_waves_id = Wave.searchForAddressCompatibleWithTime() killer = GracefulKiller() if not endless: killer.kill_now = len(time_compatible_waves_id) <= 0 print("No wave compatible") else: killer.kill_now = False if useReprinter: reprinter = Reprinter() else: reprinter = None max_tabulation = _getMaxColumnLen() while not killer.kill_now: # Extract commands with compatible time and not yet done launchableTools, waiting = findLaunchableTools() # Sort by command priority launchableTools.sort(key=lambda tup: int(tup["priority"])) dispatchLaunchableTools(my_monitor, launchableTools) printStatus(max_tabulation, waiting, reprinter) time.sleep(3) my_monitor.stop()
def findLaunchableToolsOnWorker(worker, calendarName): """ Try to find tools that matches all criteria. Args: workerName: the current working worker Returns: A tuple with two values: * A list of launchable tools as dictionary with values _id, name and priority * A dictionary of waiting tools with tool's names as keys and integer as value. """ mongoInstance = MongoCalendar.getInstance() mongoInstance.connectToDb(calendarName) toolsLaunchable = [] worker_registered = mongoInstance.findInDb("pollenisator", "workers", {"name": worker.name}, False) commands_registered = worker_registered["registeredCommands"] waiting = {} time_compatible_waves_id = Wave.searchForAddressCompatibleWithTime() for wave_id in time_compatible_waves_id: commandsLaunchableWave = Wave.getNotDoneTools(wave_id) for tool in commandsLaunchableWave: toolModel = Tool.fetchObject({"_id": tool}) if toolModel.name not in commands_registered: continue if worker.hasRegistered(toolModel): try: waiting[str(toolModel)] += 1 except KeyError: waiting[str(toolModel)] = 1 command = toolModel.getCommand() if command is None: prio = 0 else: prio = int(command.get("priority", 0)) toolsLaunchable.append({ "_id": tool, "name": str(toolModel), "priority": prio, "errored": "error" in toolModel.status }) return toolsLaunchable, waiting
def customCommand(self, _event=None): """ Ask the user for a custom tool to launch and which parser it will use. Args: _event: not used but mandatory """ mongoInstance = MongoCalendar.getInstance() workers = self.appli.scanManager.monitor.getWorkerList() workers.append("localhost") dialog = ChildDialogCustomCommand(self, workers, "localhost") self.wait_window(dialog.app) if isinstance(dialog.rvalue, tuple): commName = dialog.rvalue[0] commArgs = dialog.rvalue[1] parser = dialog.rvalue[2] worker = dialog.rvalue[3] for selected in self.selection(): view_o = self.getViewFromId(selected) if view_o is not None: lvl = "network" if isinstance(view_o, ScopeView) else None lvl = "wave" if isinstance(view_o, WaveView) else lvl lvl = "ip" if isinstance(view_o, IpView) else lvl lvl = "port" if isinstance(view_o, PortView) else lvl if lvl is not None: inst = view_o.controller.getData() wave = inst.get("wave", "Custom commands") if wave == "Custom commands": Wave().initialize("Custom commands").addInDb() tool = Tool() tool.initialize(commName, wave, inst.get("scope", ""), inst.get("ip", None), inst.get("port", None), inst.get("proto", None), lvl, commArgs, dated="None", datef="None", scanner_ip="None", notes="Arguments: " + commArgs) tool.addInDb() if tool is None: print("Tool already existing.") return self.appli.scanManager.monitor.launchTask( mongoInstance.calendarName, tool, parser, False, worker)
def prepareCalendar(self, dbName, pentest_type, start_date, end_date, scope, settings, pentesters): """ Initiate a pentest database with wizard info Args: dbName: the database name pentest_type: a pentest type choosen from settings pentest_types. Used to select commands that will be launched by default start_date: a begining date and time for the pentest end_date: ending date and time for the pentest scope: a list of scope valid string (IP, network IP or host name) settings: a dict of settings with keys: * "Add domains whose IP are in scope": if 1, will do a dns lookup on new domains and check if found IP is in scope * "Add domains who have a parent domain in scope": if 1, will add a new domain if a parent domain is in scope * "Add all domains found": Unsafe. if 1, all new domains found by tools will be considered in scope. """ commands = Command.getList({"$or":[{"types":{"$elemMatch":{"$eq":pentest_type}}}, {"types":{"$elemMatch":{"$eq":"Commun"}}}]}) if not commands: commandslist = Command.getList() if not commandslist: dialog = ChildDialogQuestion(self.parent, "No command found", "There is no registered command in the database. Would you like to import the default set?") self.parent.wait_window(dialog.app) if dialog.rvalue != "Yes": return default = os.path.join(Utils.getMainDir(), "exports/pollenisator_commands.gzip") res = self.importCommands(default) if res: default = os.path.join(Utils.getMainDir(), "exports/pollenisator_group_commands.gzip") res = self.importCommands(default) commands = Command.getList({"$or":[{"types":{"$elemMatch":{"$eq":pentest_type}}}, {"types":{"$elemMatch":{"$eq":"Commun"}}}]}) #Duplicate commands in local database allcommands = Command.fetchObjects({}) for command in allcommands: command.indb = MongoCalendar.getInstance().calendarName command.addInDb() Wave().initialize(dbName, commands).addInDb() Interval().initialize(dbName, start_date, end_date).addInDb() values = {"wave":dbName, "Scopes":scope, "Settings":False} ScopeController(Scope()).doInsert(values) self.settings.reloadSettings() self.settings.db_settings["pentest_type"] = pentest_type self.settings.db_settings["include_domains_with_ip_in_scope"] = settings['Add domains whose IP are in scope'] == 1 self.settings.db_settings["include_domains_with_topdomain_in_scope"] = settings["Add domains who have a parent domain in scope"] == 1 self.settings.db_settings["include_all_domains"] = settings["Add all domains found"] == 1 self.settings.db_settings["pentesters"] = list(map(lambda x: x.strip(), pentesters.split("\n"))) self.settings.save()
def __init__(self, parent): """ Open a child dialog of a tkinter application to ask details about existing files parsing. Args: parent: the tkinter parent view to use for this window construction. """ self.app = tk.Toplevel(parent) self.app.title("Upload result file") self.rvalue = None self.parent = parent appFrame = ttk.Frame(self.app) self.form = FormPanel() self.form.addFormLabel("Import one file or choose a directory", "", side=tk.TOP) self.form.addFormFile("File", ".+", width=50, side=tk.TOP, mode="file|directory") self.form.addFormLabel("Plugins", side=tk.TOP) self.form.addFormCombo("Plugin", ["auto-detect"] + listPlugin(), "auto-detect", side=tk.TOP) self.form.addFormLabel("Wave name", side=tk.TOP) wave_list = Wave.listWaves() if "Imported files" not in wave_list: wave_list.append("Imported files") self.form.addFormCombo("Wave", wave_list, "Imported files", side=tk.TOP) self.form.addFormButton("Parse", self.onOk, side=tk.TOP) self.form.constructView(appFrame) appFrame.pack(ipadx=10, ipady=10) self.app.transient(parent) self.app.grab_set()
def onOk(self, _event=None): """ Called when the user clicked the validation button. launch parsing with selected parser on selected file/directory. Close the window. Args: _event: not used but mandatory """ res, msg = self.form.checkForm() if not res: tk.messagebox.showwarning("Form not validated", msg, parent=self.app) return notes = None tags = None form_values = self.form.getValue() form_values_as_dicts = ViewElement.list_tuple_to_dict(form_values) file_path = form_values_as_dicts["File"] plugin = form_values_as_dicts["Plugin"] wave = form_values_as_dicts["Wave"] files = [] if os.path.isdir(file_path): # r=root, d=directories, f = files for r, _d, f in os.walk(file_path): for fil in f: files.append(os.path.join(r, fil)) else: files.append(file_path) results = {} dialog = ChildDialogProgress( self.parent, "Importing files", "Importing " + str(len(files)) + " files. Please wait for a few seconds.", 200, "determinate") dialog.show(len(files)) # LOOP ON FOLDER FILES for f_i, file_path in enumerate(files): md5File = md5(file_path) toolName = os.path.splitext( os.path.basename(file_path))[0] + md5File[:6] dialog.update(f_i) if plugin == "auto-detect": # AUTO DETECT foundPlugin = "Ignored" for pluginName in listPlugin(): if foundPlugin != "Ignored": break mod = loadPlugin(pluginName) if mod.autoDetectEnabled(): with io.open(file_path, 'r', encoding="utf-8") as f: notes, tags, lvl, targets = mod.Parse(f) if notes is not None and tags is not None: foundPlugin = pluginName results[foundPlugin] = results.get(foundPlugin, []) + [file_path] else: # SET PLUGIN mod = loadPlugin(plugin) with io.open(file_path, 'r', encoding="utf-8") as f: notes, tags, lvl, targets = mod.Parse(f) results[plugin] = results.get(plugin, []) + [file_path] # IF PLUGIN FOUND SOMETHING if notes is not None and tags is not None: # ADD THE RESULTING TOOL TO AFFECTED for target in targets.values(): date = datetime.fromtimestamp(os.path.getmtime( file_path)).strftime("%d/%m/%Y %H:%M:%S") if target is None: scope = None ip = None port = None proto = None else: scope = target.get("scope", None) ip = target.get("ip", None) port = target.get("port", None) proto = target.get("proto", None) Wave().initialize(wave, []).addInDb() tool_m = Tool().initialize(toolName, wave, scope=scope, ip=ip, port=port, proto=proto, lvl=lvl, text="", dated=date, datef=date, scanner_ip="Imported file", status="done", notes=notes, tags=tags) tool_m.addInDb() mongoInstance = MongoCalendar.getInstance() outputRelDir = tool_m.getOutputDir( mongoInstance.calendarName) abs_path = os.path.dirname(os.path.abspath(__file__)) outputDir = os.path.join(abs_path, "../../../results", outputRelDir) mod.centralizeFile(file_path, outputDir) tool_m.update({ "resultfile": os.path.join(outputRelDir, os.path.basename(file_path)) }) dialog.destroy() # DISPLAY RESULTS presResults = "" filesIgnored = 0 for key, value in results.items(): presResults += str(len(value)) + " " + str(key) + ".\n" if key == "Ignored": filesIgnored += 1 if plugin == "auto-detect": if filesIgnored > 0: tk.messagebox.showwarning("Auto-detect ended", presResults, parent=self.app) else: tk.messagebox.showinfo("Auto-detect ended", presResults, parent=self.app) else: if filesIgnored > 0: tk.messagebox.showwarning("Parsing ended", presResults, parent=self.app) else: tk.messagebox.showinfo("Parsing ended", presResults, parent=self.app) self.rvalue = None self.app.destroy()
def main(): """Main function. Start pollenisator application """ parser = argparse.ArgumentParser( description="Edit database stored in mongo database") parser.add_argument("--import", dest="importName", action="store", nargs="?") parser.add_argument("--calendar", dest="calendarName", action="store") parser.add_argument("--exec", dest="execCmd", action="store") args, remainingArgs = parser.parse_known_args() if args.importName: mongoInstance = MongoCalendar.getInstance() mongoInstance.importDatabase(args.importName) return if args.execCmd and args.calendarName: mongoInstance = MongoCalendar.getInstance() mongoInstance.connectToDb(args.calendarName) cmdName = os.path.splitext(os.path.basename(args.execCmd.split(" ")[0]))[0] cmdName +="::"+str(time.time()).replace(" ","-") wave = Wave().initialize("Custom commands") wave.addInDb() tool = Tool() tool.initialize(cmdName, "Custom commands", "", None, None, None, "wave", args.execCmd+" "+(" ".join(remainingArgs)), dated="None", datef="None", scanner_ip="localhost") tool.updateInfos({"args":args.execCmd}) res, iid = tool.addInDb() if res: slave.executeCommand(args.calendarName, str(iid), "auto-detect") return print(""" .__ .. , [__) _ || _ ._ * __ _.-+- _ ._. | (_)||(/,[ )|_) (_] | (_)[ """) root = tk.Tk() root.resizable(True, True) dir_path = os.path.dirname(os.path.realpath(__file__)) dir_path = os.path.join(dir_path, "icon/favicon.png") img = tk.PhotoImage(file=dir_path) root.iconphoto(True, img) root.minsize(width=400, height=400) root.resizable(True, True) root.title("Pollenisator") root.geometry("1220x830") gc = None app = Appli(root) try: root.protocol("WM_DELETE_WINDOW", app.onClosing) gc = GracefulKiller(app) root.mainloop() print("Exiting tkinter main loop") except tk.TclError: pass try: root.destroy() print("Destroying app window") except tk.TclError: pass app.onClosing() if gc is not None: gc.kill_now = True
def _load(self): """ Load the treeview with database information """ mongoInstance = MongoCalendar.getInstance() dialog = ChildDialogProgress(self.appli, "Loading "+str( mongoInstance.calendarName), "Opening "+str(mongoInstance.calendarName) + ". Please wait for a few seconds.", 200, "determinate") step = 0 dialog.show(100) nbObjects = mongoInstance.find("waves").count() nbObjects += mongoInstance.find("scopes").count() nbObjects += mongoInstance.find("intervals").count() nbObjects += mongoInstance.find("scopes").count() nbObjects += mongoInstance.find("ips").count() nbObjects += mongoInstance.find("ports").count() nbObjects += mongoInstance.find("tools").count() onePercentNbObject = nbObjects//100 if nbObjects > 100 else 1 nbObjectTreated = 0 for child in self.get_children(): self.delete(child) self._hidden = [] self._detached = [] self.waves_node = self.insert("", "end", str( "waves"), text="Waves", image=WaveView.getClassIcon()) # Loading every category separatly is faster than recursivly. # This is due to cursor.next function calls in pymongo # Adding wave objects waves = Wave.fetchObjects({}) for wave in waves: wave_o = WaveController(wave) wave_vw = WaveView(self, self.appli.viewframe, self.appli, wave_o) wave_vw.addInTreeview(self.waves_node, False) nbObjectTreated += 1 if nbObjectTreated % onePercentNbObject == 0: step += 1 dialog.update(step) scopes = Scope.fetchObjects({}) for scope in scopes: scope_o = ScopeController(scope) scope_vw = ScopeView(self, self.appli.viewframe, self.appli, scope_o) scope_vw.addInTreeview(None, False) nbObjectTreated += 1 if nbObjectTreated % onePercentNbObject == 0: step += 1 dialog.update(step) intervals = Interval.fetchObjects({}) for interval in intervals: interval_o = IntervalController(interval) interval_vw = IntervalView(self, self.appli.viewframe, self.appli, interval_o) interval_vw.addInTreeview(None, False) nbObjectTreated += 1 if nbObjectTreated % onePercentNbObject == 0: step += 1 dialog.update(step) # Adding ip objects self.ips_node = self.insert("", "end", str( "ips"), text="IPs", image=IpView.getClassIcon()) ips = Ip.fetchObjects({}) for ip in ips: ip_o = IpController(ip) ip_vw = IpView(self, self.appli.viewframe, self.appli, ip_o) ip_vw.addInTreeview(None, False) self.appli.statusbar.notify(ip_vw.controller.getTags()) nbObjectTreated += 1 if nbObjectTreated % onePercentNbObject == 0: step += 1 dialog.update(step) # Adding port objects ports = Port.fetchObjects({}) for port in ports: port_o = PortController(port) port_vw = PortView(self, self.appli.viewframe, self.appli, port_o) port_vw.addInTreeview(None, False) self.appli.statusbar.notify(port_vw.controller.getTags()) nbObjectTreated += 1 if nbObjectTreated % onePercentNbObject == 0: step += 1 dialog.update(step) # Adding defect objects defects = Defect.fetchObjects({"ip":{"$ne":""}}) for defect in defects: defect_o = DefectController(defect) defect_vw = DefectView( self, self.appli.viewframe, self.appli, defect_o) defect_vw.addInTreeview(None) nbObjectTreated += 1 if nbObjectTreated % onePercentNbObject == 0: step += 1 dialog.update(step) # Adding tool objects tools = Tool.fetchObjects({}) for tool in tools: tool_o = ToolController(tool) tool_vw = ToolView(self, self.appli.viewframe, self.appli, tool_o) tool_vw.addInTreeview(None, False) self.appli.statusbar.notify(tool_vw.controller.getTags()) nbObjectTreated += 1 if nbObjectTreated % onePercentNbObject == 0: step += 1 dialog.update(step) self.sort(self.ips_node) self.appli.statusbar.update() dialog.destroy()
def notify(self, db, collection, iid, action, _parent): """ Callback for the observer implemented in mongo.py. Each time an object is inserted, updated or deleted the standard way, this function will be called. Args: collection: the collection that has been modified iid: the mongo ObjectId _id that was modified/inserted/deleted action: string "update" or "insert" or "delete". It was the action performed on the iid _parent: Not used. the mongo ObjectId of the parent. Only if action in an insert. Not used anymore """ mongoInstance = MongoCalendar.getInstance() if not mongoInstance.hasACalendarOpen(): return if mongoInstance.calendarName != db: return # Delete if action == "delete": if collection == "defects": view = self.getViewFromId(str(iid)) if view is not None: view.beforeDelete() self.appli.statusbar.notify([],view.controller.getTags()) try: self.delete(ObjectId(iid)) except tk.TclError: pass # item was not inserted in the treeview # Insert if action == "insert": view = None res = mongoInstance.find(collection, {"_id": ObjectId(iid)}, False) if collection == "tools": view = ToolView(self, self.appli.viewframe, self.appli, ToolController(Tool(res))) elif collection == "waves": view = WaveView(self, self.appli.viewframe, self.appli, WaveController(Wave(res))) elif collection == "scopes": view = ScopeView(self, self.appli.viewframe, self.appli, ScopeController(Scope(res))) elif collection == "ports": view = PortView(self, self.appli.viewframe, self.appli, PortController(Port(res))) elif collection == "ips": view = IpView(self, self.appli.viewframe, self.appli, IpController(Ip(res))) elif collection == "intervals": view = IntervalView(self, self.appli.viewframe, self.appli, IntervalController(Interval(res))) elif collection == "defects": view = DefectView(self, self.appli.viewframe, self.appli, DefectController(Defect(res))) try: if view is not None: view.addInTreeview() view.insertReceived() self.appli.statusbar.notify(view.controller.getTags()) except tk.TclError: pass if action == "update": try: view = self.getViewFromId(str(iid)) if view is not None: oldTags = self.item(str(iid))["tags"] view.controller.actualize() self.appli.statusbar.notify(view.controller.getTags(), oldTags) self.item(str(iid), text=str( view.controller.getModelRepr()), image=view.getIcon()) except tk.TclError: if view is not None: view.addInTreeview() if str(self.appli.openedViewFrameId) == str(iid): for widget in self.appli.viewframe.winfo_children(): widget.destroy() view.openModifyWindow() if view is not None: view.updateReceived() self.appli.statusbar.update()