New upstream version 3.7.0
Sophie Brun
5 years ago
15 | 15 | * Micaela Ranea Sánchez |
16 | 16 | * Sebastian Kulesz |
17 | 17 | * Eric Horvat |
18 | * Jorge Luis González Iznaga | |
19 | * Javier Montilva | |
18 | 20 | |
19 | 21 | Project contributors |
20 | 22 |
0 | * Add vulnerability preview to status report | |
1 | * Update Fierce Plugin. Import can be done from GTK console. | |
2 | * Update Goohost plugin and now Faraday imports Goohost .txt report. | |
3 | * Update plugin for support WPScan v-3.4.5 | |
4 | * Update Qualysguard plugin to its 8.17.1.0.2 version | |
5 | * Update custom fields with Searcher | |
6 | * Update Recon-ng Plugin so that it accepts XML reports | |
7 | * Add postres version to status-change command | |
8 | * Couchdb configuration section will not be added anymore | |
9 | * Add unit test for config/default.xml |
6 | 6 | |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | ||
10 | ||
11 | 3.7 [Apr 3rd, 2019]: | |
12 | --- | |
13 | * New feature vulnerability preview to view vulnerability data. | |
14 | * Update Fierce Plugin. Import can be done from GTK console. | |
15 | * Update Goohost plugin and now Faraday imports Goohost .txt report. | |
16 | * Update plugin for support WPScan v-3.4.5 | |
17 | * Update Qualysguard plugin to its 8.17.1.0.2 version | |
18 | * Update custom fields with Searcher | |
19 | * Update Recon-ng Plugin so that it accepts XML reports | |
20 | * Add postres version to status-change command | |
21 | * Couchdb configuration section will not be added anymore | |
22 | * Add unit test for config/default.xml | |
9 | 23 | |
10 | 24 | |
11 | 25 | 3.6 [Feb 21th, 2019]: |
6 | 6 | |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | ||
10 | ||
11 | 3.7 [Apr 3rd, 2019]: | |
12 | --- | |
13 | * New feature vulnerability preview to view vulnerability data. | |
14 | * Update Fierce Plugin. Import can be done from GTK console. | |
15 | * Update Goohost plugin and now Faraday imports Goohost .txt report. | |
16 | * Update plugin for support WPScan v-3.4.5 | |
17 | * Update Qualysguard plugin to its 8.17.1.0.2 version | |
18 | * Update custom fields with Searcher | |
19 | * Update Recon-ng Plugin so that it accepts XML reports | |
20 | * Add postres version to status-change command | |
21 | * Couchdb configuration section will not be added anymore | |
22 | * Add unit test for config/default.xml | |
9 | 23 | |
10 | 24 | |
11 | 25 | 3.6 [Feb 21th, 2019]: |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>3.6.0</version> | |
4 | <version>3.7.0</version> | |
5 | 5 | <debug_status>0</debug_status> |
6 | 6 | <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font> |
7 | 7 | <home_path>~/</home_path> |
0 | """add markdown column to exectuive reports | |
1 | ||
2 | Revision ID: 5272b3f5a820 | |
3 | Revises: 2ca03a8feef5 | |
4 | Create Date: 2019-03-27 19:26:28.354078+00:00 | |
5 | ||
6 | """ | |
7 | from alembic import op | |
8 | import sqlalchemy as sa | |
9 | ||
10 | ||
11 | # revision identifiers, used by Alembic. | |
12 | revision = '5272b3f5a820' | |
13 | down_revision = '2ca03a8feef5' | |
14 | branch_labels = None | |
15 | depends_on = None | |
16 | ||
17 | ||
18 | def upgrade(): | |
19 | op.add_column('executive_report', sa.Column('markdown', sa.Boolean(), nullable=False, server_default='False')) | |
20 | ||
21 | ||
22 | def downgrade(): | |
23 | op.drop_column('executive_report', 'markdown') |
11 | 11 | import re |
12 | 12 | import os |
13 | 13 | import sys |
14 | import random | |
14 | 15 | |
15 | 16 | current_path = os.path.abspath(os.getcwd()) |
16 | 17 | |
40 | 41 | """ |
41 | 42 | |
42 | 43 | def __init__(self, output): |
43 | ||
44 | 44 | self.target = None |
45 | 45 | self.items = [] |
46 | 46 | |
47 | r = re.search( | |
48 | "DNS Servers for ([\w\.-]+):\r\n([^$]+)Trying zone transfer first...", | |
47 | regex = re.search( | |
48 | "DNS Servers for ([\w\.-]+):\n([^$]+)Trying zone transfer first...", | |
49 | 49 | output) |
50 | 50 | |
51 | if r is not None: | |
52 | self.target = r.group(1) | |
53 | mstr = re.sub("\t", "", r.group(2)) | |
54 | self.dns = mstr.split() | |
55 | ||
56 | r = re.search( | |
57 | "Now performing [\d]+ test\(s\)...\r\n([^$]+)\x0D\nSubnets found ", | |
51 | if regex is not None: | |
52 | self.target = regex.group(1) | |
53 | mstr = re.sub("\t", "", regex.group(2)) | |
54 | self.dns = filter(None, mstr.splitlines()) | |
55 | ||
56 | regex = re.search( | |
57 | "Now performing [\d]+ test\(s\)...\n([^$]+)\nSubnets found ", | |
58 | 58 | output) |
59 | ||
60 | if r is not None: | |
61 | list = r.group(1).split("\r\n") | |
62 | for i in list: | |
59 | if regex is not None: | |
60 | hosts_list = regex.group(1).splitlines() | |
61 | for i in hosts_list: | |
63 | 62 | if i != "": |
64 | 63 | mstr = i.split("\t") |
65 | item = {'host': mstr[1], 'type': "A", 'ip': mstr[0]} | |
66 | self.items.append(item) | |
64 | host = mstr[1] | |
65 | record = "A" | |
66 | ip = mstr[0] | |
67 | self.add_host_info_to_items(ip, host, record) | |
67 | 68 | |
68 | 69 | self.isZoneVuln = False |
69 | output= output.replace('\\$', '') | |
70 | r = re.search( | |
70 | output = output.replace('\\$', '') | |
71 | regex = re.search( | |
71 | 72 | "Whoah, it worked - misconfigured DNS server found:([^$]+)\There isn't much point continuing, you have everything.", output) |
72 | 73 | |
73 | if r is not None: | |
74 | ||
74 | if regex is not None: | |
75 | 75 | self.isZoneVuln = True |
76 | list = r.group(1).split("\n") | |
77 | for i in list: | |
78 | ||
76 | dns_list = regex.group(1).splitlines() | |
77 | for i in dns_list: | |
79 | 78 | if i != "": |
80 | 79 | mstr = i.split() |
81 | 80 | if (mstr and mstr[0] != "" and len(mstr) > 3 and mstr[3] in valid_records): |
82 | item = {'host': mstr[0], | |
83 | 'type': mstr[3], 'ip': mstr[4]} | |
84 | self.items.append(item) | |
81 | host = mstr[0] | |
82 | record = mstr[3] | |
83 | ip = mstr[4] | |
84 | self.add_host_info_to_items(ip, host, record) | |
85 | ||
86 | def add_host_info_to_items(self, ip_address, hostname, record): | |
87 | data = {} | |
88 | exists = False | |
89 | for item in self.items: | |
90 | if ip_address in item['ip']: | |
91 | item['hosts'].append(hostname) | |
92 | exists = True | |
93 | ||
94 | if not exists: | |
95 | data['ip'] = ip_address | |
96 | data['hosts'] = [hostname] | |
97 | data['record'] = record | |
98 | self.items.append(data) | |
85 | 99 | |
86 | 100 | |
87 | 101 | class FiercePlugin(core.PluginBase): |
102 | 116 | r'^(sudo fierce|fierce|sudo fierce\.pl|fierce\.pl|perl fierce\.pl|\.\/fierce\.pl).*?') |
103 | 117 | global current_path |
104 | 118 | |
119 | self.xml_arg_re = re.compile(r"^.*(>\s*[^\s]+).*$") | |
120 | ||
105 | 121 | def canParseCommandString(self, current_input): |
106 | 122 | if self._command_regex.match(current_input.strip()): |
107 | 123 | return True |
110 | 126 | |
111 | 127 | def resolveCNAME(self, item, items): |
112 | 128 | for i in items: |
113 | if (i['host'] == item['ip']): | |
129 | if (item['ip'] in i['hosts']): | |
114 | 130 | item['ip'] = i['ip'] |
115 | 131 | return item |
116 | 132 | try: |
121 | 137 | |
122 | 138 | def resolveNS(self, item, items): |
123 | 139 | try: |
124 | item['host'] = item['ip'] | |
140 | item['hosts'][0] = item['ip'] | |
125 | 141 | item['ip'] = socket.gethostbyname(item['ip']) |
126 | 142 | except: |
127 | 143 | pass |
134 | 150 | |
135 | 151 | item['isResolver'] = False |
136 | 152 | item['isZoneVuln'] = False |
137 | if (item['type'] == "CNAME"): | |
153 | if (item['record'] == "CNAME"): | |
138 | 154 | self.resolveCNAME(item, parser.items) |
139 | if (item['type'] == "NS"): | |
155 | if (item['record'] == "NS"): | |
140 | 156 | self.resolveNS(item, parser.items) |
141 | 157 | item['isResolver'] = True |
142 | 158 | item['isZoneVuln'] = parser.isZoneVuln |
148 | 164 | item['ip'] = '' |
149 | 165 | |
150 | 166 | for item in parser.items: |
151 | ||
152 | 167 | if item['ip'] == "127.0.0.1" or item['ip'] == '': |
153 | 168 | continue |
154 | h_id = self.createAndAddHost(item['ip']) | |
155 | i_id = self.createAndAddInterface( | |
156 | h_id, | |
169 | h_id = self.createAndAddHost( | |
157 | 170 | item['ip'], |
158 | ipv4_address=item['ip'], | |
159 | hostname_resolution=[item['host']]) | |
171 | hostnames=item['hosts']) | |
160 | 172 | |
161 | 173 | if item['isResolver']: |
162 | s_id = self.createAndAddServiceToInterface( | |
174 | s_id = self.createAndAddServiceToHost( | |
163 | 175 | h_id, |
164 | i_id, | |
165 | 176 | "domain", |
166 | 177 | "tcp", |
167 | 178 | ports=['53']) |
175 | 186 | ref=["CVE-1999-0532"]) |
176 | 187 | |
177 | 188 | def processCommandString(self, username, current_path, command_string): |
178 | return None | |
189 | self._output_file_path = os.path.join( | |
190 | self.data_path, | |
191 | "%s_%s_output-%s.txt" % ( | |
192 | self.get_ws(), | |
193 | self.id, | |
194 | random.uniform(1, 10)) | |
195 | ) | |
196 | ||
197 | arg_match = self.xml_arg_re.match(command_string) | |
198 | ||
199 | if arg_match is None: | |
200 | return "%s > %s" % (command_string, self._output_file_path) | |
201 | else: | |
202 | return re.sub(arg_match.group(1), | |
203 | r"> %s" % self._output_file_path, | |
204 | command_string) | |
179 | 205 | |
180 | 206 | |
181 | 207 | def createPlugin(): |
33 | 33 | TODO: Test goohost output version. Handle what happens if the parser doesn't support it. |
34 | 34 | TODO: Test cases. |
35 | 35 | |
36 | @param goohost_filepath A proper simple report generated by goohost | |
37 | 36 | @param goohost_scantype You could select scan type ip, mail or host |
38 | 37 | """ |
39 | 38 | |
40 | def __init__(self, goohost_filepath, goohost_scantype): | |
41 | self.filepath = goohost_filepath | |
42 | self.scantype = goohost_scantype | |
39 | def __init__(self, output, goohost_scantype): | |
43 | 40 | |
44 | with open(self.filepath, "r") as f: | |
45 | ||
46 | line = f.readline() | |
47 | self.items = [] | |
48 | while line: | |
49 | if self.scantype == 'ip': | |
50 | minfo = line.split() | |
51 | item = {'host': minfo[0], 'ip': minfo[1]} | |
52 | elif self.scantype == 'host': | |
53 | line = line.strip() | |
54 | item = {'host': line, 'ip': self.resolve(line)} | |
55 | else: | |
56 | item = {'data': line} | |
57 | ||
58 | self.items.append(item) | |
59 | line = f.readline() | |
41 | self.items = [] | |
42 | lines = filter(None, output.split('\n')) | |
43 | for line in lines: | |
44 | if goohost_scantype == 'ip': | |
45 | data = line.split() | |
46 | item = {'host': data[0], 'ip': data[1]} | |
47 | self.add_host_info_to_items(item['ip'], item['host']) | |
48 | elif goohost_scantype == 'host': | |
49 | data = line.strip() | |
50 | item = {'host': data, 'ip': self.resolve(data)} | |
51 | self.add_host_info_to_items(item['ip'], item['host']) | |
52 | else: | |
53 | item = {'data': line} | |
60 | 54 | |
61 | 55 | def resolve(self, host): |
62 | 56 | try: |
64 | 58 | except: |
65 | 59 | pass |
66 | 60 | return host |
61 | ||
62 | def add_host_info_to_items(self, ip_address, hostname): | |
63 | data = {} | |
64 | exists = False | |
65 | for item in self.items: | |
66 | if ip_address in item['ip']: | |
67 | item['hosts'].append(hostname) | |
68 | exists = True | |
69 | ||
70 | if not exists: | |
71 | data['ip'] = ip_address | |
72 | data['hosts'] = [hostname] | |
73 | self.items.append(data) | |
67 | 74 | |
68 | 75 | |
69 | 76 | class GoohostPlugin(core.PluginBase): |
82 | 89 | self._current_path = None |
83 | 90 | self._command_regex = re.compile( |
84 | 91 | r'^(sudo goohost\.sh|goohost\.sh|sh goohost\.sh|\.\/goohost\.sh).*?') |
85 | self.scantype = "host" | |
86 | 92 | self.host = None |
87 | 93 | |
88 | 94 | global current_path |
89 | 95 | self.output_path = None |
96 | self._command_string = None | |
90 | 97 | |
91 | 98 | def parseOutputString(self, output, debug=False): |
92 | 99 | """ |
93 | This method will discard the output the shell sends, it will read it from | |
94 | the xml where it expects it to be present. | |
100 | This method will check if the import was made through the console or by importing a Goohost report. | |
101 | ||
102 | Import from Console:The method will take the path of the report generated by Goohost from the output the shell sends and will read | |
103 | the information from the txt where it expects it to be present. | |
104 | ||
105 | Import from Report: The method receives the output of the txt report as parameter. | |
106 | ||
107 | self.scantype defines the method used to generate the Goohost report | |
95 | 108 | |
96 | 109 | NOTE: if 'debug' is true then it is being run from a test case and the |
97 | 110 | output being sent is valid. |
98 | 111 | """ |
99 | 112 | |
100 | if self.output_path is None: | |
101 | mypath = re.search("Results saved in file (\S+)", output) | |
102 | if mypath is not None: | |
103 | self.output_path = self._current_path + "/" + mypath.group(1) | |
104 | else: | |
105 | return False | |
113 | if self._command_string: | |
114 | # Import from console | |
115 | self.scantype = self.define_scantype_by_command(self._command_string) | |
116 | report_output = output | |
117 | output = self.read_output_file(report_output) | |
118 | else: | |
119 | # Import from report | |
120 | self.scantype = self.define_scantype_by_output(output) | |
106 | 121 | |
107 | 122 | if debug: |
108 | 123 | parser = GoohostParser(output, self.scantype) |
109 | 124 | else: |
110 | if not os.path.exists(self.output_path): | |
111 | return False | |
112 | ||
113 | parser = GoohostParser(self.output_path, self.scantype) | |
114 | ||
125 | parser = GoohostParser(output, self.scantype) | |
115 | 126 | if self.scantype == 'host' or self.scantype == 'ip': |
116 | 127 | for item in parser.items: |
117 | h_id = self.createAndAddHost(item['ip']) | |
118 | i_id = self.createAndAddInterface( | |
119 | h_id, | |
128 | h_id = self.createAndAddHost( | |
120 | 129 | item['ip'], |
121 | ipv4_address=item['ip'], | |
122 | hostname_resolution=item['host']) | |
130 | hostnames=item['hosts']) | |
123 | 131 | |
124 | 132 | del parser |
125 | 133 | |
128 | 136 | Set output path for parser... |
129 | 137 | """ |
130 | 138 | self._current_path = current_path |
139 | self._command_string = command_string | |
131 | 140 | |
132 | def setHost(self): | |
133 | pass | |
141 | def define_scantype_by_command(self, command): | |
142 | method_regex = re.compile(r'-m (mail|host|ip)') | |
143 | method = method_regex.search(command) | |
144 | if method: | |
145 | return method.group(1) | |
146 | ||
147 | return 'host' | |
148 | ||
149 | def define_scantype_by_output(self, output): | |
150 | lines = output.split('\n') | |
151 | line = lines[0].split(' ') | |
152 | ||
153 | if len(line) == 1: | |
154 | return 'host' | |
155 | elif len(line) == 2: | |
156 | return 'ip' | |
157 | ||
158 | def read_output_file(self, report_path): | |
159 | mypath = re.search("Results saved in file (\S+)", report_path) | |
160 | if not mypath: | |
161 | return False | |
162 | else: | |
163 | self.output_path = self._current_path + "/" + mypath.group(1) | |
164 | if not os.path.exists(self.output_path): | |
165 | return False | |
166 | with open(self.output_path, 'r') as report: | |
167 | output = report.read() | |
168 | ||
169 | return output | |
134 | 170 | |
135 | 171 | |
136 | 172 | def createPlugin(): |
137 | 173 | return GoohostPlugin() |
138 | 174 | |
139 | 175 | if __name__ == '__main__': |
140 | parser = GoohostParser(sys.argv[1]) | |
176 | with open('/home/javier/Plugins/goohost/report-10071-google.com.txt','r') as report: | |
177 | output = report.read() | |
178 | parser = GoohostPlugin() | |
179 | parser.parseOutputString(output) | |
141 | 180 | for item in parser.items: |
142 | 181 | if item.status == 'up': |
143 | 182 | print item |
10 | 10 | import re |
11 | 11 | import os |
12 | 12 | import sys |
13 | import logging | |
13 | 14 | |
14 | 15 | try: |
15 | 16 | |
21 | 22 | ETREE_VERSION = ET.VERSION |
22 | 23 | |
23 | 24 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split('.')] |
25 | ||
26 | logger = logging.getLogger(__name__) | |
24 | 27 | |
25 | 28 | current_path = os.path.abspath(os.getcwd()) |
26 | 29 | |
68 | 71 | """ |
69 | 72 | |
70 | 73 | def __init__(self, xml_output): |
71 | ||
72 | 74 | tree, type_report = self.parse_xml(xml_output) |
73 | 75 | |
74 | 76 | if not tree or type_report is None: |
104 | 106 | type_report = None |
105 | 107 | |
106 | 108 | except SyntaxError, err: |
107 | self.devlog('SyntaxError: %s. %s' % (err, xml_output)) | |
109 | logger.error('SyntaxError: %s.' % (err)) | |
108 | 110 | return None, None |
109 | 111 | |
110 | 112 | return tree, type_report |
253 | 255 | self.node = item_node |
254 | 256 | self.ip = item_node.get('value') |
255 | 257 | self.os = self.get_text_from_subnode('OS') |
256 | ||
258 | self.hostname = self.get_hostname(item_node) | |
257 | 259 | self.vulns = self.getResults(item_node) |
258 | 260 | |
259 | 261 | def getResults(self, tree): |
266 | 268 | for self.issues in tree.findall('INFOS/CAT'): |
267 | 269 | for v in self.issues.findall('INFO'): |
268 | 270 | yield ResultsScanReport(v, self.issues) |
271 | for self.issues in tree.findall('SERVICES/CAT'): | |
272 | for v in self.issues.findall('SERVICE'): | |
273 | yield ResultsScanReport(v, self.issues) | |
274 | for self.issues in tree.findall('PRACTICES/CAT'): | |
275 | for v in self.issues.findall('PRACTICE'): | |
276 | yield ResultsScanReport(v, self.issues) | |
269 | 277 | |
270 | 278 | def get_text_from_subnode(self, subnode_xpath_expr): |
271 | 279 | """ |
278 | 286 | return sub_node.text |
279 | 287 | |
280 | 288 | return None |
289 | ||
290 | def get_hostname(self, node): | |
291 | hostname = node.get('name') | |
292 | ||
293 | if hostname == 'No registered hostname': | |
294 | return "" | |
295 | ||
296 | return hostname | |
281 | 297 | |
282 | 298 | |
283 | 299 | class ResultsScanReport(): |
293 | 309 | self.severity = self.node.get('severity') |
294 | 310 | self.title = self.get_text_from_subnode('TITLE') |
295 | 311 | self.cvss = self.get_text_from_subnode('CVSS_BASE') |
296 | self.pci = self.get_text_from_subnode('PCI_FLAG') | |
297 | 312 | self.diagnosis = self.get_text_from_subnode('DIAGNOSIS') |
298 | 313 | self.solution = self.get_text_from_subnode('SOLUTION') |
299 | 314 | self.result = self.get_text_from_subnode('RESULT') |
315 | self.consequence = self.get_text_from_subnode('CONSEQUENCE') | |
300 | 316 | |
301 | 317 | self.desc = cleaner_results(self.diagnosis) |
302 | 318 | if self.result: |
303 | 319 | self.desc += '\nResult: ' + cleaner_results(self.result) |
320 | else: | |
321 | self.desc += '' | |
322 | ||
323 | if self.consequence: | |
324 | self.desc += '\nConsequence: ' + cleaner_results(self.consequence) | |
304 | 325 | else: |
305 | 326 | self.desc += '' |
306 | 327 | |
312 | 333 | self.node = r |
313 | 334 | self.ref.append('bid-' + self.get_text_from_subnode('ID')) |
314 | 335 | |
336 | if self.cvss: | |
337 | self.ref.append('CVSS BASE: ' + self.cvss) | |
338 | ||
315 | 339 | def get_text_from_subnode(self, subnode_xpath_expr): |
316 | 340 | """ |
317 | 341 | Finds a subnode in the host node and the retrieves a value from it. |
336 | 360 | self.id = 'Qualysguard' |
337 | 361 | self.name = 'Qualysguard XML Output Plugin' |
338 | 362 | self.plugin_version = '0.0.2' |
339 | self.version = 'Qualysguard 2016 March ' | |
363 | self.version = 'Qualysguard 8.17.1.0.2' | |
340 | 364 | self.framework_version = '1.0.0' |
341 | 365 | self.options = None |
342 | 366 | self._current_output = None |
353 | 377 | parser = QualysguardXmlParser(output) |
354 | 378 | |
355 | 379 | for item in parser.items: |
356 | ||
357 | 380 | h_id = self.createAndAddHost( |
358 | 381 | item.ip, |
359 | item.os) | |
360 | ||
361 | i_id = self.createAndAddInterface( | |
362 | h_id, | |
363 | item.ip, | |
364 | ipv4_address=item.ip, | |
365 | hostname_resolution=item.ip) | |
382 | item.os, | |
383 | hostnames=[item.hostname]) | |
366 | 384 | |
367 | 385 | for v in item.vulns: |
368 | ||
369 | 386 | if v.port is None: |
370 | 387 | self.createAndAddVulnToHost( |
371 | 388 | h_id, |
378 | 395 | else: |
379 | 396 | |
380 | 397 | web = False |
381 | s_id = self.createAndAddServiceToInterface( | |
398 | s_id = self.createAndAddServiceToHost( | |
382 | 399 | h_id, |
383 | i_id, | |
384 | 400 | v.port, |
385 | 401 | v.protocol, |
386 | 402 | ports=[str(v.port)], |
402 | 418 | desc=v.desc, |
403 | 419 | resolution=v.solution if v.solution else '') |
404 | 420 | |
405 | n_id = self.createAndAddNoteToService( | |
406 | h_id, | |
407 | s_id, | |
408 | 'website', | |
409 | '') | |
410 | ||
411 | self.createAndAddNoteToNote( | |
412 | h_id, | |
413 | s_id, | |
414 | n_id, | |
415 | item.ip, | |
416 | '') | |
417 | ||
418 | 421 | else: |
419 | 422 | self.createAndAddVulnToService( |
420 | 423 | h_id, |
6 | 6 | ''' |
7 | 7 | import re |
8 | 8 | import json |
9 | import socket | |
9 | 10 | import logging |
11 | try: | |
12 | from lxml import etree as ET | |
13 | except ImportError: | |
14 | import xml.etree.ElementTree as ET | |
10 | 15 | |
11 | 16 | from plugins.core import PluginBase |
12 | 17 | |
22 | 27 | logger = logging.getLogger(__name__) |
23 | 28 | |
24 | 29 | |
30 | class ReconngParser(object): | |
31 | def __init__(self, output): | |
32 | self._format = self.report_format(output) | |
33 | self.hosts = [] | |
34 | self.vulns = [] | |
35 | ||
36 | if self._format == 'xml': | |
37 | self.parsable_tree = self.get_parseable_xml_output(output) | |
38 | self.parse_xml_report(self.parsable_tree) | |
39 | ||
40 | elif self._format == 'json': | |
41 | self.parse_json_report(output) | |
42 | ||
43 | def report_format(self, output): | |
44 | xml_format_regex = re.compile(r'^<(.*?)>') | |
45 | json_format_regex = re.compile(r'(^{)') | |
46 | ||
47 | if xml_format_regex.match(output): | |
48 | output_format = 'xml' | |
49 | elif json_format_regex.match(output): | |
50 | output_format = 'json' | |
51 | else: | |
52 | return False | |
53 | ||
54 | return output_format | |
55 | ||
56 | def get_parseable_xml_output(self, xml_output): | |
57 | try: | |
58 | tree = ET.fromstring(xml_output) | |
59 | return tree | |
60 | except IndexError: | |
61 | print "Syntax error" | |
62 | return None | |
63 | ||
64 | def parse_xml_report(self, tree): | |
65 | hosts_items = tree.xpath('//hosts/item') | |
66 | self.hosts_from_report(hosts_items) | |
67 | ||
68 | vulnerabilities_items = tree.xpath('//vulnerabilities/item') | |
69 | self.vulns_from_report(vulnerabilities_items) | |
70 | ||
71 | def parse_json_report(self, output): | |
72 | reconng_data = json.loads(output) | |
73 | hosts_items = reconng_data.get('hosts', '') | |
74 | self.hosts_from_report(hosts_items) | |
75 | ||
76 | vulns_items = reconng_data.get('vulnerabilities','') | |
77 | self.vulns_from_report(vulns_items) | |
78 | ||
79 | def hosts_from_report(self, hosts_items): | |
80 | for host in hosts_items: | |
81 | host_info = self.get_info_from_host_element(host) | |
82 | self.hosts.append(host_info) | |
83 | ||
84 | def vulns_from_report(self, vulns_items): | |
85 | for vuln in vulns_items: | |
86 | vuln_info = self.get_info_from_vuln_element(vuln) | |
87 | self.vulns.append(vuln_info) | |
88 | ||
89 | def get_info_from_host_element(self, element): | |
90 | info = {} | |
91 | if self._format == 'xml': | |
92 | info['host'] = element.find('host').text | |
93 | info['ip'] = element.find('ip_address').text | |
94 | ||
95 | elif self._format == 'json': | |
96 | info['host'] = element['host'] | |
97 | info['ip'] = element['ip_address'] | |
98 | ||
99 | return info | |
100 | ||
101 | def get_info_from_vuln_element(self, element): | |
102 | info = {} | |
103 | if self._format == 'xml': | |
104 | info['host'] = element.find('host').text | |
105 | info['reference'] = element.find('reference').text | |
106 | info['module'] = element.find('module').text | |
107 | info['example'] = element.find('example').text | |
108 | info['category'] = element.find('category').text | |
109 | elif self._format == 'json': | |
110 | info['category'] = element['category'] | |
111 | info['host'] = element['host'] | |
112 | info['module'] = element['module'] | |
113 | info['reference'] = element['reference'] | |
114 | info['example'] = element['example'] | |
115 | ||
116 | if 'XSS' in info['category']: | |
117 | info['severity'] = 'high' | |
118 | elif 'SSL' in info['category']: | |
119 | info['severity'] = 'med' | |
120 | else: | |
121 | info['severity'] = 'info' | |
122 | ||
123 | return info | |
124 | ||
125 | ||
25 | 126 | class ReconngPlugin(PluginBase): |
26 | 127 | """ |
27 | 128 | Example plugin to parse qualysguard output. |
32 | 133 | PluginBase.__init__(self) |
33 | 134 | self.id = 'Reconng' |
34 | 135 | self.name = 'Reconng XML Output Plugin' |
35 | self.plugin_version = '0.0.2' | |
136 | self.plugin_version = '0.0.3' | |
36 | 137 | self.version = '' |
37 | 138 | self.framework_version = '' |
38 | 139 | self.options = None |
39 | 140 | self._current_output = None |
40 | 141 | self._command_regex = re.compile( |
41 | 142 | r'records added to') |
42 | self.importing_report = True | |
43 | 143 | |
44 | def load_from_report(self, output): | |
45 | # TODO: add credentials and ports | |
46 | reconng_data = json.loads(output) | |
47 | hosts_id_mapper = {} | |
48 | for host in reconng_data.get('hosts', []): | |
144 | self.host_mapper = {} | |
145 | ||
146 | def parseOutputString(self, output): | |
147 | parser = ReconngParser(output) | |
148 | ||
149 | for host in parser.hosts: | |
49 | 150 | h_id = self.createAndAddHost( |
50 | host['ip_address'] or host['host'] | |
151 | host['ip'], | |
152 | hostnames=[host['host']] | |
51 | 153 | ) |
52 | hosts_id_mapper[host['host']] = h_id | |
154 | self.host_mapper[host['host']] = h_id | |
155 | for vuln in parser.vulns: | |
156 | if vuln['host'] not in self.host_mapper.keys(): | |
157 | ip = self.resolve_host(vuln['host']) | |
158 | h_id = self.createAndAddHost( | |
159 | ip, | |
160 | hostnames=[vuln['host']] | |
161 | ) | |
162 | self.host_mapper[vuln['host']] = h_id | |
163 | else: | |
164 | h_id = self.host_mapper[vuln['host']] | |
53 | 165 | |
54 | severity_mapper = { | |
55 | 'Information Disclosure': 'informational' | |
56 | } | |
57 | for vulnerability in reconng_data.get('vulnerabilities', []): | |
58 | if vulnerability['host'] not in hosts_id_mapper: | |
59 | logger.warn('Could not find host_id, skipping vulnerability') | |
60 | continue | |
61 | severity = 'info' | |
62 | if 'SSL' in vulnerability['category']: | |
63 | severity = 'med' | |
64 | 166 | self.createAndAddVulnToHost( |
65 | name='Recon-ng found: ' + vulnerability['example'], | |
66 | desc='Found by module: ' + vulnerability['module'], | |
67 | severity=severity, | |
68 | ref=[vulnerability['reference']], | |
69 | host_id=hosts_id_mapper[vulnerability['host']] | |
167 | name='Recon-ng found: ' + vuln['category'] + ' vulnerability', | |
168 | desc='Found by module: ' + vuln['module'], | |
169 | severity=vuln['severity'], | |
170 | ref=[vuln['reference']], | |
171 | host_id=h_id, | |
172 | data=vuln['example'] | |
70 | 173 | ) |
71 | 174 | |
72 | def load_from_shell(self, output): | |
73 | pass | |
175 | def processCommandString(self, username, current_path, command_string): | |
176 | return | |
74 | 177 | |
75 | def parseOutputString(self, output): | |
76 | if self.importing_report: | |
77 | self.load_from_report(output) | |
78 | else: | |
79 | self.load_from_shell(output) | |
178 | def resolve_host(self, host): | |
179 | try: | |
180 | return socket.gethostbyname(host) | |
181 | except: | |
182 | pass | |
183 | return host | |
80 | 184 | |
81 | def parseCommandString(self, username, current_path, command_string): | |
82 | self.importing_report = False | |
83 | 185 | |
84 | 186 | def createPlugin(): |
85 | return ReconngPlugin()⏎ | |
187 | return ReconngPlugin() | |
188 | ||
189 | if __name__ == '__main__': | |
190 | with open("~/results_hosts_vulns.xml", "r") as report: | |
191 | parser = ReconngParser(report.read()) | |
192 | # for item in parser.items: | |
193 | # if item.status == 'up': | |
194 | # print item |
9 | 9 | |
10 | 10 | from plugins import core |
11 | 11 | import re |
12 | import os | |
12 | 13 | import socket |
13 | 14 | import json |
14 | 15 | |
35 | 36 | core.PluginBase.__init__(self) |
36 | 37 | self.id = "wpscan" |
37 | 38 | self.name = "WPscan" |
38 | self.plugin_version = "0.0.1" | |
39 | self.version = "2.9.1" | |
39 | self.plugin_version = "0.2" | |
40 | self.version = "3.4.5" | |
40 | 41 | self._command_regex = re.compile( |
41 | 42 | r"^((sudo )?(ruby )?(\.\/)?(wpscan)(.rb)?)") |
42 | self.addSetting("WPscan path", str, "~/wpscan") | |
43 | self.wpPath = self.getSetting("WPscan path") | |
44 | self.themes = {} | |
45 | self.plugins = {} | |
46 | self.wpversion = '' | |
47 | self.risks = {'AUTHBYPASS' : 'high', | |
48 | 'BYPASS' : 'med', | |
49 | 'CSRF' : 'med', | |
50 | 'DOS' : 'med', | |
51 | 'FPD' : 'info', | |
52 | 'LFI' : 'high', | |
53 | 'MULTI' : 'unclassified', | |
54 | 'PRIVESC' : 'high', | |
55 | 'RCE' : 'critical', | |
56 | 'REDIRECT' : 'low', | |
57 | 'RFI' : 'critical', | |
58 | 'SQLI' : 'high', | |
59 | 'SSRF' : 'med', | |
60 | 'UNKNOWN' : 'unclassified', | |
61 | 'UPLOAD' : 'critical', | |
62 | 'XSS' : 'high', | |
63 | 'XXE' : 'high' | |
64 | } | |
43 | self.wpPath = self.get_wpscan_filepath() | |
44 | self.addSetting("WPscan path", str, self.wpPath) | |
45 | self.themes = {} | |
46 | self.plugins = {} | |
47 | self.wpversion = '' | |
48 | self.risks = {'AUTHBYPASS': 'high', | |
49 | 'BYPASS': 'med', | |
50 | 'CSRF': 'med', | |
51 | 'DOS': 'med', | |
52 | 'FPD': 'info', | |
53 | 'LFI': 'high', | |
54 | 'MULTI': 'unclassified', | |
55 | 'OBJECTINJECTION': 'med', | |
56 | 'PRIVESC': 'high', | |
57 | 'RCE': 'critical', | |
58 | 'REDIRECT': 'low', | |
59 | 'RFI': 'critical', | |
60 | 'SQLI': 'high', | |
61 | 'SSRF': 'med', | |
62 | 'UNKNOWN': 'unclassified', | |
63 | 'UPLOAD': 'critical', | |
64 | 'XSS': 'high', | |
65 | 'XXE': 'high'} | |
66 | ||
67 | def get_wpscan_filepath(self): | |
68 | home = os.path.expanduser("~") | |
69 | ||
70 | wpscan_path = os.path.join(home, '.wpscan') | |
71 | if os.path.exists(wpscan_path): | |
72 | return wpscan_path | |
73 | else: | |
74 | return None | |
75 | ||
76 | def search_file_in_wpscan_folder(self, wp_file): | |
77 | db_path = os.path.join(self.wpPath, 'db', wp_file) | |
78 | data_path = os.path.join(self.wpPath, 'data', wp_file) | |
79 | if os.path.exists(db_path): | |
80 | return db_path | |
81 | elif os.path.exists(data_path): | |
82 | return data_path | |
65 | 83 | |
66 | 84 | def getPort(self, host, proto): |
67 | 85 | p = re.search(r"\:([0-9]+)\/", host) |
70 | 88 | elif proto == 'https': |
71 | 89 | return 443 |
72 | 90 | else: |
73 | return 80 | |
74 | ||
91 | return 80 | |
75 | 92 | |
76 | 93 | def parseOutputWpscan(self, output): |
77 | sp = output.split('0m Name:') #cut by name | |
78 | for e in sp: | |
79 | if 'Title:' in e: | |
94 | sp = output.split('0m Name:') # cut by name | |
95 | for e in sp: | |
96 | if 'Title:' in e: | |
80 | 97 | if 'WordPress version' in e: |
81 | r = re.search(r'WordPress version (\d.\w)', e) #get wordpress version | |
98 | r = re.search(r'WordPress version (\d.\w)', e) # get wordpress version | |
82 | 99 | self.wpversion = r.group(1) |
83 | 100 | |
84 | 101 | elif 'wp-content/themes/' in e: |
85 | name = re.findall(r"Location: .+themes\/(.+)\/", e) # get theme name | |
86 | title = re.findall(r"Title: (.+)", e) # get vulnerabilities title | |
87 | self.themes[name[0]] = title #insert theme in dicc {'themeName' : ['titles', 'titles']} | |
102 | name = re.findall(r"Location: .+themes\/(.+)\/", e) # get theme name | |
103 | title = re.findall(r"Title: (.+)", e) # get vulnerabilities title | |
104 | self.themes[name[0]] = title # insert theme in dicc {'themeName' : ['titles', 'titles']} | |
88 | 105 | |
89 | 106 | else: |
90 | name = re.findall(r"Location: .+plugins\/(.+)\/", e) #get plugin name | |
91 | title = re.findall(r"Title: (.+)", e) #get vulnerabilities title | |
92 | self.plugins[name[0]] = title #insert plugin in dicc {'plugin' : ['titles', 'titles']} | |
93 | ||
94 | def addThemesOrPluginsVulns(self, db, dic, host_id, serv_id, domain, wp_url, name): | |
95 | with open(self.wpPath+'/data/'+db, "r") as data: | |
107 | name = re.findall(r"Location: .+plugins\/(.+)\/", e) # get plugin name | |
108 | title = re.findall(r"Title: (.+)", e) # get vulnerabilities title | |
109 | self.plugins[name[0]] = title # insert plugin in dicc {'plugin' : ['titles', 'titles']} | |
110 | ||
111 | def addThemesOrPluginsVulns(self, wpscan_db_filename, dic, host_id, serv_id, domain, wp_url, name): | |
112 | db_file_path = self.search_file_in_wpscan_folder(wpscan_db_filename) | |
113 | with open(db_file_path, "r") as data: | |
96 | 114 | j = json.load(data) |
97 | 115 | for p in dic: |
98 | 116 | for title in dic[p]: |
99 | for vuln in j[p]['vulnerabilities']: #iter vulnerabilities | |
100 | if vuln['title'] == title: # if output title is equal | |
101 | title = vuln['title'] #title | |
102 | risk = self.risks[vuln['vuln_type']] #vuln type (xss,rce,lfi,etc) - risk | |
103 | location = wp_url+'wp-content/'+name+'/'+p+'/' | |
104 | if vuln['references'].has_key('url') == True: #if references | |
117 | for vuln in j[p]['vulnerabilities']: # iter vulnerabilities | |
118 | if vuln['title'] == title: # if output title is equal | |
119 | title = vuln['title'] # title | |
120 | risk = self.risks[vuln['vuln_type']] # vuln type (xss,rce,lfi,etc) - risk | |
121 | location = wp_url+'wp-content/'+name+'/'+p+'/' | |
122 | if vuln['references'].has_key('url') == True: # if references | |
105 | 123 | refs = vuln['references']['url'] #references[] |
106 | 124 | else: |
107 | refs = [] #references null | |
108 | self.createAndAddVulnWebToService(host_id, serv_id, title, severity = risk, website = domain, ref = refs, path = location) | |
109 | ||
110 | ||
111 | def addWPVulns(self, db, version, host_id, serv_id, domain): | |
112 | with open(self.wpPath+'/data/'+db, "r") as data: | |
125 | refs = [] # references null | |
126 | self.createAndAddVulnWebToService( | |
127 | host_id, | |
128 | serv_id, | |
129 | title, | |
130 | severity=risk, | |
131 | website=domain, | |
132 | ref=refs, | |
133 | path=location) | |
134 | ||
135 | def addWPVulns(self, wpscan_db_filename, version, host_id, serv_id, domain): | |
136 | db_file_path = self.search_file_in_wpscan_folder(wpscan_db_filename) | |
137 | with open(db_file_path, "r") as data: | |
113 | 138 | j = json.load(data) |
114 | for vuln in j[version]['vulnerabilities']: #iter vulnerabilities | |
115 | title = vuln['title'] #title | |
116 | risk = self.risks[vuln['vuln_type']] #vuln type (xss,rce,lfi,etc) - risk | |
117 | if vuln['references'].has_key('url') == True: #if references | |
118 | refs = vuln['references']['url'] #references[] | |
139 | for vuln in j[version]['vulnerabilities']: # iter vulnerabilities | |
140 | title = vuln['title'] # title | |
141 | risk = self.risks[vuln['vuln_type']] # vuln type (xss,rce,lfi,etc) - risk | |
142 | if vuln['references'].has_key('url') == True: # if references | |
143 | refs = vuln['references']['url'] # references[] | |
119 | 144 | else: |
120 | refs = [] #references null | |
121 | self.createAndAddVulnWebToService(host_id, serv_id, title, severity = risk, website = domain, ref = refs) | |
122 | ||
145 | refs = [] # references null | |
146 | self.createAndAddVulnWebToService( | |
147 | host_id, | |
148 | serv_id, | |
149 | title, | |
150 | severity=risk, | |
151 | website=domain, | |
152 | ref=refs) | |
123 | 153 | |
124 | 154 | def parseOutputString(self, output, debug=False): |
125 | 155 | """Parses the output given as a string by the wpscan tool and creates |
126 | the appropiate hosts, interface, service and vulnerabilites. Return | |
156 | the appropiate hosts, service and vulnerabilites. Return | |
127 | 157 | nothing. |
128 | 158 | """ |
129 | 159 | self.parseOutputWpscan(output) |
130 | wp_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output) | |
160 | wp_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output) | |
131 | 161 | service, base_url = self.__get_service_and_url_from_output(output) |
132 | port = self.getPort(wp_url.group(1), service) | |
133 | host_ip = socket.gethostbyname_ex(base_url)[2][0] | |
134 | host_id = self.createAndAddHost(host_ip) | |
135 | interface_id = self.createAndAddInterface(host_id, host_ip, | |
136 | ipv4_address=host_ip, | |
137 | hostname_resolution=base_url) | |
138 | ||
139 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, | |
140 | service, "tcp", ports = [port]) | |
141 | ||
142 | potential_vulns = re.findall(r"(\[\!\].*)", output) | |
143 | for potential_vuln in potential_vulns: | |
144 | vuln_name, severity = self.__get_name_and_severity(potential_vuln) | |
145 | if vuln_name is not None: | |
146 | vuln = potential_vuln # they grow up so fast | |
147 | path = self.__get_path_from_vuln(vuln) | |
148 | self.createAndAddVulnWebToService(host_id, service_id, | |
149 | name=vuln_name, | |
150 | website=base_url, | |
151 | path=path, severity=severity) | |
152 | ||
153 | if len(self.plugins) > 0: | |
154 | self.addThemesOrPluginsVulns('plugins.json', self.plugins, host_id, service_id, base_url, wp_url.group(1), 'plugins') | |
155 | if len(self.wpversion) > 0: | |
156 | self.addWPVulns('wordpresses.json', self.wpversion, host_id, service_id, base_url) | |
157 | if len(self.themes) > 0: | |
158 | self.addThemesOrPluginsVulns('themes.json', self.themes, host_id, service_id, base_url, wp_url.group(1), 'themes') | |
159 | ||
160 | ||
162 | if service and base_url: | |
163 | port = self.getPort(wp_url.group(1), service) | |
164 | host_ip = socket.gethostbyname_ex(base_url)[2][0] | |
165 | host_id = self.createAndAddHost( | |
166 | host_ip, | |
167 | hostnames=[base_url]) | |
168 | ||
169 | service_id = self.createAndAddServiceToHost(host_id, | |
170 | service, | |
171 | "tcp", | |
172 | ports=[port]) | |
173 | ||
174 | potential_vulns = re.findall(r"(\[\!\].*)", output) | |
175 | for potential_vuln in potential_vulns: | |
176 | vuln_name, severity = self.__get_name_and_severity(potential_vuln) | |
177 | if vuln_name is not None: | |
178 | vuln = potential_vuln # they grow up so fast | |
179 | path = self.__get_path_from_vuln(vuln) | |
180 | self.createAndAddVulnWebToService(host_id, service_id, | |
181 | name=vuln_name, | |
182 | website=base_url, | |
183 | path=path, severity=severity) | |
184 | ||
185 | if len(self.plugins) > 0: | |
186 | self.addThemesOrPluginsVulns( | |
187 | 'plugins.json', | |
188 | self.plugins, | |
189 | host_id, | |
190 | service_id, | |
191 | base_url, | |
192 | wp_url.group(1), | |
193 | 'plugins') | |
194 | ||
195 | if len(self.wpversion) > 0: | |
196 | self.addWPVulns( | |
197 | 'wordpresses.json', | |
198 | self.wpversion, | |
199 | host_id, | |
200 | service_id, | |
201 | base_url) | |
202 | ||
203 | if len(self.themes) > 0: | |
204 | self.addThemesOrPluginsVulns( | |
205 | 'themes.json', | |
206 | self.themes, | |
207 | host_id, | |
208 | service_id, | |
209 | base_url, | |
210 | wp_url.group(1), | |
211 | 'themes') | |
212 | ||
161 | 213 | def __get_service_and_url_from_output(self, output): |
162 | 214 | """ Return the service (http or https) and the base URL (URL without |
163 | 215 | protocol) from a given string. In case more than one URL is found, |
164 | 216 | return the service and base_url of the first one, ignore others. |
165 | 217 | """ |
166 | 218 | search_url = re.search(r"URL: ((http[s]?)\:\/\/([\w\.]+)[.\S]+)", output) |
167 | service, base_url = search_url.group(2), search_url.group(3) | |
168 | return service, base_url | |
219 | if not search_url: | |
220 | return None, None | |
221 | else: | |
222 | service, base_url = search_url.group(2), search_url.group(3) | |
223 | return service, base_url | |
169 | 224 | |
170 | 225 | def __get_name_and_severity(self, potential_vuln): |
171 | 226 | """Regex the potential_vuln string against a regex with all |
0 | import requests | |
1 | import json | |
2 | ||
3 | ||
4 | class ApiError(Exception): | |
5 | def __init__(self, message): | |
6 | super(ApiError, self).__init__(message) | |
7 | ||
8 | ||
9 | class Structure: | |
10 | def __init__(self, **entries): | |
11 | self.__dict__.update(entries) | |
12 | ||
13 | @property | |
14 | def id(self): | |
15 | if hasattr(self, '_id'): | |
16 | return self._id | |
17 | return None | |
18 | ||
19 | @property | |
20 | def class_signature(self): | |
21 | if hasattr(self, 'type'): | |
22 | return self.type | |
23 | return None | |
24 | ||
25 | @property | |
26 | def parent_id(self): | |
27 | if hasattr(self, 'parent'): | |
28 | return self.parent | |
29 | return None | |
30 | ||
31 | def getMetadata(self): | |
32 | if hasattr(self, 'metadata'): | |
33 | return self.metadata | |
34 | return None | |
35 | ||
36 | ||
37 | class Api: | |
38 | def __init__(self, workspace, username, password, base='http://127.0.0.1:5985/_api/'): | |
39 | self.base = base | |
40 | self.workspace = workspace | |
41 | self.cookies = self.login(username, password) | |
42 | if self.cookies is None: | |
43 | raise UserWarning('Invalid username or password') | |
44 | ||
45 | def _url(self, path): | |
46 | return self.base + 'v2/' + path | |
47 | ||
48 | def _get(self, url, object_name): | |
49 | response = requests.get(url, cookies=self.cookies) | |
50 | if response.status_code == 401: | |
51 | raise ApiError('Unauthorized operation trying to get {}'.format(object_name)) | |
52 | if response.status_code != 200: | |
53 | raise ApiError('Cannot fetch {}'.format(object_name)) | |
54 | return json.loads(response.content) | |
55 | ||
56 | def _post(self, url, data, object_name): | |
57 | response = requests.post(url, json=data, cookies=self.cookies) | |
58 | if response.status_code == 401: | |
59 | raise ApiError('Unauthorized operation trying to create {}'.format(object_name)) | |
60 | if response.status_code != 201: | |
61 | raise ApiError('Unable to create {}'.format(object_name)) | |
62 | return json.loads(response.content) | |
63 | ||
64 | def _put(self, url, data, object_name): | |
65 | response = requests.put(url, json=data, cookies=self.cookies) | |
66 | if response.status_code == 401: | |
67 | raise ApiError('Unauthorized operation trying to update {}'.format(object_name)) | |
68 | if response.status_code != 200: | |
69 | raise ApiError('Unable to update {}'.format(object_name)) | |
70 | return json.loads(response.content) | |
71 | ||
72 | def _delete(self, url, object_name): | |
73 | response = requests.delete(url, cookies=self.cookies) | |
74 | if response.status_code == 401: | |
75 | raise ApiError('Unauthorized operation trying to delete {}'.format(object_name)) | |
76 | if response.status_code != 204: | |
77 | raise ApiError('Unable to delete {}'.format(object_name)) | |
78 | return response.ok | |
79 | ||
80 | def login(self, username, password): | |
81 | auth = {"email": username, "password": password} | |
82 | try: | |
83 | resp = requests.post(self.base + 'login', json=auth) | |
84 | if resp.status_code == 401: | |
85 | return None | |
86 | else: | |
87 | return resp.cookies | |
88 | except requests.adapters.ConnectionError: | |
89 | return None | |
90 | except requests.adapters.ReadTimeout: | |
91 | return None | |
92 | ||
93 | def get_vulnerabilities(self): | |
94 | return [Structure(**item['value']) for item in self._get(self._url('ws/{}/vulns'.format(self.workspace)), | |
95 | 'vulnerabilities')['vulnerabilities']] | |
96 | ||
97 | def update_vulnerability(self, vulnerability): | |
98 | return Structure(**self._put(self._url('ws/{}/vulns/{}/'.format(self.workspace, vulnerability.id)), | |
99 | vulnerability.__dict__, 'vulnerability')) | |
100 | ||
101 | def delete_vulnerability(self, vulnerability_id): | |
102 | return self._delete(self._url('ws/{}/vulns/{}/'.format(self.workspace, vulnerability_id)), 'vulnerability') | |
103 | ||
104 | def get_services(self): | |
105 | return [Structure(**item['value']) for item in self._get(self._url('ws/{}/services'.format(self.workspace)), | |
106 | 'services')['services']] | |
107 | ||
108 | def get_filtered_services(self, **params): | |
109 | services = self.get_services() | |
110 | filtered_services = [] | |
111 | for key, value in params.items(): | |
112 | for service in services: | |
113 | if hasattr(service, key) and \ | |
114 | (getattr(service, key, None) == value or str(getattr(service, key, None)) == value): | |
115 | filtered_services.append(service) | |
116 | return filtered_services | |
117 | ||
118 | def update_service(self, service): | |
119 | if isinstance(service.ports, int): | |
120 | service.ports = [service.ports] | |
121 | else: | |
122 | service.ports = [] | |
123 | return Structure(**self._put(self._url('ws/{}/services/{}/'.format(self.workspace, service.id)), | |
124 | service.__dict__, 'service')) | |
125 | ||
126 | def delete_service(self, service_id): | |
127 | return self._delete(self._url('ws/{}/services/{}/'.format(self.workspace, service_id)), 'service') | |
128 | ||
129 | def get_hosts(self): | |
130 | return [Structure(**item['value']) for item in self._get(self._url('ws/{}/hosts'.format(self.workspace)), | |
131 | 'hosts')['rows']] | |
132 | ||
133 | def get_filtered_hosts(self, **params): | |
134 | hosts = self.get_hosts() | |
135 | filtered_hosts = [] | |
136 | for key, value in params.items(): | |
137 | for host in hosts: | |
138 | if hasattr(host, key) and \ | |
139 | (getattr(host, key, None) == value or str(getattr(host, key, None)) == value): | |
140 | filtered_hosts.append(host) | |
141 | return filtered_hosts | |
142 | ||
143 | def update_host(self, host): | |
144 | return Structure(**self._put(self._url('ws/{}/hosts/{}/'.format(self.workspace, host.id)), | |
145 | host.__dict__, 'hosts')) | |
146 | ||
147 | def delete_host(self, host_id): | |
148 | return self._delete(self._url('ws/{}/hosts/{}/'.format(self.workspace, host_id)), 'host') | |
149 | ||
150 | def get_vulnerability_templates(self): | |
151 | return [Structure(**item['doc']) for item in self._get(self._url('vulnerability_template'), 'templates')['rows']] | |
152 | ||
153 | def get_filtered_templates(self, **params): | |
154 | templates = self.get_vulnerability_templates() | |
155 | filtered_templates = [] | |
156 | for key, value in params.items(): | |
157 | for template in templates: | |
158 | if hasattr(template, key) and \ | |
159 | (getattr(template, key, None) == value or str(getattr(template, key, None)) == value): | |
160 | filtered_templates.append(template) | |
161 | return filtered_templates |
17 | 17 | from difflib import SequenceMatcher |
18 | 18 | from email.mime.multipart import MIMEMultipart |
19 | 19 | from email.mime.text import MIMEText |
20 | import requests | |
21 | import json | |
22 | 20 | import ast |
23 | from config.configuration import getInstanceConfiguration | |
24 | from persistence.server import models | |
25 | from persistence.server import server | |
26 | from persistence.server.server import login_user | |
27 | from persistence.server.server_io_exceptions import ResourceDoesNotExist, ConflictInDatabase | |
28 | 21 | from validator import * |
29 | import urlparse | |
30 | ||
22 | from api import Api | |
31 | 23 | |
32 | 24 | logger = logging.getLogger('Faraday searcher') |
33 | 25 | |
34 | 26 | reload(sys) |
35 | 27 | sys.setdefaultencoding("utf-8") |
36 | ||
37 | CONF = getInstanceConfiguration() | |
38 | ||
39 | 28 | |
40 | 29 | mail_from = '' |
41 | 30 | mail_password = '' |
68 | 57 | return SequenceMatcher(None, a, b).ratio() |
69 | 58 | |
70 | 59 | |
71 | def get_cwe(data, _server='http://127.0.0.1:5985/'): | |
72 | logger.debug("Getting vulnerability templates from %s " % _server) | |
73 | try: | |
74 | url = urlparse.urljoin(_server, "_api/v2/vulnerability_template/") | |
75 | session_cookie = CONF.getDBSessionCookies() | |
76 | response = requests.request("GET", url, cookies=session_cookie) | |
77 | if response.status_code == 200: | |
78 | templates = json.loads(response.content) | |
79 | cwe = None | |
80 | for row in templates['rows']: | |
81 | doc = row['doc'] | |
82 | _id = doc['_id'] | |
83 | name = doc['name'] | |
84 | description = doc['description'] | |
85 | resolution = doc['resolution'] | |
86 | if str(_id) == data or name == data: | |
87 | cwe = { | |
88 | 'id': _id, | |
89 | 'name': name, | |
90 | 'description': description, | |
91 | 'resolution': resolution | |
92 | } | |
93 | break | |
94 | return cwe | |
95 | elif response.status_code == 401: | |
96 | logger.error('You are not authorized to get the vulnerability templates') | |
97 | return None | |
98 | else: | |
99 | logger.error('We can\'t get the vulnerability templates') | |
100 | return None | |
101 | ||
102 | except Exception as error: | |
103 | logger.error(error) | |
104 | return None | |
60 | def get_cwe(data): | |
61 | logger.debug("Getting vulnerability templates") | |
62 | templates = api.get_filtered_templates(id=data, name=data) | |
63 | if len(templates) > 0: | |
64 | return templates.pop() | |
65 | return None | |
105 | 66 | |
106 | 67 | |
107 | 68 | def is_same_level(model1, model2): |
233 | 194 | |
234 | 195 | def update_vulnerability(ws, vuln, key, value, _server): |
235 | 196 | if key == 'template': |
236 | cwe = get_cwe(value, _server) | |
197 | cwe = get_cwe(value) | |
237 | 198 | if cwe is None: |
238 | 199 | logger.error("%s: cwe not found" % value) |
239 | 200 | return False |
240 | 201 | |
241 | vuln.name = cwe['name'] | |
242 | vuln.description = cwe['description'] | |
243 | vuln.desc = cwe['description'] | |
244 | vuln.resolution = cwe['resolution'] | |
202 | vuln.name = cwe.name | |
203 | vuln.description = cwe.description | |
204 | vuln.desc = cwe.description | |
205 | vuln.resolution = cwe.resolution | |
245 | 206 | |
246 | 207 | logger.info("Applying template '%s' to vulnerability '%s' with id '%s'" % (value, vuln.name, vuln.id)) |
247 | 208 | |
259 | 220 | key = key.strip('-') |
260 | 221 | to_add = False |
261 | 222 | |
262 | field = get_field(vuln, key) | |
263 | if field is not None: | |
223 | is_custom_field = False | |
224 | if key in vuln.custom_fields: | |
225 | field = vuln.custom_fields | |
226 | is_custom_field = True | |
227 | else: | |
228 | field = get_field(vuln, key) | |
229 | ||
230 | if field is not None and is_custom_field is False: | |
264 | 231 | if isinstance(field, str) or isinstance(field, unicode): |
265 | 232 | setattr(vuln, key, value) |
266 | 233 | logger.info( |
274 | 241 | |
275 | 242 | logger.info(action) |
276 | 243 | |
244 | if field is not None and is_custom_field is True: | |
245 | vuln.custom_fields[key] = value | |
246 | logger.info( | |
247 | "Changing custom field %s to %s in vulnerability '%s' with id %s" % (key, value, vuln.name, vuln.id)) | |
248 | ||
277 | 249 | try: |
278 | if vuln.class_signature == "Vulnerability": | |
279 | models.update_vuln(ws, vuln) | |
280 | ||
281 | elif vuln.class_signature == "VulnerabilityWeb": | |
282 | models.update_vuln_web(ws, vuln) | |
283 | ||
284 | except ConflictInDatabase: | |
285 | logger.error("There was a conflict trying to save '%s' with ID: %s" % (vuln.name, vuln.id)) | |
286 | return False | |
250 | api.update_vulnerability(vuln) | |
287 | 251 | except Exception as error: |
288 | 252 | logger.error(error) |
289 | 253 | return False |
318 | 282 | |
319 | 283 | logger.info(action) |
320 | 284 | try: |
321 | models.update_service(ws, service, "") | |
285 | api.update_service(service) | |
322 | 286 | except Exception as error: |
323 | 287 | logger.error(error) |
324 | 288 | return False |
352 | 316 | |
353 | 317 | logger.info(action) |
354 | 318 | try: |
355 | models.update_host(ws, host, "") | |
319 | api.update_host(host) | |
356 | 320 | except Exception as error: |
357 | 321 | logger.error(error) |
358 | 322 | return False |
363 | 327 | |
364 | 328 | def get_parent(ws, parent_tag): |
365 | 329 | logger.debug("Getting parent") |
366 | try: | |
367 | parent = models.get_host(ws, parent_tag) or models.get_service(ws, parent_tag) | |
368 | except ResourceDoesNotExist: | |
369 | parent = models.get_hosts(ws, name=parent_tag) or models.get_services(ws, name=parent_tag) | |
370 | if len(parent) == 0: | |
371 | return None | |
372 | ||
373 | return parent | |
330 | return api.get_filtered_services(id=parent_tag, name=parent_tag) or \ | |
331 | api.get_filtered_hosts(id=parent_tag, name=parent_tag) | |
374 | 332 | |
375 | 333 | |
376 | 334 | def filter_objects_by_parent(_objects, parent): |
417 | 375 | return False |
418 | 376 | return True |
419 | 377 | |
378 | if isinstance(temp_value, int): | |
379 | return value == str(temp_value) | |
380 | ||
420 | 381 | if value.encode("utf-8") != temp_value.encode("utf-8"): |
421 | 382 | return False |
422 | 383 | return True |
499 | 460 | update_host(ws, obj, key, value) |
500 | 461 | |
501 | 462 | elif command == 'DELETE': |
502 | if obj.class_signature == 'VulnerabilityWeb': | |
503 | models.delete_vuln_web(ws, obj.id) | |
504 | logger.info(" Deleting vulnerability web '%s' with id '%s':" % (obj.name, obj.id)) | |
463 | if obj.class_signature == 'VulnerabilityWeb' or obj.class_signature == 'Vulnerability': | |
464 | api.delete_vulnerability(obj.id) | |
465 | logger.info("Deleting vulnerability '%s' with id '%s':" % (obj.name, obj.id)) | |
505 | 466 | insert_rule(rule['id'], command, obj, _objs_value) |
506 | 467 | |
507 | elif obj.class_signature == 'Vulnerability': | |
508 | models.delete_vuln(ws, obj.id) | |
509 | logger.info("Deleting vulnerability '%s' with id '%s':" % (obj.name, obj.id)) | |
510 | ||
511 | 468 | elif obj.class_signature == 'Service': |
512 | models.delete_service(ws, obj.id) | |
469 | api.delete_service(obj.id) | |
513 | 470 | logger.info("Deleting service '%s' with id '%s':" % (obj.name, obj.id)) |
514 | 471 | |
515 | 472 | elif obj.class_signature == 'Host': |
516 | models.delete_host(ws, obj.id) | |
473 | api.delete_host(obj.id) | |
517 | 474 | logger.info("Deleting host '%s' with id '%s':" % (obj.name, obj.id)) |
518 | 475 | |
519 | 476 | elif command == 'EXECUTE': |
542 | 499 | _vars = list(set(r)) |
543 | 500 | for var in _vars: |
544 | 501 | value = value_item[var] |
545 | rule_str = rule_str.replace('{{'+var+'}}', value) | |
502 | rule_str = rule_str.replace('{{' + var + '}}', value) | |
546 | 503 | |
547 | 504 | return ast.literal_eval(rule_str) |
548 | 505 | |
715 | 672 | ch = logging.StreamHandler() |
716 | 673 | ch.setLevel(numeric_level) |
717 | 674 | # create formatter and add it to the handlers |
718 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') | |
675 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s', | |
676 | datefmt='%m/%d/%Y %I:%M:%S %p') | |
719 | 677 | |
720 | 678 | fh.setFormatter(formatter) |
721 | 679 | ch.setFormatter(formatter) |
724 | 682 | logger.addHandler(ch) |
725 | 683 | |
726 | 684 | try: |
727 | session_cookie = login_user(_server, _user, _password) | |
728 | if not session_cookie: | |
729 | raise UserWarning('Invalid credentials!') | |
730 | else: | |
731 | CONF.setDBUser(_user) | |
732 | CONF.setDBSessionCookies(session_cookie) | |
733 | ||
734 | server.AUTH_USER = _user | |
735 | server.AUTH_PASS = _password | |
736 | server.SERVER_URL = _server | |
737 | server.FARADAY_UP = False | |
738 | ||
739 | 685 | logger.info('Started') |
740 | 686 | logger.info('Searching objects into workspace %s ' % workspace) |
741 | 687 | |
688 | global api | |
689 | api = Api(workspace, _user, _password) | |
690 | ||
742 | 691 | logger.debug("Getting hosts ...") |
743 | hosts = models.get_hosts(workspace) | |
692 | hosts = api.get_hosts() | |
744 | 693 | |
745 | 694 | logger.debug("Getting services ...") |
746 | services = models.get_services(workspace) | |
695 | services = api.get_services() | |
747 | 696 | |
748 | 697 | logger.debug("Getting vulnerabilities ...") |
749 | vulns = models.get_all_vulns(workspace) | |
698 | vulns = api.get_vulnerabilities() | |
750 | 699 | |
751 | 700 | if validate_rules(): |
752 | 701 | process_vulnerabilities(workspace, vulns, _server) |
758 | 707 | |
759 | 708 | logger.info('Finished') |
760 | 709 | |
761 | except ResourceDoesNotExist: | |
762 | logger.error("Resource not found") | |
763 | os.remove(lockf) | |
764 | exit(0) | |
765 | ||
766 | 710 | except Exception as errorMsg: |
767 | 711 | logger.error(errorMsg) |
768 | 712 | os.remove(lockf) |
667 | 667 | else: |
668 | 668 | flask.abort(404, "Vulnerability not found") |
669 | 669 | |
670 | @route('/<int:vuln_id>/attachments/', methods=['GET']) | |
671 | def get_attachments_by_vuln(self, workspace_name, vuln_id): | |
672 | workspace = self._get_workspace(workspace_name) | |
673 | vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join( | |
674 | Workspace).filter(VulnerabilityGeneric.id == vuln_id, | |
675 | Workspace.name == workspace.name).first() | |
676 | if vuln_workspace_check: | |
677 | files = db.session.query(File).filter_by(object_type='vulnerability', | |
678 | object_id=vuln_id).all() | |
679 | res = {} | |
680 | for file_obj in files: | |
681 | ret, errors = EvidenceSchema().dump(file_obj) | |
682 | if errors: | |
683 | raise ValidationError(errors, data=ret) | |
684 | res[file_obj.filename] = ret | |
685 | ||
686 | return flask.jsonify(res) | |
687 | else: | |
688 | flask.abort(404, "Vulnerability not found") | |
689 | ||
690 | ||
670 | 691 | @route('/<int:vuln_id>/attachment/<attachment_filename>/', methods=['DELETE']) |
671 | 692 | def delete_attachment(self, workspace_name, vuln_id, attachment_filename): |
672 | 693 | vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join( |
46 | 46 | def check_postgres(): |
47 | 47 | with app.app_context(): |
48 | 48 | try: |
49 | result = str(db.engine.execute("SELECT version()")) | |
50 | return result | |
49 | result = (str(db.session.query("version()").one()),db.session.query("current_setting('server_version_num')").one()) | |
50 | return result | |
51 | 51 | except sqlalchemy.exc.OperationalError: |
52 | 52 | return False |
53 | 53 | except sqlalchemy.exc.ArgumentError: |
188 | 188 | """Prints the status of PostgreSQL using check_postgres()""" |
189 | 189 | exit_code = 0 |
190 | 190 | result = check_postgres() |
191 | if result: | |
192 | print('[{green}+{white}] PostgreSQL is running'.\ | |
191 | print(result[0]) | |
192 | if result[1]<90400: | |
193 | print('[{red}-{white}] PostgreSQL is running, but needs to be 9.4 or newer, please update PostgreSQL'.\ | |
194 | format(red=Fore.RED, white=Fore.WHITE)) | |
195 | elif result: | |
196 | print('[{green}+{white}] PostgreSQL is running and up to date'.\ | |
193 | 197 | format(green=Fore.GREEN, white=Fore.WHITE)) |
194 | 198 | return exit_code |
195 | 199 | elif result == False: |
7 | 7 | certificate= |
8 | 8 | keyfile= |
9 | 9 | ;keyfile_pwd='' |
10 | ||
11 | [couchdb] | |
12 | host=localhost | |
13 | port=5984 | |
14 | ssl_port=6984 | |
15 | user= | |
16 | password= | |
17 | protocol=http | |
18 |
1792 | 1792 | title = BlankColumn(Text) |
1793 | 1793 | confirmed = Column(Boolean, nullable=False, default=False) |
1794 | 1794 | vuln_count = Column(Integer, default=0) # saves the amount of vulns when the report was generated. |
1795 | markdown = Column(Boolean, default=False, nullable=False) | |
1795 | 1796 | |
1796 | 1797 | workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) |
1797 | 1798 | workspace = relationship( |
301 | 301 | |
302 | 302 | .tab-pane-container { |
303 | 303 | width: 100%; |
304 | overflow-y: auto; | |
305 | max-height: calc(100% - 180px); | |
304 | 306 | } |
305 | 307 | |
306 | 308 | .btn-primary-white { |
423 | 425 | -moz-user-select: none; |
424 | 426 | -ms-user-select: none; |
425 | 427 | user-select: none; |
428 | top: -5px; | |
426 | 429 | } |
427 | 430 | |
428 | 431 | /* Hide the browser's default checkbox */ |
441 | 444 | |
442 | 445 | /* When the checkbox is checked, add a blue background */ |
443 | 446 | .chbox-container input:checked ~ .checkmark { |
444 | background-color: #008000; | |
447 | background-color: #488be6; | |
445 | 448 | } |
446 | 449 | |
447 | 450 | /* Create the checkmark/indicator (hidden when not checked) */ |
485 | 488 | li.nav-item.has-error:not(.active) a { |
486 | 489 | color: #a94442; |
487 | 490 | } |
491 | ||
492 | .nav-item{ | |
493 | margin-right: 10px; | |
494 | } | |
495 | ||
488 | 496 | .active-toggle{ |
489 | 497 | width: 3%; |
490 | 498 | text-align: center!important; |
557 | 565 | |
558 | 566 | .toogle-img-container { |
559 | 567 | width: 46px; |
568 | } | |
569 | ||
570 | .right-main{ | |
571 | overflow-x: hidden; | |
572 | } | |
573 | ||
574 | #vuln-preview { | |
575 | position: fixed; | |
576 | z-index: 100000; | |
577 | top: 139px; | |
578 | height: calc(100% - 139px); | |
579 | background-color: #fff; | |
580 | border-left: 1px solid #ddd; | |
581 | border-top: solid 4px #e5e5e5; | |
582 | width: 0; | |
583 | transition: 0.5s; | |
584 | right: -75%; | |
585 | padding: 16px; | |
586 | } | |
587 | ||
588 | #vuln-preview.show-preview{ | |
589 | transition: width 0.3s!important; | |
590 | width: 70%!important; | |
591 | right: 0!important; | |
592 | } | |
593 | ||
594 | .faraday-page-header{ | |
595 | width: 100%; | |
596 | } | |
597 | ||
598 | .faraday-page-header.show-preview{ | |
599 | transition: width 0.3s!important; | |
600 | width: 74.5%!important; | |
601 | } | |
602 | ||
603 | #btn_bar.show-preview{ | |
604 | transition: width 0.3s!important; | |
605 | width: 73.2%!important; | |
606 | } | |
607 | ||
608 | .preview-header { | |
609 | width: 100%; | |
610 | padding: 5px 10px 20px; | |
611 | } | |
612 | ||
613 | .preview-section { | |
614 | border-top: 1px solid #ddd; | |
615 | padding: 24px 0; | |
616 | margin-bottom: 22px; | |
617 | } | |
618 | ||
619 | .preview-section .form-group{ | |
620 | margin-bottom: 0; | |
621 | } | |
622 | ||
623 | .preview-icon { | |
624 | color: #aaa; | |
625 | cursor: pointer; | |
626 | text-align: center; | |
627 | padding: 6px 8px; | |
628 | font-size: 0.8em; | |
629 | } | |
630 | ||
631 | .preview-icon:hover { | |
632 | background-color: #ddd; | |
633 | color: #777; | |
634 | border-radius: 50px; | |
635 | box-shadow: 2px 2px 10px 0px #aaa; | |
636 | } | |
637 | ||
638 | .preview-tag { | |
639 | background-color: #ddd; | |
640 | padding: 2px 8px; | |
641 | border-radius: 3px; | |
642 | font-weight: bold; | |
643 | color: #555; | |
644 | box-shadow: 2px 2px 2px 0 #aaa; | |
645 | margin-right: 10px; | |
646 | } | |
647 | ||
648 | .preview-section.row { | |
649 | margin-right: 0; | |
650 | margin-left: 0; | |
651 | margin-bottom: 0; | |
652 | padding-bottom: 12px; | |
653 | } | |
654 | ||
655 | .preview-section .tab-pane-header { | |
656 | font-size: 1.1em; | |
657 | overflow-wrap: break-word; | |
658 | } | |
659 | ||
660 | .prev-apply-template.active{ | |
661 | /*transition: 0.1s;*/ | |
662 | width: 75%!important; | |
663 | } | |
664 | ||
665 | .prev-apply-template { | |
666 | padding-right: 0; | |
667 | } | |
668 | ||
669 | .pretty-text.tab-pane-header { | |
670 | font-size: 1.01em; | |
671 | /* font-weight: normal; */ | |
672 | color: #aaa; | |
673 | } | |
674 | ||
675 | .text-code { | |
676 | background-color: #f9f9f9; | |
677 | padding: 15px 10px; | |
678 | font-weight: normal; | |
679 | color: #777!important; | |
680 | min-height: 150px; | |
681 | overflow-wrap: break-word; | |
682 | } | |
683 | ||
684 | .show-scroll { | |
685 | height: 230px; | |
686 | overflow-y: auto; | |
687 | } | |
688 | ||
689 | .show-scroll input { | |
690 | color: #444!important; | |
691 | } | |
692 | ||
693 | .margin-bottom-24{ | |
694 | margin-bottom: 24px; | |
695 | } | |
696 | ||
697 | .input-evidence { | |
698 | height: 40px; | |
699 | width: 100%; | |
700 | position: absolute; | |
701 | top: 0; | |
702 | left: -15px; | |
703 | opacity: 0; | |
704 | cursor: pointer; | |
705 | } | |
706 | ||
707 | .upload-options button { | |
708 | margin-bottom: 5px; | |
709 | } | |
710 | ||
711 | #attachment-img{ | |
712 | width: 100%; | |
713 | max-height: 500px; | |
714 | } | |
715 | ||
716 | ul#nav-tabs-container { | |
717 | margin-left: 50px; | |
718 | } | |
719 | ||
720 | .padding-left-0{ | |
721 | padding-left: 0; | |
722 | } | |
723 | ||
724 | .margin-left-0{ | |
725 | margin-left: 0; | |
726 | } | |
727 | ||
728 | .desc-container { | |
729 | height: 65%; | |
730 | overflow-y: auto; | |
731 | } | |
732 | ||
733 | .height-100{ | |
734 | height: 100%; | |
735 | } | |
736 | ||
737 | .line-breaks{ | |
738 | white-space: pre-wrap; | |
739 | line-height: 1.5; | |
560 | 740 | }⏎ |
68 | 68 | <script type="text/javascript" src="script/angular-selection-model.js"></script> |
69 | 69 | <script type="text/javascript" src="script/angular-file-upload-shim.js"></script><!-- compatibility with older browsers --> |
70 | 70 | <script type="text/javascript" src="script/angular-file-upload.js"></script> |
71 | <script type="text/javascript" src="script/angular-file-upload-lib.min.js"></script> | |
71 | 72 | <script type="text/javascript" src="script/ui-bootstrap-tpls-0.14.1.min.js"></script> |
72 | 73 | <script type="text/javascript" src="script/cryptojs-sha1.js"></script> |
73 | 74 | <script type="text/javascript" src="script/sanitize.js"></script> |
146 | 147 | <script type="text/javascript" src="scripts/statusReport/directives/appendSearchParam.js"></script> |
147 | 148 | <script type="text/javascript" src="scripts/statusReport/directives/autofocus.js"></script> |
148 | 149 | <script type="text/javascript" src="scripts/statusReport/directives/customField.js"></script> |
150 | <script type="text/javascript" src="scripts/statusReport/directives/customFieldPrev.js"></script> | |
149 | 151 | <script type="text/javascript" src="scripts/statusReport/directives/checkCustomType.js"></script> |
150 | 152 | <script type="text/javascript" src="scripts/statusReport/providers/target.js"></script> |
151 | 153 | <script type="text/javascript" src="scripts/statusReport/providers/reference.js"></script> |
152 | 154 | <script type="text/javascript" src="scripts/statusReport/providers/parser.js"></script> |
155 | <script type="text/javascript" src="scripts/statusReport/providers/ui-common.js"></script> | |
153 | 156 | <script type="text/javascript" src="scripts/vulns/providers/vuln.js"></script> |
154 | 157 | <script type="text/javascript" src="scripts/vulns/providers/vulns.js"></script> |
155 | 158 | <script type="text/javascript" src="scripts/vulns/providers/web.js"></script> |
0 | /* | |
1 | angular-file-upload-lib v2.5.0 | |
2 | https://github.com/nervgh/angular-file-upload-lib | |
3 | */ | |
4 | ||
5 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["angular-file-upload-lib"]=t():e["angular-file-upload-lib"]=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return e[o].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),i=o(r),s=n(2),a=o(s),u=n(3),l=o(u),p=n(4),c=o(p),f=n(5),d=o(f),h=n(6),y=o(h),v=n(7),m=o(v),_=n(8),g=o(_),b=n(9),F=o(b),O=n(10),C=o(O),w=n(11),A=o(w),I=n(12),T=o(I),U=n(13),x=o(U);angular.module(i["default"].name,[]).value("fileUploaderOptions",a["default"]).factory("FileUploader",l["default"]).factory("FileLikeObject",c["default"]).factory("FileItem",d["default"]).factory("FileDirective",y["default"]).factory("FileSelect",m["default"]).factory("FileDrop",F["default"]).factory("FileOver",C["default"]).factory("Pipeline",g["default"]).directive("nvFileSelect",A["default"]).directive("nvFileDrop",T["default"]).directive("nvFileOver",x["default"]).run(["FileUploader","FileLikeObject","FileItem","FileDirective","FileSelect","FileDrop","FileOver","Pipeline",function(e,t,n,o,r,i,s,a){e.FileLikeObject=t,e.FileItem=n,e.FileDirective=o,e.FileSelect=r,e.FileDrop=i,e.FileOver=s,e.Pipeline=a}])},function(e,t){e.exports={name:"angularFileUploadLib"}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t["default"]={url:"/",alias:"file",headers:{},queue:[],progress:0,autoUpload:!1,removeAfterUpload:!1,method:"POST",filters:[],formData:[],queueLimit:Number.MAX_VALUE,withCredentials:!1,disableMultipart:!1}},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t,n,o,i,a,u,g){var b=o.File,F=o.FormData,O=function(){function o(t){r(this,o);var n=p(e);c(this,n,t,{isUploading:!1,_nextIndex:0,_directives:{select:[],drop:[],over:[]}}),this.filters.unshift({name:"queueLimit",fn:this._queueLimitFilter}),this.filters.unshift({name:"folder",fn:this._folderFilter})}return o.prototype.addToQueue=function(e,t,n){var o=this,r=this.isArrayLikeObject(e)?Array.prototype.slice.call(e):[e],i=this._getFilters(n),l=this.queue.length,p=[],c=function d(){var e=r.shift();if(m(e))return f();var n=o.isFile(e)?e:new a(e),l=o._convertFiltersToPipes(i),c=new g(l),h=function(e){var t=e.pipe.originalFilter,n=s(e.args,2),r=n[0],i=n[1];o._onWhenAddingFileFailed(r,t,i),d()},y=function(e,t){var n=new u(o,e,t);p.push(n),o.queue.push(n),o._onAfterAddingFile(n),d()};c.onThrown=h,c.onSuccessful=y,c.exec(n,t)},f=function(){o.queue.length!==l&&(o._onAfterAddingAll(p),o.progress=o._getTotalProgress()),o._render(),o.autoUpload&&o.uploadAll()};c()},o.prototype.removeFromQueue=function(e){var t=this.getIndexOfItem(e),n=this.queue[t];n.isUploading&&n.cancel(),this.queue.splice(t,1),n._destroy(),this.progress=this._getTotalProgress()},o.prototype.clearQueue=function(){for(;this.queue.length;)this.queue[0].remove();this.progress=0},o.prototype.uploadItem=function(e){var t=this.getIndexOfItem(e),n=this.queue[t],o=this.isHTML5?"_xhrTransport":"_iframeTransport";n._prepareToUploading(),this.isUploading||(this._onBeforeUploadItem(n),n.isCancel||(n.isUploading=!0,this.isUploading=!0,this[o](n),this._render()))},o.prototype.cancelItem=function(e){var t=this,n=this.getIndexOfItem(e),o=this.queue[n],r=this.isHTML5?"_xhr":"_form";o&&(o.isCancel=!0,o.isUploading?o[r].abort():!function(){var e=[void 0,0,{}],n=function(){t._onCancelItem.apply(t,[o].concat(e)),t._onCompleteItem.apply(t,[o].concat(e))};i(n)}())},o.prototype.uploadAll=function(){var e=this.getNotUploadedItems().filter(function(e){return!e.isUploading});e.length&&(f(e,function(e){return e._prepareToUploading()}),e[0].upload())},o.prototype.cancelAll=function(){var e=this.getNotUploadedItems();f(e,function(e){return e.cancel()})},o.prototype.isFile=function(e){return this.constructor.isFile(e)},o.prototype.isFileLikeObject=function(e){return this.constructor.isFileLikeObject(e)},o.prototype.isArrayLikeObject=function(e){return this.constructor.isArrayLikeObject(e)},o.prototype.getIndexOfItem=function(e){return h(e)?e:this.queue.indexOf(e)},o.prototype.getNotUploadedItems=function(){return this.queue.filter(function(e){return!e.isUploaded})},o.prototype.getReadyItems=function(){return this.queue.filter(function(e){return e.isReady&&!e.isUploading}).sort(function(e,t){return e.index-t.index})},o.prototype.destroy=function(){var e=this;f(this._directives,function(t){f(e._directives[t],function(e){e.destroy()})})},o.prototype.onAfterAddingAll=function(e){},o.prototype.onAfterAddingFile=function(e){},o.prototype.onWhenAddingFileFailed=function(e,t,n){},o.prototype.onBeforeUploadItem=function(e){},o.prototype.onProgressItem=function(e,t){},o.prototype.onProgressAll=function(e){},o.prototype.onSuccessItem=function(e,t,n,o){},o.prototype.onErrorItem=function(e,t,n,o){},o.prototype.onCancelItem=function(e,t,n,o){},o.prototype.onCompleteItem=function(e,t,n,o){},o.prototype.onCompleteAll=function(){},o.prototype._getTotalProgress=function(e){if(this.removeAfterUpload)return e||0;var t=this.getNotUploadedItems().length,n=t?this.queue.length-t:this.queue.length,o=100/this.queue.length,r=(e||0)*o/100;return Math.round(n*o+r)},o.prototype._getFilters=function(e){if(!e)return this.filters;if(v(e))return e;var t=e.match(/[^\s,]+/g);return this.filters.filter(function(e){return-1!==t.indexOf(e.name)})},o.prototype._convertFiltersToPipes=function(e){var t=this;return e.map(function(e){var n=l(t,e.fn);return n.isAsync=3===e.fn.length,n.originalFilter=e,n})},o.prototype._render=function(){t.$$phase||t.$apply()},o.prototype._folderFilter=function(e){return!(!e.size&&!e.type)},o.prototype._queueLimitFilter=function(){return this.queue.length<this.queueLimit},o.prototype._isSuccessCode=function(e){return e>=200&&300>e||304===e},o.prototype._transformResponse=function(e,t){var o=this._headersGetter(t);return f(n.defaults.transformResponse,function(t){e=t(e,o)}),e},o.prototype._parseHeaders=function(e){var t,n,o,r={};return e?(f(e.split("\n"),function(e){o=e.indexOf(":"),t=e.slice(0,o).trim().toLowerCase(),n=e.slice(o+1).trim(),t&&(r[t]=r[t]?r[t]+", "+n:n)}),r):r},o.prototype._headersGetter=function(e){return function(t){return t?e[t.toLowerCase()]||null:e}},o.prototype._xhrTransport=function(e){var t,n=this,o=e._xhr=new XMLHttpRequest;if(e.disableMultipart?t=e._file:(t=new F,f(e.formData,function(e){f(e,function(e,n){t.append(n,e)})}),t.append(e.alias,e._file,e.file.name)),"number"!=typeof e._file.size)throw new TypeError("The file specified is no longer valid");o.upload.onprogress=function(t){var o=Math.round(t.lengthComputable?100*t.loaded/t.total:0);n._onProgressItem(e,o)},o.onload=function(){var t=n._parseHeaders(o.getAllResponseHeaders()),r=n._transformResponse(o.response,t),i=n._isSuccessCode(o.status)?"Success":"Error",s="_on"+i+"Item";n[s](e,r,o.status,t),n._onCompleteItem(e,r,o.status,t)},o.onerror=function(){var t=n._parseHeaders(o.getAllResponseHeaders()),r=n._transformResponse(o.response,t);n._onErrorItem(e,r,o.status,t),n._onCompleteItem(e,r,o.status,t)},o.onabort=function(){var t=n._parseHeaders(o.getAllResponseHeaders()),r=n._transformResponse(o.response,t);n._onCancelItem(e,r,o.status,t),n._onCompleteItem(e,r,o.status,t)},o.open(e.method,e.url,!0),o.withCredentials=e.withCredentials,f(e.headers,function(e,t){o.setRequestHeader(t,e)}),o.send(t)},o.prototype._iframeTransport=function(e){var t=this,n=_('<form style="display: none;" />'),o=_('<iframe name="iframeTransport'+Date.now()+'">'),r=e._input;e._form&&e._form.replaceWith(r),e._form=n,r.prop("name",e.alias),f(e.formData,function(e){f(e,function(e,t){var o=_('<input type="hidden" name="'+t+'" />');o.val(e),n.append(o)})}),n.prop({action:e.url,method:"POST",target:o.prop("name"),enctype:"multipart/form-data",encoding:"multipart/form-data"}),o.bind("load",function(){var n="",r=200;try{n=o[0].contentDocument.body.innerHTML}catch(i){r=500}var s={response:n,status:r,dummy:!0},a={},u=t._transformResponse(s.response,a);t._onSuccessItem(e,u,s.status,a),t._onCompleteItem(e,u,s.status,a)}),n.abort=function(){var i,s={status:0,dummy:!0},a={};o.unbind("load").prop("src","javascript:false;"),n.replaceWith(r),t._onCancelItem(e,i,s.status,a),t._onCompleteItem(e,i,s.status,a)},r.after(n),n.append(r).append(o),n[0].submit()},o.prototype._onWhenAddingFileFailed=function(e,t,n){this.onWhenAddingFileFailed(e,t,n)},o.prototype._onAfterAddingFile=function(e){this.onAfterAddingFile(e)},o.prototype._onAfterAddingAll=function(e){this.onAfterAddingAll(e)},o.prototype._onBeforeUploadItem=function(e){e._onBeforeUpload(),this.onBeforeUploadItem(e)},o.prototype._onProgressItem=function(e,t){var n=this._getTotalProgress(t);this.progress=n,e._onProgress(t),this.onProgressItem(e,t),this.onProgressAll(n),this._render()},o.prototype._onSuccessItem=function(e,t,n,o){e._onSuccess(t,n,o),this.onSuccessItem(e,t,n,o)},o.prototype._onErrorItem=function(e,t,n,o){e._onError(t,n,o),this.onErrorItem(e,t,n,o)},o.prototype._onCancelItem=function(e,t,n,o){e._onCancel(t,n,o),this.onCancelItem(e,t,n,o)},o.prototype._onCompleteItem=function(e,t,n,o){e._onComplete(t,n,o),this.onCompleteItem(e,t,n,o);var r=this.getReadyItems()[0];return this.isUploading=!1,y(r)?void r.upload():(this.onCompleteAll(),this.progress=this._getTotalProgress(),void this._render())},o.isFile=function(e){return b&&e instanceof b},o.isFileLikeObject=function(e){return e instanceof a},o.isArrayLikeObject=function(e){return d(e)&&"length"in e},o.inherit=function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.super_=t},o}();return O.prototype.isHTML5=!(!b||!F),O.isHTML5=O.prototype.isHTML5,O}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){var n=[],o=!0,r=!1,i=void 0;try{for(var s,a=e[Symbol.iterator]();!(o=(s=a.next()).done)&&(n.push(s.value),!t||n.length!==t);o=!0);}catch(u){r=!0,i=u}finally{try{!o&&a["return"]&&a["return"]()}finally{if(r)throw i}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();t["default"]=i;var a=n(1),u=(o(a),angular),l=u.bind,p=u.copy,c=u.extend,f=u.forEach,d=u.isObject,h=u.isNumber,y=u.isDefined,v=u.isArray,m=u.isUndefined,_=u.element;i.$inject=["fileUploaderOptions","$rootScope","$http","$window","$timeout","FileLikeObject","FileItem","Pipeline"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(){return function(){function e(t){r(this,e);var n=l(t),o=n?t.value:t,i=p(o)?"FakePath":"Object",s="_createFrom"+i;this[s](o)}return e.prototype._createFromFakePath=function(e){this.lastModifiedDate=null,this.size=null,this.type="like/"+e.slice(e.lastIndexOf(".")+1).toLowerCase(),this.name=e.slice(e.lastIndexOf("/")+e.lastIndexOf("\\")+2)},e.prototype._createFromObject=function(e){this.lastModifiedDate=u(e.lastModifiedDate),this.size=e.size,this.type=e.type,this.name=e.name},e}()}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=i;var s=n(1),a=(o(s),angular),u=a.copy,l=a.isElement,p=a.isString},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){return function(){function n(e,o,i){r(this,n);var s=c(o),a=s?p(o):null,f=s?null:o;l(this,{url:e.url,alias:e.alias,headers:u(e.headers),formData:u(e.formData),removeAfterUpload:e.removeAfterUpload,withCredentials:e.withCredentials,disableMultipart:e.disableMultipart,method:e.method},i,{uploader:e,file:new t(o),isReady:!1,isUploading:!1,isUploaded:!1,isSuccess:!1,isCancel:!1,isError:!1,progress:0,index:null,_file:f,_input:a}),a&&this._replaceNode(a)}return n.prototype.upload=function(){try{this.uploader.uploadItem(this)}catch(e){var t=e.name+":"+e.message;this.uploader._onCompleteItem(this,t,e.code,[]),this.uploader._onErrorItem(this,t,e.code,[])}},n.prototype.cancel=function(){this.uploader.cancelItem(this)},n.prototype.remove=function(){this.uploader.removeFromQueue(this)},n.prototype.onBeforeUpload=function(){},n.prototype.onProgress=function(e){},n.prototype.onSuccess=function(e,t,n){},n.prototype.onError=function(e,t,n){},n.prototype.onCancel=function(e,t,n){},n.prototype.onComplete=function(e,t,n){},n.prototype._onBeforeUpload=function(){this.isReady=!0,this.isUploading=!1,this.isUploaded=!1,this.isSuccess=!1,this.isCancel=!1,this.isError=!1,this.progress=0,this.onBeforeUpload()},n.prototype._onProgress=function(e){this.progress=e,this.onProgress(e)},n.prototype._onSuccess=function(e,t,n){this.isReady=!1,this.isUploading=!1,this.isUploaded=!0,this.isSuccess=!0,this.isCancel=!1,this.isError=!1,this.progress=100,this.index=null,this.onSuccess(e,t,n)},n.prototype._onError=function(e,t,n){this.isReady=!1,this.isUploading=!1,this.isUploaded=!0,this.isSuccess=!1,this.isCancel=!1,this.isError=!0,this.progress=0,this.index=null,this.onError(e,t,n)},n.prototype._onCancel=function(e,t,n){this.isReady=!1,this.isUploading=!1,this.isUploaded=!1,this.isSuccess=!1,this.isCancel=!0,this.isError=!1,this.progress=0,this.index=null,this.onCancel(e,t,n)},n.prototype._onComplete=function(e,t,n){this.onComplete(e,t,n),this.removeAfterUpload&&this.remove()},n.prototype._destroy=function(){this._input&&this._input.remove(),this._form&&this._form.remove(),delete this._form,delete this._input},n.prototype._prepareToUploading=function(){this.index=this.index||++this.uploader._nextIndex,this.isReady=!0},n.prototype._replaceNode=function(t){var n=e(t.clone())(t.scope());n.prop("value",null),t.css("display","none"),t.after(n)},n}()}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=i;var s=n(1),a=(o(s),angular),u=a.copy,l=a.extend,p=a.element,c=a.isElement;i.$inject=["$compile","FileLikeObject"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(){var e=function(){function e(t){r(this,e),u(this,t),this.uploader._directives[this.prop].push(this),this._saveLinks(),this.bind()}return e.prototype.bind=function(){for(var e in this.events){var t=this.events[e];this.element.bind(e,this[t])}},e.prototype.unbind=function(){for(var e in this.events)this.element.unbind(e,this.events[e])},e.prototype.destroy=function(){var e=this.uploader._directives[this.prop].indexOf(this);this.uploader._directives[this.prop].splice(e,1),this.unbind()},e.prototype._saveLinks=function(){for(var e in this.events){var t=this.events[e];this[t]=this[t].bind(this)}},e}();return e.prototype.events={},e}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=i;var s=n(1),a=(o(s),angular),u=a.extend},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e,t){return function(t){function n(e){r(this,n);var o=p(e,{events:{$destroy:"destroy",change:"onChange"},prop:"select"}),s=i(this,t.call(this,o));return s.uploader.isHTML5||s.element.removeAttr("multiple"),s.element.prop("value",null),s}return s(n,t),n.prototype.getOptions=function(){},n.prototype.getFilters=function(){},n.prototype.isEmptyAfterSelection=function(){return!!this.element.attr("multiple")},n.prototype.onChange=function(){var t=this.uploader.isHTML5?this.element[0].files:this.element[0],n=this.getOptions(),o=this.getFilters();this.uploader.isHTML5||this.destroy(),this.uploader.addToQueue(t,n,o),this.isEmptyAfterSelection()&&(this.element.prop("value",null),this.element.replaceWith(e(this.element.clone())(this.scope)))},n}(t)}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=a;var u=n(1),l=(o(u),angular),p=l.extend;a.$inject=["$compile","FileDirective"]},function(e,t){"use strict";function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e){return function(){function t(){var e=arguments.length<=0||void 0===arguments[0]?[]:arguments[0];o(this,t),this.pipes=e}return t.prototype.next=function(t){var o=this.pipes.shift();if(a(o))return void this.onSuccessful.apply(this,n(t));var r=new Error("The filter has not passed");if(r.pipe=o,r.args=t,o.isAsync){var i=e.defer(),u=s(this,this.next,t),l=s(this,this.onThrown,r);i.promise.then(u,l),o.apply(void 0,n(t).concat([i]))}else{var p=Boolean(o.apply(void 0,n(t)));p?this.next(t):this.onThrown(r)}},t.prototype.exec=function(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];this.next(t)},t.prototype.onThrown=function(e){},t.prototype.onSuccessful=function(){},t}()}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=r;var i=angular,s=i.bind,a=i.isUndefined;r.$inject=["$q"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e){return function(e){function t(n){r(this,t);var o=p(n,{events:{$destroy:"destroy",drop:"onDrop",dragover:"onDragOver",dragleave:"onDragLeave"},prop:"drop"});return i(this,e.call(this,o))}return s(t,e),t.prototype.getOptions=function(){},t.prototype.getFilters=function(){},t.prototype.onDrop=function(e){var t=this._getTransfer(e);if(t){var n=this.getOptions(),o=this.getFilters();this._preventAndStop(e),c(this.uploader._directives.over,this._removeOverClass,this),this.uploader.addToQueue(t.files,n,o)}},t.prototype.onDragOver=function(e){var t=this._getTransfer(e);this._haveFiles(t.types)&&(t.dropEffect="copy",this._preventAndStop(e),c(this.uploader._directives.over,this._addOverClass,this))},t.prototype.onDragLeave=function(e){e.currentTarget!==this.element[0]&&(this._preventAndStop(e),c(this.uploader._directives.over,this._removeOverClass,this))},t.prototype._getTransfer=function(e){return e.dataTransfer?e.dataTransfer:e.originalEvent.dataTransfer},t.prototype._preventAndStop=function(e){e.preventDefault(),e.stopPropagation()},t.prototype._haveFiles=function(e){return e?e.indexOf?-1!==e.indexOf("Files"):e.contains?e.contains("Files"):!1:!1},t.prototype._addOverClass=function(e){e.addOverClass()},t.prototype._removeOverClass=function(e){e.removeOverClass()},t}(e)}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=a;var u=n(1),l=(o(u),angular),p=l.extend,c=l.forEach;a.$inject=["FileDirective"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e){return function(e){function t(n){r(this,t);var o=p(n,{events:{$destroy:"destroy"},prop:"over",overClass:"nv-file-over"});return i(this,e.call(this,o))}return s(t,e),t.prototype.addOverClass=function(){this.element.addClass(this.getOverClass())},t.prototype.removeOverClass=function(){this.element.removeClass(this.getOverClass())},t.prototype.getOverClass=function(){return this.overClass},t}(e)}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=a;var u=n(1),l=(o(u),angular),p=l.extend;a.$inject=["FileDirective"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t,n){return{link:function(o,r,i){var s=o.$eval(i.uploader);if(!(s instanceof t))throw new TypeError('"Uploader" must be an instance of FileUploader');var a=new n({uploader:s,element:r,scope:o});a.getOptions=e(i.options).bind(a,o),a.getFilters=function(){return i.filters}}}}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=r;var i=n(1);o(i);r.$inject=["$parse","FileUploader","FileSelect"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t,n){return{link:function(o,r,i){var s=o.$eval(i.uploader);if(!(s instanceof t))throw new TypeError('"Uploader" must be an instance of FileUploader');if(s.isHTML5){var a=new n({uploader:s,element:r});a.getOptions=e(i.options).bind(a,o),a.getFilters=function(){return i.filters}}}}}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=r;var i=n(1);o(i);r.$inject=["$parse","FileUploader","FileDrop"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){return{link:function(n,o,r){var i=n.$eval(r.uploader);if(!(i instanceof e))throw new TypeError('"Uploader" must be an instance of FileUploader');var s=new t({uploader:i,element:o});s.getOverClass=function(){return r.overClass||s.overClass}}}}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=r;var i=n(1);o(i);r.$inject=["FileUploader","FileOver"]}])}); | |
6 | //# sourceMappingURL=angular-file-upload-lib.min.js.map⏎ |
11 | 11 | 'filter', 'angular-clipboard', 'ngCookies', 'cfp.hotkeys', 'chart.js', |
12 | 12 | 'ui.grid', 'ui.grid.selection', 'ui.grid.grouping', 'ngSanitize', |
13 | 13 | 'ui.grid.pagination', 'ui.grid.pinning', 'angularMoment', 'ui-notification', |
14 | 'ui.grid.resizeColumns', 'angularSimplePagination']) | |
14 | 'ui.grid.resizeColumns', 'angularSimplePagination', 'angularFileUploadLib']) | |
15 | 15 | .constant("BASEURL", (function() { |
16 | 16 | var url = window.location.origin + "/"; |
17 | 17 | return url; |
7 | 7 | "$location", "$uibModal", "$cookies", "$q", "$window", "BASEURL", |
8 | 8 | "SEVERITIES", "EASEOFRESOLUTION", "STATUSES", "hostsManager", "commonsFact", 'parserFact', |
9 | 9 | "vulnsManager", "workspacesFact", "csvService", "uiGridConstants", "vulnModelsManager", |
10 | "referenceFact", "ServerAPI", '$http', | |
10 | "referenceFact", "ServerAPI", '$http', 'uiCommonFact', 'FileUploader', | |
11 | 11 | function($scope, $filter, $routeParams, |
12 | 12 | $location, $uibModal, $cookies, $q, $window, BASEURL, |
13 | 13 | SEVERITIES, EASEOFRESOLUTION, STATUSES, hostsManager, commonsFact,parserFact, |
14 | vulnsManager, workspacesFact, csvService, uiGridConstants, vulnModelsManager, referenceFact, ServerAPI, $http) { | |
14 | vulnsManager, workspacesFact, csvService, uiGridConstants, vulnModelsManager, referenceFact, | |
15 | ServerAPI, $http, uiCommonFact, FileUploader) { | |
15 | 16 | $scope.baseurl; |
16 | 17 | $scope.columns; |
17 | 18 | $scope.columnsWidths; |
32 | 33 | |
33 | 34 | $scope.gridHeight; |
34 | 35 | $scope.customFields; |
36 | ||
37 | $scope.isShowingPreview; | |
38 | ||
39 | $scope.cweList; | |
40 | $scope.temTemplate; | |
41 | $scope.new_ref; | |
42 | $scope.new_policyviolation; | |
43 | ||
44 | $scope.selectedAtachment; | |
45 | ||
46 | ||
35 | 47 | var allVulns; |
36 | 48 | |
37 | 49 | var searchFilter = {}; |
42 | 54 | sortColumn: null, |
43 | 55 | sortDirection: null |
44 | 56 | }; |
57 | ||
58 | var uploader = $scope.uploader = new FileUploader({}); | |
59 | ||
60 | // FILTERS | |
61 | ||
62 | // a sync filter | |
63 | uploader.filters.push({ | |
64 | name: 'syncFilter', | |
65 | fn: function(item /*{File|FileLikeObject}*/, options) { | |
66 | return this.queue.length < 10; | |
67 | } | |
68 | }); | |
69 | ||
70 | // an async filter | |
71 | uploader.filters.push({ | |
72 | name: 'asyncFilter', | |
73 | fn: function(item /*{File|FileLikeObject}*/, options, deferred) { | |
74 | setTimeout(deferred.resolve, 1e3); | |
75 | } | |
76 | }); | |
77 | ||
45 | 78 | |
46 | 79 | var init = function() { |
47 | 80 | $scope.baseurl = BASEURL; |
287 | 320 | }); |
288 | 321 | |
289 | 322 | $cookies.remove("selectedVulns"); |
323 | $scope.isShowingPreview = false; | |
324 | ||
325 | $scope.cweList = []; | |
326 | ||
327 | vulnModelsManager.get().then(function (data) { | |
328 | $scope.cweList = data; | |
329 | }); | |
330 | ||
331 | $scope.temTemplate = undefined; | |
332 | $scope.new_ref = ""; | |
333 | $scope.new_policyviolation = ""; | |
334 | ||
335 | $scope.selectedAtachment = { | |
336 | url: '', | |
337 | name: '', | |
338 | imgPrevFail: false | |
339 | }; | |
290 | 340 | }; |
291 | 341 | |
292 | 342 | |
827 | 877 | |
828 | 878 | // action triggered from EDIT button |
829 | 879 | $scope.edit = function() { |
880 | $scope.hideVulnPreview(); | |
830 | 881 | _edit($scope.getCurrentSelection()); |
831 | 882 | }; |
832 | 883 | |
1108 | 1159 | }; |
1109 | 1160 | |
1110 | 1161 | $scope.new = function() { |
1162 | $scope.hideVulnPreview(); | |
1111 | 1163 | var modal = $uibModal.open({ |
1112 | 1164 | templateUrl: 'scripts/statusReport/partials/modalNew.html', |
1113 | 1165 | backdrop : 'static', |
1266 | 1318 | return elements.join("\n" + (useDoubleLinebreak ? "\n" : "")); |
1267 | 1319 | }; |
1268 | 1320 | |
1321 | $scope.showVulnPreview = function () { | |
1322 | $scope.isShowingPreview = true; | |
1323 | angular.element('#vuln-preview').addClass('show-preview'); | |
1324 | // angular.element('.faraday-page-header').addClass('show-preview'); | |
1325 | // angular.element('#btn_bar').addClass('show-preview'); | |
1326 | }; | |
1327 | ||
1328 | $scope.hideVulnPreview = function () { | |
1329 | $scope.lastClickedVuln = undefined; | |
1330 | $scope.isShowingPreview = false; | |
1331 | angular.element('#vuln-preview').removeClass('show-preview'); | |
1332 | // angular.element('.faraday-page-header').removeClass('show-preview'); | |
1333 | // angular.element('#btn_bar').removeClass('show-preview'); | |
1334 | }; | |
1335 | ||
1336 | ||
1337 | var updateSelectedVulnAtachments = function () { | |
1338 | var url = '/_api/v2/ws/' + $routeParams.wsId + '/vulns/' + $scope.lastClickedVuln._id + '/attachments/'; | |
1339 | $http.get(url).then( | |
1340 | function (response) { | |
1341 | $scope.lastClickedVuln._attachments = response.data | |
1342 | } | |
1343 | ); | |
1344 | }; | |
1345 | ||
1346 | $scope.toggleVulnPreview = function (e, vuln) { | |
1347 | e.stopPropagation(); | |
1348 | if ($scope.lastClickedVuln !== undefined && $scope.lastClickedVuln._id === vuln._id){ | |
1349 | $scope.hideVulnPreview(); | |
1350 | $scope.lastClickedVuln = undefined; | |
1351 | }else{ | |
1352 | $scope.showVulnPreview(); | |
1353 | $scope.realVuln = vuln; | |
1354 | $scope.lastClickedVuln = angular.copy(vuln); | |
1355 | updateSelectedVulnAtachments(); | |
1356 | uiCommonFact.updateBtnSeverityColor($scope.lastClickedVuln.severity, '#btn-chg-severity-prev', '#caret-chg-severity-prev'); | |
1357 | uiCommonFact.updateBtnStatusColor($scope.lastClickedVuln.status, '#btn-chg-status-prev', '#caret-chg-status-prev'); | |
1358 | } | |
1359 | $scope.cwe_selected = undefined; | |
1360 | $scope.selectedAtachment = { | |
1361 | url: '', | |
1362 | name: '', | |
1363 | imgPrevFail: false | |
1364 | }; | |
1365 | ||
1366 | $scope.uploader.clearQueue(); | |
1367 | }; | |
1368 | ||
1369 | ||
1370 | $scope.changeVulnPrevByEventKey = function (event) { | |
1371 | if ($scope.lastClickedVuln !== undefined) { | |
1372 | var curRowindex = -1; | |
1373 | var targetIndex = -1; | |
1374 | for (var i = 0; i < $scope.gridApi.grid.rows.length; i++) { | |
1375 | if ($scope.gridApi.grid.rows[i].entity._id === $scope.lastClickedVuln._id) { | |
1376 | curRowindex = i; | |
1377 | break; | |
1378 | } | |
1379 | } | |
1380 | ||
1381 | if (event.keyCode === uiGridConstants.keymap.DOWN) | |
1382 | targetIndex = curRowindex + 1; | |
1383 | else if (event.keyCode === uiGridConstants.keymap.UP) | |
1384 | targetIndex = curRowindex - 1; | |
1385 | ||
1386 | if (targetIndex !== -1 && targetIndex < $scope.gridApi.grid.rows.length) { | |
1387 | $scope.lastClickedVuln = $scope.gridApi.grid.rows[targetIndex].entity; | |
1388 | } | |
1389 | } | |
1390 | ||
1391 | }; | |
1392 | ||
1393 | $scope.activeEditPreview = function (field) { | |
1394 | $scope.fieldToEdit = field; | |
1395 | }; | |
1396 | ||
1397 | $scope.processToEditPreview = function (isMandatory) { | |
1398 | if (($scope.lastClickedVuln.hasOwnProperty($scope.fieldToEdit) && | |
1399 | $scope.lastClickedVuln[$scope.fieldToEdit] !== undefined && | |
1400 | $scope.lastClickedVuln[$scope.fieldToEdit] !== '') || isMandatory === false){ | |
1401 | ||
1402 | $scope.isUpdatingVuln = true; | |
1403 | if ($scope.realVuln[$scope.fieldToEdit] !== $scope.lastClickedVuln[$scope.fieldToEdit] || | |
1404 | ($scope.realVuln['custom_fields'].hasOwnProperty($scope.fieldToEdit))){ | |
1405 | vulnsManager.updateVuln($scope.realVuln, $scope.lastClickedVuln).then(function () { | |
1406 | $scope.isUpdatingVuln = false; | |
1407 | $scope.fieldToEdit = undefined; | |
1408 | }, function (data) { | |
1409 | $scope.hideVulnPreview(); | |
1410 | commonsFact.showMessage("Error updating vuln " + $scope.realVuln.name + " (" + $scope.realVuln._id + "): " + (data.message || JSON.stringify(data.messages))); | |
1411 | $scope.fieldToEdit = undefined; | |
1412 | $scope.isUpdatingVuln = false; | |
1413 | }); | |
1414 | }else{ | |
1415 | $scope.fieldToEdit = undefined; | |
1416 | $scope.isUpdatingVuln = false; | |
1417 | } | |
1418 | ||
1419 | } | |
1420 | }; | |
1421 | ||
1422 | $scope.changeSeverity = function (severity) { | |
1423 | $scope.fieldToEdit = 'severity'; | |
1424 | $scope.lastClickedVuln.severity = severity; | |
1425 | uiCommonFact.updateBtnSeverityColor(severity, '#btn-chg-severity-prev', '#caret-chg-severity-prev'); | |
1426 | $scope.processToEditPreview(); | |
1427 | }; | |
1428 | ||
1429 | $scope.changeEaseOfResolution = function (easeofresolution) { | |
1430 | $scope.fieldToEdit = 'easeofresolution'; | |
1431 | $scope.lastClickedVuln.easeofresolution = easeofresolution; | |
1432 | $scope.processToEditPreview(); | |
1433 | }; | |
1434 | ||
1435 | $scope.changeStatus = function (status) { | |
1436 | $scope.fieldToEdit = 'status'; | |
1437 | $scope.lastClickedVuln.status = status; | |
1438 | uiCommonFact.updateBtnStatusColor(status, '#btn-chg-status-prev', '#caret-chg-status-prev'); | |
1439 | $scope.processToEditPreview(); | |
1440 | }; | |
1441 | ||
1442 | $scope.changeConfirmed = function (confirmed) { | |
1443 | $scope.fieldToEdit = 'confirmed'; | |
1444 | $scope.lastClickedVuln.confirmed = confirmed; | |
1445 | $scope.processToEditPreview(); | |
1446 | }; | |
1447 | ||
1448 | $scope.toggleImpact = function (key) { | |
1449 | $scope.fieldToEdit = 'impact'; | |
1450 | $scope.lastClickedVuln.impact[key] = !$scope.lastClickedVuln.impact[key]; | |
1451 | $scope.processToEditPreview(); | |
1452 | }; | |
1453 | ||
1454 | ||
1455 | $scope.populate = function () { | |
1456 | $scope.temTemplate = angular.copy($scope.lastClickedVuln); | |
1457 | uiCommonFact.populate($scope.cwe_selected, $scope.lastClickedVuln); | |
1458 | }; | |
1459 | ||
1460 | $scope.applyTemplate = function () { | |
1461 | $scope.fieldToEdit = 'template'; | |
1462 | $scope.isUpdatingVuln = true; | |
1463 | vulnsManager.updateVuln($scope.realVuln, $scope.lastClickedVuln).then(function () { | |
1464 | $scope.isUpdatingVuln = false; | |
1465 | $scope.fieldToEdit = undefined; | |
1466 | $scope.temTemplate = undefined; | |
1467 | $scope.cwe_selected = undefined | |
1468 | }, function (data) { | |
1469 | commonsFact.showMessage("Error updating vuln " + $scope.realVuln.name + " (" + $scope.realVuln._id + "): " + (data.message || JSON.stringify(data.messages))); | |
1470 | $scope.fieldToEdit = undefined; | |
1471 | $scope.isUpdatingVuln = false; | |
1472 | $scope.temTemplate = undefined; | |
1473 | $scope.cwe_selected = undefined; | |
1474 | $scope.hideVulnPreview(); | |
1475 | }); | |
1476 | }; | |
1477 | ||
1478 | $scope.discardTemplate = function () { | |
1479 | uiCommonFact.populate($scope.temTemplate, $scope.lastClickedVuln); | |
1480 | $scope.temTemplate = undefined; | |
1481 | $scope.cwe_selected = undefined; | |
1482 | }; | |
1483 | ||
1484 | ||
1485 | $scope.newReference = function () { | |
1486 | $scope.fieldToEdit = 'refs'; | |
1487 | uiCommonFact.newReference($scope.new_ref, $scope.lastClickedVuln); | |
1488 | $scope.processToEditPreview(); | |
1489 | $scope.new_ref = ""; | |
1490 | }; | |
1491 | ||
1492 | ||
1493 | $scope.removeReference = function (index) { | |
1494 | $scope.fieldToEdit = 'refs'; | |
1495 | $scope.lastClickedVuln.refs.splice(index, 1); | |
1496 | $scope.isUpdatingVuln = true; | |
1497 | ||
1498 | vulnsManager.updateVuln($scope.realVuln, $scope.lastClickedVuln).then(function () { | |
1499 | $scope.isUpdatingVuln = false; | |
1500 | $scope.fieldToEdit = undefined; | |
1501 | }, function (data) { | |
1502 | $scope.hideVulnPreview(); | |
1503 | commonsFact.showMessage("Error updating vuln " + $scope.realVuln.name + " (" + $scope.realVuln._id + "): " + (data.message || JSON.stringify(data.messages))); | |
1504 | $scope.fieldToEdit = undefined; | |
1505 | $scope.isUpdatingVuln = false; | |
1506 | ||
1507 | }); | |
1508 | }; | |
1509 | ||
1510 | $scope.openReference = function (text) { | |
1511 | window.open(referenceFact.processReference(text), '_blank'); | |
1512 | }; | |
1513 | ||
1514 | ||
1515 | $scope.newPolicyviolation = function () { | |
1516 | $scope.fieldToEdit = 'policyviolations'; | |
1517 | uiCommonFact.newPolicyViolation($scope.new_policyviolation, $scope.lastClickedVuln); | |
1518 | $scope.processToEditPreview(); | |
1519 | $scope.new_policyviolation = ""; | |
1520 | }; | |
1521 | ||
1522 | ||
1523 | $scope.removePolicyviolation = function (index) { | |
1524 | $scope.fieldToEdit = 'policyviolations'; | |
1525 | $scope.lastClickedVuln.policyviolations.splice(index, 1); | |
1526 | $scope.isUpdatingVuln = true; | |
1527 | ||
1528 | vulnsManager.updateVuln($scope.realVuln, $scope.lastClickedVuln).then(function () { | |
1529 | $scope.isUpdatingVuln = false; | |
1530 | $scope.fieldToEdit = undefined; | |
1531 | }, function (data) { | |
1532 | $scope.hideVulnPreview(); | |
1533 | commonsFact.showMessage("Error updating vuln " + $scope.realVuln.name + " (" + $scope.realVuln._id + "): " + (data.message || JSON.stringify(data.messages))); | |
1534 | $scope.fieldToEdit = undefined; | |
1535 | $scope.isUpdatingVuln = false; | |
1536 | ||
1537 | }); | |
1538 | }; | |
1539 | ||
1540 | ||
1541 | uploader.onAfterAddingFile = function(fileItem) { | |
1542 | if ($scope.lastClickedVuln._attachments.hasOwnProperty(fileItem.file.name)){ | |
1543 | fileItem.isError = true; | |
1544 | fileItem.isReady = true; | |
1545 | return; | |
1546 | } | |
1547 | ||
1548 | $http.get('/_api/session').then( | |
1549 | function(d) { | |
1550 | $scope.csrf_token = d.data.csrf_token; | |
1551 | fileItem.formData.push({'csrf_token': $scope.csrf_token}); | |
1552 | fileItem.url = '_api/v2/ws/' + $routeParams.wsId + '/vulns/' + $scope.lastClickedVuln._id + '/attachment/'; | |
1553 | $scope.uploader.uploadAll(); | |
1554 | } | |
1555 | ); | |
1556 | ||
1557 | }; | |
1558 | ||
1559 | ||
1560 | ||
1561 | uploader.onSuccessItem = function(fileItem, response, status, headers) { | |
1562 | updateSelectedVulnAtachments(); | |
1563 | }; | |
1564 | ||
1565 | $scope.removeEvidence = function (name) { | |
1566 | var url = '/_api/v2/ws/'+ $routeParams.wsId +'/vulns/'+ $scope.lastClickedVuln._id +'/attachment/' + name + '/' | |
1567 | $http.delete(url).then( | |
1568 | function(response) { | |
1569 | if (response && response.status === 200){ | |
1570 | uiCommonFact.removeEvidence(name, $scope.lastClickedVuln); | |
1571 | } | |
1572 | } | |
1573 | ); | |
1574 | }; | |
1575 | ||
1576 | $scope.selectItemToPrev = function (name) { | |
1577 | $scope.selectedAtachment.name = name; | |
1578 | $scope.selectedAtachment.url = BASEURL + '_api/v2/ws/' + $routeParams.wsId + /vulns/ + $scope.lastClickedVuln._id + /attachment/ + name | |
1579 | $scope.selectedAtachment.imgPrevFail = false; | |
1580 | var format = $scope.selectedAtachment.name.split('.').pop(); | |
1581 | var imagesFormat = ['png','jpg', 'jpeg', 'gif']; | |
1582 | if (imagesFormat.indexOf(format) === -1){ | |
1583 | $scope.selectedAtachment.imgPrevFail = true; | |
1584 | } | |
1585 | }; | |
1586 | ||
1587 | ||
1588 | $scope.copyToClipboard = function (name) { | |
1589 | var url = BASEURL + '_api/v2/ws/' + $routeParams.wsId + /vulns/ + $scope.lastClickedVuln._id + /attachment/ + name; | |
1590 | var copyElement = document.createElement("textarea"); | |
1591 | copyElement.style.position = 'fixed'; | |
1592 | copyElement.style.opacity = '0'; | |
1593 | copyElement.textContent = decodeURI(url); | |
1594 | var body = document.getElementsByTagName('body')[0]; | |
1595 | body.appendChild(copyElement); | |
1596 | copyElement.select(); | |
1597 | document.execCommand('copy'); | |
1598 | body.removeChild(copyElement); | |
1599 | } | |
1600 | ||
1601 | ||
1602 | ||
1603 | $scope.openEvidence = function (name) { | |
1604 | uiCommonFact.openEvidence(name, $scope.lastClickedVuln, $routeParams.wsId); | |
1605 | }; | |
1606 | ||
1607 | $scope.processLinesToHtml = function (rawText) { | |
1608 | if (rawText !== undefined) | |
1609 | return rawText.replace(/(?:\r\n|\r|\n)/g, '<br>'); | |
1610 | return ''; | |
1611 | }; | |
1612 | ||
1269 | 1613 | init(); |
1270 | 1614 | }]); |
0 | // Faraday Penetration Test IDE | |
1 | // Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) | |
2 | // See the file 'doc/LICENSE' for the license information | |
3 | ||
4 | angular.module('faradayApp') | |
5 | .directive('customFieldPrev', ['vulnsManager', function (vulnsManager) { | |
6 | return { | |
7 | restrict: 'E', | |
8 | scope: false, | |
9 | replace: true, | |
10 | template: '<div><div class="tab-pane-header"><i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === cf.field_display_name"></i> {{cf.field_display_name}}</div> \n\ | |
11 | <div class="form-group" ng-if="cf.field_type !== \'list\'"> \n\ | |
12 | <label class="sr-only" for="{{cf.field_name}}">{{cf.field_display_name}}</label> \n\ | |
13 | <input type="text" class="form-control input-sm" id="{{cf.field_name}}" name="{{cf.field_name}}" \n\ | |
14 | placeholder="{{cf.field_display_name}}" \n\ | |
15 | ng-focus="activeEditPreview(cf.field_display_name)" \ | |
16 | ng-blur="processToEditPreview(false)"\ | |
17 | ng-model="lastClickedVuln.custom_fields[cf.field_display_name]" check-custom-type="{{cf.field_type}}" \n\ | |
18 | uib-tooltip="{{(cf.field_type === \'int\') ? \'Type only numbers\' : \'Input type text\'}}"/> \n\ | |
19 | </div> \n\ | |
20 | <div class="form-group " ng-if="cf.field_type === \'list\'">\n\ | |
21 | <div class="input-group"> \n\ | |
22 | <label class="sr-only" for="{{cf.field_name}}">{{cf.field_display_name}}</label> \n\ | |
23 | <input type="text" class="form-control input-sm" id="{{cf.field_name}}" name="{{cf.field_name}}" \n\ | |
24 | placeholder="{{cf.field_display_name}}" \n\ | |
25 | ng-focus="activeEditPreview(cf.field_display_name)" \ | |
26 | ng-model="valueField" \n\ | |
27 | uib-tooltip="Input type list"/> \n\ | |
28 | <span class="input-group-addon cursor"><i class="fa fa-plus-circle" ng-click="newValueField(valueField)"></i></span> \n\ | |
29 | </div> \n\ | |
30 | </div> \n\ | |
31 | <div class="col-md-12 reference last-item-field" ng-repeat="item in lastClickedVuln.custom_fields[cf.field_display_name] track by $index" ng-class="{\'last-item-field\':$last}" ng-if="cf.field_type === \'list\'"> \n\ | |
32 | <div class="input-group margin-bottom-sm"> \n\ | |
33 | <label class="sr-only" for="vuln-refs-create">{{cf.field_display_name}}</label> \n\ | |
34 | <input type="text" class="form-control" id="vuln-refs-create" placeholder="{{cf.field_display_name}}" \n\ | |
35 | ng-model="item.value" \n\ | |
36 | role="button" readonly/> \n\ | |
37 | <span class="input-group-addon cursor" ng-click="removeValueField($index)"> \n\ | |
38 | <i class="fa fa-minus-circle"></i></span> \n\ | |
39 | </div> \n\ | |
40 | </div> \n\ | |
41 | </div></div>', | |
42 | link: function (scope, element, attrs) { | |
43 | scope.newValueField = function (valueField) { | |
44 | if (valueField !== "" && valueField !== undefined) { | |
45 | if(scope.lastClickedVuln.custom_fields[scope.cf.field_display_name] === null ) | |
46 | scope.lastClickedVuln.custom_fields[scope.cf.field_display_name] = []; | |
47 | ||
48 | // we need to check if the ref already exists | |
49 | if (scope.lastClickedVuln.custom_fields[scope.cf.field_display_name].filter(function(field) {return field.value === valueField}).length === 0) { | |
50 | scope.lastClickedVuln.custom_fields[scope.cf.field_display_name].push({value: valueField}); | |
51 | scope.valueField = ""; | |
52 | } | |
53 | angular.element('#'+scope.cf.field_name).val(""); | |
54 | ||
55 | scope.fieldToEdit = scope.cf.field_display_name; | |
56 | scope.processToEditPreview(false); | |
57 | ||
58 | } | |
59 | }; | |
60 | ||
61 | scope.removeValueField = function (index) { | |
62 | scope.fieldToEdit = scope.cf.field_display_name; | |
63 | scope.lastClickedVuln.custom_fields[scope.cf.field_display_name].splice(index, 1); | |
64 | scope.isUpdatingVuln = true; | |
65 | ||
66 | vulnsManager.updateVuln(scope.realVuln, scope.lastClickedVuln).then(function () { | |
67 | scope.isUpdatingVuln = false; | |
68 | scope.fieldToEdit = undefined; | |
69 | }, function (data) { | |
70 | scope.hideVulnPreview(); | |
71 | commonsFact.showMessage("Error updating vuln " + scope.realVuln.name + " (" + scope.realVuln._id + "): " + (data.message || JSON.stringify(data.messages))); | |
72 | scope.fieldToEdit = undefined; | |
73 | scope.isUpdatingVuln = false; | |
74 | ||
75 | }); | |
76 | }; | |
77 | } | |
78 | } | |
79 | }]); |
6 | 6 | <div class="right-main"> |
7 | 7 | <div ng-controller="headerCtrl" ng-include="'scripts/commons/partials/header.html'"></div> |
8 | 8 | <div id="reports-main" class="fila clearfix"> |
9 | <div class="button-control col-md-12 col-sm-12 col-xs-12"> | |
9 | <div class="button-control col-md-12 col-sm-12 col-xs-12" id="btn_bar"> | |
10 | 10 | <div class="control-wrapper control-new"> |
11 | 11 | <button type="button" class="btn btn-success btn-new" title="{{workspaceData.readonly == true ? 'Read-only. Workspace disabled': 'New Vulns'}}" ng-click="new()" ng-disabled="workspaceData.readonly == true"> |
12 | 12 | New |
163 | 163 | <!-- .reports --> |
164 | 164 | </div> |
165 | 165 | <!-- #reports-main --> |
166 | ||
167 | <div id="vuln-preview"> | |
168 | <div class="preview-header"> | |
169 | <button type="button" class="close" ng-click="hideVulnPreview()" data-dismiss="alert"><span area-hidden="true">×</span><span class="sr-only">Close</span></button> | |
170 | </div> | |
171 | <div class="mt-3 height-100 "> | |
172 | <div class="margin-left-0 edit-vulns-dropdowns row" ng-init="updateBtnSeverityColor(lastClickedVuln.severity);updateBtnStatusColor(lastClickedVuln.status)"> | |
173 | ||
174 | <div class="margin-left-0 btn-group col-md-2 col-sm-4 col-xs-4"> | |
175 | <button type="button" class="dropdown-toggle btn-change-property primary-btn" | |
176 | data-toggle="dropdown" | |
177 | id="btn-chg-severity-prev" | |
178 | title="{{lastClickedVuln.severity}}"> | |
179 | <i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'severity'"></i>{{lastClickedVuln.severity | uppercase}} | |
180 | </button> | |
181 | <button type="button" class="dropdown-toggle secondary-btn btn-change-property" | |
182 | data-toggle="dropdown" | |
183 | id="caret-chg-severity-prev" | |
184 | title="Change severity-prev"> | |
185 | <span> <i class="fa fa-angle-down fa-lg" aria-hidden="true"></i> </span> | |
186 | </button> | |
187 | <ul class="dropdown-menu dropdown-menu-right" role="menu"> | |
188 | <li> | |
189 | <a class="ws text-unclassified" | |
190 | ng-click="changeSeverity('unclassified')">UNCLASSIFIED</a> | |
191 | <a class="ws text-info" | |
192 | ng-click="changeSeverity('info')">INFO</a> | |
193 | <a class="ws text-low" | |
194 | ng-click="changeSeverity('low')">LOW</a> | |
195 | <a class="ws text-med" | |
196 | ng-click="changeSeverity('med')">MEDIUM</a> | |
197 | <a class="ws text-high" | |
198 | ng-click="changeSeverity('high')">HIGH</a> | |
199 | <a class="ws text-critical" | |
200 | ng-click="changeSeverity('critical')">CRITICAL</a> | |
201 | </li> | |
202 | </ul> | |
203 | </div> | |
204 | ||
205 | ||
206 | <div class="btn-group col-md-2 col-sm-4 col-xs-4"> | |
207 | <button type="button" class="dropdown-toggle btn-change-property primary-btn btn-primary-white" | |
208 | data-toggle="dropdown" | |
209 | id="btn-chg-ease_resolution" | |
210 | title="{{lastClickedVuln.easeofresolution}}"> | |
211 | <i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'easeofresolution'"></i>{{lastClickedVuln.easeofresolution || "Fix Effort"}} | |
212 | </button> | |
213 | <button type="button" class="dropdown-toggle secondary-btn btn-change-property btn-secondary-white" | |
214 | data-toggle="dropdown" | |
215 | id="caret-ease_resolution" | |
216 | title="Change Ease of Resolution"> | |
217 | <span> <i class="fa fa-angle-down fa-lg" aria-hidden="true"></i> </span> | |
218 | </button> | |
219 | <ul class="dropdown-menu dropdown-menu-right" role="menu"> | |
220 | <li> | |
221 | <a class="ws" | |
222 | ng-click="changeEaseOfResolution(null)">Undetermined</a> | |
223 | <a class="ws" | |
224 | ng-click="changeEaseOfResolution('trivial')">Trivial</a> | |
225 | <a class="ws" | |
226 | ng-click="changeEaseOfResolution('simple')">Simple</a> | |
227 | <a class="ws" | |
228 | ng-click="changeEaseOfResolution('moderate')">Moderate</a> | |
229 | <a class="ws" | |
230 | ng-click="changeEaseOfResolution('difficult')">Difficult</a> | |
231 | <a class="ws" | |
232 | ng-click="changeEaseOfResolution('infeasible')">Infeasible</a> | |
233 | </li> | |
234 | </ul> | |
235 | </div> | |
236 | ||
237 | ||
238 | <div class="btn-group col-md-2 col-sm-4 col-xs-4"> | |
239 | <button type="button" class="dropdown-toggle btn-change-property primary-btn text-capitalize" | |
240 | data-toggle="dropdown" | |
241 | id="btn-chg-status-prev" | |
242 | title="{{lastClickedVuln.status}}"> | |
243 | <i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'status'"></i>{{lastClickedVuln.status || "Status"}} | |
244 | </button> | |
245 | <button type="button" class="dropdown-toggle secondary-btn btn-change-property" | |
246 | data-toggle="dropdown" | |
247 | id="caret-chg-status-prev" | |
248 | title="Change Status"> | |
249 | <span> <i class="fa fa-angle-down fa-lg" aria-hidden="true"></i> </span> | |
250 | </button> | |
251 | <ul class="dropdown-menu dropdown-menu-right" role="menu"> | |
252 | <li> | |
253 | <a class="ws" | |
254 | ng-click="changeStatus('opened')">Opened</a> | |
255 | <a class="ws" | |
256 | ng-click="changeStatus('closed')">Closed</a> | |
257 | <a class="ws" | |
258 | ng-click="changeStatus('re-opened')">Re-opened</a> | |
259 | <a class="ws" | |
260 | ng-click="changeStatus('risk-accepted')">Risk-accepted</a> | |
261 | </li> | |
262 | </ul> | |
263 | </div> | |
264 | ||
265 | <div class="col-md-2 col-sm-4 col-xs-4"> | |
266 | <label class="chbox-container"> | |
267 | <div class="tab-pane-header"><h4>Confirmed</h4></div> | |
268 | <input type="checkbox" ng-checked="lastClickedVuln.confirmed === true" ng-model="lastClickedVuln.confirmed" ng-change="changeConfirmed(lastClickedVuln.confirmed)"> | |
269 | <span class="checkmark"></span> | |
270 | </label> | |
271 | </div> | |
272 | ||
273 | </div> | |
274 | ||
275 | <div class="margin-left-0 row"> | |
276 | <div ng-if="fieldToEdit !== 'name'"> | |
277 | <div class="margin-left-0 col-md-12"> | |
278 | <div class="tab-pane-header" ng-dblclick="activeEditPreview('name')" title="Double click to edit"> | |
279 | {{lastClickedVuln.name}} <span>({{lastClickedVuln.target}})</span> | |
280 | </div> | |
281 | </div> | |
282 | </div> | |
283 | <div class="form-group col-md-12" ng-if="fieldToEdit === 'name'" style="margin-bottom: 24px"> | |
284 | <label for="inp-edit-name"><i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true"></i> Vulnerability name</label> | |
285 | <input type="text" class="form-control" id="inp-edit-name" | |
286 | ng-model="lastClickedVuln.name" name="name" placeholder="Vulnerability name" | |
287 | ng-blur="processToEditPreview()" | |
288 | autofocus required> | |
289 | </div> | |
290 | </div> | |
291 | ||
292 | <!-- Nav tabs --> | |
293 | <ul class="nav nav-tabs" id="nav-tabs-container"> | |
294 | <li class="nav-item active" ng-class="{'has-error': formEdit.desc.$invalid}"> | |
295 | <a class="nav-link active" data-toggle="tab" data-target="#general_prev" | |
296 | href="javascript:;">General</a> | |
297 | </li> | |
298 | <li class="nav-item"> | |
299 | <a class="nav-link" data-toggle="tab" data-target="#technical_details_prev" href="javascript:;">Technical | |
300 | Details</a> | |
301 | </li> | |
302 | <li class="nav-item"> | |
303 | <a class="nav-link" data-toggle="tab" data-target="#tab-evidence_prev" href="javascript:;">Evidence</a> | |
304 | </li> | |
305 | <li class="nav-item"> | |
306 | <a class="nav-link" data-toggle="tab" data-target="#hosts_prev" href="javascript:;">Host</a> | |
307 | </li> | |
308 | <li class="nav-item"> | |
309 | <a class="nav-link" data-toggle="tab" data-target="#tab-custom-fields_prev" href="javascript:;">Custom Fields</a> | |
310 | </li> | |
311 | </ul> | |
312 | ||
313 | <!-- Tab panes --> | |
314 | <div class="tab-content height-100 "> | |
315 | <div id="general_prev" class="container tab-pane-container tab-pane active"><br> | |
316 | ||
317 | <div class="row margin-left-0"> | |
318 | <div class="height-100 padding-left-0 col-md-7"> | |
319 | <div class="height-100 padding-left-0 col-md-12"> | |
320 | <div ng-if="fieldToEdit !== 'desc'" class="height-100"> | |
321 | <div class="height-100 padding-left-0 col-md-12 margin-bottom-24"> | |
322 | <div ng-if="lastClickedVuln.desc === '' || lastClickedVuln.desc === undefined" style="margin-bottom: 15px;color: #aaa;"> | |
323 | <p> | |
324 | No Description was found. | |
325 | </p> | |
326 | </div> | |
327 | ||
328 | <div class="tab-pane-header" ng-dblclick="activeEditPreview('desc')" title="Double click to edit"> | |
329 | <i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'desc'"></i> | |
330 | Description | |
331 | </div> | |
332 | ||
333 | <div> | |
334 | <div class="tab-pane-header pretty-text" id="desc-prev" ng-dblclick="activeEditPreview('desc')" title="Double click to edit"> | |
335 | <span class="line-breaks">{{lastClickedVuln.desc}}</span> | |
336 | </div> | |
337 | </div> | |
338 | ||
339 | </div> | |
340 | </div> | |
341 | <div class="form-group" ng-if="fieldToEdit === 'desc'"> | |
342 | <label for="inp-edit-desc"><i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true"></i> Description</label> | |
343 | <textarea type="text" class="form-control" id="inp-edit-desc" rows="18" | |
344 | ng-model="lastClickedVuln.desc" name="desc" placeholder="Description" | |
345 | ng-blur="processToEditPreview()" | |
346 | autofocus> | |
347 | </textarea> | |
348 | </div> | |
349 | </div> | |
350 | </div> | |
351 | ||
352 | <div class="col-md-5"> | |
353 | <div class="row"> | |
354 | <div ng-if="fieldToEdit !== 'resolution'"> | |
355 | <div class="col-md-12 margin-bottom-24"> | |
356 | <div class="tab-pane-header" ng-dblclick="activeEditPreview('resolution')" title="Double click to edit"> | |
357 | <i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'resolution'"></i> | |
358 | Resolution | |
359 | </div> | |
360 | <div ng-if="lastClickedVuln.resolution === '' || lastClickedVuln.resolution === undefined" style="margin-bottom: 15px;color: #aaa;"> | |
361 | <p> | |
362 | No Resolution was found. | |
363 | </p> | |
364 | </div> | |
365 | <div class="tab-pane-header pretty-text" id="resolution-prev" ng-dblclick="activeEditPreview('resolution')" title="Double click to edit"> | |
366 | <span class="line-breaks">{{lastClickedVuln.resolution}}</span> | |
367 | </div> | |
368 | ||
369 | </div> | |
370 | </div> | |
371 | <div class="form-group" ng-if="fieldToEdit === 'resolution'"> | |
372 | <label for="inp-edit-desc"><i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true"></i> Resolution</label> | |
373 | <textarea type="text" class="form-control" id="inp-edit-res" rows="18" | |
374 | ng-model="lastClickedVuln.resolution" name="desc" placeholder="Resolution" | |
375 | ng-blur="processToEditPreview(false)" | |
376 | autofocus> | |
377 | </textarea> | |
378 | </div> | |
379 | </div> | |
380 | </div> | |
381 | </div> | |
382 | ||
383 | <div class="row margin-top-30px small-size"> | |
384 | <div class="tab-pane-header col-md-2"> | |
385 | Owner | |
386 | <div class="text-gray">{{lastClickedVuln.metadata['owner']}}</div> | |
387 | </div> | |
388 | <div class="tab-pane-header col-md-3"> | |
389 | Created | |
390 | <div class="text-gray"><span am-time-ago="lastClickedVuln.metadata['create_time']"></span></div> | |
391 | </div> | |
392 | <div class="tab-pane-header col-md-4 "> | |
393 | Updated | |
394 | <div class="text-gray"><span am-time-ago="lastClickedVuln.metadata['update_time']"></span></div> | |
395 | </div> | |
396 | </div> | |
397 | ||
398 | </div> | |
399 | ||
400 | <div id="technical_details_prev" class="container tab-pane-container tab-pane fade"><br> | |
401 | <div class="padding-left-0 col-md-12"> | |
402 | <div class="padding-left-0 col-md-12"> | |
403 | <div class="tab-pane-header" ng-dblclick="activeEditPreview('data')" title="Double click to edit"> | |
404 | <i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'data'"></i> Data | |
405 | </div> | |
406 | <div ng-if="fieldToEdit !== 'data'"> | |
407 | <div class="padding-left-0 col-md-12 margin-bottom-24"> | |
408 | <div ng-if="lastClickedVuln.data === '' || lastClickedVuln.data === undefined" style="margin-bottom: 15px;color: #aaa;"> | |
409 | <p> | |
410 | No Data was found. | |
411 | </p> | |
412 | </div> | |
413 | <div class="tab-pane-header pretty-text" id="data-prev" ng-dblclick="activeEditPreview('data')" title="Double click to edit"> | |
414 | <span class="line-breaks">{{lastClickedVuln.data}} </span> | |
415 | </div> | |
416 | ||
417 | </div> | |
418 | </div> | |
419 | <div class="form-group" ng-if="fieldToEdit === 'data'"> | |
420 | <textarea type="text" class="form-control" id="inp-edit-data" rows="18" | |
421 | ng-model="lastClickedVuln.data" name="desc" placeholder="Data" | |
422 | ng-blur="processToEditPreview(false)" | |
423 | autofocus> | |
424 | </textarea> | |
425 | </div> | |
426 | </div> | |
427 | ||
428 | <div class="padding-left-0 row" style="margin-left: 0; margin-right: 0"> | |
429 | <div class="padding-left-0 col-md-6 col-sm-12 col-xs-12" ng-show="lastClickedVuln.type === 'VulnerabilityWeb'"> | |
430 | <div class="tab-pane-header" ng-dblclick="activeEditPreview('request')" title="Double click to edit"><i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'request'"></i> Request | |
431 | </div> | |
432 | <div ng-if="fieldToEdit !== 'request'" > | |
433 | <div class="col-md-12 margin-bottom-24"> | |
434 | <div class="tab-pane-header pretty-text text-code" id="request-prev"> | |
435 | <div ng-if="lastClickedVuln.request === '' || lastClickedVuln.request === undefined" | |
436 | style="margin-bottom: 15px;color: #aaa;"> | |
437 | <p> | |
438 | No Request was found. | |
439 | </p> | |
440 | </div> | |
441 | <span class="line-breaks" ng-dblclick="activeEditPreview('request')" title="Double click to edit"> {{lastClickedVuln.request}} </span> | |
442 | </div> | |
443 | </div> | |
444 | </div> | |
445 | <div class="form-group" ng-if="fieldToEdit === 'request'"> | |
446 | <textarea type="text" class="form-control" id="inp-edit-request" rows="18" | |
447 | ng-model="lastClickedVuln.request" name="desc" placeholder="Request" | |
448 | ng-blur="processToEditPreview(false)" | |
449 | autofocus> | |
450 | </textarea> | |
451 | </div> | |
452 | ||
453 | </div> | |
454 | <div class="col-md-6 col-sm-12 col-xs-12" ng-show="lastClickedVuln.type === 'VulnerabilityWeb'"> | |
455 | <div class="tab-pane-header" ng-dblclick="activeEditPreview('response')" title="Double click to edit"><i class="fa fa-spinner fa-spin" ng-show="isUpdatingVuln === true && fieldToEdit === 'response'"></i> Response | |
456 | </div> | |
457 | <div ng-if="fieldToEdit !== 'response'"> | |
458 | <div class="col-md-12 margin-bottom-24"> | |
459 | <div class="tab-pane-header pretty-text text-code" id="response-prev" > | |
460 | <div ng-if="lastClickedVuln.response === '' || lastClickedVuln.response === undefined" style="margin-bottom: 15px;color: #aaa;"> | |
461 | <p> | |
462 | No Response was found. | |
463 | </p> | |
464 | </div> | |
465 | <span class="line-breaks" ng-dblclick="activeEditPreview('response')" title="Double click to edit"> {{lastClickedVuln.response}} </span> | |
466 | ||
467 | </div> | |
468 | ||
469 | </div> | |
470 | </div> | |
471 | <div class="form-group" ng-if="fieldToEdit === 'response'"> | |
472 | <textarea type="text" class="form-control" id="inp-edit-response" rows="18" | |
473 | ng-model="lastClickedVuln.response" name="desc" placeholder="Response" | |
474 | ng-blur="processToEditPreview(false)" | |
475 | autofocus> | |
476 | </textarea> | |
477 | </div> | |
478 | </div> | |
479 | </div> | |
480 | </div> | |
481 | </div> | |
482 | ||
483 | <div id="tab-evidence_prev" class="container tab-pane-container tab-pane fade"><br> | |
484 | <div class="padding-left-0 col-md-12 margin-bottom-15px"> | |
485 | ||
486 | <div class="col-md-4"> | |
487 | ||
488 | <div class="tab-pane-header padding-left-0 col-md-8">Attachments</div> | |
489 | <div class="padding-left-0 form-group col-md-4"> | |
490 | <div class="upload-plus"> | |
491 | <div class="icon-evidence"> | |
492 | <button type="button" class="btn btn-success col-md-12"> | |
493 | <i class="fa fa-plus"></i> New | |
494 | </button> | |
495 | ||
496 | </div> | |
497 | <input type="file" class="input-evidence" nv-file-select="" uploader="uploader" multiple/> | |
498 | </div> | |
499 | </div> | |
500 | ||
501 | ||
502 | ||
503 | <table class="table"> | |
504 | <thead> | |
505 | <tr> | |
506 | <th width="50%">Name</th> | |
507 | <th>Actions</th> | |
508 | </tr> | |
509 | </thead> | |
510 | <tbody> | |
511 | <tr ng-repeat="(name, file) in lastClickedVuln._attachments"> | |
512 | <td> | |
513 | <strong><a href="javascript:;" ng-click="selectItemToPrev(name)">{{ name }}</a></strong> | |
514 | <div ng-show="uploader.isHTML5" ng-thumb="{ file: file, height: 100 }"></div> | |
515 | </td> | |
516 | <td nowrap> | |
517 | <button type="button" class="btn btn-danger btn-xs" ng-click="removeEvidence(name)"> | |
518 | <span class="glyphicon glyphicon-trash"></span> Remove | |
519 | </button> | |
520 | <button type="button" class="btn btn-info btn-xs" ng-click="copyToClipboard(name)"> | |
521 | <span class="fa fa-copy"></span> Copy link | |
522 | </button> | |
523 | </td> | |
524 | </tr> | |
525 | </tbody> | |
526 | </table> | |
527 | </div> | |
528 | ||
529 | ||
530 | <div class="col-md-8 attachment-prev"> | |
531 | <div ng-if="selectedAtachment.url === ''" class="no-info-overlay" style="margin-bottom: 15px;top: -8px;"> | |
532 | <p class="no-info-text"> | |
533 | No attachment selected | |
534 | </p> | |
535 | </div> | |
536 | ||
537 | <div class="tab-pane-header">{{selectedAtachment.name}}</div> | |
538 | <img ng-if="selectedAtachment.url !== '' && selectedAtachment.imgPrevFail === false" src="{{selectedAtachment.url}}" id="attachment-img"> | |
539 | <div class="no-info-overlay" ng-if="selectedAtachment.imgPrevFail === true" style="margin-bottom: 15px;top: -8px;"> | |
540 | <p class="no-info-text"> | |
541 | No available preview to this attachment | |
542 | </p> | |
543 | </div> | |
544 | </div> | |
545 | ||
546 | ||
547 | ||
548 | </div> | |
549 | ||
550 | </div> | |
551 | ||
552 | <div id="hosts_prev" class="container tab-pane-container tab-pane fade"><br> | |
553 | <div class="padding-left-0 col-md-12"> | |
554 | <div class="padding-left-0 col-md-3"> | |
555 | <div class="tab-pane-header">Host</div> | |
556 | <div class="tab-pane-header"><i class="fa fa-desktop"></i> {{lastClickedVuln.target}}:{{lastClickedVuln.service.ports}}</div> | |
557 | </div> | |
558 | ||
559 | <div class="padding-left-0 col-md-3"> | |
560 | <div class="tab-pane-header">Hostnames</div> | |
561 | <div class="tab-pane-header" ng-repeat="hostname in lastClickedVuln.hostnames"><i class="fa fa-desktop"></i> {{hostname}}</div> | |
562 | </div> | |
563 | </div> | |
564 | </div> | |
565 | ||
566 | <div id="tab-custom-fields_prev" class="container tab-pane-container tab-pane fade"><br> | |
567 | <div ng-if="customFields.length === 0" class="no-info-overlay" style="margin-bottom: 15px;"> | |
568 | <p class="no-info-text"> | |
569 | No custom fields were found. To create one refer to our | |
570 | <a href="https://github.com/infobyte/faraday/wiki/Custom-Fields" target="_blank">wiki page</a>. | |
571 | </p> | |
572 | </div> | |
573 | <div class="padding-left-0 col-md-5 margin-bottom-15px"> | |
574 | <div class="padding-left-0 col-md-12" ng-repeat="cf in customFields | orderBy : 'field_order'"> | |
575 | <custom-field-prev field="{{cf}}"></custom-field-prev> | |
576 | </div> | |
577 | </div> | |
578 | </div> | |
579 | </div> | |
580 | ||
581 | ||
582 | ||
583 | </div> | |
584 | </div> | |
166 | 585 | </div> |
167 | 586 | <!-- .right-main --> |
168 | 587 | </section> |
0 | 0 | <div ng-if="row.entity._id != undefined"> |
1 | <div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents white-space"><a ng-href="{{grid.appScope.hash}}/search/name={{grid.appScope.encodeUrl(row.entity.name)}}" append-search-param="name={{grid.appScope.encodeUrl(row.entity.name)}}">{{COL_FIELD CUSTOM_FILTERS}}</a> | |
1 | <div ng-keyup="grid.appScope.changeVulnPrevByEventKey($event)" ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents white-space"><a href="javascript:;" ng-click="grid.appScope.toggleVulnPreview($event, row.entity)">{{COL_FIELD CUSTOM_FILTERS}}</a> <span style="color: #337ab7" ng-if="row.entity._id === grid.appScope.lastClickedVuln._id"><i class="fa fa-check"></i></span> | |
2 | 2 | </div> |
3 | 3 | </div> |
4 | <div ng-if="row.groupHeader && col.grouping.groupPriority !== undefined" class="ui-grid-cell-contents white-space">{{COL_FIELD CUSTOM_FILTERS}}</div> | |
4 | <div ng-keyup="grid.appScope.changeVulnPrevByEventKey($event)" ng-if="row.groupHeader && col.grouping.groupPriority !== undefined" class="ui-grid-cell-contents white-space">{{COL_FIELD CUSTOM_FILTERS}}</div> |
0 | // Faraday Penetration Test IDE | |
1 | // Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) | |
2 | // See the file 'doc/LICENSE' for the license information | |
3 | ||
4 | angular.module('faradayApp') | |
5 | .factory('uiCommonFact', ['BASEURL', function (BASEURL) { | |
6 | ||
7 | var uiCommonFact = {}; | |
8 | ||
9 | uiCommonFact.updateBtnSeverityColor = function (severity, btnSelector, caretSelector) { | |
10 | var color = undefined; | |
11 | switch (severity) { | |
12 | case "unclassified": | |
13 | color = '#999999'; | |
14 | break; | |
15 | case "info": | |
16 | color = '#2e97bd'; | |
17 | break; | |
18 | case "low": | |
19 | color = '#a1ce31'; | |
20 | break; | |
21 | case "med": | |
22 | color = '#dfbf35'; | |
23 | break; | |
24 | case "high": | |
25 | color = '#df3936'; | |
26 | break; | |
27 | case "critical": | |
28 | color = '#932ebe'; | |
29 | break; | |
30 | } | |
31 | ||
32 | angular.element(btnSelector).css('background-color', color); | |
33 | angular.element(caretSelector).css('background-color', color); | |
34 | }; | |
35 | ||
36 | ||
37 | uiCommonFact.updateBtnStatusColor = function (status, btnSelector, caretSelector) { | |
38 | var color = undefined; | |
39 | switch (status) { | |
40 | case "opened": | |
41 | color = '#DB3130'; | |
42 | break; | |
43 | case "closed": | |
44 | color = '#97F72C'; | |
45 | break; | |
46 | case "re-opened": | |
47 | color = '#DBB72F'; | |
48 | break; | |
49 | case "risk-accepted": | |
50 | color = '#288DB4'; | |
51 | break; | |
52 | default: | |
53 | color = '#aaaaaa'; | |
54 | break; | |
55 | } | |
56 | ||
57 | angular.element(btnSelector).css('background-color', color); | |
58 | angular.element(caretSelector).css('background-color', color); | |
59 | }; | |
60 | ||
61 | ||
62 | uiCommonFact.populate = function (template, vuln) { | |
63 | for (var key in vuln) { | |
64 | if (key !== "refs" && key !== "policyviolations" && template.hasOwnProperty(key) && vuln.hasOwnProperty(key)) { | |
65 | vuln[key] = template[key]; | |
66 | } | |
67 | } | |
68 | // convert refs to an array of objects | |
69 | var refs = []; | |
70 | template.refs.forEach(function (ref) { | |
71 | refs.push(ref); | |
72 | }); | |
73 | vuln.refs = refs; | |
74 | ||
75 | // convert policyviolations to an array of objects | |
76 | var policyviolations = []; | |
77 | template.policyviolations.forEach(function (policyviolation) { | |
78 | policyviolations.push(policyviolation); | |
79 | }); | |
80 | vuln.policyviolations = policyviolations; | |
81 | }; | |
82 | ||
83 | ||
84 | uiCommonFact.newReference = function (newRef, vuln) { | |
85 | if (newRef !== "") { | |
86 | // we need to check if the ref already exists | |
87 | if (vuln.refs.filter(function (ref) { | |
88 | return ref === newRef | |
89 | }).length === 0) { | |
90 | vuln.refs.push(newRef); | |
91 | newRef = ""; | |
92 | } | |
93 | } | |
94 | }; | |
95 | ||
96 | ||
97 | uiCommonFact.newPolicyViolation = function (newPolicyViolation, vuln) { | |
98 | if (newPolicyViolation !== "") { | |
99 | // we need to check if the policy violation already exists | |
100 | if (vuln.policyviolations.filter(function (policyviolation) { | |
101 | return policyviolation.value === newPolicyViolation | |
102 | }).length === 0) { | |
103 | vuln.policyviolations.push(newPolicyViolation); | |
104 | newPolicyViolation = ""; | |
105 | } | |
106 | } | |
107 | }; | |
108 | ||
109 | ||
110 | uiCommonFact.removeEvidence = function (name, vuln) { | |
111 | delete vuln._attachments[name]; | |
112 | }; | |
113 | ||
114 | uiCommonFact.openEvidence = function (name, vuln, ws) { | |
115 | var currentEvidence = vuln._attachments[name]; | |
116 | if (!currentEvidence.newfile) | |
117 | window.open(BASEURL + '_api/v2/ws/' + ws + '/vulns/' + vuln._id + '/attachment/' + encodeURIComponent(name), '_blank'); | |
118 | }; | |
119 | ||
120 | ||
121 | return uiCommonFact; | |
122 | ||
123 | }]); |
0 | 0 | with (import <nixpkgs> {}); |
1 | let | |
2 | in | |
3 | 1 | mkShell { |
4 | buildInputs = with python27Packages; | |
2 | buildInputs = [pandoc] ++ (with python27Packages; | |
5 | 3 | [virtualenv pyopenssl psycopg2 pillow pygobject3 pynacl matplotlib lxml ldap |
6 | gobjectIntrospection gtk3 gnome3.vte ipython | |
7 | ]; | |
4 | gobjectIntrospection gtk3 gnome3.vte ipython gssapi | |
5 | ]); | |
8 | 6 | shellHook = '' |
9 | 7 | unset SOURCE_DATE_EPOCH # Required to make pip work |
8 | ||
9 | VENV_PATH=.venv-white | |
10 | grep -q p- VERSION && VENV_PATH=.venv-pink | |
11 | grep -q b- VERSION && VENV_PATH=.venv-black | |
10 | 12 | |
11 | 13 | mkvirtualenv(){ |
12 | 14 | # Reset previous virtualenv |
13 | 15 | type -t deactivate && deactivate |
14 | rm -rf venv | |
16 | rm -rf $VENV_PATH | |
15 | 17 | |
16 | 18 | # Build new virtualenv with system packages |
17 | virtualenv --system-site-packages venv | |
18 | source venv/bin/activate | |
19 | virtualenv --system-site-packages $VENV_PATH | |
20 | source $VENV_PATH/bin/activate | |
19 | 21 | pip install -r requirements_server.txt |
20 | 22 | pip install -r requirements.txt |
21 | 23 | pip install -r requirements_dev.txt |
22 | 24 | } |
23 | 25 | |
24 | if [[ -d venv ]]; then | |
25 | source venv/bin/activate | |
26 | if [[ -d $VENV_PATH ]]; then | |
27 | source $VENV_PATH/bin/activate | |
26 | 28 | else |
27 | 29 | echo Creating new virtualenv |
28 | 30 | mkvirtualenv |
13 | 13 | res = test_client.get('v2/vulners/exploits/{id}'.format(id=cve_id)) |
14 | 14 | assert res.status_code == 200 |
15 | 15 | |
16 | @pytest.mark.skip() | |
16 | 17 | def test_key_error(self, test_client): |
17 | 18 | cve_id = "CVE-2018-1999035ERROR" |
18 | 19 | res = test_client.get('v2/vulners/exploits/{id}'.format(id=cve_id)) |
19 | 20 | assert res.status_code == 400 |
20 | 21 | |
22 | @pytest.mark.skip() | |
21 | 23 | def test_get_exploit_with_modules(self, test_client): |
22 | 24 | cve_id = "CVE-2016-9299" |
23 | 25 | res = test_client.get('v2/vulners/exploits/{id}'.format(id=cve_id)) |
16 | 16 | from hypothesis import given |
17 | 17 | from hypothesis.strategies import text, lists, integers, one_of, none |
18 | 18 | from depot.manager import DepotManager |
19 | ||
20 | from server.fields import FaradayUploadedFile | |
19 | 21 | |
20 | 22 | try: |
21 | 23 | from urllib import urlencode |
41 | 43 | VulnerabilityGeneric, |
42 | 44 | Vulnerability, |
43 | 45 | VulnerabilityWeb, |
44 | Reference, PolicyViolation, CommandObject) | |
46 | Reference, PolicyViolation, CommandObject, File) | |
45 | 47 | from test_cases.factories import ServiceFactory, CommandFactory, \ |
46 | 48 | CommandObjectFactory, HostFactory, EmptyCommandFactory, \ |
47 | 49 | UserFactory, VulnerabilityWebFactory, VulnerabilityFactory, \ |
396 | 398 | assert res.status_code == 200 |
397 | 399 | assert res.data == file_content |
398 | 400 | |
401 | def test_get_attachments_by_vuln(self, test_client, session, workspace): | |
402 | vuln = VulnerabilityFactory.create(workspace=workspace) | |
403 | session.add(vuln) | |
404 | session.commit() | |
405 | ||
406 | with open('test_cases/data/faraday.png', 'r') as file_obj: | |
407 | new_file = FaradayUploadedFile(file_obj.read()) | |
408 | ||
409 | new_attach = File(object_type='vulnerability', object_id=vuln.id, name='Faraday', filename='faraday.png', | |
410 | content=new_file) | |
411 | session.add(new_attach) | |
412 | session.commit() | |
413 | ||
414 | res = test_client.get(self.url(workspace=workspace) + '{0}/attachments/'.format(vuln.id)) | |
415 | assert res.status_code == 200 | |
416 | assert new_attach.filename in res.json | |
417 | assert 'image/png' in res.json[new_attach.filename]['content_type'] | |
399 | 418 | |
400 | 419 | |
401 | 420 | def test_create_vuln_props(self, host_with_hostnames, test_client, session): |
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 | try: | |
8 | from lxml import etree as ET | |
9 | except ImportError: | |
10 | import xml.etree.ElementTree as ET | |
11 | ||
12 | from server.config import FARADAY_BASE | |
13 | ||
14 | ||
15 | def test_matching_versions(): | |
16 | with open(FARADAY_BASE + '/VERSION', 'r') as output: | |
17 | version_file = output.read().strip() | |
18 | ||
19 | version_default = parse_element_from_xml('version') | |
20 | ||
21 | assert version_file == version_default | |
22 | ||
23 | ||
24 | def parse_element_from_xml(tag_name): | |
25 | with open(FARADAY_BASE + '/config/default.xml', 'r') as output: | |
26 | default_data = output.read() | |
27 | tree = ET.fromstring(default_data) | |
28 | default_element = tree.find(tag_name).text | |
29 | ||
30 | return default_element |