Update upstream source from tag 'upstream/3.4'
Update to upstream version '3.4'
with Debian dir 96cd353e944687f23cf54b5e7ad3c82ff4e542e1
Sophie Brun
5 years ago
0 | * In GTK, check active_workspace its not null | |
1 | ||
2 | * Add fbruteforce services fplugin | |
3 | ||
4 | ||
5 | * Attachments can be added to a vulnerability through the API. | |
6 | ||
7 | * Catch gaierror error on lynis plugin | |
8 | ||
9 | * Add OR and NOT with parenthesis support on status report search | |
10 | ||
11 | * Info API now is public | |
12 | * Web UI now detects Appscan plugin | |
13 | ||
14 | * Improve performance on the workspace using cusotm query | |
15 | ||
16 | * Workspaces can be set as active/disable in welcome page. | |
17 | * Change Nmap plugin, response field in VulnWeb now goes to Data field. | |
18 | ||
19 | * Update code to support latest SQLAlchemy version | |
20 | ||
21 | * Fix `create_vuln` fplugin bug that incorrectly reported duplicated vulns | |
22 |
6 | 6 | |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | ||
10 | ||
11 | 3.4: | |
12 | --- | |
13 | * In GTK, check active_workspace its not null | |
14 | ||
15 | * Add fbruteforce services fplugin | |
16 | ||
17 | ||
18 | * Attachments can be added to a vulnerability through the API. | |
19 | ||
20 | * Catch gaierror error on lynis plugin | |
21 | ||
22 | * Add OR and NOT with parenthesis support on status report search | |
23 | ||
24 | * Info API now is public | |
25 | * Web UI now detects Appscan plugin | |
26 | ||
27 | * Improve performance on the workspace using cusotm query | |
28 | ||
29 | * Workspaces can be set as active/disable in welcome page. | |
30 | * Change Nmap plugin, response field in VulnWeb now goes to Data field. | |
31 | ||
32 | * Update code to support latest SQLAlchemy version | |
33 | ||
34 | * Fix `create_vuln` fplugin bug that incorrectly reported duplicated vulns | |
9 | 35 | |
10 | 36 | |
11 | 37 | 3.3 [Novemeber 14th, 2018]: |
6 | 6 | |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | ||
10 | ||
11 | 3.4: | |
12 | --- | |
13 | * In GTK, check active_workspace its not null | |
14 | ||
15 | * Add fbruteforce services fplugin | |
16 | ||
17 | ||
18 | * Attachments can be added to a vulnerability through the API. | |
19 | ||
20 | * Catch gaierror error on lynis plugin | |
21 | ||
22 | * Add OR and NOT with parenthesis support on status report search | |
23 | ||
24 | * Info API now is public | |
25 | * Web UI now detects Appscan plugin | |
26 | ||
27 | * Improve performance on the workspace using cusotm query | |
28 | ||
29 | * Workspaces can be set as active/disable in welcome page. | |
30 | * Change Nmap plugin, response field in VulnWeb now goes to Data field. | |
31 | ||
32 | * Update code to support latest SQLAlchemy version | |
33 | ||
34 | * Fix `create_vuln` fplugin bug that incorrectly reported duplicated vulns | |
9 | 35 | |
10 | 36 | |
11 | 37 | 3.3 [Novemeber 14th, 2018]: |
7 | 7 | |
8 | 8 | from model.common import factory |
9 | 9 | from persistence.server import models |
10 | from persistence.server.server_io_exceptions import ( | |
11 | CantCommunicateWithServerError, | |
12 | ConflictInDatabase | |
13 | ) | |
10 | 14 | |
11 | 15 | __description__ = 'Creates a new vulnerability' |
12 | 16 | __prettyname__ = 'Create Vulnerability' |
30 | 34 | default='false') |
31 | 35 | parser.add_argument('--description', help='Vulnerability description', default='') |
32 | 36 | |
33 | parser.add_argument('--dry-run', action='store_true', help='Do not touch the database. Only print the object ID') | |
34 | ||
35 | 37 | parsed_args = parser.parse_args(args) |
36 | 38 | |
37 | 39 | obj = factory.createModelObject(models.Vuln.class_signature, |
52 | 54 | 'parent': parsed_args.parent, |
53 | 55 | } |
54 | 56 | |
55 | old = models.get_vulns( | |
57 | try: | |
58 | models.create_vuln(workspace, obj) | |
59 | except ConflictInDatabase as ex: | |
60 | if ex.answer.status_code == 409: | |
61 | try: | |
62 | old_id = ex.answer.json()['object']['_id'] | |
63 | except KeyError: | |
64 | print "Vulnerability already exists. Couldn't fetch ID" | |
65 | return 2, None | |
66 | else: | |
67 | print "A vulnerability with ID %s already exists!" % old_id | |
68 | return 2, None | |
69 | else: | |
70 | print "Unknown error while creating the vulnerability" | |
71 | return 2, None | |
72 | except CantCommunicateWithServerError as ex: | |
73 | print "Error while creating vulnerability:", ex.response.text | |
74 | return 2, None | |
75 | ||
76 | new = models.get_vulns( | |
56 | 77 | workspace, |
57 | 78 | **params |
58 | 79 | ) |
59 | 80 | |
60 | if not old: | |
61 | if not parsed_args.dry_run: | |
62 | models.create_vuln(workspace, obj) | |
63 | old = models.get_vulns( | |
64 | workspace, | |
65 | **params | |
66 | ) | |
67 | else: | |
68 | print "A vulnerability with ID %s already exists!" % old[0].getID() | |
69 | return 2, None | |
70 | ||
71 | return 0, old[0].getID() | |
81 | return 0, new[0].getID() |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ''' | |
8 | ||
9 | import os | |
10 | import sys | |
11 | import base64 | |
12 | import shlex | |
13 | import time | |
14 | import re | |
15 | import requests | |
16 | ||
17 | ||
18 | from subprocess import Popen, PIPE, call | |
19 | from persistence.server import models, server | |
20 | from persistence.server.server import SERVER_URL | |
21 | ||
22 | __description__ = 'Script to perform a brute force attack on different services in a workspace' | |
23 | __prettyname__ = 'FBrute' | |
24 | ||
25 | SUPPORTED_SERVICES = ["asterisk", "cisco", "cisco-enable", "cvs", "firebird", "ftp", "ftps", "http", | |
26 | "https", "http-proxy", "icq" "imap", "imaps", "irc", "ldap2", "ldap3", | |
27 | "mssql", "mysql", "nntp", "oracle-listener", "oracle-sid", "pcanywhere", | |
28 | "pcnfs", "pop3", "pop3s", "postgres", "rdp", "redis", "rexec", "rlogin", | |
29 | "rsh", "rtsp", "s7-300", "sip", "smb", "smtp", "smtps", "smtp-enum", "snmp", | |
30 | "socks5", "ssh", "sshkey", "svn", "teamspeak", "telnet" | |
31 | "telnets", "vmauthd", "vnc", "xmpp"] | |
32 | ||
33 | PID = os.getpid() | |
34 | ||
35 | def check_hydra(): | |
36 | p = Popen(["which", "hydra"], stdout=PIPE) | |
37 | p.communicate()[0] | |
38 | return p.returncode == 0 | |
39 | ||
40 | ||
41 | def add_output(output): | |
42 | pwd = os.getcwd() | |
43 | data = {"cmd" : base64.b64encode(output), "pid" : PID, "pwd" : base64.b64encode(pwd)} | |
44 | requests.post("http://localhost:9977/cmd/input", json=data) | |
45 | ||
46 | ||
47 | def send_output(output): | |
48 | output = base64.b64encode(open(output, "r").read()) | |
49 | data = {"exit_code" : 0, "pid" : PID, "output" : output} | |
50 | requests.post("http://localhost:9977/cmd/output", json=data) | |
51 | ||
52 | ||
53 | def search_hosts_by_service(workspace, b_service): | |
54 | output = "" | |
55 | all_hosts = list(models.get_hosts(workspace)) | |
56 | all_services = list(models.get_services(workspace)) | |
57 | for host in all_hosts: | |
58 | for service in all_services: | |
59 | id_service_host = service.parent_id | |
60 | if host.id == id_service_host and service.name == b_service: | |
61 | output += host.name + "\n" | |
62 | break | |
63 | return output | |
64 | ||
65 | ||
66 | def total_credentials(workspace): | |
67 | json_creds = server._get( | |
68 | SERVER_URL + "/_api/v2/ws/%s/credential" % workspace) | |
69 | ||
70 | return len(json_creds["rows"]) | |
71 | ||
72 | ||
73 | def get_credentials(workspace, key): | |
74 | credentials = "" | |
75 | ||
76 | json_creds = server._get( | |
77 | SERVER_URL + "/_api/v2/ws/%s/credential" % workspace) | |
78 | ||
79 | if len(json_creds["rows"]) > 0: | |
80 | ||
81 | for c in json_creds["rows"]: | |
82 | credentials += c["value"][key] + "\n" | |
83 | return credentials | |
84 | ||
85 | else: | |
86 | sys.exit("No credentials were found on faraday") | |
87 | ||
88 | ||
89 | def show_table_services(workspace): | |
90 | ||
91 | services = [] | |
92 | table = "" | |
93 | ||
94 | j_parsed = server._get( | |
95 | SERVER_URL + "/_api/v2/ws/%s/services/count?group_by=name" % workspace) | |
96 | ||
97 | if len(j_parsed["groups"]) > 0: | |
98 | ||
99 | table += "Number\tService\tCount\n" | |
100 | table += "------\t-------\t------\n" | |
101 | ||
102 | for l in j_parsed["groups"]: | |
103 | if l["name"] in SUPPORTED_SERVICES: | |
104 | services.append(l["name"]) | |
105 | table += "[" + str(services.index(l["name"])) + "]\t" | |
106 | table += l["name"] + "\t" + str(l["count"]) + "\n" | |
107 | return table, services | |
108 | ||
109 | else: | |
110 | sys.exit("No services availables") | |
111 | ||
112 | ||
113 | def input_index(text, leng): | |
114 | while 1: | |
115 | ||
116 | stdin = raw_input(text+"[0-"+str(leng-1)+"/q]: ") | |
117 | ||
118 | if re.search("[0-9]", stdin) is not None: | |
119 | ||
120 | if int(stdin) > leng-1 or int(stdin) < 0: | |
121 | continue | |
122 | ||
123 | else: | |
124 | return stdin | |
125 | ||
126 | elif stdin == "q": | |
127 | sys.exit(1) | |
128 | ||
129 | else: | |
130 | continue | |
131 | ||
132 | ||
133 | def show_options(workspace): | |
134 | ||
135 | user_define_dictionary = False | |
136 | usernames_dic_path = None | |
137 | passwords_dic_path = None | |
138 | user_faraday = None | |
139 | passwd_faraday = None | |
140 | ||
141 | # Muestro los servicios en el workspace soportados por hydra, en formato tabla | |
142 | table_services, services = show_table_services(workspace) | |
143 | print table_services | |
144 | ||
145 | service = int(input_index("What service do you want to bruteforce?", len(services))) | |
146 | ||
147 | # Verifico si el usuario quiere armar un diccionario con las credenciales | |
148 | # guardadas en faraday o si quiere utilizar uno propio | |
149 | print "\n[0] Choose a dictionary" | |
150 | print "[1] Create dictionary from Faraday (based in credentials stored in Faraday)\n" | |
151 | ||
152 | dictionary = int(input_index("Options ", 2)) | |
153 | ||
154 | #Le pido el path de el user dic y el password dic | |
155 | if dictionary == 0: | |
156 | usernames_dic_path = raw_input("Usernames file: ") | |
157 | passwords_dic_path = raw_input("Passwords file: ") | |
158 | user_define_dictionary = True | |
159 | ||
160 | else: | |
161 | ||
162 | print "\n[*] Obtaining credentials from the workspace %s" % workspace | |
163 | ||
164 | user_faraday = save_targets(get_credentials(workspace, "username")) | |
165 | passwd_faraday = save_targets(get_credentials(workspace, "password")) | |
166 | ||
167 | print "[*] Credentials found: %s" % total_credentials(workspace) | |
168 | print "\nUsername\t\tPassword" | |
169 | print "--------\t\t--------" | |
170 | ||
171 | for user, passw in zip( | |
172 | open(user_faraday, "r"), open(passwd_faraday, "r")): | |
173 | ||
174 | print "%s\t\t%s" % (user.strip(), passw.strip()) | |
175 | ||
176 | ||
177 | return service, services, user_define_dictionary, user_faraday, passwd_faraday, usernames_dic_path, passwords_dic_path | |
178 | ||
179 | ||
180 | def save_targets(output): | |
181 | ||
182 | dicc = "/tmp/targets_"+str(time.time()) | |
183 | ||
184 | f = open(dicc, "w") | |
185 | f.write(output) | |
186 | f.close() | |
187 | ||
188 | return dicc | |
189 | ||
190 | ||
191 | def main(workspace='', args=None, parser=None): | |
192 | ||
193 | print "\nThis script needs to be run inside from Faraday GTK.\n" | |
194 | if check_hydra(): | |
195 | ||
196 | service, services, user_define_dictionary, user_faraday, passwd_faraday, usernames_dic_path, passwords_dic_path = show_options(workspace) | |
197 | ||
198 | b_service = services[service] | |
199 | output = search_hosts_by_service(workspace, b_service) | |
200 | targets = save_targets(output) | |
201 | ||
202 | hydra_output = "/tmp/hydra_output-%s.txt" % time.time() | |
203 | ||
204 | print "Running Hydra, please wait to finish the bruteforce...\n" | |
205 | ||
206 | if user_define_dictionary: | |
207 | ||
208 | hydra_command1 = "hydra -L {0} -P {1} -e sr -M {2} -V -q {3} -o {4}".format( | |
209 | usernames_dic_path, | |
210 | passwords_dic_path, | |
211 | targets, | |
212 | b_service, | |
213 | hydra_output) | |
214 | ||
215 | add_output(hydra_command1) | |
216 | call(shlex.split(hydra_command1)) | |
217 | ||
218 | else: | |
219 | hydra_command2 = "hydra -L {0} -P {1} -e sr -M {2} -V -q {3} -o {4}".format( | |
220 | user_faraday, | |
221 | passwd_faraday, | |
222 | targets, | |
223 | b_service, | |
224 | hydra_output) | |
225 | ||
226 | add_output(hydra_command2) | |
227 | call(shlex.split(hydra_command2)) | |
228 | ||
229 | print "Processing information found in Faraday...\n" | |
230 | send_output(hydra_output) | |
231 | ||
232 | return None, None | |
233 | ||
234 | else: | |
235 | sys.exit("Hydra is not installed on the system. Install hydra to continue execution") | |
236 | return None, None |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>3.1.1</version> | |
4 | <version>3.3</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> |
127 | 127 | |
128 | 128 | @property |
129 | 129 | def active_ws_name(self): |
130 | return self.get_active_workspace().name | |
130 | active_workspace = self.get_active_workspace() | |
131 | ||
132 | if active_workspace: | |
133 | return active_workspace.name | |
134 | return "" | |
131 | 135 | |
132 | 136 | def get_active_workspace(self): |
133 | 137 | """Return the currently active workspace""" |
390 | 394 | |
391 | 395 | Return True if everything went OK, False if there was a problem |
392 | 396 | looking for the host.""" |
393 | current_ws_name = self.get_active_workspace().name | |
397 | active_workspace = self.get_active_workspace() | |
398 | ||
399 | if active_workspace: | |
400 | current_ws_name = active_workspace.name | |
401 | else: | |
402 | current_ws_name = "" | |
394 | 403 | |
395 | 404 | host = self.serverIO.get_host(host_id) |
396 | 405 | if not host: |
785 | 794 | |
786 | 795 | def on_faraday_plugin(self, action, param): |
787 | 796 | """Defines what happens when you press "Faraday Plugin..." on the menu""" |
797 | active_workspace = self.get_active_workspace() | |
798 | ||
799 | if active_workspace: | |
800 | name = active_workspace.getName() | |
801 | else: | |
802 | name = "" | |
803 | ||
804 | ||
788 | 805 | pluginsOption_window = FaradayPluginsDialog(self.window.get_current_focused_terminal(), |
789 | self.get_active_workspace().getName(), | |
806 | name, | |
790 | 807 | self.window) |
791 | 808 | pluginsOption_window.show_all() |
792 | 809 | |
945 | 962 | plugin = "_".join(action.get_name().split('_')[1:]) |
946 | 963 | terminal = self.window.get_current_focused_terminal() |
947 | 964 | |
948 | command = fplugin_utils.build_faraday_plugin_command(plugin, self.get_active_workspace().getName()) | |
949 | fd = terminal.get_pty().get_fd() | |
950 | ||
951 | os.write(fd, command) | |
965 | active_workspace = self.get_active_workspace() | |
966 | ||
967 | if active_workspace: | |
968 | command = fplugin_utils.build_faraday_plugin_command(plugin, active_workspace.getName()) | |
969 | fd = terminal.get_pty().get_fd() | |
970 | os.write(fd, command) |
213 | 213 | CONF.setDBSessionCookies(session_cookie) |
214 | 214 | |
215 | 215 | user_info = get_user_info() |
216 | ||
217 | if user_info is None or 'userCtx' not in user_info or 'roles' not in user_info['userCtx'] or 'client' in user_info['userCtx']['roles']: | |
218 | errorDialog(self, ("You can't login as a client. You have " + | |
219 | str(attempts - 1 - attempt) + | |
220 | " attempt(s) left.")) | |
221 | continue | |
222 | 216 | |
223 | 217 | self.destroy() |
224 | 218 |
327 | 327 | else: |
328 | 328 | return "Zap" |
329 | 329 | |
330 | elif "xml-report" == tag: | |
331 | if re.search("Appscan",output) is not None: | |
332 | return "Appscan" | |
330 | 333 | elif "niktoscan" == tag: |
331 | 334 | return "Nikto" |
332 | 335 | elif "MetasploitV4" == tag: |
79 | 79 | return(m.group(1).strip()) |
80 | 80 | |
81 | 81 | def listeningservices(self): |
82 | ||
82 | ||
83 | 83 | line = re.findall('^network_listen_port\[\]=(.+)$', |
84 | 84 | self.rawcontents, re.MULTILINE) |
85 | 85 | |
94 | 94 | |
95 | 95 | if name == '-': |
96 | 96 | name = 'Unknown' |
97 | ||
97 | ||
98 | 98 | else: |
99 | 99 | items_service = combo |
100 | 100 | count = items_service.count(':') |
115 | 115 | elif count == 5: |
116 | 116 | port = elements_ip_port[5] |
117 | 117 | ip = items_service[0].replace(':{}'.format(port), '') |
118 | ||
118 | ||
119 | 119 | self._svcHelper(ip, port, protocol, name) |
120 | 120 | |
121 | 121 | return self.services |
164 | 164 | lde = LynisLogDataExtracter(output=output) |
165 | 165 | |
166 | 166 | hostname = lde.hostname() |
167 | ip = socket.gethostbyname(hostname) | |
167 | try: | |
168 | ip = socket.gethostbyname(hostname) | |
169 | except socket.gaierror: | |
170 | ip = hostname | |
168 | 171 | h_id = self.createAndAddHost(name=ip, os=lde.osfullname(), hostnames=[hostname]) |
169 | 172 | |
170 | 173 | self.createAndAddVulnToHost( |
173 | 176 | severity='info', |
174 | 177 | desc=lde.kernelVersion() |
175 | 178 | ) |
176 | ||
177 | ||
179 | ||
178 | 180 | interfaces = lde.interfaces() |
179 | 181 | macs = lde.macs() |
180 | 182 | ipv4s = lde.ipv4() |
512 | 512 | severity = 0 |
513 | 513 | desc = v.desc |
514 | 514 | refs = v.refs |
515 | desc += "\nOutput: " + v.response if v.response else "" | |
516 | 515 | |
517 | 516 | if re.search(r"VULNERABLE", desc): |
518 | 517 | severity = "high" |
541 | 540 | s_id, |
542 | 541 | v.name, |
543 | 542 | desc=desc, |
543 | response = v.response if v.response else "", | |
544 | 544 | ref=refs, |
545 | 545 | severity=severity, |
546 | 546 | website=minterfase) |
29 | 29 | return flask.jsonify(gen_web_config()) |
30 | 30 | |
31 | 31 | get_config.is_public = True |
32 | show_info.is_public = True |
4 | 4 | from flask import Blueprint |
5 | 5 | from filteralchemy import FilterSet, operators |
6 | 6 | from marshmallow import fields, post_load, ValidationError |
7 | from marshmallow.validate import OneOf | |
7 | from marshmallow.validate import OneOf, Range | |
8 | 8 | from sqlalchemy.orm.exc import NoResultFound |
9 | 9 | |
10 | 10 | from server.api.base import AutoSchema, ReadWriteWorkspacedView, FilterSetMeta, \ |
27 | 27 | owned = fields.Boolean(default=False) |
28 | 28 | owner = PrimaryKeyRelatedField('username', dump_only=True, |
29 | 29 | attribute='creator') |
30 | port = fields.Integer(dump_only=True) # Port is loaded via ports | |
31 | ports = MutableField(fields.Integer(), | |
30 | port = fields.Integer(dump_only=True, strict=True, required=True, | |
31 | validate=[Range(min=0, error="The value must be greater than or equal to 0")]) # Port is loaded via ports | |
32 | ports = MutableField(fields.Integer(strict=True, required=True, | |
33 | validate=[Range(min=0, error="The value must be greater than or equal to 0")]), | |
32 | 34 | fields.Method(deserialize='load_ports'), |
33 | 35 | required=True, |
34 | 36 | attribute='port') |
47 | 49 | if len(value) != 1: |
48 | 50 | raise ValidationError('ports must be a list with exactly one' |
49 | 51 | 'element') |
50 | return str(value.pop()) | |
52 | port = value.pop() | |
53 | if port < 0: | |
54 | raise ValidationError('The value must be greater than or equal to 0') | |
55 | ||
56 | return str(port) | |
51 | 57 | |
52 | 58 | @post_load |
53 | 59 | def post_load_parent(self, data): |
2 | 2 | # See the file 'doc/LICENSE' for the license information |
3 | 3 | import os |
4 | 4 | import io |
5 | import json | |
5 | 6 | import logging |
6 | 7 | from base64 import b64encode, b64decode |
7 | 8 | |
10 | 11 | from flask import request |
11 | 12 | from flask import Blueprint |
12 | 13 | from flask_classful import route |
14 | from flask_restless.search import search | |
13 | 15 | from marshmallow import Schema, fields, post_load, ValidationError |
14 | 16 | from marshmallow.validate import OneOf, Length |
15 | 17 | from sqlalchemy.orm import aliased, joinedload, selectin_polymorphic, undefer |
16 | 18 | from sqlalchemy.orm.exc import NoResultFound |
17 | ||
19 | from psycopg2 import DataError | |
18 | 20 | from depot.manager import DepotManager |
19 | 21 | from server.api.base import ( |
20 | 22 | AutoSchema, |
21 | FilterAlchemyMixin, | |
23 | InvalidUsage, | |
22 | 24 | FilterSetMeta, |
23 | 25 | PaginatedMixin, |
26 | FilterAlchemyMixin, | |
24 | 27 | ReadWriteWorkspacedView, |
25 | InvalidUsage) | |
28 | ) | |
26 | 29 | from server.fields import FaradayUploadedFile |
27 | 30 | from server.models import ( |
28 | 31 | db, |
29 | 32 | File, |
30 | 33 | Host, |
31 | 34 | Service, |
35 | Hostname, | |
36 | Workspace, | |
32 | 37 | Vulnerability, |
33 | 38 | VulnerabilityWeb, |
34 | 39 | VulnerabilityGeneric, |
35 | Workspace, | |
36 | Hostname | |
37 | 40 | ) |
38 | 41 | from server.utils.database import get_or_create |
39 | 42 | |
43 | 46 | PrimaryKeyRelatedField, |
44 | 47 | SelfNestedField, |
45 | 48 | SeverityField, |
46 | MetadataSchema) | |
49 | MetadataSchema, | |
50 | ) | |
47 | 51 | |
48 | 52 | vulns_api = Blueprint('vulns_api', __name__) |
49 | 53 | logger = logging.getLogger(__name__) |
560 | 564 | res['groups'] = [convert_group(group) for group in res['groups']] |
561 | 565 | return res |
562 | 566 | |
563 | @route('/<vuln_id>/attachment/<attachment_filename>/') | |
564 | def attachment(self, workspace_name, vuln_id, attachment_filename): | |
567 | @route('/<vuln_id>/attachment/', methods=['POST']) | |
568 | def post_attachment(self, workspace_name, vuln_id): | |
569 | vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join( | |
570 | Workspace).filter(VulnerabilityGeneric.id == vuln_id, Workspace.name == workspace_name) | |
571 | ||
572 | if vuln_workspace_check: | |
573 | if 'file' not in request.files: | |
574 | flask.abort(400) | |
575 | ||
576 | faraday_file = FaradayUploadedFile(request.files['file'].read()) | |
577 | filename = request.files['file'].filename | |
578 | ||
579 | get_or_create( | |
580 | db.session, | |
581 | File, | |
582 | object_id=vuln_id, | |
583 | object_type='vulnerability', | |
584 | name=filename, | |
585 | filename=filename, | |
586 | content=faraday_file | |
587 | ) | |
588 | db.session.commit() | |
589 | return flask.jsonify({'message': 'Evidence upload was successful'}) | |
590 | else: | |
591 | flask.abort(404, "Vulnerability not found") | |
592 | ||
593 | @route('/filter') | |
594 | def filter(self, workspace_name): | |
595 | try: | |
596 | filters = json.loads(request.args.get('q')) | |
597 | except ValueError as ex: | |
598 | flask.abort(400, "Invalid filters") | |
599 | ||
600 | workspace = self._get_workspace(workspace_name) | |
601 | marshmallow_params = {'many': True, 'context': {}, 'strict': True} | |
602 | try: | |
603 | normal_vulns = search(db.session, | |
604 | Vulnerability, | |
605 | filters) | |
606 | normal_vulns = normal_vulns.filter_by(workspace_id=workspace.id) | |
607 | normal_vulns = self.schema_class_dict['VulnerabilityWeb'](**marshmallow_params).dumps(normal_vulns.all()) | |
608 | normal_vulns_data = json.loads(normal_vulns.data) | |
609 | except Exception: | |
610 | normal_vulns_data = [] | |
611 | try: | |
612 | web_vulns = search(db.session, | |
613 | VulnerabilityWeb, | |
614 | filters) | |
615 | web_vulns = web_vulns.filter_by(workspace_id=workspace.id) | |
616 | web_vulns = self.schema_class_dict['VulnerabilityWeb'](**marshmallow_params).dumps(web_vulns.all()) | |
617 | web_vulns_data = json.loads(web_vulns.data) | |
618 | except Exception: | |
619 | web_vulns_data = [] | |
620 | return self._envelope_list(normal_vulns_data + web_vulns_data) | |
621 | ||
622 | @route('/<vuln_id>/attachment/<attachment_filename>/', methods=['GET']) | |
623 | def get_attachment(self, workspace_name, vuln_id, attachment_filename): | |
565 | 624 | vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join( |
566 | 625 | Workspace).filter(VulnerabilityGeneric.id == vuln_id, |
567 | 626 | Workspace.name == workspace_name).first() |
589 | 648 | else: |
590 | 649 | flask.abort(404, "Vulnerability not found") |
591 | 650 | |
651 | ||
592 | 652 | VulnerabilityView.register(vulns_api) |
6 | 6 | from flask import Blueprint |
7 | 7 | from flask_classful import route |
8 | 8 | from marshmallow import Schema, fields, post_load, validate |
9 | ||
10 | from server.models import db, Workspace | |
9 | from sqlalchemy.orm import ( | |
10 | query_expression, | |
11 | with_expression | |
12 | ) | |
13 | from sqlalchemy.orm.exc import NoResultFound | |
14 | ||
15 | ||
16 | from server.models import db, Workspace, _make_vuln_count_property | |
11 | 17 | from server.schemas import ( |
12 | 18 | JSTimestampField, |
13 | 19 | MutableField, |
81 | 87 | schema_class = WorkspaceSchema |
82 | 88 | order_field = Workspace.name.asc() |
83 | 89 | |
84 | def _get_base_query(self): | |
90 | def index(self, **kwargs): | |
91 | query = self._get_base_query() | |
92 | objects = [] | |
93 | for workspace_stat in query: | |
94 | workspace_stat = dict(workspace_stat) | |
95 | for key, value in workspace_stat.items(): | |
96 | if key.startswith('workspace_'): | |
97 | new_key = key.replace('workspace_', '') | |
98 | workspace_stat[new_key] = workspace_stat[key] | |
99 | workspace_stat['scope'] = [] | |
100 | if workspace_stat['scope_raw']: | |
101 | workspace_stat['scope_raw'] = workspace_stat['scope_raw'].split(',') | |
102 | for scope in workspace_stat['scope_raw']: | |
103 | workspace_stat['scope'].append({'name': scope}) | |
104 | objects.append(workspace_stat) | |
105 | return self._envelope_list(self._dump(objects, kwargs, many=True)) | |
106 | ||
107 | def _get_base_query(self, object_id=None): | |
85 | 108 | try: |
86 | 109 | confirmed = bool(json.loads(flask.request.args['confirmed'])) |
87 | 110 | except (KeyError, ValueError): |
88 | 111 | confirmed = None |
89 | 112 | try: |
90 | 113 | active = bool(json.loads(flask.request.args['active'])) |
91 | query = Workspace.query_with_count(confirmed).filter(self.model_class.active == active) | |
114 | query = Workspace.query_with_count( | |
115 | confirmed, | |
116 | active=active, | |
117 | workspace_name=object_id) | |
92 | 118 | except (KeyError, ValueError): |
93 | query = Workspace.query_with_count(confirmed) | |
119 | query = Workspace.query_with_count( | |
120 | confirmed, | |
121 | workspace_name=object_id) | |
94 | 122 | return query |
95 | 123 | |
96 | def _get_base_query_deactivated(self): | |
124 | def _get_object(self, object_id, eagerload=False, **kwargs): | |
125 | """ | |
126 | Given the object_id and extra route params, get an instance of | |
127 | ``self.model_class`` | |
128 | """ | |
129 | confirmed = flask.request.values.get('confirmed', None) | |
130 | if confirmed: | |
131 | try: | |
132 | confirmed = bool(int(confirmed)) | |
133 | except ValueError: | |
134 | if confirmed.lower() == 'false': | |
135 | confirmed = False | |
136 | elif confirmed.lower() == 'true': | |
137 | confirmed = True | |
138 | self._validate_object_id(object_id) | |
139 | query = db.session.query(Workspace).filter_by(name=object_id) | |
140 | query = query.options( | |
141 | with_expression( | |
142 | Workspace.vulnerability_web_count, | |
143 | _make_vuln_count_property('vulnerability_web', | |
144 | confirmed=confirmed, | |
145 | use_column_property=False), | |
146 | ), | |
147 | with_expression( | |
148 | Workspace.vulnerability_standard_count, | |
149 | _make_vuln_count_property('vulnerability', | |
150 | confirmed=confirmed, | |
151 | use_column_property=False) | |
152 | ), | |
153 | with_expression( | |
154 | Workspace.vulnerability_total_count, | |
155 | _make_vuln_count_property(type_=None, | |
156 | confirmed=confirmed, | |
157 | use_column_property=False) | |
158 | ), | |
159 | with_expression( | |
160 | Workspace.vulnerability_code_count, | |
161 | _make_vuln_count_property('vulnerability_code', | |
162 | use_column_property=False) | |
163 | ), | |
164 | ||
165 | ||
166 | ) | |
167 | ||
97 | 168 | try: |
98 | confirmed = bool(json.loads(flask.request.args['confirmed'])) | |
99 | except (KeyError, ValueError): | |
100 | confirmed = None | |
101 | query = Workspace.query_with_count(confirmed).filter(self.model_class.active == False) | |
102 | return query | |
169 | obj = query.one() | |
170 | except NoResultFound: | |
171 | flask.abort(404, 'Object with id "%s" not found' % object_id) | |
172 | return obj | |
103 | 173 | |
104 | 174 | def _perform_create(self, data, **kwargs): |
105 | 175 | scope = data.pop('scope', []) |
8 | 8 | from os.path import join, expanduser |
9 | 9 | from random import SystemRandom |
10 | 10 | |
11 | from model.workspace import Workspace | |
11 | 12 | from server.config import LOCAL_CONFIG_FILE, copy_default_config_to_local |
12 | from server.models import User | |
13 | from server.models import User, Vulnerability, VulnerabilityWeb, Workspace, VulnerabilityGeneric | |
13 | 14 | |
14 | 15 | try: |
15 | 16 | # py2.7 |
16 | 16 | String, |
17 | 17 | Text, |
18 | 18 | UniqueConstraint, |
19 | event) | |
19 | event, | |
20 | text | |
21 | ) | |
20 | 22 | from sqlalchemy.exc import IntegrityError |
21 | 23 | from sqlalchemy.orm import relationship, undefer |
22 | 24 | from sqlalchemy.sql import select, text, table |
23 | 25 | from sqlalchemy.sql.expression import asc, case, join |
26 | from sqlalchemy.ext.hybrid import hybrid_property | |
24 | 27 | from sqlalchemy import func |
25 | 28 | from sqlalchemy.orm import ( |
26 | 29 | backref, |
540 | 543 | The value_attr argument isn't relevant to this implementation |
541 | 544 | """ |
542 | 545 | |
543 | if parent.getset_factory: | |
546 | if getattr(parent, 'getset_factory', False): | |
544 | 547 | getter, setter = parent.getset_factory( |
545 | 548 | parent.collection_class, parent) |
546 | 549 | else: |
550 | 553 | lazy_collection, creator, getter, setter, parent) |
551 | 554 | |
552 | 555 | def _create(self, value): |
553 | parent_instance = self.lazy_collection.ref() | |
556 | if getattr(self.lazy_collection, 'ref', False): | |
557 | # for sqlalchemy previous to 1.3.0b1 | |
558 | parent_instance = self.lazy_collection.ref() | |
559 | else: | |
560 | parent_instance = self.lazy_collection.parent | |
554 | 561 | session = db.session |
555 | 562 | conflict_objs = session.new |
556 | 563 | try: |
930 | 937 | object_id=self.id, |
931 | 938 | object_type='vulnerability' |
932 | 939 | ) |
940 | ||
941 | @hybrid_property | |
942 | def target(self): | |
943 | return self.target_host_ip | |
933 | 944 | |
934 | 945 | |
935 | 946 | class Vulnerability(VulnerabilityGeneric): |
1312 | 1323 | cascade="all, delete-orphan") |
1313 | 1324 | |
1314 | 1325 | @classmethod |
1315 | def query_with_count(cls, confirmed): | |
1326 | def query_with_count(cls, confirmed, active=None, workspace_name=None): | |
1316 | 1327 | """ |
1317 | 1328 | Add count fields to the query. |
1318 | 1329 | |
1319 | 1330 | If confirmed is True/False, it will only show the count for confirmed / not confirmed |
1320 | 1331 | vulnerabilities. Otherwise, it will show the count of all of them |
1321 | 1332 | """ |
1322 | return cls.query.options( | |
1323 | undefer(cls.host_count), | |
1324 | undefer(cls.credential_count), | |
1325 | undefer(cls.open_service_count), | |
1326 | undefer(cls.total_service_count), | |
1327 | with_expression( | |
1328 | cls.vulnerability_web_count, | |
1329 | _make_vuln_count_property('vulnerability_web', | |
1330 | confirmed=confirmed, | |
1331 | use_column_property=False) | |
1332 | ), | |
1333 | with_expression( | |
1334 | cls.vulnerability_code_count, | |
1335 | _make_vuln_count_property('vulnerability_code', | |
1336 | confirmed=confirmed, | |
1337 | use_column_property=False) | |
1338 | ), | |
1339 | with_expression( | |
1340 | cls.vulnerability_standard_count, | |
1341 | _make_vuln_count_property('vulnerability', | |
1342 | confirmed=confirmed, | |
1343 | use_column_property=False) | |
1344 | ), | |
1345 | with_expression( | |
1346 | cls.vulnerability_total_count, | |
1347 | _make_vuln_count_property(type_=None, | |
1348 | confirmed=confirmed, | |
1349 | use_column_property=False) | |
1350 | ), | |
1351 | ) | |
1333 | query = """ | |
1334 | SELECT | |
1335 | (SELECT COUNT(credential.id) AS count_1 | |
1336 | FROM credential | |
1337 | WHERE credential.workspace_id = workspace.id | |
1338 | ) AS credentials_count, | |
1339 | (SELECT COUNT(host.id) AS count_2 | |
1340 | FROM host | |
1341 | WHERE host.workspace_id = workspace.id | |
1342 | ) AS host_count, | |
1343 | p_4.count_3 as open_services, | |
1344 | p_4.count_4 as total_service_count, | |
1345 | p_5.count_5 as vulnerability_web_count, | |
1346 | p_5.count_6 as vulnerability_code_count, | |
1347 | p_5.count_7 as vulnerability_standard_count, | |
1348 | p_5.count_8 as vulnerability_total_count, | |
1349 | workspace.create_date AS workspace_create_date, | |
1350 | workspace.update_date AS workspace_update_date, | |
1351 | workspace.id AS workspace_id, | |
1352 | workspace.customer AS workspace_customer, | |
1353 | workspace.description AS workspace_description, | |
1354 | workspace.active AS workspace_active, | |
1355 | workspace.end_date AS workspace_end_date, | |
1356 | workspace.name AS workspace_name, | |
1357 | workspace.public AS workspace_public, | |
1358 | workspace.start_date AS workspace_start_date, | |
1359 | workspace.update_user_id AS workspace_update_user_id, | |
1360 | workspace.creator_id AS workspace_creator_id, | |
1361 | (SELECT {concat_func}(scope.name, ',') FROM scope where scope.workspace_id=workspace.id) as scope_raw | |
1362 | FROM workspace | |
1363 | LEFT JOIN (SELECT w.id as wid, COUNT(case when service.id IS NOT NULL and service.status = 'open' then 1 else null end) as count_3, COUNT(case when service.id IS NOT NULL then 1 else null end) AS count_4 | |
1364 | FROM service | |
1365 | RIGHT JOIN workspace w ON service.workspace_id = w.id | |
1366 | GROUP BY w.id | |
1367 | ) AS p_4 ON p_4.wid = workspace.id | |
1368 | LEFT JOIN (SELECT w.id as w_id, COUNT(case when vulnerability.type = 'vulnerability_web' then 1 else null end) as count_5, COUNT(case when vulnerability.type = 'vulnerability_code' then 1 else null end) AS count_6, COUNT(case when vulnerability.type = 'vulnerability' then 1 else null end) as count_7, COUNT(case when vulnerability.id IS NOT NULL then 1 else null end) AS count_8 | |
1369 | FROM vulnerability | |
1370 | RIGHT JOIN workspace w ON vulnerability.workspace_id = w.id | |
1371 | WHERE 1=1 {0} | |
1372 | GROUP BY w.id | |
1373 | ) AS p_5 ON p_5.w_id = workspace.id | |
1374 | """ | |
1375 | concat_func = 'string_agg' | |
1376 | if db.engine.dialect.name == 'sqlite': | |
1377 | concat_func = 'group_concat' | |
1378 | filters = [] | |
1379 | params = {} | |
1380 | ||
1381 | confirmed_vuln_filter = '' | |
1382 | if confirmed is not None: | |
1383 | if confirmed: | |
1384 | confirmed_vuln_filter = " AND vulnerability.confirmed " | |
1385 | else: | |
1386 | confirmed_vuln_filter = " AND NOT vulnerability.confirmed " | |
1387 | query = query.format(confirmed_vuln_filter, concat_func=concat_func) | |
1388 | ||
1389 | if active is not None: | |
1390 | filters.append(" workspace.active = :active ") | |
1391 | params['active'] = active | |
1392 | if workspace_name: | |
1393 | filters.append(" workspace.name = :workspace_name ") | |
1394 | params['workspace_name'] = workspace_name | |
1395 | if filters: | |
1396 | query += ' WHERE ' + ' AND '.join(filters) | |
1397 | #query += " GROUP BY workspace.id " | |
1398 | query += " ORDER BY workspace.name ASC" | |
1399 | return db.engine.execute(text(query), params) | |
1352 | 1400 | |
1353 | 1401 | def set_scope(self, new_scope): |
1354 | 1402 | return set_children_objects(self, new_scope, |
35 | 35 | if self.many: |
36 | 36 | ret = [] |
37 | 37 | for item in value: |
38 | ret.append(getattr(item, self.field_name)) | |
38 | try: | |
39 | ret.append(getattr(item, self.field_name)) | |
40 | except AttributeError: | |
41 | ret.append(item[self.field_name]) | |
39 | 42 | return ret |
40 | 43 | else: |
41 | 44 | if value is None: |
0 | from functools import wraps | |
1 | from flask import request | |
2 | from werkzeug.contrib.cache import SimpleCache | |
3 | ||
4 | cache = SimpleCache() | |
5 | ||
6 | ||
7 | def cached(timeout=5 * 60, key='view/%s'): | |
8 | def decorator(f): | |
9 | @wraps(f) | |
10 | def decorated_function(*args, **kwargs): | |
11 | cache_key = key % request.path | |
12 | rv = cache.get(cache_key) | |
13 | if rv is not None: | |
14 | return rv | |
15 | rv = f(*args, **kwargs) | |
16 | cache.set(cache_key, rv, timeout=timeout) | |
17 | return rv | |
18 | return decorated_function | |
19 | return decorator |
145 | 145 | <script type="text/javascript" src="scripts/statusReport/directives/appendSearchParam.js"></script> |
146 | 146 | <script type="text/javascript" src="scripts/statusReport/providers/target.js"></script> |
147 | 147 | <script type="text/javascript" src="scripts/statusReport/providers/reference.js"></script> |
148 | <script type="text/javascript" src="scripts/statusReport/providers/parser.js"></script> | |
148 | 149 | <script type="text/javascript" src="scripts/vulns/providers/vuln.js"></script> |
149 | 150 | <script type="text/javascript" src="scripts/vulns/providers/vulns.js"></script> |
150 | 151 | <script type="text/javascript" src="scripts/vulns/providers/web.js"></script> |
13 | 13 | <table class="table-v3 licenses-list table table-responsive"> |
14 | 14 | <thead> |
15 | 15 | <tr class="ui-grid-header"> |
16 | <th class="ui-grid-cell-contents ui-grid-header-cell">Active</th> | |
16 | 17 | <th class="ui-grid-cell-contents ui-grid-header-cell">Name</th> |
17 | 18 | <th class="ui-grid-cell-contents ui-grid-header-cell">Vulns</th> |
18 | 19 | <th class="ui-grid-cell-contents ui-grid-header-cell">Hosts</th> |
20 | 21 | </tr> |
21 | 22 | </thead> |
22 | 23 | <tbody> |
23 | <tr ng-repeat="ws in wss"> | |
24 | <td class="ui-grid-cell-contents"> | |
25 | <span class="onhover upsize" ng-click="redirect(ws)"> | |
26 | <b>{{ws}}</b> | |
24 | <tr ng-repeat="ws in workspaces"> | |
25 | <td class="ui-grid-cell-contents active-toggle"> | |
26 | <span ng-click="activeToggle(ws)" class="active-toggle-container" ng-class="{ disabled:ws.active === false }" uib-tooltip="{{(ws.active === true) ? 'Disable workspace' : 'Enable workspace'}}" tooltip-placement="right"> | |
27 | <img ng-src="{{ (ws.active) ? 'images/icon-list-confirmed.svg' : 'images/icon-list-notconfirmed.svg'}}" class="confirm-icon" ng-style="{ 'opacity': (ws.active === true) ? '1' : '0.7' }" /> | |
27 | 28 | </span> |
28 | 29 | </td> |
29 | <td ng-bind="objects[ws]['total_vulns']" class="ui-grid-cell-contents"></td> | |
30 | <td ng-bind="objects[ws]['hosts']" class="ui-grid-cell-contents"></td> | |
31 | <td ng-bind="objects[ws]['services']" class="ui-grid-cell-contents"></td> | |
30 | <td class="ui-grid-cell-contents"> | |
31 | <span class="onhover upsize" ng-click="redirect(ws.name)"> | |
32 | <b>{{ws.name}}</b> | |
33 | </span> | |
34 | </td> | |
35 | <td ng-bind="objects[ws.name]['total_vulns']" class="ui-grid-cell-contents"></td> | |
36 | <td ng-bind="objects[ws.name]['hosts']" class="ui-grid-cell-contents"></td> | |
37 | <td ng-bind="objects[ws.name]['services']" class="ui-grid-cell-contents"></td> | |
32 | 38 | </tr> |
33 | 39 | </tbody> |
34 | 40 | </table> |
9 | 9 | function (BASEURL, $http, $q) { |
10 | 10 | var ServerAPI = {}; |
11 | 11 | var APIURL = BASEURL + "_api/v2/"; |
12 | var FILTER_URL = BASEURL + "_api/filter/"; | |
12 | 13 | |
13 | 14 | var createGetRelatedUrl = function (wsName, objectType, objectId, relatedObjectType) { |
14 | 15 | var objectName = ((objectName) ? "/" + objectType : ""); |
343 | 344 | ServerAPI.getVulns = function (wsName, data) { |
344 | 345 | var getUrl = createGetUrl(wsName, 'vulns'); |
345 | 346 | return get(getUrl, data); |
347 | } | |
348 | ||
349 | ServerAPI.getFilteredVulns = function (wsName, jsonOptions) { | |
350 | var getUrl = createGetUrl(wsName, 'vulns'); | |
351 | return get(getUrl + 'filter?q=' + jsonOptions); | |
346 | 352 | } |
347 | 353 | |
348 | 354 | ServerAPI.getVulnerabilityTemplate = function (objId) { |
45 | 45 | }, function(response) { |
46 | 46 | if (response.status == 409) { |
47 | 47 | commonsFact.showMessage("Error while updating a new Vulnerability " + response.data.name + " Conflicting Vulnarability with id: " + response.data.object._id + ". " + response.data.message); |
48 | } else { | |
48 | }if (response.status === 400) { | |
49 | var field = Object.keys(response.data.messages)[0]; | |
50 | var error = response.data.messages[field][0]; | |
51 | commonsFact.showMessage("Your input data is wrong, " + field.toUpperCase() + ": " + error); | |
52 | }else { | |
49 | 53 | commonsFact.showMessage("Error from backend: " + response.status); |
50 | 54 | } |
51 | 55 |
40 | 40 | servicesManager.createService($scope.data, $routeParams.wsId).then(function() { |
41 | 41 | $modalInstance.close($scope.data); |
42 | 42 | }, function(response) { |
43 | if (response.status == 409) { | |
43 | if (response.status === 409) { | |
44 | 44 | commonsFact.showMessage("Error while creating a new Service " + response.data.name + " Conflicting Vulnarability with id: " + response.data.object._id + ". " + response.data.message); |
45 | } else { | |
45 | } if (response.status === 400) { | |
46 | var field = Object.keys(response.data.messages)[0]; | |
47 | var error = response.data.messages[field][0]; | |
48 | commonsFact.showMessage("Your input data is wrong, " + field.toUpperCase() +": " + error); | |
49 | }else { | |
46 | 50 | commonsFact.showMessage("Error from backend: " + response.status); |
47 | 51 | } |
48 | 52 | }); |
38 | 38 | <h5>Port</h5> |
39 | 39 | <div class="input-margin"> |
40 | 40 | <label class="sr-only" for="ports">Port</label> |
41 | <input type="number" class="form-control" id="ports" placeholder="Port" ng-model="data.ports"/> | |
41 | <input type="number" min="0" class="form-control" id="ports" placeholder="Port" ng-model="data.ports"/> | |
42 | 42 | </div> |
43 | 43 | </div> |
44 | 44 | <div class="col-md-3 protocol"> |
38 | 38 | <h5>Port</h5> |
39 | 39 | <div class="input-margin" ng-class="{'has-error': form.ports.$invalid }"> |
40 | 40 | <label class="sr-only" for="ports">Port</label> |
41 | <input type="number" class="form-control" id="ports" name="ports" placeholder="Port" ng-model="data.ports" required/> | |
41 | <input type="number" min="0" class="form-control" id="ports" name="ports" placeholder="Port" ng-model="data.ports" required/> | |
42 | 42 | </div> |
43 | 43 | </div> |
44 | 44 | <div class="col-md-3 protocol" ng-class="{'has-error': form.protocol.$invalid }"> |
5 | 5 | .controller("statusReportCtrl", |
6 | 6 | ["$scope", "$filter", "$routeParams", |
7 | 7 | "$location", "$uibModal", "$cookies", "$q", "$window", "BASEURL", |
8 | "SEVERITIES", "EASEOFRESOLUTION", "STATUSES", "hostsManager", "commonsFact", | |
8 | "SEVERITIES", "EASEOFRESOLUTION", "STATUSES", "hostsManager", "commonsFact", 'parserFact', | |
9 | 9 | "vulnsManager", "workspacesFact", "csvService", "uiGridConstants", "vulnModelsManager", |
10 | 10 | "referenceFact", "ServerAPI", '$http', |
11 | 11 | function($scope, $filter, $routeParams, |
12 | 12 | $location, $uibModal, $cookies, $q, $window, BASEURL, |
13 | SEVERITIES, EASEOFRESOLUTION, STATUSES, hostsManager, commonsFact, | |
13 | SEVERITIES, EASEOFRESOLUTION, STATUSES, hostsManager, commonsFact,parserFact, | |
14 | 14 | vulnsManager, workspacesFact, csvService, uiGridConstants, vulnModelsManager, referenceFact, ServerAPI, $http) { |
15 | 15 | $scope.baseurl; |
16 | 16 | $scope.columns; |
1075 | 1075 | }); |
1076 | 1076 | }; |
1077 | 1077 | |
1078 | // changes the URL according to search params | |
1078 | var loadFilteredVulns = function(wsName, jsonOptions) { | |
1079 | delete searchFilter.confirmed; | |
1080 | $scope.loading = true; | |
1081 | ||
1082 | vulnsManager.getFilteredVulns(wsName, jsonOptions) | |
1083 | .then(function(response) { | |
1084 | $scope.loading = false; | |
1085 | $scope.gridOptions.data = response.vulnerabilities; | |
1086 | $scope.gridOptions.totalItems = response.count; | |
1087 | ||
1088 | // Add the total amount of vulnerabilities as an option for pagination | |
1089 | // if it is larger than our biggest page size | |
1090 | if ($scope.gridOptions.totalItems > paginationOptions.defaultPageSizes[paginationOptions.defaultPageSizes.length - 1]) { | |
1091 | ||
1092 | $scope.gridOptions.paginationPageSizes = paginationOptions.defaultPageSizes.concat([$scope.gridOptions.totalItems]); | |
1093 | ||
1094 | // sadly, this will load the vuln list again because it fires a paginationChanged event | |
1095 | if ($scope.gridOptions.paginationPageSize > $scope.gridOptions.totalItems) | |
1096 | $scope.gridOptions.paginationPageSize = $scope.gridOptions.totalItems; | |
1097 | ||
1098 | // New vuln and MAX items per page setted => reload page size. | |
1099 | if ($scope.gridOptions.paginationPageSize === $scope.gridOptions.totalItems - 1) | |
1100 | $scope.gridOptions.paginationPageSize = $scope.gridOptions.totalItems; | |
1101 | ||
1102 | } | |
1103 | }); | |
1104 | }; | |
1105 | ||
1079 | 1106 | $scope.searchFor = function(search, params) { |
1080 | // TODO: It would be nice to find a way for changing | |
1081 | 1107 | // the url without reloading the controller |
1108 | $scope.searchParams = params; | |
1082 | 1109 | if(window.location.hash.substring(1).indexOf('groupby') === -1) { |
1083 | var url = "/status/ws/" + $routeParams.wsId; | |
1110 | var jsonOptions = parserFact.evaluateExpression(params); | |
1111 | loadFilteredVulns($routeParams.wsId, jsonOptions); | |
1084 | 1112 | } else { |
1085 | 1113 | var url = "/status/ws/" + $routeParams.wsId + "/groupby/" + $routeParams.groupbyId; |
1086 | } | |
1087 | ||
1088 | if(search && params != "" && params != undefined) { | |
1089 | var filter = commonsFact.parseSearchExpression(params); | |
1090 | var URLParams = commonsFact.searchFilterToURLParams(filter); | |
1091 | url += "/search/" + URLParams; | |
1092 | } | |
1093 | ||
1094 | $location.path(url); | |
1114 | $location.path(url); | |
1115 | } | |
1095 | 1116 | }; |
1096 | 1117 | |
1097 | 1118 | // toggles column show property |
1106 | 1127 | } |
1107 | 1128 | $cookies.put('SRcolumns', JSON.stringify($scope.columns)); |
1108 | 1129 | recalculateLastVisibleColSize(); |
1130 | }; | |
1131 | ||
1132 | $scope.isValidExpression = function (expression) { | |
1133 | return parserFact.isValid(expression); | |
1109 | 1134 | }; |
1110 | 1135 | |
1111 | 1136 | var compareSeverities = function(a, b) { |
60 | 60 | </button> |
61 | 61 | </div> |
62 | 62 | <div class="control-wrapper search-wrapper"> |
63 | <form role="form" ng-submit="searchFor(true, searchParams)"> | |
64 | <div class="form-group"> | |
63 | <form role="form" ng-submit="searchFor(true, searchParams)" title="The search expression can't contain parentheses that are not closed, double quotes that are not closed, or double spaces."> | |
64 | <div class="form-group" ng-class="{'has-error':!isValidExpression(searchParams)}"> | |
65 | 65 | <div class="input-group"> |
66 | <span class="input-group-addon glyphicon-btn glyphicon glyphicon-remove" ng-click="searchFor(false, '')" ng-if="search && search != 'confirmed=true'"></span> | |
66 | <span class="input-group-addon glyphicon-btn glyphicon glyphicon-remove" ng-click="searchFor(false, '')" ng-if="searchParams !== '' || (search && search != 'confirmed=true')"></span> | |
67 | 67 | <input type="text" class="form-control" placeholder="Enter keywords" ng-change="currentPage = 1" ng-model="searchParams" /> |
68 | <span class="input-group-addon glyphicon-btn" ng-click="searchFor(true, searchParams)"> | |
68 | <span class="input-group-addon glyphicon-btn" ng-click="searchFor(true, searchParams)" ng-disabled="!isValidExpression(searchParams)"> | |
69 | 69 | <img src="images/icon-toolbar-searchbar-loupe.svg" class="search-icon" /> |
70 | 70 | </span> |
71 | 71 | </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('parserFact', [function () { | |
6 | var parserFact = {}; | |
7 | ||
8 | parserFact.evaluateExpression = function (expression) { | |
9 | var outputJson = { | |
10 | "filters": [] | |
11 | }; | |
12 | ||
13 | if (expression !== '') { | |
14 | var tokens = clearTokens(expression); | |
15 | console.log("Tokens: " + tokens); | |
16 | ||
17 | var operatorStack = []; | |
18 | var termStack = []; | |
19 | ||
20 | for (var i = 0; i < tokens.length; i++) { | |
21 | console.log("Analyzing Token: " + tokens[i]); | |
22 | if (tokens[i] === '(') { | |
23 | operatorStack.push(tokens[i]); | |
24 | } else if (tokens[i] === ')') { | |
25 | while (operatorStack[operatorStack.length - 1] !== '(') | |
26 | termStack.push(execute(operatorStack.pop(), termStack.pop(), termStack.pop())); | |
27 | operatorStack.pop(); | |
28 | } | |
29 | else if (tokens[i] === "and" || tokens[i] === "or") { | |
30 | while (operatorStack.length !== 0 && hasPrecedence(tokens[i], operatorStack[operatorStack.length - 1])) | |
31 | termStack.push(execute(operatorStack.pop(), termStack.pop(), termStack.pop())); | |
32 | ||
33 | // Push current token to 'ops'. | |
34 | operatorStack.push(tokens[i]); | |
35 | } | |
36 | else if (tokens[i] === "not") { | |
37 | operatorStack.push(tokens[i]); | |
38 | var termCount = 0; | |
39 | while (tokens[i] !== ')') { | |
40 | if (isTerm(tokens[i])) { | |
41 | termStack.push(tokens[i]); | |
42 | termCount++; | |
43 | } | |
44 | i++ | |
45 | } | |
46 | if (termCount === 2) { | |
47 | termStack.push(execute(operatorStack.pop(), termStack.pop(), termStack.pop())); | |
48 | } else { | |
49 | termStack.push(execute(operatorStack.pop(), termStack.pop(), undefined)); | |
50 | } | |
51 | ||
52 | ||
53 | } | |
54 | else { | |
55 | if (tokens.length === 1) { | |
56 | tokens[i] = processTerm(tokens[i], null); | |
57 | } | |
58 | ||
59 | termStack.push(tokens[i]); | |
60 | } | |
61 | } | |
62 | ||
63 | // Entire expression has been parsed at this point, apply remaining | |
64 | // ops to remaining values | |
65 | while (operatorStack.length !== 0) | |
66 | termStack.push(execute(operatorStack.pop(), termStack.pop(), termStack.pop())); | |
67 | ||
68 | // Top of 'values' contains result, return it | |
69 | var output = termStack.pop(); | |
70 | outputJson["filters"].push(output); | |
71 | } | |
72 | ||
73 | return JSON.stringify(outputJson); | |
74 | }; | |
75 | ||
76 | var testParenthesisPairs = function (string) { | |
77 | var length = string.length, | |
78 | i = 0, | |
79 | count = 0, | |
80 | openChar = arguments[1] || "(", | |
81 | closeChar = arguments[2] || ")"; | |
82 | ||
83 | while (i < length) { | |
84 | char = string.charAt(i); | |
85 | ||
86 | if (char === openChar) { | |
87 | count += 1; | |
88 | } else if (char === closeChar) { | |
89 | count -= 1; | |
90 | } | |
91 | ||
92 | if (count < 0) { | |
93 | return false; | |
94 | } | |
95 | ||
96 | i += 1; | |
97 | } | |
98 | ||
99 | return count === 0; | |
100 | }; | |
101 | ||
102 | parserFact.isValid = function (expression) { | |
103 | var reQuotes = /^(?:[^"\\]|\\.|"(?:\\.|[^"\\])*")*$/; // checks if the expressions contains unclosed quotes | |
104 | var reDoubleSpaces = /\s\s/; // checks if the expressions contains double spaces | |
105 | return testParenthesisPairs(expression) && expression.match(reQuotes) !== null && expression.match(reDoubleSpaces) === null; | |
106 | }; | |
107 | ||
108 | var clearTokens = function (expression) { | |
109 | var tokens = []; | |
110 | var isOpenQuotes = false; | |
111 | var isOpenParenthesis = false; | |
112 | var canAddToken = false; | |
113 | var withSpace = false; | |
114 | ||
115 | for (var i = 0; i < expression.length; i++){ | |
116 | switch (expression[i]){ | |
117 | case ' ': | |
118 | withSpace = true; | |
119 | if (isOpenQuotes === true) | |
120 | tokens[tokens.length - 1] += expression[i]; | |
121 | break; | |
122 | ||
123 | case '(': | |
124 | withSpace = false; | |
125 | tokens.push(expression[i]); | |
126 | isOpenParenthesis = true; | |
127 | canAddToken = true; | |
128 | break; | |
129 | ||
130 | case ')': | |
131 | withSpace = false; | |
132 | tokens.push(expression[i]); | |
133 | isOpenParenthesis = false; | |
134 | canAddToken = true; | |
135 | break; | |
136 | case '"': | |
137 | withSpace = false; | |
138 | isOpenQuotes = !isOpenQuotes; | |
139 | break; | |
140 | ||
141 | default: | |
142 | if(expression.substr(i, 3) === 'not' && !isOpenQuotes){ | |
143 | tokens.push('not'); | |
144 | i = i + 2; | |
145 | canAddToken = true; | |
146 | } | |
147 | ||
148 | else if(expression.substr(i, 3) === 'and' && !isOpenQuotes){ | |
149 | tokens.push('and'); | |
150 | canAddToken = true; | |
151 | i = i + 2; | |
152 | } | |
153 | ||
154 | else if(expression.substr(i, 2) === 'or' && !isOpenQuotes){ | |
155 | tokens.push('or'); | |
156 | canAddToken = true; | |
157 | i++; | |
158 | }else{ | |
159 | if((!isOpenQuotes && (withSpace || tokens.length === 0)) || canAddToken){ | |
160 | tokens.push(expression[i]); | |
161 | canAddToken = false; | |
162 | } | |
163 | else tokens[tokens.length - 1] += expression[i]; | |
164 | } | |
165 | withSpace = false; | |
166 | break; | |
167 | } | |
168 | } | |
169 | ||
170 | return tokens; | |
171 | }; | |
172 | ||
173 | var hasPrecedence = function (op1, op2) { | |
174 | if (op2 === '(' || op2 === ')') | |
175 | return false; | |
176 | return !((op1 === 'and' || op1 === 'or') && (op2 === 'not')); | |
177 | }; | |
178 | ||
179 | var isTerm = function (token) { | |
180 | return token.indexOf("and") === -1 && token.indexOf("or") === -1 && token.indexOf("not") === -1 | |
181 | && token.indexOf("(") === -1 && token.indexOf(")") === -1 && token !== ''; | |
182 | }; | |
183 | ||
184 | var execute = function (operator, term1, term2) { | |
185 | console.log("EXECUTE --> " + "OP: " + operator + " TERM1: " + term1 + " TERM2: " + term2); | |
186 | var item = {}; | |
187 | term1 = processTerm(term1, operator); | |
188 | term2 = processTerm(term2, operator); | |
189 | ||
190 | if (term2 === undefined || term2 === null) { | |
191 | if (operator === 'not') { | |
192 | return term1 | |
193 | } else { | |
194 | item[operator] = [term1]; | |
195 | } | |
196 | } else { | |
197 | if (operator === 'not') { | |
198 | item['and'] = [term1, term2]; | |
199 | } else { | |
200 | item[operator] = [term1, term2]; | |
201 | } | |
202 | ||
203 | ||
204 | } | |
205 | console.log(JSON.stringify(item)); | |
206 | return item; | |
207 | }; | |
208 | ||
209 | var processTerm = function (term, operator) { | |
210 | var res = { | |
211 | 'name': '', | |
212 | 'op': '', | |
213 | 'val': '' | |
214 | }; | |
215 | ||
216 | try { | |
217 | var array = term.split(':'); | |
218 | if (array.length === 2) { | |
219 | var name = array[0]; | |
220 | var val = array[1].replace(/"/g, ''); | |
221 | var op = 'like'; | |
222 | if (operator !== 'not') { | |
223 | if (name === 'confirmed' || name === 'accountability' || name === 'availability' || name === 'confidentiality' || name === 'integrity') { | |
224 | if (name !== 'confirmed') | |
225 | name = 'impact_' + name; | |
226 | ||
227 | op = '=='; | |
228 | } | |
229 | ||
230 | if (name === 'severity' || name === 'target'){ | |
231 | op = 'eq' | |
232 | } | |
233 | } else { | |
234 | if (name === 'accountability' || name === 'availability' || name === 'confidentiality' || name === 'integrity') { | |
235 | name = 'impact_' + name; | |
236 | } | |
237 | op = '!=' | |
238 | } | |
239 | ||
240 | ||
241 | if (val === 'info') val = 'informational'; | |
242 | if (val === 'med') val = 'medium'; | |
243 | ||
244 | res.name = name; | |
245 | res.op = op; | |
246 | res.val = val; | |
247 | if (op === 'like') { | |
248 | res.val = '%' + val + '%'; | |
249 | } | |
250 | return res | |
251 | } | |
252 | else { | |
253 | return term | |
254 | } | |
255 | } catch (err) { | |
256 | console.log(err.message); | |
257 | return term | |
258 | } | |
259 | }; | |
260 | ||
261 | var spliceSlice = function (str, index, count, add) { | |
262 | // We cannot pass negative indexes directly to the 2nd slicing operation. | |
263 | if (index < 0) { | |
264 | index = str.length + index; | |
265 | if (index < 0) { | |
266 | index = 0; | |
267 | } | |
268 | } | |
269 | return str.slice(0, index) + (add || "") + str.slice(index + count); | |
270 | }; | |
271 | ||
272 | return parserFact; | |
273 | ||
274 | }]); |
70 | 70 | set: function(ws, data) { |
71 | 71 | var self = this; |
72 | 72 | |
73 | if(data._id !== undefined) self._id = data._id; | |
73 | if(data._id !== undefined || data.id !== undefined) self._id = data._id | data.id; | |
74 | 74 | if(data.metadata !== undefined) self.metadata = data.metadata; |
75 | if(data.target !== undefined) self.target = data.target; | |
76 | if(data.host_os !== undefined) self.host_os = data.host_os; | |
75 | if(data.target !== undefined || data.target_host_ip !== undefined) self.target = data.target || data.target_host_ip; | |
76 | if(data.host_os !== undefined || data.target_host_os !== undefined) self.host_os = data.host_os || data.target_host_os; | |
77 | 77 | if(data.hostnames !== undefined) self.hostnames = data.hostnames; |
78 | 78 | if(data.service !== undefined) self.service = data.service; |
79 | 79 | if(data.owner !== undefined) self.owner = data.owner; |
71 | 71 | return deferred.promise; |
72 | 72 | }; |
73 | 73 | |
74 | vulnsManager.getFilteredVulns = function(wsName, jsonOptions) { | |
75 | var deferred = $q.defer(); | |
76 | ServerAPI.getFilteredVulns(wsName, jsonOptions) | |
77 | .then(function(response) { | |
78 | var result = { | |
79 | vulnerabilities: [], | |
80 | count: 0 | |
81 | }; | |
82 | ||
83 | for(var i = 0; i < response.data.vulnerabilities.length; i++) { | |
84 | var vulnData = response.data.vulnerabilities[i].value; | |
85 | try { | |
86 | if(vulnData.type === "vulnerability") { | |
87 | var vuln = new Vuln(wsName, vulnData); | |
88 | } else { | |
89 | var vuln = new WebVuln(wsName, vulnData); | |
90 | } | |
91 | result.vulnerabilities.push(vuln); | |
92 | } catch(e) { | |
93 | console.log(e.stack); | |
94 | } | |
95 | } | |
96 | vulnsCounter = response.data.count; | |
97 | result.count = response.data.count; | |
98 | deferred.resolve(result); | |
99 | }, function(response) { | |
100 | deferred.reject("Unable to retrieve vulnerabilities from server"); | |
101 | }); | |
102 | return deferred.promise; | |
103 | }; | |
104 | ||
74 | 105 | vulnsManager.loadVulnsCounter = function(ws){ |
75 | 106 | // Ugly hack to populate the vulnsCounter global variable |
76 | 107 | workspacesFact.get(ws).then(function(data){ |
22 | 22 | from test_cases import factories |
23 | 23 | |
24 | 24 | |
25 | TEMPORATY_SQLITE = NamedTemporaryFile() | |
26 | 25 | # Discover factories to automatically register them to pytest-factoryboy and to |
27 | 26 | # override its session |
28 | 27 | enabled_factories = [] |
70 | 69 | def pytest_addoption(parser): |
71 | 70 | # currently for tests using sqlite and memory have problem while using transactions |
72 | 71 | # we need to review sqlite configuraitons for persistence using PRAGMA. |
73 | print(TEMPORATY_SQLITE.name) | |
74 | parser.addoption('--connection-string', default='sqlite:////{0}'.format(TEMPORATY_SQLITE.name), | |
72 | parser.addoption('--connection-string', | |
75 | 73 | help="Database connection string. Defaults to in-memory " |
76 | 74 | "sqlite if not specified:") |
77 | 75 | parser.addoption('--ignore-nplusone', action='store_true', |
86 | 84 | config.option.markexpr = 'not hypothesis' |
87 | 85 | |
88 | 86 | |
89 | @pytest.fixture(scope='session') | |
87 | @pytest.fixture(scope='function') | |
90 | 88 | def app(request): |
91 | app = create_app(db_connection_string=request.config.getoption( | |
92 | '--connection-string'), testing=True) | |
89 | connection_string = request.config.getoption( | |
90 | '--connection-string') | |
91 | if not connection_string: | |
92 | connection_string = 'sqlite:///' | |
93 | app = create_app(db_connection_string=connection_string, testing=True) | |
93 | 94 | app.test_client_class = CustomClient |
94 | 95 | |
95 | 96 | # Establish an application context before running the tests. |
97 | 98 | ctx.push() |
98 | 99 | |
99 | 100 | def teardown(): |
100 | TEMPORATY_SQLITE.close() | |
101 | 101 | ctx.pop() |
102 | 102 | |
103 | 103 | request.addfinalizer(teardown) |
106 | 106 | return app |
107 | 107 | |
108 | 108 | |
109 | @pytest.fixture(scope='session') | |
109 | @pytest.fixture(scope='function') | |
110 | 110 | def database(app, request): |
111 | 111 | """Session-wide test database.""" |
112 | 112 | |
113 | 113 | def teardown(): |
114 | if db.engine.dialect.name == 'sqlite': | |
115 | # since sqlite was created in a temp file we skip the drops. | |
116 | return | |
114 | 117 | try: |
115 | 118 | db.engine.execute('DROP TABLE vulnerability CASCADE') |
116 | 119 | except Exception: |
124 | 127 | # Disable check_vulnerability_host_service_source_code constraint because |
125 | 128 | # it doesn't work in sqlite |
126 | 129 | vuln_constraints = db.metadata.tables['vulnerability'].constraints |
127 | vuln_constraints.remove(next( | |
128 | constraint for constraint in vuln_constraints | |
129 | if constraint.name == 'check_vulnerability_host_service_source_code')) | |
130 | ||
131 | db.app = app | |
130 | try: | |
131 | vuln_constraints.remove(next( | |
132 | constraint for constraint in vuln_constraints | |
133 | if constraint.name == 'check_vulnerability_host_service_source_code')) | |
134 | except StopIteration: | |
135 | pass | |
136 | db.init_app(app) | |
132 | 137 | db.create_all() |
133 | 138 | |
134 | 139 | request.addfinalizer(teardown) |
171 | 176 | for further information |
172 | 177 | """ |
173 | 178 | connection = database.engine.connect() |
174 | transaction = connection.begin() | |
175 | 179 | |
176 | 180 | options = {"bind": connection, 'binds': {}} |
177 | 181 | session = db.create_scoped_session(options=options) |
178 | 182 | |
179 | 183 | # start the session in a SAVEPOINT... |
180 | session.begin_nested() | |
181 | ||
182 | # then each time that SAVEPOINT ends, reopen it | |
183 | @event.listens_for(session, "after_transaction_end") | |
184 | def restart_savepoint(session, transaction): | |
185 | if transaction.nested and not transaction._parent.nested: | |
186 | ||
187 | # ensure that state is expired the way | |
188 | # session.commit() at the top level normally does | |
189 | # (optional step) | |
190 | session.expire_all() | |
191 | ||
192 | session.begin_nested() | |
184 | #session.begin_nested() | |
193 | 185 | |
194 | 186 | database.session = session |
195 | 187 | db.session = session |
202 | 194 | # Session above (including calls to commit()) |
203 | 195 | # is rolled back. |
204 | 196 | # be careful with this!!!!! |
205 | transaction.rollback() | |
206 | 197 | connection.close() |
207 | 198 | session.remove() |
208 | 199 |
149 | 149 | class ServiceFactory(WorkspaceObjectFactory): |
150 | 150 | name = FuzzyText() |
151 | 151 | description = FuzzyText() |
152 | port = FuzzyInteger(1, 65535) | |
152 | port = FuzzyInteger(1, 2**31) # Using 2**16 it generates many collisions | |
153 | 153 | protocol = FuzzyChoice(['TCP', 'UDP']) |
154 | 154 | host = factory.SubFactory(HostFactory, workspace=factory.SelfAttribute('..workspace')) |
155 | 155 | status = FuzzyChoice(Service.STATUSES) |
125 | 125 | assert new_child.id != child.id |
126 | 126 | |
127 | 127 | def test_remove_reference(self, session, child): |
128 | session.add(self.vuln) | |
128 | 129 | self.childs().add(child.name) |
129 | 130 | session.commit() |
130 | 131 | self.childs().remove(child.name) |
12 | 12 | VulnerabilityCodeFactory, |
13 | 13 | VulnerabilityWebFactory, |
14 | 14 | ) |
15 | ||
15 | 16 | |
16 | 17 | C_SOURCE_CODE_VULN_COUNT = 3 |
17 | 18 | C_STANDARD_VULN_COUNT = [6, 2] # With host parent and with service parent |
73 | 74 | db.session.commit() |
74 | 75 | |
75 | 76 | |
76 | def test_vuln_count(workspace, second_workspace): | |
77 | def test_vuln_count(workspace, second_workspace, database): | |
78 | if database.engine.dialect.name == 'sqlite': | |
79 | return | |
77 | 80 | populate_workspace(workspace) |
78 | 81 | populate_workspace(second_workspace) |
79 | workspace = Workspace.query_with_count(None).filter( | |
80 | Workspace.id == workspace.id).first() | |
81 | assert workspace.vulnerability_web_count == WEB_VULN_COUNT | |
82 | assert workspace.vulnerability_code_count == SOURCE_CODE_VULN_COUNT | |
83 | assert workspace.vulnerability_standard_count == sum( | |
82 | workspace = Workspace.query_with_count(None, workspace_name=workspace.name).fetchone() | |
83 | assert workspace['vulnerability_web_count'] == WEB_VULN_COUNT | |
84 | assert workspace['vulnerability_code_count'] == SOURCE_CODE_VULN_COUNT | |
85 | assert workspace['vulnerability_standard_count'] == sum( | |
84 | 86 | STANDARD_VULN_COUNT) |
85 | assert workspace.vulnerability_total_count == ( | |
87 | assert workspace['vulnerability_total_count'] == ( | |
86 | 88 | sum(STANDARD_VULN_COUNT) + WEB_VULN_COUNT + |
87 | 89 | SOURCE_CODE_VULN_COUNT |
88 | 90 | ) |
89 | 91 | |
90 | 92 | |
91 | def test_vuln_count_confirmed(workspace, second_workspace): | |
93 | def test_vuln_count_confirmed(workspace, second_workspace, database): | |
94 | if database.engine.dialect.name == 'sqlite': | |
95 | return | |
92 | 96 | populate_workspace(workspace) |
93 | 97 | populate_workspace(second_workspace) |
94 | workspace = Workspace.query_with_count(True).filter( | |
95 | Workspace.id == workspace.id).first() | |
96 | assert workspace.vulnerability_web_count == C_WEB_VULN_COUNT | |
97 | assert workspace.vulnerability_code_count == C_SOURCE_CODE_VULN_COUNT | |
98 | assert workspace.vulnerability_standard_count == sum( | |
98 | workspace = Workspace.query_with_count(True, workspace_name=workspace.name).fetchone() | |
99 | workspace = dict(workspace) | |
100 | assert workspace['vulnerability_web_count'] == C_WEB_VULN_COUNT | |
101 | assert workspace['vulnerability_code_count'] == C_SOURCE_CODE_VULN_COUNT | |
102 | assert workspace['vulnerability_standard_count'] == sum( | |
99 | 103 | C_STANDARD_VULN_COUNT) |
100 | assert workspace.vulnerability_total_count == ( | |
104 | assert workspace['vulnerability_total_count'] == ( | |
101 | 105 | sum(C_STANDARD_VULN_COUNT) + C_WEB_VULN_COUNT + |
102 | 106 | C_SOURCE_CODE_VULN_COUNT |
103 | 107 | ) |
104 | 108 | |
105 | 109 | |
106 | def test_vuln_no_count(workspace, second_workspace): | |
110 | def test_vuln_no_count(workspace, second_workspace, database): | |
111 | if database.engine.dialect.name == 'sqlite': | |
112 | return | |
113 | ||
107 | 114 | populate_workspace(workspace) |
108 | 115 | populate_workspace(second_workspace) |
109 | 116 | workspace = Workspace.query.get(workspace.id) |
4 | 4 | See the file 'doc/LICENSE' for the license information |
5 | 5 | |
6 | 6 | ''' |
7 | from StringIO import StringIO | |
7 | import json | |
8 | 8 | from tempfile import NamedTemporaryFile |
9 | 9 | |
10 | 10 | import os |
11 | 11 | from base64 import b64encode |
12 | from io import BytesIO | |
13 | ||
12 | 14 | |
13 | 15 | import pytz |
14 | 16 | from hypothesis import given |
15 | 17 | from hypothesis.strategies import text, lists, integers, one_of, none |
18 | from depot.manager import DepotManager | |
16 | 19 | |
17 | 20 | from test_cases.conftest import ignore_nplusone |
18 | 21 | |
1571 | 1574 | } |
1572 | 1575 | res = test_client.post(self.url(), data=data) |
1573 | 1576 | assert res.status_code == 201 |
1577 | ||
1578 | def test_add_attachment_to_vuln(self, test_client, session, host_with_hostnames): | |
1579 | ws = WorkspaceFactory.create(name='abc') | |
1580 | session.add(ws) | |
1581 | vuln = VulnerabilityFactory.create(workspace=self.workspace) | |
1582 | session.add(vuln) | |
1583 | session.commit() | |
1584 | file_contents = 'my file contents' | |
1585 | data = { | |
1586 | 'file' : (BytesIO(file_contents), 'borrar.txt') | |
1587 | } | |
1588 | headers = {'Content-type': 'multipart/form-data'} | |
1589 | res = test_client.post( | |
1590 | '/v2/ws/abc/vulns/{0}/attachment/'.format(vuln.id), | |
1591 | data=data, headers=headers, use_json_data=False) | |
1592 | assert res.status_code == 200 | |
1593 | file_id = session.query(Vulnerability).filter_by(id=vuln.id).first().evidence[0].content['file_id'] | |
1594 | depot = DepotManager.get() | |
1595 | assert file_contents == depot.get(file_id).read() | |
1574 | 1596 | |
1575 | 1597 | |
1576 | 1598 | def test_type_filter(workspace, session, |
193 | 193 | ] |
194 | 194 | raw_data = {'name': 'something', 'description': 'test', |
195 | 195 | 'scope': desired_scope} |
196 | res = test_client.put('/v2/ws/{}/'.format(workspace.name), | |
197 | data=raw_data) | |
196 | res = test_client.put('/v2/ws/{}/'.format(workspace.name), data=raw_data) | |
198 | 197 | assert res.status_code == 200 |
199 | 198 | assert set(res.json['scope']) == set(desired_scope) |
200 | 199 | assert set(s.name for s in workspace.scope) == set(desired_scope) |
56 | 56 | raise Exception('Please check server.utils.database.get_unique_fields. Vulnerability DDL changed?') |
57 | 57 | |
58 | 58 | |
59 | @pytest.mark.parametrize("obj_class, expected_unique_fields", UNIQUE_FIELDS.items()) | |
59 | @pytest.mark.parametrize("obj_class, expected_unique_fields", sorted(UNIQUE_FIELDS.items())) | |
60 | 60 | def test_unique_fields_workspace(obj_class, expected_unique_fields, session): |
61 | 61 | object_ = obj_class() |
62 | 62 | unique_constraints = get_unique_fields(session, object_) |