def addAllTool(self, command_name): """ Add the appropriate tools (level check and wave's commands check) for this scope. Args: command_name: The command that we want to create all the tools for. """ mongoInstance = MongoCalendar.getInstance() command = mongoInstance.findInDb("pollenisator", "commands", {"name": command_name}, False) if command["lvl"] == "network": newTool = Tool() newTool.initialize(command["name"], self.wave, self.scope, "", "", "", "network") newTool.addInDb() return if command["lvl"] == "domain": if not Utils.isNetworkIp(self.scope): newTool = Tool() newTool.initialize(command["name"], self.wave, self.scope, "", "", "", "domain") newTool.addInDb() return ips = self.getIpsFitting() for ip in ips: i = Ip(ip) i.addAllTool(command_name, self.wave, self.scope)
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ notes = "" tags = [] targets = {} ip, domain = parse_reverse_dig(file_opened.read()) if ip is None: return None, None, None, None if domain is not None: # Add a domain as a scope in db Ip().initialize(domain).addInDb() ip_m = Ip().initialize(ip) res, iid = ip_m.addInDb() if not res: ip_m = Ip.fetchObject({"_id": iid}) hostnames = list(set(list(ip_m.infos.get("hostname", [])) + [domain])) ip_m.updateInfos({"hostname": hostnames}) ip_m.notes = "reversed dig give this domain : "+domain+"\n"+ip_m.notes notes += "Domain found :"+domain+"\n" targets["ip"] = {"ip": ip} ip_m.update() if notes == "": notes = "No domain found\n" return notes, tags, "ip", targets
def doInsert(self, values): """ Insert the Ip represented by this model in the database with the given values. Args: values: A dictionary crafted by MultipleIpView or IpView containg all form fields values needed. Returns: { '_id': The mongo ObjectId _id of the inserted command document. 'nbErrors': The number of objects that has not been inserted in database due to errors. } """ # Only multi insert exists at the moment for IP try: multi = True except KeyError: multi = False if multi: # Get form values ret = [] total = 0 accepted = 0 for line in values["IPs"].split("\n"): if line != "": # Insert in database model = Ip().initialize(line) inserted, iid = model.addInDb() if inserted: ret.append(iid) accepted += 1 total += 1 return ret, total - accepted # nb errors = total - accepted
def Parse(self, file_opened, **kwargs): """ Parse a opened file to extract information Example file: 10.0.0.1 - UNKNOWN - no connection - timeout 10.0.0.2 - VULNERABLE - ?? - ???? Args: file_opened: the open file kwargs: port("") and proto("") are valid Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ # 5. Parse the file has you want. # Here add a note to the tool's notes of each warnings issued by this testssl run. notes = "" tags = ["Neutral"] targets = {} for line in file_opened: # Auto Detect infos = line.split(" - ") if len(infos) < 3: return None, None, None, None if not Ip.isIp(infos[0]): return None, None, None, None if infos[1] not in ["UNKNOWN", "SAFE", "VULNERABLE"]: return None, None, None, None # Parse ip = line.split(" ")[0].strip() Ip().initialize(ip).addInDb() p_o = Port.fetchObject({"ip": ip, "port": kwargs.get( "port", None), "proto": kwargs.get("proto", None)}) if p_o is not None: targets[str(p_o.getId())] = {"ip": ip, "port": kwargs.get( "port", None), "proto": kwargs.get("proto", None)} if "VULNERABLE" in line: Defect().initialize(ip, kwargs.get("port", None), kwargs.get("proto", None), "Serveur vulnérable à BlueKeep", "Difficile", "Critique", "Critique", "N/A", ["Socle"], notes=notes, proofs=[]).addInDb() tags=["P0wned!"] if p_o is not None: p_o.addTag("P0wned!") ip_o = Ip.fetchObject({"ip": ip}) if ip_o is not None: ip_o.addTag("P0wned!") elif "UNKNOWN" in line: tags = ["todo"] notes += line return notes, tags, "port", targets
def getNotDoneTools(cls, waveName): """Returns a set of tool mongo ID that are not done yet. """ notDoneTools = set() mongoInstance = MongoCalendar.getInstance() tools = mongoInstance.find( "tools", { "wave": waveName, "ip": "", "scanner_ip": "None", "dated": "None", "datef": "None" }) for tool in tools: notDoneTools.add(tool["_id"]) scopes = Scope.fetchObjects({"wave": waveName}) for scope in scopes: scopeId = scope.getId() ips = Ip.getIpsInScope(scopeId) for ip in ips: tools = mongoInstance.find( "tools", { "wave": waveName, "ip": ip.ip, "scanner_ip": "None", "dated": "None", "datef": "None" }) for tool in tools: notDoneTools.add(tool["_id"]) return notDoneTools
def addInDb(self): """ Add this scope in database. Returns: a tuple with : * bool for success * mongo ObjectId : already existing object if duplicate, create object id otherwise """ base = self.getDbKey() # Checking unicity mongoInstance = MongoCalendar.getInstance() existing = mongoInstance.find("scopes", base, False) if existing is not None: return False, existing["_id"] # Inserting scope parent = self.getParent() res_insert = mongoInstance.insert("scopes", base, parent) ret = res_insert.inserted_id self._id = ret # adding the appropriate tools for this scope. wave = mongoInstance.find("waves", {"wave": self.wave}, False) commands = wave["wave_commands"] for commName in commands: if commName.strip() != "": self.addAllTool(commName) # Testing this scope against every ips ips = Ip.fetchObjects({}) for ip in ips: if self._id not in ip.in_scopes: if ip.fitInScope(self.scope): ip.addScopeFitting(self.getId()) return True, ret
def getIpsFitting(self): """Returns a list of ip mongo dict fitting this scope Returns: A list ip IP dictionnary from mongo db """ mongoInstance = MongoCalendar.getInstance() ips = mongoInstance.find("ips", ) ips_fitting = [] isdomain = self.isDomain() for ip in ips: if isdomain: my_ip = Utils.performLookUp(self.scope) my_domain = self.scope ip_isdomain = not Utils.isIp(ip["ip"]) if ip_isdomain: if my_domain == ip["ip"]: ips_fitting.append(ip) if Scope.isSubDomain(my_domain, ip["ip"]): ips_fitting.append(ip) else: if my_ip == ip["ip"]: ips_fitting.append(ip) else: if Ip.checkIpScope(self.scope, ip["ip"]): ips_fitting.append(ip) return ips_fitting
def delete(self): """ Delete the Scope represented by this model in database. Also delete the tools associated with this scope Also remove this scope from ips in_scopes attributes """ # deleting tool with scope lvl tools = Tool.fetchObjects({ "scope": self.scope, "wave": self.wave, "$or": [{ "lvl": "network" }, { "lvl": "domain" }] }) for tool in tools: tool.delete() # Deleting this scope against every ips ips = Ip.getIpsInScope(self._id) for ip in ips: ip.removeScopeFitting(self._id) mongoInstance = MongoCalendar.getInstance() mongoInstance.delete("scopes", {"_id": self._id}) parent_wave = mongoInstance.find("waves", {"wave": self.wave}, False) if parent_wave is None: return mongoInstance.notify(mongoInstance.calendarName, "waves", parent_wave["_id"], "update", "")
def loadData(self): """ Fetch data from database """ self.ports = Port.fetchObjects({}) self.ips = Ip.fetchObjects({}) self.tools = Tool.fetchObjects({})
def Parse(self, file_opened, **kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ targets = {} notes = file_opened.read() regex_ip = r"Nmap scan report for (\S+)" ip_group = re.search(regex_ip, notes) if ip_group is None: return None, None, None, None # Auto Detect: if "smb-vuln-ms17-010:" not in notes: return None, None, None, None # Parsing ip = ip_group.group(1).strip() Ip().initialize(ip).addInDb() port_re = r"(\d+)\/(\S+)\s+open\s+microsoft-ds" res_search = re.search(port_re, notes) res_insert = None if res_search is None: port = None proto = None else: port = res_search.group(1) proto = res_search.group(2) p_o = Port() p_o.initialize(ip, port, proto, "microsoft-ds") res_insert = p_o.addInDb() targets[str(p_o.getId())] = { "ip": ip, "port": port, "proto": proto } if "VULNERABLE" in notes: d_o = Defect() d_o.initialize(ip, port, proto, "Serveur vulnérable à EternalBlue", "Difficile", "Critique", "Critique", "N/A", ["Socle"], notes=notes, proofs=[]) d_o.addInDb() tags = ["P0wned!"] if res_insert is not None: p_o.addTag("P0wned!") return notes, tags, "port", targets
def addDomainInDb(self, checkDomain=True): """ Add this scope domain in database. Args: checkDomain: boolean. If true (Default), checks that the domain IP is in scope Returns: a tuple with : * bool for success * mongo ObjectId : already existing object if duplicate, create object id otherwise """ # Checking unicity base = self.getDbKey() mongoInstance = MongoCalendar.getInstance() existing = mongoInstance.find("scopes", base, False) if existing is not None: return 0, None # Check if domain's ip fit in one of the Scope of the wave if checkDomain: if not Scope.checkDomainFit(self.wave, self.scope): return -1, None # insert the domains in the scopes parent = self.getParent() res_insert = mongoInstance.insert("scopes", base, parent) ret = res_insert.inserted_id self._id = ret # Adding appropriate tools for this scopes wave = mongoInstance.find("waves", {"wave": self.wave}, False) commands = wave["wave_commands"] for commName in commands: comm = mongoInstance.findInDb("pollenisator", "commands", { "name": commName, "lvl": "network" }, False) if comm is not None: newTool = Tool() newTool.initialize(comm["name"], self.wave, self.scope, "", "", "", "network") newTool.addInDb() else: comm = mongoInstance.findInDb("pollenisator", "commands", { "name": commName, "lvl": "domain" }, False) if comm is not None: newTool = Tool() newTool.initialize(comm["name"], self.wave, self.scope, "", "", "", "domain") newTool.addInDb() # Testing this scope against every ips ips = Ip.fetchObjects({}) for ip in ips: if self._id not in ip.in_scopes: if ip.fitInScope(self.scope): ip.addScopeFitting(self.getId()) ipToInsert = Ip() ipToInsert.initialize(self.scope) ipToInsert.addInDb() return 1, ret
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ notes = "" tags = ["todo"] marker = "- scanning for subdomain..." markerFound = False countFound = 0 for line in file_opened: if marker == line.strip(): markerFound = True if not markerFound: continue ip, domain, alias = parse_knockpy_line(line) if ip is not None and domain is not None: # a domain has been found res, iid = Ip().initialize(domain).addInDb() if res: Ip().initialize(ip).addInDb() notes += line + "\n" countFound += 1 # failed, domain is out of scope else: notes += domain + " exists but already added.\n" ip_m = Ip.fetchObject({"_id": iid}) if alias: ip_m.updateInfos({"alias": ip}) if notes.strip() == "": return None, None, None, None return notes, tags, "wave", {"wave": None}
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 checkDomainFit(cls, waveName, domain): """ Check if a found domain belongs to one of the scope of the given wave. Args: waveName: The wave id (name) you want to search for a validating scope domain: The found domain. Returns: boolean """ # Checking settings for domain check. settings = Settings() # get the domain ip so we can search for it in ipv4 range scopes. domainIp = Utils.performLookUp(domain) mongoInstance = MongoCalendar.getInstance() scopesOfWave = mongoInstance.find("scopes", {"wave": waveName}) for scopeOfWave in scopesOfWave: scopeIsANetworkIp = Utils.isNetworkIp(scopeOfWave["scope"]) if scopeIsANetworkIp: if settings.db_settings.get("include_domains_with_ip_in_scope", False): if Ip.checkIpScope(scopeOfWave["scope"], domainIp): return True else: # If scope is domain # check if we include subdomains if settings.db_settings.get("include_all_domains", False): return True else: splitted_domain = domain.split(".") # Assuring to check only if there is a domain before the tld (.com, .fr ... ) topDomainExists = len(splitted_domain) > 2 if topDomainExists: if settings.db_settings.get( "include_domains_with_topdomain_in_scope", False): if splitted_domain[1:] == scopeOfWave[ "scope"].split("."): return True if settings.db_settings.get( "include_domains_with_ip_in_scope", False): inRangeDomainIp = Utils.performLookUp( scopeOfWave["scope"]) if str(inRangeDomainIp) == str(domainIp): return True return False
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ tags = ["todo"] data = file_opened.read() notes = "" if data.strip() == "": return None, None, None, None else: hosts = parse_dirsearch_file(data) if not hosts.keys(): return None, None, None, None targets = {} for host in hosts: Ip().initialize(host).addInDb() for port in hosts[host]: port_o = Port() port_o.initialize(host, port, "tcp", hosts[host][port]["service"]) res, iid = port_o.addInDb() if not res: port_o = Port.fetchObject({"_id": iid}) targets[str(port_o.getId())] = { "ip": host, "port": port, "proto": "tcp"} hosts[host][port]["paths"].sort(key=lambda x: int(x[0])) results = "\n".join(hosts[host][port]["paths"]) notes += results newInfos = {} for statuscode in hosts[host][port]: if isinstance(statuscode, int): if hosts[host][port].get(statuscode, []): newInfos["Dirsearch_"+str(statuscode) ] = hosts[host][port][statuscode] newInfos["SSL"] = "True" if hosts[host][port]["service"] == "https" else "False" port_o.updateInfos(newInfos) return notes, tags, "port", targets
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ notes = "" tags = ["todo"] countInserted = 0 markerSublister = "# Coded By Ahmed Aboul-Ela - @aboul3la" markerFound = False for line in file_opened: if markerSublister in line: markerFound = True if not markerFound: continue if line.startswith("\x1b[92m"): line = line[len("\x1b[92m"):] if line.endswith("\x1b[0m"): line = line[:-1 * len("\x1b[0m")] domainGroup = re.search( r"((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9])", line.strip()) if domainGroup is not None: # a domain has been found domain = domainGroup.group(1) inserted, _ = Ip().initialize(domain).addInDb() # failed, domain is out of wave, still noting thi if not inserted: notes += domain + " exists but already added.\n" else: countInserted += 1 notes += domain + " inserted.\n" if notes.strip() == "": return None, None, None, None if not markerFound: return None, None, None, None return notes, tags, "wave", {"wave": None}
def editScopeIPs(hostsInfos): """ Add all the ips and theirs ports found after parsing the file to the scope object in database. Args: hostsInfos: the dictionnary with ips as keys and a list of dictionnary containing ports informations as value. """ # Check if any ip has been found. if hostsInfos is not None: for infos in hostsInfos: tags = [] if infos.get("powned", False): tags.append("P0wned!") infosToAdd = {} OS = infos.get("OS", "") if OS != "": infosToAdd["OS"] = OS creds = infos.get("creds", "") if creds != "": infosToAdd["creds"] = creds powned = infos.get("powned", False) if powned: infosToAdd["powned"] = "True" ip_m = Ip().initialize(str(infos["ip"])) res, iid = ip_m.addInDb() if not res: ip_m = Ip.fetchObject({"_id": iid}) infosToAdd["hostname"] = list( set(ip_m.infos.get("hostname", []) + [infos["hostname"]])) ip_m.notes = "hostname:" + \ infos["hostname"] + "\n"+infos.get("OS", "") ip_m.tags = tags ip_m.update() port_m = Port().initialize(str(infos["ip"]), str(infos["port"]), "tcp", "netbios-ssn") res, iid = port_m.addInDb() if not res: port_m = Port.fetchObject({"_id": iid}) port_m.updateInfos(infosToAdd) port_m.tags = tags port_m.update()
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ notes = file_opened.read() targets = {} tags = [] if "| http-methods:" not in notes: return None, None, None, None host, port, proto, service, risky_methods, supported_methods = parse( notes) if host == "": return None, None, None, None Ip().initialize(host).addInDb() p_o = Port().initialize(host, port, proto, service) res, iid = p_o.addInDb() if not res: p_o = Port.fetchObject({"_id": iid}) p_o.updateInfos({"Methods": ", ".join(supported_methods)}) targets[str(p_o.getId())] = {"ip": host, "port": port, "proto": proto} if "TRACE" in risky_methods: Defect().initialize(host, port, proto, "Méthode TRACE activée", "Difficile", "Important", "Important", "N/A", ["Socle"], notes="TRACE detected", proofs=[]).addInDb() risky_methods.remove("TRACE") if len(risky_methods) > 0: notes = "RISKY HTTP METHODS ALLOWED : " + " ".join(risky_methods) tags = [] tags.append("Interesting") return notes, tags, "port", targets
def addInTreeview(self, parentNode=None, addChildren=True): """Add this view in treeview. Also stores infos in application treeview. Args: parentNode: if None, will calculate the parent. If setted, forces the node to be inserted inside given parentNode. _addChildren: not used here """ if parentNode is None: parentNode = self.getParent() ip_node = None try: if isinstance(parentNode, ObjectId): ip_parent_o = Ip.fetchObject({"_id": parentNode}) if ip_parent_o is not None: parent_view = IpView(self.appliTw, self.appliViewFrame, self.mainApp, IpController(ip_parent_o)) parent_view.addInTreeview(None, False) ip_node = self.appliTw.insert(parentNode, "end", str(self.controller.getDbId()), text=str( self.controller.getModelRepr()), tags=self.controller.getTags(), image=self.getClassIcon()) except TclError: pass self.appliTw.views[str(self.controller.getDbId())] = {"view": self} if addChildren and ip_node is not None: self._insertChildrenDefects() self._insertChildrenTools() self._insertChildrenPorts(ip_node) # self.appliTw.sort(parentNode) if "hidden" in self.controller.getTags(): self.hide() modelData = self.controller.getData() if not modelData["in_scopes"]: self.controller.addTag("OOS")
def loadSummary(self): """Reload information about IP and Port and reload the view. """ mongoInstance = MongoCalendar.getInstance() nonEmptyIps = list( mongoInstance.aggregate("ports", [{ "$group": { "_id": "$ip" } }, { "$count": "total" }])) if not nonEmptyIps: return nonEmptyIps = nonEmptyIps[0] step = 0 dialog = ChildDialogProgress( self.parent, "Loading summary ", "Refreshing summary. Please wait for a few seconds.", 200, "determinate") dialog.show(nonEmptyIps["total"]) nonEmptyIps = mongoInstance.aggregate("ports", [{ "$group": { "_id": "$ip" } }]) for ipCIDR in nonEmptyIps: step += 1 ip = Ip.fetchObject({"ip": ipCIDR["_id"]}) if ip.in_scopes: dialog.update(step) self.insertIp(ip.ip) self.frameTw.update_idletasks() self.parent.update_idletasks() smart_grid(self.frameTw, self.root, *list(self.treeviews.values())) dialog.destroy()
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ tags = ["todo"] targets = {} notes = file_opened.read() if notes == "": return None, None, None, None if not notes.startswith("- Nikto v"): return None, None, None, None host, port, service, infos = parse_nikto_plain_text(notes) if host: if port: Ip().initialize(host).addInDb() p_o = Port().initialize(host, port, "tcp", service) res, iid = p_o.addInDb() if not res: p_o = Port.fetchObject({"_id": iid}) p_o.updateInfos({ "Nikto": infos, "SSL": "True" if service == "https" else "False" }) targets[str(iid)] = {"ip": host, "port": port, "proto": "tcp"} return notes, tags, "port", targets
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information foe.test.fr. 801 IN A 18.19.20.21 blog.test.fr. 10800 IN CNAME 22.33.44.55 Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ notes = "" tags = [] countInserted = 0 for line in file_opened: domain, _record_type, ip = parse_crtsh_line(line) if domain is not None: # a domain has been found infosToAdd = {"hostname": ip} ip_m = Ip().initialize(domain, infos=infosToAdd) res, iid = ip_m.addInDb() # failed, domain is out of scope if not res: notes += domain + " exists but already added.\n" ip_m = Ip.fetchObject({"_id": iid}) infosToAdd = { "hostname": list(set([ip] + ip_m.infos.get("hostname", []))) } ip_m.updateInfos(infosToAdd) else: countInserted += 1 notes += domain + " inserted.\n" if notes.strip() == "": return None, None, None, None elif countInserted != 0: tags.append("todo") return notes, tags, "wave", {"wave": None}
def getIpPortsNmap(nmapFile): """ Read the given nmap .nmap file results and return a dictionnary with ips and a list of their open ports. Args: nmapFile: the path to the .nmap file generated by an nmap scan Returns: notes about inseted ip and ports """ notes = "" countOpen = 0 all_text = nmapFile.read().strip() lines = all_text.split("\n") if len(lines) <= 3: # print("Not enough lines to be nmap") return None if not lines[0].startswith("# Nmap"): # print("Not starting with # Nmap") return None if "scan initiated" not in lines[0]: # print("Not scan initiated on first line") return None if "# Nmap done at" not in lines[-1]: # print("Not # Nmap done at at the end : "+str(lines[-1])) return None ipCIDR_m = None ipDom_m = None for line in lines: # Search ip in file # match an ip ip = re.search( r"^Nmap scan report for (\S+)(?: \(((?:[0-9]{1,3}\.){3}[0-9]{1,3})\))?$", line) if ip is not None: # regex match lastIp = [ ip.group(1), ip.group(2) if ip.group(2) is not None else "" ] notes_ip = "ip:" + \ str(lastIp[1]) if lastIp[1] != "" and lastIp[1] is not None else "" ipCIDR_m = Ip().initialize(str(lastIp[0]), notes=notes_ip) if lastIp[1].strip() != "" and lastIp[1] is not None: ipDom_m = Ip().initialize(str(lastIp[1]), notes="domain:" + str(lastIp[0])) else: ipDom_m = None if " open " in line: if ipCIDR_m is None: # Probably a gnmap return None notes += line + "\n" # regex to find open ports in gnmap file port_search = re.search( r"^(\d+)\/(\S+)\s+open\s+(\S+)(?: +(.+))?$", line) if port_search is not None: port_number = str(port_search.group(1)) proto = str(port_search.group(2)) service = "unknown" if str( port_search.group(3)) == "" else str(port_search.group(3)) product = str(port_search.group(4)) # a port unique key is its protocole/number. countOpen += 1 validIps = [] if ipCIDR_m is not None: ipCIDR_m.addInDb() validIps.append(ipCIDR_m.ip) if ipDom_m is not None: res, iid = ipDom_m.addInDb() if not res: ipDom_m = Ip.fetchObject({"_id": iid}) ipDom_m.updateInfos({ "hostname": list( set( list(ipDom_m.infos.get("hostname", [])) + [str(ipCIDR_m.ip)])) }) validIps.append(ipDom_m.ip) for ipFound in validIps: if ip == "": continue port_o = Port().initialize(ipFound, port_number, proto, service, product) res_insert, iid = port_o.addInDb() if not res_insert: port_o = Port.fetchObject({"_id": iid}) port_o.service = service port_o.update() notes = str(countOpen) + " open ports found\n" + notes return notes
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ notes = "" tags = [] content = file_opened.read() targets = {} try: notes_json = json.loads(content) except json.decoder.JSONDecodeError: return None, None, None, None oneScanIsValid = False for scan in notes_json: try: if scan.get('ssh_scan_version', None) is None: continue ips = [scan["hostname"], scan["ip"]] port = str(scan["port"]) for ip in ips: if ip.strip() == "": continue Ip().initialize(ip).addInDb() port_o = Port().initialize(ip, port, "tcp", "ssh") res, iid = port_o.addInDb() if not res: port_o = Port.fetchObject({"_id": iid}) notes = "\n".join(scan["compliance"].get( "recommendations", [])) targets[str(port_o.getId())] = { "ip": ip, "port": port, "proto": "tcp" } oneScanIsValid = True if "nopassword" in scan["auth_methods"]: tags = ["P0wned!"] # Will not exit if port was not ssh is_ok = scan["compliance"]["compliant"] if str(is_ok) == "False": port_o.updateInfos({"compliant": "False"}) port_o.updateInfos( {"auth_methods": scan["auth_methods"]}) Defect().initialize( ip, port, "tcp", "Défauts d’implémentation de la configuration SSH", "Très difficile", "Majeur", "Important", "N/A", ["Socle"], notes=notes, proofs=[]).addInDb() except KeyError: continue if not oneScanIsValid: return None, None, None, None return notes, tags, "port", targets
def Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ tags = ["todo"] targets = {} notes = file_opened.read() if notes == "": return None, None, None, None try: data = json.loads(notes) except json.decoder.JSONDecodeError: return None, None, None, None regex_host = r"https?://([^\/]+)" oneValidWhatweb = False for website in data: keys = website.keys() expected_keys = [ 'target', 'http_status', 'request_config', 'plugins' ] all_keys = True for expected in expected_keys: if expected not in keys: all_keys = False break if not all_keys: continue host_port_groups = re.search(regex_host, website["target"]) if host_port_groups is None: continue host_port = host_port_groups.group(1) service = "https" if "https://" in website["target"] else "http" if len(host_port.split(":")) == 2: port = host_port.split(":")[1] host = host_port.split(":")[0] else: host = host_port port = "443" if "https://" in website["target"] else "80" Ip().initialize(host).addInDb() p_o = Port().initialize(host, port, "tcp", service) inserted, iid = p_o.addInDb() if not inserted: p_o = Port.fetchObject({"_id": iid}) infosToAdd = {"URL": website["target"]} for plugin in website.get("plugins", {}): item = website["plugins"][plugin].get("string") if isinstance(item, list): if item: infosToAdd[plugin] = item else: item = str(item) if item != "": infosToAdd[plugin] = item p_o.updateInfos(infosToAdd) targets[str(p_o.getId())] = { "ip": host, "port": port, "proto": "tcp" } oneValidWhatweb = True if not oneValidWhatweb: return None, None, None, None return notes, tags, "port", targets
def parseWarnings(file_opened): """ Parse the result of a testssl json output file Args: file_opened: the opened file reference Returns: Returns a tuple with (None values if not matching a testssl output): - a list of string for each testssl NOT ok, WARN, or MEDIUM warnings - a dict of targeted objects with database id as key and a unique key as a mongo search pipeline ({}) """ targets = {} missconfiguredHosts = {} firstLine = True for line in file_opened: if firstLine: if line.strip() != '"id", "fqdn/ip", "port", "severity", "finding", "cve", "cwe"' and \ line.strip() != '"id","fqdn/ip","port","severity","finding","cve","cwe"': return None, None firstLine = False continue # Search ip in file warn = re.search( r"^\"[^\"]*\", ?\"([^\"]*)\", ?\"([^\"]*)\", ?\"(OK|INFO|NOT ok|WARN|LOW|MEDIUM|HIGH|CRITICAL)\", ?\"[^\"]*\", ?\"[^\"]*\", ?\"[^\"]*\"$", line) if warn is not None: ip = warn.group(1) domain = None port = warn.group(2) notes = warn.group(3) if "/" in ip: domain = ip.split("/")[0] ip = "/".join(ip.split("/")[1:]) Ip().initialize(domain).addInDb() Port().initialize(domain, port, "tcp", "ssl").addInDb() Ip().initialize(ip).addInDb() Port().initialize(ip, port, "tcp", "ssl").addInDb() if notes not in ["OK", "INFO"]: missconfiguredHosts[ip] = missconfiguredHosts.get(ip, {}) missconfiguredHosts[ip][port] = missconfiguredHosts[ip].get( port, []) missconfiguredHosts[ip][port].append(notes + " : " + line) if domain is not None: missconfiguredHosts[domain] = missconfiguredHosts.get( domain, {}) missconfiguredHosts[domain][port] = missconfiguredHosts[ domain].get(port, []) missconfiguredHosts[domain][port].append(notes + " : " + line) for ip in missconfiguredHosts.keys(): for port in missconfiguredHosts[ip].keys(): p_o = Port.fetchObject({"ip": ip, "port": port, "proto": "tcp"}) targets[str(p_o.getId())] = { "ip": ip, "port": port, "proto": "tcp" } missconfiguredHosts[ip][port].sort() notes = "\n".join(missconfiguredHosts[ip][port]) res, _ = Defect().initialize(ip, port, "tcp", "Défauts d'implémentation du SSL/TLS", "Très difficile", "Majeur", "Important", "N/A", ["Socle"], notes=notes, proofs=[]).addInDb() if not res: p_o.updateInfos({"compliant": "False"}) defect_o = Defect.fetchObject({ "ip": ip, "title": "Défauts d'implémentation du SSL/TLS", "port": port, "proto": "tcp" }) defect_o.notes += notes defect_o.update() if firstLine: return None, None return str(len(missconfiguredHosts.keys()) ) + " misconfigured hosts found. Defects created.", targets
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()
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 Parse(self, file_opened, **_kwargs): """ Parse a opened file to extract information Example: [ { "arguments": "./dnsrecon.py -r 10.0.0.0/24 -j /home/barre/test.json", "date": "2020-01-06 11:43:37.701513", "type": "ScanInfo" }, { "address": "10.0.0.1", "name": "_gateway", "type": "PTR" }, { "address": "10.0.0.77", "name": "barre-ThinkPad-E480", "type": "PTR" }, { "address": "10.0.0.77", "name": "barre-ThinkPad-E480.local", "type": "PTR" } ] Args: file_opened: the open file _kwargs: not used Returns: a tuple with 4 values (All set to None if Parsing wrong file): 0. notes: notes to be inserted in tool giving direct info to pentester 1. tags: a list of tags to be added to tool 2. lvl: the level of the command executed to assign to given targets 3. targets: a list of composed keys allowing retrieve/insert from/into database targerted objects. """ notes = "" tags = [] countInserted = 0 try: dnsrecon_content = json.loads(file_opened.read()) except json.decoder.JSONDecodeError: return None, None, None, None if len(dnsrecon_content) == 0: return None, None, None, None if not isinstance(dnsrecon_content[0], dict): return None, None, None, None if dnsrecon_content[0].get("type", "") != "ScanInfo": return None, None, None, None if dnsrecon_content[0].get("date", "") == "": return None, None, None, None for record in dnsrecon_content[1:]: ip = record["address"] name = record["name"] infosToAdd = {"hostname": name} ip_m = Ip().initialize(ip, infos=infosToAdd) res, iid = ip_m.addInDb() infosToAdd = {"ip": ip} ip_m = Ip().initialize(name, infos=infosToAdd) res, iid = ip_m.addInDb() # failed, domain is out of scope if not res: notes += name + " exists but already added.\n" ip_m = Ip.fetchObject({"_id": iid}) infosToAdd = {"ip": list(set([ip] + ip_m.infos.get("ip", [])))} ip_m.updateInfos(infosToAdd) else: countInserted += 1 notes += name + " inserted.\n" return notes, tags, "wave", {"wave": None}
def smbmap_format(smbmap_output): """Parse raw smbmap file Args: smbmap_output: raw smbmap file content Returns: A tuple with values: 0. a string formated where each line are : fullpath permissions date 1. a list of interesting name file found 2. targets as described in Parse """ targets = {} regex_only_match_serv = re.compile( r"^\[\+\] IP: ([^:\s]+)(?::\d+)?\sName: \S+\s+$") # group 1 = SHARE NAME group 2 = PERMISSIONS regex_only_match_share_header = re.compile(r"^\t(\S+)\s+([A-Z , ]+)$") # group 1 = directory full path regex_only_match_dir = re.compile(r"^\t(\S+)$") # group 1 = d ou -, group 2 = permissions, group 3 = date, group 4 = filename regex_only_match_files = re.compile( r"^\s+([d-])([xrw-]{9})\s+([^\t]+)\t(.+)$") ret = "" interesting_files = {} interesting_name_list = [ "passwd", "password", "pwd", "mot_de_passe", "motdepasse", "auth", "creds", "confidentiel", "confidential", "backup", ".xml", ".conf", ".cfg", "unattended" ] current_serv = "" current_share = "" current_dir = "" ip_states = [] lines = smbmap_output.split("\n") for line in lines: result = regex_only_match_serv.match(line) if result is not None: ip = str(result.group(1)) current_serv = ip if ip not in ip_states: ip_o = Ip().initialize(ip) ip_o.addInDb() Port().initialize(ip, "445", "tcp", "samba").addInDb() targets[str(ip_o.getId())] = { "ip": ip, "port": "445", "proto": "tcp" } ip_states.append(ip) continue result = regex_only_match_share_header.match(line) if result is not None: share_name = str(result.group(1)) permissions = str(result.group(2)) current_share = share_name continue result = regex_only_match_dir.match(line) if result is not None: dir_path = str(result.group(1)) current_dir = dir_path continue result = regex_only_match_files.match(line) if result is not None: # isDirectory = (str(result.group(1)) == "d") permissions = str(result.group(2)) date = str(result.group(3)) file_name = str(result.group(4)) fullpath = "\\\\"+current_serv+"\\" + \ current_share+current_dir[1:]+file_name for interesting_names in interesting_name_list: if interesting_names.lower() in file_name.lower(): interesting_files[ "Shared " + interesting_names] = interesting_files.get( "Shared " + interesting_names, []) interesting_files["Shared " + interesting_names].append(fullpath + "\t" + permissions + "\t" + date) ret += fullpath + "\t" + permissions + "\t" + date + "\n" continue return ret, interesting_files, targets