New upstream release.
Kali Janitor
2 years ago
0 | Nov 19th, 2021 |
0 | FIX extrainfo of netsparker plugin |
0 | Add nuclei_legacy plugin |
0 | 1.5.7 [Nov 19th, 2021]: | |
1 | --- | |
2 | * FIX extrainfo of netsparker plugin | |
3 | * Add nuclei_legacy plugin | |
4 | ||
0 | 5 | 1.5.6 [Nov 10th, 2021]: |
1 | 6 | --- |
2 | 7 | * FIX issue with acunetix plugin |
8 | ||
3 | 9 | * FIX typo in nikto plugin |
4 | 10 | |
5 | 11 | 1.5.5 [Oct 21st, 2021]: |
0 | faraday-plugins (1.5.7-0kali1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Kali Janitor <[email protected]> Mon, 22 Nov 2021 05:53:27 -0000 | |
5 | ||
0 | 6 | faraday-plugins (1.5.6-0kali1) kali-dev; urgency=medium |
1 | 7 | |
2 | 8 | * New upstream version 1.5.6 |
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 | |
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 |
116 | 118 | |
117 | 119 | self.extra = [] |
118 | 120 | for v in item_node.findall("extrainformation/info"): |
119 | name = v.get('name') | |
120 | if name: | |
121 | self.extra.append("{name}:{v.text}") | |
121 | name_tag = v.find('name') | |
122 | value_tag = v.find('value') | |
123 | if name_tag is not None: | |
124 | self.extra.append(f"{name_tag.text}:{value_tag.text}") | |
122 | 125 | |
123 | 126 | self.node = item_node |
124 | 127 | self.node = item_node.find("classification") |
189 | 192 | self.options = None |
190 | 193 | |
191 | 194 | def parseOutputString(self, output): |
192 | parser = NetsparkerXmlParser(output) | |
195 | parser = NetsparkerXmlParser(output, self) | |
193 | 196 | host_names_resolve = {} |
194 | 197 | for i in parser.items: |
195 | 198 |
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) |