Codebase list python-faraday / 4d84c3a
New upstream version 3.4 Sophie Brun 5 years ago
41 changed file(s) with 1100 addition(s) and 177 deletion(s). Raw diff Collapse all Expand all
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
66
77 New features in the latest update
88 =====================================
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
935
1036
1137 3.3 [Novemeber 14th, 2018]:
66
77 New features in the latest update
88 =====================================
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
935
1036
1137 3.3 [Novemeber 14th, 2018]:
0 3.3
0 3.4
77
88 from model.common import factory
99 from persistence.server import models
10 from persistence.server.server_io_exceptions import (
11 CantCommunicateWithServerError,
12 ConflictInDatabase
13 )
1014
1115 __description__ = 'Creates a new vulnerability'
1216 __prettyname__ = 'Create Vulnerability'
3034 default='false')
3135 parser.add_argument('--description', help='Vulnerability description', default='')
3236
33 parser.add_argument('--dry-run', action='store_true', help='Do not touch the database. Only print the object ID')
34
3537 parsed_args = parser.parse_args(args)
3638
3739 obj = factory.createModelObject(models.Vuln.class_signature,
5254 'parent': parsed_args.parent,
5355 }
5456
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(
5677 workspace,
5778 **params
5879 )
5980
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
11 <faraday>
22
33 <appname>Faraday - Penetration Test IDE</appname>
4 <version>3.1.1</version>
4 <version>3.3</version>
55 <debug_status>0</debug_status>
66 <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font>
77 <home_path>~/</home_path>
127127
128128 @property
129129 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 ""
131135
132136 def get_active_workspace(self):
133137 """Return the currently active workspace"""
390394
391395 Return True if everything went OK, False if there was a problem
392396 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 = ""
394403
395404 host = self.serverIO.get_host(host_id)
396405 if not host:
785794
786795 def on_faraday_plugin(self, action, param):
787796 """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
788805 pluginsOption_window = FaradayPluginsDialog(self.window.get_current_focused_terminal(),
789 self.get_active_workspace().getName(),
806 name,
790807 self.window)
791808 pluginsOption_window.show_all()
792809
945962 plugin = "_".join(action.get_name().split('_')[1:])
946963 terminal = self.window.get_current_focused_terminal()
947964
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)
213213 CONF.setDBSessionCookies(session_cookie)
214214
215215 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
222216
223217 self.destroy()
224218
327327 else:
328328 return "Zap"
329329
330 elif "xml-report" == tag:
331 if re.search("Appscan",output) is not None:
332 return "Appscan"
330333 elif "niktoscan" == tag:
331334 return "Nikto"
332335 elif "MetasploitV4" == tag:
7979 return(m.group(1).strip())
8080
8181 def listeningservices(self):
82
82
8383 line = re.findall('^network_listen_port\[\]=(.+)$',
8484 self.rawcontents, re.MULTILINE)
8585
9494
9595 if name == '-':
9696 name = 'Unknown'
97
97
9898 else:
9999 items_service = combo
100100 count = items_service.count(':')
115115 elif count == 5:
116116 port = elements_ip_port[5]
117117 ip = items_service[0].replace(':{}'.format(port), '')
118
118
119119 self._svcHelper(ip, port, protocol, name)
120120
121121 return self.services
164164 lde = LynisLogDataExtracter(output=output)
165165
166166 hostname = lde.hostname()
167 ip = socket.gethostbyname(hostname)
167 try:
168 ip = socket.gethostbyname(hostname)
169 except socket.gaierror:
170 ip = hostname
168171 h_id = self.createAndAddHost(name=ip, os=lde.osfullname(), hostnames=[hostname])
169172
170173 self.createAndAddVulnToHost(
173176 severity='info',
174177 desc=lde.kernelVersion()
175178 )
176
177
179
178180 interfaces = lde.interfaces()
179181 macs = lde.macs()
180182 ipv4s = lde.ipv4()
512512 severity = 0
513513 desc = v.desc
514514 refs = v.refs
515 desc += "\nOutput: " + v.response if v.response else ""
516515
517516 if re.search(r"VULNERABLE", desc):
518517 severity = "high"
541540 s_id,
542541 v.name,
543542 desc=desc,
543 response = v.response if v.response else "",
544544 ref=refs,
545545 severity=severity,
546546 website=minterfase)
99 tornado>=4.5.1
1010 tqdm>=4.15.0
1111 whoosh>=2.7.4
12 Flask-Restless>=0.17.0
3030 pgcli>=1.8.2
3131 websocket-client>=0.46.0
3232 attrs>=17.4.0
33 Flask-Restless==0.17.0
2929 return flask.jsonify(gen_web_config())
3030
3131 get_config.is_public = True
32 show_info.is_public = True
44 from flask import Blueprint
55 from filteralchemy import FilterSet, operators
66 from marshmallow import fields, post_load, ValidationError
7 from marshmallow.validate import OneOf
7 from marshmallow.validate import OneOf, Range
88 from sqlalchemy.orm.exc import NoResultFound
99
1010 from server.api.base import AutoSchema, ReadWriteWorkspacedView, FilterSetMeta, \
2727 owned = fields.Boolean(default=False)
2828 owner = PrimaryKeyRelatedField('username', dump_only=True,
2929 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")]),
3234 fields.Method(deserialize='load_ports'),
3335 required=True,
3436 attribute='port')
4749 if len(value) != 1:
4850 raise ValidationError('ports must be a list with exactly one'
4951 '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)
5157
5258 @post_load
5359 def post_load_parent(self, data):
22 # See the file 'doc/LICENSE' for the license information
33 import os
44 import io
5 import json
56 import logging
67 from base64 import b64encode, b64decode
78
1011 from flask import request
1112 from flask import Blueprint
1213 from flask_classful import route
14 from flask_restless.search import search
1315 from marshmallow import Schema, fields, post_load, ValidationError
1416 from marshmallow.validate import OneOf, Length
1517 from sqlalchemy.orm import aliased, joinedload, selectin_polymorphic, undefer
1618 from sqlalchemy.orm.exc import NoResultFound
17
19 from psycopg2 import DataError
1820 from depot.manager import DepotManager
1921 from server.api.base import (
2022 AutoSchema,
21 FilterAlchemyMixin,
23 InvalidUsage,
2224 FilterSetMeta,
2325 PaginatedMixin,
26 FilterAlchemyMixin,
2427 ReadWriteWorkspacedView,
25 InvalidUsage)
28 )
2629 from server.fields import FaradayUploadedFile
2730 from server.models import (
2831 db,
2932 File,
3033 Host,
3134 Service,
35 Hostname,
36 Workspace,
3237 Vulnerability,
3338 VulnerabilityWeb,
3439 VulnerabilityGeneric,
35 Workspace,
36 Hostname
3740 )
3841 from server.utils.database import get_or_create
3942
4346 PrimaryKeyRelatedField,
4447 SelfNestedField,
4548 SeverityField,
46 MetadataSchema)
49 MetadataSchema,
50 )
4751
4852 vulns_api = Blueprint('vulns_api', __name__)
4953 logger = logging.getLogger(__name__)
560564 res['groups'] = [convert_group(group) for group in res['groups']]
561565 return res
562566
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):
565624 vuln_workspace_check = db.session.query(VulnerabilityGeneric, Workspace.id).join(
566625 Workspace).filter(VulnerabilityGeneric.id == vuln_id,
567626 Workspace.name == workspace_name).first()
589648 else:
590649 flask.abort(404, "Vulnerability not found")
591650
651
592652 VulnerabilityView.register(vulns_api)
66 from flask import Blueprint
77 from flask_classful import route
88 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
1117 from server.schemas import (
1218 JSTimestampField,
1319 MutableField,
8187 schema_class = WorkspaceSchema
8288 order_field = Workspace.name.asc()
8389
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):
85108 try:
86109 confirmed = bool(json.loads(flask.request.args['confirmed']))
87110 except (KeyError, ValueError):
88111 confirmed = None
89112 try:
90113 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)
92118 except (KeyError, ValueError):
93 query = Workspace.query_with_count(confirmed)
119 query = Workspace.query_with_count(
120 confirmed,
121 workspace_name=object_id)
94122 return query
95123
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
97168 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
103173
104174 def _perform_create(self, data, **kwargs):
105175 scope = data.pop('scope', [])
88 from os.path import join, expanduser
99 from random import SystemRandom
1010
11 from model.workspace import Workspace
1112 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
1314
1415 try:
1516 # py2.7
1616 String,
1717 Text,
1818 UniqueConstraint,
19 event)
19 event,
20 text
21 )
2022 from sqlalchemy.exc import IntegrityError
2123 from sqlalchemy.orm import relationship, undefer
2224 from sqlalchemy.sql import select, text, table
2325 from sqlalchemy.sql.expression import asc, case, join
26 from sqlalchemy.ext.hybrid import hybrid_property
2427 from sqlalchemy import func
2528 from sqlalchemy.orm import (
2629 backref,
540543 The value_attr argument isn't relevant to this implementation
541544 """
542545
543 if parent.getset_factory:
546 if getattr(parent, 'getset_factory', False):
544547 getter, setter = parent.getset_factory(
545548 parent.collection_class, parent)
546549 else:
550553 lazy_collection, creator, getter, setter, parent)
551554
552555 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
554561 session = db.session
555562 conflict_objs = session.new
556563 try:
930937 object_id=self.id,
931938 object_type='vulnerability'
932939 )
940
941 @hybrid_property
942 def target(self):
943 return self.target_host_ip
933944
934945
935946 class Vulnerability(VulnerabilityGeneric):
13121323 cascade="all, delete-orphan")
13131324
13141325 @classmethod
1315 def query_with_count(cls, confirmed):
1326 def query_with_count(cls, confirmed, active=None, workspace_name=None):
13161327 """
13171328 Add count fields to the query.
13181329
13191330 If confirmed is True/False, it will only show the count for confirmed / not confirmed
13201331 vulnerabilities. Otherwise, it will show the count of all of them
13211332 """
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)
13521400
13531401 def set_scope(self, new_scope):
13541402 return set_children_objects(self, new_scope,
3535 if self.many:
3636 ret = []
3737 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])
3942 return ret
4043 else:
4144 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
145145 <script type="text/javascript" src="scripts/statusReport/directives/appendSearchParam.js"></script>
146146 <script type="text/javascript" src="scripts/statusReport/providers/target.js"></script>
147147 <script type="text/javascript" src="scripts/statusReport/providers/reference.js"></script>
148 <script type="text/javascript" src="scripts/statusReport/providers/parser.js"></script>
148149 <script type="text/javascript" src="scripts/vulns/providers/vuln.js"></script>
149150 <script type="text/javascript" src="scripts/vulns/providers/vulns.js"></script>
150151 <script type="text/javascript" src="scripts/vulns/providers/web.js"></script>
1313 <table class="table-v3 licenses-list table table-responsive">
1414 <thead>
1515 <tr class="ui-grid-header">
16 <th class="ui-grid-cell-contents ui-grid-header-cell">Active</th>
1617 <th class="ui-grid-cell-contents ui-grid-header-cell">Name</th>
1718 <th class="ui-grid-cell-contents ui-grid-header-cell">Vulns</th>
1819 <th class="ui-grid-cell-contents ui-grid-header-cell">Hosts</th>
2021 </tr>
2122 </thead>
2223 <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' }" />
2728 </span>
2829 </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>
3238 </tr>
3339 </tbody>
3440 </table>
99 function (BASEURL, $http, $q) {
1010 var ServerAPI = {};
1111 var APIURL = BASEURL + "_api/v2/";
12 var FILTER_URL = BASEURL + "_api/filter/";
1213
1314 var createGetRelatedUrl = function (wsName, objectType, objectId, relatedObjectType) {
1415 var objectName = ((objectName) ? "/" + objectType : "");
343344 ServerAPI.getVulns = function (wsName, data) {
344345 var getUrl = createGetUrl(wsName, 'vulns');
345346 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);
346352 }
347353
348354 ServerAPI.getVulnerabilityTemplate = function (objId) {
4545 }, function(response) {
4646 if (response.status == 409) {
4747 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 {
4953 commonsFact.showMessage("Error from backend: " + response.status);
5054 }
5155
4040 servicesManager.createService($scope.data, $routeParams.wsId).then(function() {
4141 $modalInstance.close($scope.data);
4242 }, function(response) {
43 if (response.status == 409) {
43 if (response.status === 409) {
4444 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 {
4650 commonsFact.showMessage("Error from backend: " + response.status);
4751 }
4852 });
3838 <h5>Port</h5>
3939 <div class="input-margin">
4040 <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"/>
4242 </div>
4343 </div>
4444 <div class="col-md-3 protocol">
3838 <h5>Port</h5>
3939 <div class="input-margin" ng-class="{'has-error': form.ports.$invalid }">
4040 <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/>
4242 </div>
4343 </div>
4444 <div class="col-md-3 protocol" ng-class="{'has-error': form.protocol.$invalid }">
55 .controller("statusReportCtrl",
66 ["$scope", "$filter", "$routeParams",
77 "$location", "$uibModal", "$cookies", "$q", "$window", "BASEURL",
8 "SEVERITIES", "EASEOFRESOLUTION", "STATUSES", "hostsManager", "commonsFact",
8 "SEVERITIES", "EASEOFRESOLUTION", "STATUSES", "hostsManager", "commonsFact", 'parserFact',
99 "vulnsManager", "workspacesFact", "csvService", "uiGridConstants", "vulnModelsManager",
1010 "referenceFact", "ServerAPI", '$http',
1111 function($scope, $filter, $routeParams,
1212 $location, $uibModal, $cookies, $q, $window, BASEURL,
13 SEVERITIES, EASEOFRESOLUTION, STATUSES, hostsManager, commonsFact,
13 SEVERITIES, EASEOFRESOLUTION, STATUSES, hostsManager, commonsFact,parserFact,
1414 vulnsManager, workspacesFact, csvService, uiGridConstants, vulnModelsManager, referenceFact, ServerAPI, $http) {
1515 $scope.baseurl;
1616 $scope.columns;
10751075 });
10761076 };
10771077
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
10791106 $scope.searchFor = function(search, params) {
1080 // TODO: It would be nice to find a way for changing
10811107 // the url without reloading the controller
1108 $scope.searchParams = params;
10821109 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);
10841112 } else {
10851113 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 }
10951116 };
10961117
10971118 // toggles column show property
11061127 }
11071128 $cookies.put('SRcolumns', JSON.stringify($scope.columns));
11081129 recalculateLastVisibleColSize();
1130 };
1131
1132 $scope.isValidExpression = function (expression) {
1133 return parserFact.isValid(expression);
11091134 };
11101135
11111136 var compareSeverities = function(a, b) {
6060 </button>
6161 </div>
6262 <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)}">
6565 <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>
6767 <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)">
6969 <img src="images/icon-toolbar-searchbar-loupe.svg" class="search-icon" />
7070 </span>
7171 </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 }]);
7070 set: function(ws, data) {
7171 var self = this;
7272
73 if(data._id !== undefined) self._id = data._id;
73 if(data._id !== undefined || data.id !== undefined) self._id = data._id | data.id;
7474 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;
7777 if(data.hostnames !== undefined) self.hostnames = data.hostnames;
7878 if(data.service !== undefined) self.service = data.service;
7979 if(data.owner !== undefined) self.owner = data.owner;
7171 return deferred.promise;
7272 };
7373
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
74105 vulnsManager.loadVulnsCounter = function(ws){
75106 // Ugly hack to populate the vulnsCounter global variable
76107 workspacesFact.get(ws).then(function(data){
2222 from test_cases import factories
2323
2424
25 TEMPORATY_SQLITE = NamedTemporaryFile()
2625 # Discover factories to automatically register them to pytest-factoryboy and to
2726 # override its session
2827 enabled_factories = []
7069 def pytest_addoption(parser):
7170 # currently for tests using sqlite and memory have problem while using transactions
7271 # 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',
7573 help="Database connection string. Defaults to in-memory "
7674 "sqlite if not specified:")
7775 parser.addoption('--ignore-nplusone', action='store_true',
8684 config.option.markexpr = 'not hypothesis'
8785
8886
89 @pytest.fixture(scope='session')
87 @pytest.fixture(scope='function')
9088 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)
9394 app.test_client_class = CustomClient
9495
9596 # Establish an application context before running the tests.
9798 ctx.push()
9899
99100 def teardown():
100 TEMPORATY_SQLITE.close()
101101 ctx.pop()
102102
103103 request.addfinalizer(teardown)
106106 return app
107107
108108
109 @pytest.fixture(scope='session')
109 @pytest.fixture(scope='function')
110110 def database(app, request):
111111 """Session-wide test database."""
112112
113113 def teardown():
114 if db.engine.dialect.name == 'sqlite':
115 # since sqlite was created in a temp file we skip the drops.
116 return
114117 try:
115118 db.engine.execute('DROP TABLE vulnerability CASCADE')
116119 except Exception:
124127 # Disable check_vulnerability_host_service_source_code constraint because
125128 # it doesn't work in sqlite
126129 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)
132137 db.create_all()
133138
134139 request.addfinalizer(teardown)
171176 for further information
172177 """
173178 connection = database.engine.connect()
174 transaction = connection.begin()
175179
176180 options = {"bind": connection, 'binds': {}}
177181 session = db.create_scoped_session(options=options)
178182
179183 # 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()
193185
194186 database.session = session
195187 db.session = session
202194 # Session above (including calls to commit())
203195 # is rolled back.
204196 # be careful with this!!!!!
205 transaction.rollback()
206197 connection.close()
207198 session.remove()
208199
149149 class ServiceFactory(WorkspaceObjectFactory):
150150 name = FuzzyText()
151151 description = FuzzyText()
152 port = FuzzyInteger(1, 65535)
152 port = FuzzyInteger(1, 2**31) # Using 2**16 it generates many collisions
153153 protocol = FuzzyChoice(['TCP', 'UDP'])
154154 host = factory.SubFactory(HostFactory, workspace=factory.SelfAttribute('..workspace'))
155155 status = FuzzyChoice(Service.STATUSES)
125125 assert new_child.id != child.id
126126
127127 def test_remove_reference(self, session, child):
128 session.add(self.vuln)
128129 self.childs().add(child.name)
129130 session.commit()
130131 self.childs().remove(child.name)
1212 VulnerabilityCodeFactory,
1313 VulnerabilityWebFactory,
1414 )
15
1516
1617 C_SOURCE_CODE_VULN_COUNT = 3
1718 C_STANDARD_VULN_COUNT = [6, 2] # With host parent and with service parent
7374 db.session.commit()
7475
7576
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
7780 populate_workspace(workspace)
7881 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(
8486 STANDARD_VULN_COUNT)
85 assert workspace.vulnerability_total_count == (
87 assert workspace['vulnerability_total_count'] == (
8688 sum(STANDARD_VULN_COUNT) + WEB_VULN_COUNT +
8789 SOURCE_CODE_VULN_COUNT
8890 )
8991
9092
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
9296 populate_workspace(workspace)
9397 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(
99103 C_STANDARD_VULN_COUNT)
100 assert workspace.vulnerability_total_count == (
104 assert workspace['vulnerability_total_count'] == (
101105 sum(C_STANDARD_VULN_COUNT) + C_WEB_VULN_COUNT +
102106 C_SOURCE_CODE_VULN_COUNT
103107 )
104108
105109
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
107114 populate_workspace(workspace)
108115 populate_workspace(second_workspace)
109116 workspace = Workspace.query.get(workspace.id)
44 See the file 'doc/LICENSE' for the license information
55
66 '''
7 from StringIO import StringIO
7 import json
88 from tempfile import NamedTemporaryFile
99
1010 import os
1111 from base64 import b64encode
12 from io import BytesIO
13
1214
1315 import pytz
1416 from hypothesis import given
1517 from hypothesis.strategies import text, lists, integers, one_of, none
18 from depot.manager import DepotManager
1619
1720 from test_cases.conftest import ignore_nplusone
1821
15711574 }
15721575 res = test_client.post(self.url(), data=data)
15731576 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()
15741596
15751597
15761598 def test_type_filter(workspace, session,
193193 ]
194194 raw_data = {'name': 'something', 'description': 'test',
195195 '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)
198197 assert res.status_code == 200
199198 assert set(res.json['scope']) == set(desired_scope)
200199 assert set(s.name for s in workspace.scope) == set(desired_scope)
5656 raise Exception('Please check server.utils.database.get_unique_fields. Vulnerability DDL changed?')
5757
5858
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()))
6060 def test_unique_fields_workspace(obj_class, expected_unique_fields, session):
6161 object_ = obj_class()
6262 unique_constraints = get_unique_fields(session, object_)