Update upstream source from tag 'upstream/1.5.9'
Update to upstream version '1.5.9'
with Debian dir 012d2b2b9bf5fd36e6e6ad661fdf5731d90b68c5
Sophie Brun
2 years ago
0 | Nov 19th, 2021 |
0 | FIX extrainfo of netsparker plugin |
0 | Add nuclei_legacy plugin |
0 | Add CVE to plugins | |
1 | - acunetix | |
2 | - appscan | |
3 | - burp | |
4 | - metasploit | |
5 | - nessus | |
6 | - netsparker | |
7 | - nexpose | |
8 | - nikto | |
9 | - nipper | |
10 | - nmap | |
11 | - openscap | |
12 | - qualysguard | |
13 | - retina | |
14 | - shodan |
0 | Add support for Sslyze 5.0 resports⏎ |
0 | Fix errors while creating hosts with wrong regex⏎ |
0 | ADD masscan support to nmap plugin |
0 | Dec 13th, 2021 |
0 | Fix bug in openvas plugin⏎ |
0 | Add cve in faraday_csv plugin⏎ |
0 | ADD Grype plugin |
0 | Dec 27th, 2021 |
154 | 154 | ... |
155 | 155 | ``` |
156 | 156 | |
157 | More documentation here https://github.com/infobyte/faraday/wiki/Basic-plugin-development | |
157 | More documentation here https://docs.faradaysec.com/Basic-plugin-development/ |
0 | 1.5.9 [Dec 27th, 2021]: | |
1 | --- | |
2 | * ADD cve in faraday_csv plugin | |
3 | * ADD Grype plugin | |
4 | ||
5 | 1.5.8 [Dec 13th, 2021]: | |
6 | --- | |
7 | * Add CVE to plugins | |
8 | - acunetix | |
9 | - appscan | |
10 | - burp | |
11 | - metasploit | |
12 | - nessus | |
13 | - netsparker | |
14 | - nexpose | |
15 | - nikto | |
16 | - nipper | |
17 | - nmap | |
18 | - openscap | |
19 | - qualysguard | |
20 | - retina | |
21 | - shodan | |
22 | * Add support for Sslyze 5.0 resports | |
23 | * Fix errors while creating hosts with wrong regex | |
24 | * ADD masscan support to nmap plugin | |
25 | * Fix bug in openvas plugin | |
26 | ||
27 | 1.5.7 [Nov 19th, 2021]: | |
28 | --- | |
29 | * FIX extrainfo of netsparker plugin | |
30 | * Add nuclei_legacy plugin | |
31 | ||
0 | 32 | 1.5.6 [Nov 10th, 2021]: |
1 | 33 | --- |
2 | 34 | * FIX issue with acunetix plugin |
35 | ||
3 | 36 | * FIX typo in nikto plugin |
4 | 37 | |
5 | 38 | 1.5.5 [Oct 21st, 2021]: |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | # Standard library imports | |
6 | 7 | import hashlib |
7 | 8 | import logging |
8 | 9 | import os |
15 | 16 | from datetime import datetime |
16 | 17 | from pathlib import Path |
17 | 18 | |
19 | # Related third party imports | |
18 | 20 | import pytz |
19 | 21 | import simplejson as json |
22 | ||
23 | # Local application imports | |
24 | from faraday_plugins.plugins.plugins_utils import its_cve | |
20 | 25 | |
21 | 26 | logger = logging.getLogger("faraday").getChild(__name__) |
22 | 27 | |
30 | 35 | |
31 | 36 | def __init__(self, ignore_info=False): |
32 | 37 | # Must be unique. Check that there is not |
33 | # an existant plugin with the same id. | |
38 | # an existent plugin with the same id. | |
34 | 39 | # TODO: Make script that list current ids. |
35 | 40 | self.ignore_info = ignore_info |
36 | 41 | self.id = None |
84 | 89 | utc_date = date.astimezone(pytz.UTC) |
85 | 90 | return utc_date.timestamp() |
86 | 91 | except Exception as e: |
87 | logger.error("Error generating timestamp: %s", e) | |
92 | logger.error(f"Error generating timestamp: {e}") | |
88 | 93 | return None |
89 | 94 | else: |
90 | 95 | return date |
252 | 257 | |
253 | 258 | def canParseCommandString(self, current_input): |
254 | 259 | """ |
255 | This method can be overriden in the plugin implementation | |
260 | This method can be overridden in the plugin implementation | |
256 | 261 | if a different kind of check is needed |
257 | 262 | """ |
258 | 263 | if (self._command_regex is not None and |
287 | 292 | |
288 | 293 | def getCompletitionSuggestionsList(self, current_input): |
289 | 294 | """ |
290 | This method can be overriden in the plugin implementation | |
295 | This method can be overridden in the plugin implementation | |
291 | 296 | if a different kind of check is needed |
292 | 297 | """ |
293 | 298 | words = current_input.split(" ") |
314 | 319 | elif filename.is_dir(): |
315 | 320 | shutil.rmtree(filename) |
316 | 321 | except Exception as e: |
317 | self.logger.error("Error on delete file: (%s) [%s]", filename, e) | |
322 | self.logger.error(f"Error on delete file: ({filename}) [{e}]") | |
318 | 323 | |
319 | 324 | def processReport(self, filepath: Path, user="faraday"): |
320 | 325 | if type(filepath) == str: # TODO workaround for compatibility, remove in the future |
383 | 388 | def createAndAddVulnToHost(self, host_id, name, desc="", ref=None, |
384 | 389 | severity="", resolution="", data="", external_id=None, run_date=None, |
385 | 390 | impact=None, custom_fields=None, status="", policyviolations=None, |
386 | easeofresolution=None, confirmed=False, tags=None): | |
391 | easeofresolution=None, confirmed=False, tags=None, cve=None): | |
387 | 392 | if ref is None: |
388 | 393 | ref = [] |
389 | 394 | if status == "": |
398 | 403 | tags = [] |
399 | 404 | if isinstance(tags, str): |
400 | 405 | tags = [tags] |
406 | if cve is None: | |
407 | cve = [] | |
408 | elif type(cve) is str: | |
409 | cve = [cve] | |
410 | cve = its_cve(cve) | |
401 | 411 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, |
402 | 412 | "external_id": external_id, "type": "Vulnerability", "resolution": resolution, "data": data, |
403 | 413 | "custom_fields": custom_fields, "status": status, "impact": impact, |
404 | "policyviolations": policyviolations, | |
414 | "policyviolations": policyviolations, "cve": cve, | |
405 | 415 | "confirmed": confirmed, "easeofresolution": easeofresolution, "tags": tags |
406 | 416 | } |
407 | 417 | if run_date: |
412 | 422 | def createAndAddVulnToService(self, host_id, service_id, name, desc="", |
413 | 423 | ref=None, severity="", resolution="", data="", external_id=None, run_date=None, |
414 | 424 | custom_fields=None, policyviolations=None, impact=None, status="", |
415 | confirmed=False, easeofresolution=None, tags=None): | |
425 | confirmed=False, easeofresolution=None, tags=None, cve=None): | |
416 | 426 | if ref is None: |
417 | 427 | ref = [] |
418 | 428 | if status == "": |
427 | 437 | tags = [] |
428 | 438 | if isinstance(tags, str): |
429 | 439 | tags = [tags] |
440 | if cve is None: | |
441 | cve = [] | |
442 | elif type(cve) is str: | |
443 | cve = [cve] | |
444 | cve = its_cve(cve) | |
430 | 445 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, |
431 | 446 | "external_id": external_id, "type": "Vulnerability", "resolution": resolution, "data": data, |
432 | 447 | "custom_fields": custom_fields, "status": status, "impact": impact, |
433 | "policyviolations": policyviolations, | |
448 | "policyviolations": policyviolations, "cve": cve, | |
434 | 449 | "easeofresolution": easeofresolution, "confirmed": confirmed, "tags": tags |
435 | 450 | } |
436 | 451 | if run_date: |
445 | 460 | params="", query="", category="", data="", external_id=None, |
446 | 461 | confirmed=False, status="", easeofresolution=None, impact=None, |
447 | 462 | policyviolations=None, status_code=None, custom_fields=None, run_date=None, |
448 | tags=None): | |
463 | tags=None, cve=None): | |
449 | 464 | if params is None: |
450 | 465 | params = "" |
451 | 466 | if response is None: |
480 | 495 | tags = [] |
481 | 496 | if isinstance(tags, str): |
482 | 497 | tags = [tags] |
498 | if cve is None: | |
499 | cve = [] | |
500 | elif type(cve) is str: | |
501 | cve = [cve] | |
502 | cve = its_cve(cve) | |
483 | 503 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, |
484 | 504 | "external_id": external_id, "type": "VulnerabilityWeb", "resolution": resolution, |
485 | 505 | "data": data, "website": website, "path": path, "request": request, "response": response, |
486 | 506 | "method": method, "pname": pname, "params": params, "query": query, "category": category, |
487 | 507 | "confirmed": confirmed, "status": status, "easeofresolution": easeofresolution, |
488 | "impact": impact, "policyviolations": policyviolations, | |
508 | "impact": impact, "policyviolations": policyviolations, "cve": cve, | |
489 | 509 | "status_code": status_code, "custom_fields": custom_fields, "tags": tags} |
490 | 510 | if run_date: |
491 | 511 | vulnerability["run_date"] = self.get_utctimestamp(run_date) |
582 | 602 | match = (self.extension == extension) |
583 | 603 | elif type(self.extension) == list: |
584 | 604 | match = (extension in self.extension) |
585 | self.logger.debug("Extension Match: [%s =/in %s] -> %s", extension, self.extension, match) | |
605 | self.logger.debug(f"Extension Match: [{extension} =/in {self.extension}] -> {match}") | |
586 | 606 | return match |
587 | 607 | |
588 | 608 | |
604 | 624 | match = (main_tag in self.identifier_tag) |
605 | 625 | if self.identifier_tag_attributes: |
606 | 626 | match = self.identifier_tag_attributes.issubset(main_tag_attributes) |
607 | self.logger.debug("Tag Match: [%s =/in %s] -> %s", main_tag, self.identifier_tag, match) | |
627 | self.logger.debug(f"Tag Match: [{main_tag} =/in {self.identifier_tag}] -> {match}") | |
608 | 628 | return match |
609 | 629 | |
610 | 630 | |
621 | 641 | if file_json_keys is None: |
622 | 642 | file_json_keys = set() |
623 | 643 | match = self.json_keys.issubset(file_json_keys) |
624 | self.logger.debug("Json Keys Match: [%s =/in %s] -> %s", file_json_keys, self.json_keys, match) | |
644 | self.logger.debug(f"Json Keys Match: [{file_json_keys} =/in {self.json_keys}] -> {match}") | |
625 | 645 | return match |
626 | 646 | |
627 | 647 | |
643 | 663 | matched_lines = list(filter(lambda json_line: self.json_keys.issubset(json_line.keys()), |
644 | 664 | json_lines)) |
645 | 665 | match = len(matched_lines) == len(json_lines) |
646 | self.logger.debug("Json Keys Match: [%s =/in %s] -> %s", json_lines[0].keys(), self.json_keys, | |
647 | match) | |
666 | self.logger.debug(f"Json Keys Match: [{json_lines[0].keys()} =/in {self.json_keys}] -> {match}") | |
648 | 667 | except ValueError: |
649 | 668 | return False |
650 | 669 | return match |
666 | 685 | match = bool(list(filter(lambda x: x.issubset(file_csv_headers), self.csv_headers))) |
667 | 686 | else: |
668 | 687 | match = self.csv_headers.issubset(file_csv_headers) |
669 | self.logger.debug("CSV Headers Match: [%s =/in %s] -> %s", file_csv_headers, self.csv_headers, match) | |
688 | self.logger.debug(f"CSV Headers Match: [{file_csv_headers} =/in {self.csv_headers}] -> {match}") | |
670 | 689 | return match |
671 | 690 | |
672 | 691 | |
687 | 706 | if files_in_zip is None: |
688 | 707 | files_in_zip = set() |
689 | 708 | match = bool(self.files_list & files_in_zip) |
690 | self.logger.debug("Files List Match: [%s =/in %s] -> %s", files_in_zip, self.files_list, match) | |
709 | self.logger.debug(f"Files List Match: [{files_in_zip} =/in {self.files_list}] -> {match}") | |
691 | 710 | return match |
0 | 0 | """ |
1 | 1 | Faraday Penetration Test IDE |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | Copyright (C) 2013 Infobyte LLC (https://www.faradaysec.com/) | |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | # Standard library imports | |
6 | 7 | import logging |
7 | 8 | import os |
8 | 9 | import socket |
10 | import re | |
9 | 11 | from urllib.parse import urlsplit |
10 | 12 | |
11 | 13 | SERVICE_MAPPER = None |
12 | ||
14 | CVE_regex = re.compile(r'CVE-\d{4}-\d{4,7}') | |
13 | 15 | logger = logging.getLogger(__name__) |
14 | 16 | |
15 | 17 | |
124 | 126 | return severity |
125 | 127 | except ValueError: |
126 | 128 | return 'unclassified' |
129 | ||
130 | ||
131 | def its_cve(cves: list): | |
132 | r = [cve for cve in cves if CVE_regex.match(cve)] | |
133 | return r |
6 | 6 | |
7 | 7 | @property |
8 | 8 | def request(self) -> str: |
9 | if not self.node: | |
9 | if self.node in ('', None): | |
10 | 10 | return '' |
11 | 11 | return self.node.findtext('Request', '') |
12 | 12 | |
13 | 13 | @property |
14 | 14 | def response(self) -> str: |
15 | if not self.node: | |
15 | if self.node in ('', None): | |
16 | 16 | return '' |
17 | 17 | return self.node.findtext('Response', '') |
18 | 18 | |
19 | 19 | |
20 | class Cve: | |
21 | def __init__(self, node): | |
22 | self.node = node | |
23 | ||
24 | @property | |
25 | def text(self) -> str: | |
26 | if self.node in ('', None): | |
27 | return '' | |
28 | return self.node.text | |
29 | ||
30 | ||
31 | class CVEList: | |
32 | def __init__(self, node): | |
33 | self.node = node | |
34 | ||
35 | @property | |
36 | def cve(self) -> Cve: | |
37 | if self.node is None: | |
38 | return '' | |
39 | return Cve(self.node.find('CVE')) | |
40 | ||
41 | ||
20 | 42 | class Cwe: |
21 | 43 | def __init__(self, node): |
22 | 44 | self.node = node |
253 | 275 | |
254 | 276 | @property |
255 | 277 | def cvelist(self): |
256 | return self.node.find('CVEList') | |
278 | return CVEList(self.node.find('CVEList')) | |
257 | 279 | |
258 | 280 | @property |
259 | 281 | def cvss(self) -> Cvss: |
54 | 54 | parser = etree.XMLParser(recover=True) |
55 | 55 | tree = etree.fromstring(xml_output, parser=parser) |
56 | 56 | except SyntaxError as err: |
57 | print("SyntaxError: %s. %s", err, xml_output) | |
57 | print(f"SyntaxError: {err}. {xml_output}") | |
58 | 58 | return None |
59 | 59 | |
60 | 60 | return tree |
144 | 144 | description += f'\nPath: {item.affects}' |
145 | 145 | if item.parameter: |
146 | 146 | description += f'\nParameter: {item.parameter}' |
147 | try: | |
148 | cve = [item.cvelist.cve.text if item.cvelist.cve else ""] | |
149 | except Exception: | |
150 | cve = [""] | |
147 | 151 | self.createAndAddVulnWebToService( |
148 | 152 | h_id, |
149 | 153 | s_id, |
156 | 160 | params=item.parameter, |
157 | 161 | request=item.technicaldetails.request, |
158 | 162 | response=item.technicaldetails.response, |
159 | ref=[i.url for i in item.references.reference]) | |
163 | ref=[i.url for i in item.references.reference], | |
164 | cve=cve) | |
160 | 165 | |
161 | 166 | @staticmethod |
162 | 167 | def get_domain(scan: Scan): |
55 | 55 | type_id = item.attrib['id'] |
56 | 56 | name = item.find("name").text |
57 | 57 | issue_types[type_id] = name |
58 | cve = item.find("cve") | |
59 | if cve and cve.text: | |
60 | issue_types[f"{type_id}_cve"] = cve.text | |
58 | 61 | return issue_types |
59 | 62 | |
60 | 63 | @staticmethod |
110 | 113 | dast_issues = [] |
111 | 114 | for item in tree: |
112 | 115 | entity = self.entities[item.find("entity/ref").text] |
113 | host = entity["host"] | |
116 | host = entity["host"].replace('\\','/') | |
114 | 117 | port = entity["port"] |
115 | 118 | name = self.issue_types[item.find("issue-type/ref").text] |
116 | 119 | severity = 0 if item.find("severity-id") is None else int(item.find("severity-id").text) |
138 | 141 | else: |
139 | 142 | cve = None |
140 | 143 | cve_url = None |
144 | if cve is None: | |
145 | cve = self.issue_types.get(f"{item.find('issue-type/ref').text}_cve", None) | |
141 | 146 | host_key = f"{host}-{port}" |
142 | 147 | issue_data = { |
143 | 148 | "host": host, |
153 | 158 | "response": response, |
154 | 159 | "website": entity['website'], |
155 | 160 | "path": entity['path'], |
156 | "external_id": cve | |
161 | "cve": [] | |
157 | 162 | } |
158 | 163 | if cve: |
159 | issue_data["ref"].append(cve) | |
164 | issue_data["cve"].append(cve) | |
160 | 165 | if cve_url: |
161 | 166 | issue_data["ref"].append(cve_url) |
162 | 167 | if cwe: |
176 | 181 | sast_issues = [] |
177 | 182 | for item in tree: |
178 | 183 | name = self.issue_types[item.find("issue-type/ref").text] |
179 | source_file = item.attrib["filename"] | |
184 | source_file = item.attrib["filename"].replace('\\','/') | |
180 | 185 | severity = 0 if item.find("severity-id") is None else int(item.find("severity-id").text) |
181 | 186 | description = item.find("fix/item/general/text").text |
182 | 187 | resolution = "" if item.find("variant-group/item/issue-information/fix-resolution-text") is None \ |
209 | 214 | "desc": description, |
210 | 215 | "ref": [], |
211 | 216 | "resolution": resolution, |
212 | "external_id": cve | |
217 | "cve": [] | |
213 | 218 | } |
219 | ||
214 | 220 | if cve: |
215 | issue_data["ref"].append(cve) | |
221 | issue_data["cve"].append(cve) | |
216 | 222 | if cve_url: |
217 | 223 | issue_data["ref"].append(cve_url) |
218 | 224 | if cwe: |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import socket | |
7 | import json | |
8 | from datetime import datetime | |
9 | import re | |
10 | from faraday_plugins.plugins.plugin import PluginMultiLineJsonFormat | |
11 | ||
12 | __author__ = "Blas Moyano" | |
13 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
14 | __credits__ = ["Blas Moyano"] | |
15 | __license__ = "" | |
16 | __version__ = "0.0.1" | |
17 | __maintainer__ = "Blas Moyano" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class AwsProwlerJsonParser: | |
23 | ||
24 | def __init__(self, json_output): | |
25 | string_manipulate = json_output.replace("}", "} #") | |
26 | string_manipulate = string_manipulate[:len(string_manipulate) - 2] | |
27 | self.report_aws = string_manipulate.split("#") | |
28 | ||
29 | ||
30 | class AwsProwlerPlugin(PluginMultiLineJsonFormat): | |
31 | """ Handle the AWS Prowler tool. Detects the output of the tool | |
32 | and adds the information to Faraday. | |
33 | """ | |
34 | ||
35 | def __init__(self, *arg, **kwargs): | |
36 | super().__init__(*arg, **kwargs) | |
37 | self.id = "awsprowler" | |
38 | self.name = "AWS Prowler" | |
39 | self.plugin_version = "0.1" | |
40 | self.version = "0.0.1" | |
41 | self.json_keys = {"Profile", "Account Number"} | |
42 | ||
43 | ||
44 | def parseOutputString(self, output, debug=False): | |
45 | parser = AwsProwlerJsonParser(output) | |
46 | region_list = [] | |
47 | for region in parser.report_aws: | |
48 | json_reg = json.loads(region) | |
49 | region_list.append(json_reg.get('Region', 'Not Info')) | |
50 | ||
51 | host_id = self.createAndAddHost(name=f'{self.name} - {region_list}', description="AWS Prowler") | |
52 | ||
53 | for vuln in parser.report_aws: | |
54 | json_vuln = json.loads(vuln) | |
55 | vuln_name = json_vuln.get('Control', 'Not Info') | |
56 | vuln_desc = json_vuln.get('Message', 'Not Info') | |
57 | vuln_severity = json_vuln.get('Level', 'Not Info') | |
58 | vuln_run_date = json_vuln.get('Timestamp', 'Not Info') | |
59 | vuln_external_id = json_vuln.get('Control ID', 'Not Info') | |
60 | vuln_policy = f'{vuln_name}:{vuln_external_id}' | |
61 | vuln_run_date = vuln_run_date.replace('T', ' ') | |
62 | vuln_run_date = vuln_run_date.replace('Z', '') | |
63 | self.createAndAddVulnToHost(host_id=host_id, name=vuln_name, desc=vuln_desc, | |
64 | severity=self.normalize_severity(vuln_severity), | |
65 | run_date=datetime.strptime(vuln_run_date, '%Y-%m-%d %H:%M:%S'), | |
66 | external_id=vuln_external_id, policyviolations=[vuln_policy]) | |
67 | ||
68 | ||
69 | def createPlugin(ignore_info=False): | |
70 | return AwsProwlerPlugin(ignore_info=ignore_info) |
5 | 5 | """ |
6 | 6 | import base64 |
7 | 7 | import distutils.util # pylint: disable=import-error |
8 | import re | |
8 | 9 | import xml.etree.ElementTree as ET |
9 | 10 | from urllib.parse import urlsplit |
10 | 11 | |
11 | 12 | from bs4 import BeautifulSoup, Comment |
12 | 13 | |
13 | 14 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
15 | from faraday_plugins.plugins.plugins_utils import CVE_regex | |
14 | 16 | |
15 | 17 | __author__ = "Francisco Amato" |
16 | 18 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
106 | 108 | detail = self.do_clean(item_node.findall('issueDetail')) |
107 | 109 | remediation = self.do_clean(item_node.findall('remediationBackground')) |
108 | 110 | background = self.do_clean(item_node.findall('issueBackground')) |
111 | self.cve = [] | |
112 | if background: | |
113 | cve = CVE_regex.search(background) | |
114 | if cve: | |
115 | self.cve = [cve.group()] | |
109 | 116 | |
110 | 117 | self.url = host_node.text |
111 | 118 | |
226 | 233 | request=item.request, |
227 | 234 | response=item.response, |
228 | 235 | resolution=resolution, |
229 | external_id=item.external_id) | |
236 | external_id=item.external_id, | |
237 | cve=item.cve | |
238 | ) | |
230 | 239 | |
231 | 240 | del parser |
232 | 241 |
0 | 0 | """ |
1 | 1 | Faraday Penetration Test IDE |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
2 | Copyright (C) 2013 Infobyte LLC (https://www.faradaysec.com/) | |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | # Standard library imports | |
5 | 6 | import sys |
6 | 7 | import re |
7 | 8 | import csv |
8 | 9 | from ast import literal_eval |
9 | 10 | |
11 | # Local application imports | |
10 | 12 | from faraday_plugins.plugins.plugin import PluginCSVFormat |
11 | 13 | |
12 | 14 | |
43 | 45 | "impact_availability", |
44 | 46 | "impact_accountability", |
45 | 47 | "policyviolations", |
48 | "cve", | |
46 | 49 | "custom_fields", |
47 | 50 | "website", |
48 | 51 | "path", |
71 | 74 | reader.fieldnames[index] = "target" |
72 | 75 | custom_fields_names = self.get_custom_fields_names(reader.fieldnames) |
73 | 76 | for row in reader: |
74 | self.data = {} | |
75 | self.data['row_with_service'] = False | |
76 | self.data['row_with_vuln'] = False | |
77 | self.data = { | |
78 | 'row_with_service': False, | |
79 | 'row_with_vuln': False | |
80 | } | |
77 | 81 | self.build_host(row) |
78 | 82 | if "service" in obj_to_import: |
79 | 83 | if row['port'] and row['protocol']: |
122 | 126 | if (port and not protocol) or (protocol and not port): |
123 | 127 | self.logger.error( |
124 | 128 | ("Missing columns in CSV file. " |
125 | "In order to import services, you need to add a column called port " | |
126 | " and a column called protocol.") | |
129 | "In order to import services, you need to add a column called port " | |
130 | " and a column called protocol.") | |
127 | 131 | ) |
128 | 132 | return None |
129 | 133 | else: |
136 | 140 | if (vuln_name and not vuln_desc) or (vuln_desc and not vuln_name): |
137 | 141 | self.logger.error( |
138 | 142 | ("Missing columns in CSV file. " |
139 | "In order to import vulnerabilities, you need to add a " | |
140 | "column called name and a column called desc.") | |
143 | "In order to import vulnerabilities, you need to add a " | |
144 | "column called name and a column called desc.") | |
141 | 145 | ) |
142 | 146 | return None |
143 | 147 | else: |
145 | 149 | |
146 | 150 | return obj_to_import |
147 | 151 | |
148 | def get_custom_fields_names(self, headers): | |
152 | @staticmethod | |
153 | def get_custom_fields_names(headers): | |
149 | 154 | custom_fields_names = [] |
150 | 155 | for header in headers: |
151 | 156 | match = re.match(r"cf_(\w+)", header) |
206 | 211 | if "impact_" in item: |
207 | 212 | impact = re.match(r"impact_(\w+)", item).group(1) |
208 | 213 | impact_dict[impact] = True if row[item] == "True" else False |
209 | elif item in ["refs", "policyviolations", "tags"]: | |
214 | elif item in ["refs", "policyviolations", "cve", "tags"]: | |
210 | 215 | self.data[item] = literal_eval(row[item]) |
211 | 216 | else: |
212 | 217 | self.data[item] = row[item] |
225 | 230 | self.logger.error("Hostname not valid. Faraday will set it as empty.") |
226 | 231 | return hostnames |
227 | 232 | |
228 | def parse_vuln_impact(self, impact): | |
233 | @staticmethod | |
234 | def parse_vuln_impact(impact): | |
229 | 235 | impacts = [ |
230 | 236 | "accountability", |
231 | 237 | "confidentiality", |
236 | 242 | if item in impact: |
237 | 243 | return item |
238 | 244 | |
239 | def parse_custom_fields(self, row, custom_fields_names): | |
245 | @staticmethod | |
246 | def parse_custom_fields(row, custom_fields_names): | |
240 | 247 | custom_fields = {} |
241 | 248 | for cf_name in custom_fields_names: |
242 | 249 | cf_value = row["cf_" + cf_name] |
301 | 308 | easeofresolution=item['easeofresolution'] or None, |
302 | 309 | impact=item['impact'], |
303 | 310 | policyviolations=item['policyviolations'], |
311 | cve=item['cve'], | |
304 | 312 | custom_fields=item['custom_fields'], |
305 | 313 | tags=item['tags'] |
306 | 314 | ) |
320 | 328 | easeofresolution=item['easeofresolution'] or None, |
321 | 329 | impact=item['impact'], |
322 | 330 | policyviolations=item['policyviolations'], |
331 | cve=item['cve'], | |
323 | 332 | custom_fields=item['custom_fields'], |
324 | 333 | tags=item['tags'] |
325 | 334 | ) |
347 | 356 | easeofresolution=item['easeofresolution'] or None, |
348 | 357 | impact=item['impact'], |
349 | 358 | policyviolations=item['policyviolations'], |
359 | cve=item['cve'], | |
350 | 360 | status_code=item['status_code'] or None, |
351 | 361 | custom_fields=item['custom_fields'], |
352 | 362 | tags=item['tags'] |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | ||
6 | import json | |
7 | import re | |
8 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
9 | ||
10 | __author__ = "Joachim Bauernberger" | |
11 | __license__ = "MIT" | |
12 | __version__ = "1.0.0" | |
13 | __maintainer__ = "Joachim Bauernberger" | |
14 | __email__ = "[email protected]" | |
15 | __status__ = "Development" | |
16 | ||
17 | ||
18 | class GrypePlugin(PluginJsonFormat): | |
19 | def __init__(self, *arg, **kwargs): | |
20 | super().__init__(*arg, **kwargs) | |
21 | self.id = 'grype' | |
22 | self.name = 'Grype JSON Plugin' | |
23 | self.plugin_version = '1.0.0' | |
24 | self._command_regex = re.compile(r'^grype\s+.*') | |
25 | self._use_temp_file = True | |
26 | self._temp_file_extension = "json" | |
27 | self.json_keys = {"source", "matches", "descriptor"} | |
28 | ||
29 | def parseOutputString(self, output, debug=True): | |
30 | grype_json = json.loads(output) | |
31 | if "userInput" in grype_json["source"]["target"]: | |
32 | name = grype_json["source"]["target"]["userInput"] | |
33 | else: | |
34 | name = grype_json["source"]["target"] | |
35 | host_id = self.createAndAddHost(name, description=f"Type: {grype_json['source']['type']}") | |
36 | for match in grype_json['matches']: | |
37 | name = match.get('vulnerability').get('id') | |
38 | cve = name | |
39 | references = [] | |
40 | if match["relatedVulnerabilities"]: | |
41 | description = match["relatedVulnerabilities"][0].get('description') | |
42 | references.append(match["relatedVulnerabilities"][0]["dataSource"]) | |
43 | related_vuln = match["relatedVulnerabilities"][0] | |
44 | severity = related_vuln["severity"].lower().replace("negligible", "info") | |
45 | for url in related_vuln["urls"]: | |
46 | references.append(url) | |
47 | else: | |
48 | description = match.get('vulnerability').get('description') | |
49 | severity = match.get('vulnerability').get('severity').lower().replace("negligible", "info") | |
50 | for url in match.get('vulnerability').get('urls'): | |
51 | references.append(url) | |
52 | if not match['artifact']['metadata']: | |
53 | data = f"Artifact: {match['artifact']['name']}" \ | |
54 | f"Version: {match['artifact']['version']} " \ | |
55 | f"Type: {match['artifact']['type']}" | |
56 | else: | |
57 | if "Source" in match['artifact']['metadata']: | |
58 | data = f"Artifact: {match['artifact']['name']} [{match['artifact']['metadata']['Source']}] " \ | |
59 | f"Version: {match['artifact']['version']} " \ | |
60 | f"Type: {match['artifact']['type']}" | |
61 | elif "VirtualPath" in match['artifact']['metadata']: | |
62 | data = f"Artifact: {match['artifact']['name']} [{match['artifact']['metadata']['VirtualPath']}] " \ | |
63 | f"Version: {match['artifact']['version']} " \ | |
64 | f"Type: {match['artifact']['type']}" | |
65 | self.createAndAddVulnToHost(host_id, | |
66 | name=name, | |
67 | desc=description, | |
68 | ref=references, | |
69 | severity=severity, | |
70 | data=data, | |
71 | cve=cve) | |
72 | ||
73 | def processCommandString(self, username, current_path, command_string): | |
74 | super().processCommandString(username, current_path, command_string) | |
75 | command_string += f" -o json --file {self._output_file_path}" | |
76 | return command_string | |
77 | ||
78 | ||
79 | def createPlugin(ignore_info=False): | |
80 | return GrypePlugin(ignore_info=ignore_info) |
7 | 7 | import xml.etree.ElementTree as ET |
8 | 8 | |
9 | 9 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
10 | from faraday_plugins.plugins.plugins_utils import CVE_regex | |
10 | 11 | |
11 | 12 | __author__ = "Francisco Amato" |
12 | 13 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
159 | 160 | return None |
160 | 161 | |
161 | 162 | |
162 | class Results(): | |
163 | class Results: | |
163 | 164 | |
164 | 165 | def __init__(self, issue_node): |
165 | 166 | self.node = issue_node |
166 | self.ref = [issue_node.get("key")] | |
167 | match = CVE_regex.match(issue_node.get("key", "")) | |
168 | if match: | |
169 | self.ref = [] | |
170 | self.cve = [match.group()] | |
171 | else: | |
172 | self.ref = [issue_node.get("key")] | |
173 | self.cve = [] | |
167 | 174 | self.severity = "" |
168 | 175 | self.port = "Unknown" |
169 | 176 | self.service_name = "n/a" |
250 | 257 | v.name, |
251 | 258 | desc=v.desc, |
252 | 259 | severity=v.severity, |
253 | ref=v.ref) | |
260 | ref=v.ref, | |
261 | cve=v.cve) | |
254 | 262 | else: |
255 | 263 | s_id = mapped_services.get(v.service_name) or mapped_ports.get(v.port) |
256 | 264 | self.createAndAddVulnToService( |
259 | 267 | v.name, |
260 | 268 | desc=v.desc, |
261 | 269 | severity=v.severity, |
262 | ref=v.ref) | |
270 | ref=v.ref, | |
271 | cve=v.cve) | |
263 | 272 | |
264 | 273 | for p in item.ports: |
265 | 274 | s_id = self.createAndAddServiceToHost( |
57 | 57 | try: |
58 | 58 | tree = ET.fromstring(xml_output) |
59 | 59 | except SyntaxError as err: |
60 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
60 | print(f"SyntaxError: {err}. {xml_output}") | |
61 | 61 | return None |
62 | 62 | |
63 | 63 | return tree |
275 | 275 | self.service_id = self.get_text_from_subnode('service-id') |
276 | 276 | self.name = self.get_text_from_subnode('name') |
277 | 277 | self.desc = self.get_text_from_subnode('info') |
278 | self.refs = [r.text for r in self.node.findall('refs/ref')] | |
278 | self.refs = [r.text for r in self.node.findall('refs/ref') if not r.text.startswith('CVE')] | |
279 | self.cve = [r.text for r in self.node.findall('refs/ref') if r.text.startswith('CVE')] | |
279 | 280 | self.exploited_date = self.get_text_from_subnode('exploited-at') |
280 | 281 | self.exploited = (self.exploited_date is not None) |
281 | 282 | self.isWeb = False |
331 | 332 | |
332 | 333 | for v in item.vulnsByHost: |
333 | 334 | self.createAndAddVulnToHost( |
334 | h_id, v.name, v.desc, ref=v.refs) | |
335 | h_id, v.name, v.desc, ref=v.refs, cve=v.cve) | |
335 | 336 | |
336 | 337 | for s in item.services: |
337 | 338 | s_id = self.createAndAddServiceToHost(h_id, s['name'], |
344 | 345 | for n in item.notesByService[item.id + "_" + s['id']]: |
345 | 346 | self.createAndAddNoteToService( |
346 | 347 | h_id, s_id, n.ntype, n.data) |
347 | ||
348 | 348 | if s['port'] in item.credsByService: |
349 | 349 | for c in item.credsByService[s['port']]: |
350 | 350 | self.createAndAddCredToService( |
362 | 362 | category=v.category) |
363 | 363 | else: |
364 | 364 | self.createAndAddVulnToService( |
365 | h_id, s_id, v.name, v.desc, ref=v.refs) | |
365 | h_id, s_id, v.name, v.desc, ref=v.refs, cve=v.cve) | |
366 | 366 | |
367 | 367 | del parser |
368 | 368 |
183 | 183 | if item.xref: |
184 | 184 | kwargs["ref"].append(item.xref) |
185 | 185 | if item.cve: |
186 | kwargs["ref"] = kwargs["ref"] + item.cve | |
186 | kwargs["cve"] = item.cve | |
187 | 187 | if item.cvss3_base_score: |
188 | 188 | kwargs["ref"].append(item.cvss3_base_score) |
189 | 189 | if item.cvss3_vector: |
190 | 190 | kwargs["ref"].append(item.cvss3_vector) |
191 | ||
192 | 191 | return kwargs |
193 | 192 | |
194 | 193 |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import sys | |
6 | 7 | import re |
7 | 8 | import xml.etree.ElementTree as ET |
8 | 9 | |
9 | 10 | from bs4 import BeautifulSoup |
10 | 11 | |
11 | 12 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
12 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
13 | from faraday_plugins.plugins.plugins_utils import resolve_hostname, CVE_regex | |
13 | 14 | |
14 | 15 | __author__ = "Francisco Amato" |
15 | 16 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
32 | 33 | @param netsparker_xml_filepath A proper xml generated by netsparker |
33 | 34 | """ |
34 | 35 | |
35 | def __init__(self, xml_output): | |
36 | def __init__(self, xml_output, plugin): | |
36 | 37 | self.filepath = xml_output |
38 | self.plugin = plugin | |
37 | 39 | |
38 | 40 | tree = self.parse_xml(xml_output) |
39 | 41 | if tree: |
53 | 55 | try: |
54 | 56 | tree = ET.fromstring(xml_output) |
55 | 57 | except SyntaxError as err: |
56 | self.logger.error("SyntaxError: %s. %s" % (err, xml_output)) | |
58 | self.plugin.logger.error("SyntaxError: %s. %s" % (err, xml_output)) | |
57 | 59 | return None |
58 | 60 | |
59 | 61 | return tree |
109 | 111 | self.request = self.get_text_from_subnode("rawrequest") |
110 | 112 | self.response = self.get_text_from_subnode("rawresponse") |
111 | 113 | self.kvulns = [] |
114 | self.cve = [] | |
112 | 115 | for v in self.node.findall("knownvulnerabilities/knownvulnerability"): |
113 | 116 | self.node = v |
117 | recheck = CVE_regex.search(v.find('title').text) | |
118 | if recheck: | |
119 | self.cve.append(recheck.group()) | |
114 | 120 | self.kvulns.append(self.get_text_from_subnode( |
115 | 121 | "severity") + "-" + self.get_text_from_subnode("title")) |
116 | 122 | |
117 | 123 | self.extra = [] |
118 | 124 | for v in item_node.findall("extrainformation/info"): |
119 | name = v.get('name') | |
120 | if name: | |
121 | self.extra.append("{name}:{v.text}") | |
125 | name_tag = v.find('name') | |
126 | value_tag = v.find('value') | |
127 | if name_tag is not None: | |
128 | self.extra.append(f"{name_tag.text}:{value_tag.text}") | |
122 | 129 | |
123 | 130 | self.node = item_node |
124 | 131 | self.node = item_node.find("classification") |
189 | 196 | self.options = None |
190 | 197 | |
191 | 198 | def parseOutputString(self, output): |
192 | parser = NetsparkerXmlParser(output) | |
199 | parser = NetsparkerXmlParser(output, self) | |
193 | 200 | host_names_resolve = {} |
194 | 201 | for i in parser.items: |
195 | 202 | |
221 | 228 | self.createAndAddVulnWebToService(h_id, s_id, name, ref=i.ref, website=i.hostname, |
222 | 229 | severity=i.severity, desc=desc, path=i.url, method=i.method, |
223 | 230 | request=i.request, response=i.response, resolution=resolution, |
224 | pname=i.param, data=i.data) | |
231 | pname=i.param, data=i.data, cve=i.cve) | |
225 | 232 | |
226 | 233 | del parser |
227 | 234 |
7 | 7 | import xml.etree.ElementTree as ET |
8 | 8 | |
9 | 9 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
10 | from faraday_plugins.plugins.plugins_utils import CVE_regex | |
10 | 11 | |
11 | 12 | __author__ = "Micaela Ranea Sanchez" |
12 | 13 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
148 | 149 | 'tags': list(), |
149 | 150 | 'is_web': vid.startswith('http-'), |
150 | 151 | 'risk': vulnDef.get('riskScore'), |
152 | 'CVE': [] | |
151 | 153 | } |
152 | 154 | |
153 | 155 | for item in list(vulnDef): |
171 | 173 | for ref in list(item): |
172 | 174 | if ref.text: |
173 | 175 | rf = ref.text.strip() |
174 | vuln['refs'].append(rf) | |
176 | check = CVE_regex.search(rf.upper()) | |
177 | if check: | |
178 | vuln["CVE"].append(check.group()) | |
179 | else: | |
180 | vuln['refs'].append(rf) | |
175 | 181 | if item.tag == 'solution': |
176 | 182 | for htmlType in list(item): |
177 | 183 | vuln['resolution'] += self.parse_html_type(htmlType) |
178 | """ | |
179 | # there is currently no method to register tags in vulns | |
180 | if item.tag == 'tags': | |
181 | for tag in list(item): | |
182 | vuln['tags'].append(tag.text.lower()) | |
183 | """ | |
184 | ||
185 | """ | |
186 | # there is currently no method to register tags in vulns | |
187 | if item.tag == 'tags': | |
188 | for tag in list(item): | |
189 | vuln['tags'].append(tag.text.lower()) | |
190 | """ | |
184 | 191 | vulns[vid] = vuln |
185 | 192 | return vulns |
186 | 193 | |
269 | 276 | v['desc'], |
270 | 277 | v['refs'], |
271 | 278 | v['severity'], |
272 | v['resolution'] | |
279 | v['resolution'], | |
280 | cve=v.get('CVE') | |
273 | 281 | ) |
274 | 282 | |
275 | 283 | for s in item['services']: |
293 | 301 | v['refs'], |
294 | 302 | v['severity'], |
295 | 303 | v['resolution'], |
296 | path=v.get('path', '')) | |
304 | cve=v.get('CVE'), | |
305 | path=v.get('path', '') | |
306 | ) | |
297 | 307 | else: |
298 | 308 | self.createAndAddVulnToService( |
299 | 309 | h_id, |
302 | 312 | v['desc'], |
303 | 313 | v['refs'], |
304 | 314 | v['severity'], |
305 | v['resolution'] | |
315 | v['resolution'], | |
316 | cve=v.get('CVE') | |
306 | 317 | ) |
307 | 318 | |
308 | 319 | del parser |
111 | 111 | self.uri = self.get_uri() |
112 | 112 | self.desc = self.get_desc() |
113 | 113 | self.params = self.get_params(self.uri) |
114 | self.CVE = self.get_cve(self.desc) | |
114 | 115 | |
115 | 116 | def get_uri(self): |
116 | 117 | |
163 | 164 | |
164 | 165 | return None |
165 | 166 | |
166 | def __str__(self): | |
167 | ports = [] | |
168 | for port in self.ports: | |
169 | var = " %s" % port | |
170 | ports.append(var) | |
171 | ports = "\n".join(ports) | |
172 | ||
173 | return "%s, %s, %s [%s], %s\n%s" % (self.hostnames, self.status, | |
174 | self.ipv4_address, self.mac_address, self.os, ports) | |
167 | def get_cve(self, desc): | |
168 | match = plugins_utils.CVE_regex.search(desc) | |
169 | if match: | |
170 | return [match.group()] | |
171 | return [] | |
175 | 172 | |
176 | 173 | |
177 | 174 | class Host: |
200 | 197 | for item_node in self.node.findall('item'): |
201 | 198 | yield Item(item_node) |
202 | 199 | |
203 | def __str__(self): | |
204 | ports = [] | |
205 | for port in self.ports: | |
206 | var = " %s" % port | |
207 | ports.append(var) | |
208 | ports = "\n".join(ports) | |
209 | ||
210 | return "%s, %s, %s [%s], %s\n%s" % (self.hostnames, self.status, | |
211 | self.ipv4_address, self.mac_address, self.os, ports) | |
212 | 200 | |
213 | 201 | |
214 | 202 | class NiktoPlugin(PluginXMLFormat): |
304 | 292 | ref=item.osvdbid, |
305 | 293 | method=item.method, |
306 | 294 | params=', '.join(item.params), |
295 | cve=item.CVE, | |
307 | 296 | **plugins_utils.get_vulnweb_url_fields(item.namelink) |
308 | 297 | ) |
309 | 298 |
11 | 11 | __mantainer__ = "@rfocke" |
12 | 12 | __status__ = "Development" |
13 | 13 | |
14 | ||
14 | 15 | class VulnSoftNipper: |
15 | 16 | def __init__(self, **kwargs): |
16 | 17 | self.name = '' |
17 | 18 | self.data = '' |
18 | 19 | self.device = '' |
19 | 20 | self.refs = [] |
21 | ||
20 | 22 | |
21 | 23 | class VulnerabilityNipper: |
22 | 24 | def __init__(self, **kwargs): |
29 | 31 | self.data = '' |
30 | 32 | self.recommendation2 = '' |
31 | 33 | |
34 | ||
32 | 35 | class NipperParser: |
33 | 36 | def __init__(self, output, debug=False): |
34 | 37 | self.vulns_first = [] |
37 | 40 | self.debug = debug |
38 | 41 | |
39 | 42 | self.tree = ET.fromstring(output) |
40 | self.report_tree = self.tree.find("report/part/[@index='2']/section/[@title='Recommendations']/table/[@title='Security Audit recommendations list']/tablebody") | |
43 | self.report_tree = self.tree.find( | |
44 | "report/part/[@index='2']/section/[@title='Recommendations']/table/[@title='Security Audit recommendations list']/tablebody") | |
41 | 45 | self.process_xml() |
42 | 46 | |
43 | 47 | def process_xml(self): |
47 | 51 | for tablerow in self.report_tree: |
48 | 52 | for i, tablecell in enumerate(tablerow.findall('tablecell')): |
49 | 53 | if len(tablecell.findall('item')) == 1: |
50 | if i == 0: #Item | |
54 | if i == 0: # Item | |
51 | 55 | vuln = VulnerabilityNipper() |
52 | 56 | vuln.name = tablecell.find('item').text |
53 | elif i == 1: #Rating | |
57 | elif i == 1: # Rating | |
54 | 58 | vuln.rating = tablecell.find('item').text |
55 | elif i == 2: #Recommendations | |
59 | elif i == 2: # Recommendations | |
56 | 60 | vuln.recommendation = tablecell.find('item').text |
57 | elif i == 3: #Affected devices (with 1 element only) | |
61 | elif i == 3: # Affected devices (with 1 element only) | |
58 | 62 | vuln.affected_devices = [] |
59 | 63 | vuln.affected_devices.append(tablecell.find('item').text) |
60 | elif i == 4: #Section | |
64 | elif i == 4: # Section | |
61 | 65 | subdetail = tablecell.find('item').text |
62 | 66 | vuln.section = subdetail |
63 | 67 | |
79 | 83 | # recomendacion de la vuln |
80 | 84 | vuln.recommendation2 = detail.find('text').text |
81 | 85 | |
82 | self.vulns_first.append(vuln) # <- GUARDADO | |
86 | self.vulns_first.append(vuln) # <- GUARDADO | |
83 | 87 | elif len(tablecell.findall('item')) > 1 and i == 3: |
84 | 88 | # affected devices |
85 | 89 | vuln.affected_devices = [] |
111 | 115 | |
112 | 116 | self.vulns_audit.append(vuln_soft) |
113 | 117 | |
118 | ||
114 | 119 | class NipperPlugin(PluginXMLFormat): |
115 | 120 | def __init__(self, *arg, **kwargs): |
116 | 121 | super().__init__(*arg, **kwargs) |
129 | 134 | for vuln in parser.vulns_first: |
130 | 135 | for device in vuln.affected_devices: |
131 | 136 | ip = resolve_hostname(device) |
132 | h_id = self.createAndAddHost(ip, hostnames = device) | |
137 | h_id = self.createAndAddHost(ip, hostnames=device) | |
133 | 138 | self.createAndAddVulnToHost(h_id, |
134 | name = vuln.name, | |
135 | desc = vuln.data, | |
136 | severity = vuln.rating, | |
137 | resolution = vuln.recommendation, | |
138 | data = vuln.data, | |
139 | ref = [], | |
140 | policyviolations = [] | |
139 | name=vuln.name, | |
140 | desc=vuln.data, | |
141 | severity=vuln.rating, | |
142 | resolution=vuln.recommendation, | |
143 | data=vuln.data, | |
144 | ref=[], | |
145 | policyviolations=[], | |
146 | cve=[vuln.name] | |
141 | 147 | ) |
142 | 148 | for vuln in parser.vulns_audit: |
143 | 149 | if vuln.data: |
144 | 150 | ip = resolve_hostname(device) |
145 | h_id = self.createAndAddHost(ip, hostnames = vuln.device) | |
151 | h_id = self.createAndAddHost(ip, hostnames=vuln.device) | |
146 | 152 | self.createAndAddVulnToHost(h_id, |
147 | name = vuln.name, | |
148 | desc = vuln.data, | |
149 | severity = '', | |
150 | resolution = '', | |
151 | data = vuln.data, | |
152 | ref = vuln.refs | |
153 | name=vuln.name, | |
154 | desc=vuln.data, | |
155 | severity='', | |
156 | resolution='', | |
157 | data=vuln.data, | |
158 | ref=vuln.refs, | |
159 | cve=[vuln.name] | |
153 | 160 | ) |
154 | 161 | |
155 | 162 | |
156 | 163 | def createPlugin(ignore_info=False): |
157 | 164 | return NipperPlugin(ignore_info=ignore_info) |
158 |
456 | 456 | self.framework_version = "1.0.0" |
457 | 457 | self.options = None |
458 | 458 | self._current_output = None |
459 | self._command_regex = re.compile(r'^(sudo nmap|nmap|\.\/nmap)\s+.*?') | |
459 | self._command_regex = re.compile(r'^(sudo nmap|nmap|\.\/nmap|sudo masscan|masscan)\s+.*?') | |
460 | 460 | self._use_temp_file = True |
461 | 461 | self._temp_file_extension = "xml" |
462 | 462 | self.xml_arg_re = re.compile(r"^.*(-oX\s*[^\s]+).*$") |
496 | 496 | desc=v.desc, |
497 | 497 | ref=v.refs, |
498 | 498 | severity=0, |
499 | external_id=v.name) | |
499 | cve=[v.name] | |
500 | ) | |
500 | 501 | |
501 | 502 | for port in host.ports: |
502 | 503 | |
542 | 543 | ref=refs, |
543 | 544 | severity=severity, |
544 | 545 | website=minterfase, |
545 | external_id=v.name) | |
546 | cve=[v.name]) | |
546 | 547 | else: |
547 | 548 | v_id = self.createAndAddVulnToService( |
548 | 549 | h_id, |
551 | 552 | desc=v.desc, |
552 | 553 | ref=refs, |
553 | 554 | severity=severity, |
554 | external_id=v.name) | |
555 | cve=[v.name] | |
556 | ) | |
555 | 557 | del parser |
556 | 558 | |
557 | 559 | def processCommandString(self, username, current_path, command_string): |
562 | 564 | super().processCommandString(username, current_path, command_string) |
563 | 565 | arg_match = self.xml_arg_re.match(command_string) |
564 | 566 | if arg_match is None: |
565 | return re.sub(r"(^.*?nmap)", | |
566 | r"\1 -oX %s" % self._output_file_path, | |
567 | command_string) | |
567 | if "masscan" in command_string: | |
568 | return re.sub(r"(^.*?masscan)", | |
569 | r"\1 -oX %s" % self._output_file_path, | |
570 | command_string) | |
571 | else: | |
572 | return re.sub(r"(^.*?nmap)", | |
573 | r"\1 -oX %s" % self._output_file_path, | |
574 | command_string) | |
568 | 575 | else: |
569 | 576 | return re.sub(arg_match.group(1), |
570 | 577 | r"-oX %s" % self._output_file_path, |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import socket | |
6 | import subprocess | |
7 | 7 | import re |
8 | import sys | |
8 | 9 | import json |
9 | 10 | import dateutil |
10 | from collections import defaultdict | |
11 | 11 | from urllib.parse import urlparse |
12 | from packaging import version | |
12 | 13 | from faraday_plugins.plugins.plugin import PluginMultiLineJsonFormat |
13 | 14 | from faraday_plugins.plugins.plugins_utils import resolve_hostname |
14 | 15 | |
31 | 32 | super().__init__(*arg, **kwargs) |
32 | 33 | self.id = "nuclei" |
33 | 34 | self.name = "Nuclei" |
34 | self.plugin_version = "1.0.1" | |
35 | self.version = "2.3.8" | |
36 | self.json_keys = {"matched", "templateID", "host"} | |
35 | self.plugin_version = "1.0.2" | |
36 | self.version = "2.5.3" | |
37 | self.json_keys = {"matched-at", "template-id", "host"} | |
37 | 38 | self._command_regex = re.compile(r'^(sudo nuclei|nuclei|\.\/nuclei|^.*?nuclei)\s+.*?') |
38 | 39 | self.xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") |
39 | 40 | self._use_temp_file = True |
62 | 63 | status='open', |
63 | 64 | version='', |
64 | 65 | description='web server') |
65 | matched = vuln_dict.get('matched') | |
66 | matched_data = urlparse(matched) | |
66 | matched = vuln_dict.get('matched-at', '') | |
67 | if matched: | |
68 | matched_data = urlparse(matched) | |
69 | else: | |
70 | print('Version not supported, use nuclei 2.5.3 or higher') | |
71 | sys.exit(1) | |
67 | 72 | reference = vuln_dict["info"].get('reference', []) |
68 | 73 | if not reference: |
69 | 74 | reference = [] |
96 | 101 | method = request.split(" ")[0] |
97 | 102 | else: |
98 | 103 | method = "" |
99 | data = [f"Matched: {vuln_dict.get('matched')}", | |
104 | ||
105 | data = [f"Matched: {vuln_dict.get('matched-at')}", | |
100 | 106 | f"Tags: {vuln_dict['info'].get('tags', '')}", |
101 | f"Template ID: {vuln_dict['templateID']}"] | |
107 | f"Template ID: {vuln_dict.get('template-id', '')}"] | |
102 | 108 | |
103 | 109 | name = vuln_dict["info"].get("name") |
104 | 110 | run_date = vuln_dict.get('timestamp') |
123 | 129 | params=matched_data.params, |
124 | 130 | path=matched_data.path, |
125 | 131 | data="\n".join(data), |
126 | external_id=f"NUCLEI-{vuln_dict.get('templateID', '')}", | |
132 | external_id=f"NUCLEI-{vuln_dict.get('template-id', '')}", | |
127 | 133 | run_date=run_date |
128 | 134 | ) |
129 | ||
135 | ||
130 | 136 | def processCommandString(self, username, current_path, command_string): |
131 | """ | |
132 | Adds the -oX parameter to get xml output to the command string that the | |
133 | user has set. | |
134 | """ | |
135 | 137 | super().processCommandString(username, current_path, command_string) |
136 | 138 | arg_match = self.xml_arg_re.match(command_string) |
137 | 139 | if arg_match is None: |
141 | 143 | else: |
142 | 144 | return re.sub(arg_match.group(1), |
143 | 145 | r"--json -irr -o %s" % self._output_file_path, |
144 | command_string) | |
146 | command_string) | |
147 | ||
148 | def canParseCommandString(self, current_input): | |
149 | can_parse = super().canParseCommandString(current_input) | |
150 | if can_parse: | |
151 | try: | |
152 | proc = subprocess.Popen([self.command, '-version'], stderr=subprocess.PIPE) | |
153 | output = proc.stderr.read() | |
154 | match = re.search(r"Current Version: ([0-9.]+)", output.decode('UTF-8')) | |
155 | if match: | |
156 | nuclei_version = match.groups()[0] | |
157 | return version.parse(nuclei_version) >= version.parse("2.5.3") | |
158 | else: | |
159 | return False | |
160 | except Exception as e: | |
161 | return False | |
145 | 162 | |
146 | 163 | |
147 | 164 | def createPlugin(ignore_info=False): |
0 | import subprocess | |
1 | import re | |
2 | import json | |
3 | import dateutil | |
4 | from packaging import version | |
5 | from urllib.parse import urlparse | |
6 | from faraday_plugins.plugins.plugin import PluginMultiLineJsonFormat | |
7 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
8 | ||
9 | __author__ = "Emilio Couto" | |
10 | __copyright__ = "Copyright (c) 2021, Faraday Security" | |
11 | __credits__ = ["Emilio Couto"] | |
12 | __license__ = "" | |
13 | __version__ = "1.0.0" | |
14 | __maintainer__ = "Emilio Couto" | |
15 | __email__ = "[email protected]" | |
16 | __status__ = "Development" | |
17 | ||
18 | ||
19 | class NucleiLegacyPlugin(PluginMultiLineJsonFormat): | |
20 | """ Handle the Nuclei tool. Detects the output of the tool | |
21 | and adds the information to Faraday. | |
22 | """ | |
23 | ||
24 | def __init__(self, *arg, **kwargs): | |
25 | super().__init__(*arg, **kwargs) | |
26 | self.id = "nuclei_legacy" | |
27 | self.name = "Nuclei" | |
28 | self.plugin_version = "1.0.0" | |
29 | self.version = "2.5.2" | |
30 | self._command_regex = re.compile(r'^(sudo nuclei|nuclei|\.\/nuclei|^.*?nuclei)\s+.*?') | |
31 | self.xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
32 | self._use_temp_file = True | |
33 | self._temp_file_extension = "json" | |
34 | self.json_keys = {"matched", "templateID", "host"} | |
35 | ||
36 | def parseOutputString(self, output, debug=False): | |
37 | for vuln_json in filter(lambda x: x != '', output.split("\n")): | |
38 | vuln_dict = json.loads(vuln_json) | |
39 | host = vuln_dict.get('host') | |
40 | url_data = urlparse(host) | |
41 | ip = vuln_dict.get("ip", resolve_hostname(url_data.hostname)) | |
42 | host_id = self.createAndAddHost( | |
43 | name=ip, | |
44 | hostnames=[url_data.hostname]) | |
45 | port = url_data.port | |
46 | if not port: | |
47 | if url_data.scheme == 'https': | |
48 | port = 443 | |
49 | else: | |
50 | port = 80 | |
51 | service_id = self.createAndAddServiceToHost( | |
52 | host_id, | |
53 | name=url_data.scheme, | |
54 | ports=port, | |
55 | protocol="tcp", | |
56 | status='open', | |
57 | version='', | |
58 | description='web server') | |
59 | matched = vuln_dict.get('matched') | |
60 | matched_data = urlparse(matched) | |
61 | reference = vuln_dict["info"].get('reference', []) | |
62 | if not reference: | |
63 | reference = [] | |
64 | else: | |
65 | if isinstance(reference, str): | |
66 | if re.match('^- ', reference): | |
67 | reference = list(filter(None, [re.sub('^- ', '', elem) for elem in reference.split('\n')])) | |
68 | else: | |
69 | reference = [reference] | |
70 | references = vuln_dict["info"].get('references', []) | |
71 | if references: | |
72 | if isinstance(references, str): | |
73 | if re.match('^- ', references): | |
74 | references = list(filter(None, [re.sub('^- ', '', elem) for elem in references.split('\n')])) | |
75 | else: | |
76 | references = [references] | |
77 | else: | |
78 | references = [] | |
79 | cwe = vuln_dict['info'].get('cwe', []) | |
80 | capec = vuln_dict['info'].get('capec', []) | |
81 | refs = sorted(list(set(reference + references + cwe + capec))) | |
82 | tags = vuln_dict['info'].get('tags', []) | |
83 | if isinstance(tags, str): | |
84 | tags = tags.split(',') | |
85 | impact = vuln_dict['info'].get('impact') | |
86 | resolution = vuln_dict['info'].get('resolution', '') | |
87 | easeofresolution = vuln_dict['info'].get('easeofresolution') | |
88 | request = vuln_dict.get('request', '') | |
89 | if request: | |
90 | method = request.split(" ")[0] | |
91 | else: | |
92 | method = "" | |
93 | data = [f"Matched: {vuln_dict.get('matched', '')}", | |
94 | f"Tags: {vuln_dict['info'].get('tags', '')}", | |
95 | f"Template ID: {vuln_dict.get('templateID', '')}"] | |
96 | ||
97 | name = vuln_dict["info"].get("name") | |
98 | run_date = vuln_dict.get('timestamp') | |
99 | if run_date: | |
100 | run_date = dateutil.parser.parse(run_date) | |
101 | self.createAndAddVulnWebToService( | |
102 | host_id, | |
103 | service_id, | |
104 | name=name, | |
105 | desc=vuln_dict["info"].get("description", name), | |
106 | ref=refs, | |
107 | severity=vuln_dict["info"].get('severity'), | |
108 | tags=tags, | |
109 | impact=impact, | |
110 | resolution=resolution, | |
111 | easeofresolution=easeofresolution, | |
112 | website=host, | |
113 | request=request, | |
114 | response=vuln_dict.get('response', ''), | |
115 | method=method, | |
116 | query=matched_data.query, | |
117 | params=matched_data.params, | |
118 | path=matched_data.path, | |
119 | data="\n".join(data), | |
120 | external_id=f"NUCLEI-{vuln_dict.get('templateID', '')}", | |
121 | run_date=run_date | |
122 | ) | |
123 | ||
124 | def processCommandString(self, username, current_path, command_string): | |
125 | super().processCommandString(username, current_path, command_string) | |
126 | arg_match = self.xml_arg_re.match(command_string) | |
127 | if arg_match is None: | |
128 | return re.sub(r"(^.*?nuclei)", | |
129 | r"\1 --json -irr -o %s" % self._output_file_path, | |
130 | command_string) | |
131 | else: | |
132 | return re.sub(arg_match.group(1), | |
133 | r"--json -irr -o %s" % self._output_file_path, | |
134 | command_string) | |
135 | ||
136 | def canParseCommandString(self, current_input): | |
137 | can_parse = super().canParseCommandString(current_input) | |
138 | if can_parse: | |
139 | try: | |
140 | proc = subprocess.Popen([self.command, '-version'], stderr=subprocess.PIPE) | |
141 | output = proc.stderr.read() | |
142 | match = re.search(r"Current Version: ([0-9.]+)", output.decode('UTF-8')) | |
143 | if match: | |
144 | nuclei_version = match.groups()[0] | |
145 | return version.parse(nuclei_version) <= version.parse("2.5.2") | |
146 | else: | |
147 | return False | |
148 | except Exception as e: | |
149 | return False | |
150 | ||
151 | def createPlugin(ignore_info=False): | |
152 | return NucleiLegacyPlugin(ignore_info=ignore_info) |
194 | 194 | if rule['id'] == info['rule_id']: |
195 | 195 | vuln_name = info['rule_title'] |
196 | 196 | vuln_data = info['rule_check'] |
197 | vuln_ref = info['rule_ident'] | |
197 | vuln_cve = info['rule_ident'] | |
198 | 198 | |
199 | 199 | self.createAndAddVulnToHost( |
200 | 200 | host_id, |
201 | 201 | vuln_name, |
202 | 202 | desc=desc, |
203 | ref=[vuln_ref], | |
204 | 203 | severity=severity, |
205 | 204 | data=vuln_data, |
206 | 205 | external_id=rule['id'], |
207 | run_date=vuln_run_date) | |
206 | run_date=vuln_run_date, | |
207 | cve=[vuln_cve]) | |
208 | 208 | |
209 | 209 | |
210 | 210 | def createPlugin(ignore_info=False): |
80 | 80 | try: |
81 | 81 | yield Item(node, hosts) |
82 | 82 | except Exception as e: |
83 | self.logger.error("Error generating Iteem from %s [%s]", node.attrib, e) | |
83 | self.logger.error(f"Error generating Iteem from {node.attrib} [{e}]") | |
84 | 84 | |
85 | 85 | except Exception as e: |
86 | self.logger.error("Tag not found: %s", e) | |
86 | self.logger.error(f"Tag not found: {e}") | |
87 | 87 | |
88 | 88 | def get_hosts(self, tree): |
89 | 89 | # Hosts are located in: /report/report/host |
153 | 153 | self.severity_nr = self.get_text_from_subnode("severity") |
154 | 154 | self.service = "Unknown" |
155 | 155 | self.protocol = "" |
156 | self.cpe = self.node.findall("detection/result/details/detail") | |
157 | self.cpe = self.cpe[0].findtext("value") if self.cpe else None | |
156 | details = self.node.findall("detection/result/details/detail") | |
157 | self.cpe = details[0].findtext("value") if details else None | |
158 | 158 | port_string = self.get_text_from_subnode('port') |
159 | 159 | info = port_string.split("/") |
160 | if len(info) < 2: | |
161 | info = details[1].findtext("value").split("/") if details else ["", ""] | |
160 | 162 | self.protocol = "".join(filter(lambda x: x.isalpha() or x in ("-", "_"), info[1])) |
161 | 163 | self.port = "".join(filter(lambda x: x.isdigit(), info[0])) or None |
162 | 164 | if not self.port: |
180 | 182 | self.cvss_vector = '' |
181 | 183 | self.tags = self.get_text_from_subnode('tags') |
182 | 184 | self.data = self.get_text_from_subnode('description') |
185 | self.data += f'\n\nid {item_node.attrib.get("id")}' | |
183 | 186 | if self.tags: |
184 | 187 | tags_data = self.get_data_from_tags(self.tags) |
185 | 188 | self.description = tags_data['description'] |
186 | 189 | self.resolution = tags_data['solution'] |
187 | 190 | self.cvss_vector = tags_data['cvss_base_vector'] |
188 | 191 | if tags_data['impact']: |
189 | self.data += '\n\nImpact: {}'.format(tags_data['impact']) | |
192 | self.data += f'\n\nImpact: {tags_data["impact"]}' | |
190 | 193 | |
191 | 194 | def get_text_from_subnode(self, subnode_xpath_expr): |
192 | 195 | """ |
337 | 340 | |
338 | 341 | if item.name is not None: |
339 | 342 | ref = [] |
343 | cve = [] | |
340 | 344 | if item.cve: |
341 | 345 | cves = item.cve.split(',') |
342 | for cve in cves: | |
343 | ref.append(cve.strip()) | |
346 | for i in cves: | |
347 | cve.append(i.strip()) | |
344 | 348 | if item.bid: |
345 | 349 | bids = item.bid.split(',') |
346 | 350 | for bid in bids: |
376 | 380 | resolution=item.resolution, |
377 | 381 | ref=ref, |
378 | 382 | external_id=item.id, |
379 | data=item.data) | |
383 | data=item.data, | |
384 | cve=cve) | |
380 | 385 | else: |
381 | 386 | if item.service: |
382 | 387 | web = re.search( |
407 | 412 | ref=ref, |
408 | 413 | resolution=item.resolution, |
409 | 414 | external_id=item.id, |
410 | data=item.data) | |
415 | data=item.data, | |
416 | cve=cve) | |
411 | 417 | elif item.severity not in self.ignored_severities: |
412 | 418 | self.createAndAddVulnToService( |
413 | 419 | h_id, |
418 | 424 | ref=ref, |
419 | 425 | resolution=item.resolution, |
420 | 426 | external_id=item.id, |
421 | data=item.data) | |
427 | data=item.data, | |
428 | cve=cve) | |
422 | 429 | del parser |
423 | 430 | |
424 | 431 | @staticmethod |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | from dateutil.parser import parse | |
7 | import json | |
8 | from datetime import datetime | |
9 | from dataclasses import dataclass | |
10 | from faraday_plugins.plugins.plugin import PluginMultiLineJsonFormat | |
11 | ||
12 | __author__ = "Nicolas Rebagliati" | |
13 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
14 | __credits__ = ["Nicolas Rebagliati"] | |
15 | __license__ = "" | |
16 | __version__ = "0.0.1" | |
17 | __maintainer__ = "Nicolas Rebagliati" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | @dataclass | |
23 | class Issue: | |
24 | region: str | |
25 | profile: str | |
26 | severity: str | |
27 | scored: str | |
28 | account: str | |
29 | message: str | |
30 | control: str | |
31 | status: str | |
32 | level: str | |
33 | control_id: str | |
34 | timestamp: datetime | |
35 | compliance: str | |
36 | service: str | |
37 | caf_epic: str | |
38 | risk: str | |
39 | doc_link: str | |
40 | remediation: str | |
41 | resource_id: str | |
42 | ||
43 | ||
44 | class ProwlerJsonParser: | |
45 | ||
46 | def parse_issues(self, records): | |
47 | for record in records: | |
48 | json_data = json.loads(record) | |
49 | region = json_data.get("Region", "AWS_REGION") | |
50 | profile = json_data.get("Profile", "") | |
51 | severity = json_data.get("Severity", "info").lower() | |
52 | scored = json_data.get("Status", "") | |
53 | account = json_data.get("Account Number", "") | |
54 | message = json_data.get("Message", "") | |
55 | control = json_data.get("Control", "") | |
56 | status = json_data.get("Status", "") | |
57 | level = json_data.get("Level", "") | |
58 | control_id = json_data.get("Control ID", "") | |
59 | timestamp = json_data.get("Timestamp", None) | |
60 | if timestamp: | |
61 | timestamp = parse(timestamp) | |
62 | compliance = json_data.get("Compliance", "") | |
63 | service = json_data.get("Service", "") | |
64 | caf_epic = json_data.get("CAF Epic", "") | |
65 | risk = json_data.get("Risk", "") | |
66 | doc_link = json_data.get("Doc link", "") | |
67 | remediation = json_data.get("Remediation", "") | |
68 | resource_id = json_data.get("Resource ID", "") | |
69 | if status == "FAIL": | |
70 | self.issues.append(Issue(region=region, profile=profile, severity=severity, scored=scored, | |
71 | account=account, message=message, control=control, status=status, | |
72 | level=level, control_id=control_id, timestamp=timestamp, compliance=compliance, | |
73 | service=service, caf_epic=caf_epic, risk=risk, doc_link=doc_link, | |
74 | remediation=remediation, resource_id=resource_id)) | |
75 | ||
76 | def __init__(self, json_output): | |
77 | self.issues = [] | |
78 | self.parse_issues(json_output.splitlines()) | |
79 | ||
80 | ||
81 | class ProwlerPlugin(PluginMultiLineJsonFormat): | |
82 | """ Handle the AWS Prowler tool. Detects the output of the tool | |
83 | and adds the information to Faraday. | |
84 | """ | |
85 | ||
86 | def __init__(self, *arg, **kwargs): | |
87 | super().__init__(*arg, **kwargs) | |
88 | self.id = "prowler" | |
89 | self.name = "Prowler" | |
90 | self.plugin_version = "0.1" | |
91 | self.version = "0.0.1" | |
92 | self.json_keys = {"Profile", "Account Number", "Region"} | |
93 | ||
94 | def parseOutputString(self, output, debug=False): | |
95 | parser = ProwlerJsonParser(output) | |
96 | for issue in parser.issues: | |
97 | host_name = f"{issue.service}-{issue.account}-{issue.region}" | |
98 | host_id = self.createAndAddHost(name=host_name, | |
99 | description=f"AWS Service: {issue.service} - Account: {issue.account}" | |
100 | f" - Region: {issue.region}") | |
101 | ||
102 | vuln_desc = f"{issue.risk}\nCompliance: {issue.compliance}\nMessage: {issue.message}" | |
103 | self.createAndAddVulnToHost(host_id=host_id, name=issue.control, desc=vuln_desc, | |
104 | data=f"Resource ID: {issue.resource_id}", | |
105 | severity=self.normalize_severity(issue.severity), resolution=issue.remediation, | |
106 | run_date=issue.timestamp, external_id=f"{self.name.upper()}-{issue.control_id}", | |
107 | ref=[issue.doc_link]) | |
108 | ||
109 | ||
110 | def createPlugin(ignore_info=False): | |
111 | return ProwlerPlugin(ignore_info=ignore_info) |
172 | 172 | |
173 | 173 | # References |
174 | 174 | self.ref = [] |
175 | self.cve = [] | |
175 | 176 | |
176 | 177 | cve_id = self.get_text_from_glossary('CVE_ID_LIST/CVE_ID/ID') |
177 | 178 | if cve_id: |
178 | self.ref.append(cve_id) | |
179 | self.cve.append(cve_id) | |
179 | 180 | |
180 | 181 | if self.cvss: |
181 | 182 | self.ref.append('CVSS SCORE: {}'.format(self.cvss)) |
303 | 304 | self.desc += '' |
304 | 305 | |
305 | 306 | self.ref = [] |
307 | self.cve = [] | |
306 | 308 | for r in issue_node.findall('CVE_ID_LIST/CVE_ID'): |
307 | 309 | self.node = r |
308 | self.ref.append(self.get_text_from_subnode('ID')) | |
310 | self.cve.append(self.get_text_from_subnode('ID')) | |
309 | 311 | for r in issue_node.findall('BUGTRAQ_ID_LIST/BUGTRAQ_ID'): |
310 | 312 | self.node = r |
311 | 313 | self.ref.append('bid-' + self.get_text_from_subnode('ID')) |
360 | 362 | severity=v.severity, |
361 | 363 | resolution=v.solution if v.solution else '', |
362 | 364 | desc=v.desc, |
363 | external_id=v.external_id) | |
365 | external_id=v.external_id, | |
366 | cve=v.cve) | |
364 | 367 | |
365 | 368 | else: |
366 | 369 | web = False |
393 | 396 | severity=v.severity, |
394 | 397 | desc=v.desc, |
395 | 398 | resolution=v.solution if v.solution else '', |
396 | external_id=v.external_id) | |
399 | external_id=v.external_id, | |
400 | cve=v.cve) | |
397 | 401 | |
398 | 402 | else: |
399 | 403 | self.createAndAddVulnToService( |
404 | 408 | severity=v.severity, |
405 | 409 | desc=v.desc, |
406 | 410 | resolution=v.solution if v.solution else '', |
407 | external_id=v.external_id) | |
411 | external_id=v.external_id, | |
412 | cve=v.cve) | |
408 | 413 | |
409 | 414 | del parser |
410 | 415 |
140 | 140 | self.desc += "\nContext: " + self.context if self.context else "" |
141 | 141 | |
142 | 142 | self.ref = [] |
143 | if self.cve: | |
144 | self.ref = self.cve.split(",") | |
145 | 143 | |
146 | 144 | def get_text_from_subnode(self, subnode_xpath_expr): |
147 | 145 | """ |
189 | 187 | for k, vulns in item.ports.items(): |
190 | 188 | if k: |
191 | 189 | for v in vulns: |
190 | cve = v.cve.split(",") if v.cve else [] | |
192 | 191 | web = False |
193 | 192 | s_id = self.createAndAddServiceToHost(h_id, 'unknown', v.protocol.lower(), ports=[str(v.port)], |
194 | 193 | status="open") |
199 | 198 | if web: |
200 | 199 | v_id = self.createAndAddVulnWebToService(h_id, s_id, v.name, ref=v.ref, |
201 | 200 | website=hostname, severity=v.severity, |
202 | resolution=v.solution, desc=v.desc) | |
201 | resolution=v.solution, desc=v.desc, cve=cve) | |
203 | 202 | else: |
204 | 203 | v_id = self.createAndAddVulnToService(h_id, s_id, v.name, ref=v.ref, |
205 | 204 | severity=v.severity, resolution=v.solution, |
206 | desc=v.desc) | |
205 | desc=v.desc, cve=cve) | |
207 | 206 | else: |
208 | 207 | for v in vulns: |
208 | cve = v.cve.split(",") if v.cve else [] | |
209 | 209 | v_id = self.createAndAddVulnToHost(h_id, v.name, ref=v.ref, severity=v.severity, |
210 | resolution=v.solution, desc=v.desc) | |
210 | resolution=v.solution, desc=v.desc, cve=cve) | |
211 | 211 | del parser |
212 | 212 | |
213 | 213 |
55 | 55 | elif os.path.isdir(filename): |
56 | 56 | shutil.rmtree(filename) |
57 | 57 | except Exception as e: |
58 | self.logger.error("Error on delete file: (%s) [%s]", filename, e) | |
58 | self.logger.error(f"Error on delete file: ({filename}) [{e}]") | |
59 | 59 | |
60 | 60 | def parseOutputString(self, output): |
61 | 61 | for vuln_json in filter(lambda x: x != '', output.split("\n")): |
71 | 71 | for name, vuln_info in vulns.items(): |
72 | 72 | description = vuln_info.get('summary') |
73 | 73 | references = vuln_info.get('references') |
74 | self.createAndAddVulnToService(h_id, s_id, name, desc=description, severity=0, ref=references) | |
75 | ||
74 | self.createAndAddVulnToService(h_id, s_id, name, desc=description, severity=0, ref=references, cve=name) | |
76 | 75 | |
77 | 76 | def processCommandString(self, username, current_path, command_string): |
78 | 77 | """ |
29 | 29 | list_vuln = [] |
30 | 30 | if scan_result: |
31 | 31 | for scan in scan_result: |
32 | try: | |
33 | host = self.get_host(scan['server_info']['server_location']) | |
34 | except KeyError: | |
35 | host = {} | |
36 | ||
37 | try: | |
38 | certif = self.get_certification(scan['scan_commands_results']['certificate_info']) | |
39 | except KeyError: | |
40 | certif = {} | |
41 | ||
42 | if not scan['scan_commands']: | |
32 | server_info = scan.get('server_info', scan).get('server_location') | |
33 | host = self.get_host(server_info) if server_info else {} | |
34 | ||
35 | scan_commands_results = scan.get('scan_commands_results', scan.get("scan_result", {})) | |
36 | ||
37 | if len(scan_commands_results) > 0: | |
38 | commands = [] | |
39 | for command in scan_commands_results: | |
40 | if command.find("cipher") >= 0 and scan_commands_results[command].get('result', True): | |
41 | commands.append(command) | |
42 | ciphers = self.get_cipher(scan_commands_results, commands) | |
43 | else: | |
43 | 44 | ciphers = {} |
44 | else: | |
45 | commands = [] | |
46 | for command in scan['scan_commands']: | |
47 | if command.find("cipher") >= 0: | |
48 | commands.append(command) | |
49 | ciphers = self.get_cipher(scan['scan_commands_results'], commands) | |
50 | ||
51 | try: | |
52 | heartbleed = self.get_heartbleed(scan['scan_commands_results']['heartbleed']) | |
53 | except KeyError: | |
54 | heartbleed = {} | |
55 | ||
56 | try: | |
57 | openssl_ccs = self.get_openssl_ccs(scan['scan_commands_results']['openssl_ccs_injection']) | |
58 | except KeyError: | |
59 | openssl_ccs = {} | |
60 | ||
45 | ||
46 | ||
47 | certificate_info = scan_commands_results.get('certificate_info') | |
48 | certif = self.get_certification(certificate_info) if certificate_info else {} | |
49 | ||
50 | heartbleed_reulsts = scan_commands_results.get('heartbleed') | |
51 | heartbleed = self.get_heartbleed(heartbleed_reulsts) if heartbleed_reulsts else {} | |
52 | ||
53 | openssl_ccs_injection = scan_commands_results.get('openssl_ccs_injection') | |
54 | openssl_ccs = self.get_openssl_ccs(openssl_ccs_injection) if openssl_ccs_injection else {} | |
61 | 55 | json_vuln = { |
62 | 56 | "host_info": host, |
63 | 57 | "certification": certif, |
64 | 58 | "ciphers": ciphers, |
65 | 59 | "heartbleed": heartbleed, |
66 | "openssl_ccs":openssl_ccs | |
60 | "openssl_ccs": openssl_ccs | |
67 | 61 | } |
68 | 62 | |
69 | 63 | list_vuln.append(json_vuln) |
90 | 84 | return json_host |
91 | 85 | |
92 | 86 | def get_certification(self, certificate): |
93 | certif_deploy = certificate['certificate_deployments'] | |
87 | certif_deploy = certificate.get('certificate_deployments', certificate.get('result')) | |
88 | certif_deploy = certif_deploy.get('certificate_deployments', [{}]) if isinstance(certif_deploy, dict) else [{}] | |
94 | 89 | send_certif = certif_deploy[0].get('leaf_certificate_subject_matches_hostname', True) |
95 | 90 | |
96 | 91 | if not send_certif: |
97 | why = certif_deploy[0]['received_certificate_chain'][0]['subject']['rfc4514_string'] | |
92 | subject = certif_deploy[0]['received_certificate_chain'][0]['subject'] | |
93 | why = subject.get('rfc4514_string') | |
94 | if not why: | |
95 | why = subject.get('attributes', {}).get('rfc4514_string') | |
96 | hostname_used_for_server = certificate.get('hostname_used_for_server_name_indication', certificate.get("result", {}) | |
97 | .get('hostname_used_for_server_name_indication', 'Not hostname')) | |
98 | 98 | json_certif = { |
99 | 99 | "name": "SSL/TLS Certificate Mismatch", |
100 | 100 | "desc": "The software communicates with a host that provides a certificate, but the software does not properly ensure that the certificate is actually associated with that host.", |
101 | "data": f"Certificate {why} does not match server hostname {certificate.get('hostname_used_for_server_name_indication', 'Not hostname')}", | |
101 | "data": f"Certificate {why} does not match server hostname {hostname_used_for_server}", | |
102 | 102 | "impact": {"integrity": True}, |
103 | 103 | "ref": ["https://cwe.mitre.org/data/definitions/297.html"], |
104 | 104 | "external_id": "CWE-297", |
148 | 148 | |
149 | 149 | def get_heartbleed(self, heartbleed): |
150 | 150 | json_heartbleed = {} |
151 | if heartbleed.get('is_vulnerable_to_heartbleed', False): | |
151 | vulnerable_to_heartbleed = heartbleed.get('is_vulnerable_to_heartbleed', heartbleed.get('result', False)) | |
152 | vulnerable_to_heartbleed = vulnerable_to_heartbleed.get('is_vulnerable_to_heartbleed') if vulnerable_to_heartbleed else False | |
153 | if vulnerable_to_heartbleed: | |
152 | 154 | json_heartbleed = { |
153 | 155 | "name": "OpenSSL Heartbleed", |
154 | 156 | "desc": "OpenSSL versions 1.0.1 through 1.0.1f contain a flaw in its implementation of the TLS/DTLS heartbeat functionality. This flaw allows an attacker to retrieve private memory of an application that uses the vulnerable OpenSSL library in chunks of 64k at a time. Note that an attacker can repeatedly leverage the vulnerability to retrieve as many 64k chunks of memory as are necessary to retrieve the intended secrets. The sensitive information that may be retrieved using this vulnerability include:\n\n Primary key material (secret keys)\n Secondary key material (user names and passwords used by vulnerable services)\n Protected content (sensitive data used by vulnerable services)\n Collateral (memory addresses and content that can be leveraged to bypass exploit mitigations)\n Exploit code is publicly available for this vulnerability. Additional details may be found in CERT/CC Vulnerability Note VU#720951.", |
161 | 163 | |
162 | 164 | def get_openssl_ccs(self, openssl_ccs): |
163 | 165 | json_openssl_ccs = {} |
164 | if openssl_ccs.get('is_vulnerable_to_ccs_injection', False): | |
166 | is_vulnerable_to_ccs_injection = openssl_ccs.get('is_vulnerable_to_ccs_injection', openssl_ccs.get('result', False)) | |
167 | is_vulnerable_to_ccs_injection = is_vulnerable_to_ccs_injection.get('is_vulnerable_to_ccs_injection', False) if is_vulnerable_to_ccs_injection else False | |
168 | if is_vulnerable_to_ccs_injection: | |
165 | 169 | json_openssl_ccs = { |
166 | 170 | "name": "OpenSSL CCS Injection", |
167 | 171 | "desc": 'OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCipherSpec messages, which allows man-in-the-middle attackers to trigger use of a zero-length master key in certain OpenSSL-to-OpenSSL communications, and consequently hijack sessions or obtain sensitive information, via a crafted TLS handshake, aka the "CCS Injection" vulnerability."', |
181 | 185 | self.name = "Sslyze Json" |
182 | 186 | self.plugin_version = "0.1" |
183 | 187 | self.version = "3.4.5" |
184 | self.json_keys = {'server_scan_results', 'sslyze_url'} | |
188 | self.json_keys = {'server_scan_results', 'sslyze_url', 'sslyze_version'} | |
185 | 189 | self._command_regex = re.compile(r'^(sudo sslyze|sslyze|\.\/sslyze)\s+.*?') |
186 | 190 | self.json_arg_re = re.compile(r"^.*(--json_out\s*[^\s]+).*$") |
187 | 191 | self._use_temp_file = True |
54 | 54 | data=data, |
55 | 55 | resolution=vulnerability['topFix']['fixResolution'], |
56 | 56 | ref=refs, |
57 | severity=vulnerability['severity']) | |
57 | severity=vulnerability['severity'], | |
58 | cve=[vulnerability['name']]) | |
58 | 59 | else: |
59 | 60 | self.createAndAddVulnToHost(host_id, |
60 | 61 | name=vulnerability['name'], |
75 | 76 | name=vulnerability['vulnerability'], |
76 | 77 | desc=vulnerability['description'], |
77 | 78 | ref=[vulnerability['link']], |
78 | severity=vulnerability['severity'] | |
79 | severity=vulnerability['severity'], | |
80 | cve=[vulnerability['vulnerability']] | |
79 | 81 | ) |
82 | ||
80 | 83 | elif 'package' in vulnerability: |
81 | 84 | host_id = self.createAndAddHost(vulnerability['feed_group']) |
82 | 85 | service_id = self.createAndAddServiceToHost( |
89 | 92 | service_id, |
90 | 93 | name=f'{vulnerability["vuln"]} {vulnerability["package_name"]}', |
91 | 94 | ref=[vulnerability['url']], |
92 | severity=vulnerability['severity'] | |
95 | severity=vulnerability['severity'], | |
96 | cve=[vulnerability['vuln']] | |
93 | 97 | ) |
94 | 98 | |
95 | 99 |
55 | 55 | |
56 | 56 | def list_report_files(): |
57 | 57 | report_filenames = os.walk(REPORTS_SUMMARY_DIR) |
58 | for root, directory, filenames in report_filenames: | |
58 | for plugin_folder, directory, filenames in report_filenames: | |
59 | 59 | if '.git' in directory or 'faraday_plugins_tests' in directory: |
60 | 60 | continue |
61 | 61 | for filename in filenames: |
62 | 62 | if filename in BLACK_LIST: |
63 | 63 | continue |
64 | if '.git' in root: | |
64 | if '.git' in plugin_folder: | |
65 | 65 | continue |
66 | 66 | if not filename.endswith('_summary.json'): |
67 | yield os.path.join(root, filename) | |
67 | yield Path(plugin_folder).name, os.path.join(plugin_folder, filename) | |
68 | 68 | |
69 | 69 | |
70 | 70 | def is_valid_ipv4_address(address): |
95 | 95 | def test_reports_collection_exists(): |
96 | 96 | assert os.path.isdir(REPORTS_SUMMARY_DIR) is True, "Please clone the report-collection repo!" |
97 | 97 | |
98 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
99 | def test_autodetected_on_all_report_collection(report_filename): | |
98 | @pytest.mark.parametrize("report_filename_and_folder", list_report_files()) | |
99 | def test_autodetected_on_all_report_collection(report_filename_and_folder): | |
100 | plugin_folder = report_filename_and_folder[0] | |
101 | report_filename = report_filename_and_folder[1] | |
100 | 102 | plugin: PluginBase = get_plugin_from_cache(report_filename) |
101 | 103 | assert plugin, report_filename |
104 | assert plugin.id == plugin_folder | |
102 | 105 | |
103 | 106 | |
104 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
105 | def test_schema_on_all_reports(report_filename): | |
107 | @pytest.mark.parametrize("report_filename_and_folder", list_report_files()) | |
108 | def test_schema_on_all_reports(report_filename_and_folder): | |
109 | report_filename = report_filename_and_folder[1] | |
106 | 110 | plugin, plugin_json = get_report_json_from_cache(report_filename) |
107 | 111 | if plugin_json: |
108 | 112 | serializer = BulkCreateSchema() |
113 | 117 | |
114 | 118 | |
115 | 119 | @pytest.mark.skip(reason="Skip validate ip format") |
116 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
117 | def test_host_ips_all_reports(report_filename): | |
120 | @pytest.mark.parametrize("report_filename_and_folder", list_report_files()) | |
121 | def test_host_ips_all_reports(report_filename_and_folder): | |
122 | report_filename = report_filename_and_folder[1] | |
118 | 123 | plugin, plugin_json = get_report_json_from_cache(report_filename) |
119 | 124 | if plugin_json: |
120 | 125 | if plugin.id not in SKIP_IP_PLUGINS: |
122 | 127 | assert is_valid_ip_address(host['ip']) is True |
123 | 128 | |
124 | 129 | |
125 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
126 | def test_summary_reports(report_filename): | |
130 | @pytest.mark.parametrize("report_filename_and_folder", list_report_files()) | |
131 | def test_summary_reports(report_filename_and_folder): | |
132 | report_filename = report_filename_and_folder[1] | |
127 | 133 | plugin, plugin_json = get_report_json_from_cache(report_filename) |
128 | 134 | if plugin_json: |
129 | 135 | summary_file = f"{os.path.splitext(report_filename)[0]}_summary.json" |
142 | 148 | |
143 | 149 | |
144 | 150 | @pytest.mark.performance |
145 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
146 | def test_detected_tools_on_all_report_collection(report_filename, benchmark): | |
151 | @pytest.mark.parametrize("report_filename_and_folder", list_report_files()) | |
152 | def test_detected_tools_on_all_report_collection(report_filename_and_folder, benchmark): | |
153 | report_filename = report_filename_and_folder[1] | |
147 | 154 | plugins_manager = PluginsManager() |
148 | 155 | analyzer = ReportAnalyzer(plugins_manager) |
149 | 156 | plugin: PluginBase = analyzer.get_plugin(report_filename) |