New upstream version 1.8.0
Sophie Brun
1 year, 5 months ago
0 | default_stages: [commit] | |
1 | repos: | |
2 | - repo: https://github.com/pre-commit/pre-commit-hooks | |
3 | rev: v3.1.0 | |
4 | hooks: | |
5 | - id: trailing-whitespace | |
6 | - id: end-of-file-fixer | |
7 | - id: check-json | |
8 | - id: check-yaml | |
9 | args: [ --unsafe ] | |
10 | - id: debug-statements | |
11 | - repo: https://gitlab.com/pycqa/flake8 | |
12 | rev: 3.8.3 | |
13 | hooks: | |
14 | - id: flake8 | |
15 | additional_dependencies: [flake8-typing-imports==1.9.0] | |
16 | - repo: https://github.com/ikamensh/flynt/ | |
17 | rev: '0.56' | |
18 | hooks: | |
19 | - id: flynt | |
20 | args: [ -df ] | |
21 | - repo: https://github.com/asottile/pyupgrade | |
22 | rev: v2.29.0 | |
23 | hooks: | |
24 | - id: pyupgrade | |
25 | args: [ --py3-plus , --py36-plus] |
0 | [Add] Add invicti plugin |
0 | [Add] Add nessus_sc plugin |
0 | [FIX] Remove cvss_vector from refs in nexpose_full |
0 | Add new identifier_tag to nikto plugin |
0 | [FIX] Now plugins check if ref field is already a dictionary |
0 | [MOD] Improve grype plugin for dockers images and change report_belong_to method for | |
1 | json plugins to check if json_keys is a list, in that case iterate the list and try if | |
2 | any of them create a match. |
0 | Oct 26th, 2022 |
0 | 1.8.0 [Oct 26th, 2022]: | |
1 | --- | |
2 | * [Add] Add invicti plugin | |
3 | * [Add] Add nessus_sc plugin | |
4 | * [FIX] Remove cvss_vector from refs in nexpose_full | |
5 | * Add new identifier_tag to nikto plugin | |
6 | * [FIX] Now plugins check if ref field is already a dictionary | |
7 | * [MOD] Improve grype plugin for dockers images and change report_belong_to method for | |
8 | json plugins to check if json_keys is a list, in that case iterate the list and try if | |
9 | any of them create a match. | |
10 | ||
0 | 11 | 1.7.0 [Sep 5th, 2022]: |
1 | 12 | --- |
2 | 13 | * Add CWE to PluginBase. The plugins that have this implemented are the following: |
427 | 427 | """ |
428 | 428 | refs = [] |
429 | 429 | if ref: |
430 | refs = [{'name': url, 'type': 'other'} for url in ref] | |
430 | for r in ref: | |
431 | if isinstance(r, dict): | |
432 | refs.append(r) | |
433 | else: | |
434 | refs.append({'name': r, 'type': 'other'}) | |
431 | 435 | return refs |
432 | 436 | |
433 | 437 | def createAndAddVulnToHost(self, host_id, name, desc="", ref=None, |
739 | 743 | if super().report_belongs_to(**kwargs): |
740 | 744 | if file_json_keys is None: |
741 | 745 | file_json_keys = set() |
742 | match = self.json_keys.issubset(file_json_keys) | |
743 | self.logger.debug(f"Json Keys Match: [{file_json_keys} =/in {self.json_keys}] -> {match}") | |
746 | if isinstance(self.json_keys, list): | |
747 | for jk in self.json_keys: | |
748 | match = jk.issubset(file_json_keys) | |
749 | self.logger.debug(f"Json Keys Match: [{file_json_keys} =/in {jk}] -> {match}") | |
750 | if match: | |
751 | break | |
752 | else: | |
753 | match = self.json_keys.issubset(file_json_keys) | |
754 | self.logger.debug(f"Json Keys Match: [{file_json_keys} =/in {self.json_keys}] -> {match}") | |
744 | 755 | return match |
745 | 756 | |
746 | 757 |
24 | 24 | self._command_regex = re.compile(r'^grype\s+.*') |
25 | 25 | self._use_temp_file = True |
26 | 26 | self._temp_file_extension = "json" |
27 | self.json_keys = {"source", "matches", "descriptor"} | |
27 | self.json_keys = [{"source", "matches", "descriptor"}, {"matches", "image"}] | |
28 | 28 | |
29 | 29 | def parseOutputString(self, output, debug=True): |
30 | 30 | grype_json = json.loads(output) |
31 | if "userInput" in grype_json["source"]["target"]: | |
31 | if "userInput" in grype_json.get("source", {"target": ""}).get("target"): | |
32 | 32 | name = grype_json["source"]["target"]["userInput"] |
33 | host_type = grype_json['source']['type'] | |
34 | elif "tags" in grype_json.get("image", {}): | |
35 | name = " ".join(grype_json["image"]["tags"]) | |
36 | host_type = "Docker Image" | |
33 | 37 | else: |
34 | 38 | name = grype_json["source"]["target"] |
35 | host_id = self.createAndAddHost(name, description=f"Type: {grype_json['source']['type']}") | |
39 | host_type = grype_json['source']['type'] | |
40 | host_id = self.createAndAddHost(name, description=f"Type: {host_type}") | |
36 | 41 | for match in grype_json['matches']: |
37 | 42 | name = match.get('vulnerability').get('id') |
38 | 43 | cve = name |
39 | 44 | references = [] |
40 | if match["relatedVulnerabilities"]: | |
45 | if match.get("relatedVulnerabilities"): | |
41 | 46 | description = match["relatedVulnerabilities"][0].get('description') |
42 | 47 | references.append(match["relatedVulnerabilities"][0]["dataSource"]) |
43 | 48 | related_vuln = match["relatedVulnerabilities"][0] |
44 | 49 | severity = related_vuln["severity"].lower().replace("negligible", "info") |
45 | for url in related_vuln["urls"]: | |
46 | references.append(url) | |
50 | if related_vuln.get("links"): | |
51 | for url in related_vuln["links"]: | |
52 | references.append(url) | |
53 | else: | |
54 | for url in related_vuln["urls"]: | |
55 | references.append(url) | |
47 | 56 | else: |
48 | description = match.get('vulnerability').get('description') | |
57 | description = match.get('vulnerability').get('description', "Issues provided no description") | |
49 | 58 | severity = match.get('vulnerability').get('severity').lower().replace("negligible", "info") |
50 | for url in match.get('vulnerability').get('urls'): | |
51 | references.append(url) | |
59 | if match.get('vulnerability').get("links"): | |
60 | for url in match.get('vulnerability')["links"]: | |
61 | references.append(url) | |
62 | else: | |
63 | for url in match.get('vulnerability')["urls"]: | |
64 | references.append(url) | |
52 | 65 | if not match['artifact'].get('metadata'): |
53 | 66 | data = f"Artifact: {match['artifact']['name']}" \ |
54 | 67 | f"Version: {match['artifact']['version']} " \ |
60 | 73 | f"Type: {match['artifact']['type']}" |
61 | 74 | elif "VirtualPath" in match['artifact']['metadata']: |
62 | 75 | data = f"Artifact: {match['artifact']['name']} [{match['artifact']['metadata']['VirtualPath']}] " \ |
76 | f"Version: {match['artifact']['version']} " \ | |
77 | f"Type: {match['artifact']['type']}" | |
78 | else: | |
79 | data = f"Artifact: {match['artifact']['name']}" \ | |
63 | 80 | f"Version: {match['artifact']['version']} " \ |
64 | 81 | f"Type: {match['artifact']['type']}" |
65 | 82 | self.createAndAddVulnToHost(host_id, |
0 | from typing import List | |
1 | ||
2 | ||
3 | class Cvss3: | |
4 | def __init__(self, node): | |
5 | self.node = node | |
6 | ||
7 | @property | |
8 | def vector(self) -> str: | |
9 | return self.node.find('vector').text | |
10 | ||
11 | ||
12 | class Reference: | |
13 | def __init__(self, node): | |
14 | self.node = node | |
15 | ||
16 | @property | |
17 | def owasp(self) -> str: | |
18 | return self.node.find('owasp').text | |
19 | ||
20 | @property | |
21 | def wasc(self) -> str: | |
22 | return self.node.find('wasc').text | |
23 | ||
24 | @property | |
25 | def cwe(self) -> str: | |
26 | return self.node.find('cwe').text | |
27 | ||
28 | @property | |
29 | def capec(self) -> str: | |
30 | return self.node.find('capec').text | |
31 | ||
32 | @property | |
33 | def pci32(self) -> str: | |
34 | return self.node.find('pci32').text | |
35 | ||
36 | @property | |
37 | def hipaa(self) -> str: | |
38 | return self.node.find('hipaa').text | |
39 | ||
40 | @property | |
41 | def owasppc(self) -> str: | |
42 | return self.node.find('owasppc').text | |
43 | ||
44 | @property | |
45 | def cvss3(self) -> Cvss3: | |
46 | return Cvss3(self.node.find("cvss31")) | |
47 | ||
48 | ||
49 | class Request: | |
50 | def __init__(self, node): | |
51 | self.node = node | |
52 | ||
53 | @property | |
54 | def method(self) -> str: | |
55 | return self.node.find("method").text | |
56 | ||
57 | @property | |
58 | def content(self) -> str: | |
59 | return self.node.find("content").text | |
60 | ||
61 | ||
62 | class Response: | |
63 | def __init__(self, node): | |
64 | self.node = node | |
65 | ||
66 | @property | |
67 | def content(self) -> str: | |
68 | return self.node.find("content").text | |
69 | ||
70 | ||
71 | class Vulnerability: | |
72 | def __init__(self, node): | |
73 | self.node = node | |
74 | ||
75 | @property | |
76 | def look_id(self) -> str: | |
77 | return self.node.find('LookupId').text | |
78 | ||
79 | @property | |
80 | def url(self) -> str: | |
81 | return self.node.find("url").text | |
82 | ||
83 | @property | |
84 | def name(self) -> str: | |
85 | return self.node.find('name').text | |
86 | ||
87 | @property | |
88 | def severity(self) -> str: | |
89 | return self.node.find('severity').text | |
90 | ||
91 | @property | |
92 | def confirmed(self) -> str: | |
93 | return self.node.find('confirmed').text | |
94 | ||
95 | @property | |
96 | def description(self) -> str: | |
97 | return self.node.find('description').text | |
98 | ||
99 | @property | |
100 | def http_request(self) -> Request: | |
101 | return Request(self.node.find("http-request")) | |
102 | ||
103 | @property | |
104 | def http_response(self) -> Response: | |
105 | return Response(self.node.find("http-response")) | |
106 | ||
107 | @property | |
108 | def impact(self) -> str: | |
109 | return self.node.find("impact").text | |
110 | ||
111 | @property | |
112 | def remedial_actions(self) -> str: | |
113 | return self.node.find("remedial-actions").text | |
114 | ||
115 | @property | |
116 | def remedial_procedure(self) -> str: | |
117 | return self.node.find("remedial-procedure").text | |
118 | ||
119 | @property | |
120 | def classification(self) -> Reference: | |
121 | return Reference(self.node.find("classification")) | |
122 | ||
123 | ||
124 | class Target: | |
125 | def __init__(self, node): | |
126 | self.node = node | |
127 | ||
128 | @property | |
129 | def scan_id(self) -> str: | |
130 | return self.node.find("scan-id").text | |
131 | ||
132 | @property | |
133 | def url(self) -> str: | |
134 | return self.node.find("url").text | |
135 | ||
136 | ||
137 | class Invicti: | |
138 | def __init__(self, node): | |
139 | self.node = node | |
140 | ||
141 | @property | |
142 | def target(self) -> Target: | |
143 | return Target(self.node.find('target')) | |
144 | ||
145 | @property | |
146 | def vulnerabilities(self) -> List[Vulnerability]: | |
147 | return [Vulnerability(i) for i in self.node.findall('vulnerabilities/vulnerability')] |
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 | from urllib.parse import urlsplit | |
7 | from bs4 import BeautifulSoup | |
8 | from lxml import etree | |
9 | ||
10 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
11 | from faraday_plugins.plugins.repo.invicti.DTO import Invicti | |
12 | ||
13 | __author__ = "Gonzalo Martinez" | |
14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
15 | __credits__ = ["Gonzalo Martinez"] | |
16 | __version__ = "1.0.0" | |
17 | __maintainer__ = "Gonzalo Martinez" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class InvictiXmlParser: | |
23 | """ | |
24 | The objective of this class is to parse a xml file generated by | |
25 | the acunetix tool. | |
26 | ||
27 | @param invicti_xml_filepath A proper xml generated by acunetix | |
28 | """ | |
29 | ||
30 | def __init__(self, xml_output): | |
31 | ||
32 | tree = self.parse_xml(xml_output) | |
33 | self.invicti = Invicti(tree) | |
34 | ||
35 | @staticmethod | |
36 | def parse_xml(xml_output): | |
37 | """ | |
38 | Open and parse an xml file. | |
39 | ||
40 | TODO: Write custom parser to just read the nodes that we need instead | |
41 | of reading the whole file. | |
42 | ||
43 | @return xml_tree An xml tree instance. None if error. | |
44 | """ | |
45 | ||
46 | try: | |
47 | parser = etree.XMLParser(recover=True) | |
48 | tree = etree.fromstring(xml_output, parser=parser) | |
49 | except SyntaxError as err: | |
50 | print(f"SyntaxError: {err}. {xml_output}") | |
51 | return None | |
52 | ||
53 | return tree | |
54 | ||
55 | ||
56 | class InvictiPlugin(PluginXMLFormat): | |
57 | """ | |
58 | Example plugin to parse invicti output. | |
59 | """ | |
60 | ||
61 | def __init__(self, *arg, **kwargs): | |
62 | super().__init__(*arg, **kwargs) | |
63 | self.identifier_tag = "invicti-enterprise" | |
64 | self.id = "Invicti" | |
65 | self.name = "Invicti XML Output Plugin" | |
66 | self.plugin_version = "1.0.0" | |
67 | self.version = "9" | |
68 | self.framework_version = "1.0.0" | |
69 | self.options = None | |
70 | self._current_output = None | |
71 | self.target = None | |
72 | ||
73 | def parseOutputString(self, output): | |
74 | """ | |
75 | This method will discard the output the shell sends, it will read it | |
76 | from the xml where it expects it to be present. | |
77 | ||
78 | NOTE: if 'debug' is true then it is being run from a test case and the | |
79 | output being sent is valid. | |
80 | """ | |
81 | parser = InvictiXmlParser(output) | |
82 | url = urlsplit(parser.invicti.target.url) | |
83 | ip = self.resolve_hostname(url.netloc) | |
84 | h_id = self.createAndAddHost(ip) | |
85 | s_id = self.createAndAddServiceToHost(h_id, url.scheme, ports=433) | |
86 | for vulnerability in parser.invicti.vulnerabilities: | |
87 | vuln = {"name": vulnerability.name, "severity": vulnerability.severity, | |
88 | "confirmed": vulnerability.confirmed, | |
89 | "desc": BeautifulSoup(vulnerability.description, features="lxml").text, | |
90 | "path": vulnerability.url.replace(parser.invicti.target.url, ""), | |
91 | "external_id": vulnerability.look_id, | |
92 | "resolution": BeautifulSoup(vulnerability.remedial_procedure, features="lxml").text} | |
93 | if vulnerability.classification: | |
94 | references = [] | |
95 | if vulnerability.classification.owasp: | |
96 | references.append("OWASP" + vulnerability.classification.owasp) | |
97 | if vulnerability.classification.wasc: | |
98 | references.append("WASC" + vulnerability.classification.wasc) | |
99 | if vulnerability.classification.cwe: | |
100 | vuln["cwe"] = "CWE-" + vulnerability.classification.cwe | |
101 | if vulnerability.classification.capec: | |
102 | references.append("CAPEC" + vulnerability.classification.capec) | |
103 | if vulnerability.classification.pci32: | |
104 | references.append("PCI32" + vulnerability.classification.pci32) | |
105 | if vulnerability.classification.hipaa: | |
106 | references.append("HIPAA" + vulnerability.classification.hipaa) | |
107 | if vulnerability.classification.owasppc: | |
108 | references.append("OWASPPC" + vulnerability.classification.owasppc) | |
109 | if vulnerability.classification.cvss3.node is not None: | |
110 | vuln["cvss3"] = {"vector_string": vulnerability.classification.cvss3.vector} | |
111 | vuln["ref"] = references | |
112 | if vulnerability.http_response.node is not None: | |
113 | vuln["response"] = vulnerability.http_response.content | |
114 | if vulnerability.http_request.node is not None: | |
115 | vuln["request"] = vulnerability.http_request.content | |
116 | self.createAndAddVulnWebToService(h_id, s_id, **vuln) | |
117 | ||
118 | ||
119 | def createPlugin(*args, **kwargs): | |
120 | return InvictiPlugin(*args, **kwargs) |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 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) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | ||
7 | from faraday_plugins.plugins.plugin import PluginCSVFormat | |
8 | import csv | |
9 | import io | |
10 | ||
11 | ||
12 | __author__ = "Gonzalo Martinez" | |
13 | __copyright__ = "Copyright (c) 2019, Infobyte LLC" | |
14 | __credits__ = ["Gonzalo Martinez"] | |
15 | __license__ = "" | |
16 | __version__ = "1.0.0" | |
17 | __maintainer__ = "Gonzalo Martinez" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class NessusScPlugin(PluginCSVFormat): | |
23 | """ | |
24 | Example plugin to parse Nessus Sc output. | |
25 | """ | |
26 | ||
27 | def __init__(self, *arg, **kwargs): | |
28 | super().__init__(*arg, **kwargs) | |
29 | self.csv_headers = [{'Plugin', 'Plugin Name'}] | |
30 | self.id = "Nessus_sc" | |
31 | self.name = "Nessus Sc Output Plugin" | |
32 | self.plugin_version = "1.0.0" | |
33 | self.version = "1.0.0" | |
34 | self.framework_version = "1.0.0" | |
35 | ||
36 | def parseOutputString(self, output): | |
37 | try: | |
38 | reader = csv.DictReader(io.StringIO(output)) | |
39 | except: | |
40 | print("Error parser output") | |
41 | return None | |
42 | ||
43 | for row in reader: | |
44 | ip = row['IP Address'] | |
45 | hostname = row['DNS Name'] | |
46 | h_id = self.createAndAddHost(name=ip, hostnames=hostname) | |
47 | protocol = row['Protocol'] | |
48 | port = row['Port'] | |
49 | s_id = self.createAndAddServiceToHost(h_id, name=port, protocol=protocol, ports=port, status="open") | |
50 | name = row['Plugin Name'] | |
51 | severity = row['Severity'] | |
52 | description = row['Description'] | |
53 | vuln = {"name": name, "severity": severity, "desc": description} | |
54 | solution = row['Solution'] | |
55 | if solution: | |
56 | vuln["resolution"] = solution | |
57 | cvss3_vector = row['CVSS V3 Vector'] | |
58 | if cvss3_vector: | |
59 | if not cvss3_vector.startswith("CVSS:3.0/"): | |
60 | cvss3_vector = "CVSS:3.0/"+cvss3_vector | |
61 | vuln["cvss3"] = {"vector_string": cvss3_vector} | |
62 | cvss2_vector = row['CVSS V2 Vector'] | |
63 | if cvss2_vector: | |
64 | vuln["cvss2"] = {"vector_string": cvss2_vector} | |
65 | external_ref = row["See Also"] | |
66 | cross_ref = row["Cross References"] | |
67 | references = [] | |
68 | if external_ref: | |
69 | references.append(external_ref) | |
70 | if cross_ref: | |
71 | references.append(cross_ref) | |
72 | vuln["ref"] = references | |
73 | cve = row['CVE'] | |
74 | if cve: | |
75 | vuln["cve"] = cve | |
76 | self.createAndAddVulnToService(h_id, s_id, **vuln) | |
77 | ||
78 | ||
79 | def createPlugin(*args, **kwargs): | |
80 | return NessusScPlugin(*args, **kwargs) |
140 | 140 | vuln = { |
141 | 141 | 'desc': "", |
142 | 142 | 'name': vulnDef.get('title'), |
143 | 'refs': ["vector: " + vector, vid], | |
143 | 'refs': [], | |
144 | 144 | 'resolution': "", |
145 | 145 | 'severity': "", |
146 | 146 | 'tags': list(), |
171 | 171 | vuln['refs'].append(nameMalware) |
172 | 172 | if item.tag == 'references': |
173 | 173 | for ref in list(item): |
174 | if ref.text: | |
175 | rf = ref.text.strip() | |
176 | check = CVE_regex.search(rf.upper()) | |
177 | if check: | |
178 | vuln["CVE"].append(check.group()) | |
179 | else: | |
180 | vuln['refs'].append(rf) | |
174 | if not ref.text: | |
175 | continue | |
176 | source = "" | |
177 | if "source" in ref.attrib: | |
178 | source = ref.attrib['source'] + ": " | |
179 | rf = ref.text.strip() | |
180 | check = CVE_regex.search(rf.upper()) | |
181 | if check: | |
182 | vuln["CVE"].append(check.group()) | |
183 | else: | |
184 | if rf.isnumeric(): | |
185 | rf = source + rf | |
186 | vuln['refs'].append(rf) | |
181 | 187 | if item.tag == 'solution': |
182 | 188 | for htmlType in list(item): |
183 | 189 | vuln['resolution'] += self.parse_html_type(htmlType) |
99 | 99 | self.node = item_node |
100 | 100 | |
101 | 101 | self.osvdbid = [ |
102 | "OSVDB-ID: " + self.node.get('osvdbid')] if self.node.get('osvdbid') != "0" else [] | |
102 | "OSVDB-ID: " + self.node.get('osvdbid')] if self.node.get('osvdbid', "0") != "0" else [] | |
103 | 103 | |
104 | 104 | self.namelink = self.get_text_from_subnode('namelink') |
105 | 105 | self.iplink = self.get_text_from_subnode('iplink') |
206 | 206 | |
207 | 207 | def __init__(self, *arg, **kwargs): |
208 | 208 | super().__init__(*arg, **kwargs) |
209 | self.identifier_tag = "niktoscan" | |
209 | self.identifier_tag = ["niktoscan", "niktoscans"] | |
210 | 210 | self.id = "Nikto" |
211 | 211 | self.name = "Nikto XML Output Plugin" |
212 | 212 | self.plugin_version = "0.0.2" |