From c07178d3077c9e74ae7468bf45026af9e600c1a0 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 2 Jun 2017 15:40:20 -0300 Subject: [PATCH 0001/1506] ADD initial websockets support --- gui/gtk/server.py | 39 ++++---- persistence/server/changes_stream.py | 141 ++++++++++++++++++++------- persistence/server/server.py | 17 +++- requirements.txt | 2 + server/app.py | 6 +- server/models.py | 17 +++- server/web.py | 26 +++-- 7 files changed, 184 insertions(+), 64 deletions(-) diff --git a/gui/gtk/server.py b/gui/gtk/server.py index a9a9314e6fc..8c6ed1e055e 100644 --- a/gui/gtk/server.py +++ b/gui/gtk/server.py @@ -7,11 +7,14 @@ ''' -import threading, time, requests +import time +import threading + from model.guiapi import notification_center from decorators import safe_io_with_server from persistence.server import models, server_io_exceptions + class ServerIO(object): def __init__(self, active_workspace): self.__active_workspace = active_workspace @@ -99,7 +102,6 @@ def continously_get_changes(self): coming from other instances of Faraday. Return the thread on any exception, of if self.stream is None. """ - # There is very arcane, dark magic involved in this method. # What you need to know: do not touch it. # If you touch it, do check out persitence/server/changes_stream.py @@ -160,22 +162,25 @@ def notification_dispatcher(obj_id, obj_type, obj_name, deleted, revision): def get_changes(): # dark maaaaaagic *sing with me!* dark maaaaaagic - if self.stream: - try: - for change, obj_type, obj_name in self.stream: - with self.changes_lock: - change = filter_changes(change, obj_type) - if change: - deleted = bool(change.get('deleted')) - obj_id = change.get('id') - revision = change.get("changes")[-1].get('rev') - notification_dispatcher(obj_id, obj_type, obj_name, - deleted, revision) - except server_io_exceptions.ChangesStreamStoppedAbruptly: - notification_center.WorkspaceProblem() + while True: + if self.stream: + try: + for change, obj_type, obj_name in self.stream: + print(change, obj_type, obj_name) + with self.changes_lock: + change = filter_changes(change, obj_type) + if change: + deleted = bool(change.get('deleted')) + obj_id = change.get('id') + revision = change.get("changes")[-1].get('rev') + notification_dispatcher(obj_id, obj_type, obj_name, + deleted, revision) + except server_io_exceptions.ChangesStreamStoppedAbruptly: + notification_center.WorkspaceProblem() + return False + else: return False - else: - return False + time.sleep(0.5) get_changes_thread = threading.Thread(target=get_changes) get_changes_thread.daemon = True diff --git a/persistence/server/changes_stream.py b/persistence/server/changes_stream.py index 62279bba73b..54a22aa55a4 100644 --- a/persistence/server/changes_stream.py +++ b/persistence/server/changes_stream.py @@ -6,19 +6,20 @@ See the file 'doc/LICENSE' for the license information ''' -import requests, json -from persistence.server.server_io_exceptions import ChangesStreamStoppedAbruptly +import time +import json +import threading +from Queue import Queue, Empty -class CouchChangesStream(object): - def __init__(self, workspace_name, server_url, since=0, heartbeat='1000', feed='continuous', **params): - self._base_url = server_url - self._change_url = "{0}/_changes".format(server_url) - self.since = since - self.heartbeat = heartbeat - self.feed = feed - self._params = params - self._response = None - self._stop = False +import requests +import websocket + +from persistence.server.server_io_exceptions import ( + ChangesStreamStoppedAbruptly +) + + +class ChangesStream(object): def __enter__(self): return self @@ -30,28 +31,7 @@ def __next__(self): return self def __iter__(self): - try: - params = {'since' : self.since, 'heartbeat': self.heartbeat, 'feed': self.feed} - self._response = requests.get(self._change_url, stream=True, params=params, **self._params) - if self._response: - for raw_line in self._response.iter_lines(): - line = self._sanitize(raw_line) - if not line: - if self._stop: break - else: continue - change = self._parse_change(line) - if not change: - continue - object_type, object_name = self._get_object_type_and_name_from_change(change) - yield change, object_type, object_name - if not self._stop: # why did we stop if no one asked me to stop? - raise ChangesStreamStoppedAbruptly - - except (requests.exceptions.RequestException, ChangesStreamStoppedAbruptly): - self.stop() - raise ChangesStreamStoppedAbruptly - except Exception as e: - self.stop() + raise NotImplementedError('Abstract class') def _get_object_type_and_name_from_change(self, change): try: @@ -86,3 +66,96 @@ def stop(self): self._response.close() self._response = None self._stop = True + + +class WebsocketsChangesStream(ChangesStream): + def __init__(self, workspace_name, server_url, **params): + self._base_url = server_url + self.changes_queue = Queue() + self.workspace_name = workspace_name + self._response = None + websocket.enableTrace(True) + self.ws = websocket.WebSocketApp( + "ws://{0}:9000".format(self._base_url), + on_message=self.on_message, + on_error=self.on_error, + on_close=self.on_close) + + self.ws.on_open = self.on_open + thread = threading.Thread(target=self.ws.run_forever, args=()) + thread.daemon = True + thread.start() + + def on_message(self, ws, message): + self.changes_queue.put(message) + + def on_error(self, ws, error): + print error + + def on_close(selg, ws): + print "### closed ###" + + def on_open(self, ws): + print 'open' + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + return False + + def __next__(self): + return self + + def __iter__(self): + try: + data = json.loads(self.changes_queue.get_nowait()) + except Empty: + raise StopIteration + yield data[0], data[1], data[2] + + def _get_object_type_and_name_from_change(self, change): + try: + id = change['id'] + response = requests.get("{0}/{1}".format(self._base_url, id), **self._params) + object_json = response.json() + except Exception: + return None, None + return object_json.get('type'), object_json.get('name') + + +class CouchChangesStream(ChangesStream): + + def __init__(self, workspace_name, server_url, since=0, heartbeat='1000', feed='continuous', **params): + self._base_url = server_url + self._change_url = "{0}/_changes".format(server_url) + self.since = since + self.heartbeat = heartbeat + self.feed = feed + self._params = params + self._response = None + self._stop = False + + def __iter__(self): + try: + params = {'since' : self.since, 'heartbeat': self.heartbeat, 'feed': self.feed} + self._response = requests.get(self._change_url, stream=True, params=params, **self._params) + if self._response: + for raw_line in self._response.iter_lines(): + line = self._sanitize(raw_line) + if not line: + if self._stop: break + else: continue + change = self._parse_change(line) + if not change: + continue + object_type, object_name = self._get_object_type_and_name_from_change(change) + yield change, object_type, object_name + if not self._stop: # why did we stop if no one asked me to stop? + raise ChangesStreamStoppedAbruptly + + except (requests.exceptions.RequestException, ChangesStreamStoppedAbruptly): + self.stop() + raise ChangesStreamStoppedAbruptly + except Exception as e: + self.stop() diff --git a/persistence/server/server.py b/persistence/server/server.py index c9f7e9b95b6..98783a2ff74 100644 --- a/persistence/server/server.py +++ b/persistence/server/server.py @@ -34,7 +34,10 @@ Unauthorized, MoreThanOneObjectFoundByID) -from persistence.server.changes_stream import CouchChangesStream +from persistence.server.changes_stream import ( + CouchChangesStream, + WebsocketsChangesStream +) # NOTE: Change is you want to use this module by itself. # If FARADAY_UP is False, SERVER_URL must be a valid faraday server url @@ -459,9 +462,17 @@ def get_objects(workspace_name, object_signature, **params): return appropiate_function(workspace_name, **params) + +def _websockets_changes(workspace_name, **extra_params): + return WebsocketsChangesStream(workspace_name, 'localhost', **extra_params) + + # cha cha cha chaaaanges! -def get_changes_stream(workspace_name, since=0, heartbeat='1000', **extra_params): - return _couch_changes(workspace_name, since=since, feed='continuous', +def get_changes_stream(workspace_name, since=0, heartbeat='1000', stream_provider=_websockets_changes, **extra_params): + """ + stream_provider: A function that returns an instance of a Stream provider + """ + return stream_provider(workspace_name, since=since, feed='continuous', heartbeat=heartbeat, **extra_params) def get_workspaces_names(): diff --git a/requirements.txt b/requirements.txt index b8d549c3033..8f2e66b1eaa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,5 @@ requests tornado flask colorama +websocket-client +autobahn diff --git a/server/app.py b/server/app.py index 9d5b7afed4f..7cb5d3bebdf 100644 --- a/server/app.py +++ b/server/app.py @@ -3,14 +3,16 @@ # See the file 'doc/LICENSE' for the license information import flask +from flask_socketio import SocketIO + import server.config import server.database from server.utils.logger import LOGGING_HANDLERS - app = flask.Flask(__name__) + def setup(): app.debug = server.config.is_debug_mode() minify_json_output(app) @@ -23,6 +25,7 @@ def remove_session_context(exception=None): for handler in LOGGING_HANDLERS: app.logger.addHandler(handler) + def minify_json_output(app): class MiniJSONEncoder(flask.json.JSONEncoder): item_separator = ',' @@ -34,4 +37,3 @@ class MiniJSONEncoder(flask.json.JSONEncoder): # Load APIs import server.api import server.modules.info - diff --git a/server/models.py b/server/models.py index 778d35dcd1c..1fe798996e5 100644 --- a/server/models.py +++ b/server/models.py @@ -4,7 +4,15 @@ import json -from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, Float, Text, UniqueConstraint +from sqlalchemy import ( + Column, + Integer, + String, + Boolean, + ForeignKey, + Float, + Text, + UniqueConstraint) from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base @@ -13,10 +21,12 @@ Base = declarative_base() + class EntityNotFound(Exception): def __init__(self, entity_id): super(EntityNotFound, self).__init__("Entity (%s) wasn't found" % entity_id) + class FaradayEntity(object): # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace', u'Interface'] @classmethod @@ -226,6 +236,7 @@ def add_relationships_from_db(self, session): query = session.query(Host).join(EntityMetadata).filter(EntityMetadata.couchdb_id == host_id) self.host = query.one() + class Service(FaradayEntity, Base): DOC_TYPE = 'Service' @@ -390,6 +401,7 @@ def add_relationships_from_db(self, session): query = session.query(Service).join(EntityMetadata).filter(EntityMetadata.couchdb_id == parent_id) self.service = query.one() + class Note(FaradayEntity, Base): DOC_TYPE = 'Note' @@ -410,6 +422,7 @@ def update_from_document(self, document): self.description=document.get('description', None) self.owned=document.get('owned', False) + class Credential(FaradayEntity, Base): DOC_TYPE = 'Cred' @@ -462,6 +475,7 @@ def add_relationships_from_db(self, session): query = session.query(Service).join(EntityMetadata).filter(EntityMetadata.couchdb_id == parent_id) self.service = query.one() + class Command(FaradayEntity, Base): DOC_TYPE = 'CommandRunInformation' @@ -490,4 +504,3 @@ def update_from_document(self, document): self.params = document.get('params', None) self.user = document.get('user', None) self.workspace = document.get('workspace', None) - diff --git a/server/web.py b/server/web.py index ca0e8c6f7c5..127d840cdd6 100644 --- a/server/web.py +++ b/server/web.py @@ -4,16 +4,24 @@ import os import functools -import twisted.web -import server.config +import twisted.web from twisted.web import proxy from twisted.internet import ssl, reactor, error from twisted.protocols.tls import TLSMemoryBIOFactory from twisted.web.static import File from twisted.web.wsgi import WSGIResource +from autobahn.twisted.websocket import ( + WebSocketServerFactory, + listenWS +) +import server.config from server.utils import logger from server.app import app +from server.websocket_factories import ( + WorkspaceServerFactory, + BroadcastServerProtocol +) class HTTPProxyClient(proxy.ProxyClient): @@ -53,7 +61,7 @@ def proxyClientFactoryClass(self, *args, **kwargs): if enabled. """ client_factory = HTTPProxyClientFactory(*args, **kwargs) - + if self.__ssl_enabled: with open(server.config.ssl.certificate) as cert_file: cert = ssl.Certificate.loadPEM(cert_file.read()) @@ -65,7 +73,7 @@ def proxyClientFactoryClass(self, *args, **kwargs): isClient=True, wrappedFactory=client_factory) else: return client_factory - + def getChild(self, path, request): """ Keeps the implementation of this class throughout the path @@ -87,7 +95,7 @@ def __init__(self, enable_ssl=False): self.__config_server() self.__config_couchdb_conn() self.__build_server_tree() - + def __config_server(self): self.__bind_address = server.config.faraday_server.bind_address self.__listen_port = int(server.config.faraday_server.port) @@ -135,10 +143,16 @@ def __build_web_resource(self): def __build_api_resource(self): return WSGIResource(reactor, reactor.getThreadPool(), app) + def __build_websockets_resource(self): + print(u"wss://{0}:9000".format(self.__bind_address)) + factory = WorkspaceServerFactory(u"ws://{0}:9000".format(self.__bind_address)) + factory.protocol = BroadcastServerProtocol + return factory + def run(self): site = twisted.web.server.Site(self.__root_resource) self.__listen_func( self.__listen_port, site, interface=self.__bind_address) + listenWS(self.__build_websockets_resource()) reactor.run() - From b430377dca11d3f3b92b60aad8cf740950acf0e1 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 5 Jun 2017 11:35:42 -0300 Subject: [PATCH 0002/1506] REFACTOR remove arcane code for couchdb changes When we used the changes api of couchdb we needed to filter and check the data sent by the changes API. Now we have full control of our websockets msgs format which allow us to simplify the code. --- gui/gtk/server.py | 99 +++++++--------------------- persistence/server/changes_stream.py | 2 +- server/app.py | 3 + 3 files changed, 29 insertions(+), 75 deletions(-) diff --git a/gui/gtk/server.py b/gui/gtk/server.py index 8c6ed1e055e..da11738f1ee 100644 --- a/gui/gtk/server.py +++ b/gui/gtk/server.py @@ -102,83 +102,34 @@ def continously_get_changes(self): coming from other instances of Faraday. Return the thread on any exception, of if self.stream is None. """ - # There is very arcane, dark magic involved in this method. - # What you need to know: do not touch it. - # If you touch it, do check out persitence/server/changes_stream.py - # there lies _most_ of the darkest magic - - def filter_changes(change, obj_type): - local_changes = models.local_changes() - cool_types = ("Host", "Interface", "Service", "Vulnerability", - "VulnerabilityWeb", "CommandRunInfomation", "Cred", - "Note") - - if not change.get('changes') or not change['changes'][0].get('rev'): - # not a change really right? - return None - - is_deleted = bool(change.get('deleted')) - if obj_type is None and not is_deleted: - # if obj_type is None it's a deleted change. retrieve its type later - return None - - if obj_type is not None and obj_type not in cool_types: - return None - - if change['changes'][0]['rev'] == local_changes.get(change['id']): - del local_changes[change['id']] - return None - - if not change or change.get('last_seq'): - return None - - if change['id'].startswith('_design'): # XXX: is this still neccesary? - return None - - return change - - def notification_dispatcher(obj_id, obj_type, obj_name, deleted, revision): - if deleted: - obj_name, obj_type = self.get_deleted_object_name_and_type(obj_id) - notification_center.deleteObject(obj_id) - update = False - else: - is_new_object = revision.split("-")[0] == "1" - obj = self.get_object(obj_type, obj_id) - if obj: - if is_new_object: - notification_center.addObject(obj) - update = False - else: - notification_center.editObject(obj) - update = True - else: - update = False - for var in [var for var in [obj_id, obj_type, obj_name] if var is None]: - var = "" - notification_center.changeFromInstance(obj_id, obj_type, - obj_name, deleted=deleted, - update=update) def get_changes(): - # dark maaaaaagic *sing with me!* dark maaaaaagic + if not self.stream: + return False while True: - if self.stream: - try: - for change, obj_type, obj_name in self.stream: - print(change, obj_type, obj_name) - with self.changes_lock: - change = filter_changes(change, obj_type) - if change: - deleted = bool(change.get('deleted')) - obj_id = change.get('id') - revision = change.get("changes")[-1].get('rev') - notification_dispatcher(obj_id, obj_type, obj_name, - deleted, revision) - except server_io_exceptions.ChangesStreamStoppedAbruptly: - notification_center.WorkspaceProblem() - return False - else: + try: + for obj_information in self.stream: + action = obj_information.get('action') + obj_id = obj_information.get('id') + obj_type = obj_information.get('type') + obj_name = obj_information.get('name') + obj = self.get_object(obj_type, obj_id) + if action == 'CREATE': + notification_center.addObject(obj) + elif action == 'UPDATE': + notification_center.editObject(obj) + elif action == 'DELETE': + notification_center.deleteObject(obj_id) + else: + raise Exception('Invalid action') + notification_center.changeFromInstance( + obj_id, + obj_type, + obj_name, + deleted=action == 'DELETE', + update=action == 'UPDATE') + except server_io_exceptions.ChangesStreamStoppedAbruptly: + notification_center.WorkspaceProblem() return False time.sleep(0.5) diff --git a/persistence/server/changes_stream.py b/persistence/server/changes_stream.py index 54a22aa55a4..3748bd9b939 100644 --- a/persistence/server/changes_stream.py +++ b/persistence/server/changes_stream.py @@ -112,7 +112,7 @@ def __iter__(self): data = json.loads(self.changes_queue.get_nowait()) except Empty: raise StopIteration - yield data[0], data[1], data[2] + yield data def _get_object_type_and_name_from_change(self, change): try: diff --git a/server/app.py b/server/app.py index 7cb5d3bebdf..d0f67dcd763 100644 --- a/server/app.py +++ b/server/app.py @@ -37,3 +37,6 @@ class MiniJSONEncoder(flask.json.JSONEncoder): # Load APIs import server.api import server.modules.info + +# Load SQLAlchemy Events +import server.events From 851cc3a5c6ca6b75505a1cb75f1fb5377ddac16f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 5 Jun 2017 11:37:43 -0300 Subject: [PATCH 0003/1506] ADD twisted websockets factories and sqlalchemy events --- server/events.py | 15 +++++++ server/websocket_factories.py | 81 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 server/events.py create mode 100644 server/websocket_factories.py diff --git a/server/events.py b/server/events.py new file mode 100644 index 00000000000..d88f14926bb --- /dev/null +++ b/server/events.py @@ -0,0 +1,15 @@ +from models import Host +from sqlalchemy import event + +from server.websocket_factories import changes_queue + + +@event.listens_for(Host, 'after_insert') +def receive_after_insert_host(mapper, connection, target): + msg = { + 'id': target.id, + 'action': 'CREATE', + 'type': 'Host', + 'name': target.name + } + changes_queue.put(msg) diff --git a/server/websocket_factories.py b/server/websocket_factories.py new file mode 100644 index 00000000000..7a6f6f40f2b --- /dev/null +++ b/server/websocket_factories.py @@ -0,0 +1,81 @@ +import json +import Cookie +from Queue import Queue, Empty + +from twisted.internet import reactor +# from twisted.python import log + +from autobahn.twisted.websocket import ( + WebSocketServerFactory, + WebSocketServerProtocol +) + +changes_queue = Queue() + + +class BroadcastServerProtocol(WebSocketServerProtocol): + + def onConnect(self, request): + protocol, headers = None, {} + # see if there already is a cookie set .. + print request + if 'cookie' in request.headers: + print 'cookie' + print (str(request.headers['cookie'])) + print 'cookie' + try: + cookie = Cookie.SimpleCookie() + cookie.load(str(request.headers['cookie'])) + except Cookie.CookieError: + pass + return (protocol, headers) + + def onOpen(self): + self.factory.register(self) + + def onMessage(self, payload, isBinary): + if not isBinary: + msg = "{} from {}".format(payload.decode('utf8'), self.peer) + self.factory.broadcast(msg) + + def connectionLost(self, reason): + WebSocketServerProtocol.connectionLost(self, reason) + self.factory.unregister(self) + + +class WorkspaceServerFactory(WebSocketServerFactory): + + def __init__(self, url): + WebSocketServerFactory.__init__(self, url) + self.workspace_clients = [] + self.tickcount = 0 + self.tick() + + def tick(self): + try: + msg = changes_queue.get_nowait() + self.broadcast(json.dumps(msg)) + except Empty: + pass + reactor.callLater(0.5, self.tick) + + def register(self, client): + print 'register' + print client + print 'register' + + if client not in self.workspace_clients: + print("registered client {}".format(client.peer)) + self.workspace_clients.append(client) + + def unregister(self, client): + if client in self.workspace_clients: + print("unregistered client {}".format(client.peer)) + self.workspace_clients.remove(client) + + def broadcast(self, msg): + print("broadcasting prepared message '{}' ..".format(msg)) + preparedMsg = self.prepareMessage(msg) + for client in self.workspace_clients: + reactor.callFromThread(client.sendPreparedMessage, preparedMsg) + print("prepared message sent to {}".format(client.peer)) From 5d7fd55d2c73ed654e4a923bebf5cf710e7072d9 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 13 Jun 2017 14:54:21 -0300 Subject: [PATCH 0004/1506] ADD improve gtk code to handle events. add orm events for Service object Some logic from gtk events removed since we have object type information. Add more ORM events for Service model. Also added update and delete events. --- gui/customevents.py | 20 ++++++------ gui/gtk/application.py | 2 +- gui/gtk/mainwidgets.py | 9 ++---- gui/gtk/server.py | 9 +++--- gui/notifier.py | 14 ++++----- persistence/server/changes_stream.py | 2 +- server/events.py | 46 ++++++++++++++++++++++++---- 7 files changed, 65 insertions(+), 37 deletions(-) diff --git a/gui/customevents.py b/gui/customevents.py index 51a67a61e42..b72eacd57ec 100644 --- a/gui/customevents.py +++ b/gui/customevents.py @@ -144,23 +144,22 @@ def __init__(self, host_id): class ChangeFromInstanceCustomEvent(CustomEvent): - def __init__(self, object_id, object_type, object_name, - deleted=False, update=False): + def __init__(self, action, object_id, object_type, object_name): CustomEvent.__init__(self, CHANGEFROMINSTANCE) self.object_id = object_id self.object_type = object_type self.object_name = object_name - self.deleted = deleted - self.updated_or_created = "updated" if update else "created" + self.action = action def __str__(self): - if not self.object_type or not self.object_name and self.deleted: - return "An object was deleted" - if self.deleted: - return "The {0} {1} was deleted".format(self.object_type, self.object_name) + action_msg = { + 'UPDATE': 'updated', + 'CREATE': 'created', + 'DELETE': 'deleted', + } return "The {0} {1} was {2}".format(self.object_type, self.object_name, - self.updated_or_created) + action_msg[self.action]) class AddObjectCustomEvent(CustomEvent): def __init__(self, new_obj): @@ -168,9 +167,10 @@ def __init__(self, new_obj): self.new_obj = new_obj class DeleteObjectCustomEvent(CustomEvent): - def __init__(self, obj_id): + def __init__(self, obj_id, obj_type): CustomEvent.__init__(self, DELETEOBJECT) self.obj_id = obj_id + self.obj_type = obj_type class UpdateObjectCustomEvent(CustomEvent): def __init__(self, obj): diff --git a/gui/gtk/application.py b/gui/gtk/application.py index f6c30a9d82c..2be79472547 100644 --- a/gui/gtk/application.py +++ b/gui/gtk/application.py @@ -566,7 +566,7 @@ def add_object(): def delete_object(): if event.obj_id: - GObject.idle_add(self.hosts_sidebar.remove_object, event.obj_id) + GObject.idle_add(self.hosts_sidebar.remove_object, event.obj_id, event.obj_type) host_count, service_count, vuln_count = self.update_counts() GObject.idle_add(self.statusbar.update_ws_info, host_count, service_count, vuln_count) diff --git a/gui/gtk/mainwidgets.py b/gui/gtk/mainwidgets.py index 9ca430bd819..e42a915ddca 100644 --- a/gui/gtk/mainwidgets.py +++ b/gui/gtk/mainwidgets.py @@ -9,7 +9,6 @@ import gi import os import math -import operator import webbrowser gi.require_version('Gtk', '3.0') @@ -397,11 +396,9 @@ def add_object(self, obj): if object_type == "Vulnerability" or object_type == "VulnerabilityWeb": self.add_vuln(vuln=obj) - def remove_object(self, obj_id): + def remove_object(self, obj_id, obj_type): """Remove an obj of id obj_id from the model, if found there""" - potential_host_id = obj_id.split('.')[0] - is_host = len(obj_id.split('.')) == 1 - if is_host: + if obj_type == 'Host': self.remove_host(host_id=obj_id) # elif not is_host and self._is_vuln_of_host(vuln_id=obj_id, host_id=potential_host_id): # self.remove_vuln(vuln_id=obj_id) @@ -409,7 +406,7 @@ def remove_object(self, obj_id): # Since we don't know the type of the delete object, # we have to assume it's a vulnerability so the host's # name is updated with the ammount of vulns - host = self.get_single_host_function(potential_host_id) + host = self.get_single_host_function(obj_id) if host: self._modify_vuln_amount_of_single_host_in_model(host.getID(), host.getVulnAmount()) diff --git a/gui/gtk/server.py b/gui/gtk/server.py index da11738f1ee..3e6985ac56a 100644 --- a/gui/gtk/server.py +++ b/gui/gtk/server.py @@ -77,7 +77,7 @@ def get_object(self, object_signature, object_id): def get_host(self, host_id): return models.get_host(self.active_workspace, host_id) - @safe_io_with_server((0,0,0,0)) + @safe_io_with_server((0, 0, 0, 0)) def get_workspace_numbers(self): return models.get_workspace_numbers(self.active_workspace) @@ -119,15 +119,14 @@ def get_changes(): elif action == 'UPDATE': notification_center.editObject(obj) elif action == 'DELETE': - notification_center.deleteObject(obj_id) + notification_center.deleteObject(obj_id, obj_type) else: raise Exception('Invalid action') notification_center.changeFromInstance( + action, obj_id, obj_type, - obj_name, - deleted=action == 'DELETE', - update=action == 'UPDATE') + obj_name) except server_io_exceptions.ChangesStreamStoppedAbruptly: notification_center.WorkspaceProblem() return False diff --git a/gui/notifier.py b/gui/notifier.py index 16a28bc881c..0563938e488 100644 --- a/gui/notifier.py +++ b/gui/notifier.py @@ -75,13 +75,11 @@ def conflictUpdate(self, vulns_changed): def conflictResolution(self, conflicts): self._notifyWidgets(events.ResolveConflictsCustomEvent(conflicts)) - def changeFromInstance(self, obj_id, obj_type, obj_name, - deleted=False, update=False): - self._notifyWidgets(events.ChangeFromInstanceCustomEvent(obj_id, + def changeFromInstance(self, action, obj_id, obj_type, obj_name): + self._notifyWidgets(events.ChangeFromInstanceCustomEvent(action, + obj_id, obj_type, - obj_name, - deleted=deleted, - update=update)) + obj_name)) def addHostFromChanges(self, obj): self._notifyWidgets(events.AddHostChangesEvent(obj)) @@ -89,8 +87,8 @@ def addHostFromChanges(self, obj): def editObject(self, obj): self._notifyWidgets(events.UpdateObjectCustomEvent(obj)) - def deleteObject(self, obj_id): - self._notifyWidgets(events.DeleteObjectCustomEvent(obj_id)) + def deleteObject(self, obj_id, obj_type): + self._notifyWidgets(events.DeleteObjectCustomEvent(obj_id, obj_type)) def addObject(self, new_object): self._notifyWidgets(events.AddObjectCustomEvent(new_object)) diff --git a/persistence/server/changes_stream.py b/persistence/server/changes_stream.py index 3748bd9b939..18d3ac98aad 100644 --- a/persistence/server/changes_stream.py +++ b/persistence/server/changes_stream.py @@ -6,7 +6,6 @@ See the file 'doc/LICENSE' for the license information ''' -import time import json import threading from Queue import Queue, Empty @@ -69,6 +68,7 @@ def stop(self): class WebsocketsChangesStream(ChangesStream): + def __init__(self, workspace_name, server_url, **params): self._base_url = server_url self.changes_queue = Queue() diff --git a/server/events.py b/server/events.py index d88f14926bb..992d510c596 100644 --- a/server/events.py +++ b/server/events.py @@ -1,15 +1,49 @@ -from models import Host +from models import ( + Host, + Service, +) from sqlalchemy import event from server.websocket_factories import changes_queue -@event.listens_for(Host, 'after_insert') -def receive_after_insert_host(mapper, connection, target): +def new_object_event(mapper, connection, instance): msg = { - 'id': target.id, + 'id': instance.id, 'action': 'CREATE', - 'type': 'Host', - 'name': target.name + 'type': instance.__class__.__name__, + 'name': instance.name } changes_queue.put(msg) + + +def delete_object_event(mapper, connection, instance): + msg = { + 'id': instance.id, + 'action': 'DELETE', + 'type': instance.__class__.__name__, + 'name': instance.name + } + changes_queue.put(msg) + + +def update_object_event(mapper, connection, instance): + msg = { + 'id': instance.id, + 'action': 'UPDATE', + 'type': instance.__class__.__name__, + 'name': instance.name + } + changes_queue.put(msg) + +# New object bindings +event.listen(Host, 'after_insert', new_object_event) +event.listen(Service, 'after_insert', new_object_event) + +# Delete object bindings +event.listen(Host, 'after_delete', delete_object_event) +event.listen(Service, 'after_delete', delete_object_event) + +# Update object bindings +event.listen(Host, 'after_update', update_object_event) +event.listen(Service, 'after_update', update_object_event) From cbbe739322a41ce98761ce73e88dddbc30661253 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 14 Jun 2017 19:22:04 -0300 Subject: [PATCH 0005/1506] ADD join workspace feature without authentication --- persistence/server/changes_stream.py | 17 ++++-- server/events.py | 15 ++++-- server/websocket_factories.py | 80 +++++++++++++++++++--------- 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/persistence/server/changes_stream.py b/persistence/server/changes_stream.py index 18d3ac98aad..55a3bb15582 100644 --- a/persistence/server/changes_stream.py +++ b/persistence/server/changes_stream.py @@ -79,13 +79,25 @@ def __init__(self, workspace_name, server_url, **params): "ws://{0}:9000".format(self._base_url), on_message=self.on_message, on_error=self.on_error, + on_open=self.on_open, on_close=self.on_close) - - self.ws.on_open = self.on_open + # ws.run_forever will call on_message, on_error, on_close and on_open + # see websocket client python docs on: + # https://github.com/websocket-client/websocket-client thread = threading.Thread(target=self.ws.run_forever, args=()) thread.daemon = True thread.start() + def stop(self): + self.ws.close() + super(WebsocketsChangesStream, self).stop() + + def on_open(self, ws): + self.ws.send(json.dumps({ + 'action': 'JOIN_WORKSPACE', + 'workspace': self.workspace_name, + })) + def on_message(self, ws, message): self.changes_queue.put(message) @@ -95,7 +107,6 @@ def on_error(self, ws, error): def on_close(selg, ws): print "### closed ###" - def on_open(self, ws): print 'open' def __enter__(self): diff --git a/server/events.py b/server/events.py index 992d510c596..fcb485a15d4 100644 --- a/server/events.py +++ b/server/events.py @@ -8,34 +8,43 @@ def new_object_event(mapper, connection, instance): + # Since we don't have jet a model for workspace we + # retrieve the name from the connection string + workspace_name = repr(connection.engine).split('.')[0].split('/')[-1] msg = { 'id': instance.id, 'action': 'CREATE', 'type': instance.__class__.__name__, - 'name': instance.name + 'name': instance.name, + 'workspace': workspace_name } changes_queue.put(msg) def delete_object_event(mapper, connection, instance): + workspace_name = repr(connection.engine).split('.')[0].split('/')[-1] msg = { 'id': instance.id, 'action': 'DELETE', 'type': instance.__class__.__name__, - 'name': instance.name + 'name': instance.name, + 'workspace': workspace_name } changes_queue.put(msg) def update_object_event(mapper, connection, instance): + workspace_name = repr(connection.engine).split('.')[0].split('/')[-1] msg = { 'id': instance.id, 'action': 'UPDATE', 'type': instance.__class__.__name__, - 'name': instance.name + 'name': instance.name, + 'workspace': workspace_name } changes_queue.put(msg) + # New object bindings event.listen(Host, 'after_insert', new_object_event) event.listen(Service, 'after_insert', new_object_event) diff --git a/server/websocket_factories.py b/server/websocket_factories.py index 7a6f6f40f2b..d955207afe6 100644 --- a/server/websocket_factories.py +++ b/server/websocket_factories.py @@ -1,5 +1,6 @@ import json import Cookie +from collections import defaultdict from Queue import Queue, Empty from twisted.internet import reactor @@ -10,6 +11,7 @@ WebSocketServerProtocol ) + changes_queue = Queue() @@ -30,13 +32,20 @@ def onConnect(self, request): pass return (protocol, headers) - def onOpen(self): - self.factory.register(self) - - def onMessage(self, payload, isBinary): - if not isBinary: - msg = "{} from {}".format(payload.decode('utf8'), self.peer) - self.factory.broadcast(msg) + def onMessage(self, payload, is_binary): + """ + We only support JOIN and LEAVE workspace messages. + When authentication is implemented we need to verify + that the user can join the selected workspace. + When authentication is implemented we need to reply + the client if the join failed. + """ + if not is_binary: + message = json.loads(payload) + if message['action'] == 'JOIN_WORKSPACE': + self.factory.join_workspace(self, message['workspace']) + if message['action'] == 'LEAVE_WORKSPACE': + self.factory.leave_workspace(self, message['workspace']) def connectionLost(self, reason): WebSocketServerProtocol.connectionLost(self, reason) @@ -44,14 +53,30 @@ def connectionLost(self, reason): class WorkspaceServerFactory(WebSocketServerFactory): + """ + This factory uses the changes_queue to broadcast + message via websockets. + + Any message put on that queue will be sent to clients. + Clients subscribe to workspace channels. + This factory will broadcast message to clients subscribed + on workspace. + + The message in the queue must contain the workspace. + """ def __init__(self, url): WebSocketServerFactory.__init__(self, url) - self.workspace_clients = [] - self.tickcount = 0 + # this dict has a key for each channel + # values are list of clients. + self.workspace_clients = defaultdict(list) self.tick() def tick(self): + """ + Uses changes_queue to broadcast messages to clients. + broadcast method knowns each client workspace. + """ try: msg = changes_queue.get_nowait() self.broadcast(json.dumps(msg)) @@ -59,23 +84,30 @@ def tick(self): pass reactor.callLater(0.5, self.tick) - def register(self, client): - print 'register' - print client - print 'register' - - if client not in self.workspace_clients: + def join_workspace(self, client, workspace): + print('Join workspace {0}'.format(workspace)) + if client not in self.workspace_clients[workspace]: print("registered client {}".format(client.peer)) - self.workspace_clients.append(client) - - def unregister(self, client): - if client in self.workspace_clients: - print("unregistered client {}".format(client.peer)) - self.workspace_clients.remove(client) + self.workspace_clients[workspace].append(client) + + def leave_workspace(self, client, workspace_name): + print('Leave workspace {0}'.format(workspace_name)) + self.workspace_clients[workspace_name].remove(client) + + def unregister(self, client_to_unregister): + """ + Search for the client_to_unregister in all workspaces + """ + for workspace_name, clients in self.workspace_clients.items(): + for client in clients: + if client == client_to_unregister: + print("unregistered client from workspace {0}".format(workspace_name)) + self.leave_workspace(client, workspace_name) + return def broadcast(self, msg): print("broadcasting prepared message '{}' ..".format(msg)) - preparedMsg = self.prepareMessage(msg) - for client in self.workspace_clients: - reactor.callFromThread(client.sendPreparedMessage, preparedMsg) + prepared_msg = json.loads(self.prepareMessage(msg).payload) + for client in self.workspace_clients[prepared_msg['workspace']]: + reactor.callFromThread(client.sendPreparedMessage, self.prepareMessage(msg)) print("prepared message sent to {}".format(client.peer)) From fa510a2ad4c6078fb591541bdeb64dabbd743268 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 6 Jul 2017 13:37:28 -0300 Subject: [PATCH 0006/1506] Add initial jenkins file. The jenkins file current tasks are: * Download requirements * Generates some reports * Execute unit tests --- Jenkinsfile | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000000..8d0176de1f4 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,63 @@ +#!groovy +node { + def ENV_PATH = "$HOME/venv/faraday" + echo "${ENV_PATH}" + + stage("Clean virtualenv") { + sh "rm -rf ${ENV_PATH}" + } + + stage("Install Python Virtual Enviroment") { + sh "virtualenv --no-site-packages ${ENV_PATH}" + } + + // Get the latest version of our application code. + stage ("Pull Code from SCM") { + checkout scm + } + + stage ("Install Application Dependencies") { + sh """ + source ${ENV_PATH}/bin/activate + pip install nose nosexcover virtualenv responses + pip install -r $WORKSPACE/requirements.txt + pip install -r $WORKSPACE/requirements_server.txt + deactivate + """ + } + + stage ("Check code style") { + sh """ + sloccount --duplicates --wide --details $WORKSPACE | fgrep -v .git > $WORKSPACE/sloccount.sc || : + find $WORKSPACE -name \\"*.py\\" | egrep -v '^./tests/' | xargs pyflakes > $WORKSPACE/pyflakes.log || : + find $WORKSPACE -name \\"*.py\\" | egrep -v '^./tests/' | xargs pylint --output-format=parseable --reports=y > $WORKSPACE/pylint.log || : + eslint -c /home/faraday/.eslintrc.js -f checkstyle $WORKSPACE/server/www/scripts/**/* > eslint.xml || true + + """ + warnings canComputeNew: false, canResolveRelativePaths: false, consoleParsers: [[parserName: 'PyFlakes']], defaultEncoding: '', excludePattern: '', healthy: '', includePattern: '', messagesPattern: '', parserConfigurations: [[parserName: 'AcuCobol Compiler', pattern: 'pyflakes.log']], unHealthy: '' + + } + + stage ("Run Unit/Integration Tests") { + def testsError = null + try { + sh """ + source ${ENV_PATH}/bin/activate + cd $WORKSPACE && nosetests --verbose --with-xunit --xunit-file=$WORKSPACE/xunit.xml --with-xcoverage --xcoverage-file=$WORKSPACE/coverage.xml -ignore-files='.*dont_run_rest_controller_apis.*' --no-byte-compile -v `find test_cases -name '*.py'| grep -v dont_run` || : + deactivate + """ + step([$class: 'CoberturaPublisher', autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: '**/coverage.xml', failNoReports: false, failUnhealthy: false, failUnstable: false, maxNumberOfBuilds: 0, onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false]) + } + catch(err) { + testsError = err + currentBuild.result = 'FAILURE' + } + finally { + junit "**/xunit.xml" + + if (testsError) { + throw testsError + } + } + } +} From 42b06106940a3472d7e34a384cb0ea2ef6c3d6e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 13 Jul 2017 18:22:43 -0300 Subject: [PATCH 0007/1506] Add flask_security test code It is the code in https://github.com/mattupstate/flask-security/blob/develop/docs/quickstart.rst#sqlalchemy-application-1 with a few modifications. --- requirements_server.txt | 2 ++ server/app.py | 27 ++++++++++++++++++++++++++- server/database.py | 15 +++++++++++++++ server/models.py | 35 +++++++++++++++++++++++++++++++++-- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/requirements_server.txt b/requirements_server.txt index 7fe736a1fcb..0f680874e7f 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -7,3 +7,5 @@ sqlalchemy>=1.0.12 pyopenssl>=16.0.0 service_identity>=16.0.0 pyasn1-modules +flask-security +bcrypt diff --git a/server/app.py b/server/app.py index 9d5b7afed4f..6932f998471 100644 --- a/server/app.py +++ b/server/app.py @@ -3,13 +3,38 @@ # See the file 'doc/LICENSE' for the license information import flask +from flask_security import Security, login_required, \ + SQLAlchemySessionUserDatastore + import server.config import server.database - +import server.models from server.utils.logger import LOGGING_HANDLERS app = flask.Flask(__name__) +app.config['SECRET_KEY'] = 'supersecret' +app.config['SECURITY_PASSWORD_SINGLE_HASH'] = True + +# Setup Flask-Security +user_datastore = SQLAlchemySessionUserDatastore(server.database.common_session, + server.models.User, + server.models.Role) +security = Security(app, user_datastore) + +# Create a user to test with +@app.before_first_request +def create_user(): + server.database.init_common_db() + user_datastore.create_user(email='matt@nobien.net', password='password') + server.database.common_session.commit() + +# Views +@app.route('/test') +@login_required +def home(): + return 'Logged in!!' + def setup(): app.debug = server.config.is_debug_mode() diff --git a/server/database.py b/server/database.py index c4a12d1fbb4..79c2dcc60e8 100644 --- a/server/database.py +++ b/server/database.py @@ -441,3 +441,18 @@ class WorkspaceNotFound(Exception): def __init__(self, workspace_name): super(WorkspaceNotFound, self).__init__('Workspace "%s" not found' % workspace_name) + +# +# Connection to a database common to al workspaces +# + +common_engine = create_engine('sqlite://') +common_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=common_engine)) +server.models.CommonBase.metadata.bind = common_engine + +def init_common_db(): + from server.models import CommonBase + CommonBase.metadata.create_all(bind=common_engine) + CommonBase.query = common_session.query_property() diff --git a/server/models.py b/server/models.py index 145e142d5fc..bf0fb878766 100644 --- a/server/models.py +++ b/server/models.py @@ -4,14 +4,17 @@ import json -from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, Float, Text, UniqueConstraint -from sqlalchemy.orm import relationship +from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, Float, \ + Text, UniqueConstraint, DateTime +from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.declarative import declarative_base +from flask_security import UserMixin, RoleMixin SCHEMA_VERSION = 'W.2.6.0' Base = declarative_base() +CommonBase = declarative_base() class EntityNotFound(Exception): def __init__(self, entity_id): @@ -493,3 +496,31 @@ def update_from_document(self, document): self.user = document.get('user', None) self.workspace = document.get('workspace', None) + +class RolesUsers(CommonBase): + __tablename__ = 'roles_users' + id = Column(Integer(), primary_key=True) + user_id = Column('user_id', Integer(), ForeignKey('user.id')) + role_id = Column('role_id', Integer(), ForeignKey('role.id')) + +class Role(CommonBase, RoleMixin): + __tablename__ = 'role' + id = Column(Integer(), primary_key=True) + name = Column(String(80), unique=True) + description = Column(String(255)) + +class User(CommonBase, UserMixin): + __tablename__ = 'user' + id = Column(Integer, primary_key=True) + email = Column(String(255), unique=True) + username = Column(String(255)) + password = Column(String(255)) + last_login_at = Column(DateTime()) + current_login_at = Column(DateTime()) + last_login_ip = Column(String(100)) + current_login_ip = Column(String(100)) + login_count = Column(Integer) + active = Column(Boolean()) + confirmed_at = Column(DateTime()) + roles = relationship('Role', secondary='roles_users', + backref=backref('users', lazy='dynamic')) From 8eb47b124196c475a8c00cdc3044e40ee0a73a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 14 Jul 2017 17:01:09 -0300 Subject: [PATCH 0008/1506] Replace login redirects for 403 errors --- server/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/app.py b/server/app.py index 6932f998471..5c4f2384164 100644 --- a/server/app.py +++ b/server/app.py @@ -22,6 +22,12 @@ server.models.Role) security = Security(app, user_datastore) +# We are exposing a RESTful API, so don't redirect a user to a login page in +# case of being unauthorized, raise a 403 error instead +@app.login_manager.unauthorized_handler +def unauthorized(): + flask.abort(403) + # Create a user to test with @app.before_first_request def create_user(): From e9a0cf42137fffac859694e9d2a48cee3e540269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 14 Jul 2017 18:36:55 -0300 Subject: [PATCH 0009/1506] Require logged user to access API endpoints --- server/app.py | 28 ++++++++++++++++------------ server/database.py | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/server/app.py b/server/app.py index 5c4f2384164..6a8c4279dec 100644 --- a/server/app.py +++ b/server/app.py @@ -29,18 +29,22 @@ def unauthorized(): flask.abort(403) # Create a user to test with -@app.before_first_request -def create_user(): - server.database.init_common_db() - user_datastore.create_user(email='matt@nobien.net', password='password') - server.database.common_session.commit() - -# Views -@app.route('/test') -@login_required -def home(): - return 'Logged in!!' - +# @app.before_first_request +# def create_user(): +server.database.init_common_db() +user_datastore.create_user(email='matt@nobien.net', password='password') +server.database.common_session.commit() + +# Make API endpoints require a login user by default. Based on +# https://stackoverflow.com/questions/13428708/best-way-to-make-flask-logins-login-required-the-default +app.view_functions['security.login'].is_public = True +app.view_functions['security.logout'].is_public = True +@app.before_request +def default_login_required(): + view = app.view_functions.get(flask.request.endpoint) + logged_in = 'user_id' in flask.session + if (not logged_in and not getattr(view, 'is_public', False)): + flask.abort(403) def setup(): app.debug = server.config.is_debug_mode() diff --git a/server/database.py b/server/database.py index 79c2dcc60e8..05e72c082a1 100644 --- a/server/database.py +++ b/server/database.py @@ -446,7 +446,7 @@ def __init__(self, workspace_name): # Connection to a database common to al workspaces # -common_engine = create_engine('sqlite://') +common_engine = create_engine('sqlite:////tmp/test.db') common_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=common_engine)) From 272ccd6666148588629b67ff1fc8af0320173353 Mon Sep 17 00:00:00 2001 From: Andres Date: Sat, 15 Jul 2017 15:04:33 -0300 Subject: [PATCH 0010/1506] #4148 fix hostnames, buttons, status and severity row alignment --- server/dao/vuln.py | 2 +- .../statusReport/partials/ui-grid/columns/severitycolumn.html | 2 +- .../statusReport/partials/ui-grid/columns/statuscolumn.html | 2 +- .../scripts/statusReport/partials/ui-grid/confirmbutton.html | 2 +- .../www/scripts/statusReport/partials/ui-grid/deletebutton.html | 2 +- .../www/scripts/statusReport/partials/ui-grid/editbutton.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/dao/vuln.py b/server/dao/vuln.py index a2376ba2c29..27df6b07864 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -190,7 +190,7 @@ def get_parent_id(couchdb_id): 'tags': [], 'type': vuln.document_type, 'target': host.name, - 'hostnames': hostnames.split(',') if hostnames else '', + 'hostnames': filter(None, hostnames.split(',')) if hostnames else '', 'service': "(%s/%s) %s" % (service.ports, service.protocol, service.s_name) if service.ports else '' }} diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html index 38d4231cf26..742e68662dd 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html @@ -1 +1 @@ -
{{COL_FIELD | uppercase}}
\ No newline at end of file +
{{COL_FIELD | uppercase}}
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html index f3017594dbf..654f2f50783 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html @@ -1 +1 @@ -
{{COL_FIELD | uppercase}}
\ No newline at end of file +
{{COL_FIELD | uppercase}}
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html index 23cc629fdf0..f6f79e1fc24 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html +++ b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html @@ -1,3 +1,3 @@ -
+
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/deletebutton.html b/server/www/scripts/statusReport/partials/ui-grid/deletebutton.html index cfd0eb53185..bc52917722e 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/deletebutton.html +++ b/server/www/scripts/statusReport/partials/ui-grid/deletebutton.html @@ -1,3 +1,3 @@ -
+
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/editbutton.html b/server/www/scripts/statusReport/partials/ui-grid/editbutton.html index ebb1c761d47..1824ba2945f 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/editbutton.html +++ b/server/www/scripts/statusReport/partials/ui-grid/editbutton.html @@ -1,3 +1,3 @@ -
+
\ No newline at end of file From 68cdd981343dcbeeab31f4f6962f3ac7c478775c Mon Sep 17 00:00:00 2001 From: Andres Date: Sat, 15 Jul 2017 16:38:35 -0300 Subject: [PATCH 0011/1506] Fix #4148 Limit severity word length --- .../statusReport/partials/ui-grid/columns/severitycolumn.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html index 742e68662dd..214fbe0a547 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html @@ -1 +1 @@ -
{{COL_FIELD | uppercase}}
\ No newline at end of file +
{{COL_FIELD | uppercase}}
\ No newline at end of file From 68108197f9bf28ba3a96f682e6fb4cad93e936f2 Mon Sep 17 00:00:00 2001 From: Andres Date: Sat, 15 Jul 2017 16:58:16 -0300 Subject: [PATCH 0012/1506] Fix #4148 Creator column aligment --- .../statusReport/partials/ui-grid/columns/creatorcolumn.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/creatorcolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/creatorcolumn.html index 980e44f0ad9..6eb751c4c5d 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/creatorcolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/creatorcolumn.html @@ -1,6 +1,4 @@
{{COL_FIELD.split("(")[0] !== " " ? COL_FIELD : "EMPTY" + COL_FIELD}}
\ No newline at end of file From aa3ee5298cb06b87543463ebeb44d0620c422a8e Mon Sep 17 00:00:00 2001 From: Andres Date: Sat, 15 Jul 2017 18:50:32 -0300 Subject: [PATCH 0013/1506] Fix #4150 Full width and height grid. --- server/www/estilos.css | 15 ++++++++++--- .../statusReport/controllers/statusReport.js | 22 ++++++++++++++++++- .../statusReport/partials/statusReport.html | 4 ++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/server/www/estilos.css b/server/www/estilos.css index cde25056779..795357a3228 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -1037,8 +1037,8 @@ div.btn-group .vulns-filter{left: -2px;} div.btn-group .dropdown-menu.margin{margin-left: 38px} /* UI GRID */ .ui-grid-cell.ui-grid-disable-selection.ui-grid-row-header-cell{pointer-events: auto!important;} -.grid { - height: 98vh; +.grid { + height: 100%; } .ui-grid-cell-contents.left-rows.text-center.ng-scope{white-space: normal!important; } .ui-grid .ui-grid-row:nth-child(odd) .ui-grid-cell .ui-grid-cell-contents .crop-text, .ui-grid .ui-grid-row:nth-child(even) .ui-grid-cell .ui-grid-cell-contents .crop-text, .ui-grid-cell-contents{ @@ -1050,7 +1050,7 @@ div.btn-group .dropdown-menu.margin{margin-left: 38px} .white-space{white-space: pre!important; word-wrap: break-word;} .ui-grid-cell-contents.center > .pos-middle.crop-text{top: 2%!important;white-space: inherit!important;} .overflow-cell{ - height: 95px!important; + height: 30px!important; overflow-y: auto!important; } div.ui-grid-header-cell .ui-grid-cell-contents{white-space: normal;} @@ -1059,6 +1059,15 @@ div.alert.alert-danger.alert-dismissible .ws-list a:hover{text-decoration: none} a.button-disable{cursor: not-allowed;pointer-events: none;opacity: 0.5} .almost-expired{background-color: #dca7a7 !important} +.statusReport-Search:before,.statusReport-Search:after { + display: table; + content: " "; +} + +.statusReport-Search:after { + clear: both; +} + #custom-search-input{ border: solid 1px #E4E4E4; border-radius: 6px; diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index a2cb6c1f083..2508fdc6013 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -29,6 +29,8 @@ angular.module('faradayApp') $scope.vulnWebSelected; $scope.confirmed = false; + + $scope.gridHeight; var allVulns; var searchFilter = {}; @@ -64,7 +66,7 @@ angular.module('faradayApp') enableHorizontalScrollbar: 0, treeRowHeaderAlwaysVisible: false, enableGroupHeaderSelection: true, - rowHeight: 95 + rowHeight: 30 }; $scope.gridOptions.columnDefs = []; @@ -202,6 +204,11 @@ angular.module('faradayApp') loadVulns(); + resizeGrid(); + + angular.element($window).bind('resize', function () { + resizeGrid(); + }); }; var defineColumns = function() { @@ -441,6 +448,19 @@ angular.module('faradayApp') return res; }; + var resizeGrid = function() { + $scope.gridHeight = getGridHeight('grid', 'right-main', 15); + } + + var getGridHeight = function(gridClass, contentClass, bottomOffset) { + var contentOffset = angular.element(document.getElementsByClassName(contentClass)[0]).offset(); + var contentHeight = angular.element(document.getElementsByClassName(contentClass)[0]).height(); + var gridOffset = angular.element(document.getElementsByClassName(gridClass)).offset(); + if (gridOffset !== undefined) { + var gridHeight = contentHeight - (gridOffset.top) - bottomOffset; + return gridHeight + 'px'; + } + } $scope.saveAsModel = function() { var self = this; diff --git a/server/www/scripts/statusReport/partials/statusReport.html b/server/www/scripts/statusReport/partials/statusReport.html index cbeaff33672..791edb8e381 100644 --- a/server/www/scripts/statusReport/partials/statusReport.html +++ b/server/www/scripts/statusReport/partials/statusReport.html @@ -98,7 +98,7 @@

Status report for {{ workspace

-
+
-
diff --git a/server/www/scripts/statusReport/providers/reference.js b/server/www/scripts/statusReport/providers/reference.js index 75312cce946..a9d168c1412 100644 --- a/server/www/scripts/statusReport/providers/reference.js +++ b/server/www/scripts/statusReport/providers/reference.js @@ -3,9 +3,10 @@ // See the file 'doc/LICENSE' for the license information angular.module('faradayApp') - .factory('referenceService', function () { - return { - processReference: function (text) { + .factory('referenceFact', [ + function () { + referenceFact = {}; + referenceFact.processReference = function (text) { var url = 'http://google.com/', url_pattern = new RegExp('^(http|https):\\/\\/?'); @@ -35,5 +36,6 @@ angular.module('faradayApp') return url; } - } - }); \ No newline at end of file + + return referenceFact; + }]); \ No newline at end of file From 883adec288ac0487504bd3e0a71609f6e99caaad Mon Sep 17 00:00:00 2001 From: Andres Date: Mon, 17 Jul 2017 19:59:33 -0300 Subject: [PATCH 0024/1506] #4129 Cambios sobre codereview de @micabot --- server/www/index.html | 1 + .../www/scripts/commons/filters/removeLinebreaks.js | 11 +++++++++++ .../scripts/statusReport/controllers/statusReport.js | 8 ++------ .../partials/ui-grid/columns/desccolumn.html | 2 +- .../partials/ui-grid/columns/resolutioncolumn.html | 2 +- .../www/scripts/statusReport/providers/reference.js | 3 ++- 6 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 server/www/scripts/commons/filters/removeLinebreaks.js diff --git a/server/www/index.html b/server/www/index.html index e65e87925bd..bc7c4e516dc 100644 --- a/server/www/index.html +++ b/server/www/index.html @@ -101,6 +101,7 @@ + diff --git a/server/www/scripts/commons/filters/removeLinebreaks.js b/server/www/scripts/commons/filters/removeLinebreaks.js new file mode 100644 index 00000000000..0ce67b6b663 --- /dev/null +++ b/server/www/scripts/commons/filters/removeLinebreaks.js @@ -0,0 +1,11 @@ +// Faraday Penetration Test IDE +// Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +// See the file 'doc/LICENSE' for the license information + +// removes line breaks \n from text + +angular.module('faradayApp') + .filter('removeLinebreaks',function(){ + return function(text){ + return text?text.replace(/\n/g, ' '):''; + }}); \ No newline at end of file diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index b213086793a..4ec60b1f05e 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -3,10 +3,6 @@ // See the file 'doc/LICENSE' for the license information angular.module('faradayApp') - .filter('removeLinesbreak',function(){ - return function(text){ - return text?text.replace(/\n/g, ' '):''; - }}) .controller('statusReportCtrl', ['$scope', '$filter', '$routeParams', '$location', '$uibModal', '$cookies', '$q', '$window', 'BASEURL', @@ -1007,7 +1003,7 @@ angular.module('faradayApp') //TODO: this is horrible var srvName = srvStr.split(') ')[1]; return $scope.encodeUrl(srvName); - } + }; $scope.concatForTooltip = function (items, isArray, useDoubleLinebreak) { var elements = []; @@ -1023,7 +1019,7 @@ angular.module('faradayApp') } return elements.join("\n" + (useDoubleLinebreak ? "\n" : "")); - } + }; init(); }]); diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/desccolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/desccolumn.html index 6d8c812a2ad..9bc251418ed 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/desccolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/desccolumn.html @@ -1 +1 @@ -
{{COL_FIELD CUSTOM_FILTERS | removeLinesbreak}}
\ No newline at end of file +
{{COL_FIELD CUSTOM_FILTERS | removeLinebreaks}}
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html index cb39e5f8b76..e281edb1886 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html @@ -1 +1 @@ -
{{COL_FIELD.split("(")[0] !== " " ? COL_FIELD : "EMPTY" + COL_FIELD | removeLinesbreak}}
\ No newline at end of file +
{{COL_FIELD.split("(")[0] !== " " ? COL_FIELD : "EMPTY" + COL_FIELD | removeLinebreaks}}
\ No newline at end of file diff --git a/server/www/scripts/statusReport/providers/reference.js b/server/www/scripts/statusReport/providers/reference.js index a9d168c1412..a09f44bece2 100644 --- a/server/www/scripts/statusReport/providers/reference.js +++ b/server/www/scripts/statusReport/providers/reference.js @@ -5,7 +5,8 @@ angular.module('faradayApp') .factory('referenceFact', [ function () { - referenceFact = {}; + var referenceFact = {}; + referenceFact.processReference = function (text) { var url = 'http://google.com/', url_pattern = new RegExp('^(http|https):\\/\\/?'); From 5cf9e57b4855da2709c8648dc0f3fd7f054c4205 Mon Sep 17 00:00:00 2001 From: Andres Date: Mon, 17 Jul 2017 23:56:33 -0300 Subject: [PATCH 0025/1506] #4129 Grid row height change --- server/www/estilos.css | 7 +++++ .../statusReport/controllers/statusReport.js | 26 +++++++++---------- .../ui-grid/columns/severitycolumn.html | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/server/www/estilos.css b/server/www/estilos.css index dd22e0b6627..bea6a48a281 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -1040,10 +1040,14 @@ div.btn-group .dropdown-menu.margin{margin-left: 38px} .grid { height: 100%; } +.ui-grid-cell-contents p{ + margin-bottom: 0; +} .ui-grid-cell-contents.left-rows.text-center.ng-scope{white-space: normal!important; } .ui-grid .ui-grid-row:nth-child(odd) .ui-grid-cell .ui-grid-cell-contents .crop-text, .ui-grid .ui-grid-row:nth-child(even) .ui-grid-cell .ui-grid-cell-contents .crop-text, .ui-grid-cell-contents{ white-space: pre; word-wrap: break-word!important; + font-size: 13px !important; } .grid.ui-grid{font-family: 12px!important;} .ui-grid-cell-contents.row-tooltip.text-center{white-space: normal!important; } @@ -1059,6 +1063,9 @@ div.alert.alert-danger.alert-dismissible .ws-list a:hover{text-decoration: none} .grid .ui-grid-header { padding: 3px 5px; } +.ui-grid-text-center { + text-align: center; +} a.button-disable{cursor: not-allowed;pointer-events: none;opacity: 0.5} .almost-expired{background-color: #dca7a7 !important} diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index 4ec60b1f05e..02700a074a0 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -67,7 +67,7 @@ angular.module('faradayApp') enableHorizontalScrollbar: 0, treeRowHeaderAlwaysVisible: false, enableGroupHeaderSelection: true, - rowHeight: 30 + rowHeight: 47 }; $scope.gridOptions.columnDefs = []; @@ -216,7 +216,7 @@ angular.module('faradayApp') var defineColumns = function() { $scope.gridOptions.columnDefs.push({ name: 'selectAll', width: '20', headerCellTemplate: "", pinnedLeft:true }); - $scope.gridOptions.columnDefs.push({ name: 'confirmVuln', width: '22', headerCellTemplate: "
", cellTemplate: 'scripts/statusReport/partials/ui-grid/confirmbutton.html' }); + $scope.gridOptions.columnDefs.push({ name: 'confirmVuln', width: '23', headerCellTemplate: "
", cellTemplate: 'scripts/statusReport/partials/ui-grid/confirmbutton.html' }); function getColumnSort(columnName){ if($cookies.get('SRsortColumn') === columnName){ @@ -242,11 +242,21 @@ angular.module('faradayApp') '
'+ '
'; + $scope.gridOptions.columnDefs.push({ name : 'severity', + cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/severitycolumn.html', + headerCellTemplate: header, + displayName : "sev", + type: 'string', + width: '70', + visible: $scope.columns["severity"], + sort: getColumnSort('severity'), + sortingAlgorithm: compareSeverities + }); $scope.gridOptions.columnDefs.push({ name : 'date', displayName : "date", cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/datecolumn.html', headerCellTemplate: header, - width: '75', + width: '80', sort: getColumnSort('date'), visible: $scope.columns["date"] }); @@ -257,16 +267,6 @@ angular.module('faradayApp') sort: getColumnSort('name'), visible: $scope.columns["name"] }); - $scope.gridOptions.columnDefs.push({ name : 'severity', - cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/severitycolumn.html', - headerCellTemplate: header, - displayName : "sever", - type: 'string', - width: '80', - visible: $scope.columns["severity"], - sort: getColumnSort('severity'), - sortingAlgorithm: compareSeverities - }); $scope.gridOptions.columnDefs.push({ name : 'service', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/servicecolumn.html', headerCellTemplate: header, diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html index 214fbe0a547..193d29028a5 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html @@ -1 +1 @@ -
{{COL_FIELD | uppercase}}
\ No newline at end of file +
{{COL_FIELD | uppercase}}
\ No newline at end of file From 5387b9cb8b9d86dbc0d95db60aa69c2e3b6cdd69 Mon Sep 17 00:00:00 2001 From: Andres Date: Tue, 18 Jul 2017 00:02:53 -0300 Subject: [PATCH 0026/1506] #4150 open evidence on edit --- .../statusReport/controllers/modalEdit.js | 38 +++++++++++++------ .../statusReport/partials/modalEdit.html | 2 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/server/www/scripts/statusReport/controllers/modalEdit.js b/server/www/scripts/statusReport/controllers/modalEdit.js index f01cf8fc27c..8400ede1da2 100644 --- a/server/www/scripts/statusReport/controllers/modalEdit.js +++ b/server/www/scripts/statusReport/controllers/modalEdit.js @@ -3,11 +3,17 @@ // See the file 'doc/LICENSE' for the license information angular.module('faradayApp') - .controller('modalEditCtrl', ['$modalInstance', 'EASEOFRESOLUTION', 'STATUSES', 'commonsFact', 'severities', 'vuln', 'cweFact', 'referenceFact', - function($modalInstance, EASEOFRESOLUTION, STATUSES, commons, severities, vuln, cweFact, referenceFact) { + .controller('modalEditCtrl', + ['$modalInstance', '$routeParams','EASEOFRESOLUTION', 'STATUSES', 'commonsFact', + 'BASEURL', 'severities', 'vuln', 'cweFact', 'referenceFact', + 'encodeURIComponentFilter', + function($modalInstance, $routeParams,EASEOFRESOLUTION, STATUSES, commons, + BASEURL, severities, vuln, cweFact, referenceFact, + encodeURIComponent) { var vm = this; + vm.baseurl; vm.saveAsModelDisabled = false; vm.easeofresolution; vm.new_ref; @@ -30,6 +36,7 @@ angular.module('faradayApp') vm.new_ref = ""; vm.new_policyviolation = ""; vm.icons = {}; + vm.baseurl = BASEURL; vm.cweList = []; cweFact.get().then(function(data) { @@ -41,6 +48,7 @@ angular.module('faradayApp') vm.file_name_error = false; vm.data = { + _id: undefined, _attachments: {}, confirmed: false, data: "", @@ -83,10 +91,11 @@ angular.module('faradayApp') vm.modelMessage = "Done." vm.vulnModelsManager.create(vm.data); vm.saveAsModelDisabled = true; - } + }; vm.selectedFiles = function(files, e) { files.forEach(function(file) { + file.newfile = true; if(file.name.charAt(0) != "_") { if(!vm.data._attachments.hasOwnProperty(file)) vm.data._attachments[file.name] = file; } else { @@ -94,12 +103,12 @@ angular.module('faradayApp') } }); vm.icons = commons.loadIcons(vm._attachments); - } + }; vm.removeEvidence = function(name) { delete vm.data._attachments[name]; delete vm.icons[name]; - } + }; vm.toggleImpact = function(key) { vm.data.impact[key] = !vm.data.impact[key]; @@ -138,12 +147,17 @@ angular.module('faradayApp') vm.new_ref = ""; } } - } + }; - vm.openReference = function(text) - { - window.open(referenceFact.processReference(text), '_blank'); - } + vm.openReference = function(text) { + window.open(referenceFact.processReference(text), '_blank'); + }; + + vm.openEvidence = function(name) { + var currentEvidence = vm.data._attachments[name]; + if (!currentEvidence.newfile) + window.open(vm.baseurl + $routeParams.wsId + '/' + vm.data._id + '/' + encodeURIComponent(name), '_blank'); + }; vm.newPolicyViolation = function() { if (vm.new_policyviolation != "") { @@ -153,7 +167,7 @@ angular.module('faradayApp') vm.new_policyviolation = ""; } } - } + }; vm.populate = function(item) { for (var key in vm.data) { @@ -174,7 +188,7 @@ angular.module('faradayApp') policyviolations.push({value: policyviolation}); }); vm.data.policyviolations = policyviolations; - } + }; init(); }]); diff --git a/server/www/scripts/statusReport/partials/modalEdit.html b/server/www/scripts/statusReport/partials/modalEdit.html index 4306e1f56e0..e4d7c0f7a98 100644 --- a/server/www/scripts/statusReport/partials/modalEdit.html +++ b/server/www/scripts/statusReport/partials/modalEdit.html @@ -166,7 +166,7 @@

Evidence

  • -
  • - + Hosts
  • @@ -51,7 +51,7 @@
  • - + Vulnerability Templates
  • diff --git a/server/www/scripts/statusReport/partials/statusReport.html b/server/www/scripts/statusReport/partials/statusReport.html index 07e83b160b5..ed075d96837 100644 --- a/server/www/scripts/statusReport/partials/statusReport.html +++ b/server/www/scripts/statusReport/partials/statusReport.html @@ -31,6 +31,35 @@

    Status report for {{ workspace
    +
    + + + +
    +
    + + + + +

-
- - - -
-
- - - - -
- -
+
+
+ +
+
-
-
@@ -140,6 +145,9 @@ + + + diff --git a/server/www/scripts/app.js b/server/www/scripts/app.js index dd6e84d5e92..3435bd800b2 100644 --- a/server/www/scripts/app.js +++ b/server/www/scripts/app.js @@ -222,6 +222,11 @@ faradayApp.config(['$routeProvider', 'ngClipProvider', '$uibTooltipProvider', controller: 'commercialCtrl', title: 'Executive Report | ' }). + when('/login', { + templateUrl: 'scripts/auth/partials/login.html', + controller: 'loginCtrl', + title: 'Login | ' + }). when('/users', { templateUrl: 'scripts/commons/partials/commercial.html', controller: 'commercialCtrl', @@ -271,10 +276,18 @@ faradayApp.config(['$routeProvider', 'ngClipProvider', '$uibTooltipProvider', }); }]); -faradayApp.run(['$location', '$rootScope', function($location, $rootScope) { +faradayApp.run(['$location', '$rootScope', 'loginSrv', function($location, $rootScope, loginSrv) { $rootScope.$on('$routeChangeSuccess', function(event, current, previous) { if(current.hasOwnProperty('$$route')) { $rootScope.title = current.$$route.title; } }); + $rootScope.$on('$routeChangeStart', function(event){ + // Require in all routes (except the login one) + // Taken from http://stackoverflow.com/questions/26145871/redirect-on-all-routes-to-login-if-not-authenticated + // I don't know why this doesn't cause an infinite loop + loginSrv.isAuthenticated().then(function(auth){ + if(!auth) $location.path('/login'); + }); + }); }]); diff --git a/server/www/scripts/auth/controllers/login.js b/server/www/scripts/auth/controllers/login.js new file mode 100644 index 00000000000..3c70bdd409d --- /dev/null +++ b/server/www/scripts/auth/controllers/login.js @@ -0,0 +1,92 @@ +// Faraday Penetration Test IDE +// Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +// See the file 'doc/LICENSE' for the license information + +angular.module('faradayApp') + .controller('loginCtrl', ['$scope', '$location', '$cookies', 'loginSrv', function($scope, $location, $cookies, loginSrv) { + + $scope.data = { + "user": null, + "pass": null + }; + + $scope.errorLoginFlag = false; + + $scope.checkResetError = function(){ + if($scope.errorLoginFlag == true) + $scope.errorMessage = ""; + }; + + $scope.login = function(){ + if ($scope.data.user && $scope.data.pass){ + loginSrv.login($scope.data.user, $scope.data.pass).then(function(user){ + var currentUrl = ""; + if($cookies.currentUrl != undefined) { + currentUrl = $cookies.currentUrl; + } + $location.path(currentUrl); + }, function(){ + $scope.errorMessage = "Invalid user or password"; + $scope.errorLoginFlag = true; + }); + } else { + $scope.errorMessage = "Every field is required"; + $scope.errorLoginFlag = true; + } + }; + + $scope.$on('$routeChangeSuccess', function(){ + loginSrv.isAuthenticated().then(function(auth){ + if(auth) $location.path('/'); + }); + }); + + }]); + +angular.module('faradayApp') + .controller('loginBarCtrl', ['$scope', '$location', '$cookies','loginSrv', function($scope, $location, $cookies,loginSrv) { + $scope.user = null; + $scope.auth = loginSrv.isAuth(); + + $scope.$watch(loginSrv.isAuth, function(newValue) { + loginSrv.getUser().then(function(user){ + $scope.user = user; + $scope.auth = newValue; + }); + }); + + $scope.getUser = function(){ + return $scope.user; + }; + + $scope.loginPage = function(){ + $location.path('/login'); + }; + + $scope.logout = function(){ + loginSrv.logout().then(function(){ + $location.path('/login'); + $cookies.currentUrl = ""; + }); + }; + }]); + +angular.module('faradayApp') + .controller('loginBackgroundCtrl', ['$scope', '$location', '$cookies', function($scope, $location, $cookies) { + $scope.component = ""; + $scope.isLogin = true; + + $scope.updateComponent = function() { + if($location.path() == "") { + $scope.component = "home"; + } else { + $scope.component = $location.path().split("/")[1]; + } + $cookies.currentComponent = $scope.component; + $scope.isLogin = $scope.component == "login"; + }; + + $scope.$on('$locationChangeSuccess', function() { + $scope.updateComponent(); + }); + }]); diff --git a/server/www/scripts/auth/partials/login.html b/server/www/scripts/auth/partials/login.html new file mode 100644 index 00000000000..afbfb76a732 --- /dev/null +++ b/server/www/scripts/auth/partials/login.html @@ -0,0 +1,35 @@ + + + +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ diff --git a/server/www/scripts/auth/services/interceptor.js b/server/www/scripts/auth/services/interceptor.js new file mode 100644 index 00000000000..fc643a679af --- /dev/null +++ b/server/www/scripts/auth/services/interceptor.js @@ -0,0 +1,28 @@ +// Faraday Penetration Test IDE +// Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +// See the file 'doc/LICENSE' for the license information + +angular.module('faradayApp'). + factory('AuthInterceptor', ['$q', '$location', '$cookies', 'loginSrv', function($q, $location, $cookies, loginSrv){ + return { + response: function(response){ + return response; + }, + + responseError: function(response) { + if(response.status === 401 || response.status === 403){ + var deferred = $q.defer(); + loginSrv.isAuthenticated().then(function(auth){ + if(!auth) { + $location.path('/login'); + $cookies.currentComponent; + } + return deferred.reject(response); + }); + return deferred.promise; + }else{ + return $q.reject(response); + } + } + } + }]); diff --git a/server/www/scripts/auth/services/login.js b/server/www/scripts/auth/services/login.js new file mode 100644 index 00000000000..7727aba2bc6 --- /dev/null +++ b/server/www/scripts/auth/services/login.js @@ -0,0 +1,102 @@ +// Faraday Penetration Test IDE +// Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +// See the file 'doc/LICENSE' for the license information + +angular.module('faradayApp') + .service('loginSrv', ['BASEURL', '$q', function(BASEURL, $q) { + + loginSrv = { + is_authenticated: false, + user_obj: null, + last_time_checked: new Date(0), + + login: function(user, pass){ + var deferred = $q.defer(); + $.ajax({ + type: 'POST', + url: BASEURL + '_api/login', + data: JSON.stringify({"email": user, "password": pass}), + dataType: 'json', + contentType: 'application/json' + }) + .done(function(){ + $.getJSON(BASEURL + '_api/session', function(data) { + loginSrv.user_obj = data; + loginSrv.last_time_checked = new Date(); + loginSrv.is_authenticated = true; + deferred.resolve(loginSrv.user_obj); + }); + }) + .fail(function(){ + loginSrv.is_authenticated = false; + loginSrv.user_obj = null; + deferred.reject(); + }); + return deferred.promise; + }, + + _check: function(deferred) { + $.getJSON(BASEURL + '_api/session', function(data) { + loginSrv.user_obj = data; + loginSrv.is_authenticated = true; + loginSrv.last_time_checked = new Date(); + deferred.resolve(loginSrv.is_authenticated); + }).fail(function(){ + loginSrv.user_obj = null; + loginSrv.is_authenticated = false; + loginSrv.last_time_checked = new Date(); + deferred.resolve(loginSrv.is_authenticated); + }); + }, + + isAuthenticated: function(){ + var deferred = $q.defer(); + var seconds = (new Date() - loginSrv.last_time_checked) / 1000; + if (seconds > 1){ + //more than one second since checked for last time + loginSrv._check(deferred); + } else { + deferred.resolve(loginSrv.is_authenticated); + } + return deferred.promise; + }, + + isAuth: function(){ + return loginSrv.is_authenticated; + }, + + getUser: function(){ + var deferred = $q.defer(); + $.getJSON(BASEURL + '_api/session', function(data) { + loginSrv.user_obj = data; + deferred.resolve(loginSrv.user_obj); + }) + .fail(function(){ + deferred.reject(); + }); + return deferred.promise; + }, + + logout: function(){ + var deferred = $q.defer(); + var callback = function(){ + loginSrv.is_authenticated = false; + loginSrv.user_obj = null; + deferred.resolve(); + } + $.ajax({ + url: BASEURL + '_api/logout', + type: 'GET', + success: callback + }) + .fail(function(){ + deferred.reject(); + }); + return deferred.promise; + } + } + + loginSrv.isAuthenticated(); + + return loginSrv; + }]); diff --git a/server/www/scripts/navigation/controllers/navigationCtrl.js b/server/www/scripts/navigation/controllers/navigationCtrl.js index c981c7f7082..bd20b0f62a8 100644 --- a/server/www/scripts/navigation/controllers/navigationCtrl.js +++ b/server/www/scripts/navigation/controllers/navigationCtrl.js @@ -29,7 +29,7 @@ angular.module('faradayApp') catch(error){ console.log("Can't connect to faradaysec.com"); } - + }, function() { console.log("Can't connect to faradaysec.com"); }); @@ -90,7 +90,7 @@ angular.module('faradayApp') }; $scope.showNavigation = function() { - var noNav = ["home", "index", ""]; + var noNav = ["", "home", "login", "index"]; return noNav.indexOf($scope.component) < 0; }; @@ -113,4 +113,4 @@ angular.module('faradayApp') // if(navigator.userAgent.toLowerCase().indexOf('iceweasel') > -1) { // $scope.isIceweasel = "Your browser is not supported, please use Firefox or Chrome"; // } - }]); \ No newline at end of file + }]); From cf3fe9a5932fe9e8acbe3665457286e86bfab243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 8 Aug 2017 18:36:31 -0300 Subject: [PATCH 0060/1506] Login with username instead of with email Also change redirect URLs of login and logout views (to make it easier to test) --- server/app.py | 3 +++ server/models.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/server/app.py b/server/app.py index 2c6ce11b314..c97d153f81d 100644 --- a/server/app.py +++ b/server/app.py @@ -22,6 +22,9 @@ def create_app(db_connection_string=None, testing=None): app.config['SECRET_KEY'] = 'supersecret' app.config['SECURITY_PASSWORD_SINGLE_HASH'] = True app.config['WTF_CSRF_ENABLED'] = False + app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ['username'] + app.config['SECURITY_POST_LOGIN_VIEW'] = '/_api/session' + app.config['SECURITY_POST_LOGOUT_VIEW'] = '/_api/login' from server.models import db db.init_app(app) diff --git a/server/models.py b/server/models.py index dfa875c41b4..1d16c096718 100644 --- a/server/models.py +++ b/server/models.py @@ -297,7 +297,7 @@ def role(self): def get_security_payload(self): return { - "username": self.email, + "username": self.username, "role": self.role, "roles": [role.name for role in self.roles], # Deprectated "name": self.email From 0040baa5015c841609bc6770329990339cca2597 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 9 Aug 2017 18:29:28 -0300 Subject: [PATCH 0061/1506] Add unit test for vuln dao --- test_cases/dao/__init__.py | 0 test_cases/dao/vuln.py | 42 ++++++++++++++++++++++++++++++++++++++ test_cases/factories.py | 19 +++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 test_cases/dao/__init__.py create mode 100644 test_cases/dao/vuln.py create mode 100644 test_cases/factories.py diff --git a/test_cases/dao/__init__.py b/test_cases/dao/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test_cases/dao/vuln.py b/test_cases/dao/vuln.py new file mode 100644 index 00000000000..3bec60f253f --- /dev/null +++ b/test_cases/dao/vuln.py @@ -0,0 +1,42 @@ +import string +import random +import unittest +from server.models import Vulnerability +from server.database import Workspace + +from server.database import initialize, get_manager +from server.dao.vuln import VulnerabilityDAO + +initialize() + + +class VulnerabilityDAOTestCases(unittest.TestCase): + + def setUp(self): + self.manager = get_manager() + self.workspace_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(15)) + + self.manager.create_workspace({'name': self.workspace_name}) + + def tearDown(self): + self.manager.delete_workspace({'name': self.workspace_name}) + + def _new_vuln(self, vuln_type): + new_vuln = Vulnerability() + new_vuln.name = 'Test vuln' + new_vuln.description = 'Test description' + new_vuln.vuln_type = vuln_type + self.manager.get_workspace(self.workspace_name).session.add(new_vuln) + self.manager.get_workspace(self.workspace_name).session.commit() + return new_vuln + + def test_count(self): + vuln_dao = VulnerabilityDAO(self.workspace_name) + res = vuln_dao.count() + expected = {'total_count': 0, 'web_vuln_count': 0, 'vuln_count': 0} + self.assertEquals(expected, res) + self._new_vuln('Vulnerability') + self._new_vuln('VulnerabilityWeb') + res = vuln_dao.count() + expected = {'total_count': 2, 'web_vuln_count': 1, 'vuln_count': 1} + self.assertEquals(expected, res) diff --git a/test_cases/factories.py b/test_cases/factories.py new file mode 100644 index 00000000000..f38b81ab963 --- /dev/null +++ b/test_cases/factories.py @@ -0,0 +1,19 @@ +import factory +from pytest_factoryboy import register +from server.models import Vulnerability, Workspace + + +class VulnerabilityFactory(factory.Factory): + + class Meta: + model = Vulnerability + + +class WorkspaceFactory(factory.Factory): + + class Meta: + model = Workspace + + +register(VulnerabilityFactory) +register(WorkspaceFactory) From ea38b9de824949ed56f35573ec6c7a0e1c602962 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 9 Aug 2017 18:30:59 -0300 Subject: [PATCH 0062/1506] Update unit test code --- test_cases/dao/vuln.py | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/test_cases/dao/vuln.py b/test_cases/dao/vuln.py index 3bec60f253f..13cdd735b61 100644 --- a/test_cases/dao/vuln.py +++ b/test_cases/dao/vuln.py @@ -1,42 +1,37 @@ +import os +import sys +sys.path.append(os.path.abspath(os.getcwd())) import string import random import unittest -from server.models import Vulnerability -from server.database import Workspace +from server.web import app +from server.models import Vulnerability, Workspace -from server.database import initialize, get_manager from server.dao.vuln import VulnerabilityDAO - -initialize() +from test_cases.factories import WorkspaceFactory, VulnerabilityFactory class VulnerabilityDAOTestCases(unittest.TestCase): def setUp(self): - self.manager = get_manager() self.workspace_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(15)) - - self.manager.create_workspace({'name': self.workspace_name}) - - def tearDown(self): - self.manager.delete_workspace({'name': self.workspace_name}) + self.workspace = WorkspaceFactory.build() def _new_vuln(self, vuln_type): - new_vuln = Vulnerability() + new_vuln = VulnerabilityFactory.build() new_vuln.name = 'Test vuln' new_vuln.description = 'Test description' new_vuln.vuln_type = vuln_type - self.manager.get_workspace(self.workspace_name).session.add(new_vuln) - self.manager.get_workspace(self.workspace_name).session.commit() return new_vuln def test_count(self): - vuln_dao = VulnerabilityDAO(self.workspace_name) - res = vuln_dao.count() - expected = {'total_count': 0, 'web_vuln_count': 0, 'vuln_count': 0} - self.assertEquals(expected, res) - self._new_vuln('Vulnerability') - self._new_vuln('VulnerabilityWeb') - res = vuln_dao.count() - expected = {'total_count': 2, 'web_vuln_count': 1, 'vuln_count': 1} - self.assertEquals(expected, res) + with app.app_context(): + vuln_dao = VulnerabilityDAO(self.workspace_name) + res = vuln_dao.count() + expected = {'total_count': 0, 'web_vuln_count': 0, 'vuln_count': 0} + self.assertEquals(expected, res) + self._new_vuln('Vulnerability') + self._new_vuln('VulnerabilityWeb') + res = vuln_dao.count() + expected = {'total_count': 2, 'web_vuln_count': 1, 'vuln_count': 1} + self.assertEquals(expected, res) From 9c9db71b33fd53751a99eb7a7239fd3e41991d93 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 9 Aug 2017 18:37:50 -0300 Subject: [PATCH 0063/1506] Set the version of flask security --- requirements_server.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_server.txt b/requirements_server.txt index e82eec74467..5ad45c1a9f1 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -8,6 +8,6 @@ sqlalchemy>=1.0.12 pyopenssl>=16.0.0 service_identity>=16.0.0 pyasn1-modules -flask-security +Flask-Security==3.0.0 bcrypt Flask-SQLAlchemy==2.2 From 8ece5f9fd0c38f5fdc3e3920584097310ba06172 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 10 Aug 2017 15:13:59 -0300 Subject: [PATCH 0064/1506] Update unit tests to use workspace db model --- server/dao/base.py | 7 ++++-- server/dao/vuln.py | 19 ++++++++++++---- test_cases/dao/vuln.py | 50 +++++++++++++++++++++++++++++------------ test_cases/factories.py | 16 ++++++++++--- 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/server/dao/base.py b/server/dao/base.py index 0efe10d31f0..9cd931953b2 100644 --- a/server/dao/base.py +++ b/server/dao/base.py @@ -5,7 +5,7 @@ import server.database import server.utils.logger -from server.models import db, EntityMetadata +from server.models import db, Workspace, EntityMetadata class FaradayDAO(object): @@ -16,10 +16,13 @@ def __init__(self, workspace): self._logger = server.utils.logger.get_logger(self) self._session = db.session self._couchdb = None + self.workspace = workspace + if not getattr(workspace, 'name', None): + self.workspace = db.session.query(Workspace).filter_by(name=workspace) def get_all(self): self.__check_valid_operation() - return self._session.query(self.MAPPED_ENTITY).all() + return self._session.query(self.MAPPED_ENTITY).filter_by(workspace=self.workspace) def __check_valid_operation(self): if self.MAPPED_ENTITY is None: diff --git a/server/dao/vuln.py b/server/dao/vuln.py index a2376ba2c29..a55bd012607 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -5,12 +5,23 @@ import json from server.dao.base import FaradayDAO -from server.utils.database import paginate, sort_results, apply_search_filter, get_count +from server.utils.database import ( + paginate, + sort_results, + apply_search_filter, + get_count +) from sqlalchemy import case from sqlalchemy.sql import func from sqlalchemy.orm.query import Bundle -from server.models import Host, Interface, Service, Vulnerability, EntityMetadata +from server.models import ( + Host, + Interface, + Service, + Vulnerability, + EntityMetadata +) class VulnerabilityDAO(FaradayDAO): @@ -97,8 +108,8 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde .outerjoin(EntityMetadata, EntityMetadata.id == Vulnerability.entity_metadata_id)\ .outerjoin(Service, Service.id == Vulnerability.service_id)\ .outerjoin(Host, Host.id == Vulnerability.host_id)\ - .join(Interface, Interface.host_id == Host.id) - + .join(Interface, Interface.host_id == Host.id)\ + .filter_by(workspace=self.workspace) # Apply pagination, sorting and filtering options to the query query = self.__specialized_sort(query, order_by, order_dir) query = apply_search_filter(query, self.COLUMNS_MAP, search, vuln_filter, self.STRICT_FILTERING) diff --git a/test_cases/dao/vuln.py b/test_cases/dao/vuln.py index 13cdd735b61..718ed2ad4cf 100644 --- a/test_cases/dao/vuln.py +++ b/test_cases/dao/vuln.py @@ -5,7 +5,7 @@ import random import unittest from server.web import app -from server.models import Vulnerability, Workspace +from server.models import db, Vulnerability, Workspace from server.dao.vuln import VulnerabilityDAO from test_cases.factories import WorkspaceFactory, VulnerabilityFactory @@ -14,24 +14,46 @@ class VulnerabilityDAOTestCases(unittest.TestCase): def setUp(self): - self.workspace_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(15)) - self.workspace = WorkspaceFactory.build() + with app.app_context(): + db.create_all() + self.workspace = WorkspaceFactory.build() + db.session.commit() - def _new_vuln(self, vuln_type): - new_vuln = VulnerabilityFactory.build() - new_vuln.name = 'Test vuln' - new_vuln.description = 'Test description' - new_vuln.vuln_type = vuln_type - return new_vuln + def tearDown(self): + with app.app_context(): + db.drop_all() - def test_count(self): + def test_vulnerability_count_per_workspace_is_filtered(self): + """ + Verifies that the dao return the correct count from each workspace + """ with app.app_context(): - vuln_dao = VulnerabilityDAO(self.workspace_name) + another_workspace = WorkspaceFactory.build() + vuln_dao = VulnerabilityDAO(self.workspace) + another_vuln_dao = VulnerabilityDAO(another_workspace) + vuln_1 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=self.workspace) + vuln_2 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=another_workspace) + db.session.add(vuln_1) + db.session.add(vuln_2) + db.session.commit() + ws_count = vuln_dao.count() + another_ws_count = another_vuln_dao.count() + ws_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} + another_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} + assert ws_count == ws_expected + assert another_ws_count == another_expected + + def test_count_by_type(self): + with app.app_context(): + vuln_dao = VulnerabilityDAO(self.workspace) res = vuln_dao.count() expected = {'total_count': 0, 'web_vuln_count': 0, 'vuln_count': 0} self.assertEquals(expected, res) - self._new_vuln('Vulnerability') - self._new_vuln('VulnerabilityWeb') + vuln = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=self.workspace) + vuln_web = VulnerabilityFactory.build(vuln_type='VulnerabilityWeb', workspace=self.workspace) + db.session.add(vuln) + db.session.add(vuln_web) + db.session.commit() res = vuln_dao.count() expected = {'total_count': 2, 'web_vuln_count': 1, 'vuln_count': 1} - self.assertEquals(expected, res) + assert expected == res diff --git a/test_cases/factories.py b/test_cases/factories.py index f38b81ab963..a8f05010d7c 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -1,18 +1,28 @@ import factory +from factory.fuzzy import FuzzyText from pytest_factoryboy import register -from server.models import Vulnerability, Workspace +from server.models import db, Vulnerability, Workspace -class VulnerabilityFactory(factory.Factory): +class VulnerabilityFactory(factory.alchemy.SQLAlchemyModelFactory): + + id = factory.Sequence(lambda n: n) + name = FuzzyText() + description = FuzzyText() class Meta: model = Vulnerability + sqlalchemy_session = db.session + +class WorkspaceFactory(factory.alchemy.SQLAlchemyModelFactory): -class WorkspaceFactory(factory.Factory): + id = factory.Sequence(lambda n: n) + name = FuzzyText() class Meta: model = Workspace + sqlalchemy_session = db.session register(VulnerabilityFactory) From 21300604683f2ce6d4ad58d11a466c0b5de79170 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 10 Aug 2017 15:14:20 -0300 Subject: [PATCH 0065/1506] Fix minor PEP8 --- server/dao/base.py | 1 - server/dao/vuln.py | 18 ++++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/server/dao/base.py b/server/dao/base.py index 9cd931953b2..1cbf0bf7fe8 100644 --- a/server/dao/base.py +++ b/server/dao/base.py @@ -38,4 +38,3 @@ def get_by_couchdb_id(self, couchdb_id): def save(self, obj): self._session.add(obj) self._session.commit() - diff --git a/server/dao/vuln.py b/server/dao/vuln.py index a55bd012607..2ff57162471 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -29,7 +29,7 @@ class VulnerabilityDAO(FaradayDAO): COLUMNS_MAP = { "couchid": [EntityMetadata.couchdb_id], "id": [Vulnerability.id], - "date": [EntityMetadata.create_time], # TODO: fix search for this field + "date": [EntityMetadata.create_time], # TODO: fix search for this field "confirmed": [Vulnerability.confirmed], "name": [Vulnerability.name], "severity": [Vulnerability.severity], @@ -83,7 +83,7 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde # Instead of using SQLAlchemy ORM facilities to fetch rows, we bundle involved columns for # organizational and MAINLY performance reasons. Doing it this way, we improve retrieving # times from large workspaces almost 2x. - vuln_bundle = Bundle('vuln', Vulnerability.id.label('server_id'),Vulnerability.name.label('v_name'),\ + vuln_bundle = Bundle('vuln', Vulnerability.id.label('server_id'), Vulnerability.name.label('v_name'),\ Vulnerability.confirmed, Vulnerability.data,\ Vulnerability.description, Vulnerability.easeofresolution, Vulnerability.impact_accountability,\ Vulnerability.impact_availability, Vulnerability.impact_confidentiality, Vulnerability.impact_integrity,\ @@ -130,12 +130,12 @@ def __specialized_sort(self, query, order_by, order_dir): # instead of a lexicographycally one column_map = { 'severity': [case( - { 'unclassified': 0, + {'unclassified': 0, 'info': 1, 'low': 2, 'med': 3, 'high': 4, - 'critical': 5 }, + 'critical': 5}, value=Vulnerability.severity )] } @@ -147,6 +147,7 @@ def __specialized_sort(self, query, order_by, order_dir): def __get_vuln_data(self, vuln, service, host, hostnames): def get_own_id(couchdb_id): return couchdb_id.split('.')[-1] + def get_parent_id(couchdb_id): return '.'.join(couchdb_id.split('.')[:-1]) @@ -207,14 +208,15 @@ def get_parent_id(couchdb_id): def count(self, group_by=None, search=None, vuln_filter={}): query = self._session.query(Vulnerability.vuln_type, func.count())\ + .filter_by(workspace=self.workspace)\ .group_by(Vulnerability.vuln_type) query = apply_search_filter(query, self.COLUMNS_MAP, search, vuln_filter) total_count = dict(query.all()) # Return total amount of services if no group-by field was provided - result_count = { 'total_count': sum(total_count.values()), - 'web_vuln_count': total_count.get('VulnerabilityWeb', 0), - 'vuln_count': total_count.get('Vulnerability', 0), } + result_count = {'total_count': sum(total_count.values()), + 'web_vuln_count': total_count.get('VulnerabilityWeb', 0), + 'vuln_count': total_count.get('Vulnerability', 0)} if group_by is None: return result_count @@ -234,7 +236,7 @@ def count(self, group_by=None, search=None, vuln_filter={}): query = apply_search_filter(query, self.COLUMNS_MAP, search, vuln_filter, self.STRICT_FILTERING) result = query.all() - result_count['groups'] = [ { group_by: value[1], 'count': count } for value, count in result ] + result_count['groups'] = [{group_by: value[1], 'count': count} for value, count in result] return result_count From b8b33a6b57639cee8591ab0ddb2992287ffe5bf3 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 10 Aug 2017 16:57:14 -0300 Subject: [PATCH 0066/1506] Fix vuln dao list. update factories --- server/dao/vuln.py | 4 +-- test_cases/dao/vuln.py | 8 ++++- test_cases/factories.py | 72 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/server/dao/vuln.py b/server/dao/vuln.py index 2ff57162471..a53f1829ff3 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -109,7 +109,8 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde .outerjoin(Service, Service.id == Vulnerability.service_id)\ .outerjoin(Host, Host.id == Vulnerability.host_id)\ .join(Interface, Interface.host_id == Host.id)\ - .filter_by(workspace=self.workspace) + .filter(Vulnerability.workspace == self.workspace) + # Apply pagination, sorting and filtering options to the query query = self.__specialized_sort(query, order_by, order_dir) query = apply_search_filter(query, self.COLUMNS_MAP, search, vuln_filter, self.STRICT_FILTERING) @@ -239,4 +240,3 @@ def count(self, group_by=None, search=None, vuln_filter={}): result_count['groups'] = [{group_by: value[1], 'count': count} for value, count in result] return result_count - diff --git a/test_cases/dao/vuln.py b/test_cases/dao/vuln.py index 718ed2ad4cf..ea068b60c45 100644 --- a/test_cases/dao/vuln.py +++ b/test_cases/dao/vuln.py @@ -23,7 +23,7 @@ def tearDown(self): with app.app_context(): db.drop_all() - def test_vulnerability_count_per_workspace_is_filtered(self): + def test_vulnerability_count_and_list_per_workspace_is_filtered(self): """ Verifies that the dao return the correct count from each workspace """ @@ -42,6 +42,12 @@ def test_vulnerability_count_per_workspace_is_filtered(self): another_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} assert ws_count == ws_expected assert another_ws_count == another_expected + ws_list = vuln_dao.list() + assert vuln_1 in ws_list['vulnerabilities'] + another_ws_list = another_vuln_dao.list() + assert vuln_2 in another_ws_list['vulnerabilities'] + + def test_count_by_type(self): with app.app_context(): diff --git a/test_cases/factories.py b/test_cases/factories.py index a8f05010d7c..5ef18aca1a5 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -1,29 +1,85 @@ import factory -from factory.fuzzy import FuzzyText +from factory.fuzzy import ( + FuzzyText, + FuzzyChoice +) from pytest_factoryboy import register -from server.models import db, Vulnerability, Workspace +from server.models import ( + db, + Host, + Service, + Interface, + Workspace, + Vulnerability, + EntityMetadata, +) -class VulnerabilityFactory(factory.alchemy.SQLAlchemyModelFactory): +class FaradayFactory(factory.alchemy.SQLAlchemyModelFactory): id = factory.Sequence(lambda n: n) + + +class WorkspaceFactory(FaradayFactory): + + name = FuzzyText() + + class Meta: + model = Workspace + sqlalchemy_session = db.session + + +class HostFactory(FaradayFactory): name = FuzzyText() description = FuzzyText() + os = FuzzyChoice(['Linux', 'Windows', 'OSX', 'Android', 'iOS']) class Meta: - model = Vulnerability + model = Host sqlalchemy_session = db.session -class WorkspaceFactory(factory.alchemy.SQLAlchemyModelFactory): +class EntityMetadataFactory(FaradayFactory): - id = factory.Sequence(lambda n: n) + class Meta: + model = EntityMetadata + sqlalchemy_session = db.session + + +class InterfaceFactory(FaradayFactory): + + class Meta: + model = Interface + sqlalchemy_session = db.session + + +class ServiceFactory(FaradayFactory): name = FuzzyText() + description = FuzzyText() + ports = FuzzyChoice(['443', '80', '22']) class Meta: - model = Workspace + model = Service + sqlalchemy_session = db.session + + +class VulnerabilityFactory(FaradayFactory): + + name = FuzzyText() + description = FuzzyText() + host = factory.SubFactory(HostFactory) + entity_metadata = factory.SubFactory(EntityMetadataFactory) + service = factory.SubFactory(ServiceFactory) + workspace = factory.SubFactory(WorkspaceFactory) + vuln_type = FuzzyChoice(['Vulnerability', 'VulnerabilityWeb']) + + class Meta: + model = Vulnerability sqlalchemy_session = db.session -register(VulnerabilityFactory) register(WorkspaceFactory) +register(HostFactory) +register(ServiceFactory) +register(InterfaceFactory) +register(VulnerabilityFactory) From d59054c409cbd221112a0d80f98e5f19e8ed2865 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 11 Aug 2017 19:03:10 -0300 Subject: [PATCH 0067/1506] Fix vuln dao Updated the unit test and factories. Obs: Interface will be removed in future. The real issue here was that factories didn't produce the correct relationship in the model and the join with host <-> interface was not possible --- server/dao/vuln.py | 1 - test_cases/dao/vuln.py | 5 +++-- test_cases/factories.py | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/server/dao/vuln.py b/server/dao/vuln.py index a53f1829ff3..5f512fbae51 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -108,7 +108,6 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde .outerjoin(EntityMetadata, EntityMetadata.id == Vulnerability.entity_metadata_id)\ .outerjoin(Service, Service.id == Vulnerability.service_id)\ .outerjoin(Host, Host.id == Vulnerability.host_id)\ - .join(Interface, Interface.host_id == Host.id)\ .filter(Vulnerability.workspace == self.workspace) # Apply pagination, sorting and filtering options to the query diff --git a/test_cases/dao/vuln.py b/test_cases/dao/vuln.py index ea068b60c45..c944ad527e8 100644 --- a/test_cases/dao/vuln.py +++ b/test_cases/dao/vuln.py @@ -43,9 +43,10 @@ def test_vulnerability_count_and_list_per_workspace_is_filtered(self): assert ws_count == ws_expected assert another_ws_count == another_expected ws_list = vuln_dao.list() - assert vuln_1 in ws_list['vulnerabilities'] + + assert vuln_1.id == ws_list['vulnerabilities'][0]['_id'] another_ws_list = another_vuln_dao.list() - assert vuln_2 in another_ws_list['vulnerabilities'] + assert vuln_2.id == another_ws_list['vulnerabilities'][0]['_id'] diff --git a/test_cases/factories.py b/test_cases/factories.py index 5ef18aca1a5..e469b075c76 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -40,6 +40,7 @@ class Meta: class EntityMetadataFactory(FaradayFactory): + couchdb_id = factory.Sequence(lambda n: '{0}.1.2'.format(n)) class Meta: model = EntityMetadata @@ -47,6 +48,10 @@ class Meta: class InterfaceFactory(FaradayFactory): + name = FuzzyText() + description = FuzzyText() + mac = FuzzyText() + host = factory.SubFactory(HostFactory) class Meta: model = Interface @@ -57,6 +62,8 @@ class ServiceFactory(FaradayFactory): name = FuzzyText() description = FuzzyText() ports = FuzzyChoice(['443', '80', '22']) + interface = factory.SubFactory(InterfaceFactory) + host = factory.SubFactory(HostFactory) class Meta: model = Service @@ -72,6 +79,10 @@ class VulnerabilityFactory(FaradayFactory): service = factory.SubFactory(ServiceFactory) workspace = factory.SubFactory(WorkspaceFactory) vuln_type = FuzzyChoice(['Vulnerability', 'VulnerabilityWeb']) + attachments = '[]' + policyviolations = '[]' + refs = '[]' + class Meta: model = Vulnerability From 36b8cdc533155709459d4a0d6ce4ca56946c81a0 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 14 Aug 2017 15:16:22 -0300 Subject: [PATCH 0068/1506] Fix Workspace import from CouchDB The workspaces were imported multiple times generating duplicated entries in the DB. --- server/importer.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/server/importer.py b/server/importer.py index 5b22ed11499..3080856b8cb 100644 --- a/server/importer.py +++ b/server/importer.py @@ -336,8 +336,7 @@ class WorkspaceImporter(object): @classmethod def update_from_document(cls, document): - workspace = Workspace() - workspace.name = document.get('name', None) + workspace, created = get_or_create(session, server.models.Workspace, name=document.get('name', None)) return workspace def add_relationships_from_dict(self, entity, entities): @@ -345,7 +344,6 @@ def add_relationships_from_dict(self, entity, entities): child_entity.workspace = entity - class FaradayEntityImporter(object): # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace', u'Interface'] @classmethod @@ -430,18 +428,11 @@ def _open_couchdb_conn(): def import_workspace_into_database(workspace_name, couchdb_server_conn): + workspace, created = get_or_create(session, server.models.Workspace, name=workspace_name) - try: - # import checks if the object exists. - # the import is idempotent - _import_from_couchdb(workspace, couchdb_server_conn) - session.commit() - except Exception as ex: - import traceback - traceback.print_exc() - logger.exception(ex) - session.rollback() - raise ex + + _import_from_couchdb(workspace, couchdb_server_conn) + session.commit() return created From 3af72830158b7246a35e9910066435f234eff440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 14 Aug 2017 16:07:45 -0300 Subject: [PATCH 0069/1506] Add __repr__ for User model To improve debugging and testing --- server/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/models.py b/server/models.py index 8e01a1eab76..eaa95170c2b 100644 --- a/server/models.py +++ b/server/models.py @@ -302,3 +302,7 @@ def get_security_payload(self): "roles": [role.name for role in self.roles], # Deprectated "name": self.email } + + def __repr__(self): + return '<%sUser: %s>' % ('LDAP ' if self.is_ldap else '', + self.username) From 07af63040d30ba64f63dd0ea9e55d499e85b2b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 14 Aug 2017 17:53:59 -0300 Subject: [PATCH 0070/1506] Refactor server tests to use pytest fixtures --- test_cases/conftest.py | 42 +++++++++++++++++++++++++++++++++++++++ test_cases/test_server.py | 39 +++++++++++++++++------------------- 2 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 test_cases/conftest.py diff --git a/test_cases/conftest.py b/test_cases/conftest.py new file mode 100644 index 00000000000..7e01cc81cc9 --- /dev/null +++ b/test_cases/conftest.py @@ -0,0 +1,42 @@ +import os +import sys +import tempfile +import pytest + +sys.path.append(os.path.abspath(os.getcwd())) +from server.app import create_app +from server.models import db + + +@pytest.fixture +def app(monkeypatch): + db_fd, db_name = tempfile.mkstemp() + db_path = 'sqlite:///' + db_name + app = create_app(db_connection_string=db_path, testing=True) + + # monkeypatch.setattr('flask.current_app', app) + # monkeypatch.setattr('flask_security.forms.current_app', app) + + with app.app_context(): + db.create_all() + yield app#.test_client() + os.close(db_fd) + os.unlink(db_name) + + +def create_user(app, username, email, password, **kwargs): + user = app.user_datastore.create_user(username=username, + email=email, + password=password, + **kwargs) + db.session.add(user) + db.session.commit() + return user + +@pytest.fixture +def user(app): + return create_user(app, 'test', 'user@test.com', 'password', is_ldap=False) + +@pytest.fixture +def ldap_user(app): + return create_user(app, 'ldap', 'ldap@test.com', 'password', is_ldap=True) diff --git a/test_cases/test_server.py b/test_cases/test_server.py index 8e305e1427e..a68276be96c 100644 --- a/test_cases/test_server.py +++ b/test_cases/test_server.py @@ -1,8 +1,10 @@ import os import sys -sys.path.append(os.path.abspath(os.getcwd())) import unittest import tempfile +import pytest + +sys.path.append(os.path.abspath(os.getcwd())) from server.app import create_app from flask_security import Security, SQLAlchemyUserDatastore from server.models import db, User, Role @@ -14,19 +16,13 @@ def endpoint(): class BaseAPITestCase(unittest.TestCase): - ENDPOINT_ROUTE = '/' - - def setUp(self): - self.db_fd, self.db_name = tempfile.mkstemp() - db_path = 'sqlite:///' + self.db_name - self.flask_app = create_app(db_connection_string=db_path, testing=True) - - self.app = self.flask_app.test_client() - self.flask_app.route(self.ENDPOINT_ROUTE)(endpoint) - def tearDown(self): - os.close(self.db_fd) - os.unlink(self.db_name) + @pytest.fixture(autouse=True) + def load_app(self, app): + """Use this to avoid having to use an app argument to every + function""" + self.flask_app = app + self.app = app.test_client() def login_as(self, user): with self.app.session_transaction() as sess: @@ -41,14 +37,15 @@ class TestAuthentication(BaseAPITestCase): """Tests related to allow/dissallow access depending of whether the user is logged in or not""" - def setUp(self): - super(TestAuthentication, self).setUp() - with self.flask_app.app_context(): - db.create_all() - self.user = self.flask_app.user_datastore.create_user( - email='user@test.net', password='password') - db.session.add(self.user) - db.session.commit() + ENDPOINT_ROUTE = '/' + + @pytest.fixture(autouse=True) + def load_user(self, user): + self.user = user + + @pytest.fixture(autouse=True) + def route_endpoint(self, app): + app.route(self.ENDPOINT_ROUTE)(endpoint) def test_403_when_getting_an_existent_view_and_not_logged(self): res = self.app.get('/') From 623df8ce00eb737079581abadb812d4799403635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 14 Aug 2017 17:54:37 -0300 Subject: [PATCH 0071/1506] Add is_ldap field to User model It will be used only on corporate versions but add it here to maintain the same schema between community and commercial versions --- server/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/models.py b/server/models.py index eaa95170c2b..f764b5c2497 100644 --- a/server/models.py +++ b/server/models.py @@ -275,6 +275,7 @@ class User(db.Model, UserMixin): email = Column(String(255), unique=True) username = Column(String(255)) password = Column(String(255)) + is_ldap = Column(Boolean(), nullable=False) last_login_at = Column(DateTime()) current_login_at = Column(DateTime()) last_login_ip = Column(String(100)) From de709cec5d0854ccbcde5c3320b8036744176701 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 14 Aug 2017 17:57:24 -0300 Subject: [PATCH 0072/1506] Update service dao to use workspace --- server/dao/service.py | 2 +- test_cases/dao/services.py | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 test_cases/dao/services.py diff --git a/server/dao/service.py b/server/dao/service.py index a720b306682..5e38bdde74a 100644 --- a/server/dao/service.py +++ b/server/dao/service.py @@ -90,7 +90,7 @@ def __get_service_data(self, service): 'vulns': service.vuln_count} def count(self, group_by=None): - total_count = self._session.query(func.count(Service.id)).scalar() + total_count = self._session.query(func.count(Service.id)).filter_by(workspace=self.workspace).scalar() # Return total amount of services if no group-by field was provided if group_by is None: diff --git a/test_cases/dao/services.py b/test_cases/dao/services.py new file mode 100644 index 00000000000..e1ef6dd9420 --- /dev/null +++ b/test_cases/dao/services.py @@ -0,0 +1,44 @@ +import os +import sys +sys.path.append(os.path.abspath(os.getcwd())) +import string +import random +import unittest +from server.web import app +from server.models import db, Service + +from server.dao.service import ServiceDAO +from test_cases.factories import WorkspaceFactory, ServiceFactory + + +class VulnerabilityDAOTestCases(unittest.TestCase): + + def setUp(self): + with app.app_context(): + db.create_all() + self.workspace = WorkspaceFactory.build() + db.session.commit() + + def tearDown(self): + with app.app_context(): + db.drop_all() + + def test_(self): + with app.app_context(): + workspace = WorkspaceFactory.build() + service_dao = ServiceDAO(workspace) + expected = {'total_count': 0} + res = service_dao.count() + assert expected == res + new_service = ServiceFactory.build(workspace=workspace) + db.session.add(new_service) + db.session.commit() + res = service_dao.count() + expected = {'total_count': 1} + assert expected == res + another_workspace = WorkspaceFactory.build() + another_service = ServiceFactory.build(workspace=another_workspace) + db.session.add(another_service) + db.session.commit() + res = service_dao.count() + assert expected == res From 9f2e696e5a652541899df8fb9edd659c5ea52e24 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 14 Aug 2017 17:57:39 -0300 Subject: [PATCH 0073/1506] Fix PEP8 warnings --- server/dao/service.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/server/dao/service.py b/server/dao/service.py index 5e38bdde74a..38bed5e796b 100644 --- a/server/dao/service.py +++ b/server/dao/service.py @@ -7,9 +7,17 @@ from sqlalchemy.orm.query import Bundle from server.dao.base import FaradayDAO -from server.models import Host, Interface, Service, EntityMetadata, Vulnerability, Credential +from server.models import ( + Host, + Interface, + Service, + EntityMetadata, + Vulnerability, + Credential +) from server.utils.database import apply_search_filter + class ServiceDAO(FaradayDAO): MAPPED_ENTITY = Service COLUMNS_MAP = { @@ -94,7 +102,7 @@ def count(self, group_by=None): # Return total amount of services if no group-by field was provided if group_by is None: - return { 'total_count': total_count } + return {'total_count': total_count} # Otherwise return the amount of services grouped by the field specified if group_by not in ServiceDAO.COLUMNS_MAP: @@ -107,7 +115,5 @@ def count(self, group_by=None): res = query.all() - return { 'total_count': total_count, - 'groups': [ { group_by: value, 'count': count } for value, count in res ] } - - + return {'total_count': total_count, + 'groups': [{group_by: value, 'count': count} for value, count in res]} From 27669b03214f052136b9d2584d4f9f8f36f9a877 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 14 Aug 2017 18:29:01 -0300 Subject: [PATCH 0074/1506] Update service dao list to use workspace filter --- server/dao/service.py | 1 + test_cases/dao/services.py | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/server/dao/service.py b/server/dao/service.py index 38bed5e796b..489927f851d 100644 --- a/server/dao/service.py +++ b/server/dao/service.py @@ -55,6 +55,7 @@ def list(self, service_filter={}): outerjoin(Credential, (Credential.service_id == Service.id) and (Credential.host_id == None)).\ outerjoin(Host, Host.id == Interface.host_id) + query = query.filter(Service.workspace == self.workspace) query = apply_search_filter(query, self.COLUMNS_MAP, None, service_filter, self.STRICT_FILTERING) # 'LIKE' for search services started by hostId.%.% diff --git a/test_cases/dao/services.py b/test_cases/dao/services.py index e1ef6dd9420..2d4e50fad1f 100644 --- a/test_cases/dao/services.py +++ b/test_cases/dao/services.py @@ -23,7 +23,33 @@ def tearDown(self): with app.app_context(): db.drop_all() - def test_(self): + def test_list_with_multiple_workspace(self): + with app.app_context(): + workspace = WorkspaceFactory.build() + service_dao = ServiceDAO(workspace) + expected = {'services': []} + + res = service_dao.list() + assert expected == res + + new_service = ServiceFactory.build(workspace=workspace) + db.session.add(new_service) + db.session.commit() + + res = service_dao.list() + expected = {'services': [{'value': {'status': None, 'protocol': None, 'description': new_service.description, '_rev': None, 'owned': None, 'owner': None, 'credentials': 0, 'name': new_service.name, 'version': None, '_id': None, 'ports': [int(new_service.ports)], 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}}, '_id': 2, 'id': None, 'key': None, 'vulns': 0}]} + + assert expected == res + + another_workspace = WorkspaceFactory.build() + another_service = ServiceFactory.build(workspace=another_workspace) + db.session.add(another_service) + db.session.commit() + res = service_dao.list() + assert len(res['services']) == 1 + assert expected == res + + def test_count_with_multiple_workspaces(self): with app.app_context(): workspace = WorkspaceFactory.build() service_dao = ServiceDAO(workspace) From 511d009c9c96ae7ad7c7ee6b2a301145574ecbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 14 Aug 2017 18:34:09 -0300 Subject: [PATCH 0075/1506] Continue refactor of server tests Add a common logged_user fixture that will be useful for future test cases, and refactor test_200_when_logged_in to use it. --- test_cases/conftest.py | 19 +++++++++++++++++++ test_cases/test_server.py | 40 +++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 7e01cc81cc9..05447fe15ae 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -23,6 +23,10 @@ def app(monkeypatch): os.close(db_fd) os.unlink(db_name) +@pytest.fixture +def test_client(app): + return app.test_client() + def create_user(app, username, email, password, **kwargs): user = app.user_datastore.create_user(username=username, @@ -33,10 +37,25 @@ def create_user(app, username, email, password, **kwargs): db.session.commit() return user + @pytest.fixture def user(app): return create_user(app, 'test', 'user@test.com', 'password', is_ldap=False) + @pytest.fixture def ldap_user(app): return create_user(app, 'ldap', 'ldap@test.com', 'password', is_ldap=True) + + +def login_as(test_client, user): + with test_client.session_transaction() as sess: + # Without this line the test breaks. Taken from + # http://pythonhosted.org/Flask-Testing/#testing-with-sqlalchemy + db.session.add(user) + sess['user_id'] = user.id + +@pytest.fixture +def logged_user(test_client, user): + login_as(test_client, user) + return user diff --git a/test_cases/test_server.py b/test_cases/test_server.py index a68276be96c..0d3994dce38 100644 --- a/test_cases/test_server.py +++ b/test_cases/test_server.py @@ -15,29 +15,15 @@ def endpoint(): return 'OK' -class BaseAPITestCase(unittest.TestCase): +class BaseAPITestCase: + ENDPOINT_ROUTE = '/' @pytest.fixture(autouse=True) - def load_app(self, app): + def load_app(self, app, test_client): """Use this to avoid having to use an app argument to every function""" self.flask_app = app - self.app = app.test_client() - - def login_as(self, user): - with self.app.session_transaction() as sess: - # Without this line the test breaks. Taken from - # http://pythonhosted.org/Flask-Testing/#testing-with-sqlalchemy - db.session.add(self.user) - - sess['user_id'] = user.id - - -class TestAuthentication(BaseAPITestCase): - """Tests related to allow/dissallow access depending of whether - the user is logged in or not""" - - ENDPOINT_ROUTE = '/' + self.app = test_client @pytest.fixture(autouse=True) def load_user(self, user): @@ -47,6 +33,11 @@ def load_user(self, user): def route_endpoint(self, app): app.route(self.ENDPOINT_ROUTE)(endpoint) + +class TestAuthentication(BaseAPITestCase, unittest.TestCase): + """Tests related to allow/dissallow access depending of whether + the user is logged in or not""" + def test_403_when_getting_an_existent_view_and_not_logged(self): res = self.app.get('/') self.assertEqual(res.status_code, 403) @@ -59,11 +50,6 @@ def test_403_when_accessing_a_non_existent_view_and_not_logged(self): res = self.app.post('/dfsdfsdd', 'data') self.assertEqual(res.status_code, 403) - def test_200_when_logged_in(self): - self.login_as(self.user) - res = self.app.get('/') - self.assertEqual(res.status_code, 200) - def test_200_when_not_logged_but_endpoint_is_public(self): endpoint.is_public = True res = self.app.get('/') @@ -87,5 +73,13 @@ def test_403_when_logged_user_is_deleted(self): self.assertEqual(res.status_code, 403) +class TestAuthenticationPytest(BaseAPITestCase): + + @pytest.mark.usefixtures('logged_user') + def test_200_when_logged_in(self, test_client): + res = test_client.get('/') + assert res.status_code == 200 + + if __name__ == '__main__': unittest.main() From e00315e4bc492468fdfc1f27ad170bd9182795ab Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 14 Aug 2017 19:04:28 -0300 Subject: [PATCH 0076/1506] Update flask sqlalchemt fixtures and change service test to use pytest fixtures --- server/app.py | 2 + test_cases/conftest.py | 61 ++++++++++++++++----- test_cases/dao/__init__.py | 1 + test_cases/dao/services.py | 105 ++++++++++++++++--------------------- 4 files changed, 97 insertions(+), 72 deletions(-) diff --git a/server/app.py b/server/app.py index c97d153f81d..ed28f00471f 100644 --- a/server/app.py +++ b/server/app.py @@ -25,6 +25,8 @@ def create_app(db_connection_string=None, testing=None): app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ['username'] app.config['SECURITY_POST_LOGIN_VIEW'] = '/_api/session' app.config['SECURITY_POST_LOGOUT_VIEW'] = '/_api/login' + if testing: + app.config['TESTING'] = testing from server.models import db db.init_app(app) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 7e01cc81cc9..4493fdf3dc3 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -1,6 +1,5 @@ import os import sys -import tempfile import pytest sys.path.append(os.path.abspath(os.getcwd())) @@ -8,20 +7,54 @@ from server.models import db -@pytest.fixture -def app(monkeypatch): - db_fd, db_name = tempfile.mkstemp() - db_path = 'sqlite:///' + db_name - app = create_app(db_connection_string=db_path, testing=True) +@pytest.fixture(scope='session') +def app(request): + # we use sqlite memory for tests + test_conn_string = 'sqlite://' + app = create_app(db_connection_string=test_conn_string, testing=True) + + # Establish an application context before running the tests. + ctx = app.app_context() + ctx.push() + + def teardown(): + ctx.pop() + + request.addfinalizer(teardown) + return app + + +@pytest.fixture(scope='session') +def database(app, request): + """Session-wide test database.""" + + def teardown(): + db.drop_all() + + db.app = app + db.create_all() - # monkeypatch.setattr('flask.current_app', app) - # monkeypatch.setattr('flask_security.forms.current_app', app) + request.addfinalizer(teardown) + return db - with app.app_context(): - db.create_all() - yield app#.test_client() - os.close(db_fd) - os.unlink(db_name) + +@pytest.fixture(scope='function') +def session(database, request): + connection = db.engine.connect() + transaction = connection.begin() + + options = {"bind": connection, 'binds': {}} + session = db.create_scoped_session(options=options) + + db.session = session + + def teardown(): + transaction.rollback() + connection.close() + session.remove() + + request.addfinalizer(teardown) + return session def create_user(app, username, email, password, **kwargs): @@ -33,10 +66,12 @@ def create_user(app, username, email, password, **kwargs): db.session.commit() return user + @pytest.fixture def user(app): return create_user(app, 'test', 'user@test.com', 'password', is_ldap=False) + @pytest.fixture def ldap_user(app): return create_user(app, 'ldap', 'ldap@test.com', 'password', is_ldap=True) diff --git a/test_cases/dao/__init__.py b/test_cases/dao/__init__.py index e69de29bb2d..8b137891791 100644 --- a/test_cases/dao/__init__.py +++ b/test_cases/dao/__init__.py @@ -0,0 +1 @@ + diff --git a/test_cases/dao/services.py b/test_cases/dao/services.py index 2d4e50fad1f..f2bfcffb467 100644 --- a/test_cases/dao/services.py +++ b/test_cases/dao/services.py @@ -4,67 +4,54 @@ import string import random import unittest -from server.web import app -from server.models import db, Service from server.dao.service import ServiceDAO from test_cases.factories import WorkspaceFactory, ServiceFactory -class VulnerabilityDAOTestCases(unittest.TestCase): - - def setUp(self): - with app.app_context(): - db.create_all() - self.workspace = WorkspaceFactory.build() - db.session.commit() - - def tearDown(self): - with app.app_context(): - db.drop_all() - - def test_list_with_multiple_workspace(self): - with app.app_context(): - workspace = WorkspaceFactory.build() - service_dao = ServiceDAO(workspace) - expected = {'services': []} - - res = service_dao.list() - assert expected == res - - new_service = ServiceFactory.build(workspace=workspace) - db.session.add(new_service) - db.session.commit() - - res = service_dao.list() - expected = {'services': [{'value': {'status': None, 'protocol': None, 'description': new_service.description, '_rev': None, 'owned': None, 'owner': None, 'credentials': 0, 'name': new_service.name, 'version': None, '_id': None, 'ports': [int(new_service.ports)], 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}}, '_id': 2, 'id': None, 'key': None, 'vulns': 0}]} - - assert expected == res - - another_workspace = WorkspaceFactory.build() - another_service = ServiceFactory.build(workspace=another_workspace) - db.session.add(another_service) - db.session.commit() - res = service_dao.list() - assert len(res['services']) == 1 - assert expected == res - - def test_count_with_multiple_workspaces(self): - with app.app_context(): - workspace = WorkspaceFactory.build() - service_dao = ServiceDAO(workspace) - expected = {'total_count': 0} - res = service_dao.count() - assert expected == res - new_service = ServiceFactory.build(workspace=workspace) - db.session.add(new_service) - db.session.commit() - res = service_dao.count() - expected = {'total_count': 1} - assert expected == res - another_workspace = WorkspaceFactory.build() - another_service = ServiceFactory.build(workspace=another_workspace) - db.session.add(another_service) - db.session.commit() - res = service_dao.count() - assert expected == res +def test_list_with_multiple_workspace(app, session): + with app.app_context(): + workspace = WorkspaceFactory.build() + service_dao = ServiceDAO(workspace) + expected = {'services': []} + + res = service_dao.list() + assert expected == res + + new_service = ServiceFactory.build(workspace=workspace) + session.add(new_service) + session.commit() + + res = service_dao.list() + expected = {'services': [{'value': {'status': None, 'protocol': None, 'description': new_service.description, '_rev': None, 'owned': None, 'owner': None, 'credentials': 0, 'name': new_service.name, 'version': None, '_id': None, 'ports': [int(new_service.ports)], 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}}, '_id': new_service.id, 'id': None, 'key': None, 'vulns': 0}]} + + assert expected == res + + another_workspace = WorkspaceFactory.build() + another_service = ServiceFactory.build(workspace=another_workspace) + session.add(another_service) + session.commit() + res = service_dao.list() + assert len(res['services']) == 1 + assert expected == res + + +def test_count_with_multiple_workspaces(app, session): + with app.app_context(): + workspace = WorkspaceFactory.build() + service_dao = ServiceDAO(workspace) + expected = {'total_count': 0} + res = service_dao.count() + assert expected == res + new_service = ServiceFactory.build(workspace=workspace) + session.add(new_service) + session.commit() + res = service_dao.count() + expected = {'total_count': 1} + assert expected == res + another_workspace = WorkspaceFactory.build() + another_service = ServiceFactory.build(workspace=another_workspace) + session.add(another_service) + session.commit() + res = service_dao.count() + assert expected == res From c09308549fadf4eece7615eee86868c238a6c325 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 14 Aug 2017 19:11:25 -0300 Subject: [PATCH 0077/1506] Fix test_server tests --- test_cases/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index c7c7da869a1..4e2737c9145 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -72,12 +72,12 @@ def create_user(app, username, email, password, **kwargs): @pytest.fixture -def user(app): +def user(app, session): return create_user(app, 'test', 'user@test.com', 'password', is_ldap=False) @pytest.fixture -def ldap_user(app): +def ldap_user(app, session): return create_user(app, 'ldap', 'ldap@test.com', 'password', is_ldap=True) From ae1ff26ac633f3c56b5574e37739af1bdd732152 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 15 Aug 2017 11:52:41 -0300 Subject: [PATCH 0078/1506] Use workspace for list in dao --- server/dao/host.py | 1 + test_cases/dao/host.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 test_cases/dao/host.py diff --git a/server/dao/host.py b/server/dao/host.py index 9bc00ad72de..cf02bceba0e 100644 --- a/server/dao/host.py +++ b/server/dao/host.py @@ -63,6 +63,7 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde .outerjoin(Credential, (Credential.host_id == Host.id) & Credential.service_id == None)\ .group_by(Host.id) + query = query.filter(Host.workspace == self.workspace) # Apply pagination, sorting and filtering options to the query query = sort_results(query, self.COLUMNS_MAP, order_by, order_dir, default=Host.id) query = apply_search_filter(query, self.COLUMNS_MAP, search, host_filter, self.STRICT_FILTERING) diff --git a/test_cases/dao/host.py b/test_cases/dao/host.py new file mode 100644 index 00000000000..0fa43105650 --- /dev/null +++ b/test_cases/dao/host.py @@ -0,0 +1,36 @@ +import os +import sys +sys.path.append(os.path.abspath(os.getcwd())) +import string +import random +import unittest + +from server.dao.host import HostDAO +from test_cases.factories import WorkspaceFactory, HostFactory + + +def test_list_with_multiple_workspace(app, session): + with app.app_context(): + workspace = WorkspaceFactory.build() + + hosts_dao = HostDAO(workspace) + expected = {'rows': [], 'total_rows': 0} + + res = hosts_dao.list() + assert expected == res + + host = HostFactory.build(workspace=workspace) + session.add(host) + session.commit() + expected = {'rows': [{'value': {'description': host.description, 'default_gateway': [None, None], 'vulns': 0, '_rev': None, 'owned': None, 'owner': None, 'services': 0, 'credentials': 0, 'name': host.name, '_id': None, 'os': host.os, 'interfaces': [], 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}}, '_id': host.id, 'id': None, 'key': None}], 'total_rows': 1} + res = hosts_dao.list() + assert expected == res + + another_workspace = WorkspaceFactory.build() + + another_host = HostFactory.build(workspace=another_workspace) + session.add(another_host) + session.commit() + + res = hosts_dao.list() + assert expected == res From 4cab02b88f92ed3d8e4840522e752e3c3298694c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 15 Aug 2017 11:52:53 -0300 Subject: [PATCH 0079/1506] Some code style improvements --- server/dao/host.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/server/dao/host.py b/server/dao/host.py index cf02bceba0e..ef47af78e36 100644 --- a/server/dao/host.py +++ b/server/dao/host.py @@ -3,12 +3,24 @@ # See the file "doc/LICENSE" for the license information from server.dao.base import FaradayDAO -from server.utils.database import paginate, sort_results, apply_search_filter, get_count +from server.utils.database import ( + paginate, + sort_results, + apply_search_filter, + get_count +) from sqlalchemy import distinct from sqlalchemy.orm.query import Bundle from sqlalchemy.sql import func -from server.models import Host, Interface, Service, Vulnerability, EntityMetadata, Credential +from server.models import ( + Host, + Interface, + Service, + Vulnerability, + EntityMetadata, + Credential +) class HostDAO(FaradayDAO): @@ -126,4 +138,4 @@ def count(self, group_by=None): result_count["groups"] = [{group_by: value, "count": count} for value, count in res] - return result_count \ No newline at end of file + return result_count From be568d839e33fc90927403feae29fe3868df8ce8 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 15 Aug 2017 11:56:31 -0300 Subject: [PATCH 0080/1506] Fix hosts dao count to use workspace --- server/dao/host.py | 2 +- test_cases/dao/host.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/server/dao/host.py b/server/dao/host.py index ef47af78e36..cc56eb47487 100644 --- a/server/dao/host.py +++ b/server/dao/host.py @@ -120,7 +120,7 @@ def __get_host_data(self, host): }} def count(self, group_by=None): - total_count = self._session.query(func.count(Host.id)).scalar() + total_count = self._session.query(func.count(Host.id)).filter_by(workspace=self.workspace).scalar() # Return total amount of services if no group-by field was provided result_count = {"total_count": total_count} diff --git a/test_cases/dao/host.py b/test_cases/dao/host.py index 0fa43105650..0258a6f60f3 100644 --- a/test_cases/dao/host.py +++ b/test_cases/dao/host.py @@ -34,3 +34,29 @@ def test_list_with_multiple_workspace(app, session): res = hosts_dao.list() assert expected == res + + +def test_count_with_multiple_workspace(app, session): + with app.app_context(): + workspace = WorkspaceFactory.build() + + hosts_dao = HostDAO(workspace) + expected = {'total_count': 0} + + res = hosts_dao.count() + assert expected == res + + host = HostFactory.build(workspace=workspace) + session.add(host) + session.commit() + + another_workspace = WorkspaceFactory.build() + + another_host = HostFactory.build(workspace=another_workspace) + session.add(another_host) + session.commit() + + expected = {'total_count': 1} + + res = hosts_dao.count() + assert expected == res From 6ca39aa4a997e4b9ec2437a104578699ced18da9 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 15 Aug 2017 12:21:21 -0300 Subject: [PATCH 0081/1506] Fix credential dao to use workspace --- server/dao/credential.py | 15 +++++++++----- test_cases/dao/credential.py | 38 ++++++++++++++++++++++++++++++++++++ test_cases/factories.py | 11 ++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 test_cases/dao/credential.py diff --git a/server/dao/credential.py b/server/dao/credential.py index 2688e5b75a9..3d8c123dccd 100644 --- a/server/dao/credential.py +++ b/server/dao/credential.py @@ -34,15 +34,20 @@ def list(self, search=None, cred_filter={}): return result def __query_database(self, search=None, cred_filter={}): - creds_bundle = Bundle('cred', Credential.username, Credential.password, Credential.name, - Credential.description, Credential.owned, EntityMetadata.couchdb_id,\ - EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ - EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ - EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id) + creds_bundle = Bundle( + 'cred', Credential.username, Credential.password, + Credential.name, EntityMetadata.couchdb_id, + Credential.description, Credential.owned, + EntityMetadata.revision, EntityMetadata.update_time, + EntityMetadata.update_user, EntityMetadata.create_time, + EntityMetadata.update_action, EntityMetadata.creator, + EntityMetadata.update_controller_action, EntityMetadata.owner, + EntityMetadata.command_id) query = self._session.query(creds_bundle)\ .outerjoin(EntityMetadata, EntityMetadata.id == Credential.entity_metadata_id) + query = query.filter(Credential.workspace == self.workspace) # Apply filtering options to the query query = apply_search_filter(query, self.COLUMNS_MAP, search, cred_filter, self.STRICT_FILTERING) diff --git a/test_cases/dao/credential.py b/test_cases/dao/credential.py new file mode 100644 index 00000000000..d5463e697be --- /dev/null +++ b/test_cases/dao/credential.py @@ -0,0 +1,38 @@ +import os +import sys +sys.path.append(os.path.abspath(os.getcwd())) +import string +import random +import unittest + +from server.dao.credential import CredentialDAO +from test_cases.factories import WorkspaceFactory, CredentialFactory + + +def test_list_with_multiple_workspace(app, session): + with app.app_context(): + workspace = WorkspaceFactory.build() + + credentials_dao = CredentialDAO(workspace) + expected = {'rows': []} + + res = credentials_dao.list() + assert expected == res + + credential = CredentialFactory.build(workspace=workspace) + session.add(credential) + session.commit() + expected = {} + res = credentials_dao.list() + expected = {'rows': [{'value': {'username': credential.username, 'password': credential.password, 'description': None, 'couchid': None, 'owner': None, '_id': None, 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}, 'owned': None, 'name': None}, 'id': None, 'key': None}]} + + assert expected == res + + another_workspace = WorkspaceFactory.build() + + another_credential = CredentialFactory.build(workspace=another_workspace) + session.add(another_credential) + session.commit() + + res = credentials_dao.list() + assert expected == res diff --git a/test_cases/factories.py b/test_cases/factories.py index e469b075c76..a94e4c56ac2 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -10,6 +10,7 @@ Service, Interface, Workspace, + Credential, Vulnerability, EntityMetadata, ) @@ -83,14 +84,22 @@ class VulnerabilityFactory(FaradayFactory): policyviolations = '[]' refs = '[]' - class Meta: model = Vulnerability sqlalchemy_session = db.session +class CredentialFactory(FaradayFactory): + username = FuzzyText() + password = FuzzyText() + + class Meta: + model = Credential + sqlalchemy_session = db.session + register(WorkspaceFactory) register(HostFactory) register(ServiceFactory) register(InterfaceFactory) register(VulnerabilityFactory) +register(CredentialFactory) From 78bb0ef87c138493e2d7de512e19e02d2a796356 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 15 Aug 2017 12:37:11 -0300 Subject: [PATCH 0082/1506] Fix command dao to use workspace --- server/dao/command.py | 3 ++- test_cases/dao/command.py | 37 +++++++++++++++++++++++++++++++++++++ test_cases/factories.py | 10 ++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 test_cases/dao/command.py diff --git a/server/dao/command.py b/server/dao/command.py index 8f74600db88..db56f030d40 100644 --- a/server/dao/command.py +++ b/server/dao/command.py @@ -42,6 +42,7 @@ def __query_database(self, search=None, page=0, page_size=0, command_filter={}): .outerjoin(EntityMetadata, EntityMetadata.id == Command.entity_metadata_id) # Apply filtering options to the query + query = query.filter(Command.workspace == self.workspace) query = apply_search_filter(query, self.COLUMNS_MAP, None, command_filter, self.STRICT_FILTERING) if page_size: @@ -62,6 +63,6 @@ def __get_command_data(self, command): "hostname": command.hostname, "command": command.command, "user": command.user, - "workspace": command.workspace, + "workspace": command.workspace_id, "duration": command.duration, "params": command.params}} diff --git a/test_cases/dao/command.py b/test_cases/dao/command.py new file mode 100644 index 00000000000..9ddab8fdde0 --- /dev/null +++ b/test_cases/dao/command.py @@ -0,0 +1,37 @@ +import os +import sys +sys.path.append(os.path.abspath(os.getcwd())) +import string +import random +import unittest + +from server.dao.command import CommandDAO +from test_cases.factories import WorkspaceFactory, CommandFactory + + +def test_list_with_multiple_workspace(app, session): + with app.app_context(): + workspace = WorkspaceFactory.build() + + commands_dao = CommandDAO(workspace) + expected = {'commands': []} + + res = commands_dao.list() + assert expected == res + + command = CommandFactory.build(workspace=workspace) + session.add(command) + session.commit() + expected = {'commands': [{'value': {'itime': None, 'command': command.command, 'user': None, 'workspace': 0, 'params': None, 'duration': None, 'ip': None, '_id': None, 'hostname': None}, 'id': None, 'key': None}]} + res = commands_dao.list() + assert expected == res + + another_workspace = WorkspaceFactory.build() + + another_command = CommandFactory.build(workspace=another_workspace) + session.add(another_command) + session.commit() + + res = commands_dao.list() + assert len(res['commands']) == 1 + assert expected == res diff --git a/test_cases/factories.py b/test_cases/factories.py index a94e4c56ac2..195ba120cc4 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -7,6 +7,7 @@ from server.models import ( db, Host, + Command, Service, Interface, Workspace, @@ -97,6 +98,15 @@ class Meta: model = Credential sqlalchemy_session = db.session + +class CommandFactory(FaradayFactory): + command = FuzzyText() + + class Meta: + model = Command + sqlalchemy_session = db.session + + register(WorkspaceFactory) register(HostFactory) register(ServiceFactory) From a20cf685b548a7e8ef0b9f62053be903470a600e Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 15 Aug 2017 12:37:25 -0300 Subject: [PATCH 0083/1506] Fix some pep8 warns --- server/dao/command.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/dao/command.py b/server/dao/command.py index db56f030d40..9ef1ec1ddd7 100644 --- a/server/dao/command.py +++ b/server/dao/command.py @@ -8,6 +8,7 @@ from server.models import Command, EntityMetadata from server.utils.database import apply_search_filter, paginate + class CommandDAO(FaradayDAO): MAPPED_ENTITY = Command COLUMNS_MAP = { @@ -18,7 +19,7 @@ class CommandDAO(FaradayDAO): def list(self, search=None, page=0, page_size=0, command_filter={}): results = self.__query_database(search, page, page_size, command_filter) - rows = [ self.__get_command_data(result.command) for result in results ] + rows = [self.__get_command_data(result.command) for result in results] result = { 'commands': rows @@ -33,8 +34,8 @@ def __query_database(self, search=None, page=0, page_size=0, command_filter={}): Command.hostname, Command.command, Command.user, - Command.workspace_id, Command.duration, + Command.workspace_id, Command.params, EntityMetadata.couchdb_id) From 095b0c17aa816aac17572bfbac72fd7ccfec374d Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 15 Aug 2017 18:26:57 -0300 Subject: [PATCH 0084/1506] update test to a more pytest friendly code --- test_cases/dao/vuln.py | 95 +++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/test_cases/dao/vuln.py b/test_cases/dao/vuln.py index c944ad527e8..eecdab185b0 100644 --- a/test_cases/dao/vuln.py +++ b/test_cases/dao/vuln.py @@ -11,56 +11,45 @@ from test_cases.factories import WorkspaceFactory, VulnerabilityFactory -class VulnerabilityDAOTestCases(unittest.TestCase): - - def setUp(self): - with app.app_context(): - db.create_all() - self.workspace = WorkspaceFactory.build() - db.session.commit() - - def tearDown(self): - with app.app_context(): - db.drop_all() - - def test_vulnerability_count_and_list_per_workspace_is_filtered(self): - """ - Verifies that the dao return the correct count from each workspace - """ - with app.app_context(): - another_workspace = WorkspaceFactory.build() - vuln_dao = VulnerabilityDAO(self.workspace) - another_vuln_dao = VulnerabilityDAO(another_workspace) - vuln_1 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=self.workspace) - vuln_2 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=another_workspace) - db.session.add(vuln_1) - db.session.add(vuln_2) - db.session.commit() - ws_count = vuln_dao.count() - another_ws_count = another_vuln_dao.count() - ws_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} - another_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} - assert ws_count == ws_expected - assert another_ws_count == another_expected - ws_list = vuln_dao.list() - - assert vuln_1.id == ws_list['vulnerabilities'][0]['_id'] - another_ws_list = another_vuln_dao.list() - assert vuln_2.id == another_ws_list['vulnerabilities'][0]['_id'] - - - - def test_count_by_type(self): - with app.app_context(): - vuln_dao = VulnerabilityDAO(self.workspace) - res = vuln_dao.count() - expected = {'total_count': 0, 'web_vuln_count': 0, 'vuln_count': 0} - self.assertEquals(expected, res) - vuln = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=self.workspace) - vuln_web = VulnerabilityFactory.build(vuln_type='VulnerabilityWeb', workspace=self.workspace) - db.session.add(vuln) - db.session.add(vuln_web) - db.session.commit() - res = vuln_dao.count() - expected = {'total_count': 2, 'web_vuln_count': 1, 'vuln_count': 1} - assert expected == res +def test_vulnerability_count_and_list_per_workspace_is_filtered(app, session): + """ + Verifies that the dao return the correct count from each workspace + """ + with app.app_context(): + workspace = WorkspaceFactory.build() + another_workspace = WorkspaceFactory.build() + vuln_dao = VulnerabilityDAO(workspace) + another_vuln_dao = VulnerabilityDAO(another_workspace) + vuln_1 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=workspace) + vuln_2 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=another_workspace) + db.session.add(vuln_1) + db.session.add(vuln_2) + db.session.commit() + ws_count = vuln_dao.count() + another_ws_count = another_vuln_dao.count() + ws_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} + another_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} + assert ws_count == ws_expected + assert another_ws_count == another_expected + ws_list = vuln_dao.list() + + assert vuln_1.id == ws_list['vulnerabilities'][0]['_id'] + another_ws_list = another_vuln_dao.list() + assert vuln_2.id == another_ws_list['vulnerabilities'][0]['_id'] + + +def test_count_by_type(app, session): + with app.app_context(): + workspace = WorkspaceFactory.build() + vuln_dao = VulnerabilityDAO(workspace) + res = vuln_dao.count() + expected = {'total_count': 0, 'web_vuln_count': 0, 'vuln_count': 0} + assert expected == res + vuln = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=workspace) + vuln_web = VulnerabilityFactory.build(vuln_type='VulnerabilityWeb', workspace=workspace) + db.session.add(vuln) + db.session.add(vuln_web) + db.session.commit() + res = vuln_dao.count() + expected = {'total_count': 2, 'web_vuln_count': 1, 'vuln_count': 1} + assert expected == res From c79d07655bdc4cf85a829c9d02631152c42b1c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 17 Aug 2017 14:54:03 -0300 Subject: [PATCH 0085/1506] Don't import workspaces from CouchDB if and env var is set FARADAY_DONT_IMPORT --- server/importer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/importer.py b/server/importer.py index e0eff1613ef..587552863ad 100644 --- a/server/importer.py +++ b/server/importer.py @@ -4,6 +4,7 @@ import sys import json +import os import server.app import server.utils.logger @@ -438,6 +439,8 @@ def import_workspace_into_database(workspace_name, couchdb_server_conn): def _import_from_couchdb(workspace, couchdb_conn): + if 'FARADAY_DONT_IMPORT' in os.environ: + return couchdb_workspace = server.couchdb.CouchDBWorkspace(workspace.name, couchdb_server_conn=couchdb_conn) total_amount = couchdb_workspace.get_total_amount_of_documents() processed_docs, progress = 0, 0 From 33d05596f222596ff869ca5fb1c28f8f0e34b502 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 17 Aug 2017 17:08:22 -0300 Subject: [PATCH 0086/1506] remove old code for api views import. we use blueprints now --- server/api/__init__.py | 24 ------------------------ server/app.py | 5 ----- 2 files changed, 29 deletions(-) diff --git a/server/api/__init__.py b/server/api/__init__.py index 375acddd1e7..e4f24fb396f 100644 --- a/server/api/__init__.py +++ b/server/api/__init__.py @@ -1,27 +1,3 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information - -import os.path -import glob -import sys - -# Add handlers modules directory to sys.path -modules_path = os.path.join(os.path.dirname(__file__), 'modules') -sys.path.append(modules_path) - -# Get modules path, compiled or not -modules_files = glob.glob(os.path.join(modules_path, '*.py')) -modules_files += glob.glob(os.path.join(modules_path, '*.pyc')) - -# Remove duplicate names -extract_module_name = lambda module_path: os.path.splitext(os.path.basename(module_path))[0] -modules = set(map(extract_module_name, modules_files)) - -# Import and add to handlers namespace every module found in modules -for handler_name in modules: - globals()[handler_name] = __import__(handler_name) - - -def get_handlers(): - return BaseHandler.BaseHandler.get_handlers() diff --git a/server/app.py b/server/app.py index ed28f00471f..77e56e56eee 100644 --- a/server/app.py +++ b/server/app.py @@ -102,8 +102,3 @@ class MiniJSONEncoder(JSONEncoder): app.json_encoder = MiniJSONEncoder app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False - - -# Load APIs -import server.api -import server.modules.info From 7e44dd5465ab03e81e66372578ad27ca0282ca2f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 17 Aug 2017 17:10:59 -0300 Subject: [PATCH 0087/1506] Remove import * to fix PEP8 warnings --- faraday.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/faraday.py b/faraday.py index a16172c8364..f62773ea9d4 100755 --- a/faraday.py +++ b/faraday.py @@ -17,7 +17,22 @@ import sys from config.configuration import getInstanceConfiguration -from config.globals import * +from config.globals import ( + CONST_USER_HOME, + CONST_FARADAY_HOME_PATH, + CONST_FARADAY_PLUGINS_PATH, + CONST_FARADAY_PLUGINS_REPO_PATH, + CONST_FARADAY_IMAGES, + CONST_FARADAY_USER_CFG, + CONST_FARADAY_BASE_CFG, + CONST_USER_ZSHRC, + CONST_FARADAY_ZSHRC, + CONST_ZSH_PATH, + CONST_FARADAY_ZSH_FARADAY, + CONST_VERSION_FILE, + CONST_REQUIREMENTS_FILE, + CONST_FARADAY_FOLDER_LIST, +) from utils import dependencies from utils.logs import getLogger, setUpLogger from utils.profilehooks import profile @@ -478,7 +493,7 @@ def checkCouchUrl(): to set the path of the cert """ sys.exit(-1) - except Exception as e: + except Exception: # Non fatal error pass @@ -496,7 +511,7 @@ def checkVersion(): getInstanceConfiguration().setVersion(f_version) f.close() - except Exception as e: + except Exception: getLogger("launcher").error( "It seems that something's wrong with your version\nPlease contact customer support") sys.exit(-1) From b46f1088738962d1c79f9fbe899fb89ef5f7e51a Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 17 Aug 2017 17:20:07 -0300 Subject: [PATCH 0088/1506] rename test for pytest --- test_cases/dao/{command.py => test_command.py} | 0 test_cases/dao/{credential.py => test_credential.py} | 0 test_cases/dao/{host.py => test_host.py} | 0 test_cases/dao/{services.py => test_services.py} | 0 test_cases/dao/{vuln.py => test_vuln.py} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename test_cases/dao/{command.py => test_command.py} (100%) rename test_cases/dao/{credential.py => test_credential.py} (100%) rename test_cases/dao/{host.py => test_host.py} (100%) rename test_cases/dao/{services.py => test_services.py} (100%) rename test_cases/dao/{vuln.py => test_vuln.py} (100%) diff --git a/test_cases/dao/command.py b/test_cases/dao/test_command.py similarity index 100% rename from test_cases/dao/command.py rename to test_cases/dao/test_command.py diff --git a/test_cases/dao/credential.py b/test_cases/dao/test_credential.py similarity index 100% rename from test_cases/dao/credential.py rename to test_cases/dao/test_credential.py diff --git a/test_cases/dao/host.py b/test_cases/dao/test_host.py similarity index 100% rename from test_cases/dao/host.py rename to test_cases/dao/test_host.py diff --git a/test_cases/dao/services.py b/test_cases/dao/test_services.py similarity index 100% rename from test_cases/dao/services.py rename to test_cases/dao/test_services.py diff --git a/test_cases/dao/vuln.py b/test_cases/dao/test_vuln.py similarity index 100% rename from test_cases/dao/vuln.py rename to test_cases/dao/test_vuln.py From 4c7f625895de692e18d34bae04b0c48bb601820f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 17 Aug 2017 19:28:55 -0300 Subject: [PATCH 0089/1506] change status code to 401 --- server/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/app.py b/server/app.py index 77e56e56eee..691488a5cb1 100644 --- a/server/app.py +++ b/server/app.py @@ -80,10 +80,10 @@ def create_app(db_connection_string=None, testing=None): app.register_blueprint(session_api) # We are exposing a RESTful API, so don't redirect a user to a login page in - # case of being unauthorized, raise a 403 error instead + # case of being unauthorized, raise a 401 error instead @app.login_manager.unauthorized_handler def unauthorized(): - flask.abort(403) + flask.abort(401) @app.before_request def default_login_required(): From a90c99bdc29c588d29b0e0c804ca88315f6c641f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 17 Aug 2017 20:05:33 -0300 Subject: [PATCH 0090/1506] Start to use interceptor to redirect to login or forbidden page --- server/www/scripts/app.js | 12 ++++-------- server/www/scripts/auth/services/interceptor.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/server/www/scripts/app.js b/server/www/scripts/app.js index 3435bd800b2..8bb7e551535 100644 --- a/server/www/scripts/app.js +++ b/server/www/scripts/app.js @@ -271,6 +271,10 @@ faradayApp.config(['$routeProvider', 'ngClipProvider', '$uibTooltipProvider', controller: 'commercialCtrl', title: 'Methodologies | ' }). + when('/forbidden', { + templateUrl: 'scripts/auth/partials/forbidden.html', + title: ' Forbidden |' + }). otherwise({ templateUrl: 'scripts/commons/partials/home.html' }); @@ -282,12 +286,4 @@ faradayApp.run(['$location', '$rootScope', 'loginSrv', function($location, $root $rootScope.title = current.$$route.title; } }); - $rootScope.$on('$routeChangeStart', function(event){ - // Require in all routes (except the login one) - // Taken from http://stackoverflow.com/questions/26145871/redirect-on-all-routes-to-login-if-not-authenticated - // I don't know why this doesn't cause an infinite loop - loginSrv.isAuthenticated().then(function(auth){ - if(!auth) $location.path('/login'); - }); - }); }]); diff --git a/server/www/scripts/auth/services/interceptor.js b/server/www/scripts/auth/services/interceptor.js index fc643a679af..99b72dd82df 100644 --- a/server/www/scripts/auth/services/interceptor.js +++ b/server/www/scripts/auth/services/interceptor.js @@ -8,9 +8,9 @@ angular.module('faradayApp'). response: function(response){ return response; }, - + responseError: function(response) { - if(response.status === 401 || response.status === 403){ + if(response.status === 401){ var deferred = $q.defer(); loginSrv.isAuthenticated().then(function(auth){ if(!auth) { @@ -20,9 +20,16 @@ angular.module('faradayApp'). return deferred.reject(response); }); return deferred.promise; + }else if (response.status === 403) { + $location.path('/forbidden'); }else{ return $q.reject(response); } } } +}]); + +angular.module('faradayApp'). + config(['$httpProvider', function($httpProvider) { + $httpProvider.interceptors.push('AuthInterceptor'); }]); From 1b04e6147519e4c87fd9ec7de59ef7cf7b58e5e6 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 18 Aug 2017 11:34:54 -0300 Subject: [PATCH 0091/1506] Add forbidden page --- server/www/scripts/auth/partials/forbidden.html | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 server/www/scripts/auth/partials/forbidden.html diff --git a/server/www/scripts/auth/partials/forbidden.html b/server/www/scripts/auth/partials/forbidden.html new file mode 100644 index 00000000000..f39c7f455ee --- /dev/null +++ b/server/www/scripts/auth/partials/forbidden.html @@ -0,0 +1,10 @@ +
+
+
+ +
+

Forbidden

+
+
+
+
From 795fdc3966794d8cee1e274a065525853b173fa6 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 18 Aug 2017 12:39:36 -0300 Subject: [PATCH 0092/1506] Disable SQLALCHEMY_TRACK_MODIFICATIONS We are not using flask-sqlalchemy events, but we will use sqlalchemy builtin events. SQLALCHEMY_TRACK_MODIFICATIONS is set to false to suppress warnings --- server/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/app.py b/server/app.py index 77e56e56eee..432ccda75e4 100644 --- a/server/app.py +++ b/server/app.py @@ -25,6 +25,7 @@ def create_app(db_connection_string=None, testing=None): app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ['username'] app.config['SECURITY_POST_LOGIN_VIEW'] = '/_api/session' app.config['SECURITY_POST_LOGOUT_VIEW'] = '/_api/login' + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False if testing: app.config['TESTING'] = testing From 21f88728e2ff2672cc5a8ca9ea890babab9e024d Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 18 Aug 2017 14:20:11 -0300 Subject: [PATCH 0093/1506] add test_ prefix to test files. fix nexpose test --- test_cases/plugins/{acunetix.py => test_acunetix.py} | 0 test_cases/plugins/{burp.py => test_burp.py} | 0 test_cases/plugins/{nessus.py => test_nessus.py} | 0 .../plugins/{nexpose_full.py => test_nexpose_full.py} | 10 +++++----- test_cases/plugins/{nmap.py => test_nmap.py} | 0 test_cases/plugins/{ping.py => test_ping.py} | 0 test_cases/plugins/{telnet.py => test_telnet.py} | 0 test_cases/plugins/{whois.py => test_whois.py} | 0 8 files changed, 5 insertions(+), 5 deletions(-) rename test_cases/plugins/{acunetix.py => test_acunetix.py} (100%) rename test_cases/plugins/{burp.py => test_burp.py} (100%) rename test_cases/plugins/{nessus.py => test_nessus.py} (100%) rename test_cases/plugins/{nexpose_full.py => test_nexpose_full.py} (85%) rename test_cases/plugins/{nmap.py => test_nmap.py} (100%) rename test_cases/plugins/{ping.py => test_ping.py} (100%) rename test_cases/plugins/{telnet.py => test_telnet.py} (100%) rename test_cases/plugins/{whois.py => test_whois.py} (100%) diff --git a/test_cases/plugins/acunetix.py b/test_cases/plugins/test_acunetix.py similarity index 100% rename from test_cases/plugins/acunetix.py rename to test_cases/plugins/test_acunetix.py diff --git a/test_cases/plugins/burp.py b/test_cases/plugins/test_burp.py similarity index 100% rename from test_cases/plugins/burp.py rename to test_cases/plugins/test_burp.py diff --git a/test_cases/plugins/nessus.py b/test_cases/plugins/test_nessus.py similarity index 100% rename from test_cases/plugins/nessus.py rename to test_cases/plugins/test_nessus.py diff --git a/test_cases/plugins/nexpose_full.py b/test_cases/plugins/test_nexpose_full.py similarity index 85% rename from test_cases/plugins/nexpose_full.py rename to test_cases/plugins/test_nexpose_full.py index 3143fa211b8..30588f034f4 100644 --- a/test_cases/plugins/nexpose_full.py +++ b/test_cases/plugins/test_nexpose_full.py @@ -52,12 +52,12 @@ def test_Plugin_creates_apropiate_objects(self): self.assertEqual(action[2].name, "192.168.1.1") for i in range(131): action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDVULNHOST) + if type(action[2]) in [Vuln, VulnWeb]: + assert action[0] == modelactions.ADDVULNHOST + elif type(action[-1]) == Service: + assert action[0] == modelactions.ADDSERVICEINT action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDSERVICEINT) - for i in range(15): - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDVULNSRV) + assert action[0] == modelactions.ADDVULNSRV if __name__ == '__main__': diff --git a/test_cases/plugins/nmap.py b/test_cases/plugins/test_nmap.py similarity index 100% rename from test_cases/plugins/nmap.py rename to test_cases/plugins/test_nmap.py diff --git a/test_cases/plugins/ping.py b/test_cases/plugins/test_ping.py similarity index 100% rename from test_cases/plugins/ping.py rename to test_cases/plugins/test_ping.py diff --git a/test_cases/plugins/telnet.py b/test_cases/plugins/test_telnet.py similarity index 100% rename from test_cases/plugins/telnet.py rename to test_cases/plugins/test_telnet.py diff --git a/test_cases/plugins/whois.py b/test_cases/plugins/test_whois.py similarity index 100% rename from test_cases/plugins/whois.py rename to test_cases/plugins/test_whois.py From 088d87d286d4a852f6783303f249c05d1c24d84c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 18 Aug 2017 14:48:59 -0300 Subject: [PATCH 0094/1506] update test to be executed with pytest --- test_cases/server_models.py | 21 ------------------- test_cases/{common.py => test_common.py} | 8 +++---- test_cases/{models.py => test_models.py} | 0 .../{server_io.py => test_server_io.py} | 2 +- 4 files changed, 5 insertions(+), 26 deletions(-) delete mode 100644 test_cases/server_models.py rename test_cases/{common.py => test_common.py} (81%) rename test_cases/{models.py => test_models.py} (100%) rename test_cases/{server_io.py => test_server_io.py} (99%) diff --git a/test_cases/server_models.py b/test_cases/server_models.py deleted file mode 100644 index 25f1bed5550..00000000000 --- a/test_cases/server_models.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys -import json -import unittest -sys.path.append(os.path.abspath(os.getcwd())) - -from server.models import Interface - -INTERFACE_TEST_CASE_1 = {"network_segment": "", "description": "", "_rev": "1-ffffffffffffffffbcb43323dfeeeeee", "owned": False, "mac": "00:00:00:00:00:00", "hostnames": None, "owner": "", "name": "192.168.1.1", "ipv4": {"mask": "0.0.0.0", "gateway": "0.0.0.0", "DNS": [], "address": "192.168.1.1"}, "ipv6": {"prefix": "00", "gateway": "0000:0000:0000:0000:0000:0000:0000:0000", "DNS": [], "address": "0000:0000:0000:0000:0000:0000:0000:0000"}, "_id": "90aa44756bd2f4fc2390f903a6f25f43216b0790.0e9d8e8deab983df5e8af607f00901e089174881", "type": "Interface", "metadata": {"update_time": 1498579348.26915, "update_user": "leonardo", "update_action": 0, "creator": "Metasploit", "create_time": 1498579348.26915, "update_controller_action": "No model controller call", "owner": "leonardo", "command_id": "f09ea09db5264f2185a6d142ecd794f2"}} - - -class ModelsTest(unittest.TestCase): - - def test_(self): - interface = Interface(INTERFACE_TEST_CASE_1) - interface.update_from_document(INTERFACE_TEST_CASE_1) - self.assertEquals(interface.hostname, '') - - -if __name__ == '__main__': - unittest.main() diff --git a/test_cases/common.py b/test_cases/test_common.py similarity index 81% rename from test_cases/common.py rename to test_cases/test_common.py index 8626fbd15f4..aa183554fb6 100644 --- a/test_cases/common.py +++ b/test_cases/test_common.py @@ -34,13 +34,13 @@ def create_host_vuln(self, host, name, desc, severity): return vuln def create_int_vuln(self, host, interface, name, desc, severity): - vuln = Vulnerability(name, desc, severity) - self.model_controller.addVulnToInterfaceSYNC( host.getID(), interface.getID(), vuln) + vuln = Vulnerability(name=name, description=desc, severity=severity) + self.model_controller.addVulnToInterfaceSYNC(host.getID(), interface.getID(), vuln) return vuln def create_serv_vuln(self, host, service, name, desc, severity): - vuln = Vulnerability(name, desc, severity) - self.model_controller.addVulnToServiceSYNC( host.getID(), service.getID(), vuln) + vuln = Vulnerability(name=name, description=desc, severity=severity) + self.model_controller.addVulnToServiceSYNC(host.getID(), service.getID(), vuln) return vuln diff --git a/test_cases/models.py b/test_cases/test_models.py similarity index 100% rename from test_cases/models.py rename to test_cases/test_models.py diff --git a/test_cases/server_io.py b/test_cases/test_server_io.py similarity index 99% rename from test_cases/server_io.py rename to test_cases/test_server_io.py index 907ccbfa828..6fedde59d9f 100644 --- a/test_cases/server_io.py +++ b/test_cases/test_server_io.py @@ -45,7 +45,7 @@ def test_create_server_get_ws_names_url(self): @responses.activate def test_raise_conflict_in_database(self): url = "http://just_raise_conflict.com" - responses.add(responses.PUT, url, body='{"name": "betcha"}', status=409, + responses.add(responses.PUT, url, status=409, content_type="application/json", json={'error': 'conflict'}) with self.assertRaises(server_io_exceptions.ConflictInDatabase): server._unsafe_io_with_server(requests.put, 200, url, json={"name": "betcha"}) From 7c88ebc84c0410c458448d4e1c8bebda52a02ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 22 Aug 2017 16:04:31 -0300 Subject: [PATCH 0095/1506] Add script to import users from CouchDB Not idempotent yet --- import_users_from_couch.py | 95 ++++++++++++++++++++++ server/app.py | 11 +++ test_cases/test_import_users_from_couch.py | 61 ++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 import_users_from_couch.py create mode 100644 test_cases/test_import_users_from_couch.py diff --git a/import_users_from_couch.py b/import_users_from_couch.py new file mode 100644 index 00000000000..5939a94d20c --- /dev/null +++ b/import_users_from_couch.py @@ -0,0 +1,95 @@ +from __future__ import print_function +import argparse +import requests +from urlparse import urljoin + +from binascii import unhexlify +from passlib.utils.binary import ab64_encode +from passlib.hash import pbkdf2_sha1 + +from flask_script import Manager +from server.web import app +from server.models import db + + +COUCHDB_USER_PREFIX = 'org.couchdb.user:' +COUCHDB_PASSWORD_PREXFIX = '-pbkdf2-' + + +def modular_crypt_pbkdf2_sha1(checksum, salt, iterations=1000): + return '$pbkdf2${iterations}${salt}${checksum}'.format( + iterations=iterations, + salt=ab64_encode(salt), + checksum=ab64_encode(unhexlify(checksum)), + ) + + +def convert_couchdb_hash(original_hash): + if not original_hash.startswith(COUCHDB_PASSWORD_PREXFIX): + # Should be a plaintext password + return original_hash + checksum, salt, iterations = original_hash[ + len(COUCHDB_PASSWORD_PREXFIX):].split(',') + iterations = int(iterations) + return modular_crypt_pbkdf2_sha1(checksum, salt, iterations) + + +def get_hash_from_document(doc): + scheme = doc.get('password_scheme', 'unset') + if scheme != 'pbkdf2': + raise ValueError('Unknown password scheme: %s' % scheme) + return modular_crypt_pbkdf2_sha1(doc['derived_key'], doc['salt'], + doc['iterations']) + + +def parse_all_docs(doc): + return [row['doc'] for row in doc['rows']] + + +def import_users(admins, all_users): + + manager = Manager(app) + with app.app_context(): + + # Import admin users + for (username, password) in admins.items(): + print("Creating user", username) + app.user_datastore.create_user( + username=username, + email=username + '@test.com', + password=convert_couchdb_hash(password), + is_ldap=False + ) + + # Import non admin users + for user in all_users: + if not user['_id'].startswith(COUCHDB_USER_PREFIX): + # It can be a view or something other than a user + continue + if user['name'] in admins.keys(): + # This is an already imported admin user, skip + continue + print("Importing", user['name']) + app.user_datastore.create_user( + username=user['name'], + email=user['name'] + '@test.com', + password=get_hash_from_document(user), + is_ldap=False + ) + db.session.commit() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--couch-url', default='http://localhost:5984') + parser.add_argument('username') + parser.add_argument('password') + args = parser.parse_args() + + auth = (args.username, args.password) + admins_url = urljoin(args.couch_url, + '/_config/admins') + users_url = urljoin(args.couch_url, + '/_users/_all_docs?include_docs=true') + import_users(requests.get(admins_url, auth=auth).json(), + parse_all_docs(requests.get(users_url, auth=auth).json())) diff --git a/server/app.py b/server/app.py index 77e56e56eee..5a45870b599 100644 --- a/server/app.py +++ b/server/app.py @@ -25,6 +25,17 @@ def create_app(db_connection_string=None, testing=None): app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ['username'] app.config['SECURITY_POST_LOGIN_VIEW'] = '/_api/session' app.config['SECURITY_POST_LOGOUT_VIEW'] = '/_api/login' + app.config['SECURITY_PASSWORD_SCHEMES'] = [ + 'bcrypt', # This should be the default value + # 'des_crypt', + 'pbkdf2_sha1', # Used by CouchDB passwords + # 'pbkdf2_sha256', + # 'pbkdf2_sha512', + # 'sha256_crypt', + # 'sha512_crypt', + # And always last one... + 'plaintext' # TODO: remove + ] if testing: app.config['TESTING'] = testing diff --git a/test_cases/test_import_users_from_couch.py b/test_cases/test_import_users_from_couch.py new file mode 100644 index 00000000000..91385fcb688 --- /dev/null +++ b/test_cases/test_import_users_from_couch.py @@ -0,0 +1,61 @@ +import pytest +from passlib.hash import pbkdf2_sha1 +import import_users_from_couch + +NON_ADMIN_DOC = { + "_id": "org.couchdb.user:removeme2", + "_rev": "0-00000000000000000000000000000000", + "password_scheme": "pbkdf2", + "iterations": 10, + "name": "removeme2", + "roles": [ + "pentester" + ], + "type": "user", + "derived_key": "d2061cb98f85b5e14eda97da556c1625906e5c2b", + "salt": "60604061640065389e9e90ca12c83b8d" +} + +ADMIN_DOC = { + "_id": "org.couchdb.user:removeme", + "_rev": "1-00000000000000000000000000000000", + "name": "removeme", + "password": None, + "roles": [], + "type": "user" +} + +def test_import_encrypted_password_from_admin_user(): + original_hash = ('-pbkdf2-eeea435c505e74d33a8c1b55c39d8dd355db4c2d,' + 'aedeef5a01f96a84360d2719fc521b9f,10') + new_hash = import_users_from_couch.convert_couchdb_hash(original_hash) + assert pbkdf2_sha1.verify('12345', new_hash) + + +def test_import_plaintext_password_from_admin_user(): + assert import_users_from_couch.convert_couchdb_hash('12345') == '12345' + + +def test_import_non_admin_from_document(): + new_hash = import_users_from_couch.get_hash_from_document(NON_ADMIN_DOC) + assert pbkdf2_sha1.verify('12345', new_hash) + + +def test_import_admin_from_document_fails(): + with pytest.raises(ValueError): + import_users_from_couch.get_hash_from_document(ADMIN_DOC) + + +def test_parse_all_docs_response_succeeds(): + doc_with_metadata = { + "id": "org.couchdb.user:removeme", + "key": "org.couchdb.user:removeme", + "value": {"rev": "1-00000000000000000000000000000000"}, + "doc": ADMIN_DOC + } + data = { + "total_rows": 15, + "offset": 0, + "rows": [doc_with_metadata] * 15 + } + assert import_users_from_couch.parse_all_docs(data) == [ADMIN_DOC] * 15 From 92d6855d123efe2ecf0c67299ad5af64bcfd6285 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 22 Aug 2017 16:06:00 -0300 Subject: [PATCH 0096/1506] Add WorkspaceDAO Also modifies the ws wndpoint to use the new DAO. --- server/api/modules/workspaces.py | 35 ++++++++-- server/dao/workspace.py | 114 +++++++++++++++++++++++++++++++ server/models.py | 13 +++- 3 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 server/dao/workspace.py diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 488eeebd1f6..476f06d147f 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -12,12 +12,16 @@ from server.dao.service import ServiceDAO from server.dao.interface import InterfaceDAO from server.dao.note import NoteDAO +from server.dao.workspace import WorkspaceDAO +from server.utils.logger import get_logger from server.utils.web import ( - gzipped, - validate_workspace, + build_bad_request_response, + filter_request_args, get_basic_auth, + get_integer_parameter, + gzipped, validate_admin_perm, - build_bad_request_response + validate_workspace ) from server.couchdb import ( list_workspaces_as_user, @@ -27,13 +31,30 @@ workspace_api = Blueprint('workspace_api', __name__) - @workspace_api.route('/ws', methods=['GET']) @gzipped def workspace_list(): - return flask.jsonify( - list_workspaces_as_user( - flask.request.cookies, get_basic_auth())) + get_logger(__name__).debug("Request parameters: {!r}" + .format(flask.request.args)) + + page = get_integer_parameter('page', default=0) + page_size = get_integer_parameter('page_size', default=0) + search = flask.request.args.get('search') + order_by = flask.request.args.get('sort') + order_dir = flask.request.args.get('sort_dir') + + ws_filter = filter_request_args('page', 'page_size', 'search', 'sort', 'sort_dir') + + ws_dao = WorkspaceDAO() + + result = ws_dao.list(search=search, + page=page, + page_size=page_size, + order_by=order_by, + order_dir=order_dir, + workspace_filter=ws_filter) + + return flask.jsonify(result) @workspace_api.route('/ws//summary', methods=['GET']) diff --git a/server/dao/workspace.py b/server/dao/workspace.py new file mode 100644 index 00000000000..52a6bd7fa74 --- /dev/null +++ b/server/dao/workspace.py @@ -0,0 +1,114 @@ +# Faraday Penetration Test IDE +# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) +# See the file "doc/LICENSE" for the license information + +from server.dao.base import FaradayDAO +from server.utils.database import ( + apply_search_filter, + get_count, + paginate +) +from server.utils import logger + +from sqlalchemy.orm.query import Bundle +from server.models import db, Workspace + + +class WorkspaceDAO(object): + + MAPPED_ENTITY = Workspace + + COLUMNS_MAP = { + "create_date": [Workspace.create_date], + "creator": [Workspace.creator], + "customer": [Workspace.customer], + "description": [Workspace.description], + "disabled": [Workspace.disabled], + "end_date": [Workspace.end_date], + "name": [Workspace.name], + "public": [Workspace.public], + "scope": [Workspace.scope], + "start_date": [Workspace.start_date], + "update_date": [Workspace.update_date] + } + + STRICT_FILTERING = ["name", "creator", "customer", "disabled", + "public", "update_date"] + + def __init__(self): + self._logger = logger.get_logger(self) + self._session = db.session + self._couchdb = None + + def get_all(self): + self.__check_valid_operation() + return self._session.query(self.MAPPED_ENTITY) + + def __check_valid_operation(self): + if self.MAPPED_ENTITY is None: + raise RuntimeError('Invalid operation') + + def save(self, obj): + self._session.add(obj) + self._session.commit() + + def list(self, search=None, page=0, page_size=0, order_by=None, + order_dir=None, workspace_filter={}): + + results, count = self.__query_database(search, page, page_size, + order_by, order_dir, + workspace_filter) + rows = [self.__get_workspace_data(result.workspace) + for result in results] + + result = { + "total_rows": count, + "rows": rows + } + + return result + + def __query_database(self, search=None, page=0, page_size=0, + order_by=None, order_dir=None, workspace_filter={}): + + workspace_bundle = Bundle('workspace', + Workspace.create_date, + Workspace.creator, + Workspace.customer, + Workspace.description, + Workspace.disabled, + Workspace.end_date, + Workspace.name, + Workspace.public, + Workspace.scope, + Workspace.start_date, + Workspace.update_date) + + query = self._session.query(workspace_bundle) + + query = apply_search_filter(query, self.COLUMNS_MAP, search, + workspace_filter, self.STRICT_FILTERING) + count = get_count(query, count_col=Workspace.name) + + if page_size: + query = paginate(query, page, page_size) + + results = query.all() + + return results, count + + def __get_workspace_data(self, workspace): + + return { + "create_date": workspace.create_date, + "creator": workspace.creator, + "customer": workspace.customer, + "description": workspace.description, + "disabled": workspace.disabled, + "end_date": workspace.end_date, + "name": workspace.name, + "public": workspace.public, + "scope": workspace.scope, + "start_date": workspace.start_date, + "update_date": workspace.update_date + } diff --git a/server/models.py b/server/models.py index d676a9ec993..0d16bd111e9 100644 --- a/server/models.py +++ b/server/models.py @@ -244,7 +244,18 @@ class Command(db.Model): class Workspace(db.Model): __tablename__ = 'workspace' id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=True) + # TODO: change nullable=True for appropriate fields + create_date = Column(DateTime(), nullable=True) + creator = Column(Integer(), nullable=True) + customer = Column(String(250), nullable=True) + description = Column(Text(), nullable=True) + disabled = Column(Boolean(), nullable=True) + end_date = Column(DateTime(), nullable=True) + name = Column(String(250), nullable=True, unique=True) + public = Column(Boolean(), nullable=True) + scope = Column(Text(), nullable=True) + start_date = Column(DateTime(), nullable=True) + update_date = Column(DateTime(), nullable=True) def is_valid_workspace(workspace_name): From f018c937a5aa56bdc48b81c83c6e622b8b3dd0bb Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 22 Aug 2017 17:28:01 -0300 Subject: [PATCH 0097/1506] add fields to new model --- server/models.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/server/models.py b/server/models.py index d676a9ec993..71196feb11d 100644 --- a/server/models.py +++ b/server/models.py @@ -244,7 +244,7 @@ class Command(db.Model): class Workspace(db.Model): __tablename__ = 'workspace' id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=True) + name = Column(String(250), nullable=False, unique=True) def is_valid_workspace(workspace_name): @@ -266,25 +266,27 @@ class Role(db.Model, RoleMixin): __tablename__ = 'role' id = Column(Integer(), primary_key=True) name = Column(String(80), unique=True) - description = Column(String(255)) + description = Column(String(255), nullable=True) class User(db.Model, UserMixin): __tablename__ = 'user' id = Column(Integer, primary_key=True) - email = Column(String(255), unique=True) - username = Column(String(255)) - password = Column(String(255)) - is_ldap = Column(Boolean(), nullable=False) - last_login_at = Column(DateTime()) - current_login_at = Column(DateTime()) - last_login_ip = Column(String(100)) - current_login_ip = Column(String(100)) - login_count = Column(Integer) - active = Column(Boolean()) + username = Column(String(255), unique=True, nullable=False) + password = Column(String(255), nullable=True) + email = Column(String(255), unique=True, nullable=True) # TBI + name = Column(String(255), nullalbe=True) # TBI + is_ldap = Column(Boolean(), nullable=False, default=False) + last_login_at = Column(DateTime()) # flask-security + current_login_at = Column(DateTime()) # flask-security + last_login_ip = Column(String(100)) # flask-security + current_login_ip = Column(String(100)) # flask-security + login_count = Column(Integer) # flask-security + active = Column(Boolean(), default=True, nullable=False) # TBI flask-security confirmed_at = Column(DateTime()) roles = relationship('Role', secondary='roles_users', backref=backref('users', lazy='dynamic')) + # TODO: add many to many relationship to add permission to workspace @property def role(self): From 998cd96ef67c1d6de0953dabf0353efe776ab415 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 22 Aug 2017 18:32:35 -0300 Subject: [PATCH 0098/1506] Add new model changes --- server/models.py | 92 ++++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/server/models.py b/server/models.py index 8ede9825877..563d11cf2f8 100644 --- a/server/models.py +++ b/server/models.py @@ -143,31 +143,55 @@ class Service(db.Model): workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) +class Reference(db.Model): + __tablename__ = 'reference' + id = Column(Integer, primary_key=True) + name = Column(String(250)) + + workspace = relationship('Workspace') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + + vulnerability = relationship('Vulnerability') + vulnerability_id = Column(Integer, ForeignKey('vulnerbility.id'), index=True) + + +class PolicyViolation(db.Model): + __tablename__ = 'policiy_violation' + id = Column(Integer, primary_key=True) + name = Column(String(250)) + + workspace = relationship('Workspace') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + + vulnerability = relationship('Vulnerability', backref='policy_violations') + vulnerability_id = Column(Integer, ForeignKey('vulnerbility.id'), index=True) + + class Vulnerability(db.Model): + # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website + # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien __tablename__ = 'vulnerability' id = Column(Integer, primary_key=True) name = Column(String(250), nullable=False) description = Column(Text(), nullable=False) - confirmed = Column(Boolean) - vuln_type = Column(String(250)) + confirmed = Column(Boolean, default=False) + vuln_type = Column(String(250)) # TODO: add enum data = Column(Text()) - easeofresolution = Column(String(50)) - refs = Column(Text()) - resolution = Column(Text()) - severity = Column(String(50)) - owned = Column(Boolean) - attachments = Column(Text(), nullable=True) - policyviolations = Column(Text()) - - impact_accountability = Column(Boolean) - impact_availability = Column(Boolean) - impact_confidentiality = Column(Boolean) - impact_integrity = Column(Boolean) - - method = Column(String(50)) - params = Column(String(500)) - path = Column(String(500)) + ease_of_resolution = Column(String(50)) # TODO: add enum + resolution = Column(Text(), nullable=True) + severity = Column(String(50), nullalbe=False) # TODO: add enum + # TODO add evidence + + impact_accountability = Column(Boolean, default=False) + impact_availability = Column(Boolean, default=False) + impact_confidentiality = Column(Boolean, default=False) + impact_integrity = Column(Boolean, default=False) + + method = Column(String(50), nullable=True) + params = Column(String(500), nullable=True) + path = Column(String(500), nullable=True) + # REVIEW FROM FROM HERE pname = Column(String(250)) query = Column(Text()) request = Column(Text()) @@ -190,6 +214,7 @@ class Vulnerability(db.Model): class Note(db.Model): + # TODO: review this model __tablename__ = 'note' id = Column(Integer, primary_key=True) name = Column(String(250), nullable=False) @@ -208,18 +233,18 @@ class Credential(db.Model): id = Column(Integer, primary_key=True) username = Column(String(250), nullable=False) password = Column(Text(), nullable=False) - owned = Column(Boolean) description = Column(Text(), nullable=True) name = Column(String(250), nullable=True) entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - host_id = Column(Integer, ForeignKey(Host.id), index=True) + host_id = Column(Integer, ForeignKey(Host.id), index=True, nullalbe=False) host = relationship('Host', back_populates='credentials') - service_id = Column(Integer, ForeignKey(Service.id), index=True) + service_id = Column(Integer, ForeignKey(Service.id), index=True, nullalbe=False) service = relationship('Service', back_populates='credentials') + workspace = relationship('Workspace') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) @@ -227,16 +252,16 @@ class Credential(db.Model): class Command(db.Model): __tablename__ = 'command' id = Column(Integer, primary_key=True) - command = Column(String(250), nullable=True) - duration = Column(Float, nullable=True) - itime = Column(Float, nullable=True) - ip = Column(String(250), nullable=True) - hostname = Column(String(250), nullable=True) + command = Column(String(250), nullable=False) + start_date = Column(DateTime, nullable=False) + end_date = Column(DateTime, nullable=False) + ip = Column(String(250), nullable=False) # where the command was executed + hostname = Column(String(250), nullable=False) # where the command was executed params = Column(String(250), nullable=True) - user = Column(String(250), nullable=True) + user = Column(String(250), nullable=True) # where the command was executed workspace = relationship('Workspace') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) - + # TODO: add Tool relationship and report_attachment entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) @@ -245,17 +270,14 @@ class Workspace(db.Model): __tablename__ = 'workspace' id = Column(Integer, primary_key=True) # TODO: change nullable=True for appropriate fields - create_date = Column(DateTime(), nullable=True) - creator = Column(Integer(), nullable=True) - customer = Column(String(250), nullable=True) + customer = Column(String(250), nullable=True) # TBI description = Column(Text(), nullable=True) - disabled = Column(Boolean(), nullable=True) + active = Column(Boolean(), nullable=False, default=True) # TBI end_date = Column(DateTime(), nullable=True) - name = Column(String(250), nullable=True, unique=True) - public = Column(Boolean(), nullable=True) + name = Column(String(250), nullable=False, unique=True) + public = Column(Boolean(), nullable=False, default=True) # TBI scope = Column(Text(), nullable=True) start_date = Column(DateTime(), nullable=True) - update_date = Column(DateTime(), nullable=True) def is_valid_workspace(workspace_name): From 5f0aaa7e5a2deb38e06deac8a3d58078b51a13fc Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 22 Aug 2017 19:39:24 -0300 Subject: [PATCH 0099/1506] Add initdb command to create the database --- manage.py | 13 +++++++++++++ requirements_server.txt | 1 + 2 files changed, 14 insertions(+) create mode 100644 manage.py diff --git a/manage.py b/manage.py new file mode 100644 index 00000000000..654c5e5dae5 --- /dev/null +++ b/manage.py @@ -0,0 +1,13 @@ +# manage.py + +from flask_script import Manager + +from server.web import app +from server.commands import InitDB + +manager = Manager(app) + + +if __name__ == "__main__": + manager.add_command('initdb', InitDB()) + manager.run() diff --git a/requirements_server.txt b/requirements_server.txt index 5ad45c1a9f1..4e95cbcb3e0 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -11,3 +11,4 @@ pyasn1-modules Flask-Security==3.0.0 bcrypt Flask-SQLAlchemy==2.2 +Flask-Script==2.0.5 From 9f99d010974b31054f1b2cbbe34e4b08fcaa8850 Mon Sep 17 00:00:00 2001 From: micabot Date: Wed, 23 Aug 2017 14:45:07 -0300 Subject: [PATCH 0100/1506] Update models to match new Relational Model Missing UNIQUE constraints, ENUMS and other details. No work was done on EntityMetadata object. --- server/models.py | 166 +++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 85 deletions(-) diff --git a/server/models.py b/server/models.py index 563d11cf2f8..6dca6afbaa6 100644 --- a/server/models.py +++ b/server/models.py @@ -56,119 +56,106 @@ class EntityMetadata(db.Model): class Host(db.Model): + # TODO: add unique constraint -> ip, workspace __tablename__ = 'host' id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - description = Column(Text(), nullable=False) - os = Column(String(250), nullable=False) + ip = Column(Text, nullable=False) # IP v4 or v6 + description = Column(Text, nullable=True) + os = Column(Text, nullable=True) - owned = Column(Boolean) + owned = Column(Boolean, nullable=False, default=False) - default_gateway_ip = Column(String(250)) - default_gateway_mac = Column(String(250)) + default_gateway_ip = Column(Text, nullable=True) + default_gateway_mac = Column(Text, nullable=True) + + mac = Column(Text, nullable=True) + net_segment = Column(Text, nullable=True) entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - interfaces = relationship('Interface') - services = relationship('Service') - vulnerabilities = relationship('Vulnerability') - credentials = relationship('Credential') - - workspace = relationship('Workspace') + workspace = relationship('Workspace', backref='hosts') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) -class Interface(db.Model): - __tablename__ = 'interface' +class Hostname(db.Model): + # TODO: add unique constraint -> name, host, workspace + __tablename__ = 'hostname' id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - description = Column(String(250), nullable=False) - mac = Column(String(250), nullable=False) - owned = Column(Boolean) - - hostnames = Column(String(250)) - network_segment = Column(String(250)) + name = Column(Text, nullable=False) - ipv4_address = Column(String(250)) - ipv4_gateway = Column(String(250)) - ipv4_dns = Column(String(250)) - ipv4_mask = Column(String(250)) - - ipv6_address = Column(String(250)) - ipv6_gateway = Column(String(250)) - ipv6_dns = Column(String(250)) - ipv6_prefix = Column(String(250)) - - ports_filtered = Column(Integer) - ports_opened = Column(Integer) - ports_closed = Column(Integer) - - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - - host_id = Column(Integer, ForeignKey(Host.id), index=True) - host = relationship('Host', back_populates='interfaces') - workspace = relationship('Workspace') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) - - services = relationship('Service') + host = relationship('Host', backref='hostnames') + host_id = Column(Integer, ForeignKey('host.id'), index=True) class Service(db.Model): - # Table schema + # TODO: add unique constraint to -> port, protocol, host_id, workspace __tablename__ = 'service' id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - description = Column(String(250), nullable=False) - ports = Column(String(250), nullable=False) - owned = Column(Boolean) + name = Column(Text, nullable=True) + description = Column(Text, nullable=True) + port = Column(Integer, nullable=False) + owned = Column(Boolean, nullable=False, default=False) - protocol = Column(String(250)) - status = Column(String(250)) - version = Column(String(250)) + protocol = Column(Text, nullable=False) + # open, closed, filtered + status = Column(Text, nullable=True) # TODO: add enum + version = Column(Text, nullable=True) + + banner = Column(Text, nullable=True) entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - host_id = Column(Integer, ForeignKey(Host.id), index=True) - host = relationship('Host', back_populates='services') - - interface_id = Column(Integer, ForeignKey(Interface.id), index=True) - interface = relationship('Interface', back_populates='services') + host = relationship('Host', backref='services') + host_id = Column(Integer, ForeignKey('host.id'), index=True) - vulnerabilities = relationship('Vulnerability') - credentials = relationship('Credential') - workspace = relationship('Workspace') + workspace = relationship('Workspace', backref='services') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) class Reference(db.Model): __tablename__ = 'reference' id = Column(Integer, primary_key=True) - name = Column(String(250)) + name = Column(Text, nullable=False) - workspace = relationship('Workspace') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship('Workspace', backref='references') + workspace_id = Column( + Integer, + ForeignKey('workspace.id'), + index=True + ) - vulnerability = relationship('Vulnerability') - vulnerability_id = Column(Integer, ForeignKey('vulnerbility.id'), index=True) + vulnerability = relationship('Vulnerability', backref='references') + vulnerability_id = Column( + Integer, + ForeignKey('vulnerbility.id'), + index=True + ) class PolicyViolation(db.Model): - __tablename__ = 'policiy_violation' + __tablename__ = 'policy_violation' id = Column(Integer, primary_key=True) - name = Column(String(250)) + name = Column(Text, nullable=False) - workspace = relationship('Workspace') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship('Workspace', backref='policy_violations') + workspace_id = Column( + Integer, + ForeignKey('workspace.id'), + index=True + ) vulnerability = relationship('Vulnerability', backref='policy_violations') - vulnerability_id = Column(Integer, ForeignKey('vulnerbility.id'), index=True) + vulnerability_id = Column( + Integer, + ForeignKey('vulnerbility.id'), + index=True + ) class Vulnerability(db.Model): - # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website + # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website, workspace # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien __tablename__ = 'vulnerability' id = Column(Integer, primary_key=True) @@ -189,16 +176,17 @@ class Vulnerability(db.Model): impact_integrity = Column(Boolean, default=False) method = Column(String(50), nullable=True) - params = Column(String(500), nullable=True) + parameters = Column(String(500), nullable=True) path = Column(String(500), nullable=True) # REVIEW FROM FROM HERE - pname = Column(String(250)) - query = Column(Text()) - request = Column(Text()) - response = Column(Text()) - website = Column(String(250)) + parameter_name = Column(String(250), nullable=True) + query = Column(Text(), nullable=True) + request = Column(Text(), nullable=True) + response = Column(Text(), nullable=True) + website = Column(String(250), nullable=True) - status = Column(String(250)) + # open, closed, re-opened, risk-accepted + status = Column(String(250), nullable=False, default="open") # TODO: add enum entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) @@ -210,7 +198,12 @@ class Vulnerability(db.Model): service = relationship('Service', back_populates='vulnerabilities') workspace = relationship('Workspace') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column( + Integer, + ForeignKey('workspace.id'), + index=True, + back_populates='vulnerabilities' + ) class Note(db.Model): @@ -229,6 +222,8 @@ class Note(db.Model): class Credential(db.Model): + # TODO: add unique constraint -> username, host o service y workspace + # TODO: add constraint host y service, uno o el otro __tablename__ = 'credential' id = Column(Integer, primary_key=True) username = Column(String(250), nullable=False) @@ -239,14 +234,14 @@ class Credential(db.Model): entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - host_id = Column(Integer, ForeignKey(Host.id), index=True, nullalbe=False) - host = relationship('Host', back_populates='credentials') + host = relationship('Host', backref='credentials') + host_id = Column(Integer, ForeignKey(Host.id), index=True, nullalbe=True) - service_id = Column(Integer, ForeignKey(Service.id), index=True, nullalbe=False) - service = relationship('Service', back_populates='credentials') + service = relationship('Service', backref='credentials') + service_id = Column(Integer, ForeignKey(Service.id), index=True, nullalbe=True) - workspace = relationship('Workspace') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship('Workspace', backref='credentials') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) class Command(db.Model): @@ -262,6 +257,7 @@ class Command(db.Model): workspace = relationship('Workspace') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) # TODO: add Tool relationship and report_attachment + entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) From 89fbea4cafc929b5f0f3ce45fdf1ae5857652491 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 23 Aug 2017 16:44:10 -0300 Subject: [PATCH 0101/1506] Add enum to the model --- server/models.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/server/models.py b/server/models.py index 6dca6afbaa6..53e14f1c232 100644 --- a/server/models.py +++ b/server/models.py @@ -10,7 +10,8 @@ Float, Text, UniqueConstraint, - DateTime + DateTime, + Enum, ) from sqlalchemy.orm import relationship, backref from flask_sqlalchemy import SQLAlchemy @@ -59,7 +60,7 @@ class Host(db.Model): # TODO: add unique constraint -> ip, workspace __tablename__ = 'host' id = Column(Integer, primary_key=True) - ip = Column(Text, nullable=False) # IP v4 or v6 + ip = Column(Text, nullable=False) # IP v4 or v6 description = Column(Text, nullable=True) os = Column(Text, nullable=True) @@ -90,6 +91,11 @@ class Hostname(db.Model): class Service(db.Model): # TODO: add unique constraint to -> port, protocol, host_id, workspace + STATUSES = [ + 'open', + 'closed', + 'filtered' + ] __tablename__ = 'service' id = Column(Integer, primary_key=True) name = Column(Text, nullable=True) @@ -98,8 +104,7 @@ class Service(db.Model): owned = Column(Boolean, nullable=False, default=False) protocol = Column(Text, nullable=False) - # open, closed, filtered - status = Column(Text, nullable=True) # TODO: add enum + status = Column(Enum(*STATUSES), nullable=True) version = Column(Text, nullable=True) banner = Column(Text, nullable=True) @@ -157,17 +162,34 @@ class PolicyViolation(db.Model): class Vulnerability(db.Model): # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website, workspace # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien + VULN_TYPES = [ + 'vuln', + 'vuln_web', + 'source_code'] + EASE_OF_RESOLUTIONS = [ + 'trivial', + 'simple', + 'moderate', + 'difficult', + 'infeasible' + ] + STATUSES = [ + 'open', + 'closed', + 're-opened', + 'risk-accepted' + ] __tablename__ = 'vulnerability' id = Column(Integer, primary_key=True) name = Column(String(250), nullable=False) description = Column(Text(), nullable=False) confirmed = Column(Boolean, default=False) - vuln_type = Column(String(250)) # TODO: add enum + vuln_type = Column(Enum(*VULN_TYPES)) data = Column(Text()) - ease_of_resolution = Column(String(50)) # TODO: add enum + ease_of_resolution = Column(Enum(*EASE_OF_RESOLUTIONS)) resolution = Column(Text(), nullable=True) - severity = Column(String(50), nullalbe=False) # TODO: add enum + severity = Column(String(50), nullalbe=False) # TODO add evidence impact_accountability = Column(Boolean, default=False) @@ -186,7 +208,7 @@ class Vulnerability(db.Model): website = Column(String(250), nullable=True) # open, closed, re-opened, risk-accepted - status = Column(String(250), nullable=False, default="open") # TODO: add enum + status = Column(Enum(*STATUSES), nullable=False, default="open") entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) From 617228545c2c4a894e84581e3f16911ab9fb3a21 Mon Sep 17 00:00:00 2001 From: micabot Date: Wed, 23 Aug 2017 17:36:41 -0300 Subject: [PATCH 0102/1506] Add models for Task and Methodology --- server/models.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/server/models.py b/server/models.py index 53e14f1c232..c8cce59f991 100644 --- a/server/models.py +++ b/server/models.py @@ -360,3 +360,99 @@ def get_security_payload(self): def __repr__(self): return '<%sUser: %s>' % ('LDAP ' if self.is_ldap else '', self.username) + + +class MethodologyTemplate(db.Model): + # TODO: reset template_id in methodologies when deleting meth template + __tablename__ = 'methodology_template' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False) + + +class Methodology(db.Model): + # TODO: add unique constraint -> name, workspace + __tablename__ = 'methodology' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False) + + entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) + entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + + template = relationship('MethodologyTemplate', backref='methodologies') + template_id = Column( + Integer, + ForeignKey('methodology_template.id'), + index=True, + nullable=True, + ) + + workspace = relationship('Workspace', backref='methodologies') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + + +class TaskABC(db.Model): + __tablename__ = 'task_abc' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False) + description = Column(Text, nullable=False) + + +class TaskTemplate(TaskABC): + __tablename__ = 'task_template' + id = Column(Integer, primary_key=True) + + __mapper_args__ = { + 'concrete': True + } + + template = relationship('MethodologyTemplate', backref='tasks') + template_id = Column( + Integer, + ForeignKey('methodology_template.id'), + index=True, + nullable=False, + ) + + +class Task(TaskABC): + STATUSES = [ + 'new', + 'in progress', + 'review', + 'completed', + ] + + __tablename__ = 'task' + id = Column(Integer, primary_key=True) + + due_date = Column(DateTime, nullable=True) + status = Column(Enum(*STATUSES), nullable=True) + + __mapper_args__ = { + 'concrete': True + } + + entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) + entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + + assigned_to = relationship('User', backref='assigned_tasks') + assigned_to_id = Column(Integer, ForeignKey('user.id'), nullable=True) + + methodology = relationship('Methodology', backref='tasks') + methodology_id = Column( + Integer, + ForeignKey('methodology.id'), + index=True, + nullable=False, + ) + + template = relationship('TaskTemplate', backref='tasks') + template_id = Column( + Integer, + ForeignKey('task_template.id'), + index=True, + nullable=True, + ) + + workspace = relationship('Workspace', backref='tasks') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) From d49a5173751c44887b9d117af087d557aba502b6 Mon Sep 17 00:00:00 2001 From: micabot Date: Wed, 23 Aug 2017 17:59:51 -0300 Subject: [PATCH 0103/1506] Add models for several objects that were missing Removes Notes model. Adds License, Tag, TagObject association class, Comment. --- server/models.py | 60 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/server/models.py b/server/models.py index c8cce59f991..12e5db11786 100644 --- a/server/models.py +++ b/server/models.py @@ -228,21 +228,6 @@ class Vulnerability(db.Model): ) -class Note(db.Model): - # TODO: review this model - __tablename__ = 'note' - id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - text = Column(Text(), nullable=True) - description = Column(Text(), nullable=True) - owned = Column(Boolean) - - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - workspace = relationship('Workspace') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) - - class Credential(db.Model): # TODO: add unique constraint -> username, host o service y workspace # TODO: add constraint host y service, uno o el otro @@ -456,3 +441,48 @@ class Task(TaskABC): workspace = relationship('Workspace', backref='tasks') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + + +class License(db.Model): + __tablename__ = 'license' + id = Column(Integer, primary_key=True) + product = Column(Text, nullable=False) + start_date = Column(DateTime, nullable=False) + end_date = Column(DateTime, nullable=False) + + type = Column(Text, nullable=True) + notes = Column(Text, nullable=True) + + +class Tag(db.Model): + __tablename__ = 'tag' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False, unique=True) + slug = Column(Text, nullable=False, unique=True) + + +class TagObject(db.Model): + __tablename__ = 'tag_object' + id = Column(Integer, primary_key=True) + + object_id = Column(Integer, nullable=False) + object_type = Column(Text, nullable=False) + + tag = relationship('Tag', backref='tagged_objects') + tag_id = Column(Integer, ForeignKey('tag.id'), index=True) + + +class Comment(db.Model): + __tablename__ = 'comment' + id = Column(Integer, primary_key=True) + + text = Column(Text, nullable=False) + + reply_to = relationship('Comment', backref='replies') + reply_to_id = Column(Integer, ForeignKey('comment.id')) + + object_id = Column(Integer, nullable=False) + object_type = Column(Text, nullable=False) + + workspace = relationship('Workspace') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) From a3c11eeb5be51c1a9908fa9ea45f20217b7c29e6 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 24 Aug 2017 12:33:59 -0300 Subject: [PATCH 0104/1506] add server command class --- server/commands/__init__.py | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 server/commands/__init__.py diff --git a/server/commands/__init__.py b/server/commands/__init__.py new file mode 100644 index 00000000000..232ee31a008 --- /dev/null +++ b/server/commands/__init__.py @@ -0,0 +1,56 @@ +import sys +import getpass +from subprocess import Popen, PIPE + +try: + from configparser import ConfigParser, NoSectionError, NoOptionError +except ImportError: + from ConfigParser import ConfigParser, NoSectionError, NoOptionError + +from flask import current_app +from flask_script import Command + +from server.config import LOCAL_CONFIG_FILE + + +class InitDB(Command): + + def run(self): + database_name = raw_input('Please enter the database name (press enter to use "faraday"): ') or 'faraday' + username = raw_input('Please enter the database user (press enter to use "faraday"): ') or 'faraday' + postgres_command = ['sudo', '-u', 'postgres'] + password = None + while not password: + password = getpass.getpass(prompt='Please enter the password for the postgreSQL username: ') + if not password: + print('Please type a valid password') + command = postgres_command + ['psql', '-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)] + p = Popen(command) + p.wait() + db_server = raw_input('Enter the postgresql address (press enter for localhost): ') or 'localhost' + print('Creating database {0}'.format(database_name)) + command = postgres_command + ['createdb', '-O', username, database_name] + p = Popen(command, stdin=PIPE) + p.wait() + + print('Saving database credentials file in {0}'.format(LOCAL_CONFIG_FILE)) + config = ConfigParser() + config.read(LOCAL_CONFIG_FILE) + try: + config.get('database', 'connection_string') + except NoSectionError: + config.add_section('database') + + conn_string = 'postgresql+psycopg2://{username}:{password}@{server}'.format( + username=username, + password=password, + server=db_server, + ) + config.set('database', 'connection_string', conn_string) + with open(LOCAL_CONFIG_FILE, 'w') as configfile: + config.write(configfile) + + print('Creating tables') + from server.models import db + current_app.config['SQLALCHEMY_DATABASE_URI'] = conn_string + db.create_all() From 6a6ac87373e66d226fe186f5b18fd2e60ffcc1b8 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 24 Aug 2017 14:35:51 -0300 Subject: [PATCH 0105/1506] some improvements on dbinit and code cleanup --- manage.py | 3 +- server/commands/__init__.py | 63 ++++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 15 deletions(-) mode change 100644 => 100755 manage.py diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 index 654c5e5dae5..e8c2ddd696f --- a/manage.py +++ b/manage.py @@ -1,4 +1,5 @@ -# manage.py +#!/usr/bin/env python + from flask_script import Manager diff --git a/server/commands/__init__.py b/server/commands/__init__.py index 232ee31a008..c06cbc3873a 100644 --- a/server/commands/__init__.py +++ b/server/commands/__init__.py @@ -1,3 +1,4 @@ +import os import sys import getpass from subprocess import Popen, PIPE @@ -10,46 +11,80 @@ from flask import current_app from flask_script import Command +from config.globals import CONST_FARADAY_HOME_PATH from server.config import LOCAL_CONFIG_FILE class InitDB(Command): + def _check_current_config(self, config): + try: + config.get('database', 'connection_string') + reconfigure = None + while not reconfigure: + reconfigure = raw_input('Database section \e[31m already found. \e[30m Do you want to reconfigure database? (yes/no) ') + if reconfigure.lower() == 'no': + return False + elif reconfigure.lower() == 'yes': + continue + else: + reconfigure = None + except NoSectionError: + config.add_section('database') + + return True + def run(self): - database_name = raw_input('Please enter the database name (press enter to use "faraday"): ') or 'faraday' - username = raw_input('Please enter the database user (press enter to use "faraday"): ') or 'faraday' + config = ConfigParser() + config.read(LOCAL_CONFIG_FILE) + if not self._check_current_config(config): + return + faraday_path_conf = os.path.expanduser(CONST_FARADAY_HOME_PATH) + psql_log_filename = os.path.join(faraday_path_conf, 'logs', 'psql_log.log') + with open(psql_log_filename, 'a+') as psql_log_file: + username, password = self._configure_postgres(psql_log_file) + database_name = self._create_database(username, psql_log_file) + conn_string = self._save_config(config, username, password, database_name) + self._create_tables(conn_string) + + def _configure_postgres(self, psql_log_file): + username = raw_input('Please enter the \e[21m database user \e[0m (press enter to use "faraday"): ') or 'faraday' postgres_command = ['sudo', '-u', 'postgres'] password = None while not password: - password = getpass.getpass(prompt='Please enter the password for the postgreSQL username: ') + password = getpass.getpass(prompt='Please enter the \e[21m password for the postgreSQL username \e[0m: ') if not password: print('Please type a valid password') command = postgres_command + ['psql', '-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)] - p = Popen(command) + p = Popen(command, stderr=psql_log_file, stdout=psql_log_file) p.wait() - db_server = raw_input('Enter the postgresql address (press enter for localhost): ') or 'localhost' + return username, password + + def _create_database(self, username, psql_log_file): + postgres_command = ['sudo', '-u', 'postgres'] + database_name = raw_input('Please enter the \e[21m database name \e[0m (press enter to use "faraday"): ') or 'faraday' print('Creating database {0}'.format(database_name)) command = postgres_command + ['createdb', '-O', username, database_name] - p = Popen(command, stdin=PIPE) + p = Popen(command, stderr=psql_log_file, stdout=psql_log_file) p.wait() + return database_name - print('Saving database credentials file in {0}'.format(LOCAL_CONFIG_FILE)) - config = ConfigParser() - config.read(LOCAL_CONFIG_FILE) - try: - config.get('database', 'connection_string') - except NoSectionError: - config.add_section('database') + def _save_config(self, config, username, password, database_name): + db_server = 'localhost' + print('\e[5m\e[39mSaving \e[30m database credentials file in {0}'.format(LOCAL_CONFIG_FILE)) - conn_string = 'postgresql+psycopg2://{username}:{password}@{server}'.format( + conn_string = 'postgresql+psycopg2://{username}:{password}@{server}/{database_name}'.format( username=username, password=password, server=db_server, + database_name=database_name ) config.set('database', 'connection_string', conn_string) with open(LOCAL_CONFIG_FILE, 'w') as configfile: config.write(configfile) + return conn_string + def _create_tables(self, conn_string): print('Creating tables') from server.models import db current_app.config['SQLALCHEMY_DATABASE_URI'] = conn_string From f86a58f088577e70e9008e9a67a38d834263fbae Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 24 Aug 2017 14:43:45 -0300 Subject: [PATCH 0106/1506] Add colors and catch keyboard interrupt exception --- server/commands/__init__.py | 39 ++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/server/commands/__init__.py b/server/commands/__init__.py index c06cbc3873a..9e3c09b8c55 100644 --- a/server/commands/__init__.py +++ b/server/commands/__init__.py @@ -10,9 +10,13 @@ from flask import current_app from flask_script import Command +from colorama import init +from colorama import Fore, Back, Style + from config.globals import CONST_FARADAY_HOME_PATH from server.config import LOCAL_CONFIG_FILE +init() class InitDB(Command): @@ -22,7 +26,7 @@ def _check_current_config(self, config): config.get('database', 'connection_string') reconfigure = None while not reconfigure: - reconfigure = raw_input('Database section \e[31m already found. \e[30m Do you want to reconfigure database? (yes/no) ') + reconfigure = raw_input('Database section {red} already found.{white} Do you want to reconfigure database? (yes/no) '.format(red=Fore.RED, white=Fore.WHITE)) if reconfigure.lower() == 'no': return False elif reconfigure.lower() == 'yes': @@ -35,24 +39,27 @@ def _check_current_config(self, config): return True def run(self): - config = ConfigParser() - config.read(LOCAL_CONFIG_FILE) - if not self._check_current_config(config): - return - faraday_path_conf = os.path.expanduser(CONST_FARADAY_HOME_PATH) - psql_log_filename = os.path.join(faraday_path_conf, 'logs', 'psql_log.log') - with open(psql_log_filename, 'a+') as psql_log_file: - username, password = self._configure_postgres(psql_log_file) - database_name = self._create_database(username, psql_log_file) - conn_string = self._save_config(config, username, password, database_name) - self._create_tables(conn_string) + try: + config = ConfigParser() + config.read(LOCAL_CONFIG_FILE) + if not self._check_current_config(config): + return + faraday_path_conf = os.path.expanduser(CONST_FARADAY_HOME_PATH) + psql_log_filename = os.path.join(faraday_path_conf, 'logs', 'psql_log.log') + with open(psql_log_filename, 'a+') as psql_log_file: + username, password = self._configure_postgres(psql_log_file) + database_name = self._create_database(username, psql_log_file) + conn_string = self._save_config(config, username, password, database_name) + self._create_tables(conn_string) + except KeyboardInterrupt: + print('User cancelled.') def _configure_postgres(self, psql_log_file): - username = raw_input('Please enter the \e[21m database user \e[0m (press enter to use "faraday"): ') or 'faraday' + username = raw_input('Please enter the {red} database user {white} (press enter to use "faraday"): '.format(red=Fore.RED, white=Fore.WHITE)) or 'faraday' postgres_command = ['sudo', '-u', 'postgres'] password = None while not password: - password = getpass.getpass(prompt='Please enter the \e[21m password for the postgreSQL username \e[0m: ') + password = getpass.getpass(prompt='Please enter the {red} password for the postgreSQL username {white}: '.format(red=Fore.RED, white=Fore.WHITE)) if not password: print('Please type a valid password') command = postgres_command + ['psql', '-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)] @@ -62,7 +69,7 @@ def _configure_postgres(self, psql_log_file): def _create_database(self, username, psql_log_file): postgres_command = ['sudo', '-u', 'postgres'] - database_name = raw_input('Please enter the \e[21m database name \e[0m (press enter to use "faraday"): ') or 'faraday' + database_name = raw_input('Please enter the {red} database name {white} (press enter to use "faraday"): '.format(red=Fore.RED, white=Fore.WHITE)) or 'faraday' print('Creating database {0}'.format(database_name)) command = postgres_command + ['createdb', '-O', username, database_name] p = Popen(command, stderr=psql_log_file, stdout=psql_log_file) @@ -71,7 +78,7 @@ def _create_database(self, username, psql_log_file): def _save_config(self, config, username, password, database_name): db_server = 'localhost' - print('\e[5m\e[39mSaving \e[30m database credentials file in {0}'.format(LOCAL_CONFIG_FILE)) + print('{red}Saving {white} database credentials file in {0}'.format(LOCAL_CONFIG_FILE, red=Fore.RED, white=Fore.WHITE)) conn_string = 'postgresql+psycopg2://{username}:{password}@{server}/{database_name}'.format( username=username, From a886dc51cdd777113990a7a8fef6bbd0045ec603 Mon Sep 17 00:00:00 2001 From: micabot Date: Thu, 24 Aug 2017 18:20:41 -0300 Subject: [PATCH 0107/1506] Add VulnerabilityABC class - Incomplete commit Removes table names from abstract classes and other changes. --- server/models.py | 55 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/server/models.py b/server/models.py index 12e5db11786..5b54007159a 100644 --- a/server/models.py +++ b/server/models.py @@ -159,13 +159,14 @@ class PolicyViolation(db.Model): ) -class Vulnerability(db.Model): +class VulnerabilityABC(db.Model): # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website, workspace # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien VULN_TYPES = [ 'vuln', 'vuln_web', - 'source_code'] + 'source_code' + ] EASE_OF_RESOLUTIONS = [ 'trivial', 'simple', @@ -179,17 +180,20 @@ class Vulnerability(db.Model): 're-opened', 'risk-accepted' ] - __tablename__ = 'vulnerability' + + __abstract__ = True + id = Column(Integer, primary_key=True) - name = Column(String(250), nullable=False) - description = Column(Text(), nullable=False) - - confirmed = Column(Boolean, default=False) - vuln_type = Column(Enum(*VULN_TYPES)) - data = Column(Text()) - ease_of_resolution = Column(Enum(*EASE_OF_RESOLUTIONS)) - resolution = Column(Text(), nullable=True) - severity = Column(String(50), nullalbe=False) + + confirmed = Column(Boolean, nullable=False, default=False) + data = Column(Text, nullable=True) + description = Column(Text, nullable=False) + ease_of_resolution = Column(Enum(*EASE_OF_RESOLUTIONS), nullable=True) + name = Column(Text, nullable=False) + resolution = Column(Text, nullable=True) + severity = Column(String(50), nullable=False) + status = Column(Enum(*STATUSES), nullable=False, default="open") + vuln_type = Column(Enum(*VULN_TYPES), nullable=False) # TODO add evidence impact_accountability = Column(Boolean, default=False) @@ -197,18 +201,28 @@ class Vulnerability(db.Model): impact_confidentiality = Column(Boolean, default=False) impact_integrity = Column(Boolean, default=False) + # Web Vulns method = Column(String(50), nullable=True) parameters = Column(String(500), nullable=True) - path = Column(String(500), nullable=True) - # REVIEW FROM FROM HERE parameter_name = Column(String(250), nullable=True) + path = Column(String(500), nullable=True) query = Column(Text(), nullable=True) request = Column(Text(), nullable=True) response = Column(Text(), nullable=True) website = Column(String(250), nullable=True) - # open, closed, re-opened, risk-accepted - status = Column(Enum(*STATUSES), nullable=False, default="open") + # Code Vulns + line = Column(Integer, nullable=True) + + entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) + entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + + +class Vulnerability(VulnerabilityABC): + __tablename__ = 'vulnerability' + id = Column(Integer, primary_key=True) + + vuln_type = Column(Enum(*VulnerabilityABC.VULN_TYPES), nullable=False) entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) @@ -228,6 +242,12 @@ class Vulnerability(db.Model): ) +class VulnerabilityTemplate(VulnerabilityABC): + __tablename__ = 'vulnerability_template' + + vuln_type = Column(Enum(*VULN_TYPES), nullable=False) + + class Credential(db.Model): # TODO: add unique constraint -> username, host o service y workspace # TODO: add constraint host y service, uno o el otro @@ -376,7 +396,8 @@ class Methodology(db.Model): class TaskABC(db.Model): - __tablename__ = 'task_abc' + __abstract__ = True + id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) description = Column(Text, nullable=False) From a444be2e789ab6259e8c3ada39d6a997f559a4d3 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 25 Aug 2017 13:13:46 -0300 Subject: [PATCH 0108/1506] Set first database uri and then configure db for app --- server/app.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/server/app.py b/server/app.py index 432ccda75e4..94b530fac6b 100644 --- a/server/app.py +++ b/server/app.py @@ -28,6 +28,12 @@ def create_app(db_connection_string=None, testing=None): app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False if testing: app.config['TESTING'] = testing + try: + app.config['SQLALCHEMY_DATABASE_URI'] = db_connection_string or server.config.database.connection_string.strip("'") + except AttributeError: + print('Missing [database] section on server.ini. Please configure the database before running the server.') + except NoOptionError: + print('Missing connection_string on [database] section on server.ini. Please configure the database before running the server.') from server.models import db db.init_app(app) @@ -48,14 +54,6 @@ def create_app(db_connection_string=None, testing=None): for handler in LOGGING_HANDLERS: app.logger.addHandler(handler) - try: - app.config['SQLALCHEMY_DATABASE_URI'] = db_connection_string or server.config.database.connection_string.strip("'") - except AttributeError: - print('Missing [database] section on server.ini. Please configure the database before running the server.') - sys.exit(1) - except NoOptionError: - print('Missing connection_string on [database] section on server.ini. Please configure the database before running the server.') - sys.exit(1) from server.api.modules.workspaces import workspace_api from server.api.modules.doc import doc_api From 22d67d4f88cba596e63eedd4ac3103a09cbd76cd Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 25 Aug 2017 13:14:09 -0300 Subject: [PATCH 0109/1506] Add command and improve error checking Error checks includes detect missing database installation. --- server/commands/__init__.py | 46 ++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/server/commands/__init__.py b/server/commands/__init__.py index 9e3c09b8c55..1455f61686e 100644 --- a/server/commands/__init__.py +++ b/server/commands/__init__.py @@ -1,6 +1,7 @@ import os import sys import getpass +from tempfile import TemporaryFile from subprocess import Popen, PIPE try: @@ -39,22 +40,50 @@ def _check_current_config(self, config): return True def run(self): + """ + Main entry point that executes these steps: + * creates role in database. + * creates database. + * save new configuration on server.ini. + * creates tables. + """ + current_psql_output = TemporaryFile() try: config = ConfigParser() config.read(LOCAL_CONFIG_FILE) if not self._check_current_config(config): return faraday_path_conf = os.path.expanduser(CONST_FARADAY_HOME_PATH) + # we use psql_log_filename for historical saving. we will ask faraday users this file. + # current_psql_output is for checking psql command already known errors for each execution. psql_log_filename = os.path.join(faraday_path_conf, 'logs', 'psql_log.log') with open(psql_log_filename, 'a+') as psql_log_file: - username, password = self._configure_postgres(psql_log_file) - database_name = self._create_database(username, psql_log_file) + username, password = self._configure_postgres(current_psql_output) + current_psql_output.seek(0) + psql_output = current_psql_output.read() + psql_log_file.write(psql_output) + current_psql_output.seek(0) + psql_output = current_psql_output.read() + self._check_psql_output(psql_output) + database_name = self._create_database(username, current_psql_output) + self._check_psql_output(psql_output) + current_psql_output.close() conn_string = self._save_config(config, username, password, database_name) self._create_tables(conn_string) except KeyboardInterrupt: + current_psql_output.close() print('User cancelled.') + sys.exit(1) + + def _check_psql_output(self, psql_log_output): + if 'unknown user: postgres' in psql_log_output: + raise UserWarning('postgres user not found. did you installed postgresql?') def _configure_postgres(self, psql_log_file): + """ + This step will create the role on the database. + we return username and password and those values will be saved in the config file. + """ username = raw_input('Please enter the {red} database user {white} (press enter to use "faraday"): '.format(red=Fore.RED, white=Fore.WHITE)) or 'faraday' postgres_command = ['sudo', '-u', 'postgres'] password = None @@ -68,6 +97,9 @@ def _configure_postgres(self, psql_log_file): return username, password def _create_database(self, username, psql_log_file): + """ + This step uses the createdb command to add a new database. + """ postgres_command = ['sudo', '-u', 'postgres'] database_name = raw_input('Please enter the {red} database name {white} (press enter to use "faraday"): '.format(red=Fore.RED, white=Fore.WHITE)) or 'faraday' print('Creating database {0}'.format(database_name)) @@ -77,6 +109,9 @@ def _create_database(self, username, psql_log_file): return database_name def _save_config(self, config, username, password, database_name): + """ + This step saves database configuration to server.ini + """ db_server = 'localhost' print('{red}Saving {white} database credentials file in {0}'.format(LOCAL_CONFIG_FILE, red=Fore.RED, white=Fore.WHITE)) @@ -95,4 +130,9 @@ def _create_tables(self, conn_string): print('Creating tables') from server.models import db current_app.config['SQLALCHEMY_DATABASE_URI'] = conn_string - db.create_all() + try: + db.create_all() + except ImportError as ex: + if 'psycopg2' in ex: + print('Missing python depency {red}psycopg2{white}. Please install it with {green}pip install psycopg2'.format(red=Fore.RED, white=Fore.WHITE, green=Fore.GREEN)) + sys.exit(1) From 8f85febe09e28fed94b4f760b67bc0a679399e7e Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 25 Aug 2017 15:11:37 -0300 Subject: [PATCH 0110/1506] Add command to generate db schemas --- manage.py | 14 ++++++++++ server/commands/__init__.py | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100755 manage.py create mode 100644 server/commands/__init__.py diff --git a/manage.py b/manage.py new file mode 100755 index 00000000000..58aae122dcc --- /dev/null +++ b/manage.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + + +from flask_script import Manager + +from server.web import app +from server.commands import DatabaseSchema + +manager = Manager(app) + + +if __name__ == "__main__": + manager.add_command('generate_database_schemas', DatabaseSchema()) + manager.run() diff --git a/server/commands/__init__.py b/server/commands/__init__.py new file mode 100644 index 00000000000..0b134acf51f --- /dev/null +++ b/server/commands/__init__.py @@ -0,0 +1,51 @@ +import sys +from flask_script import Command +from sqlalchemy import MetaData +try: + from sqlalchemy_schemadisplay import create_schema_graph + from sqlalchemy_schemadisplay import create_uml_graph +except ImportError: + print('Please install sqlalchemy_schemadisplay with "pip install sqlalchemy_schemadisplay"') + sys.exit(1) +from sqlalchemy.orm import class_mapper + +from server import models +import server.config + + +class DatabaseSchema(Command): + + def run(self): + self._draw_entity_diagrama() + self._draw_uml_class_diagram() + + def _draw_entity_diagrama(self): + # create the pydot graph object by autoloading all tables via a bound metadata object + graph = create_schema_graph( + metadata=MetaData(server.config.database.connection_string.strip("'")), + show_datatypes=False, # The image would get nasty big if we'd show the datatypes + show_indexes=False, # ditto for indexes + rankdir='LR', # From left to right (instead of top to bottom) + concentrate=False # Don't try to join the relation lines together + ) + graph.write_png('entity_dbschema.png') # write out the file + + def _draw_uml_class_diagram(self): + # lets find all the mappers in our model + mappers = [] + for attr in dir(models): + if attr[0] == '_': + continue + try: + cls = getattr(models, attr) + mappers.append(class_mapper(cls)) + except: + pass + + # pass them to the function and set some formatting options + graph = create_uml_graph( + mappers, + show_operations=False, # not necessary in this case + show_multiplicity_one=False # some people like to see the ones, some don't + ) + graph.write_png('uml_schema.png') # write out the file From ba7c1cde7e6ead96115eb0aaec9d18d7f5f42f91 Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 15:15:52 -0300 Subject: [PATCH 0111/1506] Add Vuln types and SourceCode to models --- server/models.py | 83 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/server/models.py b/server/models.py index 5b54007159a..3366c1d43c0 100644 --- a/server/models.py +++ b/server/models.py @@ -56,6 +56,16 @@ class EntityMetadata(db.Model): document_type = Column(String(250)) +class SourceCode(db.Model): + # TODO: add unique constraint -> filename, workspace + __tablename__ = 'source_code' + id = Column(Integer, primary_key=True) + filename = Column(Text, nullable=False) + + workspace = relationship('Workspace', backref='source_codes') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + + class Host(db.Model): # TODO: add unique constraint -> ip, workspace __tablename__ = 'host' @@ -162,11 +172,6 @@ class PolicyViolation(db.Model): class VulnerabilityABC(db.Model): # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website, workspace # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien - VULN_TYPES = [ - 'vuln', - 'vuln_web', - 'source_code' - ] EASE_OF_RESOLUTIONS = [ 'trivial', 'simple', @@ -218,34 +223,72 @@ class VulnerabilityABC(db.Model): entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) -class Vulnerability(VulnerabilityABC): +class VulnerabilityGeneric(VulnerabilityABC): + VULN_TYPES = [ + 'vulnerability', + 'vulnerability_web', + 'vulnerability_code' + ] + __tablename__ = 'vulnerability' - id = Column(Integer, primary_key=True) - vuln_type = Column(Enum(*VulnerabilityABC.VULN_TYPES), nullable=False) + type = Column(Enum(*VULN_TYPES), nullable=False) + + workspace = relationship('Workspace', backref='vulnerabilities') + workspace_id = Column( + Integer, + ForeignKey('workspace.id'), + index=True, + ) + + __mapper_args__ = { + 'polymorphic_on': type + } + + +class Vulnerability(VulnerabilityGeneric): + id = Column(Integer, primary_key=True) entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + host = relationship('Host', backref='vulnerabilities') host_id = Column(Integer, ForeignKey(Host.id), index=True) - host = relationship('Host', back_populates='vulnerabilities') + service = relationship('Service', backref='vulnerabilities') service_id = Column(Integer, ForeignKey(Service.id), index=True) - service = relationship('Service', back_populates='vulnerabilities') - workspace = relationship('Workspace') - workspace_id = Column( - Integer, - ForeignKey('workspace.id'), - index=True, - back_populates='vulnerabilities' - ) + __mapper_args__ = { + 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[0] + } -class VulnerabilityTemplate(VulnerabilityABC): - __tablename__ = 'vulnerability_template' +class VulnerabilityWeb(VulnerabilityGeneric): + id = Column(Integer, primary_key=True) - vuln_type = Column(Enum(*VULN_TYPES), nullable=False) + entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) + entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + + service = relationship('Service', backref='vulnerabilities') + service_id = Column(Integer, ForeignKey(Service.id), index=True) + + __mapper_args__ = { + 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[1] + } + + +class VulnerabilityCode(VulnerabilityGeneric): + id = Column(Integer, primary_key=True) + + entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) + entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + + source_code = relationship('SourceCode', backref='vulnerabilities') + source_code_id = Column(Integer, ForeignKey('SourceCode.id'), index=True) + + __mapper_args__ = { + 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[2] + } class Credential(db.Model): From 83cdef68eea1f8570c789c5a8ef76f0cc195bb5e Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 15:22:14 -0300 Subject: [PATCH 0112/1506] Add VulnerabilityTemplate class to models Also removes type column from VulnerabilityGeneric class --- server/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index 3366c1d43c0..1316a86c67f 100644 --- a/server/models.py +++ b/server/models.py @@ -198,7 +198,6 @@ class VulnerabilityABC(db.Model): resolution = Column(Text, nullable=True) severity = Column(String(50), nullable=False) status = Column(Enum(*STATUSES), nullable=False, default="open") - vuln_type = Column(Enum(*VULN_TYPES), nullable=False) # TODO add evidence impact_accountability = Column(Boolean, default=False) @@ -223,6 +222,10 @@ class VulnerabilityABC(db.Model): entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) +class VulnerabilityTemplate(VulnerabilityABC): + __tablename__ = 'vulnerability_template' + + class VulnerabilityGeneric(VulnerabilityABC): VULN_TYPES = [ 'vulnerability', From f75227fa2552c6b78555d5ae87fcf2e267f789b7 Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 15:25:40 -0300 Subject: [PATCH 0113/1506] Fix vuln fields in models Removes fields that don't belong in the VulnerabilityABC class and moves them to VulnerabilityGeneric so they won't be present in the Templates table. --- server/models.py | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/server/models.py b/server/models.py index 1316a86c67f..12b7a1e2c82 100644 --- a/server/models.py +++ b/server/models.py @@ -179,25 +179,16 @@ class VulnerabilityABC(db.Model): 'difficult', 'infeasible' ] - STATUSES = [ - 'open', - 'closed', - 're-opened', - 'risk-accepted' - ] __abstract__ = True - id = Column(Integer, primary_key=True) - confirmed = Column(Boolean, nullable=False, default=False) data = Column(Text, nullable=True) description = Column(Text, nullable=False) ease_of_resolution = Column(Enum(*EASE_OF_RESOLUTIONS), nullable=True) name = Column(Text, nullable=False) resolution = Column(Text, nullable=True) severity = Column(String(50), nullable=False) - status = Column(Enum(*STATUSES), nullable=False, default="open") # TODO add evidence impact_accountability = Column(Boolean, default=False) @@ -205,19 +196,6 @@ class VulnerabilityABC(db.Model): impact_confidentiality = Column(Boolean, default=False) impact_integrity = Column(Boolean, default=False) - # Web Vulns - method = Column(String(50), nullable=True) - parameters = Column(String(500), nullable=True) - parameter_name = Column(String(250), nullable=True) - path = Column(String(500), nullable=True) - query = Column(Text(), nullable=True) - request = Column(Text(), nullable=True) - response = Column(Text(), nullable=True) - website = Column(String(250), nullable=True) - - # Code Vulns - line = Column(Integer, nullable=True) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) @@ -227,6 +205,12 @@ class VulnerabilityTemplate(VulnerabilityABC): class VulnerabilityGeneric(VulnerabilityABC): + STATUSES = [ + 'open', + 'closed', + 're-opened', + 'risk-accepted' + ] VULN_TYPES = [ 'vulnerability', 'vulnerability_web', @@ -234,7 +218,8 @@ class VulnerabilityGeneric(VulnerabilityABC): ] __tablename__ = 'vulnerability' - + confirmed = Column(Boolean, nullable=False, default=False) + status = Column(Enum(*STATUSES), nullable=False, default="open") type = Column(Enum(*VULN_TYPES), nullable=False) workspace = relationship('Workspace', backref='vulnerabilities') @@ -268,6 +253,14 @@ class Vulnerability(VulnerabilityGeneric): class VulnerabilityWeb(VulnerabilityGeneric): id = Column(Integer, primary_key=True) + method = Column(String(50), nullable=True) + parameters = Column(String(500), nullable=True) + parameter_name = Column(String(250), nullable=True) + path = Column(String(500), nullable=True) + query = Column(Text(), nullable=True) + request = Column(Text(), nullable=True) + response = Column(Text(), nullable=True) + website = Column(String(250), nullable=True) entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) @@ -282,6 +275,7 @@ class VulnerabilityWeb(VulnerabilityGeneric): class VulnerabilityCode(VulnerabilityGeneric): id = Column(Integer, primary_key=True) + line = Column(Integer, nullable=True) entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) From 1e26cc657a358fa8005e9d3a39faa712116b2687 Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 15:44:05 -0300 Subject: [PATCH 0114/1506] Remove metadata from Vulnerabilities --- server/models.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/server/models.py b/server/models.py index 12b7a1e2c82..2e379359def 100644 --- a/server/models.py +++ b/server/models.py @@ -2,30 +2,29 @@ # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information from sqlalchemy import ( + Boolean, Column, + DateTime, + Enum, + Float, + ForeignKey, Integer, String, - Boolean, - ForeignKey, - Float, Text, UniqueConstraint, - DateTime, - Enum, ) from sqlalchemy.orm import relationship, backref from flask_sqlalchemy import SQLAlchemy from flask_security import ( - UserMixin, RoleMixin, + UserMixin, ) import server.config db = SQLAlchemy() - -SCHEMA_VERSION = 'W.2.6.3' +SCHEMA_VERSION = 'W.3.0.0' class DatabaseMetadata(db.Model): @@ -196,9 +195,6 @@ class VulnerabilityABC(db.Model): impact_confidentiality = Column(Boolean, default=False) impact_integrity = Column(Boolean, default=False) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - class VulnerabilityTemplate(VulnerabilityABC): __tablename__ = 'vulnerability_template' From 041956ebeee1a6d7dd0cca0b8e4c37b333eb50b7 Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 15:58:12 -0300 Subject: [PATCH 0115/1506] Fix typos and make models work The file server/models.py is now clean and doesn't throw any errors on server initialization. --- server/models.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/server/models.py b/server/models.py index 2e379359def..37e9bf586ce 100644 --- a/server/models.py +++ b/server/models.py @@ -231,24 +231,22 @@ class VulnerabilityGeneric(VulnerabilityABC): class Vulnerability(VulnerabilityGeneric): - id = Column(Integer, primary_key=True) - - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - host = relationship('Host', backref='vulnerabilities') host_id = Column(Integer, ForeignKey(Host.id), index=True) service = relationship('Service', backref='vulnerabilities') service_id = Column(Integer, ForeignKey(Service.id), index=True) + __table_args__ = { + 'extend_existing': True + } + __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[0] } class VulnerabilityWeb(VulnerabilityGeneric): - id = Column(Integer, primary_key=True) method = Column(String(50), nullable=True) parameters = Column(String(500), nullable=True) parameter_name = Column(String(250), nullable=True) @@ -258,27 +256,28 @@ class VulnerabilityWeb(VulnerabilityGeneric): response = Column(Text(), nullable=True) website = Column(String(250), nullable=True) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - - service = relationship('Service', backref='vulnerabilities') + service = relationship('Service', backref='vulnerabilities_web') service_id = Column(Integer, ForeignKey(Service.id), index=True) + __table_args__ = { + 'extend_existing': True + } + __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[1] } class VulnerabilityCode(VulnerabilityGeneric): - id = Column(Integer, primary_key=True) line = Column(Integer, nullable=True) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - source_code = relationship('SourceCode', backref='vulnerabilities') source_code_id = Column(Integer, ForeignKey('SourceCode.id'), index=True) + __table_args__ = { + 'extend_existing': True + } + __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[2] } @@ -298,10 +297,10 @@ class Credential(db.Model): entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) host = relationship('Host', backref='credentials') - host_id = Column(Integer, ForeignKey(Host.id), index=True, nullalbe=True) + host_id = Column(Integer, ForeignKey(Host.id), index=True, nullable=True) service = relationship('Service', backref='credentials') - service_id = Column(Integer, ForeignKey(Service.id), index=True, nullalbe=True) + service_id = Column(Integer, ForeignKey(Service.id), index=True, nullable=True) workspace = relationship('Workspace', backref='credentials') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) @@ -367,7 +366,7 @@ class User(db.Model, UserMixin): username = Column(String(255), unique=True, nullable=False) password = Column(String(255), nullable=True) email = Column(String(255), unique=True, nullable=True) # TBI - name = Column(String(255), nullalbe=True) # TBI + name = Column(String(255), nullable=True) # TBI is_ldap = Column(Boolean(), nullable=False, default=False) last_login_at = Column(DateTime()) # flask-security current_login_at = Column(DateTime()) # flask-security From f9a4994fced615f425497772524c704cbd8695c3 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 25 Aug 2017 18:05:34 -0300 Subject: [PATCH 0116/1506] Fix a bug, set SQLALCHEMY_DATABASE_URI before than init db --- server/app.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/server/app.py b/server/app.py index 432ccda75e4..6e79a079bc2 100644 --- a/server/app.py +++ b/server/app.py @@ -28,6 +28,14 @@ def create_app(db_connection_string=None, testing=None): app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False if testing: app.config['TESTING'] = testing + try: + app.config['SQLALCHEMY_DATABASE_URI'] = db_connection_string or server.config.database.connection_string.strip("'") + except AttributeError: + print('Missing [database] section on server.ini. Please configure the database before running the server.') + sys.exit(1) + except NoOptionError: + print('Missing connection_string on [database] section on server.ini. Please configure the database before running the server.') + sys.exit(1) from server.models import db db.init_app(app) @@ -48,15 +56,6 @@ def create_app(db_connection_string=None, testing=None): for handler in LOGGING_HANDLERS: app.logger.addHandler(handler) - try: - app.config['SQLALCHEMY_DATABASE_URI'] = db_connection_string or server.config.database.connection_string.strip("'") - except AttributeError: - print('Missing [database] section on server.ini. Please configure the database before running the server.') - sys.exit(1) - except NoOptionError: - print('Missing connection_string on [database] section on server.ini. Please configure the database before running the server.') - sys.exit(1) - from server.api.modules.workspaces import workspace_api from server.api.modules.doc import doc_api from server.api.modules.interfaces import interfaces_api From 05d799af2427b7ce9f750279beac79204455e737 Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 18:31:51 -0300 Subject: [PATCH 0117/1506] Remove Interface logic to get the Server up There's still some in the repo, this commit only removes enough to avoid errors during Server bootstrap. --- apis/rest/api.py | 12 -- gui/gtk/dialogs.py | 210 ++++++------------- persistence/server/models.py | 342 +++++++++++++------------------ server/api/modules/interfaces.py | 28 --- server/api/modules/notes.py | 45 ---- server/api/modules/workspaces.py | 6 - server/app.py | 4 - server/dao/host.py | 12 +- server/dao/interface.py | 82 -------- server/dao/note.py | 76 ------- server/dao/service.py | 13 +- server/dao/vuln.py | 55 ++--- server/dao/workspace.py | 25 +-- server/importer.py | 54 +---- server/models.py | 153 +++++++++++--- 15 files changed, 361 insertions(+), 756 deletions(-) delete mode 100644 server/api/modules/interfaces.py delete mode 100644 server/api/modules/notes.py delete mode 100644 server/dao/interface.py delete mode 100644 server/dao/note.py diff --git a/apis/rest/api.py b/apis/rest/api.py index af0e86c39cb..3810bab056f 100644 --- a/apis/rest/api.py +++ b/apis/rest/api.py @@ -126,10 +126,6 @@ def getRoutes(self): view_func=self.listWebVulns, methods=['GET'])) - routes.append(Route(path='/model/interface', - view_func=self.createInterface, - methods=['PUT'])) - routes.append(Route(path='/model/service', view_func=self.createService, methods=['PUT'])) @@ -245,14 +241,6 @@ def createHost(self): self.controller.newHost, ['name', 'os']) - def createInterface(self): - return self._create( - self.controller.newInterface, - ['name', 'mac', 'ipv6_address', 'ipv4_mask', 'ipv4_gateway', - 'ipv4_dns', 'ipv6_address', 'ipv6_prefix', 'ipv6_gateway', - 'ipv6_dns', 'network_segment', 'hostname_resolution', - 'parent_id']) - def createService(self): return self._create( self.controller.newService, diff --git a/gui/gtk/dialogs.py b/gui/gtk/dialogs.py index 519db94a65b..96f96d047ed 100644 --- a/gui/gtk/dialogs.py +++ b/gui/gtk/dialogs.py @@ -566,7 +566,7 @@ def type_faraday_plugin_command(self, plugin): class HostInfoDialog(Gtk.Window): """Sets the blueprints for a simple host info window. It will display - basic information in labels as well as interfaces/services in a treeview. + basic information in labels as well as services in a treeview. While working in this class, keep in mind the distinction between selections (which are part of a model that holds data about an object as @@ -715,20 +715,15 @@ def create_vuln_list(self): def create_model(self, host): """Return a model for the given host. It holds quite a bit of info. It has 15 columns holding the host ID and name as parent, - all the information about the interfaces of that host and all the - information about the services of those interfaces. + all the information about the services. The model is difficult to draw because of its nested nature, but you can think of it like this, keeping in mind each node has several columns HOST - -----> INTERFACE1 - ------------> SERVICE1 - ------------> SERVICE2 - -----> INTERFACE2 - -----------> SERVICE1 - -----------> SERVICE2 + ------------> SERVICE1 + ------------> SERVICE2 And so on and so on, like Zizek says. """ @@ -758,60 +753,10 @@ def lst_to_str(lst): """Convenient function to avoid this long line everywhere""" return ', '.join([str(word) for word in lst if word]) - def add_interface_to_host_in_model(interface, host_pos, model): - """Append an interface to the host within a model. - Return the tree_iter represeting the position of the interface - within the model. Modifies the model. - """ - ipv4_dic = interface.getIPv4() - ipv6_dic = interface.getIPv6() - display_str = str(interface) - - position = model.append(host_pos, [interface.getID(), - interface.getName(), - interface.getDescription(), - interface.getMAC(), - ipv4_dic['mask'], - ipv4_dic['gateway'], - lst_to_str(ipv4_dic['DNS']), - ipv4_dic['address'], - ipv6_dic['prefix'], - ipv6_dic['gateway'], - lst_to_str(ipv6_dic['DNS']), - ipv6_dic['address'], - display_str]) - return position - - def add_service_to_interface_in_model(service, interface_pos, model): - """Append a service to an interface at interface_pos in the given - model. Return None. Modifies the model""" - display_str = service.getName() + " (" + str(len(service.getVulns())) + ")" - model.append(interface_pos, [service.getID(), - service.getName(), - service.getDescription(), - service.getProtocol(), - service.getStatus(), - lst_to_str(service.getPorts()), - service.getVersion(), - "Yes" if service.isOwned() else "No", - "", "", "", "", display_str]) - - interfaces = host.getAllInterfaces() - for interface in interfaces: - interface_position = add_interface_to_host_in_model(interface, - host_position, - model) - services = interface.getAllServices() - for service in services: - add_service_to_interface_in_model(service, interface_position, - model) - - return model - @scrollable(width=250) def create_main_tree_view(self, model): """Return a box containing the main tree (the one showing - Host/Interfaces/Services) as its content. + Host/Services) as its content. """ view = Gtk.TreeView(model) view.set_activate_on_single_click(True) @@ -820,7 +765,7 @@ def create_main_tree_view(self, model): view.expand_all() renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Host/Interfaces/Services", + column = Gtk.TreeViewColumn("Host/Services", renderer, text=12) view.append_column(column) @@ -843,12 +788,12 @@ def on_main_tree_selection(self, tree_selection): object_info = model[tree_iter] iter_depth = model.iter_depth(tree_iter) - object_type = {0: 'Host', 1: 'Interface', 2: 'Service'}[iter_depth] + object_type = {0: 'Host', 1: 'Service'}[iter_depth] if object_type == 'Host': self.set_vuln_model(self.create_vuln_model(self.host)) - elif object_type == 'Interface' or object_type == 'Service': + elif object_type == 'Service': self.clear(self.specific_info) self.change_label_in_frame(self.specific_info_frame, object_type) prop_names = self.get_properties_names(object_type) @@ -999,38 +944,33 @@ def safe_wrapper(*args, **kwargs): return safe_wrapper object_id = selected_object[0] - if object_type == 'Interface': - # an interface is a direct child of a host - object_ = safely(self.host.getInterface)(object_id) - elif object_type == 'Service': - # a service is a grand-child of a host, so we should look - # for its parent interface and ask her about the child - parent_interface_iter = selected_object.get_parent() - parent_interface_id = parent_interface_iter[0] - parent_interface = safely(self.host.getInterface)(parent_interface_id) - if parent_interface: - object_ = safely(parent_interface.getService)(object_id) - else: - object_ = None + object_ = None + # TODO: fix this code to get services removing interface logic + # if object_type == 'Interface': + # # an interface is a direct child of a host + # object_ = safely(self.host.getInterface)(object_id) + # elif object_type == 'Service': + # # a service is a grand-child of a host, so we should look + # # for its parent interface and ask her about the child + # parent_interface_iter = selected_object.get_parent() + # parent_interface_id = parent_interface_iter[0] + # parent_interface = safely(self.host.getInterface)(parent_interface_id) + # if parent_interface: + # object_ = safely(parent_interface.getService)(object_id) + # else: + # object_ = None return object_ def get_properties_names(self, object_type): """Return a list with the property names for objects of type - Interface, Service, Vulnerability and VulnerabilityWeb (passed as a + Service, Vulnerability and VulnerabilityWeb (passed as a string). """ if object_type == "Host": property_names = ["Name: ", "OS: ", "Owned: ", "Vulnerabilities: "] - elif object_type == "Interface": - property_names = ["Name: ", "Description: ", "MAC: ", - "IPv4 Mask: ", "IPv4 Gateway: ", "IPv4 DNS: ", - "IPv4 Address: ", "IPv6 Prefix: ", - "IPv6 Gateway", "IPv6 DNS: ", - "IPv6 Address: "] - elif object_type == "Service": property_names = ["Name: ", "Description: ", "Protocol: ", "Status: ", "Port: ", "Version: ", "Is Owned?: "] @@ -1148,15 +1088,16 @@ def save(self, button, keeper): elif keeper == "left": n = 1 - # interface needs a special case, 'cause it's the only object - # which resolveConflict() will expect its solution to have a - # dicitionary inside the solution dictionary - if current_conflict_type != "Interface": - solution = {} - for row in self.current_conflict_model: - solution[row[0].lower()] = self.uncook(row[n], row[4]) - else: - solution = self.case_for_interfaces(self.current_conflict_model, n) + # TODO: fix this code to remove interface logic + # # interface needs a special case, 'cause it's the only object + # # which resolveConflict() will expect its solution to have a + # # dicitionary inside the solution dictionary + # if current_conflict_type != "Interface": + # solution = {} + # for row in self.current_conflict_model: + # solution[row[0].lower()] = self.uncook(row[n], row[4]) + # else: + # solution = self.case_for_interfaces(self.current_conflict_model, n) try: guiapi.resolveConflict(self.current_conflict, solution) @@ -1191,32 +1132,33 @@ def save(self, button, keeper): dialog.destroy() self._next_conflict_or_close() - def case_for_interfaces(self, model, n): - """The custom case for the interfaces. Plays a little - with the information in the given model to create a solution acceptable - by resolveConflict. n is the right or left view, should be - either 1 or 2 as integers""" - solution = {} - solution["ipv4"] = {} - solution["ipv6"] = {} - for row in model: - prop_name = row[0].lower() - if prop_name.startswith("ipv4"): - prop_name = prop_name.split(" ")[1] - if not prop_name.startswith("dns"): - solution["ipv4"][prop_name] = self.uncook(row[n], row[4]) - elif prop_name.startswith("dns"): - solution["ipv4"]["DNS"] = self.uncook(row[n], row[4]) - - elif prop_name.startswith("ipv6"): - prop_name = prop_name.split(" ")[1] - if not prop_name.startswith("dns"): - solution["ipv6"][prop_name] = self.uncook(row[n], row[4]) - elif prop_name.startswith("dns"): - solution["ipv6"]["DNS"] = self.uncook(row[n], row[4]) - else: - solution[prop_name] = self.uncook(row[n], row[4]) - return solution + # TODO: refactor or remove this code + # def case_for_interfaces(self, model, n): + # """The custom case for the interfaces. Plays a little + # with the information in the given model to create a solution acceptable + # by resolveConflict. n is the right or left view, should be + # either 1 or 2 as integers""" + # solution = {} + # solution["ipv4"] = {} + # solution["ipv6"] = {} + # for row in model: + # prop_name = row[0].lower() + # if prop_name.startswith("ipv4"): + # prop_name = prop_name.split(" ")[1] + # if not prop_name.startswith("dns"): + # solution["ipv4"][prop_name] = self.uncook(row[n], row[4]) + # elif prop_name.startswith("dns"): + # solution["ipv4"]["DNS"] = self.uncook(row[n], row[4]) + + # elif prop_name.startswith("ipv6"): + # prop_name = prop_name.split(" ")[1] + # if not prop_name.startswith("dns"): + # solution["ipv6"][prop_name] = self.uncook(row[n], row[4]) + # elif prop_name.startswith("dns"): + # solution["ipv6"]["DNS"] = self.uncook(row[n], row[4]) + # else: + # solution[prop_name] = self.uncook(row[n], row[4]) + # return solution def on_quit(self, button): """Exits the window""" @@ -1321,8 +1263,6 @@ def create_conflicts_models(self, conflicts): if conflict_type == "Service": self.fill_service_conflict_model(model, obj1, obj2) - elif conflict_type == "Interface": - self.fill_interface_conflict_model(model, obj1, obj2) elif conflict_type == "Host": self.fill_host_conflict_model(model, obj1, obj2) elif conflict_type == "Vulnerability": @@ -1371,34 +1311,6 @@ def fill_host_conflict_model(self, model, obj1, obj2): model = self.fill_model_from_props_and_attr(model, attr, props) return model - def fill_interface_conflict_model(self, model, obj1, obj2): - """ - Precondition: the model has 5 string columns, obj1 && obj2 are - interfaces - Will get a model and two objects and return a - model with all the appropiate information""" - attr = [] - for obj in [obj1, obj2]: - attr.append((obj.getName(), - obj.getDescription(), - obj.getHostnames(), - obj.getMAC(), - obj.getIPv4Address(), - obj.getIPv4Mask(), - obj.getIPv4Gateway(), - obj.getIPv4DNS(), - obj.getIPv6Address(), - obj.getIPv6Gateway(), - obj.getIPv6DNS(), - obj.isOwned())) - - props = ["Name", "Description", "Hostnames", "MAC", "IPv4 Address", - "IPv4 Mask", "IPv4 Gateway", "IPv4 DNS", "IPv6 Address", - "IPv6 Gateway", "IPv6 DNS", "Owned"] - - model = self.fill_model_from_props_and_attr(model, attr, props) - return model - def fill_vuln_conflict_model(self, model, obj1, obj2): """ Precondition: the model has 5 string columns, obj1 && obj2 are vulns diff --git a/persistence/server/models.py b/persistence/server/models.py index d0808f0a0b4..79a569b36b6 100644 --- a/persistence/server/models.py +++ b/persistence/server/models.py @@ -19,7 +19,6 @@ from persistence.server.utils import (force_unique, get_hash, get_host_properties, - get_interface_properties, get_service_properties, get_vuln_properties, get_vuln_web_properties, @@ -102,17 +101,16 @@ def _flatten_dictionary(dictionary): def _get_faraday_ready_objects(workspace_name, faraday_ready_object_dictionaries, faraday_object_name): """Takes a workspace name, a faraday object ('hosts', 'vulns', - 'interfaces' or 'services') a row_name (the name of the row where + or 'services') a row_name (the name of the row where the information about the objects live) and an arbitray number of params to customize to request. Return a list of faraday objects: either - Host, Interface, Service, Vuln, VulnWeb, Credential or Command. + Host, Service, Vuln, VulnWeb, Credential or Command. """ object_to_class = {'hosts': Host, 'vulns': Vuln, 'vulns_web': VulnWeb, - 'interfaces': Interface, 'services': Service, 'notes': Note, 'credentials': Credential, @@ -150,10 +148,6 @@ def _get_faraday_ready_services(workspace_name, services_dictionaries): """Return a list of Services created with the information found on services_dictionaries""" return _get_faraday_ready_objects(workspace_name, services_dictionaries, 'services') -def _get_faraday_ready_interfaces(workspace_name, interfaces_dictionaries): - """Return a list of Interfaces created with the information found on interfaces_dictionaries""" - return _get_faraday_ready_objects(workspace_name, interfaces_dictionaries, 'interfaces') - def _get_faraday_ready_credentials(workspace_name, credentials_dictionaries): """Return a list of Credentials created with the information found on credentials_dictionaries""" return _get_faraday_ready_objects(workspace_name, credentials_dictionaries, 'credentials') @@ -224,19 +218,6 @@ def get_web_vuln(workspace_name, vuln_id): """Return the WebVuln of id vuln_id. None if not found.""" return force_unique(get_web_vulns(workspace_name, couchid=vuln_id)) -def get_interfaces(workspace_name, **params): - """Take a workspace name and a arbitrary number of params to customize the - request. - - Return a list of Interfaces objects - """ - interfaces_dictionaries = server.get_interfaces(workspace_name, **params) - return _get_faraday_ready_interfaces(workspace_name, interfaces_dictionaries) - -def get_interface(workspace_name, interface_id): - """Return the Interface of id interface_id. None if not found.""" - return force_unique(get_interfaces(workspace_name, couchid=interface_id)) - def get_services(workspace_name, **params): """Take a workspace name and a arbitrary number of params to customize the request. @@ -300,13 +281,12 @@ def get_object(workspace_name, object_signature, object_id): about 'object_signature' objects matching the query. object_signature must be either 'Host', 'Vulnerability', 'VulnerabilityWeb', - 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. + 'Service', 'Cred', 'Note' or 'CommandRunInformation'. Will raise an WrongObjectSignature error if this condition is not met. """ object_to_func = {Host.class_signature: get_host, Vuln.class_signature: get_vuln, VulnWeb.class_signature: get_web_vuln, - Interface.class_signature: get_interface, Service.class_signature: get_service, Credential.class_signature: get_credential, Note.class_signature: get_note, @@ -342,23 +322,6 @@ def update_host(workspace_name, host): host_properties = get_host_properties(host) return server.update_host(workspace_name, **host_properties) -@_ignore_in_changes -def create_interface(workspace_name, interface): - """Take a workspace_name and an interface object and save it to the sever. - Return the server's json response as a dictionary. - """ - interface_properties = get_interface_properties(interface) - return server.create_interface(workspace_name, **interface_properties) - -@_ignore_in_changes -def update_interface(workspace_name, interface): - """Take a workspace_name and an interface object and update it in the sever. - - Return the server's json response as a dictionary. - """ - interface_properties = get_interface_properties(interface) - return server.update_interface(workspace_name, **interface_properties) - @_ignore_in_changes def create_service(workspace_name, service): """Take a workspace_name and a service object and save it to the sever. @@ -464,13 +427,12 @@ def create_object(workspace_name, object_signature, obj): object_signature must match the type of the object. object_signature must be either 'Host', 'Vulnerability', 'VulnerabilityWeb', - 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. + 'Service', 'Cred', 'Note' or 'CommandRunInformation'. Will raise an WrongObjectSignature error if this condition is not met. """ object_to_func = {Host.class_signature: create_host, Vuln.class_signature: create_vuln, VulnWeb.class_signature: create_vuln_web, - Interface.class_signature: create_interface, Service.class_signature: create_service, Credential.class_signature: create_credential, Note.class_signature: create_note, @@ -489,14 +451,13 @@ def update_object(workspace_name, object_signature, obj): object_signature must match the type of the object. object_signature must be either 'Host', 'Vulnerability', 'VulnerabilityWeb', - 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. + 'Service', 'Cred', 'Note' or 'CommandRunInformation'. Will raise an WrongObjectSignature error if this condition is not met. """ object_to_func = {Host.class_signature: update_host, Vuln.class_signature: update_vuln, VulnWeb.class_signature: update_vuln_web, - Interface.class_signature: update_interface, Service.class_signature: update_service, Credential.class_signature: update_credential, Note.class_signature: update_note, @@ -527,7 +488,7 @@ def get_workspace_summary(workspace_name): return server.get_workspace_summary(workspace_name) def get_workspace_numbers(workspace_name): - """Return a tuple with the number of hosts, interfaces, services and vulns + """Return a tuple with the number of hosts, services and vulns on the workspace of name workspace_name. """ return server.get_workspace_numbers(workspace_name) @@ -542,18 +503,13 @@ def get_services_number(workspace_name, **params): """ return server.get_services_number(workspace_name, **params) -def get_interfaces_number(workspace_name, **params): - """Return the number of interfaces found on the workspace of name workspace_name - """ - return server.get_interfaces_number(workspace_name, **params) - def get_vulns_number(workspace_name, **params): """Return the number of vulns found on the workspace of name workspace_name """ return server.get_vulns_number(workspace_name, **params) # NOTE: the delete functions are actually the same. -# there's no difference between delete_host and delete_interface +# there's no difference between delete_host and # except for their names. # maybe implement some kind of validation in the future? @@ -564,13 +520,6 @@ def delete_host(workspace_name, host_id): """ return server.delete_host(workspace_name, host_id) -@_ignore_in_changes -def delete_interface(workspace_name, interface_id): - """Delete the interface of id interface_id on workspace workspace_name. - Return the json response from the server. - """ - return server.delete_interface(workspace_name, interface_id) - @_ignore_in_changes def delete_service(workspace_name, service_id): """Delete the service of id service_id on workspace workspace_name. @@ -617,13 +566,12 @@ def delete_object(workspace_name, object_signature, obj_id): """Given a workspace name, an object_signature as string and an object id. object_signature must be either 'Host', 'Vulnerability', 'VulnerabilityWeb', - 'Interface', 'Service', 'Cred', 'Note' or 'CommandRunInformation'. + 'Service', 'Cred', 'Note' or 'CommandRunInformation'. Will raise an WrongObjectSignature error if this condition is not met. """ object_to_func = {Host.class_signature: delete_host, Vuln.class_signature: delete_vuln, VulnWeb.class_signature: delete_vuln_web, - Interface.class_signature: delete_interface, Service.class_signature: delete_service, Credential.class_signature: delete_credential, Note.class_signature: delete_note, @@ -809,147 +757,147 @@ def getID(self): return self.id def getDefaultGateway(self): return self.default_gateway def getVulns(self): return get_all_vulns(self._workspace_name, hostid=self._server_id) - def getInterface(self, interface_couch_id): - service = get_interfaces(self._workspace_name, couchid=interface_couch_id) - return service[0] - def getAllInterfaces(self): - return get_interfaces(self._workspace_name, host=self._server_id) + # def getInterface(self, interface_couch_id): + # service = get_interfaces(self._workspace_name, couchid=interface_couch_id) + # return service[0] + # def getAllInterfaces(self): + # return get_interfaces(self._workspace_name, host=self._server_id) def getServices(self): return get_services(self._workspace_name, hostid=self._server_id) -class Interface(ModelBase): - """A simple Interface class. Should implement all the methods of the - Interface object in Model.Host - Any method here more than a couple of lines long probably represent - a search the server is missing. - """ - class_signature = 'Interface' - - def __init__(self, interface, workspace_name): - ModelBase.__init__(self, interface, workspace_name) - self.hostnames = interface.get('hostnames', []) - - # NOTE. i don't know why this is like this - # probably a remnant of the old faraday style classes - try: - self.ipv4 = interface['ipv4'] - self.ipv6 = interface['ipv6'] - except KeyError: - self.ipv4 = {'address': interface['ipv4_address'], - 'gateway': interface['ipv4_gateway'], - 'mask': interface['ipv4_mask'], - 'DNS': interface['ipv4_dns']} - self.ipv6 = {'address': interface['ipv6_address'], - 'gateway': interface['ipv6_gateway'], - 'prefix': interface['ipv6_prefix'], - 'DNS': interface['ipv6_dns']} - self.mac = interface.get('mac') - self.network_segment = interface.get('network_segment') - self.ports = interface.get('ports') - - self.amount_ports_opened = 0 - self.amount_ports_closed = 0 - self.amount_ports_filtered = 0 - - def setID(self, parent_id): - try: - ipv4_address = self.ipv4_address - ipv6_address = self.ipv6_address - except AttributeError: - ipv4_address = self.ipv4['address'] - ipv6_address = self.ipv6['address'] - - ModelBase.setID(self, parent_id, self.network_segment, ipv4_address, ipv6_address) - - @staticmethod - def publicattrsrefs(): - publicattrs = dict(ModelBase.publicattrsrefs(), **{ - 'MAC Address' : 'mac', - 'IPV4 Settings' : 'ipv4', - 'IPV6 Settings' : 'ipv6', - 'Network Segment' : 'network_segment', - 'Hostnames' : 'hostnames' - }) - return publicattrs - - def tieBreakable(self, property_key): - """ - Return true if we can auto resolve this conflict. - """ - if property_key in ["hostnames"]: - return True - return False - - def tieBreak(self, key, prop1, prop2): - """ - Return the 'choosen one' - Return a tuple with prop1, prop2 if we cant resolve conflict. - """ - if key == "hostnames": - prop1.extend(prop2) - # Remove duplicated with set... - return list(set(prop1)) - - return (prop1, prop2) - - def updateAttributes(self, name=None, description=None, hostnames=None, mac=None, ipv4=None, ipv6=None, - network_segment=None, amount_ports_opened=None, amount_ports_closed=None, - amount_ports_filtered=None, owned=None): - - if name is not None: - self.name = name - if description is not None: - self.description = description - if hostnames is not None: - self.hostnames = hostnames - if mac is not None: - self.mac = mac - if ipv4 is not None: - self.ipv4 = ipv4 - if ipv6 is not None: - self.ipv6 = ipv6 - if network_segment is not None: - self.network_segment = network_segment - if amount_ports_opened is not None: - self.setPortsOpened(amount_ports_opened) - if amount_ports_closed is not None: - self.setPortsClosed(amount_ports_closed) - if amount_ports_filtered is not None: - self.setPortsFiltered(amount_ports_filtered) - if owned is not None: - self.owned = owned - - def setPortsOpened(self, ports_opened): - self.amount_ports_opened = ports_opened - - def setPortsClosed(self, ports_closed): - self.amount_ports_closed = ports_closed - - def setPortsFiltered(self, ports_filtered): - self.amount_ports_filtered = ports_filtered - - def __str__(self): return "{0}".format(self.name) - def getID(self): return self.id - def getHostnames(self): return self.hostnames - def getIPv4(self): return self.ipv4 - def getIPv6(self): return self.ipv6 - def getIPv4Address(self): return self.ipv4['address'] - def getIPv4Mask(self): return self.ipv4['mask'] - def getIPv4Gateway(self): return self.ipv4['gateway'] - def getIPv4DNS(self): return self.ipv4['DNS'] - def getIPv6Address(self): return self.ipv6['address'] - def getIPv6Gateway(self): return self.ipv6['gateway'] - def getIPv6DNS(self): return self.ipv6['DNS'] - def getMAC(self): return self.mac - def getNetworkSegment(self): return self.network_segment - - def getService(self, service_couch_id): - return get_service(self._workspace_name, service_couch_id) - def getAllServices(self): - return get_services(self._workspace_name, interface=self._server_id) - def getVulns(self): - return get_all_vulns(self._workspace_name, interfaceid=self._server_id) +# class Interface(ModelBase): +# """A simple Interface class. Should implement all the methods of the +# Interface object in Model.Host +# Any method here more than a couple of lines long probably represent +# a search the server is missing. +# """ +# class_signature = 'Interface' + +# def __init__(self, interface, workspace_name): +# ModelBase.__init__(self, interface, workspace_name) +# self.hostnames = interface.get('hostnames', []) + +# # NOTE. i don't know why this is like this +# # probably a remnant of the old faraday style classes +# try: +# self.ipv4 = interface['ipv4'] +# self.ipv6 = interface['ipv6'] +# except KeyError: +# self.ipv4 = {'address': interface['ipv4_address'], +# 'gateway': interface['ipv4_gateway'], +# 'mask': interface['ipv4_mask'], +# 'DNS': interface['ipv4_dns']} +# self.ipv6 = {'address': interface['ipv6_address'], +# 'gateway': interface['ipv6_gateway'], +# 'prefix': interface['ipv6_prefix'], +# 'DNS': interface['ipv6_dns']} +# self.mac = interface.get('mac') +# self.network_segment = interface.get('network_segment') +# self.ports = interface.get('ports') + +# self.amount_ports_opened = 0 +# self.amount_ports_closed = 0 +# self.amount_ports_filtered = 0 + +# def setID(self, parent_id): +# try: +# ipv4_address = self.ipv4_address +# ipv6_address = self.ipv6_address +# except AttributeError: +# ipv4_address = self.ipv4['address'] +# ipv6_address = self.ipv6['address'] + +# ModelBase.setID(self, parent_id, self.network_segment, ipv4_address, ipv6_address) + +# @staticmethod +# def publicattrsrefs(): +# publicattrs = dict(ModelBase.publicattrsrefs(), **{ +# 'MAC Address' : 'mac', +# 'IPV4 Settings' : 'ipv4', +# 'IPV6 Settings' : 'ipv6', +# 'Network Segment' : 'network_segment', +# 'Hostnames' : 'hostnames' +# }) +# return publicattrs + +# def tieBreakable(self, property_key): +# """ +# Return true if we can auto resolve this conflict. +# """ +# if property_key in ["hostnames"]: +# return True +# return False + +# def tieBreak(self, key, prop1, prop2): +# """ +# Return the 'choosen one' +# Return a tuple with prop1, prop2 if we cant resolve conflict. +# """ +# if key == "hostnames": +# prop1.extend(prop2) +# # Remove duplicated with set... +# return list(set(prop1)) + +# return (prop1, prop2) + +# def updateAttributes(self, name=None, description=None, hostnames=None, mac=None, ipv4=None, ipv6=None, +# network_segment=None, amount_ports_opened=None, amount_ports_closed=None, +# amount_ports_filtered=None, owned=None): + +# if name is not None: +# self.name = name +# if description is not None: +# self.description = description +# if hostnames is not None: +# self.hostnames = hostnames +# if mac is not None: +# self.mac = mac +# if ipv4 is not None: +# self.ipv4 = ipv4 +# if ipv6 is not None: +# self.ipv6 = ipv6 +# if network_segment is not None: +# self.network_segment = network_segment +# if amount_ports_opened is not None: +# self.setPortsOpened(amount_ports_opened) +# if amount_ports_closed is not None: +# self.setPortsClosed(amount_ports_closed) +# if amount_ports_filtered is not None: +# self.setPortsFiltered(amount_ports_filtered) +# if owned is not None: +# self.owned = owned + +# def setPortsOpened(self, ports_opened): +# self.amount_ports_opened = ports_opened + +# def setPortsClosed(self, ports_closed): +# self.amount_ports_closed = ports_closed + +# def setPortsFiltered(self, ports_filtered): +# self.amount_ports_filtered = ports_filtered + +# def __str__(self): return "{0}".format(self.name) +# def getID(self): return self.id +# def getHostnames(self): return self.hostnames +# def getIPv4(self): return self.ipv4 +# def getIPv6(self): return self.ipv6 +# def getIPv4Address(self): return self.ipv4['address'] +# def getIPv4Mask(self): return self.ipv4['mask'] +# def getIPv4Gateway(self): return self.ipv4['gateway'] +# def getIPv4DNS(self): return self.ipv4['DNS'] +# def getIPv6Address(self): return self.ipv6['address'] +# def getIPv6Gateway(self): return self.ipv6['gateway'] +# def getIPv6DNS(self): return self.ipv6['DNS'] +# def getMAC(self): return self.mac +# def getNetworkSegment(self): return self.network_segment + +# def getService(self, service_couch_id): +# return get_service(self._workspace_name, service_couch_id) +# def getAllServices(self): +# return get_services(self._workspace_name, interface=self._server_id) +# def getVulns(self): +# return get_all_vulns(self._workspace_name, interfaceid=self._server_id) class Service(ModelBase): diff --git a/server/api/modules/interfaces.py b/server/api/modules/interfaces.py deleted file mode 100644 index 9e6039b5b24..00000000000 --- a/server/api/modules/interfaces.py +++ /dev/null @@ -1,28 +0,0 @@ -# Faraday Penetration Test IDE -# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) -# See the file 'doc/LICENSE' for the license information - -import flask -from flask import Blueprint - -from server.utils.logger import get_logger -from server.utils.web import ( - gzipped, - validate_workspace, -) - -from server.dao.interface import InterfaceDAO - -interfaces_api = Blueprint('interface_api', __name__) - - -@gzipped -@interfaces_api.route('/ws//interfaces', methods=['GET']) -def list_interfaces(workspace=None): - validate_workspace(workspace) - get_logger(__name__).debug("Request parameters: {!r}".format(flask.request.args)) - - dao = InterfaceDAO(workspace) - result = dao.list(interface_filter=flask.request.args) - - return flask.jsonify(result) diff --git a/server/api/modules/notes.py b/server/api/modules/notes.py deleted file mode 100644 index f8d552fcf62..00000000000 --- a/server/api/modules/notes.py +++ /dev/null @@ -1,45 +0,0 @@ -# Faraday Penetration Test IDE -# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) -# See the file 'doc/LICENSE' for the license information - -from flask import request, jsonify, abort -from flask import Blueprint - -from server.utils.logger import get_logger -from server.utils.web import gzipped, validate_workspace, filter_request_args - -from server.dao.note import NoteDAO - -notes_api = Blueprint('notes_api', __name__) - - -@gzipped -@notes_api.route('/ws//notes', methods=['GET']) -def list_notes(workspace=None): - - validate_workspace(workspace) - get_logger(__name__).debug( - "Request parameters: {!r}".format(request.args)) - - note_filter = filter_request_args() - - dao = NoteDAO(workspace) - - result = dao.list(note_filter=note_filter) - - return jsonify(result) - - -@notes_api.route('/ws//notes/count', methods=['GET']) -@gzipped -def count_notes(workspace=None): - validate_workspace(workspace) - get_logger(__name__).debug("Request parameters: {!r}"\ - .format(request.args)) - - services_dao = NoteDAO(workspace) - result = services_dao.count() - if result is None: - abort(400) - - return jsonify(result) diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 476f06d147f..2befd1cde85 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -10,8 +10,6 @@ from server.dao.host import HostDAO from server.dao.vuln import VulnerabilityDAO from server.dao.service import ServiceDAO -from server.dao.interface import InterfaceDAO -from server.dao.note import NoteDAO from server.dao.workspace import WorkspaceDAO from server.utils.logger import get_logger from server.utils.web import ( @@ -65,8 +63,6 @@ def workspace_summary(workspace=None): services_count = ServiceDAO(workspace).count() vuln_count = VulnerabilityDAO(workspace).count(vuln_filter=flask.request.args) host_count = HostDAO(workspace).count() - iface_count = InterfaceDAO(workspace).count() - note_count = NoteDAO(workspace).count() response = { 'stats': { @@ -75,8 +71,6 @@ def workspace_summary(workspace=None): 'web_vulns': vuln_count.get('web_vuln_count', 0), 'std_vulns': vuln_count.get('vuln_count', 0), 'hosts': host_count.get('total_count', 0), - 'interfaces': iface_count.get('total_count', 0), - 'notes': note_count.get('total_count', 0), } } diff --git a/server/app.py b/server/app.py index 432ccda75e4..34cbc7a5467 100644 --- a/server/app.py +++ b/server/app.py @@ -59,24 +59,20 @@ def create_app(db_connection_string=None, testing=None): from server.api.modules.workspaces import workspace_api from server.api.modules.doc import doc_api - from server.api.modules.interfaces import interfaces_api from server.api.modules.vuln_csv import vuln_csv_api from server.api.modules.hosts import host_api from server.api.modules.commandsrun import commandsrun_api from server.api.modules.services import services_api from server.api.modules.credentials import credentials_api - from server.api.modules.notes import notes_api from server.api.modules.session import session_api from server.modules.info import info_api app.register_blueprint(workspace_api) app.register_blueprint(doc_api) - app.register_blueprint(interfaces_api) app.register_blueprint(vuln_csv_api) app.register_blueprint(host_api) app.register_blueprint(commandsrun_api) app.register_blueprint(services_api) app.register_blueprint(credentials_api) - app.register_blueprint(notes_api) app.register_blueprint(info_api) app.register_blueprint(session_api) diff --git a/server/dao/host.py b/server/dao/host.py index cc56eb47487..75f48a776d9 100644 --- a/server/dao/host.py +++ b/server/dao/host.py @@ -15,7 +15,6 @@ from sqlalchemy.sql import func from server.models import ( Host, - Interface, Service, Vulnerability, EntityMetadata, @@ -29,7 +28,7 @@ class HostDAO(FaradayDAO): COLUMNS_MAP = { "couchid": [EntityMetadata.couchdb_id], - "name": [Host.name], + "ip": [Host.ip], "service": [Service.name], "services": ["open_services_count"], "vulns": ["vuln_count"], @@ -55,21 +54,19 @@ def list(self, search=None, page=0, page_size=0, order_by=None, order_dir=None, def __query_database(self, search=None, page=0, page_size=0, order_by=None, order_dir=None, host_filter={}): host_bundle = Bundle( - "host", Host.id, Host.name, Host.os, Host.description, Host.owned, + "host", Host.id, Host.ip, Host.os, Host.description, Host.owned, Host.default_gateway_ip, Host.default_gateway_mac, EntityMetadata.couchdb_id, EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user, EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time, EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id, - func.group_concat(distinct(Interface.id)).label("interfaces"), func.count(distinct(Vulnerability.id)).label("vuln_count"), func.count(distinct(Service.id)).label("open_services_count"), func.count(distinct(Credential.id)).label("credentials_count")) query = self._session.query(host_bundle)\ .outerjoin(EntityMetadata, EntityMetadata.id == Host.entity_metadata_id)\ - .outerjoin(Interface, Host.id == Interface.host_id)\ .outerjoin(Vulnerability, Host.id == Vulnerability.host_id)\ .outerjoin(Service, (Host.id == Service.host_id) & (Service.status.in_(("open", "running", "opened"))))\ .outerjoin(Credential, (Credential.host_id == Host.id) & Credential.service_id == None)\ @@ -97,7 +94,7 @@ def __get_host_data(self, host): "value": { "_id": host.couchdb_id, "_rev": host.revision, - "name": host.name, + "ip": host.ip, "os": host.os, "owned": host.owned, "owner": host.owner, @@ -115,7 +112,6 @@ def __get_host_data(self, host): }, "vulns": host.vuln_count, "services": host.open_services_count, - "interfaces": map(int, host.interfaces.split(",")) if host.interfaces else [], "credentials": host.credentials_count }} @@ -129,7 +125,7 @@ def count(self, group_by=None): # Otherwise return the amount of services grouped by the field specified # Strict restriction is applied for this entity - if group_by not in ["name", "os"]: + if group_by not in ["ip", "os"]: return None col = HostDAO.COLUMNS_MAP.get(group_by)[0] diff --git a/server/dao/interface.py b/server/dao/interface.py deleted file mode 100644 index 0bd6e918935..00000000000 --- a/server/dao/interface.py +++ /dev/null @@ -1,82 +0,0 @@ -# Faraday Penetration Test IDE -# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) -# See the file 'doc/LICENSE' for the license information - -from server.utils.database import apply_search_filter -from server.dao.base import FaradayDAO -from server.models import Interface, EntityMetadata -from sqlalchemy.orm.query import Bundle -from sqlalchemy.sql import func - -class InterfaceDAO(FaradayDAO): - MAPPED_ENTITY = Interface - COLUMNS_MAP = { - "host": [Interface.host_id], - "couchid": [EntityMetadata.couchdb_id], - } - STRICT_FILTERING = ["host", "couchid"] - - def count(self): - total_count = self._session.query(func.count(Interface.id)).scalar() - return { 'total_count': total_count } - - def list(self, interface_filter={}): - interface_bundle = Bundle('interface', - Interface.id, Interface.name, Interface.description, Interface.mac, - Interface.owned, Interface.hostnames, Interface.network_segment, Interface.ipv4_address, - Interface.ipv4_gateway, Interface.ipv4_dns, Interface.ipv4_mask, Interface.ipv6_address, - Interface.ipv6_gateway, Interface.ipv6_dns, Interface.ipv6_prefix, Interface.ports_filtered, - Interface.ports_opened, Interface.ports_closed, Interface.host_id, EntityMetadata.couchdb_id,\ - EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ - EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ - EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id) - - query = self._session.query(interface_bundle).\ - outerjoin(EntityMetadata, EntityMetadata.id == Interface.entity_metadata_id) - - query = apply_search_filter(query, self.COLUMNS_MAP, None, interface_filter, self.STRICT_FILTERING) - - raw_interfaces = query.all() - interfaces = [self.__get_interface_data(r.interface) for r in raw_interfaces] - result = {'interfaces': interfaces} - - return result - - def __get_interface_data(self, interface): - return { - 'id': interface.couchdb_id, - 'key': interface.couchdb_id, - '_id': interface.id, - 'value': { - '_id': interface.couchdb_id, - '_rev': interface.revision, - 'name': interface.name, - 'description': interface.description, - 'mac': interface.mac, - 'owned': interface.owned, - 'owner': interface.owner, - 'hostnames': interface.hostnames.split(',') if interface.hostnames else [], - 'network_segment': interface.network_segment, - 'ipv4': {'address': interface.ipv4_address, - 'gateway': interface.ipv4_gateway, - 'DNS': interface.ipv4_dns.split(',') if interface.ipv4_dns else [], - 'mask': interface.ipv4_mask}, - 'ipv6': {'address': interface.ipv6_address, - 'gateway': interface.ipv6_gateway, - 'DNS': interface.ipv6_dns.split(',') if interface.ipv6_dns else [], - 'prefix': interface.ipv6_prefix}, - 'ports': {'filtered': interface.ports_filtered, - 'opened': interface.ports_opened, - 'closed': interface.ports_closed}, - 'metadata': { - 'update_time': interface.update_time, - 'update_user': interface.update_user, - 'update_action': interface.update_action, - 'creator': interface.creator, - 'create_time': interface.create_time, - 'update_controller_action': interface.update_controller_action, - 'owner': interface.owner, - 'command_id': interface.command_id - }, - 'host_id': interface.host_id} - } diff --git a/server/dao/note.py b/server/dao/note.py deleted file mode 100644 index ba35a62006e..00000000000 --- a/server/dao/note.py +++ /dev/null @@ -1,76 +0,0 @@ -# Faraday Penetration Test IDE -# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) -# See the file 'doc/LICENSE' for the license information - -from sqlalchemy.sql import func -from sqlalchemy.orm.query import Bundle - -from server.dao.base import FaradayDAO -from server.models import Note, EntityMetadata -from server.utils.database import apply_search_filter - -class NoteDAO(FaradayDAO): - MAPPED_ENTITY = Note - COLUMNS_MAP = { - 'couchid': [EntityMetadata.couchdb_id], - 'name': [Note.name], - 'text': [Note.text], - 'description': [Note.description], - } - STRICT_FILTERING = ["couchid"] - - def list(self, search=None, note_filter={}): - results = self.__query_database(search, note_filter) - - rows = [ self.__get_note_data(result.note) for result in results ] - - result = { - 'rows': rows - } - - return result - - def __query_database(self, search=None, note_filter={}): - note_bundle = Bundle('note', Note.id, Note.name, Note.text, Note.description, Note.owned, EntityMetadata.couchdb_id,\ - EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ - EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ - EntityMetadata.update_controller_action, EntityMetadata.owner, EntityMetadata.command_id) - - query = self._session.query(note_bundle)\ - .outerjoin(EntityMetadata, EntityMetadata.id == Note.entity_metadata_id) - - # Apply filtering options to the query - query = apply_search_filter(query, self.COLUMNS_MAP, search, note_filter, self.STRICT_FILTERING) - - results = query.all() - - return results - - def __get_note_data(self, note): - return { - 'id': note.couchdb_id, - 'key': note.couchdb_id, - '_id': note.id, - 'value': { - '_id': note.couchdb_id, - 'name': note.name, - 'text': note.text, - 'description': note.description, - 'owned': note.owned, - 'owner': note.owner, - 'metadata': { - 'update_time': note.update_time, - 'update_user': note.update_user, - 'update_action': note.update_action, - 'creator': note.creator, - 'create_time': note.create_time, - 'update_controller_action': note.update_controller_action, - 'owner': note.owner, - 'command_id': note.command_id - }, - 'couchid': note.couchdb_id }} - - def count(self): - total_count = self._session.query(func.count(Note.id)).scalar() - return { 'total_count': total_count } - diff --git a/server/dao/service.py b/server/dao/service.py index 489927f851d..c98fc991538 100644 --- a/server/dao/service.py +++ b/server/dao/service.py @@ -9,7 +9,6 @@ from server.dao.base import FaradayDAO from server.models import ( Host, - Interface, Service, EntityMetadata, Vulnerability, @@ -21,7 +20,6 @@ class ServiceDAO(FaradayDAO): MAPPED_ENTITY = Service COLUMNS_MAP = { - "interface": [Service.interface_id], "couchid": [EntityMetadata.couchdb_id], "command_id": [EntityMetadata.command_id], 'id': [Service.id], @@ -34,13 +32,12 @@ class ServiceDAO(FaradayDAO): "hostIdCouchdb": [] } - STRICT_FILTERING = ["couchid", "interface", 'id', 'hostid'] + STRICT_FILTERING = ["couchid", 'id', 'hostid'] def list(self, service_filter={}): service_bundle = Bundle('service', Service.id, Service.name, Service.description, Service.protocol, - Service.status, Service.ports, Service.version, Service.owned, - Service.interface_id, + Service.status, Service.port, Service.version, Service.owned, func.count(distinct(Vulnerability.id)).label('vuln_count'), EntityMetadata.couchdb_id,\ EntityMetadata.revision, EntityMetadata.update_time, EntityMetadata.update_user,\ EntityMetadata.update_action, EntityMetadata.creator, EntityMetadata.create_time,\ @@ -51,9 +48,7 @@ def list(self, service_filter={}): group_by(Service.id).\ outerjoin(EntityMetadata, EntityMetadata.id == Service.entity_metadata_id).\ outerjoin(Vulnerability, Service.id == Vulnerability.service_id).group_by(Service.id).\ - outerjoin(Interface, Interface.id == Service.interface_id).\ - outerjoin(Credential, (Credential.service_id == Service.id) and (Credential.host_id == None)).\ - outerjoin(Host, Host.id == Interface.host_id) + outerjoin(Credential, (Credential.service_id == Service.id) and (Credential.host_id == None)) query = query.filter(Service.workspace == self.workspace) query = apply_search_filter(query, self.COLUMNS_MAP, None, service_filter, self.STRICT_FILTERING) @@ -90,7 +85,7 @@ def __get_service_data(self, service): }, 'protocol': service.protocol, 'status': service.status, - 'ports': [int(i) for i in service.ports.split(',') if service.ports], + 'port': [int(i) for i in service.port.split(',') if service.port], 'version': service.version, 'owned': service.owned, 'owner': service.owner, diff --git a/server/dao/vuln.py b/server/dao/vuln.py index 5f512fbae51..8d12e2a70b6 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -17,7 +17,6 @@ from sqlalchemy.orm.query import Bundle from server.models import ( Host, - Interface, Service, Vulnerability, EntityMetadata @@ -33,40 +32,27 @@ class VulnerabilityDAO(FaradayDAO): "confirmed": [Vulnerability.confirmed], "name": [Vulnerability.name], "severity": [Vulnerability.severity], - "service": [Service.ports, Service.protocol, Service.name], - "target": [Host.name], + "service": [Service.port, Service.protocol, Service.name], + "target": [Host.ip], "desc": [Vulnerability.description], "resolution": [Vulnerability.resolution], "data": [Vulnerability.data], "owner": [EntityMetadata.owner], - "owned": [Vulnerability.owned], - "easeofresolution": [Vulnerability.easeofresolution], + "ease_of_resolution": [Vulnerability.ease_of_resolution], "type": [EntityMetadata.document_type], "status": [Vulnerability.status], - "website": [Vulnerability.website], - "path": [Vulnerability.path], - "request": [Vulnerability.request], - "refs": [Vulnerability.refs], "tags": [], "evidence": [], - "hostnames": [Interface.hostnames], "impact": [], - "method": [Vulnerability.method], - "params": [Vulnerability.params], - "pname": [Vulnerability.pname], - "query": [Vulnerability.query], - "response": [Vulnerability.response], "hostid": [Host.id], "serviceid": [Service.id], - "interfaceid": [Interface.id], "web": [], "issuetracker": [], "creator": [EntityMetadata.creator], - "command_id": [EntityMetadata.command_id], - "policyviolations": [Vulnerability.policyviolations] + "command_id": [EntityMetadata.command_id] } - STRICT_FILTERING = ["type", "service", "couchid", "hostid", "serviceid", 'interfaceid', 'id', 'status', 'command_id'] + STRICT_FILTERING = ["type", "service", "couchid", "hostid", "serviceid", 'id', 'status', 'command_id'] def list(self, search=None, page=0, page_size=0, order_by=None, order_dir=None, vuln_filter={}): results, count = self.__query_database(search, page, page_size, order_by, order_dir, vuln_filter) @@ -85,25 +71,22 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde # times from large workspaces almost 2x. vuln_bundle = Bundle('vuln', Vulnerability.id.label('server_id'), Vulnerability.name.label('v_name'),\ Vulnerability.confirmed, Vulnerability.data,\ - Vulnerability.description, Vulnerability.easeofresolution, Vulnerability.impact_accountability,\ + Vulnerability.description, Vulnerability.ease_of_resolution, Vulnerability.impact_accountability,\ Vulnerability.impact_availability, Vulnerability.impact_confidentiality, Vulnerability.impact_integrity,\ - Vulnerability.refs, Vulnerability.resolution, Vulnerability.severity, Vulnerability.owned, Vulnerability.status,\ - Vulnerability.website, Vulnerability.path, Vulnerability.request, Vulnerability.response,\ - Vulnerability.method, Vulnerability.params, Vulnerability.pname, Vulnerability.query,\ + Vulnerability.resolution, Vulnerability.severity, Vulnerability.status,\ EntityMetadata.couchdb_id, EntityMetadata.revision, EntityMetadata.create_time, EntityMetadata.creator,\ EntityMetadata.owner, EntityMetadata.update_action, EntityMetadata.update_controller_action,\ EntityMetadata.update_time, EntityMetadata.update_user, EntityMetadata.document_type, EntityMetadata.command_id, \ - Vulnerability.attachments, Vulnerability.policyviolations) - service_bundle = Bundle('service', Service.name.label('s_name'), Service.ports, Service.protocol, Service.id) - host_bundle = Bundle('host', Host.name) + Vulnerability.attachments) + service_bundle = Bundle('service', Service.name.label('s_name'), Service.port, Service.protocol, Service.id) + host_bundle = Bundle('host', Host.ip) # IMPORTANT: OUTER JOINS on those tables is IMPERATIVE. Changing them could result in loss of # data. For example, on vulnerabilities not associated with any service and instead to its host # directly. query = self._session.query(vuln_bundle, service_bundle, - host_bundle, - func.group_concat(Interface.hostnames))\ + host_bundle)\ .group_by(Vulnerability.id)\ .outerjoin(EntityMetadata, EntityMetadata.id == Vulnerability.entity_metadata_id)\ .outerjoin(Service, Service.id == Vulnerability.service_id)\ @@ -162,7 +145,7 @@ def get_parent_id(couchdb_id): 'data': vuln.data, 'desc': vuln.description, 'description': vuln.description, - 'easeofresolution': vuln.easeofresolution, + 'ease_of_resolution': vuln.ease_of_resolution, 'impact': { 'accountability': vuln.impact_accountability, 'availability': vuln.impact_availability, @@ -186,24 +169,14 @@ def get_parent_id(couchdb_id): 'owned': vuln.owned, 'owner': vuln.owner, 'parent': get_parent_id(vuln.couchdb_id), - 'policyviolations': json.loads(vuln.policyviolations), - 'refs': json.loads(vuln.refs), 'status': vuln.status, - 'website': vuln.website, - 'path': vuln.path, - 'request': vuln.request, - 'response': vuln.response, - 'method': vuln.method, - 'params': vuln.params, - 'pname': vuln.pname, - 'query': vuln.query, 'resolution': vuln.resolution, 'severity': vuln.severity, 'tags': [], 'type': vuln.document_type, - 'target': host.name, + 'target': Host.ip, 'hostnames': hostnames.split(',') if hostnames else '', - 'service': "(%s/%s) %s" % (service.ports, service.protocol, service.s_name) if service.ports else '' + 'service': "(%s/%s) %s" % (service.port, service.protocol, service.s_name) if service.port else '' }} def count(self, group_by=None, search=None, vuln_filter={}): diff --git a/server/dao/workspace.py b/server/dao/workspace.py index 52a6bd7fa74..d373e736fd2 100644 --- a/server/dao/workspace.py +++ b/server/dao/workspace.py @@ -19,22 +19,16 @@ class WorkspaceDAO(object): MAPPED_ENTITY = Workspace COLUMNS_MAP = { - "create_date": [Workspace.create_date], - "creator": [Workspace.creator], + "active": [Workspace.active], "customer": [Workspace.customer], "description": [Workspace.description], - "disabled": [Workspace.disabled], "end_date": [Workspace.end_date], "name": [Workspace.name], "public": [Workspace.public], "scope": [Workspace.scope], - "start_date": [Workspace.start_date], - "update_date": [Workspace.update_date] + "start_date": [Workspace.start_date] } - STRICT_FILTERING = ["name", "creator", "customer", "disabled", - "public", "update_date"] - def __init__(self): self._logger = logger.get_logger(self) self._session = db.session @@ -72,18 +66,18 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, order_dir=None, workspace_filter={}): workspace_bundle = Bundle('workspace', - Workspace.create_date, - Workspace.creator, + Workspace.active, Workspace.customer, Workspace.description, - Workspace.disabled, Workspace.end_date, Workspace.name, Workspace.public, Workspace.scope, Workspace.start_date, - Workspace.update_date) + ) + STRICT_FILTERING = ["name", "customer", "disabled", + "public", "update_date"] query = self._session.query(workspace_bundle) query = apply_search_filter(query, self.COLUMNS_MAP, search, @@ -100,15 +94,12 @@ def __query_database(self, search=None, page=0, page_size=0, def __get_workspace_data(self, workspace): return { - "create_date": workspace.create_date, - "creator": workspace.creator, + "active": workspace.active, "customer": workspace.customer, "description": workspace.description, - "disabled": workspace.disabled, "end_date": workspace.end_date, "name": workspace.name, "public": workspace.public, "scope": workspace.scope, - "start_date": workspace.start_date, - "update_date": workspace.update_date + "start_date": workspace.start_date } diff --git a/server/importer.py b/server/importer.py index 587552863ad..56c95909c5c 100644 --- a/server/importer.py +++ b/server/importer.py @@ -16,9 +16,7 @@ db, EntityMetadata, Host, - Interface, Service, - Note, Command, Workspace, Vulnerability @@ -104,43 +102,6 @@ def __get_default_gateway(self, document): return u'', u'' -class InterfaceImporter(object): - DOC_TYPE = 'Interface' - - @classmethod - def update_from_document(cls, document): - interface = Interface() - interface.name = document.get('name') - interface.description = document.get('description') - interface.mac = document.get('mac') - interface.owned = document.get('owned', False) - interface.hostnames = u','.join(document.get('hostnames') or []) - interface.network_segment = document.get('network_segment') - interface.ipv4_address = document.get('ipv4').get('address') - interface.ipv4_gateway = document.get('ipv4').get('gateway') - interface.ipv4_dns = u','.join(document.get('ipv4').get('DNS')) - interface.ipv4_mask = document.get('ipv4').get('mask') - interface.ipv6_address = document.get('ipv6').get('address') - interface.ipv6_gateway = document.get('ipv6').get('gateway') - interface.ipv6_dns = u','.join(document.get('ipv6').get('DNS')) - interface.ipv6_prefix = str(document.get('ipv6').get('prefix')) - interface.ports_filtered = document.get('ports', {}).get('filtered') - interface.ports_opened = document.get('ports', {}).get('opened') - interface.ports_closed = document.get('ports', {}).get('closed') - return interface - - def add_relationships_from_dict(self, entity, entities): - host_id = '.'.join(entity.entity_metadata.couchdb_id.split('.')[:-1]) - if host_id not in entities: - raise EntityNotFound(host_id) - entity.host = entities[host_id] - - def add_relationships_from_db(self, entity, session): - host_id = '.'.join(entity.entity_metadata.couchdb_id.split('.')[:-1]) - query = session.query(Host).join(EntityMetadata).filter(EntityMetadata.couchdb_id == host_id) - entity.host = query.one() - - class ServiceImporter(object): DOC_TYPE = 'Service' @@ -169,21 +130,12 @@ def add_relationships_from_dict(self, entity, entities): raise EntityNotFound(host_id) entity.host = entities[host_id] - interface_id = '.'.join(couchdb_id.split('.')[:-1]) - if interface_id not in entities: - raise EntityNotFound(interface_id) - entity.interface = entities[interface_id] - def add_relationships_from_db(self, entity, session): couchdb_id = entity.entity_metadata.couchdb_id host_id = couchdb_id.split('.')[0] query = session.query(Host).join(EntityMetadata).filter(EntityMetadata.couchdb_id == host_id) entity.host = query.one() - interface_id = '.'.join(couchdb_id.split('.')[:-1]) - query = session.query(Interface).join(EntityMetadata).filter(EntityMetadata.couchdb_id == interface_id) - entity.interface = query.one() - class VulnerabilityImporter(object): DOC_TYPE = ['Vulnerability', 'VulnerabilityWeb'] @@ -346,7 +298,7 @@ def add_relationships_from_dict(self, entity, entities): class FaradayEntityImporter(object): - # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace', u'Interface'] + # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace'] @classmethod def parse(cls, document): """Get an instance of a DAO object given a document""" @@ -367,7 +319,6 @@ def get_importer_from_document(cls, document): importer_class_mapper = { 'EntityMetadata': EntityMetadataImporter, 'Host': HostImporter, - 'Interface': InterfaceImporter, 'Service': ServiceImporter, 'Note': NoteImporter, 'CommandRunInformation': CommandImporter, @@ -375,8 +326,9 @@ def get_importer_from_document(cls, document): 'Vulnerability': VulnerabilityImporter, 'VulnerabilityWeb': VulnerabilityImporter, } + # TODO: remove this! if doc_type in ('Communication', 'Cred', 'Reports', - 'Task', 'TaskGroup'): + 'Task', 'TaskGroup', 'Interface', 'Note'): return importer_cls = importer_class_mapper.get(doc_type, None) if not importer_cls: diff --git a/server/models.py b/server/models.py index 37e9bf586ce..f477642e092 100644 --- a/server/models.py +++ b/server/models.py @@ -61,8 +61,8 @@ class SourceCode(db.Model): id = Column(Integer, primary_key=True) filename = Column(Text, nullable=False) - workspace = relationship('Workspace', backref='source_codes') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship('Workspace', backref='source_codes') class Host(db.Model): @@ -81,11 +81,21 @@ class Host(db.Model): mac = Column(Text, nullable=True) net_segment = Column(Text, nullable=True) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + entity_metadata = relationship( + EntityMetadata, + uselist=False, + cascade="all, delete-orphan", + single_parent=True, + foreign_keys=[entity_metadata_id] + ) - workspace = relationship('Workspace', backref='hosts') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship( + 'Workspace', + backref='hosts', + foreign_keys=[workspace_id] + ) class Hostname(db.Model): @@ -94,8 +104,8 @@ class Hostname(db.Model): id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) - host = relationship('Host', backref='hostnames') host_id = Column(Integer, ForeignKey('host.id'), index=True) + host = relationship('Host', backref='hostnames') class Service(db.Model): @@ -118,14 +128,24 @@ class Service(db.Model): banner = Column(Text, nullable=True) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + entity_metadata = relationship( + EntityMetadata, + uselist=False, + cascade="all, delete-orphan", + single_parent=True, + foreign_keys=[entity_metadata_id] + ) - host = relationship('Host', backref='services') host_id = Column(Integer, ForeignKey('host.id'), index=True) + host = relationship('Host', backref='services', foreign_keys=[host_id]) - workspace = relationship('Workspace', backref='services') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship( + 'Workspace', + backref='services', + foreign_keys=[workspace_id] + ) class Reference(db.Model): @@ -133,19 +153,27 @@ class Reference(db.Model): id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) - workspace = relationship('Workspace', backref='references') workspace_id = Column( Integer, ForeignKey('workspace.id'), index=True ) + workspace = relationship( + 'Workspace', + backref='references', + foreign_keys=[workspace_id], + ) - vulnerability = relationship('Vulnerability', backref='references') vulnerability_id = Column( Integer, - ForeignKey('vulnerbility.id'), + ForeignKey('vulnerability.id'), index=True ) + vulnerability = relationship( + 'Vulnerability', + backref='references', + foreign_keys=[vulnerability_id], + ) class PolicyViolation(db.Model): @@ -153,19 +181,27 @@ class PolicyViolation(db.Model): id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) - workspace = relationship('Workspace', backref='policy_violations') workspace_id = Column( Integer, ForeignKey('workspace.id'), index=True ) + workspace = relationship( + 'Workspace', + backref='policy_violations', + foreign_keys=[workspace_id], + ) - vulnerability = relationship('Vulnerability', backref='policy_violations') vulnerability_id = Column( Integer, - ForeignKey('vulnerbility.id'), + ForeignKey('vulnerability.id'), index=True ) + vulnerability = relationship( + 'Vulnerability', + backref='policy_violations', + foreign_keys=[vulnerability_id] + ) class VulnerabilityABC(db.Model): @@ -218,12 +254,12 @@ class VulnerabilityGeneric(VulnerabilityABC): status = Column(Enum(*STATUSES), nullable=False, default="open") type = Column(Enum(*VULN_TYPES), nullable=False) - workspace = relationship('Workspace', backref='vulnerabilities') workspace_id = Column( Integer, ForeignKey('workspace.id'), index=True, ) + workspace = relationship('Workspace', backref='vulnerabilities') __mapper_args__ = { 'polymorphic_on': type @@ -231,11 +267,19 @@ class VulnerabilityGeneric(VulnerabilityABC): class Vulnerability(VulnerabilityGeneric): - host = relationship('Host', backref='vulnerabilities') host_id = Column(Integer, ForeignKey(Host.id), index=True) + host = relationship( + 'Host', + backref='vulnerabilities', + foreign_keys=[host_id], + ) - service = relationship('Service', backref='vulnerabilities') service_id = Column(Integer, ForeignKey(Service.id), index=True) + service = relationship( + 'Service', + backref='vulnerabilities', + foreign_keys=[service_id], + ) __table_args__ = { 'extend_existing': True @@ -256,8 +300,12 @@ class VulnerabilityWeb(VulnerabilityGeneric): response = Column(Text(), nullable=True) website = Column(String(250), nullable=True) - service = relationship('Service', backref='vulnerabilities_web') - service_id = Column(Integer, ForeignKey(Service.id), index=True) + service_id_lala = Column(Integer, ForeignKey(Service.id), index=True) + service_lala = relationship( + 'Service', + backref='vulnerabilities_web', + foreign_keys=[service_id_lala], + ) __table_args__ = { 'extend_existing': True @@ -271,8 +319,12 @@ class VulnerabilityWeb(VulnerabilityGeneric): class VulnerabilityCode(VulnerabilityGeneric): line = Column(Integer, nullable=True) - source_code = relationship('SourceCode', backref='vulnerabilities') - source_code_id = Column(Integer, ForeignKey('SourceCode.id'), index=True) + source_code_id = Column(Integer, ForeignKey(SourceCode.id), index=True) + source_code = relationship( + SourceCode, + backref='vulnerabilities', + foreign_keys=[source_code_id] + ) __table_args__ = { 'extend_existing': True @@ -293,17 +345,31 @@ class Credential(db.Model): description = Column(Text(), nullable=True) name = Column(String(250), nullable=True) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + entity_metadata = relationship( + EntityMetadata, + uselist=False, + cascade="all, delete-orphan", + single_parent=True, + foreign_keys=[entity_metadata_id], + ) - host = relationship('Host', backref='credentials') host_id = Column(Integer, ForeignKey(Host.id), index=True, nullable=True) + host = relationship('Host', backref='credentials', foreign_keys=[host_id]) - service = relationship('Service', backref='credentials') service_id = Column(Integer, ForeignKey(Service.id), index=True, nullable=True) + service = relationship( + 'Service', + backref='credentials', + foreign_keys=[service_id], + ) - workspace = relationship('Workspace', backref='credentials') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) + workspace = relationship( + 'Workspace', + backref='credentials', + foreign_keys=[workspace_id], + ) class Command(db.Model): @@ -316,12 +382,23 @@ class Command(db.Model): hostname = Column(String(250), nullable=False) # where the command was executed params = Column(String(250), nullable=True) user = Column(String(250), nullable=True) # where the command was executed - workspace = relationship('Workspace') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship('Workspace', foreign_keys=[workspace_id]) # TODO: add Tool relationship and report_attachment - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + entity_metadata_id = Column( + Integer, + ForeignKey(EntityMetadata.id), + index=True + ) + entity_metadata = relationship( + EntityMetadata, + uselist=False, + cascade="all, delete-orphan", + single_parent=True, + foreign_keys=[entity_metadata_id] + ) class Workspace(db.Model): @@ -415,8 +492,18 @@ class Methodology(db.Model): id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) - entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) - entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) + entity_metadata_id = Column( + Integer, + ForeignKey(EntityMetadata.id), + index=True + ) + entity_metadata = relationship( + EntityMetadata, + uselist=False, + cascade="all, delete-orphan", + single_parent=True, + foreign_keys=[entity_metadata_id] + ) template = relationship('MethodologyTemplate', backref='methodologies') template_id = Column( @@ -534,11 +621,15 @@ class Comment(db.Model): text = Column(Text, nullable=False) - reply_to = relationship('Comment', backref='replies') reply_to_id = Column(Integer, ForeignKey('comment.id')) + reply_to = relationship( + 'Comment', + remote_side=[id], + foreign_keys=[reply_to_id] + ) object_id = Column(Integer, nullable=False) object_type = Column(Text, nullable=False) - workspace = relationship('Workspace') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship('Workspace', foreign_keys=[workspace_id]) From f4094920979c53802c7627ae6fc59a162092a3ce Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 19:25:14 -0300 Subject: [PATCH 0118/1506] Fix Policy Violations and References inheritance --- server/models.py | 112 +++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/server/models.py b/server/models.py index f477642e092..1c424ac5b32 100644 --- a/server/models.py +++ b/server/models.py @@ -148,62 +148,6 @@ class Service(db.Model): ) -class Reference(db.Model): - __tablename__ = 'reference' - id = Column(Integer, primary_key=True) - name = Column(Text, nullable=False) - - workspace_id = Column( - Integer, - ForeignKey('workspace.id'), - index=True - ) - workspace = relationship( - 'Workspace', - backref='references', - foreign_keys=[workspace_id], - ) - - vulnerability_id = Column( - Integer, - ForeignKey('vulnerability.id'), - index=True - ) - vulnerability = relationship( - 'Vulnerability', - backref='references', - foreign_keys=[vulnerability_id], - ) - - -class PolicyViolation(db.Model): - __tablename__ = 'policy_violation' - id = Column(Integer, primary_key=True) - name = Column(Text, nullable=False) - - workspace_id = Column( - Integer, - ForeignKey('workspace.id'), - index=True - ) - workspace = relationship( - 'Workspace', - backref='policy_violations', - foreign_keys=[workspace_id], - ) - - vulnerability_id = Column( - Integer, - ForeignKey('vulnerability.id'), - index=True - ) - vulnerability = relationship( - 'Vulnerability', - backref='policy_violations', - foreign_keys=[vulnerability_id] - ) - - class VulnerabilityABC(db.Model): # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website, workspace # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien @@ -335,6 +279,62 @@ class VulnerabilityCode(VulnerabilityGeneric): } +class Reference(db.Model): + __tablename__ = 'reference' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False) + + workspace_id = Column( + Integer, + ForeignKey('workspace.id'), + index=True + ) + workspace = relationship( + 'Workspace', + backref='references', + foreign_keys=[workspace_id], + ) + + vulnerability_id = Column( + Integer, + ForeignKey(VulnerabilityGeneric.id), + index=True + ) + vulnerability = relationship( + 'VulnerabilityGeneric', + backref='references', + foreign_keys=[vulnerability_id], + ) + + +class PolicyViolation(db.Model): + __tablename__ = 'policy_violation' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False) + + workspace_id = Column( + Integer, + ForeignKey('workspace.id'), + index=True + ) + workspace = relationship( + 'Workspace', + backref='policy_violations', + foreign_keys=[workspace_id], + ) + + vulnerability_id = Column( + Integer, + ForeignKey(VulnerabilityGeneric.id), + index=True + ) + vulnerability = relationship( + 'VulnerabilityGeneric', + backref='policy_violations', + foreign_keys=[vulnerability_id] + ) + + class Credential(db.Model): # TODO: add unique constraint -> username, host o service y workspace # TODO: add constraint host y service, uno o el otro From d4bff910620d02a12984caecbc496bd17d9527fb Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 25 Aug 2017 19:35:06 -0300 Subject: [PATCH 0119/1506] Add Policy Violation and Reference template models These templates tables are added to provide PV and Ref for the Vuln Templates. --- server/models.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/server/models.py b/server/models.py index 1c424ac5b32..8c4c9850720 100644 --- a/server/models.py +++ b/server/models.py @@ -279,6 +279,23 @@ class VulnerabilityCode(VulnerabilityGeneric): } +class ReferenceTemplate(db.Model): + __tablename__ = 'reference_template' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False) + + vulnerability_id = Column( + Integer, + ForeignKey(VulnerabilityTemplate.id), + index=True + ) + vulnerability = relationship( + 'VulnerabilityTemplate', + backref='references', + foreign_keys=[vulnerability_id], + ) + + class Reference(db.Model): __tablename__ = 'reference' id = Column(Integer, primary_key=True) @@ -307,6 +324,23 @@ class Reference(db.Model): ) +class PolicyViolationTemplate(db.Model): + __tablename__ = 'policy_violation_template' + id = Column(Integer, primary_key=True) + name = Column(Text, nullable=False) + + vulnerability_id = Column( + Integer, + ForeignKey(VulnerabilityTemplate.id), + index=True + ) + vulnerability = relationship( + 'VulnerabilityTemplate', + backref='policy_violations', + foreign_keys=[vulnerability_id] + ) + + class PolicyViolation(db.Model): __tablename__ = 'policy_violation' id = Column(Integer, primary_key=True) From 404471ae4696b199196642ec1cd0be37df736747 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 28 Aug 2017 14:24:14 -0300 Subject: [PATCH 0120/1506] Disable object importer on faraday-server startup --- faraday-server.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/faraday-server.py b/faraday-server.py index 1278a8a3394..7cfac7ff035 100755 --- a/faraday-server.py +++ b/faraday-server.py @@ -52,11 +52,6 @@ def setup_environment(check_deps=False): server.couchdb.push_reports() -def import_workspaces(): - import server.importer - server.importer.import_workspaces() - - def stop_server(): if not daemonize.stop_server(): # Exists with an error if it couldn't close the server @@ -118,7 +113,6 @@ def main(): if not args.no_setup: setup_environment(not args.nodeps) - import_workspaces() if args.start: # Starts a new process on background with --ignore-setup From 7e6d506c896f1e0594cfa410d372151c97e4b521 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 28 Aug 2017 14:37:29 -0300 Subject: [PATCH 0121/1506] Fix service relationship in vuln and web vuln --- server/models.py | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/server/models.py b/server/models.py index 8c4c9850720..06854c8b96a 100644 --- a/server/models.py +++ b/server/models.py @@ -14,6 +14,7 @@ UniqueConstraint, ) from sqlalchemy.orm import relationship, backref +from sqlalchemy.ext.declarative import declared_attr from flask_sqlalchemy import SQLAlchemy from flask_security import ( RoleMixin, @@ -218,12 +219,21 @@ class Vulnerability(VulnerabilityGeneric): foreign_keys=[host_id], ) - service_id = Column(Integer, ForeignKey(Service.id), index=True) + @declared_attr + def service_id(cls): + return VulnerabilityGeneric.__table__.c.get( + 'service_id', + Column( + Integer, + ForeignKey(Service.id), + index=True + ) + ) + service = relationship( - 'Service', - backref='vulnerabilities', - foreign_keys=[service_id], - ) + 'Service', + backref='vulnerabilities', + ) __table_args__ = { 'extend_existing': True @@ -244,12 +254,21 @@ class VulnerabilityWeb(VulnerabilityGeneric): response = Column(Text(), nullable=True) website = Column(String(250), nullable=True) - service_id_lala = Column(Integer, ForeignKey(Service.id), index=True) - service_lala = relationship( - 'Service', - backref='vulnerabilities_web', - foreign_keys=[service_id_lala], - ) + @declared_attr + def service_id(cls): + return VulnerabilityGeneric.__table__.c.get( + 'service_id', + Column( + Integer, + ForeignKey(Service.id), + index=True, + ) + ) + + service = relationship( + 'Service', + backref='vulnerabilities_web', + ) __table_args__ = { 'extend_existing': True From 88ea4d93471bfb3e0b723c6d8c3e668b3aa6d6ff Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 28 Aug 2017 16:03:35 -0300 Subject: [PATCH 0122/1506] Update importer to use levels --- server/importer.py | 312 ++++++++++++++++++--------------------------- 1 file changed, 121 insertions(+), 191 deletions(-) diff --git a/server/importer.py b/server/importer.py index 587552863ad..390bbf23650 100644 --- a/server/importer.py +++ b/server/importer.py @@ -6,15 +6,22 @@ import json import os +import requests +from tqdm import tqdm +from flask_script import Command as FlaskScriptCommand +from restkit.errors import RequestError, Unauthorized + import server.app import server.utils.logger import server.couchdb import server.database import server.models +import server.config from server.utils.database import get_or_create from server.models import ( db, EntityMetadata, + Credential, Host, Interface, Service, @@ -23,9 +30,7 @@ Workspace, Vulnerability ) -from restkit.errors import RequestError, Unauthorized -from tqdm import tqdm logger = server.utils.logger.get_logger(__name__) session = db.session @@ -73,9 +78,9 @@ class HostImporter(object): DOC_TYPE = 'Host' @classmethod - def update_from_document(cls, document): + def update_from_document(cls, document, workspace): # Ticket #3387: if the 'os' field is None, we default to 'unknown' - host = Host() + host, created = get_or_create(session, Host, name=document.get('name')) if not document.get('os'): document['os'] = 'unknown' @@ -87,29 +92,19 @@ def update_from_document(cls, document): host.default_gateway_ip = default_gateway and default_gateway[0] or '' host.default_gateway_mac = default_gateway and default_gateway[1] or '' host.owned = document.get('owned', False) + host.workspace = workspace return host - def add_relationships_from_dict(self, host, entities): - assert len(filter(lambda entity: isinstance(entity, Workspace), entities)) <= 1 - for couch_id, entity in entities.items(): - if isinstance(entity, Workspace): - host.workspace = entity - - - def __get_default_gateway(self, document): - default_gateway = document.get('default_gateway', None) - if default_gateway: - return default_gateway - else: - return u'', u'' + def set_parent(self, host, parent): + raise NotImplementedError class InterfaceImporter(object): DOC_TYPE = 'Interface' @classmethod - def update_from_document(cls, document): - interface = Interface() + def update_from_document(cls, document, workspace): + interface, created = get_or_create(session, Interface, name=document.get('name')) interface.name = document.get('name') interface.description = document.get('description') interface.mac = document.get('mac') @@ -127,32 +122,28 @@ def update_from_document(cls, document): interface.ports_filtered = document.get('ports', {}).get('filtered') interface.ports_opened = document.get('ports', {}).get('opened') interface.ports_closed = document.get('ports', {}).get('closed') + interface.workspace = workspace return interface - def add_relationships_from_dict(self, entity, entities): - host_id = '.'.join(entity.entity_metadata.couchdb_id.split('.')[:-1]) - if host_id not in entities: - raise EntityNotFound(host_id) - entity.host = entities[host_id] + @classmethod + def set_parent(cls, interface, parent_relation_db_id, level): + interface.host = session.query(Host).filter_by(id=parent_relation_db_id).first() - def add_relationships_from_db(self, entity, session): - host_id = '.'.join(entity.entity_metadata.couchdb_id.split('.')[:-1]) - query = session.query(Host).join(EntityMetadata).filter(EntityMetadata.couchdb_id == host_id) - entity.host = query.one() class ServiceImporter(object): DOC_TYPE = 'Service' @classmethod - def update_from_document(cls, document): - service = Service() + def update_from_document(cls, document, workspace): + service, created = get_or_create(session, Service, name=document.get('name')) service.name = document.get('name') service.description = document.get('description') service.owned = document.get('owned', False) service.protocol = document.get('protocol') service.status = document.get('status') service.version = document.get('version') + service.workspace = workspace # We found workspaces where ports are defined as an integer if isinstance(document.get('ports', None), (int, long)): @@ -161,38 +152,16 @@ def update_from_document(cls, document): service.ports = u','.join(map(str, document.get('ports'))) return service - def add_relationships_from_dict(self, entity, entities): - couchdb_id = entity.entity_metadata.couchdb_id - - host_id = couchdb_id.split('.')[0] - if host_id not in entities: - raise EntityNotFound(host_id) - entity.host = entities[host_id] - - interface_id = '.'.join(couchdb_id.split('.')[:-1]) - if interface_id not in entities: - raise EntityNotFound(interface_id) - entity.interface = entities[interface_id] - - def add_relationships_from_db(self, entity, session): - couchdb_id = entity.entity_metadata.couchdb_id - host_id = couchdb_id.split('.')[0] - query = session.query(Host).join(EntityMetadata).filter(EntityMetadata.couchdb_id == host_id) - entity.host = query.one() - - interface_id = '.'.join(couchdb_id.split('.')[:-1]) - query = session.query(Interface).join(EntityMetadata).filter(EntityMetadata.couchdb_id == interface_id) - entity.interface = query.one() + def set_parent(self, service, parent_id): + raise NotImplementedError class VulnerabilityImporter(object): DOC_TYPE = ['Vulnerability', 'VulnerabilityWeb'] @classmethod - def update_from_document(cls, document): - vulnerability = Vulnerability() - vulnerability.name = document.get('name') - vulnerability.description = document.get('desc') + def update_from_document(cls, document, workspace): + vulnerability, created = get_or_create(session, Vulnerability, name=document.get('name'), description= document.get('desc')) vulnerability.confirmed = document.get('confirmed') vulnerability.vuln_type = document.get('type') vulnerability.data = document.get('data') @@ -215,6 +184,7 @@ def update_from_document(cls, document): vulnerability.response = document.get('response') vulnerability.website = document.get('website') vulnerability.status = document.get('status', 'opened') + vulnerability.workspace = workspace params = document.get('params', u'') if isinstance(params, (list, tuple)): @@ -224,36 +194,19 @@ def update_from_document(cls, document): return vulnerability - def add_relationships_from_dict(self, entity, entities): - couchdb_id = entity.entity_metadata.couchdb_id - host_id = couchdb_id.split('.')[0] - if host_id not in entities: - raise EntityNotFound(host_id) - entity.host = entities[host_id] - - parent_id = '.'.join(couchdb_id.split('.')[:-1]) - if parent_id != host_id: - if parent_id not in entities: - raise EntityNotFound(parent_id) - entity.service = entities[parent_id] - - def add_relationships_from_db(self, entity, session): - couchdb_id = entity.entity_metadata.couchdb_id - host_id = couchdb_id.split('.')[0] - query = session.query(Host).join(EntityMetadata).filter(EntityMetadata.couchdb_id == host_id) - entity.host = query.one() - - parent_id = '.'.join(couchdb_id.split('.')[:-1]) - if parent_id != host_id: - query = session.query(Service).join(EntityMetadata).filter(EntityMetadata.couchdb_id == parent_id) - entity.service = query.one() - + @classmethod + def set_parent(self, vulnerability, parent_id, level=2): + logger.debug('Set parent for vulnerabiity level {0}'.format(level)) + if level == 2: + vulnerability.host = session.query(Host).filter_by(id=parent_id).first() + if level == 3: + vulnerability.service = session.query(Service).filter_by(id=parent_id).first() class CommandImporter(object): DOC_TYPE = 'CommandRunInformation' @classmethod - def update_from_document(cls, document): + def update_from_document(cls, document, workspace): command, instance = get_or_create(session, Command, command=document.get('command', None)) command.command = document.get('command', None) command.duration = document.get('duration', None) @@ -262,18 +215,12 @@ def update_from_document(cls, document): command.hostname = document.get('hostname', None) command.params = document.get('params', None) command.user = document.get('user', None) - - workspace_name = document.get('workspace', None) - if workspace_name: - workspace, instance = get_or_create(session, Workspace, name=document.get('workspace', None)) - command.workspace = workspace + command.workspace = workspace return command - def add_relationships_from_dict(self, entity, entities): - # this method is not required since update_from_document uses - # workspace name to create the relation - pass + def set_parent(self, command, parent_id): + raise NotImplementedError class NoteImporter(object): @@ -298,38 +245,18 @@ class CredentialImporter(object): DOC_TYPE = 'Cred' @classmethod - def update_from_document(cls, document): - credential = Credential() + def update_from_document(cls, document, workspace): + credential, created = get_or_create(session, Credential, name=document.get('username')) credential.username = document.get('username') credential.password = document.get('password', '') credential.owned = document.get('owned', False) credential.description = document.get('description', '') credential.name = document.get('name', '') + credential.workspace = workspace return credential - def add_relationships_from_dict(self, entity, entities): - couchdb_id = entity.entity_metadata.couchdb_id - host_id = couchdb_id.split('.')[0] - if host_id not in entities: - raise EntityNotFound(host_id) - entity.host = entities[host_id] - - parent_id = '.'.join(couchdb_id.split('.')[:-1]) - if parent_id != host_id: - if parent_id not in entities: - raise EntityNotFound(parent_id) - entity.service = entities[parent_id] - - def add_relationships_from_db(self, entity, session): - couchdb_id = entity.entity_metadata.couchdb_id - host_id = couchdb_id.split('.')[0] - query = session.query(Host).join(EntityMetadata).filter(EntityMetadata.couchdb_id == host_id) - entity.host = query.one() - - parent_id = '.'.join(couchdb_id.split('.')[:-1]) - if parent_id != host_id: - query = session.query(Service).join(EntityMetadata).filter(EntityMetadata.couchdb_id == parent_id) - entity.service = query.one() + def set_parent(self, credential, parent_id): + raise NotImplementedError class WorkspaceImporter(object): @@ -360,10 +287,8 @@ def parse(cls, document): return None, None @classmethod - def get_importer_from_document(cls, document): - doc_type = document.get('type') - if not doc_type: - return + def get_importer_from_document(cls, doc_type): + logger.info('Getting class importer for {0}'.format(doc_type)) importer_class_mapper = { 'EntityMetadata': EntityMetadataImporter, 'Host': HostImporter, @@ -388,87 +313,92 @@ def update_from_document(self, document): raise Exception('MUST IMPLEMENT') -def import_workspaces(): - """ - Main entry point for couchdb import - """ - app = server.app.create_app() - app.app_context().push() - db.create_all() +class ImportCouchDB(FlaskScriptCommand): - couchdb_server_conn, workspaces_list = _open_couchdb_conn() + def _open_couchdb_conn(self): + try: + couchdb_server_conn = server.couchdb.CouchDBServer() + workspaces_list = couchdb_server_conn.list_workspaces() - for workspace_name in workspaces_list: - logger.info(u'Setting up workspace {}'.format(workspace_name)) + except RequestError: + logger.error(u"CouchDB is not running at {}. Check faraday-server's"\ + " configuration and make sure CouchDB is running".format( + server.couchdb.get_couchdb_url())) + sys.exit(1) - if not server.couchdb.server_has_access_to(workspace_name): + except Unauthorized: logger.error(u"Unauthorized access to CouchDB. Make sure faraday-server's"\ - " configuration file has CouchDB admin's credentials set") + " configuration file has CouchDB admin's credentials set") sys.exit(1) - import_workspace_into_database(workspace_name, couchdb_server_conn=couchdb_server_conn) + return couchdb_server_conn, workspaces_list + def run(self): + """ + Main entry point for couchdb import + """ + couchdb_server_conn, workspaces_list = self._open_couchdb_conn() -def _open_couchdb_conn(): - try: - couchdb_server_conn = server.couchdb.CouchDBServer() - workspaces_list = couchdb_server_conn.list_workspaces() + for workspace_name in workspaces_list: + logger.info(u'Setting up workspace {}'.format(workspace_name)) - except RequestError: - logger.error(u"CouchDB is not running at {}. Check faraday-server's"\ - " configuration and make sure CouchDB is running".format( - server.couchdb.get_couchdb_url())) - sys.exit(1) + if not server.couchdb.server_has_access_to(workspace_name): + logger.error(u"Unauthorized access to CouchDB. Make sure faraday-server's"\ + " configuration file has CouchDB admin's credentials set") + sys.exit(1) - except Unauthorized: - logger.error(u"Unauthorized access to CouchDB. Make sure faraday-server's"\ - " configuration file has CouchDB admin's credentials set") - sys.exit(1) + self.import_workspace_into_database(workspace_name) - return couchdb_server_conn, workspaces_list - - -def import_workspace_into_database(workspace_name, couchdb_server_conn): - - workspace, created = get_or_create(session, server.models.Workspace, name=workspace_name) - - _import_from_couchdb(workspace, couchdb_server_conn) - session.commit() - - return created - - -def _import_from_couchdb(workspace, couchdb_conn): - if 'FARADAY_DONT_IMPORT' in os.environ: - return - couchdb_workspace = server.couchdb.CouchDBWorkspace(workspace.name, couchdb_server_conn=couchdb_conn) - total_amount = couchdb_workspace.get_total_amount_of_documents() - processed_docs, progress = 0, 0 - should_flush_changes = False - host_entities = {} - - def flush_changes(): - host_entities.clear() - session.commit() - session.expunge_all() - - for doc in tqdm(couchdb_workspace.get_documents(per_request=1000), total=total_amount): - processed_docs = processed_docs + 1 - entity_data = doc.get('doc') - importer, entity = FaradayEntityImporter.parse(entity_data) - if entity is not None: - if isinstance(entity, server.models.Host) and should_flush_changes: - flush_changes() - should_flush_changes = False - - try: - importer.add_relationships_from_dict(entity, host_entities) - except EntityNotFound: - logger.warning(u"Ignoring {} entity ({}) because its parent wasn't found".format( - entity.entity_metadata.document_type, entity.entity_metadata.couchdb_id)) - else: - host_entities[doc.get('key')] = entity - session.add(entity) + def get_objs(self, host, obj_type, level): + data = { + "map": "function(doc) { if(doc.type == '%s' && doc._id.split('.').length == %d) emit(null, doc); }" % (obj_type, level) + } - logger.info(u'{} importation done!'.format(workspace.name)) - flush_changes() + r = requests.post(host, json=data) + + return r.json() + + def import_workspace_into_database(self, workspace_name): + + faraday_importer = FaradayEntityImporter() + workspace, created = get_or_create(session, server.models.Workspace, name=workspace_name) + + couch_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_temp_view?include_docs=true".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace_name + ) + + obj_types = [ + (1, 'Host'), + (1, 'EntityMetadata'), + (1, 'Note'), + (1, 'CommandRunInformation'), + (2, 'Interface'), + (2, 'Service'), + (2, 'Vulnerability'), + (2, 'VulnerabilityWeb'), + (3, 'Vulnerability'), + (3, 'VulnerabilityWeb'), + ] + couchdb_relational_map = {} + + for level, obj_type in obj_types: + obj_importer = faraday_importer.get_importer_from_document(obj_type) + objs_dict = self.get_objs(couch_url, obj_type, level) + for raw_obj in tqdm(objs_dict.get('rows', [])): + raw_obj = raw_obj['value'] + couchdb_id = raw_obj['_id'] + new_obj = obj_importer.update_from_document(raw_obj, workspace) + if raw_obj.get('parent', None): + obj_importer.set_parent( + new_obj, + couchdb_relational_map[raw_obj['parent']], + level + ) + session.commit() + couchdb_relational_map[couchdb_id] = new_obj.id + + return created From 0d1926ce6f575b7a2884f48dff05d58a601ada28 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 28 Aug 2017 16:49:23 -0300 Subject: [PATCH 0123/1506] Add Exception when Postgres is not running --- server/commands/__init__.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/server/commands/__init__.py b/server/commands/__init__.py index 1455f61686e..2776353d754 100644 --- a/server/commands/__init__.py +++ b/server/commands/__init__.py @@ -5,15 +5,17 @@ from subprocess import Popen, PIPE try: + # py2.7 from configparser import ConfigParser, NoSectionError, NoOptionError except ImportError: + # py3 from ConfigParser import ConfigParser, NoSectionError, NoOptionError from flask import current_app from flask_script import Command from colorama import init from colorama import Fore, Back, Style - +from sqlalchemy.exc import OperationalError from config.globals import CONST_FARADAY_HOME_PATH from server.config import LOCAL_CONFIG_FILE @@ -27,7 +29,7 @@ def _check_current_config(self, config): config.get('database', 'connection_string') reconfigure = None while not reconfigure: - reconfigure = raw_input('Database section {red} already found.{white} Do you want to reconfigure database? (yes/no) '.format(red=Fore.RED, white=Fore.WHITE)) + reconfigure = raw_input('Database section {yellow} already found.{white} Do you want to reconfigure database? (yes/no) '.format(yellow=Fore.YELLOW, white=Fore.WHITE)) if reconfigure.lower() == 'no': return False elif reconfigure.lower() == 'yes': @@ -77,18 +79,18 @@ def run(self): def _check_psql_output(self, psql_log_output): if 'unknown user: postgres' in psql_log_output: - raise UserWarning('postgres user not found. did you installed postgresql?') + raise UserWarning('postgres user not found. did you install postgresql?') def _configure_postgres(self, psql_log_file): """ This step will create the role on the database. we return username and password and those values will be saved in the config file. """ - username = raw_input('Please enter the {red} database user {white} (press enter to use "faraday"): '.format(red=Fore.RED, white=Fore.WHITE)) or 'faraday' + username = raw_input('Please enter the {blue} database user {white} (press enter to use "faraday"): '.format(blue=Fore.BLUE, white=Fore.WHITE)) or 'faraday' postgres_command = ['sudo', '-u', 'postgres'] password = None while not password: - password = getpass.getpass(prompt='Please enter the {red} password for the postgreSQL username {white}: '.format(red=Fore.RED, white=Fore.WHITE)) + password = getpass.getpass(prompt='Please enter the {blue} password for the postgreSQL username {white}: '.format(blue=Fore.BLUE, white=Fore.WHITE)) if not password: print('Please type a valid password') command = postgres_command + ['psql', '-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)] @@ -101,19 +103,19 @@ def _create_database(self, username, psql_log_file): This step uses the createdb command to add a new database. """ postgres_command = ['sudo', '-u', 'postgres'] - database_name = raw_input('Please enter the {red} database name {white} (press enter to use "faraday"): '.format(red=Fore.RED, white=Fore.WHITE)) or 'faraday' + database_name = raw_input('Please enter the {blue} database name {white} (press enter to use "faraday"): '.format(blue=Fore.BLUE, white=Fore.WHITE)) or 'faraday' print('Creating database {0}'.format(database_name)) command = postgres_command + ['createdb', '-O', username, database_name] p = Popen(command, stderr=psql_log_file, stdout=psql_log_file) p.wait() - return database_name + return database_name, p.returncode def _save_config(self, config, username, password, database_name): """ This step saves database configuration to server.ini """ db_server = 'localhost' - print('{red}Saving {white} database credentials file in {0}'.format(LOCAL_CONFIG_FILE, red=Fore.RED, white=Fore.WHITE)) + print('Saving database credentials file in {0}'.format(LOCAL_CONFIG_FILE)) conn_string = 'postgresql+psycopg2://{username}:{password}@{server}/{database_name}'.format( username=username, @@ -132,7 +134,16 @@ def _create_tables(self, conn_string): current_app.config['SQLALCHEMY_DATABASE_URI'] = conn_string try: db.create_all() + except OperationalError as ex: + if 'could not connect to server' in ex.message: + print('[ERROR] {red}PostgreSQL service{white} is not running. Please verify that it is running in port 5432 before executing setup script.'.format(red=Fore.RED, white=Fore.WHITE)) + sys.exit(1) + else: + raise except ImportError as ex: if 'psycopg2' in ex: - print('Missing python depency {red}psycopg2{white}. Please install it with {green}pip install psycopg2'.format(red=Fore.RED, white=Fore.WHITE, green=Fore.GREEN)) + print( + '[ERROR] Missing python depency {red}psycopg2{white}. Please install it with {blue}pip install psycopg2'.format(red=Fore.RED, white=Fore.WHITE, blue=Fore.BLUE)) sys.exit(1) + else: + raise From ace54b3f6879b78a438bbb8485c154c8b20dde50 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 28 Aug 2017 17:29:26 -0300 Subject: [PATCH 0124/1506] add manage script --- manage.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 manage.py diff --git a/manage.py b/manage.py new file mode 100755 index 00000000000..0eb516eab14 --- /dev/null +++ b/manage.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + + +from flask_script import Manager + +from server.web import app +from server.importer import ImportCouchDB + +manager = Manager(app) + + +if __name__ == "__main__": + manager.add_command('import-from-couchdb', ImportCouchDB()) + manager.run() From ca26d5cd23039af9fc97aea70ffff0c6b7a15757 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 28 Aug 2017 17:47:21 -0300 Subject: [PATCH 0125/1506] Add warning for specific errors on DB initialization Also changes the name for the default DB user --- server/commands/__init__.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/server/commands/__init__.py b/server/commands/__init__.py index 2776353d754..0f81fe896ca 100644 --- a/server/commands/__init__.py +++ b/server/commands/__init__.py @@ -29,7 +29,7 @@ def _check_current_config(self, config): config.get('database', 'connection_string') reconfigure = None while not reconfigure: - reconfigure = raw_input('Database section {yellow} already found.{white} Do you want to reconfigure database? (yes/no) '.format(yellow=Fore.YELLOW, white=Fore.WHITE)) + reconfigure = raw_input('Database section {yellow} already found{white}. Do you want to reconfigure database? (yes/no) '.format(yellow=Fore.YELLOW, white=Fore.WHITE)) if reconfigure.lower() == 'no': return False elif reconfigure.lower() == 'yes': @@ -60,15 +60,15 @@ def run(self): # current_psql_output is for checking psql command already known errors for each execution. psql_log_filename = os.path.join(faraday_path_conf, 'logs', 'psql_log.log') with open(psql_log_filename, 'a+') as psql_log_file: - username, password = self._configure_postgres(current_psql_output) + username, password, process_status = self._configure_postgres(current_psql_output) current_psql_output.seek(0) psql_output = current_psql_output.read() psql_log_file.write(psql_output) current_psql_output.seek(0) psql_output = current_psql_output.read() - self._check_psql_output(psql_output) - database_name = self._create_database(username, current_psql_output) - self._check_psql_output(psql_output) + self._check_psql_output(psql_output, process_status) + database_name, process_status = self._create_database(username, current_psql_output) + self._check_psql_output(psql_output, process_status) current_psql_output.close() conn_string = self._save_config(config, username, password, database_name) self._create_tables(conn_string) @@ -77,16 +77,22 @@ def run(self): print('User cancelled.') sys.exit(1) - def _check_psql_output(self, psql_log_output): + def _check_psql_output(self, psql_log_output, process_status): if 'unknown user: postgres' in psql_log_output: - raise UserWarning('postgres user not found. did you install postgresql?') + print('ERROR: Postgres user not found. Did you install package {blue}postgresql{white}?'.format(blue=Fore.BLUE, white=Fore.WHITE)) + elif 'could not connect to server' in psql_log_output: + print('ERROR: {red}PostgreSQL service{white} is not running. Please verify that it is running in port 5432 before executing setup script.'.format(red=Fore.RED, white=Fore.WHITE)) + elif process_status > 0: + print('ERROR: ' + psql_log_output) + sys.exit(1) def _configure_postgres(self, psql_log_file): """ This step will create the role on the database. we return username and password and those values will be saved in the config file. """ - username = raw_input('Please enter the {blue} database user {white} (press enter to use "faraday"): '.format(blue=Fore.BLUE, white=Fore.WHITE)) or 'faraday' + username_default = 'faraday_db_admin' + username = raw_input('Please enter the {blue} database user {white} (press enter to use "{0}"): '.format(username_default, blue=Fore.BLUE, white=Fore.WHITE)) or username_default postgres_command = ['sudo', '-u', 'postgres'] password = None while not password: @@ -96,7 +102,7 @@ def _configure_postgres(self, psql_log_file): command = postgres_command + ['psql', '-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)] p = Popen(command, stderr=psql_log_file, stdout=psql_log_file) p.wait() - return username, password + return username, password, p.returncode def _create_database(self, username, psql_log_file): """ @@ -136,14 +142,14 @@ def _create_tables(self, conn_string): db.create_all() except OperationalError as ex: if 'could not connect to server' in ex.message: - print('[ERROR] {red}PostgreSQL service{white} is not running. Please verify that it is running in port 5432 before executing setup script.'.format(red=Fore.RED, white=Fore.WHITE)) + print('ERROR: {red}PostgreSQL service{white} is not running. Please verify that it is running in port 5432 before executing setup script.'.format(red=Fore.RED, white=Fore.WHITE)) sys.exit(1) else: raise except ImportError as ex: if 'psycopg2' in ex: print( - '[ERROR] Missing python depency {red}psycopg2{white}. Please install it with {blue}pip install psycopg2'.format(red=Fore.RED, white=Fore.WHITE, blue=Fore.BLUE)) + 'ERROR: Missing python depency {red}psycopg2{white}. Please install it with {blue}pip install psycopg2'.format(red=Fore.RED, white=Fore.WHITE, blue=Fore.BLUE)) sys.exit(1) else: raise From afba2442a9e62d60a22f6f324573a4141e8b4338 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 28 Aug 2017 18:49:27 -0300 Subject: [PATCH 0126/1506] normalize manage command --- manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage.py b/manage.py index 44b8bad5fc0..498d5d07bfb 100755 --- a/manage.py +++ b/manage.py @@ -12,7 +12,7 @@ if __name__ == "__main__": - manager.add_command('import-from-couchdb', ImportCouchDB()) + manager.add_command('import_from_couchdb', ImportCouchDB()) manager.add_command('generate_database_schemas', DatabaseSchema()) manager.add_command('initdb', InitDB()) manager.add_command('faraday_schema_display', DatabaseSchema()) From fc91cffaa1bfc477bc9b66070043730f034147c9 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 28 Aug 2017 19:08:23 -0300 Subject: [PATCH 0127/1506] Fix bug introduced in InitDB When checking the log for the Init DB script the program always ended. --- server/commands/initdb.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/commands/initdb.py b/server/commands/initdb.py index 0f81fe896ca..b32905ee758 100644 --- a/server/commands/initdb.py +++ b/server/commands/initdb.py @@ -84,7 +84,9 @@ def _check_psql_output(self, psql_log_output, process_status): print('ERROR: {red}PostgreSQL service{white} is not running. Please verify that it is running in port 5432 before executing setup script.'.format(red=Fore.RED, white=Fore.WHITE)) elif process_status > 0: print('ERROR: ' + psql_log_output) - sys.exit(1) + + if process_status is not 0: + sys.exit(process_status) def _configure_postgres(self, psql_log_file): """ From 489d4dd7cb23f3826b2048e3ca7371779d88762e Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 28 Aug 2017 19:14:59 -0300 Subject: [PATCH 0128/1506] Fix Enum error on DB creation PostgreSQL requires all Enum to be named. --- server/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/models.py b/server/models.py index 06854c8b96a..bcef172b576 100644 --- a/server/models.py +++ b/server/models.py @@ -124,7 +124,7 @@ class Service(db.Model): owned = Column(Boolean, nullable=False, default=False) protocol = Column(Text, nullable=False) - status = Column(Enum(*STATUSES), nullable=True) + status = Column(Enum(*STATUSES, name='service_statuses'), nullable=True) version = Column(Text, nullable=True) banner = Column(Text, nullable=True) @@ -165,7 +165,7 @@ class VulnerabilityABC(db.Model): data = Column(Text, nullable=True) description = Column(Text, nullable=False) - ease_of_resolution = Column(Enum(*EASE_OF_RESOLUTIONS), nullable=True) + ease_of_resolution = Column(Enum(*EASE_OF_RESOLUTIONS, name='vulnerability_ease_of_resolution'), nullable=True) name = Column(Text, nullable=False) resolution = Column(Text, nullable=True) severity = Column(String(50), nullable=False) @@ -196,8 +196,8 @@ class VulnerabilityGeneric(VulnerabilityABC): __tablename__ = 'vulnerability' confirmed = Column(Boolean, nullable=False, default=False) - status = Column(Enum(*STATUSES), nullable=False, default="open") - type = Column(Enum(*VULN_TYPES), nullable=False) + status = Column(Enum(*STATUSES, name='vulnerbaility_statuses'), nullable=False, default="open") + type = Column(Enum(*VULN_TYPES, name='vulnerability_types'), nullable=False) workspace_id = Column( Integer, @@ -607,7 +607,7 @@ class Task(TaskABC): id = Column(Integer, primary_key=True) due_date = Column(DateTime, nullable=True) - status = Column(Enum(*STATUSES), nullable=True) + status = Column(Enum(*STATUSES, name='task_statuses'), nullable=True) __mapper_args__ = { 'concrete': True From 6a910117393bbb13621fb6c20143c34aecb41e49 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 28 Aug 2017 19:37:27 -0300 Subject: [PATCH 0129/1506] Fix typo --- server/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index bcef172b576..79d85fa69c9 100644 --- a/server/models.py +++ b/server/models.py @@ -196,7 +196,7 @@ class VulnerabilityGeneric(VulnerabilityABC): __tablename__ = 'vulnerability' confirmed = Column(Boolean, nullable=False, default=False) - status = Column(Enum(*STATUSES, name='vulnerbaility_statuses'), nullable=False, default="open") + status = Column(Enum(*STATUSES, name='vulnerability_statuses'), nullable=False, default="open") type = Column(Enum(*VULN_TYPES, name='vulnerability_types'), nullable=False) workspace_id = Column( From f70862e87bcdf3f5b8023a061ad95bbfbb1ed73e Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 26 Jun 2017 11:48:51 -0300 Subject: [PATCH 0130/1506] ADD postgresql support --- server/dao/host.py | 5 +++-- server/dao/service.py | 2 +- server/utils/database.py | 27 +++++++++++++++++++++++---- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/server/dao/host.py b/server/dao/host.py index 75f48a776d9..b90e55ef08d 100644 --- a/server/dao/host.py +++ b/server/dao/host.py @@ -69,8 +69,9 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde .outerjoin(EntityMetadata, EntityMetadata.id == Host.entity_metadata_id)\ .outerjoin(Vulnerability, Host.id == Vulnerability.host_id)\ .outerjoin(Service, (Host.id == Service.host_id) & (Service.status.in_(("open", "running", "opened"))))\ - .outerjoin(Credential, (Credential.host_id == Host.id) & Credential.service_id == None)\ - .group_by(Host.id) + .outerjoin(Credential, (Credential.host_id == Host.id) ) \ + .filter(Credential.service_id == None) \ + .group_by(Host.id, EntityMetadata.id) query = query.filter(Host.workspace == self.workspace) # Apply pagination, sorting and filtering options to the query diff --git a/server/dao/service.py b/server/dao/service.py index c98fc991538..f39cd32b2fc 100644 --- a/server/dao/service.py +++ b/server/dao/service.py @@ -45,7 +45,7 @@ def list(self, service_filter={}): func.count(distinct(Credential.id)).label("credentials_count")) query = self._session.query(service_bundle).\ - group_by(Service.id).\ + group_by(Service.id, EntityMetadata.id).\ outerjoin(EntityMetadata, EntityMetadata.id == Service.entity_metadata_id).\ outerjoin(Vulnerability, Service.id == Vulnerability.service_id).group_by(Service.id).\ outerjoin(Credential, (Credential.service_id == Service.id) and (Credential.host_id == None)) diff --git a/server/utils/database.py b/server/utils/database.py index 7da669d1220..bb506440cca 100644 --- a/server/utils/database.py +++ b/server/utils/database.py @@ -2,11 +2,11 @@ # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information -import server.utils.logger - from sqlalchemy import distinct, Boolean from sqlalchemy.sql import func, asc, desc from sqlalchemy.sql.expression import ClauseElement +from sqlalchemy.sql import expression +from sqlalchemy.ext import compiler class ORDER_DIRECTIONS: @@ -18,7 +18,7 @@ def paginate(query, page, page_size): """ Limit results from a query based on pagination parameters """ - if not (page >= 0 and page_size >=0): + if not (page >= 0 and page_size >= 0): raise Exception("invalid values for pagination (page: %d, page_size: %d)" % (page, page_size)) return query.limit(page_size).offset(page * page_size) @@ -153,7 +153,8 @@ def get_count(query, count_col=None): else: count_filter = [func.count(distinct(count_col))] - count_q = query.statement.with_only_columns(count_filter).order_by(None).group_by(None) + count_q = query.statement.with_only_columns(count_filter).\ + order_by(None).group_by(None) count = query.session.execute(count_q).scalar() return count @@ -169,3 +170,21 @@ def get_or_create(session, model, defaults=None, **kwargs): instance = model(**params) session.add(instance) return instance, True + + +class GroupConcat(expression.FunctionElement): + name = "group_concat" + + +@compiler.compiles(GroupConcat, 'postgresql') +def _group_concat_postgresql(element, compiler, **kw): + if len(element.clauses) == 2: + separator = compiler.process(element.clauses.clauses[1]) + else: + separator = ',' + + res = 'array_to_string(array_agg({0}), \'{1}\')'.format( + compiler.process(element.clauses.clauses[0]), + separator, + ) + return res From a6b793842b90e5b3120068750957b82c923cdf6e Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 10 Jul 2017 16:13:20 -0300 Subject: [PATCH 0131/1506] Fix more code to work in postgresql --- server/dao/vuln.py | 12 +++++++----- server/database.py | 2 +- server/importer.py | 2 +- server/utils/database.py | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/server/dao/vuln.py b/server/dao/vuln.py index 8d12e2a70b6..1a181424544 100644 --- a/server/dao/vuln.py +++ b/server/dao/vuln.py @@ -6,6 +6,7 @@ from server.dao.base import FaradayDAO from server.utils.database import ( + GroupConcat, paginate, sort_results, apply_search_filter, @@ -23,6 +24,7 @@ ) + class VulnerabilityDAO(FaradayDAO): MAPPED_ENTITY = Vulnerability COLUMNS_MAP = { @@ -86,8 +88,9 @@ def __query_database(self, search=None, page=0, page_size=0, order_by=None, orde # directly. query = self._session.query(vuln_bundle, service_bundle, - host_bundle)\ - .group_by(Vulnerability.id)\ + host_bundle, + GroupConcat(Interface.hostnames))\ + .group_by(Vulnerability.id, EntityMetadata.id, Service.id, Host.id)\ .outerjoin(EntityMetadata, EntityMetadata.id == Vulnerability.entity_metadata_id)\ .outerjoin(Service, Service.id == Vulnerability.service_id)\ .outerjoin(Host, Host.id == Vulnerability.host_id)\ @@ -201,10 +204,9 @@ def count(self, group_by=None, search=None, vuln_filter={}): return None col = VulnerabilityDAO.COLUMNS_MAP.get(group_by)[0] - vuln_bundle = Bundle('vuln', Vulnerability.id, col) + vuln_bundle = Bundle('vuln', col) query = self._session.query(vuln_bundle, func.count())\ - .group_by(col)\ - .outerjoin(EntityMetadata, EntityMetadata.id == Vulnerability.entity_metadata_id) + .group_by(col) query = apply_search_filter(query, self.COLUMNS_MAP, search, vuln_filter, self.STRICT_FILTERING) result = query.all() diff --git a/server/database.py b/server/database.py index 01f53001547..343b79b27cf 100644 --- a/server/database.py +++ b/server/database.py @@ -25,7 +25,7 @@ def add_entity_from_doc(self, document): include into the database (ie: these documents are added on future changes) """ - entity = server.models.FaradayEntity.parse(document) + entity = server.models.FaradayEntity.parse(self.__db_conn.session, document) if entity is None: return False diff --git a/server/importer.py b/server/importer.py index 56c95909c5c..67ace178276 100644 --- a/server/importer.py +++ b/server/importer.py @@ -340,6 +340,7 @@ def update_from_document(self, document): raise Exception('MUST IMPLEMENT') + def import_workspaces(): """ Main entry point for couchdb import @@ -386,7 +387,6 @@ def import_workspace_into_database(workspace_name, couchdb_server_conn): _import_from_couchdb(workspace, couchdb_server_conn) session.commit() - return created diff --git a/server/utils/database.py b/server/utils/database.py index bb506440cca..e13800ae65c 100644 --- a/server/utils/database.py +++ b/server/utils/database.py @@ -94,7 +94,7 @@ def apply_search_filter(query, field_to_col_map, free_text_search=None, field_fi # ignore this list since its purpose is clearly to # match anything it can find. if is_direct_filter_search and attribute in strict_filter: - search_term = column.is_(field_filter.get(attribute)) + search_term = column.op('=')(field_filter.get(attribute)) else: search_term = column.like(like_str) From 5226640210369c40b6b21e56d37dd9da904cd48f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 29 Aug 2017 16:07:16 -0300 Subject: [PATCH 0132/1506] update code to use the new model --- server/importer.py | 106 +++++++++++++++++++++++++++++++-------------- server/models.py | 6 +-- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/server/importer.py b/server/importer.py index a0c9f8bd821..2e1d0868db6 100644 --- a/server/importer.py +++ b/server/importer.py @@ -4,7 +4,7 @@ import sys import json -import os +import datetime import requests from tqdm import tqdm @@ -26,6 +26,7 @@ Service, Command, Workspace, + Hostname, Vulnerability ) @@ -78,7 +79,7 @@ class HostImporter(object): @classmethod def update_from_document(cls, document, workspace): # Ticket #3387: if the 'os' field is None, we default to 'unknown' - host, created = get_or_create(session, Host, name=document.get('name')) + host, created = get_or_create(session, Host, ip=document.get('name')) if not document.get('os'): document['os'] = 'unknown' @@ -98,34 +99,60 @@ def set_parent(self, host, parent): class InterfaceImporter(object): + """ + Class interface was removed in the new model. + We will merge the interface data with the host. + For ports we will create new services for open ports + if it was not previously created. + """ DOC_TYPE = 'Interface' @classmethod def update_from_document(cls, document, workspace): - interface, created = get_or_create(session, Interface, name=document.get('name')) - interface.name = document.get('name') - interface.description = document.get('description') - interface.mac = document.get('mac') - interface.owned = document.get('owned', False) - interface.hostnames = u','.join(document.get('hostnames') or []) - interface.network_segment = document.get('network_segment') - interface.ipv4_address = document.get('ipv4').get('address') - interface.ipv4_gateway = document.get('ipv4').get('gateway') - interface.ipv4_dns = u','.join(document.get('ipv4').get('DNS')) - interface.ipv4_mask = document.get('ipv4').get('mask') - interface.ipv6_address = document.get('ipv6').get('address') - interface.ipv6_gateway = document.get('ipv6').get('gateway') - interface.ipv6_dns = u','.join(document.get('ipv6').get('DNS')) - interface.ipv6_prefix = str(document.get('ipv6').get('prefix')) - interface.ports_filtered = document.get('ports', {}).get('filtered') - interface.ports_opened = document.get('ports', {}).get('opened') - interface.ports_closed = document.get('ports', {}).get('closed') - interface.workspace = workspace + + interface = {} + interface['name'] = document.get('name') + interface['description'] = document.get('description') + interface['mac'] = document.get('mac') + interface['owned'] = document.get('owned', False) + interface['hostnames'] = document.get('hostnames') + interface['network_segment'] = document.get('network_segment') + interface['ipv4_address'] = document.get('ipv4').get('address') + interface['ipv4_gateway'] = document.get('ipv4').get('gateway') + interface['ipv4_dns'] = document.get('ipv4').get('DNS') + interface['ipv4_mask'] = document.get('ipv4').get('mask') + interface['ipv6_address'] = document.get('ipv6').get('address') + interface['ipv6_gateway'] = document.get('ipv6').get('gateway') + interface['ipv6_dns'] = document.get('ipv6').get('DNS') + interface['ipv6_prefix'] = str(document.get('ipv6').get('prefix')) + # ports_* are integers with counts + interface['ports_filtered'] = document.get('ports', {}).get('filtered') + interface['ports_opened'] = document.get('ports', {}).get('opened') + interface['ports_closed'] = document.get('ports', {}).get('closed') + interface['workspace'] = workspace return interface @classmethod def set_parent(cls, interface, parent_relation_db_id, level): - interface.host = session.query(Host).filter_by(id=parent_relation_db_id).first() + host = session.query(Host).filter_by(id=parent_relation_db_id).first() + assert host.workspace == interface['workspace'] + if interface['mac']: + host.mac = interface['mac'] + if interface['owned']: + host.owned = interface['owned'] + if interface['ipv4_address'] or interface['ipv6_address']: + host.ip = interface['ipv4_address'] or interface['ipv6_address'] + if interface['ipv4_gateway'] or interface['ipv6_gateway']: + host.default_gateway_ip = interface['ipv4_gateway'] or interface['ipv6_gateway'] + #host.default_gateway_mac + if interface['network_segment']: + host.net_segment = interface['network_segment'] + if interface['description']: + host.description += '\n {0}'.format(interface['description']) + + for hostname in interface['hostnames']: + hostname, created = get_or_create(session, Hostname, name=hostname, host=host) + host.owned = host.owned or interface['owned'] class ServiceImporter(object): @@ -180,7 +207,12 @@ def update_from_document(cls, document, workspace): vulnerability.request = document.get('request') vulnerability.response = document.get('response') vulnerability.website = document.get('website') - vulnerability.status = document.get('status', 'opened') + status_map = { + 'opened': 'open', + 'closed': 'closed', + } + status = status_map[document.get('status', 'opened')] + vulnerability.status = status vulnerability.workspace = workspace params = document.get('params', u'') @@ -204,10 +236,16 @@ class CommandImporter(object): @classmethod def update_from_document(cls, document, workspace): - command, instance = get_or_create(session, Command, command=document.get('command', None)) + start_date = datetime.datetime.fromtimestamp(document.get('itime')) + end_date = start_date + datetime.timedelta(seconds=document.get('duration')) + command, instance = get_or_create( + session, + Command, + command=document.get('command', None), + start_date=start_date, + end_date=end_date + ) command.command = document.get('command', None) - command.duration = document.get('duration', None) - command.itime = document.get('itime', None) command.ip = document.get('ip', None) command.hostname = document.get('hostname', None) command.params = document.get('params', None) @@ -291,15 +329,12 @@ def get_importer_from_document(cls, doc_type): 'Host': HostImporter, 'Service': ServiceImporter, 'Note': NoteImporter, + 'Interface': InterfaceImporter, 'CommandRunInformation': CommandImporter, 'Workspace': WorkspaceImporter, 'Vulnerability': VulnerabilityImporter, 'VulnerabilityWeb': VulnerabilityImporter, } - # TODO: remove this! - if doc_type in ('Communication', 'Cred', 'Reports', - 'Task', 'TaskGroup', 'Interface', 'Note'): - return importer_cls = importer_class_mapper.get(doc_type, None) if not importer_cls: raise NotImplementedError('Class importer for {0} not implemented'.format(doc_type)) @@ -368,6 +403,9 @@ def import_workspace_into_database(self, workspace_name): workspace_name=workspace_name ) + # obj_types are tuples. the first value is the level on the tree + # for the desired obj. + # the idea is to improt by level from couchdb data. obj_types = [ (1, 'Host'), (1, 'EntityMetadata'), @@ -381,13 +419,16 @@ def import_workspace_into_database(self, workspace_name): (3, 'VulnerabilityWeb'), ] couchdb_relational_map = {} - + removed_objs = ['Interface'] for level, obj_type in obj_types: obj_importer = faraday_importer.get_importer_from_document(obj_type) objs_dict = self.get_objs(couch_url, obj_type, level) for raw_obj in tqdm(objs_dict.get('rows', [])): raw_obj = raw_obj['value'] couchdb_id = raw_obj['_id'] + if obj_importer is None: + import ipdb + ipdb.set_trace() new_obj = obj_importer.update_from_document(raw_obj, workspace) if raw_obj.get('parent', None): obj_importer.set_parent( @@ -396,6 +437,7 @@ def import_workspace_into_database(self, workspace_name): level ) session.commit() - couchdb_relational_map[couchdb_id] = new_obj.id + if obj_type not in removed_objs: + couchdb_relational_map[couchdb_id] = new_obj.id return created diff --git a/server/models.py b/server/models.py index bcef172b576..a5c78d7f8e6 100644 --- a/server/models.py +++ b/server/models.py @@ -428,13 +428,13 @@ class Credential(db.Model): class Command(db.Model): __tablename__ = 'command' id = Column(Integer, primary_key=True) - command = Column(String(250), nullable=False) + command = Column(Text(), nullable=False) start_date = Column(DateTime, nullable=False) end_date = Column(DateTime, nullable=False) ip = Column(String(250), nullable=False) # where the command was executed hostname = Column(String(250), nullable=False) # where the command was executed - params = Column(String(250), nullable=True) - user = Column(String(250), nullable=True) # where the command was executed + params = Column(Text(), nullable=True) + user = Column(String(250), nullable=True) # os username where the command was executed workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) workspace = relationship('Workspace', foreign_keys=[workspace_id]) From a28fe7efae4989d8ba1e913868b1143a654088d0 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 29 Aug 2017 16:23:14 -0300 Subject: [PATCH 0133/1506] Temporary fix for service_id prop Vuln and VulnWeb --- server/models.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/server/models.py b/server/models.py index bcef172b576..5fadc2ecf06 100644 --- a/server/models.py +++ b/server/models.py @@ -219,17 +219,7 @@ class Vulnerability(VulnerabilityGeneric): foreign_keys=[host_id], ) - @declared_attr - def service_id(cls): - return VulnerabilityGeneric.__table__.c.get( - 'service_id', - Column( - Integer, - ForeignKey(Service.id), - index=True - ) - ) - + service_id = Column(Integer, ForeignKey(Service.id)) service = relationship( 'Service', backref='vulnerabilities', @@ -254,17 +244,7 @@ class VulnerabilityWeb(VulnerabilityGeneric): response = Column(Text(), nullable=True) website = Column(String(250), nullable=True) - @declared_attr - def service_id(cls): - return VulnerabilityGeneric.__table__.c.get( - 'service_id', - Column( - Integer, - ForeignKey(Service.id), - index=True, - ) - ) - + service_id = Column(Integer, ForeignKey(Service.id)) service = relationship( 'Service', backref='vulnerabilities_web', From c06dcb084ae4283e0afbd2f2ac7b1109e37a8f77 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 29 Aug 2017 18:08:52 -0300 Subject: [PATCH 0134/1506] Add model for Executive Report --- server/models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/server/models.py b/server/models.py index d24303c7b6b..408fe7dc128 100644 --- a/server/models.py +++ b/server/models.py @@ -666,3 +666,26 @@ class Comment(db.Model): workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) workspace = relationship('Workspace', foreign_keys=[workspace_id]) + +class ExecutiveReport(db.Model): + STATUSES = [ + 'created' + ] + __tablename__ = 'executive_report' + id = Column(Integer, primary_key=True) + + grouped = Column(Boolean, nullable=False, default=False) + name = Column(Text, nullable=False, index=True) + status = Column(Enum(*STATUSES, name='executive_report_statuses'), nullable=True) + template_name = Column(Text, nullable=False) + + conclusions = Column(Text, nullable=True) + enterprise = Column(Text, nullable=True) + objectives = Column(Text, nullable=True) + recommendations = Column(Text, nullable=True) + scope = Column(Text, nullable=True) + summary = Column(Text, nullable=True) + title = Column(Text, nullable=True) + + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace = relationship('Workspace', foreign_keys=[workspace_id]) From c470240070a278fb3f807d41838d6a29ac658d02 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 29 Aug 2017 18:32:18 -0300 Subject: [PATCH 0135/1506] Add Statuses for Executive Reports --- server/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index 408fe7dc128..82d557bb69a 100644 --- a/server/models.py +++ b/server/models.py @@ -669,7 +669,9 @@ class Comment(db.Model): class ExecutiveReport(db.Model): STATUSES = [ - 'created' + 'created', + 'error', + 'processing', ] __tablename__ = 'executive_report' id = Column(Integer, primary_key=True) From fa7a8ccdaf343efd3dbb732925f82b05d2d47a08 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 29 Aug 2017 20:04:06 -0300 Subject: [PATCH 0136/1506] Importer updates. add credential support and import one service per port --- server/importer.py | 128 +++++++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 50 deletions(-) diff --git a/server/importer.py b/server/importer.py index 2e1d0868db6..8246d4cd815 100644 --- a/server/importer.py +++ b/server/importer.py @@ -43,7 +43,7 @@ def __init__(self, entity_id): class EntityMetadataImporter(object): @classmethod - def update_from_document(cls, document): + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): entity, created = get_or_create(session, EntityMetadata, couchdb_id=document.get('_id')) metadata = document.get('metadata', dict()) entity.update_time = metadata.get('update_time', None) @@ -60,7 +60,7 @@ def update_from_document(cls, document): if entity.create_time is not None: entity.create_time = cls.__truncate_to_epoch_in_seconds(entity.create_time) - return entity + yield entity @classmethod def __truncate_to_epoch_in_seconds(self, timestamp): @@ -77,7 +77,7 @@ class HostImporter(object): DOC_TYPE = 'Host' @classmethod - def update_from_document(cls, document, workspace): + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): # Ticket #3387: if the 'os' field is None, we default to 'unknown' host, created = get_or_create(session, Host, ip=document.get('name')) if not document.get('os'): @@ -92,7 +92,7 @@ def update_from_document(cls, document, workspace): host.default_gateway_mac = default_gateway and default_gateway[1] or '' host.owned = document.get('owned', False) host.workspace = workspace - return host + yield host def set_parent(self, host, parent): raise NotImplementedError @@ -108,7 +108,7 @@ class InterfaceImporter(object): DOC_TYPE = 'Interface' @classmethod - def update_from_document(cls, document, workspace): + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): interface = {} interface['name'] = document.get('name') @@ -130,7 +130,7 @@ def update_from_document(cls, document, workspace): interface['ports_opened'] = document.get('ports', {}).get('opened') interface['ports_closed'] = document.get('ports', {}).get('closed') interface['workspace'] = workspace - return interface + yield interface @classmethod def set_parent(cls, interface, parent_relation_db_id, level): @@ -159,32 +159,44 @@ class ServiceImporter(object): DOC_TYPE = 'Service' @classmethod - def update_from_document(cls, document, workspace): - service, created = get_or_create(session, Service, name=document.get('name')) - service.name = document.get('name') - service.description = document.get('description') - service.owned = document.get('owned', False) - service.protocol = document.get('protocol') - service.status = document.get('status') - service.version = document.get('version') - service.workspace = workspace - - # We found workspaces where ports are defined as an integer - if isinstance(document.get('ports', None), (int, long)): - service.ports = str(document.get('ports')) - else: - service.ports = u','.join(map(str, document.get('ports'))) - return service + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + #service was always above interface, not it's above host. + try: + parent_id = document['parent'].split('.')[0] + except KeyError: + # some services are missing the parent key + parent_id = document['_id'].split('.')[0] + host, created = get_or_create(session, Host, id=couchdb_relational_map[parent_id]) + for port in document.get('ports'): + service, created = get_or_create(session, Service, name=document.get('name'), port=port) + service.name = document.get('name') + service.description = document.get('description') + service.owned = document.get('owned', False) + service.protocol = document.get('protocol') + if not document.get('status'): + logger.warning('Service {0} with empty status. Using open as status'.format(document['_id'])) + document['status'] = 'open' + status_mapper = { + 'open': 'open', + 'closed': 'closed', + 'filtered': 'filtered' + } + service.status = status_mapper[document.get('status')] + service.version = document.get('version') + service.workspace = workspace + + yield service - def set_parent(self, service, parent_id): - raise NotImplementedError + @classmethod + def set_parent(self, service, parent_id, level): + pass class VulnerabilityImporter(object): DOC_TYPE = ['Vulnerability', 'VulnerabilityWeb'] @classmethod - def update_from_document(cls, document, workspace): + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): vulnerability, created = get_or_create(session, Vulnerability, name=document.get('name'), description= document.get('desc')) vulnerability.confirmed = document.get('confirmed') vulnerability.vuln_type = document.get('type') @@ -221,7 +233,7 @@ def update_from_document(cls, document, workspace): else: vulnerability.params = params if params is not None else u'' - return vulnerability + yield vulnerability @classmethod def set_parent(self, vulnerability, parent_id, level=2): @@ -235,7 +247,7 @@ class CommandImporter(object): DOC_TYPE = 'CommandRunInformation' @classmethod - def update_from_document(cls, document, workspace): + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): start_date = datetime.datetime.fromtimestamp(document.get('itime')) end_date = start_date + datetime.timedelta(seconds=document.get('duration')) command, instance = get_or_create( @@ -252,7 +264,7 @@ def update_from_document(cls, document, workspace): command.user = document.get('user', None) command.workspace = workspace - return command + yield command def set_parent(self, command, parent_id): raise NotImplementedError @@ -262,13 +274,13 @@ class NoteImporter(object): DOC_TYPE = 'Note' @classmethod - def update_from_document(cls, document): + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): note = Note() note.name = document.get('name') note.text = document.get('text', None) note.description = document.get('description', None) note.owned = document.get('owned', False) - return note + yield note def add_relationships_from_dict(self, entity, entities): # this method is not required since update_from_document uses @@ -280,18 +292,25 @@ class CredentialImporter(object): DOC_TYPE = 'Cred' @classmethod - def update_from_document(cls, document, workspace): - credential, created = get_or_create(session, Credential, name=document.get('username')) + def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + host = None + service = None + if level == 2: + parent_id = couchdb_relational_map[document['_id'].split('.')[0]] + host = session.query(Host).filter_by(id=parent_id).first() + if level == 4: + parent_id = couchdb_relational_map['.'.join(document['_id'].split('.')[:3])] + service = session.query(Service).filter_by(id=parent_id).first() + if not host and not service: + raise Exception('Missing host or service for credential {0}'.format(document['_id'])) + credential, created = get_or_create(session, Credential, name=document.get('username'), host=host, service=service) credential.username = document.get('username') credential.password = document.get('password', '') credential.owned = document.get('owned', False) credential.description = document.get('description', '') credential.name = document.get('name', '') credential.workspace = workspace - return credential - - def set_parent(self, credential, parent_id): - raise NotImplementedError + yield credential class WorkspaceImporter(object): @@ -300,7 +319,7 @@ class WorkspaceImporter(object): @classmethod def update_from_document(cls, document): workspace, created = get_or_create(session, server.models.Workspace, name=document.get('name', None)) - return workspace + yield workspace def add_relationships_from_dict(self, entity, entities): for couch_id, child_entity in entities.items(): @@ -329,6 +348,7 @@ def get_importer_from_document(cls, doc_type): 'Host': HostImporter, 'Service': ServiceImporter, 'Note': NoteImporter, + 'Credential': CredentialImporter, 'Interface': InterfaceImporter, 'CommandRunInformation': CommandImporter, 'Workspace': WorkspaceImporter, @@ -382,6 +402,8 @@ def run(self): self.import_workspace_into_database(workspace_name) def get_objs(self, host, obj_type, level): + if obj_type == 'Credential': + obj_type = 'Cred' data = { "map": "function(doc) { if(doc.type == '%s' && doc._id.split('.').length == %d) emit(null, doc); }" % (obj_type, level) } @@ -413,12 +435,16 @@ def import_workspace_into_database(self, workspace_name): (1, 'CommandRunInformation'), (2, 'Interface'), (2, 'Service'), + (2, 'Credential'), (2, 'Vulnerability'), (2, 'VulnerabilityWeb'), (3, 'Vulnerability'), (3, 'VulnerabilityWeb'), + (3, 'Service'), + (4, 'Credential'), # Level 4 is for interface ] couchdb_relational_map = {} + couchdb_removed_objs = set() removed_objs = ['Interface'] for level, obj_type in obj_types: obj_importer = faraday_importer.get_importer_from_document(obj_type) @@ -426,18 +452,20 @@ def import_workspace_into_database(self, workspace_name): for raw_obj in tqdm(objs_dict.get('rows', [])): raw_obj = raw_obj['value'] couchdb_id = raw_obj['_id'] - if obj_importer is None: - import ipdb - ipdb.set_trace() - new_obj = obj_importer.update_from_document(raw_obj, workspace) - if raw_obj.get('parent', None): - obj_importer.set_parent( - new_obj, - couchdb_relational_map[raw_obj['parent']], - level - ) - session.commit() - if obj_type not in removed_objs: - couchdb_relational_map[couchdb_id] = new_obj.id + for new_obj in obj_importer.update_from_document(raw_obj, workspace, level, couchdb_relational_map): + if raw_obj.get('parent', None): + parent_id = raw_obj['parent'] + if parent_id in couchdb_removed_objs: + parent_id = '.'.join(parent_id.split('.')[:-1]) + obj_importer.set_parent( + new_obj, + couchdb_relational_map[parent_id], + level + ) + session.commit() + if obj_type not in removed_objs: + couchdb_relational_map[couchdb_id] = new_obj.id + else: + couchdb_removed_objs.add(couchdb_id) return created From 42d1d7c05ac07e895ce86c6cf3d7f79142c3e0de Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 30 Aug 2017 11:53:38 -0300 Subject: [PATCH 0137/1506] Code clean up --- server/importer.py | 99 ++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 64 deletions(-) diff --git a/server/importer.py b/server/importer.py index 8246d4cd815..bc0d15608fe 100644 --- a/server/importer.py +++ b/server/importer.py @@ -42,8 +42,7 @@ def __init__(self, entity_id): class EntityMetadataImporter(object): - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): entity, created = get_or_create(session, EntityMetadata, couchdb_id=document.get('_id')) metadata = document.get('metadata', dict()) entity.update_time = metadata.get('update_time', None) @@ -58,11 +57,11 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa entity.command_id = metadata.get('command_id', None) if entity.create_time is not None: - entity.create_time = cls.__truncate_to_epoch_in_seconds(entity.create_time) + entity.create_time = self.__truncate_to_epoch_in_seconds(entity.create_time) yield entity - @classmethod + def __truncate_to_epoch_in_seconds(self, timestamp): """ In a not so elegant fashion, identifies and truncate epoch timestamps expressed in milliseconds to seconds""" @@ -76,8 +75,7 @@ def __truncate_to_epoch_in_seconds(self, timestamp): class HostImporter(object): DOC_TYPE = 'Host' - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): # Ticket #3387: if the 'os' field is None, we default to 'unknown' host, created = get_or_create(session, Host, ip=document.get('name')) if not document.get('os'): @@ -94,9 +92,6 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa host.workspace = workspace yield host - def set_parent(self, host, parent): - raise NotImplementedError - class InterfaceImporter(object): """ @@ -107,8 +102,7 @@ class InterfaceImporter(object): """ DOC_TYPE = 'Interface' - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): interface = {} interface['name'] = document.get('name') @@ -130,10 +124,14 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa interface['ports_opened'] = document.get('ports', {}).get('opened') interface['ports_closed'] = document.get('ports', {}).get('closed') interface['workspace'] = workspace + couch_parent_id = document.get('parent', None) + if not couch_parent_id: + couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) + parent_id = couchdb_relational_map[couch_parent_id] + self.merge_with_host(interface, parent_id) yield interface - @classmethod - def set_parent(cls, interface, parent_relation_db_id, level): + def merge_with_host(self, interface, parent_relation_db_id): host = session.query(Host).filter_by(id=parent_relation_db_id).first() assert host.workspace == interface['workspace'] if interface['mac']: @@ -158,8 +156,8 @@ def set_parent(cls, interface, parent_relation_db_id, level): class ServiceImporter(object): DOC_TYPE = 'Service' - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): #service was always above interface, not it's above host. try: parent_id = document['parent'].split('.')[0] @@ -187,16 +185,12 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa yield service - @classmethod - def set_parent(self, service, parent_id, level): - pass - class VulnerabilityImporter(object): DOC_TYPE = ['Vulnerability', 'VulnerabilityWeb'] - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): vulnerability, created = get_or_create(session, Vulnerability, name=document.get('name'), description= document.get('desc')) vulnerability.confirmed = document.get('confirmed') vulnerability.vuln_type = document.get('type') @@ -233,9 +227,13 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa else: vulnerability.params = params if params is not None else u'' + couch_parent_id = document.get('parent', None) + if not couch_parent_id: + couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) + parent_id = couchdb_relational_map[couch_parent_id] + self.set_parent(vulnerability, parent_id, level) yield vulnerability - @classmethod def set_parent(self, vulnerability, parent_id, level=2): logger.debug('Set parent for vulnerabiity level {0}'.format(level)) if level == 2: @@ -243,11 +241,12 @@ def set_parent(self, vulnerability, parent_id, level=2): if level == 3: vulnerability.service = session.query(Service).filter_by(id=parent_id).first() + class CommandImporter(object): DOC_TYPE = 'CommandRunInformation' - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): start_date = datetime.datetime.fromtimestamp(document.get('itime')) end_date = start_date + datetime.timedelta(seconds=document.get('duration')) command, instance = get_or_create( @@ -266,15 +265,12 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa yield command - def set_parent(self, command, parent_id): - raise NotImplementedError - class NoteImporter(object): DOC_TYPE = 'Note' - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): note = Note() note.name = document.get('name') note.text = document.get('text', None) @@ -282,17 +278,11 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa note.owned = document.get('owned', False) yield note - def add_relationships_from_dict(self, entity, entities): - # this method is not required since update_from_document uses - # workspace name to create the relation - pass - class CredentialImporter(object): DOC_TYPE = 'Cred' - @classmethod - def update_from_document(cls, document, workspace, level=None, couchdb_relational_map=None): + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): host = None service = None if level == 2: @@ -316,22 +306,17 @@ def update_from_document(cls, document, workspace, level=None, couchdb_relationa class WorkspaceImporter(object): DOC_TYPE = 'Workspace' - @classmethod - def update_from_document(cls, document): + def update_from_document(self, document): workspace, created = get_or_create(session, server.models.Workspace, name=document.get('name', None)) yield workspace - def add_relationships_from_dict(self, entity, entities): - for couch_id, child_entity in entities.items(): - child_entity.workspace = entity - class FaradayEntityImporter(object): # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace'] - @classmethod - def parse(cls, document): + + def parse(self, document): """Get an instance of a DAO object given a document""" - importer_class = cls.get_importer_from_document(document) + importer_class = self.get_importer_from_document(document) if importer_class is not None: importer = importer_class() entity = importer.update_from_document(document) @@ -340,8 +325,7 @@ def parse(cls, document): return importer, entity return None, None - @classmethod - def get_importer_from_document(cls, doc_type): + def get_importer_from_document(self, doc_type): logger.info('Getting class importer for {0}'.format(doc_type)) importer_class_mapper = { 'EntityMetadata': EntityMetadataImporter, @@ -355,14 +339,10 @@ def get_importer_from_document(cls, doc_type): 'Vulnerability': VulnerabilityImporter, 'VulnerabilityWeb': VulnerabilityImporter, } - importer_cls = importer_class_mapper.get(doc_type, None) - if not importer_cls: + importer_self = importer_class_mapper.get(doc_type, None) + if not importer_self: raise NotImplementedError('Class importer for {0} not implemented'.format(doc_type)) - return importer_cls - - @classmethod - def update_from_document(self, document): - raise Exception('MUST IMPLEMENT') + return importer_self class ImportCouchDB(FlaskScriptCommand): @@ -447,21 +427,12 @@ def import_workspace_into_database(self, workspace_name): couchdb_removed_objs = set() removed_objs = ['Interface'] for level, obj_type in obj_types: - obj_importer = faraday_importer.get_importer_from_document(obj_type) + obj_importer = faraday_importer.get_importer_from_document(obj_type)() objs_dict = self.get_objs(couch_url, obj_type, level) for raw_obj in tqdm(objs_dict.get('rows', [])): raw_obj = raw_obj['value'] couchdb_id = raw_obj['_id'] for new_obj in obj_importer.update_from_document(raw_obj, workspace, level, couchdb_relational_map): - if raw_obj.get('parent', None): - parent_id = raw_obj['parent'] - if parent_id in couchdb_removed_objs: - parent_id = '.'.join(parent_id.split('.')[:-1]) - obj_importer.set_parent( - new_obj, - couchdb_relational_map[parent_id], - level - ) session.commit() if obj_type not in removed_objs: couchdb_relational_map[couchdb_id] = new_obj.id From 416299eb7f2bd0b11ecd0471f87df41cff799ba5 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 30 Aug 2017 11:58:28 -0300 Subject: [PATCH 0138/1506] Fix duplicate credentials --- server/importer.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/server/importer.py b/server/importer.py index bc0d15608fe..46bf73e5be3 100644 --- a/server/importer.py +++ b/server/importer.py @@ -293,12 +293,11 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation service = session.query(Service).filter_by(id=parent_id).first() if not host and not service: raise Exception('Missing host or service for credential {0}'.format(document['_id'])) - credential, created = get_or_create(session, Credential, name=document.get('username'), host=host, service=service) - credential.username = document.get('username') - credential.password = document.get('password', '') + credential, created = get_or_create(session, Credential, username=document.get('username'), host=host, service=service) + credential.password = document.get('password', None) credential.owned = document.get('owned', False) - credential.description = document.get('description', '') - credential.name = document.get('name', '') + credential.description = document.get('description', None) + credential.name = document.get('name', None) credential.workspace = workspace yield credential From 23cfc650bf78657e200f22feafee2902f1a5a2c8 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 30 Aug 2017 12:59:59 -0300 Subject: [PATCH 0139/1506] add import verification and log diff if necessary --- server/importer.py | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/server/importer.py b/server/importer.py index 46bf73e5be3..b6f701e0a2b 100644 --- a/server/importer.py +++ b/server/importer.py @@ -1,7 +1,7 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information - +import os import sys import json import datetime @@ -10,6 +10,7 @@ from tqdm import tqdm from flask_script import Command as FlaskScriptCommand from restkit.errors import RequestError, Unauthorized +from IPy import IP import server.app import server.utils.logger @@ -61,7 +62,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation yield entity - def __truncate_to_epoch_in_seconds(self, timestamp): """ In a not so elegant fashion, identifies and truncate epoch timestamps expressed in milliseconds to seconds""" @@ -156,9 +156,8 @@ def merge_with_host(self, interface, parent_relation_db_id): class ServiceImporter(object): DOC_TYPE = 'Service' - def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - #service was always above interface, not it's above host. + # service was always above interface, not it's above host. try: parent_id = document['parent'].split('.')[0] except KeyError: @@ -189,7 +188,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class VulnerabilityImporter(object): DOC_TYPE = ['Vulnerability', 'VulnerabilityWeb'] - def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): vulnerability, created = get_or_create(session, Vulnerability, name=document.get('name'), description= document.get('desc')) vulnerability.confirmed = document.get('confirmed') @@ -245,7 +243,6 @@ def set_parent(self, vulnerability, parent_id, level=2): class CommandImporter(object): DOC_TYPE = 'CommandRunInformation' - def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): start_date = datetime.datetime.fromtimestamp(document.get('itime')) end_date = start_date + datetime.timedelta(seconds=document.get('duration')) @@ -269,7 +266,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class NoteImporter(object): DOC_TYPE = 'Note' - def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): note = Note() note.name = document.get('name') @@ -391,6 +387,35 @@ def get_objs(self, host, obj_type, level): return r.json() + def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, workspace): + all_docs_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_all_docs?include_docs=true".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace.name + ) + all_ids = map(lambda x: x['doc']['_id'], requests.get(all_docs_url).json()['rows']) + if len(all_ids) != len(couchdb_relational_map.keys()) + len(couchdb_removed_objs): + missing_objs_filename = os.path.join(os.path.expanduser('~/.faraday'), 'logs', 'import_missing_objects_{0}.json'.format(workspace.name)) + logger.warning('Not all objects were imported. Saving difference to file {0}'.format(missing_objs_filename)) + missing_ids = set(all_ids) - set(couchdb_relational_map.keys()).union(couchdb_removed_objs) + objs_diff = [] + logger.info('Downloading missing couchdb docs') + for missing_id in tqdm(missing_ids): + doc_url = 'http://{username}:{password}@{hostname}:{port}/{workspace_name}/{doc_id}'.format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace.name, + doc_id=missing_id + ) + objs_diff.append(requests.get(doc_url).json()) + + with open(missing_objs_filename, 'w') as missing_objs_file: + missing_objs_file.write(json.dumps(objs_diff)) + def import_workspace_into_database(self, workspace_name): faraday_importer = FaradayEntityImporter() @@ -420,7 +445,7 @@ def import_workspace_into_database(self, workspace_name): (3, 'Vulnerability'), (3, 'VulnerabilityWeb'), (3, 'Service'), - (4, 'Credential'), # Level 4 is for interface + (4, 'Credential'), # Level 4 is for interface ] couchdb_relational_map = {} couchdb_removed_objs = set() @@ -437,5 +462,5 @@ def import_workspace_into_database(self, workspace_name): couchdb_relational_map[couchdb_id] = new_obj.id else: couchdb_removed_objs.add(couchdb_id) - + self.verify_import_data(couchdb_relational_map, couchdb_removed_objs, workspace) return created From 356a69cf2144ff800348d346410a8093babcf01c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 30 Aug 2017 15:21:46 -0300 Subject: [PATCH 0140/1506] Merge user import from couchdb --- import_users_from_couch.py | 95 -------------------------------------- server/importer.py | 94 ++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 96 deletions(-) delete mode 100644 import_users_from_couch.py diff --git a/import_users_from_couch.py b/import_users_from_couch.py deleted file mode 100644 index 5939a94d20c..00000000000 --- a/import_users_from_couch.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import print_function -import argparse -import requests -from urlparse import urljoin - -from binascii import unhexlify -from passlib.utils.binary import ab64_encode -from passlib.hash import pbkdf2_sha1 - -from flask_script import Manager -from server.web import app -from server.models import db - - -COUCHDB_USER_PREFIX = 'org.couchdb.user:' -COUCHDB_PASSWORD_PREXFIX = '-pbkdf2-' - - -def modular_crypt_pbkdf2_sha1(checksum, salt, iterations=1000): - return '$pbkdf2${iterations}${salt}${checksum}'.format( - iterations=iterations, - salt=ab64_encode(salt), - checksum=ab64_encode(unhexlify(checksum)), - ) - - -def convert_couchdb_hash(original_hash): - if not original_hash.startswith(COUCHDB_PASSWORD_PREXFIX): - # Should be a plaintext password - return original_hash - checksum, salt, iterations = original_hash[ - len(COUCHDB_PASSWORD_PREXFIX):].split(',') - iterations = int(iterations) - return modular_crypt_pbkdf2_sha1(checksum, salt, iterations) - - -def get_hash_from_document(doc): - scheme = doc.get('password_scheme', 'unset') - if scheme != 'pbkdf2': - raise ValueError('Unknown password scheme: %s' % scheme) - return modular_crypt_pbkdf2_sha1(doc['derived_key'], doc['salt'], - doc['iterations']) - - -def parse_all_docs(doc): - return [row['doc'] for row in doc['rows']] - - -def import_users(admins, all_users): - - manager = Manager(app) - with app.app_context(): - - # Import admin users - for (username, password) in admins.items(): - print("Creating user", username) - app.user_datastore.create_user( - username=username, - email=username + '@test.com', - password=convert_couchdb_hash(password), - is_ldap=False - ) - - # Import non admin users - for user in all_users: - if not user['_id'].startswith(COUCHDB_USER_PREFIX): - # It can be a view or something other than a user - continue - if user['name'] in admins.keys(): - # This is an already imported admin user, skip - continue - print("Importing", user['name']) - app.user_datastore.create_user( - username=user['name'], - email=user['name'] + '@test.com', - password=get_hash_from_document(user), - is_ldap=False - ) - db.session.commit() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--couch-url', default='http://localhost:5984') - parser.add_argument('username') - parser.add_argument('password') - args = parser.parse_args() - - auth = (args.username, args.password) - admins_url = urljoin(args.couch_url, - '/_config/admins') - users_url = urljoin(args.couch_url, - '/_users/_all_docs?include_docs=true') - import_users(requests.get(admins_url, auth=auth).json(), - parse_all_docs(requests.get(users_url, auth=auth).json())) diff --git a/server/importer.py b/server/importer.py index b6f701e0a2b..39aeee9e20e 100644 --- a/server/importer.py +++ b/server/importer.py @@ -5,14 +5,17 @@ import sys import json import datetime +from binascii import unhexlify import requests from tqdm import tqdm from flask_script import Command as FlaskScriptCommand from restkit.errors import RequestError, Unauthorized from IPy import IP +from passlib.utils.binary import ab64_encode +from passlib.hash import pbkdf2_sha1 -import server.app +from server.web import app import server.utils.logger import server.couchdb import server.database @@ -31,6 +34,9 @@ Vulnerability ) +COUCHDB_USER_PREFIX = 'org.couchdb.user:' +COUCHDB_PASSWORD_PREXFIX = '-pbkdf2-' + logger = server.utils.logger.get_logger(__name__) session = db.session @@ -340,6 +346,90 @@ def get_importer_from_document(self, doc_type): return importer_self +class ImportCouchDBUsers(FlaskScriptCommand): + + def modular_crypt_pbkdf2_sha1(self, checksum, salt, iterations=1000): + return '$pbkdf2${iterations}${salt}${checksum}'.format( + iterations=iterations, + salt=ab64_encode(salt), + checksum=ab64_encode(unhexlify(checksum)), + ) + + def convert_couchdb_hash(self, original_hash): + if not original_hash.startswith(COUCHDB_PASSWORD_PREXFIX): + # Should be a plaintext password + return original_hash + checksum, salt, iterations = original_hash[ + len(COUCHDB_PASSWORD_PREXFIX):].split(',') + iterations = int(iterations) + return self.modular_crypt_pbkdf2_sha1(checksum, salt, iterations) + + def get_hash_from_document(self, doc): + scheme = doc.get('password_scheme', 'unset') + if scheme != 'pbkdf2': + raise ValueError('Unknown password scheme: %s' % scheme) + return self.modular_crypt_pbkdf2_sha1(doc['derived_key'], doc['salt'], + doc['iterations']) + + def parse_all_docs(self, doc): + return [row['doc'] for row in doc['rows']] + + def get_users_and_admins(self): + admins_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + path='_config/admins' + ) + + users_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + path='_users/_all_docs?include_docs=true' + ) + admins = requests.get(admins_url).json() + users = requests.get(users_url).json() + return users, admins + + def import_admins(self, admins): + # Import admin users + for (username, password) in admins.items(): + logger.info('Creating user {0}'.format(username)) + app.user_datastore.create_user( + username=username, + email=username + '@test.com', + password=self.convert_couchdb_hash(password), + is_ldap=False + ) + + def import_users(self, all_users, admins): + # Import non admin users + for user in all_users['rows']: + user = user['doc'] + if not user['_id'].startswith(COUCHDB_USER_PREFIX): + # It can be a view or something other than a user + continue + if user['name'] in admins.keys(): + # This is an already imported admin user, skip + continue + logger.info('Importing {0}'.format(user['name'])) + app.user_datastore.create_user( + username=user['name'], + email=user['name'] + '@test.com', + password=self.get_hash_from_document(user), + is_ldap=False + ) + + def run(self): + all_users, admins = self.get_users_and_admins() + self.import_users(all_users, admins) + self.import_admins(admins) + db.session.commit() + + class ImportCouchDB(FlaskScriptCommand): def _open_couchdb_conn(self): @@ -364,6 +454,8 @@ def run(self): """ Main entry point for couchdb import """ + users_import = ImportCouchDBUsers() + users_import.run() couchdb_server_conn, workspaces_list = self._open_couchdb_conn() for workspace_name in workspaces_list: From 5d404486cec9eab64fcf6b91dbc8c80f9266a2b2 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 30 Aug 2017 15:34:22 -0300 Subject: [PATCH 0141/1506] Skip user creation if already exists --- server/importer.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/server/importer.py b/server/importer.py index 39aeee9e20e..06cf495f97f 100644 --- a/server/importer.py +++ b/server/importer.py @@ -31,7 +31,8 @@ Command, Workspace, Hostname, - Vulnerability + Vulnerability, + User, ) COUCHDB_USER_PREFIX = 'org.couchdb.user:' @@ -398,12 +399,13 @@ def import_admins(self, admins): # Import admin users for (username, password) in admins.items(): logger.info('Creating user {0}'.format(username)) - app.user_datastore.create_user( - username=username, - email=username + '@test.com', - password=self.convert_couchdb_hash(password), - is_ldap=False - ) + if not db.session.query(User).filter_by(username=username).first(): + app.user_datastore.create_user( + username=username, + email=username + '@test.com', + password=self.convert_couchdb_hash(password), + is_ldap=False + ) def import_users(self, all_users, admins): # Import non admin users @@ -416,12 +418,13 @@ def import_users(self, all_users, admins): # This is an already imported admin user, skip continue logger.info('Importing {0}'.format(user['name'])) - app.user_datastore.create_user( - username=user['name'], - email=user['name'] + '@test.com', - password=self.get_hash_from_document(user), - is_ldap=False - ) + if not db.session.query(User).filter_by(username=user['name']).first(): + app.user_datastore.create_user( + username=user['name'], + email=user['name'] + '@test.com', + password=self.get_hash_from_document(user), + is_ldap=False + ) def run(self): all_users, admins = self.get_users_and_admins() From afeffd35e65105cb0f75fbcb3fa7b1810ee0a041 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 30 Aug 2017 15:51:21 -0300 Subject: [PATCH 0142/1506] Import vuln level 4 instead level 3 --- server/importer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/importer.py b/server/importer.py index 06cf495f97f..16a03364858 100644 --- a/server/importer.py +++ b/server/importer.py @@ -243,7 +243,7 @@ def set_parent(self, vulnerability, parent_id, level=2): logger.debug('Set parent for vulnerabiity level {0}'.format(level)) if level == 2: vulnerability.host = session.query(Host).filter_by(id=parent_id).first() - if level == 3: + if level == 4: vulnerability.service = session.query(Service).filter_by(id=parent_id).first() @@ -537,10 +537,10 @@ def import_workspace_into_database(self, workspace_name): (2, 'Credential'), (2, 'Vulnerability'), (2, 'VulnerabilityWeb'), - (3, 'Vulnerability'), - (3, 'VulnerabilityWeb'), (3, 'Service'), (4, 'Credential'), # Level 4 is for interface + (4, 'Vulnerability'), + (4, 'VulnerabilityWeb'), ] couchdb_relational_map = {} couchdb_removed_objs = set() From a132aa0508d8ff3a3d30c7545cdf24409f8cc936 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 31 Aug 2017 12:04:57 -0300 Subject: [PATCH 0143/1506] Add reference import --- server/importer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index 16a03364858..0c238d1aa6b 100644 --- a/server/importer.py +++ b/server/importer.py @@ -28,6 +28,7 @@ Credential, Host, Service, + Reference, Command, Workspace, Hostname, @@ -201,7 +202,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.vuln_type = document.get('type') vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') - vulnerability.refs = json.dumps(document.get('refs', [])) vulnerability.resolution = document.get('resolution') vulnerability.severity = document.get('severity') vulnerability.owned = document.get('owned', False) @@ -237,8 +237,14 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) parent_id = couchdb_relational_map[couch_parent_id] self.set_parent(vulnerability, parent_id, level) + self.add_references(document, vulnerability, workspace) yield vulnerability + def add_references(self, document, vulnerability, workspace): + for ref in document.get('refs', []): + new_ref, created = get_or_create(session, Reference, name=ref, workspace=workspace, vulnerability=vulnerability) + + def set_parent(self, vulnerability, parent_id, level=2): logger.debug('Set parent for vulnerabiity level {0}'.format(level)) if level == 2: From 08de51dbf1f8936012216c389f52bd2f9edc229c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 31 Aug 2017 15:36:52 -0300 Subject: [PATCH 0144/1506] Add Generic view, readonly mixins and readonly host API --- requirements_server.txt | 2 + server/api/base.py | 98 +++++++++++++++++++++++++++++++++++++ server/api/modules/hosts.py | 18 +++++++ 3 files changed, 118 insertions(+) create mode 100644 server/api/base.py diff --git a/requirements_server.txt b/requirements_server.txt index 4e95cbcb3e0..56279c73f37 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -12,3 +12,5 @@ Flask-Security==3.0.0 bcrypt Flask-SQLAlchemy==2.2 Flask-Script==2.0.5 +flask-classful +marshmallow diff --git a/server/api/base.py b/server/api/base.py new file mode 100644 index 00000000000..f51bae783f6 --- /dev/null +++ b/server/api/base.py @@ -0,0 +1,98 @@ +import flask +import json + +from flask_classful import FlaskView +from sqlalchemy.orm.exc import NoResultFound +from werkzeug.routing import parse_rule +from server.models import Workspace + + +def output_json(data, code, headers=None): + content_type = 'application/json' + dumped = json.dumps(data) + if headers: + headers.update({'Content-Type': content_type}) + else: + headers = {'Content-Type': content_type} + response = flask.make_response(dumped, code, headers) + return response + + +# TODO: Require @view decorator to enable custom routes +class GenericWorkspacedView(FlaskView): + """Abstract class for a view that depends on the workspace, that is + passed in the URL""" + + # Must-implement attributes + model_class = None + schema_class = None + + # Default attributes + route_prefix = '/v2//' + base_args = ['workspace_name'] # Required to prevent double usage of + representations = {'application/json': output_json} + lookup_field = 'id' + + @classmethod + def get_route_base(cls): + """Fix issue with base_args overriding + + See https://github.com/teracyhq/flask-classful/issues/50 for + more information""" + + if cls.route_base is not None: + route_base = cls.route_base + base_rule = parse_rule(route_base) + cls.base_args += [r[2] for r in base_rule] + else: + route_base = cls.default_route_base() + + return route_base.strip("/") + + def _get_schema_class(self): + assert self.schema_class is not None, "You must define schema_class" + return self.schema_class + + def _get_lookup_field(self): + return getattr(self.model_class, self.lookup_field) + + def _get_base_query(self, workspace_name): + try: + ws = Workspace.query.filter_by(name=workspace_name).one() + except NoResultFound: + flask.abort(404, "No such workspace: %s" % workspace_name) + return self.model_class.query.join(Workspace) \ + .filter(Workspace.id==ws.id) + + def _get_object(self, workspace_name, object_id): + try: + obj = self._get_base_query(workspace_name).filter( + self._get_lookup_field() == object_id).one() + except NoResultFound: + flask.abort(404, 'Object with id "%s" not found' % object_id) + return obj + + def _dump(self, obj, **kwargs): + return self._get_schema_class()(**kwargs).dump(obj) + + +class ListWorkspacedMixin(object): + """Add GET // route""" + + def index(self, workspace_name): + return self._dump(self._get_base_query(workspace_name).all(), + many=True) + + +class RetrieveWorkspacedMixin(object): + """Add GET /// route""" + + def get(self, workspace_name, object_id): + return self._dump(self._get_object(workspace_name, object_id)) + + +class ReadOnlyWorkspacedView(GenericWorkspacedView, + ListWorkspacedMixin, + RetrieveWorkspacedMixin): + """A generic view with list and retrieve endpoints""" + pass diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index ea13cf8d90c..42c9d1bbb2d 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -4,15 +4,33 @@ import flask from flask import Blueprint +from marshmallow import Schema, fields from server.utils.logger import get_logger from server.utils.web import gzipped, validate_workspace,\ get_integer_parameter, filter_request_args from server.dao.host import HostDAO +from server.api.base import ReadOnlyWorkspacedView +from server.models import Host host_api = Blueprint('host_api', __name__) +class HostSchema(Schema): + id = fields.String(required=True, dump_only=True) + ip = fields.String(required=True) + description = fields.String(required=True) + os = fields.String() + + +class HostsView(ReadOnlyWorkspacedView): + route_base = 'hosts' + model_class = Host + schema_class = HostSchema + +HostsView.register(host_api) + + @gzipped @host_api.route('/ws//hosts', methods=['GET']) def list_hosts(workspace=None): From c17624912fc3deb5b692d97efe7b6a535e569ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 1 Sep 2017 15:12:41 -0300 Subject: [PATCH 0145/1506] Fix tests and factories Fix integration with pytest_factoryboy (register fixtures in conftest), remove interface factory, don't use id field in factories, add workspacefactory abstract class --- test_cases/conftest.py | 10 ++++++++++ test_cases/factories.py | 43 +++++++++++++---------------------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 4e2737c9145..7945acfc558 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -1,10 +1,20 @@ import os import sys import pytest +from pytest_factoryboy import register sys.path.append(os.path.abspath(os.getcwd())) from server.app import create_app from server.models import db +from test_cases import factories + + + +register(factories.WorkspaceFactory) +register(factories.HostFactory) +register(factories.ServiceFactory) +register(factories.VulnerabilityFactory) +register(factories.CredentialFactory) @pytest.fixture(scope='session') diff --git a/test_cases/factories.py b/test_cases/factories.py index 195ba120cc4..e6001554460 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -3,13 +3,11 @@ FuzzyText, FuzzyChoice ) -from pytest_factoryboy import register from server.models import ( db, Host, Command, Service, - Interface, Workspace, Credential, Vulnerability, @@ -19,7 +17,8 @@ class FaradayFactory(factory.alchemy.SQLAlchemyModelFactory): - id = factory.Sequence(lambda n: n) + # id = factory.Sequence(lambda n: n) + pass class WorkspaceFactory(FaradayFactory): @@ -31,8 +30,12 @@ class Meta: sqlalchemy_session = db.session -class HostFactory(FaradayFactory): - name = FuzzyText() +class WorkspaceObjectFactory(FaradayFactory): + workspace = factory.SubFactory(WorkspaceFactory) + + +class HostFactory(WorkspaceObjectFactory): + ip = factory.Faker('ipv4') description = FuzzyText() os = FuzzyChoice(['Linux', 'Windows', 'OSX', 'Android', 'iOS']) @@ -41,7 +44,7 @@ class Meta: sqlalchemy_session = db.session -class EntityMetadataFactory(FaradayFactory): +class EntityMetadataFactory(WorkspaceObjectFactory): couchdb_id = factory.Sequence(lambda n: '{0}.1.2'.format(n)) class Meta: @@ -49,22 +52,10 @@ class Meta: sqlalchemy_session = db.session -class InterfaceFactory(FaradayFactory): - name = FuzzyText() - description = FuzzyText() - mac = FuzzyText() - host = factory.SubFactory(HostFactory) - - class Meta: - model = Interface - sqlalchemy_session = db.session - - -class ServiceFactory(FaradayFactory): +class ServiceFactory(WorkspaceObjectFactory): name = FuzzyText() description = FuzzyText() ports = FuzzyChoice(['443', '80', '22']) - interface = factory.SubFactory(InterfaceFactory) host = factory.SubFactory(HostFactory) class Meta: @@ -72,7 +63,7 @@ class Meta: sqlalchemy_session = db.session -class VulnerabilityFactory(FaradayFactory): +class VulnerabilityFactory(WorkspaceObjectFactory): name = FuzzyText() description = FuzzyText() @@ -90,7 +81,7 @@ class Meta: sqlalchemy_session = db.session -class CredentialFactory(FaradayFactory): +class CredentialFactory(WorkspaceObjectFactory): username = FuzzyText() password = FuzzyText() @@ -99,17 +90,9 @@ class Meta: sqlalchemy_session = db.session -class CommandFactory(FaradayFactory): +class CommandFactory(WorkspaceObjectFactory): command = FuzzyText() class Meta: model = Command sqlalchemy_session = db.session - - -register(WorkspaceFactory) -register(HostFactory) -register(ServiceFactory) -register(InterfaceFactory) -register(VulnerabilityFactory) -register(CredentialFactory) From d369221ed5df2298f5a9bb8b3e2a1598edb5d5a5 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 1 Sep 2017 16:23:07 -0300 Subject: [PATCH 0146/1506] Update importer to use the new model --- server/importer.py | 136 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 120 insertions(+), 16 deletions(-) diff --git a/server/importer.py b/server/importer.py index 0c238d1aa6b..53c168112cb 100644 --- a/server/importer.py +++ b/server/importer.py @@ -33,7 +33,14 @@ Workspace, Hostname, Vulnerability, + VulnerabilityWeb, User, + PolicyViolation, + Task, + TaskTemplate, + Methodology, + MethodologyTemplate, + ExecutiveReport ) COUCHDB_USER_PREFIX = 'org.couchdb.user:' @@ -197,27 +204,34 @@ class VulnerabilityImporter(object): DOC_TYPE = ['Vulnerability', 'VulnerabilityWeb'] def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - vulnerability, created = get_or_create(session, Vulnerability, name=document.get('name'), description= document.get('desc')) + vuln_class = Vulnerability + if document['type'] == 'VulnerabilityWeb': + vuln_class = VulnerabilityWeb + vulnerability, created = get_or_create( + session, + vuln_class, + name=document.get('name'), + description=document.get('desc') + ) vulnerability.confirmed = document.get('confirmed') - vulnerability.vuln_type = document.get('type') vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') vulnerability.resolution = document.get('resolution') vulnerability.severity = document.get('severity') vulnerability.owned = document.get('owned', False) - vulnerability.attachments = json.dumps(document.get('_attachments', {})) - vulnerability.policyviolations = json.dumps(document.get('policyviolations', [])) + #vulnerability.attachments = json.dumps(document.get('_attachments', {})) vulnerability.impact_accountability = document.get('impact', {}).get('accountability') vulnerability.impact_availability = document.get('impact', {}).get('availability') vulnerability.impact_confidentiality = document.get('impact', {}).get('confidentiality') vulnerability.impact_integrity = document.get('impact', {}).get('integrity') - vulnerability.method = document.get('method') - vulnerability.path = document.get('path') - vulnerability.pname = document.get('pname') - vulnerability.query = document.get('query') - vulnerability.request = document.get('request') - vulnerability.response = document.get('response') - vulnerability.website = document.get('website') + if document['type'] == 'VulnerabilityWeb': + vulnerability.method = document.get('method') + vulnerability.path = document.get('path') + vulnerability.pname = document.get('pname') + vulnerability.query = document.get('query') + vulnerability.request = document.get('request') + vulnerability.response = document.get('response') + vulnerability.website = document.get('website') status_map = { 'opened': 'open', 'closed': 'closed', @@ -238,19 +252,35 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation parent_id = couchdb_relational_map[couch_parent_id] self.set_parent(vulnerability, parent_id, level) self.add_references(document, vulnerability, workspace) + self.add_policy_violations(document, vulnerability, workspace) yield vulnerability + def add_policy_violations(self, document, vulnerability, workspace): + for policy_violation in document.get('policyviolations', []): + get_or_create( + session, + PolicyViolation, + name=policy_violation, + workspace=workspace, + vulnerability=vulnerability + ) + def add_references(self, document, vulnerability, workspace): for ref in document.get('refs', []): - new_ref, created = get_or_create(session, Reference, name=ref, workspace=workspace, vulnerability=vulnerability) - + get_or_create( + session, + Reference, + name=ref, + workspace=workspace, + vulnerability=vulnerability + ) def set_parent(self, vulnerability, parent_id, level=2): logger.debug('Set parent for vulnerabiity level {0}'.format(level)) if level == 2: - vulnerability.host = session.query(Host).filter_by(id=parent_id).first() + vulnerability.host_id = session.query(Host).filter_by(id=parent_id).first().id if level == 4: - vulnerability.service = session.query(Service).filter_by(id=parent_id).first() + vulnerability.service_id = session.query(Service).filter_by(id=parent_id).first().id class CommandImporter(object): @@ -314,11 +344,77 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class WorkspaceImporter(object): DOC_TYPE = 'Workspace' - def update_from_document(self, document): + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): workspace, created = get_or_create(session, server.models.Workspace, name=document.get('name', None)) + workspace.description = document.get('description') + workspace.start_date = datetime.datetime.fromtimestamp(document.get('duration')['start']/1000) + workspace.end_date = datetime.datetime.fromtimestamp(document.get('duration')['end']/1000) + workspace.scope = document.get('scope') yield workspace +class MethodologyImporter(object): + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): + if document.get('group_type') == 'template': + methodology, created = get_or_create(session, MethodologyTemplate, name=document.get('name')) + + yield methodology + + if document.get('group_type') == 'instance': + methodology, created = get_or_create(session, Methodology, name=document.get('name')) + methodology.workspace = workspace + yield methodology +# methodology. + + +class TaskImporter(object): + + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): + methodology_id = couchdb_relational_map[document.get('group_id')] + methodology = session.query(Methodology).filter_by(id=methodology_id).first() + task_class = Task + if not methodology: + methodology = session.query(MethodologyTemplate).filter_by(id=methodology_id).first() + task_class = TaskTemplate + task, created = get_or_create(session, task_class, name=document.get('name')) + if task_class == TaskTemplate: + task.template = methodology + else: + task.methodology = methodology + task.description = document.get('description') + task.assigned_to = session.query(User).filter_by(username=document.get('username')).first() + mapped_status = { + 'New': 'new', + 'In Progress': 'in progress', + 'Review': 'review', + 'Completed': 'completed' + } + task.status = mapped_status[document.get('status')] + #tags + #task.due_date = datetime.datetime.fromtimestamp(document.get('due_date')) + yield task + + +class ReportsImporter(object): + + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): + report, created = get_or_create(session, ExecutiveReport, name=document.get('name')) + report.template_name = document.get('name') + report.title = document.get('title') + report.status = document.get('status') + # TODO: add tags + report.conclusions = document.get('conclusions') + report.summary = document.get('summary') + report.recommendations = document.get('recommendations') + report.enterprise = document.get('enterprise') + report.summary = document.get('summary') + report.scope = document.get('scope') + report.objectives = document.get('objectives') + report.grouped = document.get('grouped') + report.workspace = workspace + yield report + + class FaradayEntityImporter(object): # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace'] @@ -346,6 +442,9 @@ def get_importer_from_document(self, doc_type): 'Workspace': WorkspaceImporter, 'Vulnerability': VulnerabilityImporter, 'VulnerabilityWeb': VulnerabilityImporter, + 'TaskGroup': MethodologyImporter, + 'Task': TaskImporter, + 'Reports': ReportsImporter, } importer_self = importer_class_mapper.get(doc_type, None) if not importer_self: @@ -538,6 +637,10 @@ def import_workspace_into_database(self, workspace_name): (1, 'EntityMetadata'), (1, 'Note'), (1, 'CommandRunInformation'), + (1, 'TaskGroup'), + (1, 'Task'), + (1, 'Workspace'), + (1, 'Reports'), (2, 'Interface'), (2, 'Service'), (2, 'Credential'), @@ -557,6 +660,7 @@ def import_workspace_into_database(self, workspace_name): for raw_obj in tqdm(objs_dict.get('rows', [])): raw_obj = raw_obj['value'] couchdb_id = raw_obj['_id'] + for new_obj in obj_importer.update_from_document(raw_obj, workspace, level, couchdb_relational_map): session.commit() if obj_type not in removed_objs: From ef6c52a190903e5f488ca07c36d4f6c40909cd9c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 1 Sep 2017 16:31:40 -0300 Subject: [PATCH 0147/1506] If report status was error don't save grouped. Add check when timestamps were empty --- server/importer.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/importer.py b/server/importer.py index 53c168112cb..55072637d33 100644 --- a/server/importer.py +++ b/server/importer.py @@ -347,8 +347,10 @@ class WorkspaceImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): workspace, created = get_or_create(session, server.models.Workspace, name=document.get('name', None)) workspace.description = document.get('description') - workspace.start_date = datetime.datetime.fromtimestamp(document.get('duration')['start']/1000) - workspace.end_date = datetime.datetime.fromtimestamp(document.get('duration')['end']/1000) + if document.get('duration')['start']: + workspace.start_date = datetime.datetime.fromtimestamp(float(document.get('duration')['start'])/1000) + if document.get('duration')['end']: + workspace.end_date = datetime.datetime.fromtimestamp(float(document.get('duration')['end'])/1000) workspace.scope = document.get('scope') yield workspace @@ -410,7 +412,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation report.summary = document.get('summary') report.scope = document.get('scope') report.objectives = document.get('objectives') - report.grouped = document.get('grouped') + if report.status != 'error': + report.grouped = document.get('grouped') report.workspace = workspace yield report From f5a1b72277de812e4cf747959bf3a6f96a466121 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 1 Sep 2017 16:43:34 -0300 Subject: [PATCH 0148/1506] Filter some couchdb documents --- server/importer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index 55072637d33..d3ffac5be9c 100644 --- a/server/importer.py +++ b/server/importer.py @@ -601,7 +601,6 @@ def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, works all_ids = map(lambda x: x['doc']['_id'], requests.get(all_docs_url).json()['rows']) if len(all_ids) != len(couchdb_relational_map.keys()) + len(couchdb_removed_objs): missing_objs_filename = os.path.join(os.path.expanduser('~/.faraday'), 'logs', 'import_missing_objects_{0}.json'.format(workspace.name)) - logger.warning('Not all objects were imported. Saving difference to file {0}'.format(missing_objs_filename)) missing_ids = set(all_ids) - set(couchdb_relational_map.keys()).union(couchdb_removed_objs) objs_diff = [] logger.info('Downloading missing couchdb docs') @@ -614,7 +613,13 @@ def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, works workspace_name=workspace.name, doc_id=missing_id ) - objs_diff.append(requests.get(doc_url).json()) + not_imported_obj = requests.get(doc_url).json() + filter_keys = ['views', 'validate_doc_update'] + if not any(map(lambda x: x not in filter_keys,not_imported_obj.keys())): + # we filter custom views, validation funcs, etc + logger.warning( + 'Not all objects were imported. Saving difference to file {0}'.format(missing_objs_filename)) + objs_diff.append() with open(missing_objs_filename, 'w') as missing_objs_file: missing_objs_file.write(json.dumps(objs_diff)) From d89354622ce426993bd0ec7dab256566a71850a5 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 1 Sep 2017 18:42:38 -0300 Subject: [PATCH 0149/1506] Minor fixes to support import old couchdb documents --- server/importer.py | 28 ++++++++++++++++------------ server/models.py | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/server/importer.py b/server/importer.py index d3ffac5be9c..a00f3c85dd1 100644 --- a/server/importer.py +++ b/server/importer.py @@ -191,7 +191,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation status_mapper = { 'open': 'open', 'closed': 'closed', - 'filtered': 'filtered' + 'filtered': 'filtered', + 'open|filtered': 'filtered' } service.status = status_mapper[document.get('status')] service.version = document.get('version') @@ -213,7 +214,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation name=document.get('name'), description=document.get('desc') ) - vulnerability.confirmed = document.get('confirmed') + vulnerability.confirmed = document.get('confirmed', False) vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') vulnerability.resolution = document.get('resolution') @@ -288,14 +289,16 @@ class CommandImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): start_date = datetime.datetime.fromtimestamp(document.get('itime')) - end_date = start_date + datetime.timedelta(seconds=document.get('duration')) + command, instance = get_or_create( session, Command, command=document.get('command', None), start_date=start_date, - end_date=end_date ) + if document.get('duration'): + command.end_date = start_date + datetime.timedelta(seconds=document.get('duration')) + command.command = document.get('command', None) command.ip = document.get('ip', None) command.hostname = document.get('hostname', None) @@ -401,7 +404,7 @@ class ReportsImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): report, created = get_or_create(session, ExecutiveReport, name=document.get('name')) - report.template_name = document.get('name') + report.template_name = document.get('template_name', 'generic_default.docx') report.title = document.get('title') report.status = document.get('status') # TODO: add tags @@ -412,8 +415,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation report.summary = document.get('summary') report.scope = document.get('scope') report.objectives = document.get('objectives') - if report.status != 'error': - report.grouped = document.get('grouped') + report.grouped = document.get('grouped', False) report.workspace = workspace yield report @@ -476,7 +478,9 @@ def convert_couchdb_hash(self, original_hash): def get_hash_from_document(self, doc): scheme = doc.get('password_scheme', 'unset') if scheme != 'pbkdf2': - raise ValueError('Unknown password scheme: %s' % scheme) + # Flask Security will encrypt the password next time the user logs in. + logger.warning('Found user {0} without password.'.format(doc.get('name'))) + return 'changeme' return self.modular_crypt_pbkdf2_sha1(doc['derived_key'], doc['salt'], doc['iterations']) @@ -615,14 +619,14 @@ def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, works ) not_imported_obj = requests.get(doc_url).json() filter_keys = ['views', 'validate_doc_update'] - if not any(map(lambda x: x not in filter_keys,not_imported_obj.keys())): + if not any(map(lambda x: x not in filter_keys, not_imported_obj.keys())): # we filter custom views, validation funcs, etc logger.warning( 'Not all objects were imported. Saving difference to file {0}'.format(missing_objs_filename)) - objs_diff.append() + objs_diff.append(not_imported_obj) - with open(missing_objs_filename, 'w') as missing_objs_file: - missing_objs_file.write(json.dumps(objs_diff)) + with open(missing_objs_filename, 'w') as missing_objs_file: + missing_objs_file.write(json.dumps(objs_diff)) def import_workspace_into_database(self, workspace_name): diff --git a/server/models.py b/server/models.py index 82745c148fa..4d9f231fdb1 100644 --- a/server/models.py +++ b/server/models.py @@ -410,7 +410,7 @@ class Command(db.Model): id = Column(Integer, primary_key=True) command = Column(Text(), nullable=False) start_date = Column(DateTime, nullable=False) - end_date = Column(DateTime, nullable=False) + end_date = Column(DateTime, nullable=True) ip = Column(String(250), nullable=False) # where the command was executed hostname = Column(String(250), nullable=False) # where the command was executed params = Column(Text(), nullable=True) From 53b3705e9ac73a4c84998bee4db6fa5c5594704d Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 4 Sep 2017 13:48:25 -0300 Subject: [PATCH 0150/1506] Import fixes to support old dbs --- server/importer.py | 19 +++++++++++++------ server/models.py | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/server/importer.py b/server/importer.py index a00f3c85dd1..88284f52d52 100644 --- a/server/importer.py +++ b/server/importer.py @@ -214,7 +214,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation name=document.get('name'), description=document.get('desc') ) - vulnerability.confirmed = document.get('confirmed', False) + vulnerability.confirmed = document.get('confirmed', False) or False vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') vulnerability.resolution = document.get('resolution') @@ -350,9 +350,9 @@ class WorkspaceImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): workspace, created = get_or_create(session, server.models.Workspace, name=document.get('name', None)) workspace.description = document.get('description') - if document.get('duration')['start']: + if document.get('duration') and document.get('duration')['start']: workspace.start_date = datetime.datetime.fromtimestamp(float(document.get('duration')['start'])/1000) - if document.get('duration')['end']: + if document.get('duration') and document.get('duration')['end']: workspace.end_date = datetime.datetime.fromtimestamp(float(document.get('duration')['end'])/1000) workspace.scope = document.get('scope') yield workspace @@ -362,7 +362,7 @@ class MethodologyImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): if document.get('group_type') == 'template': methodology, created = get_or_create(session, MethodologyTemplate, name=document.get('name')) - + methodology.workspace = workspace yield methodology if document.get('group_type') == 'instance': @@ -375,7 +375,11 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class TaskImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - methodology_id = couchdb_relational_map[document.get('group_id')] + try: + methodology_id = couchdb_relational_map[document.get('group_id')] + except KeyError: + logger.warn('Could not found methodology with id {0}'.format(document.get('group_id'))) + return [] methodology = session.query(Methodology).filter_by(id=methodology_id).first() task_class = Task if not methodology: @@ -395,9 +399,10 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation 'Completed': 'completed' } task.status = mapped_status[document.get('status')] + task.workspace = workspace #tags #task.due_date = datetime.datetime.fromtimestamp(document.get('due_date')) - yield task + return [task] class ReportsImporter(object): @@ -674,6 +679,8 @@ def import_workspace_into_database(self, workspace_name): couchdb_id = raw_obj['_id'] for new_obj in obj_importer.update_from_document(raw_obj, workspace, level, couchdb_relational_map): + if not new_obj: + continue session.commit() if obj_type not in removed_objs: couchdb_relational_map[couchdb_id] = new_obj.id diff --git a/server/models.py b/server/models.py index 4d9f231fdb1..a5173c7e5a3 100644 --- a/server/models.py +++ b/server/models.py @@ -616,7 +616,7 @@ class Task(TaskABC): ) workspace = relationship('Workspace', backref='tasks') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) class License(db.Model): From fe51a9d68c80d8bfa61d32b3903943b648c5964f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 4 Sep 2017 14:24:05 -0300 Subject: [PATCH 0151/1506] Fix ugly pytest stuff breaking the tests --- server/api/base.py | 7 ++++-- test_cases/conftest.py | 35 +++++++++++++++++++----------- test_cases/test_server_api.py | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 test_cases/test_server_api.py diff --git a/server/api/base.py b/server/api/base.py index f51bae783f6..d8bd2aa41fe 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -56,13 +56,16 @@ def _get_schema_class(self): def _get_lookup_field(self): return getattr(self.model_class, self.lookup_field) - def _get_base_query(self, workspace_name): + def _get_workspace(self, workspace_name): try: ws = Workspace.query.filter_by(name=workspace_name).one() except NoResultFound: flask.abort(404, "No such workspace: %s" % workspace_name) + return ws + + def _get_base_query(self, workspace_name): return self.model_class.query.join(Workspace) \ - .filter(Workspace.id==ws.id) + .filter(Workspace.id==self._get_workspace(workspace_name).id) def _get_object(self, workspace_name, object_id): try: diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 7945acfc558..6834d4ae747 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -10,11 +10,15 @@ -register(factories.WorkspaceFactory) -register(factories.HostFactory) -register(factories.ServiceFactory) -register(factories.VulnerabilityFactory) -register(factories.CredentialFactory) +enabled_factories = [ + factories.WorkspaceFactory, + factories.HostFactory, + factories.ServiceFactory, + factories.VulnerabilityFactory, + factories.CredentialFactory, +] +for factory in enabled_factories: + register(factory) @pytest.fixture(scope='session') @@ -50,14 +54,20 @@ def teardown(): @pytest.fixture(scope='function') def session(database, request): - connection = db.engine.connect() + connection = database.engine.connect() transaction = connection.begin() options = {"bind": connection, 'binds': {}} session = db.create_scoped_session(options=options) + database.session = session db.session = session + for factory in enabled_factories: + factory._meta.sqlalchemy_session = session + + + def teardown(): transaction.rollback() connection.close() @@ -71,24 +81,25 @@ def test_client(app): return app.test_client() -def create_user(app, username, email, password, **kwargs): +def create_user(app, session, username, email, password, **kwargs): user = app.user_datastore.create_user(username=username, email=email, password=password, **kwargs) - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() return user @pytest.fixture -def user(app, session): - return create_user(app, 'test', 'user@test.com', 'password', is_ldap=False) +def user(app, database, session): + # print 'user', id(session), session + return create_user(app, session, 'test', 'user@test.com', 'password', is_ldap=False) @pytest.fixture def ldap_user(app, session): - return create_user(app, 'ldap', 'ldap@test.com', 'password', is_ldap=True) + return create_user(app, session, 'ldap', 'ldap@test.com', 'password', is_ldap=True) def login_as(test_client, user): diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py new file mode 100644 index 00000000000..14fed270395 --- /dev/null +++ b/test_cases/test_server_api.py @@ -0,0 +1,40 @@ +import pytest +from json import loads as decode_json +from test_cases import factories +from server.models import db, Workspace + +PREFIX = '/v2/' +HOSTS_COUNT = 5 + +@pytest.mark.usefixtures('database', 'logged_user') +class TestHostAPI: + + @pytest.fixture(autouse=True) + def load_workspace_with_hosts(self, request, database, session, workspace, host_factory): + host_factory.create_batch(HOSTS_COUNT, workspace=workspace) + database.session.commit() + assert workspace.id is not None + assert workspace.hosts[0].id is not None + self.workspace = workspace + return workspace + + def url(self, host=None, workspace=None): + workspace = workspace or self.workspace + url = PREFIX + workspace.name + '/hosts/' + if host is not None: + url += str(host.id) + return url + + def test_list_retrieves_all_items(self, test_client): + res = test_client.get(self.url()) + assert res.status_code == 200 + assert len(decode_json(res.data)) == HOSTS_COUNT + + def test_retrieve_one_host(self, test_client, database): + # self.workspace = Workspace.query.first() + host = self.workspace.hosts[0] + assert host.id is not None + res = test_client.get(self.url(host)) + assert res.status_code == 200 + assert decode_json(res.data)['ip'] == host.ip + From ddadf91f6940dd1e329a1aaba65a14fd2f42829d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 4 Sep 2017 14:58:14 -0300 Subject: [PATCH 0152/1506] Add custom flask client test class To easily use JSON requests. Also add test for retrieving a host from other workspace --- test_cases/conftest.py | 13 +++++++++++++ test_cases/test_server_api.py | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 6834d4ae747..4decffbf2e8 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -1,6 +1,8 @@ import os import sys +import json import pytest +from flask.testing import FlaskClient from pytest_factoryboy import register sys.path.append(os.path.abspath(os.getcwd())) @@ -21,11 +23,22 @@ register(factory) +class CustomClient(FlaskClient): + def open(self, *args, **kwargs): + ret = super(CustomClient, self).open(*args, **kwargs) + try: + ret.json = json.loads(ret.data) + except ValueError: + ret.json = None + return ret + + @pytest.fixture(scope='session') def app(request): # we use sqlite memory for tests test_conn_string = 'sqlite://' app = create_app(db_connection_string=test_conn_string, testing=True) + app.test_client_class = CustomClient # Establish an application context before running the tests. ctx = app.app_context() diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 14fed270395..86cd14622a5 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -1,5 +1,4 @@ import pytest -from json import loads as decode_json from test_cases import factories from server.models import db, Workspace @@ -10,7 +9,7 @@ class TestHostAPI: @pytest.fixture(autouse=True) - def load_workspace_with_hosts(self, request, database, session, workspace, host_factory): + def load_workspace_with_hosts(self, database, session, workspace, host_factory): host_factory.create_batch(HOSTS_COUNT, workspace=workspace) database.session.commit() assert workspace.id is not None @@ -28,7 +27,7 @@ def url(self, host=None, workspace=None): def test_list_retrieves_all_items(self, test_client): res = test_client.get(self.url()) assert res.status_code == 200 - assert len(decode_json(res.data)) == HOSTS_COUNT + assert len(res.json) == HOSTS_COUNT def test_retrieve_one_host(self, test_client, database): # self.workspace = Workspace.query.first() @@ -36,5 +35,14 @@ def test_retrieve_one_host(self, test_client, database): assert host.id is not None res = test_client.get(self.url(host)) assert res.status_code == 200 - assert decode_json(res.data)['ip'] == host.ip + assert res.json['ip'] == host.ip + + def test_retrieve_fails_with_host_of_another_workspace(self, + test_client, + session, + workspace_factory): + new = workspace_factory.create() + session.commit() + res = test_client.get(self.url(self.workspace.hosts[0], new)) + assert res.status_code == 404 From f9ed41c052d44015c2bb80b9dbcb9d202ee49d8a Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 4 Sep 2017 15:44:25 -0300 Subject: [PATCH 0153/1506] Add vulnerability template importer and some fixes and add nullalbe=False in some relations. minor fixes on importer to proper data import. --- server/commands/faraday_schema_display.py | 4 ++ server/importer.py | 80 ++++++++++++++++++----- server/models.py | 23 ++++--- 3 files changed, 82 insertions(+), 25 deletions(-) diff --git a/server/commands/faraday_schema_display.py b/server/commands/faraday_schema_display.py index 0b134acf51f..5bebf7ef529 100644 --- a/server/commands/faraday_schema_display.py +++ b/server/commands/faraday_schema_display.py @@ -19,6 +19,10 @@ def run(self): self._draw_entity_diagrama() self._draw_uml_class_diagram() + @property + def description(self): + return 'Generates an entity diagram and uml class diagram from the implemented model' + def _draw_entity_diagrama(self): # create the pydot graph object by autoloading all tables via a bound metadata object graph = create_schema_graph( diff --git a/server/importer.py b/server/importer.py index 88284f52d52..6852170c932 100644 --- a/server/importer.py +++ b/server/importer.py @@ -9,6 +9,7 @@ import requests from tqdm import tqdm +from IPy import IP from flask_script import Command as FlaskScriptCommand from restkit.errors import RequestError, Unauthorized from IPy import IP @@ -40,7 +41,9 @@ TaskTemplate, Methodology, MethodologyTemplate, - ExecutiveReport + ExecutiveReport, + VulnerabilityTemplate, + ReferenceTemplate, ) COUCHDB_USER_PREFIX = 'org.couchdb.user:' @@ -92,7 +95,12 @@ class HostImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): # Ticket #3387: if the 'os' field is None, we default to 'unknown' - host, created = get_or_create(session, Host, ip=document.get('name')) + try: + IP(document.get('name')) # this will raise ValueError on valid IPs + host, created = get_or_create(session, Host, ip=document.get('name')) + except ValueError: + host, created = get_or_create(session, Host, ip=document.get('ip')) + if not document.get('os'): document['os'] = 'unknown' @@ -101,8 +109,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation host.name = document.get('name') host.description = document.get('description') host.os = document.get('os') - host.default_gateway_ip = default_gateway and default_gateway[0] or '' - host.default_gateway_mac = default_gateway and default_gateway[1] or '' + host.default_gateway_ip = default_gateway and default_gateway[0] + host.default_gateway_mac = default_gateway and default_gateway[1] host.owned = document.get('owned', False) host.workspace = workspace yield host @@ -118,7 +126,7 @@ class InterfaceImporter(object): DOC_TYPE = 'Interface' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - + # interface dict is used to merge interface with host and connect it the services to the host interface = {} interface['name'] = document.get('name') interface['description'] = document.get('description') @@ -143,7 +151,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation if not couch_parent_id: couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) parent_id = couchdb_relational_map[couch_parent_id] - self.merge_with_host(interface, parent_id) + interface['parent_id'] = parent_id + host = self.merge_with_host(interface, parent_id) yield interface def merge_with_host(self, interface, parent_relation_db_id): @@ -161,11 +170,12 @@ def merge_with_host(self, interface, parent_relation_db_id): if interface['network_segment']: host.net_segment = interface['network_segment'] if interface['description']: - host.description += '\n {0}'.format(interface['description']) + host.description += '\n Interface data: {0}'.format(interface['description']) for hostname in interface['hostnames']: hostname, created = get_or_create(session, Hostname, name=hostname, host=host) host.owned = host.owned or interface['owned'] + return host class ServiceImporter(object): @@ -180,10 +190,14 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation parent_id = document['_id'].split('.')[0] host, created = get_or_create(session, Host, id=couchdb_relational_map[parent_id]) for port in document.get('ports'): - service, created = get_or_create(session, Service, name=document.get('name'), port=port) - service.name = document.get('name') + service, created = get_or_create(session, + Service, + name=document.get('name'), + port=port, + host=host) service.description = document.get('description') service.owned = document.get('owned', False) + service.banner = document.get('banner') service.protocol = document.get('protocol') if not document.get('status'): logger.warning('Service {0} with empty status. Using open as status'.format(document['_id'])) @@ -233,6 +247,11 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.request = document.get('request') vulnerability.response = document.get('response') vulnerability.website = document.get('website') + params = document.get('params', u'') + if isinstance(params, (list, tuple)): + vulnerability.parameters = (u' '.join(params)).strip() + else: + vulnerability.parameters = params if params is not None else u'' status_map = { 'opened': 'open', 'closed': 'closed', @@ -241,11 +260,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.status = status vulnerability.workspace = workspace - params = document.get('params', u'') - if isinstance(params, (list, tuple)): - vulnerability.params = (u' '.join(params)).strip() - else: - vulnerability.params = params if params is not None else u'' + couch_parent_id = document.get('parent', None) if not couch_parent_id: @@ -390,6 +405,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation task.template = methodology else: task.methodology = methodology + task.workspace = workspace task.description = document.get('description') task.assigned_to = session.query(User).filter_by(username=document.get('username')).first() mapped_status = { @@ -399,7 +415,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation 'Completed': 'completed' } task.status = mapped_status[document.get('status')] - task.workspace = workspace #tags #task.due_date = datetime.datetime.fromtimestamp(document.get('due_date')) return [task] @@ -550,6 +565,38 @@ def run(self): db.session.commit() + +class ImportVulnerabilityTemplates(FlaskScriptCommand): + + def run(self): + cwe_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + path='cwe/_all_docs?include_docs=true' + ) + cwes = requests.get(cwe_url).json()['rows'] + for cwe in cwes: + document = cwe['doc'] + vuln_template, created = get_or_create(session, + VulnerabilityTemplate, + name=document.get('name'), + severity=document.get('exploitation'), + description=document.get('description')) + vuln_template.resolution = document.get('resolution') + for ref_doc in document['references']: + get_or_create(session, + ReferenceTemplate, + vulnerability=vuln_template, + name=ref_doc) + + + + + + + class ImportCouchDB(FlaskScriptCommand): def _open_couchdb_conn(self): @@ -574,6 +621,8 @@ def run(self): """ Main entry point for couchdb import """ + vuln_templates_import = ImportVulnerabilityTemplates() + vuln_templates_import.run() users_import = ImportCouchDBUsers() users_import.run() couchdb_server_conn, workspaces_list = self._open_couchdb_conn() @@ -685,6 +734,7 @@ def import_workspace_into_database(self, workspace_name): if obj_type not in removed_objs: couchdb_relational_map[couchdb_id] = new_obj.id else: + couchdb_relational_map[couchdb_id] = new_obj['parent_id'] couchdb_removed_objs.add(couchdb_id) self.verify_import_data(couchdb_relational_map, couchdb_removed_objs, workspace) return created diff --git a/server/models.py b/server/models.py index a5173c7e5a3..a46f3983287 100644 --- a/server/models.py +++ b/server/models.py @@ -62,7 +62,7 @@ class SourceCode(db.Model): id = Column(Integer, primary_key=True) filename = Column(Text, nullable=False) - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', backref='source_codes') @@ -91,7 +91,7 @@ class Host(db.Model): foreign_keys=[entity_metadata_id] ) - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship( 'Workspace', backref='hosts', @@ -138,10 +138,10 @@ class Service(db.Model): foreign_keys=[entity_metadata_id] ) - host_id = Column(Integer, ForeignKey('host.id'), index=True) + host_id = Column(Integer, ForeignKey('host.id'), index=True, nullable=False) host = relationship('Host', backref='services', foreign_keys=[host_id]) - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship( 'Workspace', backref='services', @@ -203,6 +203,7 @@ class VulnerabilityGeneric(VulnerabilityABC): Integer, ForeignKey('workspace.id'), index=True, + nullable = False ) workspace = relationship('Workspace', backref='vulnerabilities') @@ -303,7 +304,8 @@ class Reference(db.Model): workspace_id = Column( Integer, ForeignKey('workspace.id'), - index=True + index=True, + nullable=False ) workspace = relationship( 'Workspace', @@ -348,7 +350,8 @@ class PolicyViolation(db.Model): workspace_id = Column( Integer, ForeignKey('workspace.id'), - index=True + index=True, + nullable=False ) workspace = relationship( 'Workspace', @@ -416,7 +419,7 @@ class Command(db.Model): params = Column(Text(), nullable=True) user = Column(String(250), nullable=True) # os username where the command was executed - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', foreign_keys=[workspace_id]) # TODO: add Tool relationship and report_attachment @@ -547,7 +550,7 @@ class Methodology(db.Model): ) workspace = relationship('Workspace', backref='methodologies') - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) class TaskABC(db.Model): @@ -664,7 +667,7 @@ class Comment(db.Model): object_id = Column(Integer, nullable=False) object_type = Column(Text, nullable=False) - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', foreign_keys=[workspace_id]) class ExecutiveReport(db.Model): @@ -689,5 +692,5 @@ class ExecutiveReport(db.Model): summary = Column(Text, nullable=True) title = Column(Text, nullable=True) - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', foreign_keys=[workspace_id]) From 6e07d30e810214337140eb48cf10465d412fe1c4 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 4 Sep 2017 17:03:30 -0300 Subject: [PATCH 0154/1506] Fix severities enum in vulnerability model --- server/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index 82d557bb69a..d31e7f2fd54 100644 --- a/server/models.py +++ b/server/models.py @@ -159,6 +159,14 @@ class VulnerabilityABC(db.Model): 'difficult', 'infeasible' ] + SEVERITIES = [ + 'critical', + 'high', + 'medium', + 'low', + 'informational', + 'unclassified', + ] __abstract__ = True id = Column(Integer, primary_key=True) @@ -168,7 +176,7 @@ class VulnerabilityABC(db.Model): ease_of_resolution = Column(Enum(*EASE_OF_RESOLUTIONS, name='vulnerability_ease_of_resolution'), nullable=True) name = Column(Text, nullable=False) resolution = Column(Text, nullable=True) - severity = Column(String(50), nullable=False) + severity = Column(Enum(*SEVERITIES, name='vulnerability_severity'), nullable=False) # TODO add evidence impact_accountability = Column(Boolean, default=False) From 2ab1f6e2c73e4803e821d6fa260ecdf367148721 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 4 Sep 2017 17:51:09 -0300 Subject: [PATCH 0155/1506] Add unique constraint --- server/importer.py | 4 ++-- server/models.py | 53 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/server/importer.py b/server/importer.py index 6852170c932..5bcf09dd882 100644 --- a/server/importer.py +++ b/server/importer.py @@ -105,8 +105,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation document['os'] = 'unknown' default_gateway = document.get('default_gateway', None) - - host.name = document.get('name') + if not host.ip: + host.ip = document.get('name') host.description = document.get('description') host.os = document.get('os') host.default_gateway_ip = default_gateway and default_gateway[0] diff --git a/server/models.py b/server/models.py index a46f3983287..35de236a492 100644 --- a/server/models.py +++ b/server/models.py @@ -57,7 +57,6 @@ class EntityMetadata(db.Model): class SourceCode(db.Model): - # TODO: add unique constraint -> filename, workspace __tablename__ = 'source_code' id = Column(Integer, primary_key=True) filename = Column(Text, nullable=False) @@ -65,9 +64,12 @@ class SourceCode(db.Model): workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', backref='source_codes') + __table_args__ = ( + UniqueConstraint(filename, workspace_id, name='uix_source_code_filename_workspace'), + ) + class Host(db.Model): - # TODO: add unique constraint -> ip, workspace __tablename__ = 'host' id = Column(Integer, primary_key=True) ip = Column(Text, nullable=False) # IP v4 or v6 @@ -97,16 +99,27 @@ class Host(db.Model): backref='hosts', foreign_keys=[workspace_id] ) + __table_args__ = ( + UniqueConstraint(ip, workspace_id, name='uix_host_ip_workspace'), + ) class Hostname(db.Model): - # TODO: add unique constraint -> name, host, workspace __tablename__ = 'hostname' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) - host_id = Column(Integer, ForeignKey('host.id'), index=True) + host_id = Column(Integer, ForeignKey('host.id'), index=True, nullable=False) host = relationship('Host', backref='hostnames') + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) + workspace = relationship( + 'Workspace', + backref='hosts', + foreign_keys=[workspace_id] + ) + __table_args__ = ( + UniqueConstraint(name, host_id, workspace_id, name='uix_hostname_host_workspace'), + ) class Service(db.Model): @@ -147,6 +160,9 @@ class Service(db.Model): backref='services', foreign_keys=[workspace_id] ) + __table_args__ = ( + UniqueConstraint(port, protocol, host_id, workspace_id, name='uix_service_port_protocol_host_workspace'), + ) class VulnerabilityABC(db.Model): @@ -180,6 +196,10 @@ class VulnerabilityABC(db.Model): class VulnerabilityTemplate(VulnerabilityABC): __tablename__ = 'vulnerability_template' + __table_args__ = ( + UniqueConstraint('name', name='uix_vulnerability_template_name'), + ) + class VulnerabilityGeneric(VulnerabilityABC): STATUSES = [ @@ -211,6 +231,19 @@ class VulnerabilityGeneric(VulnerabilityABC): 'polymorphic_on': type } + __table_args__ = ( + UniqueConstraint('name', + 'severity', + 'host_id', + 'service_id', + 'method', + 'parameter_name', + 'path', + 'website', + 'workspace_id', + name='uix_vulnerability'), + ) + class Vulnerability(VulnerabilityGeneric): host_id = Column(Integer, ForeignKey(Host.id), index=True) @@ -577,6 +610,10 @@ class TaskTemplate(TaskABC): nullable=False, ) + __table_args__ = ( + UniqueConstraint(TaskABC.name, template_id, name='uix_tass_template_name_desc_template'), + ) + class Task(TaskABC): STATUSES = [ @@ -602,25 +639,29 @@ class Task(TaskABC): assigned_to = relationship('User', backref='assigned_tasks') assigned_to_id = Column(Integer, ForeignKey('user.id'), nullable=True) - methodology = relationship('Methodology', backref='tasks') methodology_id = Column( Integer, ForeignKey('methodology.id'), index=True, nullable=False, ) + methodology = relationship('Methodology', backref='tasks') - template = relationship('TaskTemplate', backref='tasks') template_id = Column( Integer, ForeignKey('task_template.id'), index=True, nullable=True, ) + template = relationship('TaskTemplate', backref='tasks') workspace = relationship('Workspace', backref='tasks') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) + __table_args__ = ( + UniqueConstraint(TaskABC.name, methodology_id, workspace_id, name='uix_task_name_desc_methodology_workspace'), + ) + class License(db.Model): __tablename__ = 'license' From 623661dddfee582fdd27a6b803693a7429e0c873 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 4 Sep 2017 17:52:04 -0300 Subject: [PATCH 0156/1506] Add License unique constraint --- server/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index e6d2244bb53..e8c83a08079 100644 --- a/server/models.py +++ b/server/models.py @@ -211,7 +211,7 @@ class VulnerabilityGeneric(VulnerabilityABC): Integer, ForeignKey('workspace.id'), index=True, - nullable = False + nullable=False, ) workspace = relationship('Workspace', backref='vulnerabilities') @@ -640,6 +640,10 @@ class License(db.Model): type = Column(Text, nullable=True) notes = Column(Text, nullable=True) + __table_args__ = ( + UniqueConstraint('product', 'start_date', 'end_date', name='uix_license'), + ) + class Tag(db.Model): __tablename__ = 'tag' From 7192a61f1a640a941cc41d1b6e55661ce0e5aa41 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 4 Sep 2017 17:54:17 -0300 Subject: [PATCH 0157/1506] Fix License constraint name --- server/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index 7692a2fff1b..95da98c7ee1 100644 --- a/server/models.py +++ b/server/models.py @@ -682,7 +682,7 @@ class License(db.Model): notes = Column(Text, nullable=True) __table_args__ = ( - UniqueConstraint('product', 'start_date', 'end_date', name='uix_license'), + UniqueConstraint('product', 'start_date', 'end_date', name='uix_license_product_start_end_dates'), ) From f9dc69fb3f781c3cc757798ee8b0ae48c79d411b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 4 Sep 2017 19:22:09 -0300 Subject: [PATCH 0158/1506] Partial commit Add POST method to API. First attempt --- requirements_server.txt | 1 + server/api/base.py | 48 ++++++++++++++++++++++++++++++++--- server/api/modules/hosts.py | 4 +-- server/models.py | 2 +- test_cases/conftest.py | 8 ++++++ test_cases/test_server_api.py | 32 ++++++++++++++++++++++- 6 files changed, 87 insertions(+), 8 deletions(-) diff --git a/requirements_server.txt b/requirements_server.txt index 56279c73f37..7c9bacffd8a 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -14,3 +14,4 @@ Flask-SQLAlchemy==2.2 Flask-Script==2.0.5 flask-classful marshmallow +webargs diff --git a/server/api/base.py b/server/api/base.py index d8bd2aa41fe..39720295e25 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -4,7 +4,8 @@ from flask_classful import FlaskView from sqlalchemy.orm.exc import NoResultFound from werkzeug.routing import parse_rule -from server.models import Workspace +from webargs.flaskparser import FlaskParser +from server.models import Workspace, db def output_json(data, code, headers=None): @@ -78,6 +79,27 @@ def _get_object(self, workspace_name, object_id): def _dump(self, obj, **kwargs): return self._get_schema_class()(**kwargs).dump(obj) + def _parse_data(self, schema, request, *args, **kwargs): + return FlaskParser().parse(schema, request, locations=('json',), + *args, **kwargs) + + @classmethod + def register(cls, app, *args, **kwargs): + """Register and add JSON error handler. Use error code + 400 instead of 422""" + super(GenericWorkspacedView, cls).register(app, *args, **kwargs) + @app.errorhandler(422) + def handle_unprocessable_entity(err): + # webargs attaches additional metadata to the `data` attribute + exc = getattr(err, 'exc') + if exc: + # Get validations from the ValidationError object + messages = exc.messages + else: + messages = ['Invalid request'] + return flask.jsonify({ + 'messages': messages, + }), 400 class ListWorkspacedMixin(object): """Add GET // route""" @@ -94,8 +116,26 @@ def get(self, workspace_name, object_id): return self._dump(self._get_object(workspace_name, object_id)) -class ReadOnlyWorkspacedView(GenericWorkspacedView, - ListWorkspacedMixin, - RetrieveWorkspacedMixin): +class ReadOnlyWorkspacedView(ListWorkspacedMixin, + RetrieveWorkspacedMixin, + GenericWorkspacedView): """A generic view with list and retrieve endpoints""" pass + + +class CreateWorkspacedMixin(object): + + def post(self, workspace_name): + data = self._parse_data(self._get_schema_class()(strict=True), + flask.request) + obj = self.model_class(**data) + obj.workspace = self._get_workspace(workspace_name) + db.session.add(obj) + db.session.commit() + return self._dump(obj).data, 201 + + +class ReadWriteWorkspacedView(CreateWorkspacedMixin, + ReadOnlyWorkspacedView, + GenericWorkspacedView): + pass diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index 42c9d1bbb2d..ff753db757e 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -10,7 +10,7 @@ from server.utils.web import gzipped, validate_workspace,\ get_integer_parameter, filter_request_args from server.dao.host import HostDAO -from server.api.base import ReadOnlyWorkspacedView +from server.api.base import ReadWriteWorkspacedView from server.models import Host host_api = Blueprint('host_api', __name__) @@ -23,7 +23,7 @@ class HostSchema(Schema): os = fields.String() -class HostsView(ReadOnlyWorkspacedView): +class HostsView(ReadWriteWorkspacedView): route_base = 'hosts' model_class = Host schema_class = HostSchema diff --git a/server/models.py b/server/models.py index d24303c7b6b..d6df2eb75d0 100644 --- a/server/models.py +++ b/server/models.py @@ -91,7 +91,7 @@ class Host(db.Model): foreign_keys=[entity_metadata_id] ) - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True) + workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship( 'Workspace', backref='hosts', diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 4decffbf2e8..57567edd662 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -24,7 +24,15 @@ class CustomClient(FlaskClient): + def open(self, *args, **kwargs): + if kwargs.pop('use_json_data', True) and 'data' in kwargs: + # JSON-encode data by default + kwargs['data'] = json.dumps(kwargs['data']) + kwargs['headers'] = kwargs.get('headers', []) + [ + ('Content-Type', 'application/json'), + ] + ret = super(CustomClient, self).open(*args, **kwargs) try: ret.json = json.loads(ret.data) diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 86cd14622a5..69e8fa3b3b1 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -1,6 +1,6 @@ import pytest from test_cases import factories -from server.models import db, Workspace +from server.models import db, Workspace, Host PREFIX = '/v2/' HOSTS_COUNT = 5 @@ -46,3 +46,33 @@ def test_retrieve_fails_with_host_of_another_workspace(self, res = test_client.get(self.url(self.workspace.hosts[0], new)) assert res.status_code == 404 + def test_create_a_host_succeeds(self, test_client): + res = test_client.post(self.url(), data={ + "ip": "127.0.0.1", + "description": "aaaaa", + # os is not required + }) + print res.data + assert res.status_code == 201 + host_id = res.json['id'] + host = Host.query.get(host_id) + assert host.ip == "127.0.0.1" + assert host.description == "aaaaa" + assert host.os is None + assert host.workspace == self.workspace + + def test_create_a_host_fails_with_missing_desc(self, test_client): + res = test_client.post(self.url(), data={ + "ip": "127.0.0.1", + }) + assert res.status_code == 400 + + def test_create_a_host_fails_with_existing_ip(self, test_client, + session, host): + session.add(host) + session.commit() + res = test_client.post(self.url(), data={ + "ip": host.ip, + "description": "aaaaa", + }) + assert res.status_code == 400 From 657cb7669f42b3e1792110b3936f83446c12f7a2 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 4 Sep 2017 19:27:11 -0300 Subject: [PATCH 0159/1506] Add check and unique constraints Adds unique to Reference, ReferenceTemplate, PolicyViolation, PolicyViolationTemplate, Credential, Vulnerability. Adds check to Vulnerability and Credential. --- server/models.py | 87 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/server/models.py b/server/models.py index 95da98c7ee1..7bdc2a4c9c2 100644 --- a/server/models.py +++ b/server/models.py @@ -3,6 +3,7 @@ # See the file 'doc/LICENSE' for the license information from sqlalchemy import ( Boolean, + CheckConstraint, Column, DateTime, Enum, @@ -123,7 +124,6 @@ class Hostname(db.Model): class Service(db.Model): - # TODO: add unique constraint to -> port, protocol, host_id, workspace STATUSES = [ 'open', 'closed', @@ -166,7 +166,6 @@ class Service(db.Model): class VulnerabilityABC(db.Model): - # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website, workspace # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien EASE_OF_RESOLUTIONS = [ 'trivial', @@ -239,19 +238,6 @@ class VulnerabilityGeneric(VulnerabilityABC): 'polymorphic_on': type } - __table_args__ = ( - UniqueConstraint('name', - 'severity', - 'host_id', - 'service_id', - 'method', - 'parameter_name', - 'path', - 'website', - 'workspace_id', - name='uix_vulnerability'), - ) - class Vulnerability(VulnerabilityGeneric): host_id = Column(Integer, ForeignKey(Host.id), index=True) @@ -336,6 +322,10 @@ class ReferenceTemplate(db.Model): foreign_keys=[vulnerability_id], ) + __table_args__ = ( + UniqueConstraint('name', 'vulnerability_id', name='uix_reference_template_name_vulnerability'), + ) + class Reference(db.Model): __tablename__ = 'reference' @@ -365,6 +355,10 @@ class Reference(db.Model): foreign_keys=[vulnerability_id], ) + __table_args__ = ( + UniqueConstraint('name', 'vulnerability_id', 'workspace_id', name='uix_reference_name_vulnerability_workspace'), + ) + class PolicyViolationTemplate(db.Model): __tablename__ = 'policy_violation_template' @@ -382,6 +376,13 @@ class PolicyViolationTemplate(db.Model): foreign_keys=[vulnerability_id] ) + __table_args__ = ( + UniqueConstraint( + 'name', + 'vulnerability_id', + name='uix_policy_violation_template_name_vulnerability'), + ) + class PolicyViolation(db.Model): __tablename__ = 'policy_violation' @@ -411,6 +412,14 @@ class PolicyViolation(db.Model): foreign_keys=[vulnerability_id] ) + __table_args__ = ( + UniqueConstraint( + 'name', + 'vulnerability_id', + 'workspace_id', + name='uix_policy_violation_template_name_vulnerability_workspace'), + ) + class Credential(db.Model): # TODO: add unique constraint -> username, host o service y workspace @@ -448,6 +457,19 @@ class Credential(db.Model): foreign_keys=[workspace_id], ) + __table_args__ = ( + CheckConstraint('(host_id IS NULL AND service_id IS NOT NULL) OR ' + '(host_id IS NOT NULL AND service_id IS NULL)', + name='check_credential_host_service'), + UniqueConstraint( + 'username', + 'host_id', + 'service_id', + 'workspace_id', + name='uix_credential_username_host_service_workspace' + ), + ) + class Command(db.Model): __tablename__ = 'command' @@ -618,9 +640,9 @@ class TaskTemplate(TaskABC): nullable=False, ) - __table_args__ = ( - UniqueConstraint(TaskABC.name, template_id, name='uix_tass_template_name_desc_template'), - ) + # __table_args__ = ( + # UniqueConstraint(template_id, name='uix_task_template_name_desc_template_delete'), + # ) class Task(TaskABC): @@ -666,9 +688,9 @@ class Task(TaskABC): workspace = relationship('Workspace', backref='tasks') workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) - __table_args__ = ( - UniqueConstraint(TaskABC.name, methodology_id, workspace_id, name='uix_task_name_desc_methodology_workspace'), - ) + # __table_args__ = ( + # UniqueConstraint(TaskABC.name, methodology_id, workspace_id, name='uix_task_name_desc_methodology_workspace'), + # ) class License(db.Model): @@ -747,3 +769,26 @@ class ExecutiveReport(db.Model): workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', foreign_keys=[workspace_id]) + + +# This constraint uses Columns from different classes +# Since it applies to the table vulnerability it should be adVulnerability.ded to the Vulnerability class +# However, since it contains columns from children classes, this cannot be done +# This is a workaround suggested by SQLAlchemy's creator +CheckConstraint('((Vulnerability.host_id IS NULL)::int+' + '(Vulnerability.service_id IS NULL)::int+' + '(Vulnerability.source_code_id IS NULL)::int)=1', + name='check_vulnerability_host_service_source_code', + table=VulnerabilityGeneric.__table__) +UniqueConstraint(VulnerabilityGeneric.name, + VulnerabilityGeneric.severity, + Vulnerability.host_id, + VulnerabilityWeb.service_id, + VulnerabilityWeb.method, + VulnerabilityWeb.parameter_name, + VulnerabilityWeb.path, + VulnerabilityWeb.website, + VulnerabilityGeneric.workspace_id, + VulnerabilityCode.source_code_id, + name='uix_vulnerability' +) \ No newline at end of file From f696e00014001a194abfacfae0069eb8f71a3a50 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 5 Sep 2017 12:43:06 -0300 Subject: [PATCH 0160/1506] Fix check constraint --- server/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/models.py b/server/models.py index 7bdc2a4c9c2..541959b3f9f 100644 --- a/server/models.py +++ b/server/models.py @@ -775,9 +775,9 @@ class ExecutiveReport(db.Model): # Since it applies to the table vulnerability it should be adVulnerability.ded to the Vulnerability class # However, since it contains columns from children classes, this cannot be done # This is a workaround suggested by SQLAlchemy's creator -CheckConstraint('((Vulnerability.host_id IS NULL)::int+' - '(Vulnerability.service_id IS NULL)::int+' - '(Vulnerability.source_code_id IS NULL)::int)=1', +CheckConstraint('((Vulnerability.host_id IS NOT NULL)::int+' + '(Vulnerability.service_id IS NOT NULL)::int+' + '(Vulnerability.source_code_id IS NOT NULL)::int)=1', name='check_vulnerability_host_service_source_code', table=VulnerabilityGeneric.__table__) UniqueConstraint(VulnerabilityGeneric.name, From 3ccd295f41946d7a29d2ff91c4c869577afc6650 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 5 Sep 2017 12:43:53 -0300 Subject: [PATCH 0161/1506] Importer fixes after new unique and check constraints --- server/importer.py | 63 +++++++++++++++++++++++++++++++++------------- server/models.py | 2 +- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/server/importer.py b/server/importer.py index 5bcf09dd882..67daaf1e1b4 100644 --- a/server/importer.py +++ b/server/importer.py @@ -152,10 +152,10 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) parent_id = couchdb_relational_map[couch_parent_id] interface['parent_id'] = parent_id - host = self.merge_with_host(interface, parent_id) + host = self.merge_with_host(interface, workspace, parent_id) yield interface - def merge_with_host(self, interface, parent_relation_db_id): + def merge_with_host(self, interface, workspace, parent_relation_db_id): host = session.query(Host).filter_by(id=parent_relation_db_id).first() assert host.workspace == interface['workspace'] if interface['mac']: @@ -173,7 +173,13 @@ def merge_with_host(self, interface, parent_relation_db_id): host.description += '\n Interface data: {0}'.format(interface['description']) for hostname in interface['hostnames']: - hostname, created = get_or_create(session, Hostname, name=hostname, host=host) + hostname, created = get_or_create( + session, + Hostname, + name=hostname, + host=host, + workspace=workspace + ) host.owned = host.owned or interface['owned'] return host @@ -189,7 +195,10 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation # some services are missing the parent key parent_id = document['_id'].split('.')[0] host, created = get_or_create(session, Host, id=couchdb_relational_map[parent_id]) - for port in document.get('ports'): + ports = document.get('ports') + if len(ports) > 2: + logger.warn('More than one port found in services!') + for port in ports: service, created = get_or_create(session, Service, name=document.get('name'), @@ -232,7 +241,15 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') vulnerability.resolution = document.get('resolution') - vulnerability.severity = document.get('severity') + mapped_severity = { + 'med': 'medium', + 'critical': 'critical', + 'high':'high', + 'low': 'low', + 'info': 'informational', + 'unclassified': 'unclassified', + } + vulnerability.severity = mapped_severity[document.get('severity')] vulnerability.owned = document.get('owned', False) #vulnerability.attachments = json.dumps(document.get('_attachments', {})) vulnerability.impact_accountability = document.get('impact', {}).get('accountability') @@ -291,7 +308,7 @@ def add_references(self, document, vulnerability, workspace): vulnerability=vulnerability ) - def set_parent(self, vulnerability, parent_id, level=2): + def set_parent(self, vulnerability, parent_id, level): logger.debug('Set parent for vulnerabiity level {0}'.format(level)) if level == 2: vulnerability.host_id = session.query(Host).filter_by(id=parent_id).first().id @@ -579,10 +596,18 @@ def run(self): cwes = requests.get(cwe_url).json()['rows'] for cwe in cwes: document = cwe['doc'] + mapped_exploitation = { + 'critical': 'critical', + 'med': 'medium', + 'high':'high', + 'low': 'low', + 'info': 'informational', + 'unclassified': 'unclassified', + } vuln_template, created = get_or_create(session, VulnerabilityTemplate, name=document.get('name'), - severity=document.get('exploitation'), + severity=mapped_exploitation[document.get('exploitation')], description=document.get('description')) vuln_template.resolution = document.get('resolution') for ref_doc in document['references']: @@ -724,17 +749,19 @@ def import_workspace_into_database(self, workspace_name): obj_importer = faraday_importer.get_importer_from_document(obj_type)() objs_dict = self.get_objs(couch_url, obj_type, level) for raw_obj in tqdm(objs_dict.get('rows', [])): - raw_obj = raw_obj['value'] - couchdb_id = raw_obj['_id'] - - for new_obj in obj_importer.update_from_document(raw_obj, workspace, level, couchdb_relational_map): - if not new_obj: - continue - session.commit() - if obj_type not in removed_objs: - couchdb_relational_map[couchdb_id] = new_obj.id - else: - couchdb_relational_map[couchdb_id] = new_obj['parent_id'] + # we use no_autoflush since some queries triggers flush and some relationship are missing in the middle + with session.no_autoflush: + raw_obj = raw_obj['value'] + couchdb_id = raw_obj['_id'] + + for new_obj in obj_importer.update_from_document(raw_obj, workspace, level, couchdb_relational_map): + if not new_obj: + continue + session.commit() + if obj_type not in removed_objs: + couchdb_relational_map[couchdb_id] = new_obj.id + else: + couchdb_relational_map[couchdb_id] = new_obj['parent_id'] couchdb_removed_objs.add(couchdb_id) self.verify_import_data(couchdb_relational_map, couchdb_removed_objs, workspace) return created diff --git a/server/models.py b/server/models.py index 541959b3f9f..87fb9f0ab7e 100644 --- a/server/models.py +++ b/server/models.py @@ -97,7 +97,7 @@ class Host(db.Model): workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship( 'Workspace', - backref='hosts', + backref='workspace_hosts', foreign_keys=[workspace_id] ) __table_args__ = ( From b2f4b2a4b69ca2cdb28709fe21182e74fbc227f1 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 5 Sep 2017 14:24:31 -0300 Subject: [PATCH 0162/1506] Add ip address check --- server/importer.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index 67daaf1e1b4..f676494813f 100644 --- a/server/importer.py +++ b/server/importer.py @@ -155,6 +155,13 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation host = self.merge_with_host(interface, workspace, parent_id) yield interface + def check_ip_address(self, ip_str): + if not ip_str or ip_str == '0.0.0.0': + return False + if not ip_str or ip_str == '0000:0000:0000:0000:0000:0000:0000:0000': + return False + return True + def merge_with_host(self, interface, workspace, parent_relation_db_id): host = session.query(Host).filter_by(id=parent_relation_db_id).first() assert host.workspace == interface['workspace'] @@ -162,9 +169,9 @@ def merge_with_host(self, interface, workspace, parent_relation_db_id): host.mac = interface['mac'] if interface['owned']: host.owned = interface['owned'] - if interface['ipv4_address'] or interface['ipv6_address']: + if self.check_ip_address(interface['ipv4_address']) or self.check_ip_address(interface['ipv6_address']): host.ip = interface['ipv4_address'] or interface['ipv6_address'] - if interface['ipv4_gateway'] or interface['ipv6_gateway']: + if self.check_ip_address(interface['ipv4_gateway']) or self.check_ip_address(interface['ipv6_gateway']): host.default_gateway_ip = interface['ipv4_gateway'] or interface['ipv6_gateway'] #host.default_gateway_mac if interface['network_segment']: From 78f305097f64954883391522c54d94f60cccdb7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 5 Sep 2017 15:39:19 -0300 Subject: [PATCH 0163/1506] Improve creation endpoint (generic and host one) Make it work, add unique logic (with fields that are unique together with workspace id) --- server/api/base.py | 33 +++++++++++++++++++++++++++++---- server/api/modules/hosts.py | 1 + server/models.py | 3 +++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 39720295e25..16e81a060da 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -4,7 +4,8 @@ from flask_classful import FlaskView from sqlalchemy.orm.exc import NoResultFound from werkzeug.routing import parse_rule -from webargs.flaskparser import FlaskParser +from webargs.flaskparser import FlaskParser, abort +from webargs.core import ValidationError from server.models import Workspace, db @@ -33,6 +34,7 @@ class GenericWorkspacedView(FlaskView): base_args = ['workspace_name'] # Required to prevent double usage of representations = {'application/json': output_json} lookup_field = 'id' + unique_fields = [] # Fields unique together with workspace_id @classmethod def get_route_base(cls): @@ -83,6 +85,19 @@ def _parse_data(self, schema, request, *args, **kwargs): return FlaskParser().parse(schema, request, locations=('json',), *args, **kwargs) + def _validate_uniqueness(self, obj): + assert obj.workspace is not None, "Object must have a " \ + "workspace attribute set to call _validate_uniqueness" + for field_name in self.unique_fields: + field = getattr(self.model_class, field_name) + value = getattr(obj, field_name) + query = self._get_base_query(obj.workspace.name).filter( + field==value) + if query.one_or_none(): + abort(422, ValidationError('Existing value for %s field: %s' % ( + field_name, value + ))) + @classmethod def register(cls, app, *args, **kwargs): """Register and add JSON error handler. Use error code @@ -129,13 +144,23 @@ def post(self, workspace_name): data = self._parse_data(self._get_schema_class()(strict=True), flask.request) obj = self.model_class(**data) - obj.workspace = self._get_workspace(workspace_name) - db.session.add(obj) + created = self._perform_create(workspace_name, obj) + return self._dump(created).data, 201 + + def _perform_create(self, workspace_name, obj): + assert not db.session.new + with db.session.no_autoflush: + # Required because _validate_uniqueness does a select. Doing this + # outside a no_autoflush block would result in a premature create. + obj.workspace = self._get_workspace(workspace_name) + self._validate_uniqueness(obj) + db.session.add(obj) db.session.commit() - return self._dump(obj).data, 201 + return obj class ReadWriteWorkspacedView(CreateWorkspacedMixin, ReadOnlyWorkspacedView, GenericWorkspacedView): + """A generic view with list, retrieve and create endpoints""" pass diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index ff753db757e..70cc144cdc8 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -27,6 +27,7 @@ class HostsView(ReadWriteWorkspacedView): route_base = 'hosts' model_class = Host schema_class = HostSchema + unique_fields = ['ip'] HostsView.register(host_api) diff --git a/server/models.py b/server/models.py index d6df2eb75d0..b1b80fa3119 100644 --- a/server/models.py +++ b/server/models.py @@ -97,6 +97,9 @@ class Host(db.Model): backref='hosts', foreign_keys=[workspace_id] ) + __table_args__ = ( + UniqueConstraint(ip, workspace_id, name='uix_host_ip_workspace'), + ) class Hostname(db.Model): From e86b0c57d0142e58bbae2d337815faccbc31ae4a Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 5 Sep 2017 17:36:54 -0300 Subject: [PATCH 0164/1506] More updates after new contraints * Use service and host to retrieve vulns * Sometimes hostnames were string/unicode and not list * Check for validity in interface ip before override host ip --- server/importer.py | 80 +++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/server/importer.py b/server/importer.py index f676494813f..cd0f5ad8e87 100644 --- a/server/importer.py +++ b/server/importer.py @@ -160,6 +160,8 @@ def check_ip_address(self, ip_str): return False if not ip_str or ip_str == '0000:0000:0000:0000:0000:0000:0000:0000': return False + if not ip_str or ip_str == '0000:0000:0000:0000:0000:0000:0000:0001': + return False return True def merge_with_host(self, interface, workspace, parent_relation_db_id): @@ -169,8 +171,16 @@ def merge_with_host(self, interface, workspace, parent_relation_db_id): host.mac = interface['mac'] if interface['owned']: host.owned = interface['owned'] - if self.check_ip_address(interface['ipv4_address']) or self.check_ip_address(interface['ipv6_address']): - host.ip = interface['ipv4_address'] or interface['ipv6_address'] + if self.check_ip_address(interface['ipv4_address']): + interface_ip = interface['ipv4_address'] + if host.ip != interface_ip: + logger.warn('Overriding host ip address {0} with new ip {1}'.format(host.ip, interface_ip)) + host.ip = interface_ip + if self.check_ip_address(interface['ipv6_address']): + interface_ip = interface['ipv6_address'] + if host.ip != interface_ip: + logger.warn('Overriding host ip address {0} with new ip {1}'.format(host.ip, interface_ip)) + host.ip = interface_ip if self.check_ip_address(interface['ipv4_gateway']) or self.check_ip_address(interface['ipv6_gateway']): host.default_gateway_ip = interface['ipv4_gateway'] or interface['ipv6_gateway'] #host.default_gateway_mac @@ -178,12 +188,16 @@ def merge_with_host(self, interface, workspace, parent_relation_db_id): host.net_segment = interface['network_segment'] if interface['description']: host.description += '\n Interface data: {0}'.format(interface['description']) - - for hostname in interface['hostnames']: + if type(interface['hostnames']) in (str, unicode): + interface['hostnames'] = [interface['hostnames']] + for hostname_str in interface['hostnames']: + if not hostname_str: + # skip empty hostnames + continue hostname, created = get_or_create( session, Hostname, - name=hostname, + name=hostname_str, host=host, workspace=workspace ) @@ -235,15 +249,37 @@ class VulnerabilityImporter(object): DOC_TYPE = ['Vulnerability', 'VulnerabilityWeb'] def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - vuln_class = Vulnerability + couch_parent_id = document.get('parent', None) + if not couch_parent_id: + couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) + parent_id = couchdb_relational_map[couch_parent_id] + if level == 2: + parent = session.query(Host).filter_by(id=parent_id).first() + if level == 4: + parent = session.query(Service).filter_by(id=parent_id).first() if document['type'] == 'VulnerabilityWeb': - vuln_class = VulnerabilityWeb - vulnerability, created = get_or_create( - session, - vuln_class, - name=document.get('name'), - description=document.get('desc') - ) + vulnerability, created = get_or_create( + session, + VulnerabilityWeb, + name=document.get('name'), + description=document.get('desc'), + service_id=parent.id, + ) + if document['type'] == 'Vulnerability': + vuln_params = { + 'name': document.get('name'), + 'description': document.get('desc') + } + if type(parent) == Host: + vuln_params.update({'host_id': parent.id}) + elif type(parent) == Service: + vuln_params.update({'service_id': parent.id}) + vulnerability, created = get_or_create( + session, + Vulnerability, + **vuln_params + ) + vulnerability.confirmed = document.get('confirmed', False) or False vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') @@ -284,13 +320,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.status = status vulnerability.workspace = workspace - - - couch_parent_id = document.get('parent', None) - if not couch_parent_id: - couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) - parent_id = couchdb_relational_map[couch_parent_id] - self.set_parent(vulnerability, parent_id, level) self.add_references(document, vulnerability, workspace) self.add_policy_violations(document, vulnerability, workspace) yield vulnerability @@ -315,13 +344,6 @@ def add_references(self, document, vulnerability, workspace): vulnerability=vulnerability ) - def set_parent(self, vulnerability, parent_id, level): - logger.debug('Set parent for vulnerabiity level {0}'.format(level)) - if level == 2: - vulnerability.host_id = session.query(Host).filter_by(id=parent_id).first().id - if level == 4: - vulnerability.service_id = session.query(Service).filter_by(id=parent_id).first().id - class CommandImporter(object): DOC_TYPE = 'CommandRunInformation' @@ -387,7 +409,6 @@ class WorkspaceImporter(object): DOC_TYPE = 'Workspace' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - workspace, created = get_or_create(session, server.models.Workspace, name=document.get('name', None)) workspace.description = document.get('description') if document.get('duration') and document.get('duration')['start']: workspace.start_date = datetime.datetime.fromtimestamp(float(document.get('duration')['start'])/1000) @@ -717,7 +738,8 @@ def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, works def import_workspace_into_database(self, workspace_name): faraday_importer = FaradayEntityImporter() - workspace, created = get_or_create(session, server.models.Workspace, name=workspace_name) + workspace, created = get_or_create(session, Workspace, name=workspace_name) + session.commit() couch_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_temp_view?include_docs=true".format( username=server.config.couchdb.user, From f5a82d3c4c6a31a7cb416c709597f0489fadf02b Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 6 Sep 2017 15:08:24 -0300 Subject: [PATCH 0165/1506] Move interface importer inside host importer. --- server/importer.py | 202 +++++++++++++++++++++++++-------------------- 1 file changed, 111 insertions(+), 91 deletions(-) diff --git a/server/importer.py b/server/importer.py index cd0f5ad8e87..10a5d066923 100644 --- a/server/importer.py +++ b/server/importer.py @@ -47,7 +47,7 @@ ) COUCHDB_USER_PREFIX = 'org.couchdb.user:' -COUCHDB_PASSWORD_PREXFIX = '-pbkdf2-' +COUCHDB_PASSWORD_PREFIX = '-pbkdf2-' logger = server.utils.logger.get_logger(__name__) @@ -90,99 +90,125 @@ def __truncate_to_epoch_in_seconds(self, timestamp): return timestamp -class HostImporter(object): - DOC_TYPE = 'Host' - - def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - # Ticket #3387: if the 'os' field is None, we default to 'unknown' - try: - IP(document.get('name')) # this will raise ValueError on valid IPs - host, created = get_or_create(session, Host, ip=document.get('name')) - except ValueError: - host, created = get_or_create(session, Host, ip=document.get('ip')) +def check_ip_address(ip_str): + if not ip_str: + return False + if ip_str == '0.0.0.0': + return False + if ip_str == '0000:0000:0000:0000:0000:0000:0000:0000': + return False + if ip_str == '0000:0000:0000:0000:0000:0000:0000:0001': + return False + try: + IP(ip_str) + except ValueError: + return False + return True - if not document.get('os'): - document['os'] = 'unknown' - default_gateway = document.get('default_gateway', None) - if not host.ip: - host.ip = document.get('name') - host.description = document.get('description') - host.os = document.get('os') - host.default_gateway_ip = default_gateway and default_gateway[0] - host.default_gateway_mac = default_gateway and default_gateway[1] - host.owned = document.get('owned', False) - host.workspace = workspace - yield host - - -class InterfaceImporter(object): +class HostImporter(object): """ Class interface was removed in the new model. We will merge the interface data with the host. For ports we will create new services for open ports if it was not previously created. """ - DOC_TYPE = 'Interface' + + def retrieve_ips_from_host_document(self, document): + """ + + :param document: json document from couchdb with host data + :return: str with ip or name if no valid ip was found. + """ + try: + IP(document.get('name')) # this will raise ValueError on invalid IPs + yield document.get('name') + except ValueError: + host_ip = document.get('ipv4') + created_ipv4 = False + created_ipv6 = False + if check_ip_address(host_ip): + yield host_ip + created_ipv4 = True + host_ip = document.get('ipv6') + if check_ip_address(host_ip): + yield host_ip + if not created_ipv4 or not created_ipv6: + # sometimes the host lacks the ip. + yield document.get('name') + if created_ipv4 and created_ipv6: + logger.warn('Two host will be created one with ipv4 and another one with ipv6. Couch id is {0}'.format(document.get('_id'))) def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - # interface dict is used to merge interface with host and connect it the services to the host - interface = {} - interface['name'] = document.get('name') - interface['description'] = document.get('description') - interface['mac'] = document.get('mac') - interface['owned'] = document.get('owned', False) - interface['hostnames'] = document.get('hostnames') - interface['network_segment'] = document.get('network_segment') - interface['ipv4_address'] = document.get('ipv4').get('address') - interface['ipv4_gateway'] = document.get('ipv4').get('gateway') - interface['ipv4_dns'] = document.get('ipv4').get('DNS') - interface['ipv4_mask'] = document.get('ipv4').get('mask') - interface['ipv6_address'] = document.get('ipv6').get('address') - interface['ipv6_gateway'] = document.get('ipv6').get('gateway') - interface['ipv6_dns'] = document.get('ipv6').get('DNS') - interface['ipv6_prefix'] = str(document.get('ipv6').get('prefix')) - # ports_* are integers with counts - interface['ports_filtered'] = document.get('ports', {}).get('filtered') - interface['ports_opened'] = document.get('ports', {}).get('opened') - interface['ports_closed'] = document.get('ports', {}).get('closed') - interface['workspace'] = workspace - couch_parent_id = document.get('parent', None) - if not couch_parent_id: - couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) - parent_id = couchdb_relational_map[couch_parent_id] - interface['parent_id'] = parent_id - host = self.merge_with_host(interface, workspace, parent_id) - yield interface - - def check_ip_address(self, ip_str): - if not ip_str or ip_str == '0.0.0.0': - return False - if not ip_str or ip_str == '0000:0000:0000:0000:0000:0000:0000:0000': - return False - if not ip_str or ip_str == '0000:0000:0000:0000:0000:0000:0000:0001': - return False - return True - - def merge_with_host(self, interface, workspace, parent_relation_db_id): - host = session.query(Host).filter_by(id=parent_relation_db_id).first() - assert host.workspace == interface['workspace'] + hosts = [] + host_ips = [name_or_ip for name_or_ip in self.retrieve_ips_from_host_document(document)] + interfaces = self.host_interfaces_from_couch(workspace, document.get('_id')) + for interface in interfaces: + interface = interface['value'] + if check_ip_address(interface['ipv4']['address']): + interface_ip = interface['ipv4']['address'] + host, created = get_or_create(session, Host, ip=interface_ip, workspace=workspace) + host.default_gateway_ip = interface['ipv4']['gateway'] + self.merge_with_host(host, interface, workspace) + hosts.append(host) + if check_ip_address(interface['ipv6']['address']): + interface_ip = interface['ipv6']['address'] + host, created = get_or_create(session, Host, ip=interface_ip, workspace=workspace) + host.default_gateway_ip = interface['ipv6']['gateway'] + self.merge_with_host(host, interface, workspace) + hosts.append(host) + if not hosts: + # if not host were created after inspecting interfaces + # we create a host with "name" as ip to avoid losing hosts. + # some hosts lacks of interface + for name_or_ip in host_ips: + host, created = get_or_create(session, Host, ip=name_or_ip, workspace=workspace) + hosts.append(host) + + if len(hosts) > 1: + logger.warning('Total hosts found {0} for couchdb id {1}'.format(len(hosts), document.get('_id'))) + + for host in hosts: + # we update or set other host attributes in this cycle + # Ticket #3387: if the 'os' field is None, we default to 'unknown + if not document.get('os'): + document['os'] = 'unknown' + + default_gateway = document.get('default_gateway', None) + + host.description = document.get('description') + host.os = document.get('os') + host.default_gateway_ip = default_gateway and default_gateway[0] + host.default_gateway_mac = default_gateway and default_gateway[1] + host.owned = document.get('owned', False) + host.workspace = workspace + yield host + + def host_interfaces_from_couch(self, workspace, host_couchdb_id): + couch_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_temp_view?include_docs=true".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace.name + ) + data = { + "map": "function(doc) { if(doc.type == '%s' && doc._id.split('.').slice(0, 1).join('.') == '%s') emit(null, doc); }" % ( + 'Interface', host_couchdb_id) + } + + r = requests.post(couch_url, json=data) + try: + return r.json()['rows'] + except Exception as ex: + print('sadsa') + + def merge_with_host(self, host, interface, workspace): if interface['mac']: host.mac = interface['mac'] if interface['owned']: host.owned = interface['owned'] - if self.check_ip_address(interface['ipv4_address']): - interface_ip = interface['ipv4_address'] - if host.ip != interface_ip: - logger.warn('Overriding host ip address {0} with new ip {1}'.format(host.ip, interface_ip)) - host.ip = interface_ip - if self.check_ip_address(interface['ipv6_address']): - interface_ip = interface['ipv6_address'] - if host.ip != interface_ip: - logger.warn('Overriding host ip address {0} with new ip {1}'.format(host.ip, interface_ip)) - host.ip = interface_ip - if self.check_ip_address(interface['ipv4_gateway']) or self.check_ip_address(interface['ipv6_gateway']): - host.default_gateway_ip = interface['ipv4_gateway'] or interface['ipv6_gateway'] + #host.default_gateway_mac if interface['network_segment']: host.net_segment = interface['network_segment'] @@ -190,6 +216,7 @@ def merge_with_host(self, interface, workspace, parent_relation_db_id): host.description += '\n Interface data: {0}'.format(interface['description']) if type(interface['hostnames']) in (str, unicode): interface['hostnames'] = [interface['hostnames']] + for hostname_str in interface['hostnames']: if not hostname_str: # skip empty hostnames @@ -209,7 +236,7 @@ class ServiceImporter(object): DOC_TYPE = 'Service' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - # service was always above interface, not it's above host. + # service was always below interface, not it's below host. try: parent_id = document['parent'].split('.')[0] except KeyError: @@ -507,7 +534,6 @@ def get_importer_from_document(self, doc_type): 'Service': ServiceImporter, 'Note': NoteImporter, 'Credential': CredentialImporter, - 'Interface': InterfaceImporter, 'CommandRunInformation': CommandImporter, 'Workspace': WorkspaceImporter, 'Vulnerability': VulnerabilityImporter, @@ -532,11 +558,11 @@ def modular_crypt_pbkdf2_sha1(self, checksum, salt, iterations=1000): ) def convert_couchdb_hash(self, original_hash): - if not original_hash.startswith(COUCHDB_PASSWORD_PREXFIX): + if not original_hash.startswith(COUCHDB_PASSWORD_PREFIX): # Should be a plaintext password return original_hash checksum, salt, iterations = original_hash[ - len(COUCHDB_PASSWORD_PREXFIX):].split(',') + len(COUCHDB_PASSWORD_PREFIX):].split(',') iterations = int(iterations) return self.modular_crypt_pbkdf2_sha1(checksum, salt, iterations) @@ -645,11 +671,6 @@ def run(self): name=ref_doc) - - - - - class ImportCouchDB(FlaskScriptCommand): def _open_couchdb_conn(self): @@ -751,7 +772,7 @@ def import_workspace_into_database(self, workspace_name): # obj_types are tuples. the first value is the level on the tree # for the desired obj. - # the idea is to improt by level from couchdb data. + # the idea is to import by level from couchdb data. obj_types = [ (1, 'Host'), (1, 'EntityMetadata'), @@ -761,7 +782,6 @@ def import_workspace_into_database(self, workspace_name): (1, 'Task'), (1, 'Workspace'), (1, 'Reports'), - (2, 'Interface'), (2, 'Service'), (2, 'Credential'), (2, 'Vulnerability'), From b2b19198b5870441189532965821f879c73245d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 6 Sep 2017 16:41:36 -0300 Subject: [PATCH 0166/1506] Fix rollback issues when creating a host with an existing IP Detailed list of changes: - When creating host with an IP that already exists in that workspace, rollback (so it isn't saved because it would throw an IntegrityError) and abort - Use custom SQLAlchemy to correctly handle rollbacks in SQLite Based on http://docs.sqlalchemy.org/en/rel_1_0/dialects/sqlite.html#serializable-isolation-savepoints-transactional-ddl but with support with Flask-SQLAlchemy - Improve session fixture to support functions doing rollbacks Based on http://docs.sqlalchemy.org/en/latest/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites - Add "second_workspace" fixture - Fixed test_create_a_host_fails_with_existing_ip and test_create_a_host_succeeds test cases to do an assertion based on the number of hosts in the database - Added empty test_create_a_host_with_ip_of_other_workspace test, TBI - Add commented line to activate echo in SQLAlchemy --- server/api/base.py | 1 + server/app.py | 1 + server/models.py | 46 +++++++++++++++++++++++++++- test_cases/conftest.py | 56 ++++++++++++++++++++++++++++++++++- test_cases/test_server_api.py | 12 ++++++-- 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 16e81a060da..554f99b0c9f 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -94,6 +94,7 @@ def _validate_uniqueness(self, obj): query = self._get_base_query(obj.workspace.name).filter( field==value) if query.one_or_none(): + db.session.rollback() abort(422, ValidationError('Existing value for %s field: %s' % ( field_name, value ))) diff --git a/server/app.py b/server/app.py index 957772923c8..d243f2798c4 100644 --- a/server/app.py +++ b/server/app.py @@ -27,6 +27,7 @@ def create_app(db_connection_string=None, testing=None): app.config['SECURITY_POST_LOGOUT_VIEW'] = '/_api/login' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False if testing: + # app.config['SQLALCHEMY_ECHO'] = True app.config['TESTING'] = testing try: app.config['SQLALCHEMY_DATABASE_URI'] = db_connection_string or server.config.database.connection_string.strip("'") diff --git a/server/models.py b/server/models.py index b1b80fa3119..9f9a66ac2df 100644 --- a/server/models.py +++ b/server/models.py @@ -12,10 +12,14 @@ String, Text, UniqueConstraint, + event ) from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.declarative import declared_attr -from flask_sqlalchemy import SQLAlchemy +from flask_sqlalchemy import ( + SQLAlchemy as OriginalSQLAlchemy, + _EngineConnector +) from flask_security import ( RoleMixin, UserMixin, @@ -23,6 +27,46 @@ import server.config + +class SQLAlchemy(OriginalSQLAlchemy): + """Override to fix issues when doing a rollback with sqlite driver + See http://docs.sqlalchemy.org/en/rel_1_0/dialects/sqlite.html#serializable-isolation-savepoints-transactional-ddl + and https://bitbucket.org/zzzeek/sqlalchemy/issues/3561/sqlite-nested-transactions-fail-with + for furhter information""" + + # TODO: only do this on sqlite, not on postgres! + + def make_connector(self, app=None, bind=None): + """Creates the connector for a given state and bind.""" + return CustomEngineConnector(self, self.get_app(app), bind) + + +class CustomEngineConnector(_EngineConnector): + """Used by overrideb SQLAlchemy class to fix rollback issues""" + + def get_engine(self): + # Use an existent engine and don't register events if possible + uri = self.get_uri() + echo = self._app.config['SQLALCHEMY_ECHO'] + if (uri, echo) == self._connected_for: + return self._engine + + # Call original metohd and register events + rv = super(CustomEngineConnector, self).get_engine() + with self._lock: + @event.listens_for(rv, "connect") + def do_connect(dbapi_connection, connection_record): + # disable pysqlite's emitting of the BEGIN statement entirely. + # also stops it from emitting COMMIT before any DDL. + dbapi_connection.isolation_level = None + + @event.listens_for(rv, "begin") + def do_begin(conn): + # emit our own BEGIN + conn.execute("BEGIN") + return rv + + db = SQLAlchemy() SCHEMA_VERSION = 'W.3.0.0' diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 57567edd662..f67c569e282 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -3,6 +3,7 @@ import json import pytest from flask.testing import FlaskClient +from sqlalchemy import event from pytest_factoryboy import register sys.path.append(os.path.abspath(os.getcwd())) @@ -22,6 +23,8 @@ for factory in enabled_factories: register(factory) +register(factories.WorkspaceFactory, "second_workspace") + class CustomClient(FlaskClient): @@ -74,7 +77,7 @@ def teardown(): @pytest.fixture(scope='function') -def session(database, request): +def fake_session(database, request): connection = database.engine.connect() transaction = connection.begin() @@ -87,9 +90,59 @@ def session(database, request): for factory in enabled_factories: factory._meta.sqlalchemy_session = session + def teardown(): + # rollback - everything that happened with the + # Session above (including calls to commit()) + # is rolled back. + # be careful with this!!!!! + transaction.rollback() + connection.close() + session.remove() + request.addfinalizer(teardown) + return session + + +@pytest.fixture(scope='function') +def session(database, request): + """Use this fixture if the function being tested does a session + rollback. + + See http://docs.sqlalchemy.org/en/latest/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites + for further information + """ + connection = database.engine.connect() + transaction = connection.begin() + + options = {"bind": connection, 'binds': {}} + session = db.create_scoped_session(options=options) + + # start the session in a SAVEPOINT... + session.begin_nested() + + # then each time that SAVEPOINT ends, reopen it + @event.listens_for(session, "after_transaction_end") + def restart_savepoint(session, transaction): + if transaction.nested and not transaction._parent.nested: + + # ensure that state is expired the way + # session.commit() at the top level normally does + # (optional step) + session.expire_all() + + session.begin_nested() + + database.session = session + db.session = session + + for factory in enabled_factories: + factory._meta.sqlalchemy_session = session def teardown(): + # rollback - everything that happened with the + # Session above (including calls to commit()) + # is rolled back. + # be careful with this!!!!! transaction.rollback() connection.close() session.remove() @@ -97,6 +150,7 @@ def teardown(): request.addfinalizer(teardown) return session + @pytest.fixture def test_client(app): return app.test_client() diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 69e8fa3b3b1..b3b04b4888c 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -52,8 +52,8 @@ def test_create_a_host_succeeds(self, test_client): "description": "aaaaa", # os is not required }) - print res.data assert res.status_code == 201 + assert Host.query.count() == HOSTS_COUNT + 1 host_id = res.json['id'] host = Host.query.get(host_id) assert host.ip == "127.0.0.1" @@ -67,12 +67,18 @@ def test_create_a_host_fails_with_missing_desc(self, test_client): }) assert res.status_code == 400 - def test_create_a_host_fails_with_existing_ip(self, test_client, - session, host): + def test_create_a_host_fails_with_existing_ip(self, session, + test_client, host): session.add(host) session.commit() + res = test_client.post(self.url(), data={ "ip": host.ip, "description": "aaaaa", }) assert res.status_code == 400 + assert Host.query.count() == HOSTS_COUNT + 1 + +# def test_create_a_host_with_ip_of_other_workspace(self, test_client, +# second_workspace, host): +# pass From 70142344b95aaf6eb0cc865350a3fee17e875268 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 6 Sep 2017 16:53:07 -0300 Subject: [PATCH 0167/1506] Add generic model to comment any model --- server/models.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/server/models.py b/server/models.py index 87fb9f0ab7e..59e652624f4 100644 --- a/server/models.py +++ b/server/models.py @@ -726,6 +726,17 @@ class TagObject(db.Model): tag_id = Column(Integer, ForeignKey('tag.id'), index=True) +class CommentObject(db.Model): + __tablename__ = 'comment_object' + id = Column(Integer, primary_key=True) + + object_id = Column(Integer, nullable=False) + object_type = Column(Text, nullable=False) + + comment = relationship('Comment', backref='comment_objects') + comment_id = Column(Integer, ForeignKey('comment.id'), index=True) + + class Comment(db.Model): __tablename__ = 'comment' id = Column(Integer, primary_key=True) @@ -739,12 +750,10 @@ class Comment(db.Model): foreign_keys=[reply_to_id] ) - object_id = Column(Integer, nullable=False) - object_type = Column(Text, nullable=False) - workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', foreign_keys=[workspace_id]) + class ExecutiveReport(db.Model): STATUSES = [ 'created', From 67d6299e5d738b756615c5f5aa55a6ef625608b7 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 6 Sep 2017 16:53:34 -0300 Subject: [PATCH 0168/1506] Add communication and license importer --- server/importer.py | 62 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/server/importer.py b/server/importer.py index 10a5d066923..5b0922a921d 100644 --- a/server/importer.py +++ b/server/importer.py @@ -8,21 +8,17 @@ from binascii import unhexlify import requests -from tqdm import tqdm from IPy import IP from flask_script import Command as FlaskScriptCommand -from restkit.errors import RequestError, Unauthorized -from IPy import IP from passlib.utils.binary import ab64_encode -from passlib.hash import pbkdf2_sha1 +from restkit.errors import RequestError, Unauthorized +from tqdm import tqdm -from server.web import app -import server.utils.logger +import server.config import server.couchdb import server.database import server.models -import server.config -from server.utils.database import get_or_create +import server.utils.logger from server.models import ( db, EntityMetadata, @@ -44,7 +40,12 @@ ExecutiveReport, VulnerabilityTemplate, ReferenceTemplate, + License, + Comment, + CommentObject, ) +from server.utils.database import get_or_create +from server.web import app COUCHDB_USER_PREFIX = 'org.couchdb.user:' COUCHDB_PASSWORD_PREFIX = '-pbkdf2-' @@ -512,6 +513,23 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation yield report +class CommunicationImporter(object): + def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): + comment, created = get_or_create( + session, + Comment, + text=document.get('text'), + workspace=workspace) + + get_or_create( + session, + CommentObject, + object_id=workspace.id, + object_type='Workspace', + comment=comment, + ) + yield comment + class FaradayEntityImporter(object): # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace'] @@ -541,6 +559,7 @@ def get_importer_from_document(self, doc_type): 'TaskGroup': MethodologyImporter, 'Task': TaskImporter, 'Reports': ReportsImporter, + 'Communication': CommunicationImporter } importer_self = importer_class_mapper.get(doc_type, None) if not importer_self: @@ -671,6 +690,30 @@ def run(self): name=ref_doc) +class ImportLicense(FlaskScriptCommand): + + def run(self): + cwe_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + path='faraday_licenses/_all_docs?include_docs=true' + ) + licenses = requests.get(cwe_url).json()['rows'] + for license in licenses: + document = license['doc'] + + license_obj, created = get_or_create(session, + License, + product=document.get('product'), + start_date=datetime.datetime.strptime(document['start'], "%Y-%m-%dT%H:%M:%S.%fZ"), + end_date=datetime.datetime.strptime(document['end'], "%Y-%m-%dT%H:%M:%S.%fZ"), + notes=document.get('notes'), + type=document.get('lictype') + ) + + class ImportCouchDB(FlaskScriptCommand): def _open_couchdb_conn(self): @@ -695,6 +738,8 @@ def run(self): """ Main entry point for couchdb import """ + license_import = ImportLicense() + license_import.run() vuln_templates_import = ImportVulnerabilityTemplates() vuln_templates_import.run() users_import = ImportCouchDBUsers() @@ -780,6 +825,7 @@ def import_workspace_into_database(self, workspace_name): (1, 'CommandRunInformation'), (1, 'TaskGroup'), (1, 'Task'), + (1, 'Communication'), (1, 'Workspace'), (1, 'Reports'), (2, 'Service'), From 0d88d92fba046ba11438722feb2a5b62e0e198dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 6 Sep 2017 18:18:35 -0300 Subject: [PATCH 0169/1506] Add test_create_a_host_with_ip_of_other_workspace test --- test_cases/test_server_api.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index b3b04b4888c..d95c97f07d6 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -79,6 +79,16 @@ def test_create_a_host_fails_with_existing_ip(self, session, assert res.status_code == 400 assert Host.query.count() == HOSTS_COUNT + 1 -# def test_create_a_host_with_ip_of_other_workspace(self, test_client, -# second_workspace, host): -# pass + def test_create_a_host_with_ip_of_other_workspace(self, test_client, + session, + second_workspace, host): + session.add(host) + session.commit() + + res = test_client.post(self.url(workspace=second_workspace), data={ + "ip": host.ip, + "description": "aaaaa", + }) + assert res.status_code == 201 + # It should create two hosts, one for each workspace + assert Host.query.count() == HOSTS_COUNT + 2 From baed666a962e43133dfb3c6464c95d3a3b1b763e Mon Sep 17 00:00:00 2001 From: micabot Date: Wed, 6 Sep 2017 18:34:57 -0300 Subject: [PATCH 0170/1506] Add function to query CouchDB objs by level and parent Adds a function to the importer that uploads a view to CouchDB and then queries it. --- server/importer.py | 101 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 25 deletions(-) diff --git a/server/importer.py b/server/importer.py index cd0f5ad8e87..aff9caad5ec 100644 --- a/server/importer.py +++ b/server/importer.py @@ -53,6 +53,81 @@ logger = server.utils.logger.get_logger(__name__) session = db.session +OBJ_TYPES = [ + (1, 'Host'), + (1, 'EntityMetadata'), + (1, 'Note'), + (1, 'CommandRunInformation'), + (1, 'TaskGroup'), + (1, 'Task'), + (1, 'Workspace'), + (1, 'Reports'), + (2, 'Interface'), + (2, 'Service'), + (2, 'Credential'), + (2, 'Vulnerability'), + (2, 'VulnerabilityWeb'), + (3, 'Service'), + (4, 'Credential'), # Level 4 is for interface + (4, 'Vulnerability'), + (4, 'VulnerabilityWeb'), + ] + +def get_children_from_couch(workspace, parent_couchdb_id, child_type, level): + """ + Performance for temporary views suck, so this method uploads a view and queries it instead + + :param workspace: workspace to upload the view + :param parent_couchdb_id: ID of the parent document + :param child_type: type of the child obj we're looking for + :param level: level of the child obj we're looking for, must match those in OBJ_TYPES + :return: + """ + if (level, child_type) not in OBJ_TYPES: + logger.warn('Unable to retrieve children of type {0} at level {1}'.format(child_type, level)) + return [] + + couch_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace.name, + ) + + # create the new view + view_url = "{}_design/importer".format(couch_url) + view_data = { + "views": { + "children_by_parent_and_type": { + "map": "function(doc) { id_parent = doc._id.split('.').slice(0, -1).join('.');" + "key = [id_parent,doc.type]; emit(key, doc); }" + } + } + } + + try: + r = requests.put(view_url, json=view_data) + except requests.exceptions.RequestException as e: + logger.warn(e) + return [] + + # and now, finally query it! + couch_url += "_design/importer/_view/children_by_parent_and_type?" \ + "startkey=[\"{parent_id}\",\"{child_type}\"]&" \ + "endkey=[\"{parent_id}\",\"{child_type}\"]".format( + parent_id=parent_couchdb_id, + child_type=child_type, + ) + + try: + r = requests.get(couch_url) + except requests.exceptions.RequestException as e: + logger.warn(e) + return [] + + return r.json()['rows'] + class EntityNotFound(Exception): def __init__(self, entity_id): @@ -645,13 +720,7 @@ def run(self): name=ref_doc) - - - - - class ImportCouchDB(FlaskScriptCommand): - def _open_couchdb_conn(self): try: couchdb_server_conn = server.couchdb.CouchDBServer() @@ -752,25 +821,7 @@ def import_workspace_into_database(self, workspace_name): # obj_types are tuples. the first value is the level on the tree # for the desired obj. # the idea is to improt by level from couchdb data. - obj_types = [ - (1, 'Host'), - (1, 'EntityMetadata'), - (1, 'Note'), - (1, 'CommandRunInformation'), - (1, 'TaskGroup'), - (1, 'Task'), - (1, 'Workspace'), - (1, 'Reports'), - (2, 'Interface'), - (2, 'Service'), - (2, 'Credential'), - (2, 'Vulnerability'), - (2, 'VulnerabilityWeb'), - (3, 'Service'), - (4, 'Credential'), # Level 4 is for interface - (4, 'Vulnerability'), - (4, 'VulnerabilityWeb'), - ] + obj_types = OBJ_TYPES couchdb_relational_map = {} couchdb_removed_objs = set() removed_objs = ['Interface'] From 82bbcbc7893319b29a2125c5d0ea6e0224c189d3 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 7 Sep 2017 13:03:18 -0300 Subject: [PATCH 0171/1506] Use new function to retrieve children. Generate multiple hosts * We generate multiple host for each interface and also for ipv4 and ipv6, one for each. * For each new host we generate vulns. So if a host with ipv4 and ipv6 set the vulns will be duplicate, the same for services. Not the host has only one field * Temporal views were very slow for retrieve multiple children and also caused couchdb to collapse. --- server/importer.py | 287 ++++++++++++++++++++++----------------------- 1 file changed, 137 insertions(+), 150 deletions(-) diff --git a/server/importer.py b/server/importer.py index 597ed78f018..baa57341396 100644 --- a/server/importer.py +++ b/server/importer.py @@ -5,6 +5,7 @@ import sys import json import datetime +from collections import defaultdict from binascii import unhexlify import requests @@ -74,19 +75,15 @@ (4, 'VulnerabilityWeb'), ] -def get_children_from_couch(workspace, parent_couchdb_id, child_type, level): +def get_children_from_couch(workspace, parent_couchdb_id, child_type): """ Performance for temporary views suck, so this method uploads a view and queries it instead :param workspace: workspace to upload the view :param parent_couchdb_id: ID of the parent document :param child_type: type of the child obj we're looking for - :param level: level of the child obj we're looking for, must match those in OBJ_TYPES :return: """ - if (level, child_type) not in OBJ_TYPES: - logger.warn('Unable to retrieve children of type {0} at level {1}'.format(child_type, level)) - return [] couch_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/".format( username=server.config.couchdb.user, @@ -173,8 +170,6 @@ def check_ip_address(ip_str): return False if ip_str == '0000:0000:0000:0000:0000:0000:0000:0000': return False - if ip_str == '0000:0000:0000:0000:0000:0000:0000:0001': - return False try: IP(ip_str) except ValueError: @@ -218,7 +213,7 @@ def retrieve_ips_from_host_document(self, document): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): hosts = [] host_ips = [name_or_ip for name_or_ip in self.retrieve_ips_from_host_document(document)] - interfaces = self.host_interfaces_from_couch(workspace, document.get('_id')) + interfaces = get_children_from_couch(workspace, document.get('_id'), 'Interface') for interface in interfaces: interface = interface['value'] if check_ip_address(interface['ipv4']['address']): @@ -260,25 +255,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation host.workspace = workspace yield host - def host_interfaces_from_couch(self, workspace, host_couchdb_id): - couch_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_temp_view?include_docs=true".format( - username=server.config.couchdb.user, - password=server.config.couchdb.password, - hostname=server.config.couchdb.host, - port=server.config.couchdb.port, - workspace_name=workspace.name - ) - data = { - "map": "function(doc) { if(doc.type == '%s' && doc._id.split('.').slice(0, 1).join('.') == '%s') emit(null, doc); }" % ( - 'Interface', host_couchdb_id) - } - - r = requests.post(couch_url, json=data) - try: - return r.json()['rows'] - except Exception as ex: - print('sadsa') - def merge_with_host(self, host, interface, workspace): if interface['mac']: host.mac = interface['mac'] @@ -318,34 +294,35 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation except KeyError: # some services are missing the parent key parent_id = document['_id'].split('.')[0] - host, created = get_or_create(session, Host, id=couchdb_relational_map[parent_id]) - ports = document.get('ports') - if len(ports) > 2: - logger.warn('More than one port found in services!') - for port in ports: - service, created = get_or_create(session, - Service, - name=document.get('name'), - port=port, - host=host) - service.description = document.get('description') - service.owned = document.get('owned', False) - service.banner = document.get('banner') - service.protocol = document.get('protocol') - if not document.get('status'): - logger.warning('Service {0} with empty status. Using open as status'.format(document['_id'])) - document['status'] = 'open' - status_mapper = { - 'open': 'open', - 'closed': 'closed', - 'filtered': 'filtered', - 'open|filtered': 'filtered' - } - service.status = status_mapper[document.get('status')] - service.version = document.get('version') - service.workspace = workspace - - yield service + for relational_parent_id in couchdb_relational_map[parent_id]: + host, created = get_or_create(session, Host, id=relational_parent_id) + ports = document.get('ports') + if len(ports) > 2: + logger.warn('More than one port found in services!') + for port in ports: + service, created = get_or_create(session, + Service, + name=document.get('name'), + port=port, + host=host) + service.description = document.get('description') + service.owned = document.get('owned', False) + service.banner = document.get('banner') + service.protocol = document.get('protocol') + if not document.get('status'): + logger.warning('Service {0} with empty status. Using open as status'.format(document['_id'])) + document['status'] = 'open' + status_mapper = { + 'open': 'open', + 'closed': 'closed', + 'filtered': 'filtered', + 'open|filtered': 'filtered' + } + service.status = status_mapper[document.get('status')] + service.version = document.get('version') + service.workspace = workspace + + yield service class VulnerabilityImporter(object): @@ -355,76 +332,77 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation couch_parent_id = document.get('parent', None) if not couch_parent_id: couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) - parent_id = couchdb_relational_map[couch_parent_id] - if level == 2: - parent = session.query(Host).filter_by(id=parent_id).first() - if level == 4: - parent = session.query(Service).filter_by(id=parent_id).first() - if document['type'] == 'VulnerabilityWeb': - vulnerability, created = get_or_create( - session, - VulnerabilityWeb, - name=document.get('name'), - description=document.get('desc'), - service_id=parent.id, - ) - if document['type'] == 'Vulnerability': - vuln_params = { - 'name': document.get('name'), - 'description': document.get('desc') - } - if type(parent) == Host: - vuln_params.update({'host_id': parent.id}) - elif type(parent) == Service: - vuln_params.update({'service_id': parent.id}) - vulnerability, created = get_or_create( - session, - Vulnerability, - **vuln_params - ) + parent_ids = couchdb_relational_map[couch_parent_id] + for parent_id in parent_ids: + if level == 2: + parent = session.query(Host).filter_by(id=parent_id).first() + if level == 4: + parent = session.query(Service).filter_by(id=parent_id).first() + if document['type'] == 'VulnerabilityWeb': + vulnerability, created = get_or_create( + session, + VulnerabilityWeb, + name=document.get('name'), + description=document.get('desc'), + service_id=parent.id, + ) + if document['type'] == 'Vulnerability': + vuln_params = { + 'name': document.get('name'), + 'description': document.get('desc') + } + if type(parent) == Host: + vuln_params.update({'host_id': parent.id}) + elif type(parent) == Service: + vuln_params.update({'service_id': parent.id}) + vulnerability, created = get_or_create( + session, + Vulnerability, + **vuln_params + ) - vulnerability.confirmed = document.get('confirmed', False) or False - vulnerability.data = document.get('data') - vulnerability.easeofresolution = document.get('easeofresolution') - vulnerability.resolution = document.get('resolution') - mapped_severity = { - 'med': 'medium', - 'critical': 'critical', - 'high':'high', - 'low': 'low', - 'info': 'informational', - 'unclassified': 'unclassified', - } - vulnerability.severity = mapped_severity[document.get('severity')] - vulnerability.owned = document.get('owned', False) - #vulnerability.attachments = json.dumps(document.get('_attachments', {})) - vulnerability.impact_accountability = document.get('impact', {}).get('accountability') - vulnerability.impact_availability = document.get('impact', {}).get('availability') - vulnerability.impact_confidentiality = document.get('impact', {}).get('confidentiality') - vulnerability.impact_integrity = document.get('impact', {}).get('integrity') - if document['type'] == 'VulnerabilityWeb': - vulnerability.method = document.get('method') - vulnerability.path = document.get('path') - vulnerability.pname = document.get('pname') - vulnerability.query = document.get('query') - vulnerability.request = document.get('request') - vulnerability.response = document.get('response') - vulnerability.website = document.get('website') - params = document.get('params', u'') - if isinstance(params, (list, tuple)): - vulnerability.parameters = (u' '.join(params)).strip() - else: - vulnerability.parameters = params if params is not None else u'' - status_map = { - 'opened': 'open', - 'closed': 'closed', - } - status = status_map[document.get('status', 'opened')] - vulnerability.status = status - vulnerability.workspace = workspace + vulnerability.confirmed = document.get('confirmed', False) or False + vulnerability.data = document.get('data') + vulnerability.easeofresolution = document.get('easeofresolution') + vulnerability.resolution = document.get('resolution') + mapped_severity = { + 'med': 'medium', + 'critical': 'critical', + 'high':'high', + 'low': 'low', + 'info': 'informational', + 'unclassified': 'unclassified', + } + vulnerability.severity = mapped_severity[document.get('severity')] + vulnerability.owned = document.get('owned', False) + #vulnerability.attachments = json.dumps(document.get('_attachments', {})) + vulnerability.impact_accountability = document.get('impact', {}).get('accountability') + vulnerability.impact_availability = document.get('impact', {}).get('availability') + vulnerability.impact_confidentiality = document.get('impact', {}).get('confidentiality') + vulnerability.impact_integrity = document.get('impact', {}).get('integrity') + if document['type'] == 'VulnerabilityWeb': + vulnerability.method = document.get('method') + vulnerability.path = document.get('path') + vulnerability.pname = document.get('pname') + vulnerability.query = document.get('query') + vulnerability.request = document.get('request') + vulnerability.response = document.get('response') + vulnerability.website = document.get('website') + params = document.get('params', u'') + if isinstance(params, (list, tuple)): + vulnerability.parameters = (u' '.join(params)).strip() + else: + vulnerability.parameters = params if params is not None else u'' + status_map = { + 'opened': 'open', + 'closed': 'closed', + } + status = status_map[document.get('status', 'opened')] + vulnerability.status = status + vulnerability.workspace = workspace - self.add_references(document, vulnerability, workspace) - self.add_policy_violations(document, vulnerability, workspace) + self.add_references(document, vulnerability, workspace) + self.add_policy_violations(document, vulnerability, workspace) yield vulnerability def add_policy_violations(self, document, vulnerability, workspace): @@ -489,22 +467,28 @@ class CredentialImporter(object): DOC_TYPE = 'Cred' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - host = None - service = None + parents = [] if level == 2: - parent_id = couchdb_relational_map[document['_id'].split('.')[0]] - host = session.query(Host).filter_by(id=parent_id).first() + parent_ids = couchdb_relational_map[document['_id'].split('.')[0]] + parents = session.query(Host).filter(Host.id.in_(parent_ids)).all() if level == 4: - parent_id = couchdb_relational_map['.'.join(document['_id'].split('.')[:3])] - service = session.query(Service).filter_by(id=parent_id).first() - if not host and not service: + parent_ids = couchdb_relational_map['.'.join(document['_id'].split('.')[:3])] + parents = session.query(Service).filter(Host.id.in_(parent_ids)).all() + if not parents: raise Exception('Missing host or service for credential {0}'.format(document['_id'])) - credential, created = get_or_create(session, Credential, username=document.get('username'), host=host, service=service) - credential.password = document.get('password', None) - credential.owned = document.get('owned', False) - credential.description = document.get('description', None) - credential.name = document.get('name', None) - credential.workspace = workspace + for parent in parents: + service = None + host = None + if isinstance(parent, Host): + host = parent + if isinstance(parent, Service): + service = parent + credential, created = get_or_create(session, Credential, username=document.get('username'), host=host, service=service) + credential.password = document.get('password', None) + credential.owned = document.get('owned', False) + credential.description = document.get('description', None) + credential.name = document.get('name', None) + credential.workspace = workspace yield credential @@ -539,10 +523,13 @@ class TaskImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): try: - methodology_id = couchdb_relational_map[document.get('group_id')] + methodology_id = couchdb_relational_map[document.get('group_id')][0] except KeyError: logger.warn('Could not found methodology with id {0}'.format(document.get('group_id'))) return [] + if len(couchdb_relational_map[document.get('group_id')]) > 1: + logger.error('It was expected only one parent in methodology {0}'.format(document.get('_id'))) + methodology = session.query(Methodology).filter_by(id=methodology_id).first() task_class = Task if not methodology: @@ -841,7 +828,7 @@ def get_objs(self, host, obj_type, level): return r.json() - def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, workspace): + def verify_import_data(self, couchdb_relational_map, workspace): all_docs_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_all_docs?include_docs=true".format( username=server.config.couchdb.user, password=server.config.couchdb.password, @@ -850,11 +837,14 @@ def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, works workspace_name=workspace.name ) all_ids = map(lambda x: x['doc']['_id'], requests.get(all_docs_url).json()['rows']) - if len(all_ids) != len(couchdb_relational_map.keys()) + len(couchdb_removed_objs): + if len(all_ids) != len(couchdb_relational_map.keys()): missing_objs_filename = os.path.join(os.path.expanduser('~/.faraday'), 'logs', 'import_missing_objects_{0}.json'.format(workspace.name)) - missing_ids = set(all_ids) - set(couchdb_relational_map.keys()).union(couchdb_removed_objs) + missing_ids = set(all_ids) - set(couchdb_relational_map.keys()) + missing_ids = missing_ids - set([u'_design/commands', u'_design/hosts', u'_design/comms', u'_design/tags', u'_design/vulns', u'_design/utils', u'_design/importer', u'_design/auth', u'_design/mapper', u'_design/services', u'_design/interfaces', u'_design/changes', u'_design/reports', +]) objs_diff = [] - logger.info('Downloading missing couchdb docs') + if missing_ids: + logger.info('Downloading missing couchdb docs') for missing_id in tqdm(missing_ids): doc_url = 'http://{username}:{password}@{hostname}:{port}/{workspace_name}/{doc_id}'.format( username=server.config.couchdb.user, @@ -865,6 +855,9 @@ def verify_import_data(self, couchdb_relational_map, couchdb_removed_objs, works doc_id=missing_id ) not_imported_obj = requests.get(doc_url).json() + if not_imported_obj['type'] == 'Interface': + # we know that interface obj was not imported + continue filter_keys = ['views', 'validate_doc_update'] if not any(map(lambda x: x not in filter_keys, not_imported_obj.keys())): # we filter custom views, validation funcs, etc @@ -892,9 +885,7 @@ def import_workspace_into_database(self, workspace_name): # obj_types are tuples. the first value is the level on the tree # for the desired obj. obj_types = OBJ_TYPES - couchdb_relational_map = {} - couchdb_removed_objs = set() - removed_objs = ['Interface'] + couchdb_relational_map = defaultdict(list) for level, obj_type in obj_types: obj_importer = faraday_importer.get_importer_from_document(obj_type)() objs_dict = self.get_objs(couch_url, obj_type, level) @@ -908,10 +899,6 @@ def import_workspace_into_database(self, workspace_name): if not new_obj: continue session.commit() - if obj_type not in removed_objs: - couchdb_relational_map[couchdb_id] = new_obj.id - else: - couchdb_relational_map[couchdb_id] = new_obj['parent_id'] - couchdb_removed_objs.add(couchdb_id) - self.verify_import_data(couchdb_relational_map, couchdb_removed_objs, workspace) + couchdb_relational_map[couchdb_id].append(new_obj.id) + self.verify_import_data(couchdb_relational_map, workspace) return created From c0cd3c7877d5b741149ca24765e91db3fdf3cef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 7 Sep 2017 13:22:54 -0300 Subject: [PATCH 0172/1506] Add update endpoint logic --- server/api/base.py | 30 +++++++++++++++++++++++++++++- test_cases/test_server_api.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/server/api/base.py b/server/api/base.py index 554f99b0c9f..ccf493a2da2 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -3,6 +3,7 @@ from flask_classful import FlaskView from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.inspection import inspect from werkzeug.routing import parse_rule from webargs.flaskparser import FlaskParser, abort from webargs.core import ValidationError @@ -85,14 +86,19 @@ def _parse_data(self, schema, request, *args, **kwargs): return FlaskParser().parse(schema, request, locations=('json',), *args, **kwargs) - def _validate_uniqueness(self, obj): + def _validate_uniqueness(self, obj, object_id=None): assert obj.workspace is not None, "Object must have a " \ "workspace attribute set to call _validate_uniqueness" + primary_key_field = inspect(self.model_class).primary_key[0] for field_name in self.unique_fields: field = getattr(self.model_class, field_name) value = getattr(obj, field_name) query = self._get_base_query(obj.workspace.name).filter( field==value) + if object_id is not None: + # The object already exists in DB, we want to fetch an object + # different to this one but with the same unique field + query = query.filter(primary_key_field != object_id) if query.one_or_none(): db.session.rollback() abort(422, ValidationError('Existing value for %s field: %s' % ( @@ -160,7 +166,29 @@ def _perform_create(self, workspace_name, obj): return obj +class UpdateWorkspacedMixin(object): + def put(self, workspace_name, object_id): + data = self._parse_data(self._get_schema_class()(strict=True), + flask.request) + obj = self._get_object(workspace_name, object_id) + self._update_object(obj, data) + updated = self._perform_update(workspace_name, object_id, obj) + return self._dump(obj).data, 200 + + def _update_object(self, obj, data): + for (key, value) in data.items(): + setattr(obj, key, value) + + def _perform_update(self, workspace_name, object_id, obj): + with db.session.no_autoflush: + obj.workspace = self._get_workspace(workspace_name) + self._validate_uniqueness(obj, object_id) + db.session.add(obj) + db.session.commit() + + class ReadWriteWorkspacedView(CreateWorkspacedMixin, + UpdateWorkspacedMixin, ReadOnlyWorkspacedView, GenericWorkspacedView): """A generic view with list, retrieve and create endpoints""" diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index d95c97f07d6..37e25444242 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -92,3 +92,35 @@ def test_create_a_host_with_ip_of_other_workspace(self, test_client, assert res.status_code == 201 # It should create two hosts, one for each workspace assert Host.query.count() == HOSTS_COUNT + 2 + + def test_update_a_host(self, test_client): + host = self.workspace.hosts[0] + res = test_client.put(self.url(host), data={ + "ip": host.ip, + "description": "bbbbb", + }) + assert res.status_code == 200 + assert res.json['description'] == 'bbbbb' + assert Host.query.get(res.json['id']).description == 'bbbbb' + assert Host.query.count() == HOSTS_COUNT + + def test_update_a_host_fails_with_existing_ip(self, test_client, session): + host = self.workspace.hosts[0] + original_ip = host.ip + original_desc = host.description + res = test_client.put(self.url(host), data={ + "ip": self.workspace.hosts[1].ip, # Existing IP + "description": "bbbbb", + }) + assert res.status_code == 400 + session.refresh(host) + assert host.ip == original_ip + assert host.description == original_desc # It shouldn't do a partial update + + def test_update_a_host_fails_with_missing_fields(self, test_client): + """To do this the user should use a PATCH request""" + host = self.workspace.hosts[0] + res = test_client.put(self.url(host), data={ + "ip": "1.2.3.4", # Existing IP + }) + assert res.status_code == 400 From 8b8425ff98d58a32329be134b83fa8e72d1490cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 7 Sep 2017 13:27:06 -0300 Subject: [PATCH 0173/1506] Add delete endpoint logic --- server/api/base.py | 12 ++++++++++++ test_cases/test_server_api.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/server/api/base.py b/server/api/base.py index ccf493a2da2..5020b03b530 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -187,8 +187,20 @@ def _perform_update(self, workspace_name, object_id, obj): db.session.commit() +class DeleteWorkspacedMixin(object): + def delete(self, workspace_name, object_id): + obj = self._get_object(workspace_name, object_id) + self._perform_delete(obj) + return None, 204 + + def _perform_delete(self, obj): + db.session.delete(obj) + db.session.commit() + + class ReadWriteWorkspacedView(CreateWorkspacedMixin, UpdateWorkspacedMixin, + DeleteWorkspacedMixin, ReadOnlyWorkspacedView, GenericWorkspacedView): """A generic view with list, retrieve and create endpoints""" diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 37e25444242..1855e17e28d 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -1,4 +1,5 @@ import pytest +from sqlalchemy.orm.util import was_deleted from test_cases import factories from server.models import db, Workspace, Host @@ -124,3 +125,16 @@ def test_update_a_host_fails_with_missing_fields(self, test_client): "ip": "1.2.3.4", # Existing IP }) assert res.status_code == 400 + + def test_delete_a_host(self, test_client): + host = self.workspace.hosts[0] + res = test_client.delete(self.url(host)) + assert res.status_code == 204 # No content + assert was_deleted(host) + + def test_delete_host_from_other_workspace_fails(self, test_client, + second_workspace): + host = self.workspace.hosts[0] + res = test_client.delete(self.url(host, workspace=second_workspace)) + assert res.status_code == 404 # No content + assert not was_deleted(host) From 77a063cd56d6cbbb58ab34a0e09bbf3ceb17e3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 7 Sep 2017 15:05:05 -0300 Subject: [PATCH 0174/1506] Improve Host API tests Check that list doesn't show hosts of other workspace, use session instead of database.session and remove unused old comment --- test_cases/test_server_api.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 1855e17e28d..2e19d3ac62c 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -12,7 +12,7 @@ class TestHostAPI: @pytest.fixture(autouse=True) def load_workspace_with_hosts(self, database, session, workspace, host_factory): host_factory.create_batch(HOSTS_COUNT, workspace=workspace) - database.session.commit() + session.commit() assert workspace.id is not None assert workspace.hosts[0].id is not None self.workspace = workspace @@ -25,13 +25,17 @@ def url(self, host=None, workspace=None): url += str(host.id) return url - def test_list_retrieves_all_items(self, test_client): + def test_list_retrieves_all_items_from_workspace(self, test_client, + second_workspace, + session, + host_factory): + other_host = host_factory.create(workspace=second_workspace) + session.commit() res = test_client.get(self.url()) assert res.status_code == 200 assert len(res.json) == HOSTS_COUNT def test_retrieve_one_host(self, test_client, database): - # self.workspace = Workspace.query.first() host = self.workspace.hosts[0] assert host.id is not None res = test_client.get(self.url(host)) From 4afc8dfe044cabbbaf79e80086b16675e119ae66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 7 Sep 2017 15:09:29 -0300 Subject: [PATCH 0175/1506] Do sqlite transaction hacks only when SQListe is configured Otherwise, using postresql or other RDBMS will fail --- server/models.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/models.py b/server/models.py index 9f9a66ac2df..5772835a083 100644 --- a/server/models.py +++ b/server/models.py @@ -34,8 +34,6 @@ class SQLAlchemy(OriginalSQLAlchemy): and https://bitbucket.org/zzzeek/sqlalchemy/issues/3561/sqlite-nested-transactions-fail-with for furhter information""" - # TODO: only do this on sqlite, not on postgres! - def make_connector(self, app=None, bind=None): """Creates the connector for a given state and bind.""" return CustomEngineConnector(self, self.get_app(app), bind) @@ -53,17 +51,19 @@ def get_engine(self): # Call original metohd and register events rv = super(CustomEngineConnector, self).get_engine() - with self._lock: - @event.listens_for(rv, "connect") - def do_connect(dbapi_connection, connection_record): - # disable pysqlite's emitting of the BEGIN statement entirely. - # also stops it from emitting COMMIT before any DDL. - dbapi_connection.isolation_level = None - - @event.listens_for(rv, "begin") - def do_begin(conn): - # emit our own BEGIN - conn.execute("BEGIN") + if uri.startswith('sqlite://'): + with self._lock: + @event.listens_for(rv, "connect") + def do_connect(dbapi_connection, connection_record): + # disable pysqlite's emitting of the BEGIN statement + # entirely. also stops it from emitting COMMIT before any + # DDL. + dbapi_connection.isolation_level = None + + @event.listens_for(rv, "begin") + def do_begin(conn): + # emit our own BEGIN + conn.execute("BEGIN") return rv From b0ad9a1ecbaf69196a13643b12b18130d08c78f5 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 7 Sep 2017 15:32:46 -0300 Subject: [PATCH 0176/1506] When methodology was deleted some task were not deleted --- server/importer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/importer.py b/server/importer.py index baa57341396..25972964d7e 100644 --- a/server/importer.py +++ b/server/importer.py @@ -527,6 +527,10 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation except KeyError: logger.warn('Could not found methodology with id {0}'.format(document.get('group_id'))) return [] + except IndexError: + logger.warn('Could not find methodology {0} of task {1}'.format(document.get('group_id'), document.get('_id'))) + return [] + if len(couchdb_relational_map[document.get('group_id')]) > 1: logger.error('It was expected only one parent in methodology {0}'.format(document.get('_id'))) From 99862fda190f7c3ac391d008c573af52dbae2a5e Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 7 Sep 2017 16:55:19 -0300 Subject: [PATCH 0177/1506] Add new function get_object_from_couchdb and add Note importer --- server/importer.py | 50 ++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/server/importer.py b/server/importer.py index 25972964d7e..8ebaaa2bbe2 100644 --- a/server/importer.py +++ b/server/importer.py @@ -65,6 +65,7 @@ (1, 'Workspace'), (1, 'Reports'), (1, 'Communication'), + (1, 'Note'), (2, 'Service'), (2, 'Credential'), (2, 'Vulnerability'), @@ -75,6 +76,19 @@ (4, 'VulnerabilityWeb'), ] + +def get_object_from_couchdb(couchdb_id, workspace): + doc_url = 'http://{username}:{password}@{hostname}:{port}/{workspace_name}/{doc_id}'.format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace.name, + doc_id=couchdb_id + ) + return requests.get(doc_url).json() + + def get_children_from_couch(workspace, parent_couchdb_id, child_type): """ Performance for temporary views suck, so this method uploads a view and queries it instead @@ -452,15 +466,24 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class NoteImporter(object): - DOC_TYPE = 'Note' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): - note = Note() - note.name = document.get('name') - note.text = document.get('text', None) - note.description = document.get('description', None) - note.owned = document.get('owned', False) - yield note + couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) + parent_document = get_object_from_couchdb(couch_parent_id, workspace) + comment, created = get_or_create( + session, + Comment, + text='{0}\n{1}'.format(document.get('text', ''), document.get('description', '')), + workspace=workspace) + + get_or_create( + session, + CommentObject, + object_id=couchdb_relational_map[parent_document.get('_id')], + object_type=parent_document['type'], + comment=comment, + ) + yield comment class CredentialImporter(object): @@ -741,7 +764,7 @@ def run(self): 'high':'high', 'low': 'low', 'info': 'informational', - 'unclassified': 'unclassified', + 'unclassified': 'unclassified', } vuln_template, created = get_or_create(session, VulnerabilityTemplate, @@ -850,15 +873,8 @@ def verify_import_data(self, couchdb_relational_map, workspace): if missing_ids: logger.info('Downloading missing couchdb docs') for missing_id in tqdm(missing_ids): - doc_url = 'http://{username}:{password}@{hostname}:{port}/{workspace_name}/{doc_id}'.format( - username=server.config.couchdb.user, - password=server.config.couchdb.password, - hostname=server.config.couchdb.host, - port=server.config.couchdb.port, - workspace_name=workspace.name, - doc_id=missing_id - ) - not_imported_obj = requests.get(doc_url).json() + not_imported_obj = get_object_from_couchdb(missing_id, workspace) + if not_imported_obj['type'] == 'Interface': # we know that interface obj was not imported continue From bd8244f90486eca951a9b4cb352acfd90cd7d5c6 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 7 Sep 2017 16:55:33 -0300 Subject: [PATCH 0178/1506] Remove TODO --- server/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/models.py b/server/models.py index 59e652624f4..c2694f6b454 100644 --- a/server/models.py +++ b/server/models.py @@ -422,8 +422,6 @@ class PolicyViolation(db.Model): class Credential(db.Model): - # TODO: add unique constraint -> username, host o service y workspace - # TODO: add constraint host y service, uno o el otro __tablename__ = 'credential' id = Column(Integer, primary_key=True) username = Column(String(250), nullable=False) From fff9348960327f86aeae5e9bc6bb36ef2272d507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 7 Sep 2017 17:02:41 -0300 Subject: [PATCH 0179/1506] Add generic API tests --- test_cases/factories.py | 6 ++ test_cases/test_server_api.py | 163 ++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) diff --git a/test_cases/factories.py b/test_cases/factories.py index e6001554460..6001cc90b28 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -33,6 +33,12 @@ class Meta: class WorkspaceObjectFactory(FaradayFactory): workspace = factory.SubFactory(WorkspaceFactory) + @classmethod + def build_dict(cls, **kwargs): + ret = factory.build(dict, FACTORY_CLASS=cls) + del ret['workspace'] # It is passed in the URL, not in POST data + return ret + class HostFactory(WorkspaceObjectFactory): ip = factory.Faker('ipv4') diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 2e19d3ac62c..16599ee0374 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -142,3 +142,166 @@ def test_delete_host_from_other_workspace_fails(self, test_client, res = test_client.delete(self.url(host, workspace=second_workspace)) assert res.status_code == 404 # No content assert not was_deleted(host) + + +PREFIX = '/v2/' +OBJECT_COUNT = 5 + + +@pytest.mark.usefixtures('logged_user') +class GenericAPITest: + + model = None + factory = None + api_endpoint = None + pk_field = 'id' + unique_fields = [] + update_fields = [] + + @pytest.fixture(autouse=True) + def load_workspace_with_objects(self, database, session, workspace): + objects = self.factory.create_batch(OBJECT_COUNT, workspace=workspace) + self.first_object = objects[0] + session.commit() + assert workspace.id is not None + self.workspace = workspace + return workspace + + @pytest.fixture + def object_instance(self, session, workspace): + """An object instance with the correct workspace assigned, + saved in the database""" + obj = self.factory.create(workspace=workspace) + session.commit() + return obj + + def url(self, obj=None, workspace=None): + workspace = workspace or self.workspace + url = PREFIX + workspace.name + '/' + self.api_endpoint + '/' + if obj is not None: + url += str(obj.id) + return url + + +class ListTestsMixin: + + def test_list_retrieves_all_items_from_workspace(self, test_client, + second_workspace, + session): + self.factory.create(workspace=second_workspace) + session.commit() + res = test_client.get(self.url()) + assert res.status_code == 200 + assert len(res.json) == OBJECT_COUNT + + +class RetrieveTestsMixin: + + def test_retrieve_one_object(self, test_client): + res = test_client.get(self.url(self.first_object)) + assert res.status_code == 200 + assert isinstance(res.json, dict) + + def test_retrieve_fails_object_of_other_workspcae(self, + test_client, + session, + second_workspace): + res = test_client.get(self.url(self.first_object, second_workspace)) + assert res.status_code == 404 + + +class CreateTestsMixin: + + def test_create_succeeds(self, test_client): + res = test_client.post(self.url(), + data=self.factory.build_dict()) + assert res.status_code == 201 + assert self.model.query.count() == OBJECT_COUNT + 1 + object_id = res.json['id'] + obj = self.model.query.get(object_id) + assert obj.workspace == self.workspace + + def test_create_fails_with_empty_dict(self, test_client): + res = test_client.post(self.url(), data={}) + assert res.status_code == 400 + + def test_create_fails_with_existing(self, session, test_client): + for unique_field in self.unique_fields: + data = self.factory.build_dict() + data[unique_field] = getattr(self.first_object, unique_field) + res = test_client.post(self.url(), data=data) + assert res.status_code == 400 + assert self.model.query.count() == OBJECT_COUNT + + def test_create_with_existing_in_other_workspace(self, test_client, + session, + second_workspace): + unique_field = self.unique_fields[0] + other_object = self.factory.create(workspace=second_workspace) + session.commit() + + data = self.factory.build_dict() + data[unique_field] = getattr(other_object, unique_field) + res = test_client.post(self.url(), data=data) + assert res.status_code == 201 + # It should create two hosts, one for each workspace + assert self.model.query.count() == OBJECT_COUNT + 2 + + +class UpdateTestsMixin: + + def test_update_a_host(self, test_client): + host = self.workspace.hosts[0] + res = test_client.put(self.url(self.first_object), + data=self.factory.build_dict()) + assert res.status_code == 200 + assert self.model.query.count() == OBJECT_COUNT + + def test_update_fails_with_existing(self, test_client, session): + for unique_field in self.unique_fields: + data = self.factory.build_dict() + data[unique_field] = getattr(self.first_object, unique_field) + res = test_client.put(self.url(self.workspace.hosts[1]), data=data) + assert res.status_code == 400 + assert self.model.query.count() == OBJECT_COUNT + + def test_update_a_host_fails_with_empty_dict(self, test_client): + """To do this the user should use a PATCH request""" + host = self.workspace.hosts[0] + res = test_client.put(self.url(host), data={}) + assert res.status_code == 400 + + +class DeleteTestsMixin: + + def test_delete(self, test_client): + res = test_client.delete(self.url(self.first_object)) + assert res.status_code == 204 # No content + assert was_deleted(self.first_object) + assert self.model.query.count() == OBJECT_COUNT - 1 + + def test_delete_from_other_workspace_fails(self, test_client, + second_workspace): + res = test_client.delete(self.url(self.first_object, + workspace=second_workspace)) + assert res.status_code == 404 # No content + assert not was_deleted(self.first_object) + assert self.model.query.count() == OBJECT_COUNT + + +class ReadWriteTestsMixin(ListTestsMixin, + RetrieveTestsMixin, + CreateTestsMixin, + UpdateTestsMixin, + DeleteTestsMixin): + pass + + +class TestHostAPIGeneric(ReadWriteTestsMixin, + GenericAPITest): + model = Host + factory = factories.HostFactory + api_endpoint = 'hosts' + unique_fields = ['ip'] + update_fields = ['ip', 'description', 'os'] + From 492d53bd860854112804caff02c9e95dc757ffa7 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 7 Sep 2017 17:12:09 -0300 Subject: [PATCH 0180/1506] Fix a bug on Service query --- server/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index 8ebaaa2bbe2..a3e5fc2f7d4 100644 --- a/server/importer.py +++ b/server/importer.py @@ -496,7 +496,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation parents = session.query(Host).filter(Host.id.in_(parent_ids)).all() if level == 4: parent_ids = couchdb_relational_map['.'.join(document['_id'].split('.')[:3])] - parents = session.query(Service).filter(Host.id.in_(parent_ids)).all() + parents = session.query(Service).filter(Service.id.in_(parent_ids)).all() if not parents: raise Exception('Missing host or service for credential {0}'.format(document['_id'])) for parent in parents: From 8ff9a7b4b2cc50670e7d86422799cfd110bd2eef Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 8 Sep 2017 13:16:38 -0300 Subject: [PATCH 0181/1506] Add required field on get or create vulns --- server/importer.py | 70 +++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/server/importer.py b/server/importer.py index a3e5fc2f7d4..7f48939aaab 100644 --- a/server/importer.py +++ b/server/importer.py @@ -347,23 +347,42 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation if not couch_parent_id: couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) parent_ids = couchdb_relational_map[couch_parent_id] + mapped_severity = { + 'med': 'medium', + 'critical': 'critical', + 'high': 'high', + 'low': 'low', + 'info': 'informational', + 'unclassified': 'unclassified', + } + severity = mapped_severity[document.get('severity')] for parent_id in parent_ids: if level == 2: parent = session.query(Host).filter_by(id=parent_id).first() if level == 4: parent = session.query(Service).filter_by(id=parent_id).first() if document['type'] == 'VulnerabilityWeb': + method = document.get('method') + path = document.get('path') + pname = document.get('pname') + website = document.get('website') vulnerability, created = get_or_create( session, VulnerabilityWeb, name=document.get('name'), - description=document.get('desc'), + severity=severity, service_id=parent.id, + method=method, + parameter_name=pname, + path=path, + website=website, + workspace=workspace, ) if document['type'] == 'Vulnerability': vuln_params = { 'name': document.get('name'), - 'description': document.get('desc') + 'severity': severity, + 'workspace': workspace, } if type(parent) == Host: vuln_params.update({'host_id': parent.id}) @@ -374,20 +393,12 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation Vulnerability, **vuln_params ) - + vulnerability.description = document.get('desc'), vulnerability.confirmed = document.get('confirmed', False) or False vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') vulnerability.resolution = document.get('resolution') - mapped_severity = { - 'med': 'medium', - 'critical': 'critical', - 'high':'high', - 'low': 'low', - 'info': 'informational', - 'unclassified': 'unclassified', - } - vulnerability.severity = mapped_severity[document.get('severity')] + vulnerability.owned = document.get('owned', False) #vulnerability.attachments = json.dumps(document.get('_attachments', {})) vulnerability.impact_accountability = document.get('impact', {}).get('accountability') @@ -395,13 +406,12 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.impact_confidentiality = document.get('impact', {}).get('confidentiality') vulnerability.impact_integrity = document.get('impact', {}).get('integrity') if document['type'] == 'VulnerabilityWeb': - vulnerability.method = document.get('method') - vulnerability.path = document.get('path') - vulnerability.pname = document.get('pname') + + vulnerability.query = document.get('query') vulnerability.request = document.get('request') vulnerability.response = document.get('response') - vulnerability.website = document.get('website') + params = document.get('params', u'') if isinstance(params, (list, tuple)): vulnerability.parameters = (u' '.join(params)).strip() @@ -413,7 +423,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation } status = status_map[document.get('status', 'opened')] vulnerability.status = status - vulnerability.workspace = workspace self.add_references(document, vulnerability, workspace) self.add_policy_violations(document, vulnerability, workspace) @@ -855,7 +864,28 @@ def get_objs(self, host, obj_type, level): return r.json() - def verify_import_data(self, couchdb_relational_map, workspace): + def verify_host_vulns_count_is_correct(self, couchdb_relational_map, couchdb_relational_map_by_type, workspace): + hosts = session.query(Host).filter_by(workspace=workspace) + for host in hosts: + parent_couchdb_id = None + for couchdb_id, relational_ids in couchdb_relational_map_by_type.items(): + for obj_data in relational_ids: + if obj_data['type'] == 'Host' and host.id == obj_data['id']: + parent_couchdb_id = couchdb_id + break + if parent_couchdb_id: + break + if not parent_couchdb_id: + raise Exception('Could not found couchdb id!') + vulns = get_children_from_couch(workspace, parent_couchdb_id, 'Vulnerability') + interfaces = get_children_from_couch(workspace, parent_couchdb_id, 'Interface') + for interface in interfaces: + interface = interface['value'] + vulns += get_children_from_couch(workspace, interface.get('_id'), 'Vulnerability') + assert len(vulns) == len(host.vulnerabilities) + + def verify_import_data(self, couchdb_relational_map, couchdb_relational_map_by_type, workspace): + self.verify_host_vulns_count_is_correct(couchdb_relational_map, couchdb_relational_map_by_type, workspace) all_docs_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_all_docs?include_docs=true".format( username=server.config.couchdb.user, password=server.config.couchdb.password, @@ -906,6 +936,7 @@ def import_workspace_into_database(self, workspace_name): # for the desired obj. obj_types = OBJ_TYPES couchdb_relational_map = defaultdict(list) + couchdb_relational_map_by_type = defaultdict(list) for level, obj_type in obj_types: obj_importer = faraday_importer.get_importer_from_document(obj_type)() objs_dict = self.get_objs(couch_url, obj_type, level) @@ -919,6 +950,7 @@ def import_workspace_into_database(self, workspace_name): if not new_obj: continue session.commit() + couchdb_relational_map_by_type[couchdb_id].append({'type': obj_type, 'id': new_obj.id}) couchdb_relational_map[couchdb_id].append(new_obj.id) - self.verify_import_data(couchdb_relational_map, workspace) + self.verify_import_data(couchdb_relational_map, couchdb_relational_map_by_type, workspace) return created From 9d21f19fac4e2ea3d575833b468bf5359e27412c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 8 Sep 2017 15:08:56 -0300 Subject: [PATCH 0182/1506] Make Service.status field not nullable It must be set and have one of the three allowed values --- server/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/models.py b/server/models.py index 7bdc2a4c9c2..bfaf785cefd 100644 --- a/server/models.py +++ b/server/models.py @@ -137,7 +137,7 @@ class Service(db.Model): owned = Column(Boolean, nullable=False, default=False) protocol = Column(Text, nullable=False) - status = Column(Enum(*STATUSES, name='service_statuses'), nullable=True) + status = Column(Enum(*STATUSES, name='service_statuses'), nullable=False) version = Column(Text, nullable=True) banner = Column(Text, nullable=True) @@ -791,4 +791,4 @@ class ExecutiveReport(db.Model): VulnerabilityGeneric.workspace_id, VulnerabilityCode.source_code_id, name='uix_vulnerability' -) \ No newline at end of file +) From f1dee4a376365e7caebfd69ea152823424091a2f Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 8 Sep 2017 15:41:10 -0300 Subject: [PATCH 0183/1506] Fix importer to avoid Design Docs The importer was only ignoring some particular design docs. --- server/importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index 7f48939aaab..a0ba813f5f3 100644 --- a/server/importer.py +++ b/server/importer.py @@ -2,6 +2,7 @@ # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information import os +import re import sys import json import datetime @@ -897,8 +898,7 @@ def verify_import_data(self, couchdb_relational_map, couchdb_relational_map_by_t if len(all_ids) != len(couchdb_relational_map.keys()): missing_objs_filename = os.path.join(os.path.expanduser('~/.faraday'), 'logs', 'import_missing_objects_{0}.json'.format(workspace.name)) missing_ids = set(all_ids) - set(couchdb_relational_map.keys()) - missing_ids = missing_ids - set([u'_design/commands', u'_design/hosts', u'_design/comms', u'_design/tags', u'_design/vulns', u'_design/utils', u'_design/importer', u'_design/auth', u'_design/mapper', u'_design/services', u'_design/interfaces', u'_design/changes', u'_design/reports', -]) + missing_ids = set([x for x in missing_ids if not re.match(r'^\_design', x)]) objs_diff = [] if missing_ids: logger.info('Downloading missing couchdb docs') From 58e6828b4242e21a47f8d1495764813ad06567b6 Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 8 Sep 2017 15:42:18 -0300 Subject: [PATCH 0184/1506] Fix typo --- server/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index a0ba813f5f3..17a916c34fe 100644 --- a/server/importer.py +++ b/server/importer.py @@ -771,7 +771,7 @@ def run(self): mapped_exploitation = { 'critical': 'critical', 'med': 'medium', - 'high':'high', + 'high': 'high', 'low': 'low', 'info': 'informational', 'unclassified': 'unclassified', From 4bd0871c3549c1d26b415ec6ce2d74c55e9647b1 Mon Sep 17 00:00:00 2001 From: micabot Date: Fri, 8 Sep 2017 15:43:23 -0300 Subject: [PATCH 0185/1506] Fix Licenses importer If the Licenses database was not present in CouchDB, the script failed. Fixed it to check the existence of this DB before trying to import it. --- server/importer.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index 17a916c34fe..c2a21dd0e5e 100644 --- a/server/importer.py +++ b/server/importer.py @@ -799,7 +799,17 @@ def run(self): port=server.config.couchdb.port, path='faraday_licenses/_all_docs?include_docs=true' ) - licenses = requests.get(cwe_url).json()['rows'] + + if not requests.head(cwe_url).ok: + logger.info('No Licenses database found, nothing to see here, move along!') + return + + try: + licenses = requests.get(cwe_url).json()['rows'] + except requests.exceptions.RequestException as e: + logger.warn(e) + return + for license in licenses: document = license['doc'] From fbba0e00fb40bfa6df06dec33c27855e65dae057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 8 Sep 2017 15:47:34 -0300 Subject: [PATCH 0186/1506] Add nested services endpoint //hosts//services/ Also improve service and host factories and schemas --- server/api/modules/hosts.py | 17 ++++++++++++++++- test_cases/factories.py | 7 +++++-- test_cases/test_server_api.py | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index 70cc144cdc8..d16bf98229d 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -4,6 +4,7 @@ import flask from flask import Blueprint +from flask_classful import route from marshmallow import Schema, fields from server.utils.logger import get_logger @@ -17,18 +18,32 @@ class HostSchema(Schema): - id = fields.String(required=True, dump_only=True) + id = fields.Integer(required=True, dump_only=True) ip = fields.String(required=True) description = fields.String(required=True) os = fields.String() +class ServiceSchema(Schema): + id = fields.Integer(required=True, dump_only=True) + name = fields.String(required=True) + description = fields.String(required=False) + port = fields.Integer(required=True) + protocol = fields.String(required=True) + status = fields.String(required=True) + + class HostsView(ReadWriteWorkspacedView): route_base = 'hosts' model_class = Host schema_class = HostSchema unique_fields = ['ip'] + @route('//services/') + def service_list(self, workspace_name, host_id): + services = self._get_object(workspace_name, host_id).services + return ServiceSchema(many=True).dump(services) + HostsView.register(host_api) diff --git a/test_cases/factories.py b/test_cases/factories.py index 6001cc90b28..85c94b8ece9 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -1,7 +1,8 @@ import factory from factory.fuzzy import ( + FuzzyChoice, + FuzzyInteger, FuzzyText, - FuzzyChoice ) from server.models import ( db, @@ -61,8 +62,10 @@ class Meta: class ServiceFactory(WorkspaceObjectFactory): name = FuzzyText() description = FuzzyText() - ports = FuzzyChoice(['443', '80', '22']) + port = FuzzyInteger(1, 65535) + protocol = FuzzyChoice(['tcp', 'udp']) host = factory.SubFactory(HostFactory) + status = FuzzyChoice(Service.STATUSES) class Meta: model = Service diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 16599ee0374..19c80af5ed4 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -11,7 +11,9 @@ class TestHostAPI: @pytest.fixture(autouse=True) def load_workspace_with_hosts(self, database, session, workspace, host_factory): - host_factory.create_batch(HOSTS_COUNT, workspace=workspace) + self.hosts = host_factory.create_batch(HOSTS_COUNT, + workspace=workspace) + self.first_host = self.hosts[0] session.commit() assert workspace.id is not None assert workspace.hosts[0].id is not None @@ -25,6 +27,9 @@ def url(self, host=None, workspace=None): url += str(host.id) return url + def services_url(self, host, workspace=None): + return self.url(host, workspace) + '/services/' + def test_list_retrieves_all_items_from_workspace(self, test_client, second_workspace, session, @@ -143,6 +148,32 @@ def test_delete_host_from_other_workspace_fails(self, test_client, assert res.status_code == 404 # No content assert not was_deleted(host) + def test_get_host_services(self, test_client, session, + second_workspace, + service_factory): + SERVICE_COUNT = 10 + + # Create the services that must be shown + real = service_factory.create_batch( + SERVICE_COUNT, + host=self.first_host, + workspace=self.first_host.workspace) + + # Create a service of other host, must not be shown + other_host = service_factory.create( + host=self.hosts[1], + workspace=self.hosts[1].workspace) + + session.commit() + ids_expected = {host.id for host in real} + + res = test_client.get(self.services_url(self.first_host)) + assert res.status_code == 200 + ids_returned = {host['id'] for host in res.json} + assert other_host.id not in ids_returned + assert ids_expected == ids_returned + + PREFIX = '/v2/' OBJECT_COUNT = 5 From d965796b7f31bcd1d3b9517003924aa2464b368e Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 8 Sep 2017 18:32:42 -0300 Subject: [PATCH 0187/1506] Add metadata model --- server/models.py | 63 +++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/server/models.py b/server/models.py index c2694f6b454..67ffa01491f 100644 --- a/server/models.py +++ b/server/models.py @@ -1,6 +1,9 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information +from datetime import datetime + +import pytz from sqlalchemy import ( Boolean, CheckConstraint, @@ -36,6 +39,22 @@ class DatabaseMetadata(db.Model): value = Column(String(250), nullable=False) +class Metadata(db.Model): + + __abstract__ = True + + @declared_attr + def creator_id(cls): + return Column(Integer, ForeignKey('user.id'), nullable=True) + + @declared_attr + def creator(cls): + return relationship('User', foreign_keys=[cls.creator_id]) + + create_date = Column(DateTime, default=datetime.utcnow) + update_date = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + class EntityMetadata(db.Model): __tablename__ = 'metadata' __table_args__ = ( @@ -57,7 +76,7 @@ class EntityMetadata(db.Model): document_type = Column(String(250)) -class SourceCode(db.Model): +class SourceCode(Metadata): __tablename__ = 'source_code' id = Column(Integer, primary_key=True) filename = Column(Text, nullable=False) @@ -70,7 +89,7 @@ class SourceCode(db.Model): ) -class Host(db.Model): +class Host(Metadata): __tablename__ = 'host' id = Column(Integer, primary_key=True) ip = Column(Text, nullable=False) # IP v4 or v6 @@ -105,7 +124,7 @@ class Host(db.Model): ) -class Hostname(db.Model): +class Hostname(Metadata): __tablename__ = 'hostname' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) @@ -123,7 +142,7 @@ class Hostname(db.Model): ) -class Service(db.Model): +class Service(Metadata): STATUSES = [ 'open', 'closed', @@ -165,7 +184,7 @@ class Service(db.Model): ) -class VulnerabilityABC(db.Model): +class VulnerabilityABC(Metadata): # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien EASE_OF_RESOLUTIONS = [ 'trivial', @@ -306,7 +325,7 @@ class VulnerabilityCode(VulnerabilityGeneric): } -class ReferenceTemplate(db.Model): +class ReferenceTemplate(Metadata): __tablename__ = 'reference_template' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) @@ -327,7 +346,7 @@ class ReferenceTemplate(db.Model): ) -class Reference(db.Model): +class Reference(Metadata): __tablename__ = 'reference' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) @@ -360,7 +379,7 @@ class Reference(db.Model): ) -class PolicyViolationTemplate(db.Model): +class PolicyViolationTemplate(Metadata): __tablename__ = 'policy_violation_template' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) @@ -384,7 +403,7 @@ class PolicyViolationTemplate(db.Model): ) -class PolicyViolation(db.Model): +class PolicyViolation(Metadata): __tablename__ = 'policy_violation' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) @@ -421,7 +440,7 @@ class PolicyViolation(db.Model): ) -class Credential(db.Model): +class Credential(Metadata): __tablename__ = 'credential' id = Column(Integer, primary_key=True) username = Column(String(250), nullable=False) @@ -469,7 +488,7 @@ class Credential(db.Model): ) -class Command(db.Model): +class Command(Metadata): __tablename__ = 'command' id = Column(Integer, primary_key=True) command = Column(Text(), nullable=False) @@ -498,7 +517,7 @@ class Command(db.Model): ) -class Workspace(db.Model): +class Workspace(Metadata): __tablename__ = 'workspace' id = Column(Integer, primary_key=True) # TODO: change nullable=True for appropriate fields @@ -527,14 +546,14 @@ class RolesUsers(db.Model): role_id = Column('role_id', Integer(), ForeignKey('role.id')) -class Role(db.Model, RoleMixin): +class Role(Metadata, RoleMixin): __tablename__ = 'role' id = Column(Integer(), primary_key=True) name = Column(String(80), unique=True) description = Column(String(255), nullable=True) -class User(db.Model, UserMixin): +class User(Metadata, UserMixin): __tablename__ = 'user' id = Column(Integer, primary_key=True) username = Column(String(255), unique=True, nullable=False) @@ -576,14 +595,14 @@ def __repr__(self): self.username) -class MethodologyTemplate(db.Model): +class MethodologyTemplate(Metadata): # TODO: reset template_id in methodologies when deleting meth template __tablename__ = 'methodology_template' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False) -class Methodology(db.Model): +class Methodology(Metadata): # TODO: add unique constraint -> name, workspace __tablename__ = 'methodology' id = Column(Integer, primary_key=True) @@ -614,7 +633,7 @@ class Methodology(db.Model): workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) -class TaskABC(db.Model): +class TaskABC(Metadata): __abstract__ = True id = Column(Integer, primary_key=True) @@ -664,8 +683,8 @@ class Task(TaskABC): entity_metadata = relationship(EntityMetadata, uselist=False, cascade="all, delete-orphan", single_parent=True) entity_metadata_id = Column(Integer, ForeignKey(EntityMetadata.id), index=True) - assigned_to = relationship('User', backref='assigned_tasks') assigned_to_id = Column(Integer, ForeignKey('user.id'), nullable=True) + assigned_to = relationship('User', backref='assigned_tasks', foreign_keys=[assigned_to_id]) methodology_id = Column( Integer, @@ -691,7 +710,7 @@ class Task(TaskABC): # ) -class License(db.Model): +class License(Metadata): __tablename__ = 'license' id = Column(Integer, primary_key=True) product = Column(Text, nullable=False) @@ -706,7 +725,7 @@ class License(db.Model): ) -class Tag(db.Model): +class Tag(Metadata): __tablename__ = 'tag' id = Column(Integer, primary_key=True) name = Column(Text, nullable=False, unique=True) @@ -735,7 +754,7 @@ class CommentObject(db.Model): comment_id = Column(Integer, ForeignKey('comment.id'), index=True) -class Comment(db.Model): +class Comment(Metadata): __tablename__ = 'comment' id = Column(Integer, primary_key=True) @@ -752,7 +771,7 @@ class Comment(db.Model): workspace = relationship('Workspace', foreign_keys=[workspace_id]) -class ExecutiveReport(db.Model): +class ExecutiveReport(Metadata): STATUSES = [ 'created', 'error', From b2499d611e75a5cc760ff42eade9856f9c864849 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 8 Sep 2017 19:00:03 -0300 Subject: [PATCH 0188/1506] Add nullable to creator_id --- server/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index 67ffa01491f..42bc4edfa64 100644 --- a/server/models.py +++ b/server/models.py @@ -45,7 +45,7 @@ class Metadata(db.Model): @declared_attr def creator_id(cls): - return Column(Integer, ForeignKey('user.id'), nullable=True) + return Column(Integer, ForeignKey('user.id'), nullable=False) @declared_attr def creator(cls): From d269adc87d7a698d72a04ed46482827580eeda48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 8 Sep 2017 19:59:24 -0300 Subject: [PATCH 0189/1506] Add service_count field to Host Optimized version via undeferring an originally deferred column_property --- server/api/modules/hosts.py | 8 ++++++++ server/models.py | 9 +++++++++ test_cases/test_server_api.py | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index d16bf98229d..880e027d8ec 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -6,6 +6,7 @@ from flask import Blueprint from flask_classful import route from marshmallow import Schema, fields +from sqlalchemy.orm import undefer from server.utils.logger import get_logger from server.utils.web import gzipped, validate_workspace,\ @@ -22,6 +23,7 @@ class HostSchema(Schema): ip = fields.String(required=True) description = fields.String(required=True) os = fields.String() + service_count = fields.Integer() class ServiceSchema(Schema): @@ -44,6 +46,12 @@ def service_list(self, workspace_name, host_id): services = self._get_object(workspace_name, host_id).services return ServiceSchema(many=True).dump(services) + def _get_base_query(self, workspace_name): + """Get services_count in one query and not deferred, that doe + one query per host""" + original = super(HostsView, self)._get_base_query(workspace_name) + return original.options(undefer(Host.service_count)) + HostsView.register(host_api) diff --git a/server/models.py b/server/models.py index 5772835a083..42f31bbdf44 100644 --- a/server/models.py +++ b/server/models.py @@ -15,6 +15,9 @@ event ) from sqlalchemy.orm import relationship, backref +from sqlalchemy.sql import select +from sqlalchemy import func +from sqlalchemy.orm import column_property from sqlalchemy.ext.declarative import declared_attr from flask_sqlalchemy import ( SQLAlchemy as OriginalSQLAlchemy, @@ -195,6 +198,12 @@ class Service(db.Model): foreign_keys=[workspace_id] ) +# TODO: Move this to Host definition. I need a way to reference Service before +# it is declared +Host.service_count = column_property((select([func.count(Service.id)]). + where(Service.host_id == Host.id)#.label('service_count') + ), deferred=True) + class VulnerabilityABC(db.Model): # TODO: add unique constraint to -> name, description, severity, parent, method, pname, path, website, workspace diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index 19c80af5ed4..be8ab9b1a19 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -5,6 +5,7 @@ PREFIX = '/v2/' HOSTS_COUNT = 5 +SERVICE_COUNT = [10, 5] # 10 services to the first host, 5 to the second @pytest.mark.usefixtures('database', 'logged_user') class TestHostAPI: @@ -20,6 +21,20 @@ def load_workspace_with_hosts(self, database, session, workspace, host_factory): self.workspace = workspace return workspace + @pytest.fixture + def host_services(self, session, service_factory): + """ + Add some services to the first len(SERVICE_COUNT) hosts. + + Return a dictionary mapping hosts to a list of services + """ + ret = {} + for (count, host) in zip(SERVICE_COUNT, self.hosts): + ret[host] = service_factory.create_batch( + count, host=host, workspace=host.workspace) + session.commit() + return ret + def url(self, host=None, workspace=None): workspace = workspace or self.workspace url = PREFIX + workspace.name + '/hosts/' @@ -149,13 +164,10 @@ def test_delete_host_from_other_workspace_fails(self, test_client, assert not was_deleted(host) def test_get_host_services(self, test_client, session, - second_workspace, service_factory): - SERVICE_COUNT = 10 - # Create the services that must be shown real = service_factory.create_batch( - SERVICE_COUNT, + SERVICE_COUNT[0], host=self.first_host, workspace=self.first_host.workspace) @@ -173,6 +185,20 @@ def test_get_host_services(self, test_client, session, assert other_host.id not in ids_returned assert ids_expected == ids_returned + def test_retrieve_shows_service_count(self, test_client, host_services): + for (host, services) in host_services.items(): + res = test_client.get(self.url(host)) + assert res.json['service_count'] == len(services) + + def test_index_shows_service_count(self, test_client, host_services): + ids_map = {host.id: services + for (host, services) in host_services.items()} + res = test_client.get(self.url()) + assert len(res.json) >= len(ids_map) # Some hosts can have no services + for host in res.json: + if host['id'] in ids_map: + assert host['service_count'] == len(ids_map[host['id']]) + PREFIX = '/v2/' From 0717ba12951044e6dd88ecbf7a579fed44ff93d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 11 Sep 2017 15:22:15 -0300 Subject: [PATCH 0190/1506] Require ids to be numeric by default Otherwise, postgresql query will fail when comparing numeric fields with strings. This behavior doesn't happen in SQLite --- server/api/base.py | 8 ++++++++ test_cases/test_server_api.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/server/api/base.py b/server/api/base.py index 5020b03b530..eb16fd7abfb 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -35,6 +35,7 @@ class GenericWorkspacedView(FlaskView): base_args = ['workspace_name'] # Required to prevent double usage of representations = {'application/json': output_json} lookup_field = 'id' + lookup_field_type = int unique_fields = [] # Fields unique together with workspace_id @classmethod @@ -72,6 +73,7 @@ def _get_base_query(self, workspace_name): .filter(Workspace.id==self._get_workspace(workspace_name).id) def _get_object(self, workspace_name, object_id): + self._validate_object_id(object_id) try: obj = self._get_base_query(workspace_name).filter( self._get_lookup_field() == object_id).one() @@ -86,6 +88,12 @@ def _parse_data(self, schema, request, *args, **kwargs): return FlaskParser().parse(schema, request, locations=('json',), *args, **kwargs) + def _validate_object_id(self, object_id): + try: + self.lookup_field_type(object_id) + except ValueError: + flask.abort(404, 'Invalid format of lookup field') + def _validate_uniqueness(self, obj, object_id=None): assert obj.workspace is not None, "Object must have a " \ "workspace attribute set to call _validate_uniqueness" diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index be8ab9b1a19..bedf2cab962 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -1,3 +1,4 @@ +#-*- coding: utf8 -*- import pytest from sqlalchemy.orm.util import was_deleted from test_cases import factories @@ -236,7 +237,9 @@ def url(self, obj=None, workspace=None): workspace = workspace or self.workspace url = PREFIX + workspace.name + '/' + self.api_endpoint + '/' if obj is not None: - url += str(obj.id) + id_ = unicode(obj.id) if isinstance( + obj, self.model) else unicode(obj) + url += id_ return url @@ -266,6 +269,13 @@ def test_retrieve_fails_object_of_other_workspcae(self, res = test_client.get(self.url(self.first_object, second_workspace)) assert res.status_code == 404 + @pytest.mark.parametrize('object_id', [123, -1, 'xxx', u'áá']) + def test_404_when_retrieving_unexistent_object(self, test_client, + object_id): + url = self.url(object_id) + res = test_client.get(url) + assert res.status_code == 404 + class CreateTestsMixin: From 1ae42310451c82e6e61edb14e46db017ae72aba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 11 Sep 2017 18:00:52 -0300 Subject: [PATCH 0191/1506] Upgrade to flask classful v0.14.0-flask-c - Install dependency from github instead of from PyPI (it wasn't published yet) - Add trailing slashes to endpoints in test cases(otherwise they would fail because of 301 redirect status codes) - Remove fix for https://github.com/teracyhq/flask-classful/issues/50 since it was fixed on the package --- requirements_server.txt | 2 +- server/api/base.py | 16 ---------------- test_cases/test_server_api.py | 6 +++--- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/requirements_server.txt b/requirements_server.txt index 7c9bacffd8a..371a6f8d32d 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -12,6 +12,6 @@ Flask-Security==3.0.0 bcrypt Flask-SQLAlchemy==2.2 Flask-Script==2.0.5 -flask-classful +https://github.com/teracyhq/flask-classful/archive/develop.zip marshmallow webargs diff --git a/server/api/base.py b/server/api/base.py index eb16fd7abfb..a4a7c4e74af 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -38,22 +38,6 @@ class GenericWorkspacedView(FlaskView): lookup_field_type = int unique_fields = [] # Fields unique together with workspace_id - @classmethod - def get_route_base(cls): - """Fix issue with base_args overriding - - See https://github.com/teracyhq/flask-classful/issues/50 for - more information""" - - if cls.route_base is not None: - route_base = cls.route_base - base_rule = parse_rule(route_base) - cls.base_args += [r[2] for r in base_rule] - else: - route_base = cls.default_route_base() - - return route_base.strip("/") - def _get_schema_class(self): assert self.schema_class is not None, "You must define schema_class" return self.schema_class diff --git a/test_cases/test_server_api.py b/test_cases/test_server_api.py index bedf2cab962..5491676931f 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_server_api.py @@ -40,11 +40,11 @@ def url(self, host=None, workspace=None): workspace = workspace or self.workspace url = PREFIX + workspace.name + '/hosts/' if host is not None: - url += str(host.id) + url += str(host.id) + '/' return url def services_url(self, host, workspace=None): - return self.url(host, workspace) + '/services/' + return self.url(host, workspace) + 'services/' def test_list_retrieves_all_items_from_workspace(self, test_client, second_workspace, @@ -239,7 +239,7 @@ def url(self, obj=None, workspace=None): if obj is not None: id_ = unicode(obj.id) if isinstance( obj, self.model) else unicode(obj) - url += id_ + url += id_ + u'/' return url From ce2a62fc57a30e575abf37bbd755105c46560b38 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 12 Sep 2017 11:53:11 -0300 Subject: [PATCH 0192/1506] Fix a bug in the model. --- server/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index 42bc4edfa64..232e4c1057e 100644 --- a/server/models.py +++ b/server/models.py @@ -553,7 +553,7 @@ class Role(Metadata, RoleMixin): description = Column(String(255), nullable=True) -class User(Metadata, UserMixin): +class User(db.Model, UserMixin): __tablename__ = 'user' id = Column(Integer, primary_key=True) username = Column(String(255), unique=True, nullable=False) From 99b70dcf12c85e5d976f6688bd4cf2247e556696 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 12 Sep 2017 11:53:27 -0300 Subject: [PATCH 0193/1506] Update factories to the new model --- test_cases/factories.py | 46 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/test_cases/factories.py b/test_cases/factories.py index 195ba120cc4..7513492f062 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -6,10 +6,10 @@ from pytest_factoryboy import register from server.models import ( db, + User, Host, Command, Service, - Interface, Workspace, Credential, Vulnerability, @@ -22,9 +22,18 @@ class FaradayFactory(factory.alchemy.SQLAlchemyModelFactory): id = factory.Sequence(lambda n: n) +class UserFactory(FaradayFactory): + + username = FuzzyText() + + class Meta: + model = User + sqlalchemy_session = db.session + class WorkspaceFactory(FaradayFactory): name = FuzzyText() + creator = factory.SubFactory(UserFactory) class Meta: model = Workspace @@ -32,9 +41,11 @@ class Meta: class HostFactory(FaradayFactory): - name = FuzzyText() + ip = FuzzyText() description = FuzzyText() os = FuzzyChoice(['Linux', 'Windows', 'OSX', 'Android', 'iOS']) + workspace = factory.SubFactory(WorkspaceFactory) + creator = factory.SubFactory(UserFactory) class Meta: model = Host @@ -49,23 +60,14 @@ class Meta: sqlalchemy_session = db.session -class InterfaceFactory(FaradayFactory): - name = FuzzyText() - description = FuzzyText() - mac = FuzzyText() - host = factory.SubFactory(HostFactory) - - class Meta: - model = Interface - sqlalchemy_session = db.session - - class ServiceFactory(FaradayFactory): name = FuzzyText() description = FuzzyText() - ports = FuzzyChoice(['443', '80', '22']) - interface = factory.SubFactory(InterfaceFactory) + port = FuzzyChoice(['443', '80', '22']) + protocol = FuzzyChoice(['TCP', 'UDP']) host = factory.SubFactory(HostFactory) + workspace = factory.SubFactory(WorkspaceFactory) + creator = factory.SubFactory(UserFactory) class Meta: model = Service @@ -76,14 +78,11 @@ class VulnerabilityFactory(FaradayFactory): name = FuzzyText() description = FuzzyText() - host = factory.SubFactory(HostFactory) - entity_metadata = factory.SubFactory(EntityMetadataFactory) - service = factory.SubFactory(ServiceFactory) + # host = factory.SubFactory(HostFactory) + # service = factory.SubFactory(ServiceFactory) workspace = factory.SubFactory(WorkspaceFactory) - vuln_type = FuzzyChoice(['Vulnerability', 'VulnerabilityWeb']) - attachments = '[]' - policyviolations = '[]' - refs = '[]' + creator = factory.SubFactory(UserFactory) + severity = FuzzyChoice(['critical', 'high']) class Meta: model = Vulnerability @@ -106,10 +105,9 @@ class Meta: model = Command sqlalchemy_session = db.session - +register(UserFactory) register(WorkspaceFactory) register(HostFactory) register(ServiceFactory) -register(InterfaceFactory) register(VulnerabilityFactory) register(CredentialFactory) From 08ac9bbc828c750851c6894371745baed1d587a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Sep 2017 15:00:29 -0300 Subject: [PATCH 0194/1506] Move SQLALCHEMY_ECHO commented code It is useful to have printed queries when running the server. Now it was only enabled when running the server (and with the line uncommented) --- server/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app.py b/server/app.py index d243f2798c4..2f4778817fc 100644 --- a/server/app.py +++ b/server/app.py @@ -26,8 +26,8 @@ def create_app(db_connection_string=None, testing=None): app.config['SECURITY_POST_LOGIN_VIEW'] = '/_api/session' app.config['SECURITY_POST_LOGOUT_VIEW'] = '/_api/login' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + # app.config['SQLALCHEMY_ECHO'] = True if testing: - # app.config['SQLALCHEMY_ECHO'] = True app.config['TESTING'] = testing try: app.config['SQLALCHEMY_DATABASE_URI'] = db_connection_string or server.config.database.connection_string.strip("'") From 78d33ab4a93de74845f9cbf812e9f53ca71588ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Sep 2017 15:18:07 -0300 Subject: [PATCH 0195/1506] Split base api tests and host api tests Make generic tests available in test_api_workspaced_base --- .../{test_server_api.py => test_api_hosts.py} | 174 +----------------- test_cases/test_api_workspaced_base.py | 170 +++++++++++++++++ 2 files changed, 175 insertions(+), 169 deletions(-) rename test_cases/{test_server_api.py => test_api_hosts.py} (55%) create mode 100644 test_cases/test_api_workspaced_base.py diff --git a/test_cases/test_server_api.py b/test_cases/test_api_hosts.py similarity index 55% rename from test_cases/test_server_api.py rename to test_cases/test_api_hosts.py index 5491676931f..c94a1cf79a0 100644 --- a/test_cases/test_server_api.py +++ b/test_cases/test_api_hosts.py @@ -1,10 +1,10 @@ -#-*- coding: utf8 -*- import pytest from sqlalchemy.orm.util import was_deleted + from test_cases import factories -from server.models import db, Workspace, Host +from test_api_workspaced_base import API_PREFIX, ReadWriteAPITests +from server.models import db, Host -PREFIX = '/v2/' HOSTS_COUNT = 5 SERVICE_COUNT = [10, 5] # 10 services to the first host, 5 to the second @@ -38,7 +38,7 @@ def host_services(self, session, service_factory): def url(self, host=None, workspace=None): workspace = workspace or self.workspace - url = PREFIX + workspace.name + '/hosts/' + url = API_PREFIX + workspace.name + '/hosts/' if host is not None: url += str(host.id) + '/' return url @@ -201,171 +201,7 @@ def test_index_shows_service_count(self, test_client, host_services): assert host['service_count'] == len(ids_map[host['id']]) - -PREFIX = '/v2/' -OBJECT_COUNT = 5 - - -@pytest.mark.usefixtures('logged_user') -class GenericAPITest: - - model = None - factory = None - api_endpoint = None - pk_field = 'id' - unique_fields = [] - update_fields = [] - - @pytest.fixture(autouse=True) - def load_workspace_with_objects(self, database, session, workspace): - objects = self.factory.create_batch(OBJECT_COUNT, workspace=workspace) - self.first_object = objects[0] - session.commit() - assert workspace.id is not None - self.workspace = workspace - return workspace - - @pytest.fixture - def object_instance(self, session, workspace): - """An object instance with the correct workspace assigned, - saved in the database""" - obj = self.factory.create(workspace=workspace) - session.commit() - return obj - - def url(self, obj=None, workspace=None): - workspace = workspace or self.workspace - url = PREFIX + workspace.name + '/' + self.api_endpoint + '/' - if obj is not None: - id_ = unicode(obj.id) if isinstance( - obj, self.model) else unicode(obj) - url += id_ + u'/' - return url - - -class ListTestsMixin: - - def test_list_retrieves_all_items_from_workspace(self, test_client, - second_workspace, - session): - self.factory.create(workspace=second_workspace) - session.commit() - res = test_client.get(self.url()) - assert res.status_code == 200 - assert len(res.json) == OBJECT_COUNT - - -class RetrieveTestsMixin: - - def test_retrieve_one_object(self, test_client): - res = test_client.get(self.url(self.first_object)) - assert res.status_code == 200 - assert isinstance(res.json, dict) - - def test_retrieve_fails_object_of_other_workspcae(self, - test_client, - session, - second_workspace): - res = test_client.get(self.url(self.first_object, second_workspace)) - assert res.status_code == 404 - - @pytest.mark.parametrize('object_id', [123, -1, 'xxx', u'áá']) - def test_404_when_retrieving_unexistent_object(self, test_client, - object_id): - url = self.url(object_id) - res = test_client.get(url) - assert res.status_code == 404 - - -class CreateTestsMixin: - - def test_create_succeeds(self, test_client): - res = test_client.post(self.url(), - data=self.factory.build_dict()) - assert res.status_code == 201 - assert self.model.query.count() == OBJECT_COUNT + 1 - object_id = res.json['id'] - obj = self.model.query.get(object_id) - assert obj.workspace == self.workspace - - def test_create_fails_with_empty_dict(self, test_client): - res = test_client.post(self.url(), data={}) - assert res.status_code == 400 - - def test_create_fails_with_existing(self, session, test_client): - for unique_field in self.unique_fields: - data = self.factory.build_dict() - data[unique_field] = getattr(self.first_object, unique_field) - res = test_client.post(self.url(), data=data) - assert res.status_code == 400 - assert self.model.query.count() == OBJECT_COUNT - - def test_create_with_existing_in_other_workspace(self, test_client, - session, - second_workspace): - unique_field = self.unique_fields[0] - other_object = self.factory.create(workspace=second_workspace) - session.commit() - - data = self.factory.build_dict() - data[unique_field] = getattr(other_object, unique_field) - res = test_client.post(self.url(), data=data) - assert res.status_code == 201 - # It should create two hosts, one for each workspace - assert self.model.query.count() == OBJECT_COUNT + 2 - - -class UpdateTestsMixin: - - def test_update_a_host(self, test_client): - host = self.workspace.hosts[0] - res = test_client.put(self.url(self.first_object), - data=self.factory.build_dict()) - assert res.status_code == 200 - assert self.model.query.count() == OBJECT_COUNT - - def test_update_fails_with_existing(self, test_client, session): - for unique_field in self.unique_fields: - data = self.factory.build_dict() - data[unique_field] = getattr(self.first_object, unique_field) - res = test_client.put(self.url(self.workspace.hosts[1]), data=data) - assert res.status_code == 400 - assert self.model.query.count() == OBJECT_COUNT - - def test_update_a_host_fails_with_empty_dict(self, test_client): - """To do this the user should use a PATCH request""" - host = self.workspace.hosts[0] - res = test_client.put(self.url(host), data={}) - assert res.status_code == 400 - - -class DeleteTestsMixin: - - def test_delete(self, test_client): - res = test_client.delete(self.url(self.first_object)) - assert res.status_code == 204 # No content - assert was_deleted(self.first_object) - assert self.model.query.count() == OBJECT_COUNT - 1 - - def test_delete_from_other_workspace_fails(self, test_client, - second_workspace): - res = test_client.delete(self.url(self.first_object, - workspace=second_workspace)) - assert res.status_code == 404 # No content - assert not was_deleted(self.first_object) - assert self.model.query.count() == OBJECT_COUNT - - -class ReadWriteTestsMixin(ListTestsMixin, - RetrieveTestsMixin, - CreateTestsMixin, - UpdateTestsMixin, - DeleteTestsMixin): - pass - - -class TestHostAPIGeneric(ReadWriteTestsMixin, - GenericAPITest): +class TestHostAPIGeneric(ReadWriteAPITests): model = Host factory = factories.HostFactory api_endpoint = 'hosts' diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py new file mode 100644 index 00000000000..3d9b9d3d583 --- /dev/null +++ b/test_cases/test_api_workspaced_base.py @@ -0,0 +1,170 @@ +#-*- coding: utf8 -*- +import pytest +from sqlalchemy.orm.util import was_deleted +from server.models import db, Workspace + +API_PREFIX = '/v2/' +OBJECT_COUNT = 5 + + +@pytest.mark.usefixtures('logged_user') +class GenericAPITest: + + model = None + factory = None + api_endpoint = None + pk_field = 'id' + unique_fields = [] + update_fields = [] + + @pytest.fixture(autouse=True) + def load_workspace_with_objects(self, database, session, workspace): + objects = self.factory.create_batch(OBJECT_COUNT, workspace=workspace) + self.first_object = objects[0] + session.commit() + assert workspace.id is not None + self.workspace = workspace + return workspace + + @pytest.fixture + def object_instance(self, session, workspace): + """An object instance with the correct workspace assigned, + saved in the database""" + obj = self.factory.create(workspace=workspace) + session.commit() + return obj + + def url(self, obj=None, workspace=None): + workspace = workspace or self.workspace + url = API_PREFIX + workspace.name + '/' + self.api_endpoint + '/' + if obj is not None: + id_ = unicode(obj.id) if isinstance( + obj, self.model) else unicode(obj) + url += id_ + u'/' + return url + + +class ListTestsMixin: + + def test_list_retrieves_all_items_from_workspace(self, test_client, + second_workspace, + session): + self.factory.create(workspace=second_workspace) + session.commit() + res = test_client.get(self.url()) + assert res.status_code == 200 + assert len(res.json) == OBJECT_COUNT + + +class RetrieveTestsMixin: + + def test_retrieve_one_object(self, test_client): + res = test_client.get(self.url(self.first_object)) + assert res.status_code == 200 + assert isinstance(res.json, dict) + + def test_retrieve_fails_object_of_other_workspcae(self, + test_client, + session, + second_workspace): + res = test_client.get(self.url(self.first_object, second_workspace)) + assert res.status_code == 404 + + @pytest.mark.parametrize('object_id', [123, -1, 'xxx', u'áá']) + def test_404_when_retrieving_unexistent_object(self, test_client, + object_id): + url = self.url(object_id) + res = test_client.get(url) + assert res.status_code == 404 + + +class CreateTestsMixin: + + def test_create_succeeds(self, test_client): + res = test_client.post(self.url(), + data=self.factory.build_dict()) + assert res.status_code == 201 + assert self.model.query.count() == OBJECT_COUNT + 1 + object_id = res.json['id'] + obj = self.model.query.get(object_id) + assert obj.workspace == self.workspace + + def test_create_fails_with_empty_dict(self, test_client): + res = test_client.post(self.url(), data={}) + assert res.status_code == 400 + + def test_create_fails_with_existing(self, session, test_client): + for unique_field in self.unique_fields: + data = self.factory.build_dict() + data[unique_field] = getattr(self.first_object, unique_field) + res = test_client.post(self.url(), data=data) + assert res.status_code == 400 + assert self.model.query.count() == OBJECT_COUNT + + def test_create_with_existing_in_other_workspace(self, test_client, + session, + second_workspace): + unique_field = self.unique_fields[0] + other_object = self.factory.create(workspace=second_workspace) + session.commit() + + data = self.factory.build_dict() + data[unique_field] = getattr(other_object, unique_field) + res = test_client.post(self.url(), data=data) + assert res.status_code == 201 + # It should create two hosts, one for each workspace + assert self.model.query.count() == OBJECT_COUNT + 2 + + +class UpdateTestsMixin: + + def test_update_a_host(self, test_client): + host = self.workspace.hosts[0] + res = test_client.put(self.url(self.first_object), + data=self.factory.build_dict()) + assert res.status_code == 200 + assert self.model.query.count() == OBJECT_COUNT + + def test_update_fails_with_existing(self, test_client, session): + for unique_field in self.unique_fields: + data = self.factory.build_dict() + data[unique_field] = getattr(self.first_object, unique_field) + res = test_client.put(self.url(self.workspace.hosts[1]), data=data) + assert res.status_code == 400 + assert self.model.query.count() == OBJECT_COUNT + + def test_update_a_host_fails_with_empty_dict(self, test_client): + """To do this the user should use a PATCH request""" + host = self.workspace.hosts[0] + res = test_client.put(self.url(host), data={}) + assert res.status_code == 400 + + +class DeleteTestsMixin: + + def test_delete(self, test_client): + res = test_client.delete(self.url(self.first_object)) + assert res.status_code == 204 # No content + assert was_deleted(self.first_object) + assert self.model.query.count() == OBJECT_COUNT - 1 + + def test_delete_from_other_workspace_fails(self, test_client, + second_workspace): + res = test_client.delete(self.url(self.first_object, + workspace=second_workspace)) + assert res.status_code == 404 # No content + assert not was_deleted(self.first_object) + assert self.model.query.count() == OBJECT_COUNT + + +class ReadWriteTestsMixin(ListTestsMixin, + RetrieveTestsMixin, + CreateTestsMixin, + UpdateTestsMixin, + DeleteTestsMixin): + pass + + +class ReadWriteAPITests(ReadWriteTestsMixin, + GenericAPITest): + pass From b4005aafa8b51c32e0a4dae22069baa0b9496c35 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 12 Sep 2017 15:29:57 -0300 Subject: [PATCH 0196/1506] Add Workspace name to FaradayEntity importer Allows for logging of the ws name when importing objects. --- server/importer.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index c2a21dd0e5e..e470369836b 100644 --- a/server/importer.py +++ b/server/importer.py @@ -632,6 +632,9 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class FaradayEntityImporter(object): # Document Types: [u'Service', u'Communication', u'Vulnerability', u'CommandRunInformation', u'Reports', u'Host', u'Workspace'] + def __init__(self, workspace_name): + self.workspace_name = workspace_name + def parse(self, document): """Get an instance of a DAO object given a document""" importer_class = self.get_importer_from_document(document) @@ -644,7 +647,7 @@ def parse(self, document): return None, None def get_importer_from_document(self, doc_type): - logger.info('Getting class importer for {0}'.format(doc_type)) + logger.info('Getting class importer for {0} in workspace {1}'.format(doc_type, self.workspace_name)) importer_class_mapper = { 'EntityMetadata': EntityMetadataImporter, 'Host': HostImporter, @@ -930,7 +933,7 @@ def verify_import_data(self, couchdb_relational_map, couchdb_relational_map_by_t def import_workspace_into_database(self, workspace_name): - faraday_importer = FaradayEntityImporter() + faraday_importer = FaradayEntityImporter(workspace_name) workspace, created = get_or_create(session, Workspace, name=workspace_name) session.commit() From 31545b44995fa480c1c3b90f976b0bf9623dd7cd Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 12 Sep 2017 15:30:49 -0300 Subject: [PATCH 0197/1506] Fix error when CouchDB is not running When trying to import from a turned off CouchDB, a nice error is shown to the user. --- server/importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index e470369836b..7bf5eaff6d0 100644 --- a/server/importer.py +++ b/server/importer.py @@ -849,13 +849,14 @@ def run(self): """ Main entry point for couchdb import """ + couchdb_server_conn, workspaces_list = self._open_couchdb_conn() license_import = ImportLicense() license_import.run() vuln_templates_import = ImportVulnerabilityTemplates() vuln_templates_import.run() users_import = ImportCouchDBUsers() users_import.run() - couchdb_server_conn, workspaces_list = self._open_couchdb_conn() + for workspace_name in workspaces_list: logger.info(u'Setting up workspace {}'.format(workspace_name)) From a772fa458485bfb1d747630f9c4f51e7371fa097 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 12 Sep 2017 15:31:50 -0300 Subject: [PATCH 0198/1506] Add description to unique contraint for vulns A hashing function is needed in order to avoid exceeding Postgres max length. The constraint was replaced by an index to allow using md5() over the description. --- server/models.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/server/models.py b/server/models.py index c2694f6b454..a286dd15c1d 100644 --- a/server/models.py +++ b/server/models.py @@ -7,6 +7,7 @@ Column, DateTime, Enum, + event, Float, ForeignKey, Integer, @@ -15,7 +16,7 @@ UniqueConstraint, ) from sqlalchemy.orm import relationship, backref -from sqlalchemy.ext.declarative import declared_attr +from sqlalchemy.schema import DDL from flask_sqlalchemy import SQLAlchemy from flask_security import ( RoleMixin, @@ -787,15 +788,15 @@ class ExecutiveReport(db.Model): '(Vulnerability.source_code_id IS NOT NULL)::int)=1', name='check_vulnerability_host_service_source_code', table=VulnerabilityGeneric.__table__) -UniqueConstraint(VulnerabilityGeneric.name, - VulnerabilityGeneric.severity, - Vulnerability.host_id, - VulnerabilityWeb.service_id, - VulnerabilityWeb.method, - VulnerabilityWeb.parameter_name, - VulnerabilityWeb.path, - VulnerabilityWeb.website, - VulnerabilityGeneric.workspace_id, - VulnerabilityCode.source_code_id, - name='uix_vulnerability' + +vulnerability_uniqueness = DDL( + "CREATE UNIQUE INDEX uix_vulnerability ON %(fullname)s " + "(name, md5(description), severity, host_id, service_id, " + "method, parameter_name, path, website, workspace_id, source_code_id);" +) + +event.listen( + VulnerabilityGeneric.__table__, + 'after_create', + vulnerability_uniqueness.execute_if(dialect='postgresql') ) \ No newline at end of file From e4e955d518aa175df6d919e7916acd0670965d45 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 12 Sep 2017 16:15:22 -0300 Subject: [PATCH 0199/1506] Allow creator to be nullalble --- server/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index 32fc42933a5..30381cc1170 100644 --- a/server/models.py +++ b/server/models.py @@ -46,7 +46,7 @@ class Metadata(db.Model): @declared_attr def creator_id(cls): - return Column(Integer, ForeignKey('user.id'), nullable=False) + return Column(Integer, ForeignKey('user.id'), nullable=True) @declared_attr def creator(cls): From e23a491ecbcac4d10437ad52ec71d6dbb8df4791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Sep 2017 16:52:08 -0300 Subject: [PATCH 0200/1506] Refactor API generic views To make it support views that don't depend on a workspace, this make them really generic. I changed the order of _get_object when inheriting from ReadWriteWorkspacedView, it first takes the object_id and then the workspace_name --- server/api/base.py | 109 ++++++++++++++++--------- server/api/modules/hosts.py | 2 +- test_cases/test_api_workspaced_base.py | 3 + 3 files changed, 73 insertions(+), 41 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index a4a7c4e74af..79d01db4f81 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -22,21 +22,21 @@ def output_json(data, code, headers=None): # TODO: Require @view decorator to enable custom routes -class GenericWorkspacedView(FlaskView): - """Abstract class for a view that depends on the workspace, that is - passed in the URL""" +class GenericView(FlaskView): + """Abstract class to provide helpers. Inspired in Django REST + Framework generic viewsets""" # Must-implement attributes model_class = None schema_class = None # Default attributes - route_prefix = '/v2//' - base_args = ['workspace_name'] # Required to prevent double usage of + route_prefix = '/v2/' + base_args = [] representations = {'application/json': output_json} lookup_field = 'id' lookup_field_type = int - unique_fields = [] # Fields unique together with workspace_id + unique_fields = [] # Fields unique def _get_schema_class(self): assert self.schema_class is not None, "You must define schema_class" @@ -45,21 +45,19 @@ def _get_schema_class(self): def _get_lookup_field(self): return getattr(self.model_class, self.lookup_field) - def _get_workspace(self, workspace_name): + def _validate_object_id(self, object_id): try: - ws = Workspace.query.filter_by(name=workspace_name).one() - except NoResultFound: - flask.abort(404, "No such workspace: %s" % workspace_name) - return ws + self.lookup_field_type(object_id) + except ValueError: + flask.abort(404, 'Invalid format of lookup field') - def _get_base_query(self, workspace_name): - return self.model_class.query.join(Workspace) \ - .filter(Workspace.id==self._get_workspace(workspace_name).id) + def _get_base_query(self): + return self.model_class.query - def _get_object(self, workspace_name, object_id): + def _get_object(self, object_id, **kwargs): self._validate_object_id(object_id) try: - obj = self._get_base_query(workspace_name).filter( + obj = self._get_base_query(**kwargs).filter( self._get_lookup_field() == object_id).one() except NoResultFound: flask.abort(404, 'Object with id "%s" not found' % object_id) @@ -72,13 +70,61 @@ def _parse_data(self, schema, request, *args, **kwargs): return FlaskParser().parse(schema, request, locations=('json',), *args, **kwargs) - def _validate_object_id(self, object_id): + def _validate_uniqueness(self, obj, object_id=None): + # TODO: Implement this + return True + + @classmethod + def register(cls, app, *args, **kwargs): + """Register and add JSON error handler. Use error code + 400 instead of 422""" + super(GenericView, cls).register(app, *args, **kwargs) + @app.errorhandler(422) + def handle_unprocessable_entity(err): + # webargs attaches additional metadata to the `data` attribute + exc = getattr(err, 'exc') + if exc: + # Get validations from the ValidationError object + messages = exc.messages + else: + messages = ['Invalid request'] + return flask.jsonify({ + 'messages': messages, + }), 400 + + +class GenericWorkspacedView(GenericView): + """Abstract class for a view that depends on the workspace, that is + passed in the URL""" + + # Default attributes + route_prefix = '/v2//' + base_args = ['workspace_name'] # Required to prevent double usage of + unique_fields = [] # Fields unique together with workspace_id + + def _get_workspace(self, workspace_name): try: - self.lookup_field_type(object_id) - except ValueError: - flask.abort(404, 'Invalid format of lookup field') + ws = Workspace.query.filter_by(name=workspace_name).one() + except NoResultFound: + flask.abort(404, "No such workspace: %s" % workspace_name) + return ws + + def _get_base_query(self, workspace_name): + base = super(GenericWorkspacedView, self)._get_base_query() + return base.join(Workspace).filter( + Workspace.id==self._get_workspace(workspace_name).id) + + def _get_object(self, object_id, workspace_name): + self._validate_object_id(object_id) + try: + obj = self._get_base_query(workspace_name).filter( + self._get_lookup_field() == object_id).one() + except NoResultFound: + flask.abort(404, 'Object with id "%s" not found' % object_id) + return obj def _validate_uniqueness(self, obj, object_id=None): + # TODO: Use implementation of GenericView assert obj.workspace is not None, "Object must have a " \ "workspace attribute set to call _validate_uniqueness" primary_key_field = inspect(self.model_class).primary_key[0] @@ -97,23 +143,6 @@ def _validate_uniqueness(self, obj, object_id=None): field_name, value ))) - @classmethod - def register(cls, app, *args, **kwargs): - """Register and add JSON error handler. Use error code - 400 instead of 422""" - super(GenericWorkspacedView, cls).register(app, *args, **kwargs) - @app.errorhandler(422) - def handle_unprocessable_entity(err): - # webargs attaches additional metadata to the `data` attribute - exc = getattr(err, 'exc') - if exc: - # Get validations from the ValidationError object - messages = exc.messages - else: - messages = ['Invalid request'] - return flask.jsonify({ - 'messages': messages, - }), 400 class ListWorkspacedMixin(object): """Add GET // route""" @@ -127,7 +156,7 @@ class RetrieveWorkspacedMixin(object): """Add GET /// route""" def get(self, workspace_name, object_id): - return self._dump(self._get_object(workspace_name, object_id)) + return self._dump(self._get_object(object_id, workspace_name)) class ReadOnlyWorkspacedView(ListWorkspacedMixin, @@ -162,7 +191,7 @@ class UpdateWorkspacedMixin(object): def put(self, workspace_name, object_id): data = self._parse_data(self._get_schema_class()(strict=True), flask.request) - obj = self._get_object(workspace_name, object_id) + obj = self._get_object(object_id, workspace_name) self._update_object(obj, data) updated = self._perform_update(workspace_name, object_id, obj) return self._dump(obj).data, 200 @@ -181,7 +210,7 @@ def _perform_update(self, workspace_name, object_id, obj): class DeleteWorkspacedMixin(object): def delete(self, workspace_name, object_id): - obj = self._get_object(workspace_name, object_id) + obj = self._get_object(object_id, workspace_name) self._perform_delete(obj) return None, 204 diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index 880e027d8ec..598d169a2b7 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -43,7 +43,7 @@ class HostsView(ReadWriteWorkspacedView): @route('//services/') def service_list(self, workspace_name, host_id): - services = self._get_object(workspace_name, host_id).services + services = self._get_object(host_id, workspace_name).services return ServiceSchema(many=True).dump(services) def _get_base_query(self, workspace_name): diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py index 3d9b9d3d583..8fa9b4ce324 100644 --- a/test_cases/test_api_workspaced_base.py +++ b/test_cases/test_api_workspaced_base.py @@ -1,4 +1,7 @@ #-*- coding: utf8 -*- + +"""Generic tests for APIs prefixed with a workspace_name""" + import pytest from sqlalchemy.orm.util import was_deleted from server.models import db, Workspace From 52dec81a6adb7ee458ed247b1c9b89c7daba4257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Sep 2017 18:31:56 -0300 Subject: [PATCH 0201/1506] Add List generic mixin and License API with that mixin --- server/api/base.py | 35 +++- server/app.py | 2 + test_cases/conftest.py | 1 + test_cases/factories.py | 37 +++- test_cases/test_api_non_workspaced_base.py | 167 ++++++++++++++++++ test_cases/test_api_non_workspaced_various.py | 17 ++ 6 files changed, 248 insertions(+), 11 deletions(-) create mode 100644 test_cases/test_api_non_workspaced_base.py create mode 100644 test_cases/test_api_non_workspaced_various.py diff --git a/server/api/base.py b/server/api/base.py index 79d01db4f81..13370089d63 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -144,14 +144,20 @@ def _validate_uniqueness(self, obj, object_id=None): ))) -class ListWorkspacedMixin(object): +class ListMixin(object): """Add GET // route""" - def index(self, workspace_name): - return self._dump(self._get_base_query(workspace_name).all(), + def index(self, **kwargs): + return self._dump(self._get_base_query(**kwargs).all(), many=True) +class ListWorkspacedMixin(ListMixin): + # There are no differences with the non-workspaced implementations. The code + # inside the view generic methods is enough + pass + + class RetrieveWorkspacedMixin(object): """Add GET /// route""" @@ -159,10 +165,17 @@ def get(self, workspace_name, object_id): return self._dump(self._get_object(object_id, workspace_name)) +class ReadOnlyView(ListMixin, + # RetrieveMixin, + GenericView): + """A generic view with list and retrieve endpoints""" + pass + + class ReadOnlyWorkspacedView(ListWorkspacedMixin, RetrieveWorkspacedMixin, GenericWorkspacedView): - """A generic view with list and retrieve endpoints""" + """A workspaced generic view with list and retrieve endpoints""" pass @@ -219,10 +232,18 @@ def _perform_delete(self, obj): db.session.commit() +class ReadWriteView(#CreateWorkspacedMixin, + #UpdateWorkspacedMixin, + # DeleteWorkspacedMixin, + ReadOnlyView): + """A generic view with list, retrieve and create endpoints""" + pass + + class ReadWriteWorkspacedView(CreateWorkspacedMixin, UpdateWorkspacedMixin, DeleteWorkspacedMixin, - ReadOnlyWorkspacedView, - GenericWorkspacedView): - """A generic view with list, retrieve and create endpoints""" + ReadOnlyWorkspacedView): + """A generic workspaced view with list, retrieve and create + endpoints""" pass diff --git a/server/app.py b/server/app.py index 2f4778817fc..a25f3f992bd 100644 --- a/server/app.py +++ b/server/app.py @@ -59,6 +59,7 @@ def create_app(db_connection_string=None, testing=None): from server.api.modules.doc import doc_api from server.api.modules.vuln_csv import vuln_csv_api from server.api.modules.hosts import host_api + from server.api.modules.licenses import license_api from server.api.modules.commandsrun import commandsrun_api from server.api.modules.services import services_api from server.api.modules.credentials import credentials_api @@ -68,6 +69,7 @@ def create_app(db_connection_string=None, testing=None): app.register_blueprint(doc_api) app.register_blueprint(vuln_csv_api) app.register_blueprint(host_api) + app.register_blueprint(license_api) app.register_blueprint(commandsrun_api) app.register_blueprint(services_api) app.register_blueprint(credentials_api) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index f67c569e282..34ce9c29cde 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -19,6 +19,7 @@ factories.ServiceFactory, factories.VulnerabilityFactory, factories.CredentialFactory, + factories.LicenseFactory, ] for factory in enabled_factories: register(factory) diff --git a/test_cases/factories.py b/test_cases/factories.py index 85c94b8ece9..e2d2c55af9d 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -1,21 +1,39 @@ import factory +import datetime from factory.fuzzy import ( FuzzyChoice, + FuzzyNaiveDateTime, FuzzyInteger, FuzzyText, ) from server.models import ( db, - Host, Command, - Service, - Workspace, Credential, - Vulnerability, EntityMetadata, + Host, + License, + Service, + Vulnerability, + Workspace, +) + +# Make partials for start and end date. End date must be after start date +FuzzyStartTime = lambda: ( + FuzzyNaiveDateTime( + datetime.datetime.now() - datetime.timedelta(days=40), + datetime.datetime.now() - datetime.timedelta(days=20), + ) +) +FuzzyEndTime = lambda: ( + FuzzyNaiveDateTime( + datetime.datetime.now() - datetime.timedelta(days=19), + datetime.datetime.now() + ) ) + class FaradayFactory(factory.alchemy.SQLAlchemyModelFactory): # id = factory.Sequence(lambda n: n) @@ -105,3 +123,14 @@ class CommandFactory(WorkspaceObjectFactory): class Meta: model = Command sqlalchemy_session = db.session + + +class LicenseFactory(FaradayFactory): + product = FuzzyText() + start_date = FuzzyStartTime() + end_date = FuzzyEndTime() + type = FuzzyText() + + class Meta: + model = License + sqlalchemy_session = db.session diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py new file mode 100644 index 00000000000..fdaabb1bffd --- /dev/null +++ b/test_cases/test_api_non_workspaced_base.py @@ -0,0 +1,167 @@ +#-*- coding: utf8 -*- + +"""Generic tests for APIs NOT prefixed with a workspace_name""" + +import pytest +from sqlalchemy.orm.util import was_deleted +from server.models import db + +API_PREFIX = '/v2/' +OBJECT_COUNT = 5 + +@pytest.mark.usefixtures('logged_user') +class GenericAPITest: + + model = None + factory = None + api_endpoint = None + pk_field = 'id' + unique_fields = [] + update_fields = [] + + @pytest.fixture(autouse=True) + def load_many_objects(self, database, session): + objects = self.factory.create_batch(OBJECT_COUNT) + self.first_object = objects[0] + session.commit() + assert self.model.query.count() == OBJECT_COUNT + return objects + + @pytest.fixture + def object_instance(self, session): + """An object instance saved in the database""" + obj = self.factory.create() + session.commit() + return obj + + def url(self, obj=None): + url = API_PREFIX + self.api_endpoint + '/' + if obj is not None: + id_ = unicode(obj.id) if isinstance( + obj, self.model) else unicode(obj) + url += id_ + u'/' + return url + + +class ListTestsMixin: + + def test_list_retrieves_all_items_from(self, test_client, + session): + res = test_client.get(self.url()) + assert res.status_code == 200 + assert len(res.json) == OBJECT_COUNT + + +class RetrieveTestsMixin: + + def test_retrieve_one_object(self, test_client): + res = test_client.get(self.url(self.first_object)) + assert res.status_code == 200 + assert isinstance(res.json, dict) + + def test_retrieve_fails_object_of_other_workspcae(self, + test_client, + session, + second_workspace): + res = test_client.get(self.url(self.first_object, second_workspace)) + assert res.status_code == 404 + + @pytest.mark.parametrize('object_id', [123, -1, 'xxx', u'áá']) + def test_404_when_retrieving_unexistent_object(self, test_client, + object_id): + url = self.url(object_id) + res = test_client.get(url) + assert res.status_code == 404 + + +class CreateTestsMixin: + + def test_create_succeeds(self, test_client): + res = test_client.post(self.url(), + data=self.factory.build_dict()) + assert res.status_code == 201 + assert self.model.query.count() == OBJECT_COUNT + 1 + object_id = res.json['id'] + obj = self.model.query.get(object_id) + assert obj.workspace == self.workspace + + def test_create_fails_with_empty_dict(self, test_client): + res = test_client.post(self.url(), data={}) + assert res.status_code == 400 + + def test_create_fails_with_existing(self, session, test_client): + for unique_field in self.unique_fields: + data = self.factory.build_dict() + data[unique_field] = getattr(self.first_object, unique_field) + res = test_client.post(self.url(), data=data) + assert res.status_code == 400 + assert self.model.query.count() == OBJECT_COUNT + + def test_create_with_existing_in_other_workspace(self, test_client, + session, + second_workspace): + unique_field = self.unique_fields[0] + other_object = self.factory.create(workspace=second_workspace) + session.commit() + + data = self.factory.build_dict() + data[unique_field] = getattr(other_object, unique_field) + res = test_client.post(self.url(), data=data) + assert res.status_code == 201 + # It should create two hosts, one for each workspace + assert self.model.query.count() == OBJECT_COUNT + 2 + + +class UpdateTestsMixin: + + def test_update_a_host(self, test_client): + host = self.workspace.hosts[0] + res = test_client.put(self.url(self.first_object), + data=self.factory.build_dict()) + assert res.status_code == 200 + assert self.model.query.count() == OBJECT_COUNT + + def test_update_fails_with_existing(self, test_client, session): + for unique_field in self.unique_fields: + data = self.factory.build_dict() + data[unique_field] = getattr(self.first_object, unique_field) + res = test_client.put(self.url(self.workspace.hosts[1]), data=data) + assert res.status_code == 400 + assert self.model.query.count() == OBJECT_COUNT + + def test_update_a_host_fails_with_empty_dict(self, test_client): + """To do this the user should use a PATCH request""" + host = self.workspace.hosts[0] + res = test_client.put(self.url(host), data={}) + assert res.status_code == 400 + + +class DeleteTestsMixin: + + def test_delete(self, test_client): + res = test_client.delete(self.url(self.first_object)) + assert res.status_code == 204 # No content + assert was_deleted(self.first_object) + assert self.model.query.count() == OBJECT_COUNT - 1 + + def test_delete_from_other_workspace_fails(self, test_client, + second_workspace): + res = test_client.delete(self.url(self.first_object, + workspace=second_workspace)) + assert res.status_code == 404 # No content + assert not was_deleted(self.first_object) + assert self.model.query.count() == OBJECT_COUNT + + +class ReadWriteTestsMixin(ListTestsMixin, + # RetrieveTestsMixin, + # CreateTestsMixin, + # UpdateTestsMixin, + # DeleteTestsMixin + ): + pass + + +class ReadWriteAPITests(ReadWriteTestsMixin, + GenericAPITest): + pass diff --git a/test_cases/test_api_non_workspaced_various.py b/test_cases/test_api_non_workspaced_various.py new file mode 100644 index 00000000000..36ed35b50ee --- /dev/null +++ b/test_cases/test_api_non_workspaced_various.py @@ -0,0 +1,17 @@ +#-*- coding: utf8 -*- + +"""Tests for many API endpoints that do not depend on workspace_name""" + +import pytest +from test_cases import factories +from test_api_non_workspaced_base import ReadWriteAPITests +from server.models import License + +class TestLicensesAPI(ReadWriteAPITests): + model = License + factory = factories.LicenseFactory + api_endpoint = 'licenses' + unique_fields = ['ip'] + update_fields = ['ip', 'description', 'os'] + + From 743429a2fca2127d27ea2e96e1fd535ccb1ffce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Sep 2017 19:01:13 -0300 Subject: [PATCH 0202/1506] Add generic retrieve mixin and apply it to licenses API --- server/api/base.py | 22 +++++++++++++++------- test_cases/test_api_non_workspaced_base.py | 9 +-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 13370089d63..2e3d98c7188 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -145,7 +145,7 @@ def _validate_uniqueness(self, obj, object_id=None): class ListMixin(object): - """Add GET // route""" + """Add GET / route""" def index(self, **kwargs): return self._dump(self._get_base_query(**kwargs).all(), @@ -153,20 +153,28 @@ def index(self, **kwargs): class ListWorkspacedMixin(ListMixin): + """Add GET // route""" # There are no differences with the non-workspaced implementations. The code # inside the view generic methods is enough pass -class RetrieveWorkspacedMixin(object): - """Add GET /// route""" +class RetrieveMixin(object): + """Add GET // route""" + + def get(self, object_id, **kwargs): + return self._dump(self._get_object(object_id, **kwargs)) + - def get(self, workspace_name, object_id): - return self._dump(self._get_object(object_id, workspace_name)) +class RetrieveWorkspacedMixin(RetrieveMixin): + """Add GET /// route""" + # There are no differences with the non-workspaced implementations. The code + # inside the view generic methods is enough + pass class ReadOnlyView(ListMixin, - # RetrieveMixin, + RetrieveMixin, GenericView): """A generic view with list and retrieve endpoints""" pass @@ -234,7 +242,7 @@ def _perform_delete(self, obj): class ReadWriteView(#CreateWorkspacedMixin, #UpdateWorkspacedMixin, - # DeleteWorkspacedMixin, + #DeleteWorkspacedMixin, ReadOnlyView): """A generic view with list, retrieve and create endpoints""" pass diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py index fdaabb1bffd..795e147232c 100644 --- a/test_cases/test_api_non_workspaced_base.py +++ b/test_cases/test_api_non_workspaced_base.py @@ -59,13 +59,6 @@ def test_retrieve_one_object(self, test_client): assert res.status_code == 200 assert isinstance(res.json, dict) - def test_retrieve_fails_object_of_other_workspcae(self, - test_client, - session, - second_workspace): - res = test_client.get(self.url(self.first_object, second_workspace)) - assert res.status_code == 404 - @pytest.mark.parametrize('object_id', [123, -1, 'xxx', u'áá']) def test_404_when_retrieving_unexistent_object(self, test_client, object_id): @@ -154,7 +147,7 @@ def test_delete_from_other_workspace_fails(self, test_client, class ReadWriteTestsMixin(ListTestsMixin, - # RetrieveTestsMixin, + RetrieveTestsMixin, # CreateTestsMixin, # UpdateTestsMixin, # DeleteTestsMixin From 8a812ccb1909de3c482a92f68e7b5b56ac2876b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Sep 2017 19:47:35 -0300 Subject: [PATCH 0203/1506] Add generic create mixin and apply it to licences API It fails when POSTing with an empty dict --- server/api/base.py | 27 ++++++++++++------- test_cases/factories.py | 15 ++++++++++- test_cases/test_api_non_workspaced_base.py | 17 +----------- test_cases/test_api_non_workspaced_various.py | 4 +-- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 2e3d98c7188..0c7f2d4ab7b 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -187,27 +187,36 @@ class ReadOnlyWorkspacedView(ListWorkspacedMixin, pass -class CreateWorkspacedMixin(object): +class CreateMixin(object): + """Add POST / route""" - def post(self, workspace_name): + def post(self, **kwargs): data = self._parse_data(self._get_schema_class()(strict=True), flask.request) obj = self.model_class(**data) - created = self._perform_create(workspace_name, obj) + created = self._perform_create(obj, **kwargs) return self._dump(created).data, 201 - def _perform_create(self, workspace_name, obj): - assert not db.session.new + def _perform_create(self, obj): + # assert not db.session.new with db.session.no_autoflush: # Required because _validate_uniqueness does a select. Doing this # outside a no_autoflush block would result in a premature create. - obj.workspace = self._get_workspace(workspace_name) self._validate_uniqueness(obj) db.session.add(obj) db.session.commit() return obj +class CreateWorkspacedMixin(CreateMixin): + """Add POST // route""" + + def _perform_create(self, obj, workspace_name): + assert not db.session.new + obj.workspace = self._get_workspace(workspace_name) + return super(CreateWorkspacedMixin, self)._perform_create(obj) + + class UpdateWorkspacedMixin(object): def put(self, workspace_name, object_id): data = self._parse_data(self._get_schema_class()(strict=True), @@ -240,9 +249,9 @@ def _perform_delete(self, obj): db.session.commit() -class ReadWriteView(#CreateWorkspacedMixin, - #UpdateWorkspacedMixin, - #DeleteWorkspacedMixin, +class ReadWriteView(CreateMixin, + #UpdateMixin, + #DeleteMixin, ReadOnlyView): """A generic view with list, retrieve and create endpoints""" pass diff --git a/test_cases/factories.py b/test_cases/factories.py index e2d2c55af9d..cbea807eab2 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -39,6 +39,11 @@ class FaradayFactory(factory.alchemy.SQLAlchemyModelFactory): # id = factory.Sequence(lambda n: n) pass + @classmethod + def build_dict(cls, **kwargs): + return factory.build(dict, FACTORY_CLASS=cls) + + class WorkspaceFactory(FaradayFactory): @@ -54,7 +59,7 @@ class WorkspaceObjectFactory(FaradayFactory): @classmethod def build_dict(cls, **kwargs): - ret = factory.build(dict, FACTORY_CLASS=cls) + ret = super(WorkspaceObjectFactory, cls).build_dict(**kwargs) del ret['workspace'] # It is passed in the URL, not in POST data return ret @@ -134,3 +139,11 @@ class LicenseFactory(FaradayFactory): class Meta: model = License sqlalchemy_session = db.session + + @classmethod + def build_dict(cls, **kwargs): + # Ugly hack to JSON-serialize datetimes + ret = super(LicenseFactory, cls).build_dict(**kwargs) + ret['start_date'] = ret['start_date'].isoformat() + ret['end_date'] = ret['end_date'].isoformat() + return ret diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py index 795e147232c..f0f7bdb2073 100644 --- a/test_cases/test_api_non_workspaced_base.py +++ b/test_cases/test_api_non_workspaced_base.py @@ -76,7 +76,6 @@ def test_create_succeeds(self, test_client): assert self.model.query.count() == OBJECT_COUNT + 1 object_id = res.json['id'] obj = self.model.query.get(object_id) - assert obj.workspace == self.workspace def test_create_fails_with_empty_dict(self, test_client): res = test_client.post(self.url(), data={}) @@ -90,20 +89,6 @@ def test_create_fails_with_existing(self, session, test_client): assert res.status_code == 400 assert self.model.query.count() == OBJECT_COUNT - def test_create_with_existing_in_other_workspace(self, test_client, - session, - second_workspace): - unique_field = self.unique_fields[0] - other_object = self.factory.create(workspace=second_workspace) - session.commit() - - data = self.factory.build_dict() - data[unique_field] = getattr(other_object, unique_field) - res = test_client.post(self.url(), data=data) - assert res.status_code == 201 - # It should create two hosts, one for each workspace - assert self.model.query.count() == OBJECT_COUNT + 2 - class UpdateTestsMixin: @@ -148,7 +133,7 @@ def test_delete_from_other_workspace_fails(self, test_client, class ReadWriteTestsMixin(ListTestsMixin, RetrieveTestsMixin, - # CreateTestsMixin, + CreateTestsMixin, # UpdateTestsMixin, # DeleteTestsMixin ): diff --git a/test_cases/test_api_non_workspaced_various.py b/test_cases/test_api_non_workspaced_various.py index 36ed35b50ee..37471b925e0 100644 --- a/test_cases/test_api_non_workspaced_various.py +++ b/test_cases/test_api_non_workspaced_various.py @@ -11,7 +11,7 @@ class TestLicensesAPI(ReadWriteAPITests): model = License factory = factories.LicenseFactory api_endpoint = 'licenses' - unique_fields = ['ip'] - update_fields = ['ip', 'description', 'os'] + # unique_fields = ['ip'] + # update_fields = ['ip', 'description', 'os'] From 6e549eb2eb7e2f22e348495c287c39e432750612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Sep 2017 14:51:13 -0300 Subject: [PATCH 0204/1506] Add dev requirements Dependencies for testing --- requirements_dev.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 requirements_dev.txt diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 00000000000..dbf901a1aab --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,3 @@ +pytest +pytest-factoryboy +factory-boy<=2.8.1 From 8cdb33a18197c3f7f49570345148a09b0954063a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Sep 2017 14:51:57 -0300 Subject: [PATCH 0205/1506] Add licenses API code I forgot to add this before --- server/api/modules/licenses.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 server/api/modules/licenses.py diff --git a/server/api/modules/licenses.py b/server/api/modules/licenses.py new file mode 100644 index 00000000000..61173568f14 --- /dev/null +++ b/server/api/modules/licenses.py @@ -0,0 +1,28 @@ +# Faraday Penetration Test IDE +# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) +# See the file 'doc/LICENSE' for the license information + +import flask +from flask import Blueprint +from marshmallow import Schema, fields + +from server.models import License +from server.api.base import ReadWriteView + +license_api = Blueprint('license_api', __name__) + + +class LicenseSchema(Schema): + id = fields.Integer(required=True, dump_only=True) + product = fields.String() + start_date = fields.DateTime() + end_date = fields.DateTime() + + +class LicenseView(ReadWriteView): + route_base = 'licenses' + model_class = License + schema_class = LicenseSchema + unique_fields = [] + +LicenseView.register(license_api) From 99c752597e718d71ec56fa5067cbec3ae00012cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Sep 2017 15:12:13 -0300 Subject: [PATCH 0206/1506] Set required fields in LicenseSchema Now all API tests succeed --- server/api/modules/licenses.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/api/modules/licenses.py b/server/api/modules/licenses.py index 61173568f14..914f3177aab 100644 --- a/server/api/modules/licenses.py +++ b/server/api/modules/licenses.py @@ -14,9 +14,9 @@ class LicenseSchema(Schema): id = fields.Integer(required=True, dump_only=True) - product = fields.String() - start_date = fields.DateTime() - end_date = fields.DateTime() + product = fields.String(required=True) + start_date = fields.DateTime(required=True) + end_date = fields.DateTime(required=True) class LicenseView(ReadWriteView): From aacf27a489af07b24dc62a12d6ac9aa38101d368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Sep 2017 15:19:12 -0300 Subject: [PATCH 0207/1506] Add generic delete API mixin and apply it to licenses API --- server/api/base.py | 14 ++++++++++---- test_cases/test_api_non_workspaced_base.py | 11 +++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 0c7f2d4ab7b..75bc6409b20 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -238,9 +238,10 @@ def _perform_update(self, workspace_name, object_id, obj): db.session.commit() -class DeleteWorkspacedMixin(object): - def delete(self, workspace_name, object_id): - obj = self._get_object(object_id, workspace_name) +class DeleteMixin(object): + """Add DELETE // route""" + def delete(self, object_id, **kwargs): + obj = self._get_object(object_id, **kwargs) self._perform_delete(obj) return None, 204 @@ -249,9 +250,14 @@ def _perform_delete(self, obj): db.session.commit() +class DeleteWorkspacedMixin(DeleteMixin): + """Add DELETE /// route""" + pass + + class ReadWriteView(CreateMixin, #UpdateMixin, - #DeleteMixin, + DeleteMixin, ReadOnlyView): """A generic view with list, retrieve and create endpoints""" pass diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py index f0f7bdb2073..fdb603702b0 100644 --- a/test_cases/test_api_non_workspaced_base.py +++ b/test_cases/test_api_non_workspaced_base.py @@ -122,12 +122,11 @@ def test_delete(self, test_client): assert was_deleted(self.first_object) assert self.model.query.count() == OBJECT_COUNT - 1 - def test_delete_from_other_workspace_fails(self, test_client, - second_workspace): - res = test_client.delete(self.url(self.first_object, - workspace=second_workspace)) + @pytest.mark.parametrize('object_id', [123, -1, 'xxx', u'áá']) + def test_delete_non_existent_raises_404(self, test_client, + object_id): + res = test_client.delete(self.url(object_id)) assert res.status_code == 404 # No content - assert not was_deleted(self.first_object) assert self.model.query.count() == OBJECT_COUNT @@ -135,7 +134,7 @@ class ReadWriteTestsMixin(ListTestsMixin, RetrieveTestsMixin, CreateTestsMixin, # UpdateTestsMixin, - # DeleteTestsMixin + DeleteTestsMixin, ): pass From 988afb83a626ded2d9a0b5fead1ce8403cd3436d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Sep 2017 15:35:15 -0300 Subject: [PATCH 0208/1506] Fix generic update tests They were only valid for host tests, and not for other models. Also add validation that the fields in `updated_fields` are efectively updated --- test_cases/test_api_non_workspaced_base.py | 15 ++++++++------- test_cases/test_api_workspaced_base.py | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py index fdb603702b0..e6fd8a17645 100644 --- a/test_cases/test_api_non_workspaced_base.py +++ b/test_cases/test_api_non_workspaced_base.py @@ -92,25 +92,26 @@ def test_create_fails_with_existing(self, session, test_client): class UpdateTestsMixin: - def test_update_a_host(self, test_client): - host = self.workspace.hosts[0] + def test_update_an_object(self, test_client): res = test_client.put(self.url(self.first_object), data=self.factory.build_dict()) assert res.status_code == 200 assert self.model.query.count() == OBJECT_COUNT + for updated_field in self.update_fields: + assert res.json[updated_field] == getattr(self.first_object, + updated_field) def test_update_fails_with_existing(self, test_client, session): for unique_field in self.unique_fields: data = self.factory.build_dict() - data[unique_field] = getattr(self.first_object, unique_field) - res = test_client.put(self.url(self.workspace.hosts[1]), data=data) + data[unique_field] = getattr(self.objects[1], unique_field) + res = test_client.put(self.url(self.first_object), data=data) assert res.status_code == 400 assert self.model.query.count() == OBJECT_COUNT - def test_update_a_host_fails_with_empty_dict(self, test_client): + def test_update_an_object_fails_with_empty_dict(self, test_client): """To do this the user should use a PATCH request""" - host = self.workspace.hosts[0] - res = test_client.put(self.url(host), data={}) + res = test_client.put(self.url(self.first_object), data={}) assert res.status_code == 400 diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py index 8fa9b4ce324..a73d9a8029c 100644 --- a/test_cases/test_api_workspaced_base.py +++ b/test_cases/test_api_workspaced_base.py @@ -22,8 +22,9 @@ class GenericAPITest: @pytest.fixture(autouse=True) def load_workspace_with_objects(self, database, session, workspace): - objects = self.factory.create_batch(OBJECT_COUNT, workspace=workspace) - self.first_object = objects[0] + self.objects = self.factory.create_batch( + OBJECT_COUNT, workspace=workspace) + self.first_object = self.objects[0] session.commit() assert workspace.id is not None self.workspace = workspace @@ -121,25 +122,26 @@ def test_create_with_existing_in_other_workspace(self, test_client, class UpdateTestsMixin: - def test_update_a_host(self, test_client): - host = self.workspace.hosts[0] + def test_update_an_object(self, test_client): res = test_client.put(self.url(self.first_object), data=self.factory.build_dict()) assert res.status_code == 200 assert self.model.query.count() == OBJECT_COUNT + for updated_field in self.update_fields: + assert res.json[updated_field] == getattr(self.first_object, + updated_field) def test_update_fails_with_existing(self, test_client, session): for unique_field in self.unique_fields: data = self.factory.build_dict() - data[unique_field] = getattr(self.first_object, unique_field) - res = test_client.put(self.url(self.workspace.hosts[1]), data=data) + data[unique_field] = getattr(self.objects[1], unique_field) + res = test_client.put(self.url(self.first_object), data=data) assert res.status_code == 400 assert self.model.query.count() == OBJECT_COUNT - def test_update_a_host_fails_with_empty_dict(self, test_client): + def test_update_an_object_fails_with_empty_dict(self, test_client): """To do this the user should use a PATCH request""" - host = self.workspace.hosts[0] - res = test_client.put(self.url(host), data={}) + res = test_client.put(self.url(self.first_object), data={}) assert res.status_code == 400 From f6fa24c8d38ec324350c0d458ba28b458d77d524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Sep 2017 16:02:17 -0300 Subject: [PATCH 0209/1506] Add generic update API mixin and apply it to licenses API --- server/api/base.py | 26 ++++++++++++++++------ test_cases/test_api_non_workspaced_base.py | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 75bc6409b20..cedc575926b 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -217,27 +217,39 @@ def _perform_create(self, obj, workspace_name): return super(CreateWorkspacedMixin, self)._perform_create(obj) -class UpdateWorkspacedMixin(object): - def put(self, workspace_name, object_id): +class UpdateMixin(object): + """Add PUT /// route""" + + def put(self, object_id, **kwargs): data = self._parse_data(self._get_schema_class()(strict=True), flask.request) - obj = self._get_object(object_id, workspace_name) + obj = self._get_object(object_id, **kwargs) self._update_object(obj, data) - updated = self._perform_update(workspace_name, object_id, obj) + updated = self._perform_update(object_id, obj, **kwargs) return self._dump(obj).data, 200 def _update_object(self, obj, data): for (key, value) in data.items(): setattr(obj, key, value) - def _perform_update(self, workspace_name, object_id, obj): + def _perform_update(self, object_id, obj): with db.session.no_autoflush: - obj.workspace = self._get_workspace(workspace_name) self._validate_uniqueness(obj, object_id) db.session.add(obj) db.session.commit() +class UpdateWorkspacedMixin(UpdateMixin): + """Add PUT // route""" + + def _perform_update(self, object_id, obj, workspace_name): + assert not db.session.new + with db.session.no_autoflush: + obj.workspace = self._get_workspace(workspace_name) + return super(UpdateWorkspacedMixin, self)._perform_update( + object_id, obj) + + class DeleteMixin(object): """Add DELETE // route""" def delete(self, object_id, **kwargs): @@ -256,7 +268,7 @@ class DeleteWorkspacedMixin(DeleteMixin): class ReadWriteView(CreateMixin, - #UpdateMixin, + UpdateMixin, DeleteMixin, ReadOnlyView): """A generic view with list, retrieve and create endpoints""" diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py index e6fd8a17645..f7ea1b54676 100644 --- a/test_cases/test_api_non_workspaced_base.py +++ b/test_cases/test_api_non_workspaced_base.py @@ -134,7 +134,7 @@ def test_delete_non_existent_raises_404(self, test_client, class ReadWriteTestsMixin(ListTestsMixin, RetrieveTestsMixin, CreateTestsMixin, - # UpdateTestsMixin, + UpdateTestsMixin, DeleteTestsMixin, ): pass From 4f594c4d88b192a3e7131a2a7d419d207043639e Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 13 Sep 2017 17:08:47 -0300 Subject: [PATCH 0210/1506] Check for deleted items on couchdb --- server/importer.py | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/server/importer.py b/server/importer.py index 7bf5eaff6d0..c01837e0a64 100644 --- a/server/importer.py +++ b/server/importer.py @@ -868,16 +868,30 @@ def run(self): self.import_workspace_into_database(workspace_name) - def get_objs(self, host, obj_type, level): + def get_objs(self, host, obj_type, level, workspace): if obj_type == 'Credential': obj_type = 'Cred' data = { "map": "function(doc) { if(doc.type == '%s' && doc._id.split('.').length == %d) emit(null, doc); }" % (obj_type, level) } - r = requests.post(host, json=data) + documents = requests.post(host, json=data).json() + for doc in documents['rows']: + doc['value']['deleted'] = False + doc_status_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_all_docs?keys=[\"{doc_id}\"]".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace.name, + doc_id=doc['value']['_id'] + ) + document_status = requests.get(doc_status_url).json() + assert len(document_status['rows']) == 1 + if any(map(lambda doc_hist: 'error' in doc_hist, document_status['rows'])): + doc['value']['deleted'] = True - return r.json() + return documents def verify_host_vulns_count_is_correct(self, couchdb_relational_map, couchdb_relational_map_by_type, workspace): hosts = session.query(Host).filter_by(workspace=workspace) @@ -897,7 +911,9 @@ def verify_host_vulns_count_is_correct(self, couchdb_relational_map, couchdb_rel for interface in interfaces: interface = interface['value'] vulns += get_children_from_couch(workspace, interface.get('_id'), 'Vulnerability') - assert len(vulns) == len(host.vulnerabilities) + # some vulns had the same name but different description. + # we check that we have the same vuln names now + assert len(set(map(lambda vuln: vuln['value'].get('name'), vulns))) == len(set(map(lambda vuln: vuln.name, host.vulnerabilities))) def verify_import_data(self, couchdb_relational_map, couchdb_relational_map_by_type, workspace): self.verify_host_vulns_count_is_correct(couchdb_relational_map, couchdb_relational_map_by_type, workspace) @@ -939,12 +955,12 @@ def import_workspace_into_database(self, workspace_name): session.commit() couch_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_temp_view?include_docs=true".format( - username=server.config.couchdb.user, - password=server.config.couchdb.password, - hostname=server.config.couchdb.host, - port=server.config.couchdb.port, - workspace_name=workspace_name - ) + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + workspace_name=workspace_name + ) # obj_types are tuples. the first value is the level on the tree # for the desired obj. @@ -953,8 +969,11 @@ def import_workspace_into_database(self, workspace_name): couchdb_relational_map_by_type = defaultdict(list) for level, obj_type in obj_types: obj_importer = faraday_importer.get_importer_from_document(obj_type)() - objs_dict = self.get_objs(couch_url, obj_type, level) + objs_dict = self.get_objs(couch_url, obj_type, level, workspace) for raw_obj in tqdm(objs_dict.get('rows', [])): + if raw_obj['value']['deleted']: + logger.warn('Skipping deleted object from couchdb {0}'.format(raw_obj['value']['_id'])) + continue # we use no_autoflush since some queries triggers flush and some relationship are missing in the middle with session.no_autoflush: raw_obj = raw_obj['value'] From 4fad47479aaf0883ce4245262b67e3788cea5f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Sep 2017 17:10:06 -0300 Subject: [PATCH 0211/1506] Change workspaced views URL From /v2/// to /v2/ws///. This way makes it more compatible with the old version, and allows accessing workspaces named "licences", "users", etc. --- server/api/base.py | 2 +- test_cases/test_api_workspaced_base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index cedc575926b..36d57c675c8 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -98,7 +98,7 @@ class GenericWorkspacedView(GenericView): passed in the URL""" # Default attributes - route_prefix = '/v2//' + route_prefix = '/v2/ws//' base_args = ['workspace_name'] # Required to prevent double usage of unique_fields = [] # Fields unique together with workspace_id diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py index a73d9a8029c..d7d757ffd4f 100644 --- a/test_cases/test_api_workspaced_base.py +++ b/test_cases/test_api_workspaced_base.py @@ -6,7 +6,7 @@ from sqlalchemy.orm.util import was_deleted from server.models import db, Workspace -API_PREFIX = '/v2/' +API_PREFIX = '/v2/ws/' OBJECT_COUNT = 5 From d77b67aef1d767236e5731fc6d59d6abe2a9c531 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 13 Sep 2017 18:40:21 -0300 Subject: [PATCH 0212/1506] Use description to check if vuln already exists --- server/importer.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/importer.py b/server/importer.py index c01837e0a64..ed6d134baef 100644 --- a/server/importer.py +++ b/server/importer.py @@ -371,6 +371,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation session, VulnerabilityWeb, name=document.get('name'), + description=document.get('desc'), severity=severity, service_id=parent.id, method=method, @@ -384,6 +385,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation 'name': document.get('name'), 'severity': severity, 'workspace': workspace, + 'description': document.get('desc') } if type(parent) == Host: vuln_params.update({'host_id': parent.id}) @@ -394,7 +396,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation Vulnerability, **vuln_params ) - vulnerability.description = document.get('desc'), vulnerability.confirmed = document.get('confirmed', False) or False vulnerability.data = document.get('data') vulnerability.easeofresolution = document.get('easeofresolution') @@ -911,9 +912,7 @@ def verify_host_vulns_count_is_correct(self, couchdb_relational_map, couchdb_rel for interface in interfaces: interface = interface['value'] vulns += get_children_from_couch(workspace, interface.get('_id'), 'Vulnerability') - # some vulns had the same name but different description. - # we check that we have the same vuln names now - assert len(set(map(lambda vuln: vuln['value'].get('name'), vulns))) == len(set(map(lambda vuln: vuln.name, host.vulnerabilities))) + assert len(vulns) == len(host.vulnerabilities) def verify_import_data(self, couchdb_relational_map, couchdb_relational_map_by_type, workspace): self.verify_host_vulns_count_is_correct(couchdb_relational_map, couchdb_relational_map_by_type, workspace) From add50db56fe57ce732e6ef5f65bb7bea5065502f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 13 Sep 2017 19:04:43 -0300 Subject: [PATCH 0213/1506] Change assert to verify without duplicates The assert could return false positive but we found some old couchdb data that has duplicate vulns. --- server/importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index ed6d134baef..483f8357af1 100644 --- a/server/importer.py +++ b/server/importer.py @@ -912,7 +912,8 @@ def verify_host_vulns_count_is_correct(self, couchdb_relational_map, couchdb_rel for interface in interfaces: interface = interface['value'] vulns += get_children_from_couch(workspace, interface.get('_id'), 'Vulnerability') - assert len(vulns) == len(host.vulnerabilities) + + assert len(set(map(lambda vuln: vuln['value'].get('name'), vulns))) == len(set(map(lambda vuln: vuln.name, host.vulnerabilities))) def verify_import_data(self, couchdb_relational_map, couchdb_relational_map_by_type, workspace): self.verify_host_vulns_count_is_correct(couchdb_relational_map, couchdb_relational_map_by_type, workspace) From 3768e8af457e22c9cb119754805574de1bfce1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Sep 2017 11:56:09 -0300 Subject: [PATCH 0214/1506] Don't use flask classful from GitHub It breaks the dependency checker. The API endpoints won't work properly until a new version of flask classful is released, in a few weeks. Meawhile, you can manually execute pip install https://github.com/teracyhq/flask-classful/archive/develop.zip --- requirements_server.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_server.txt b/requirements_server.txt index 371a6f8d32d..7c9bacffd8a 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -12,6 +12,6 @@ Flask-Security==3.0.0 bcrypt Flask-SQLAlchemy==2.2 Flask-Script==2.0.5 -https://github.com/teracyhq/flask-classful/archive/develop.zip +flask-classful marshmallow webargs From 235a6b282d393878a810de6a1494b6eddad08c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Sep 2017 11:58:42 -0300 Subject: [PATCH 0215/1506] Add plain text password scheme It is used in the createsuperuser.py script. It should be deleted soon --- server/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/app.py b/server/app.py index f70f6df7ea4..c9bc560bfe8 100644 --- a/server/app.py +++ b/server/app.py @@ -34,6 +34,7 @@ def create_app(db_connection_string=None, testing=None): # 'pbkdf2_sha512', # 'sha256_crypt', # 'sha512_crypt', + 'plaintext', # TODO: remove it ] if testing: app.config['TESTING'] = testing From 9cee36d234d07de5a6f39bfab88191e3410c7fc3 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 14 Sep 2017 14:15:19 -0300 Subject: [PATCH 0216/1506] add more cases for vuln template exploitation --- server/importer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index 483f8357af1..464ac9c292d 100644 --- a/server/importer.py +++ b/server/importer.py @@ -775,15 +775,19 @@ def run(self): mapped_exploitation = { 'critical': 'critical', 'med': 'medium', + 'high to very high': 'high', 'high': 'high', 'low': 'low', 'info': 'informational', + 'unknown': 'unclassified', 'unclassified': 'unclassified', } + if document.get('exploitation') not in mapped_exploitation.values(): + logger.warn('Vuln template exploitation {0} not found. using unclassified'.format(document.get('exploitation'))) vuln_template, created = get_or_create(session, VulnerabilityTemplate, name=document.get('name'), - severity=mapped_exploitation[document.get('exploitation')], + severity=mapped_exploitation[document.get('exploitation', 'unclassified').lower()], description=document.get('description')) vuln_template.resolution = document.get('resolution') for ref_doc in document['references']: From 94110fde8ce8b43023b657945f9f4bd424411083 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 14 Sep 2017 14:17:05 -0300 Subject: [PATCH 0217/1506] filter deleted docs --- server/importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index 464ac9c292d..fa6f58b785d 100644 --- a/server/importer.py +++ b/server/importer.py @@ -877,11 +877,11 @@ def get_objs(self, host, obj_type, level, workspace): if obj_type == 'Credential': obj_type = 'Cred' data = { - "map": "function(doc) { if(doc.type == '%s' && doc._id.split('.').length == %d) emit(null, doc); }" % (obj_type, level) + "map": "function(doc) { if(doc.type == '%s' && doc._id.split('.').length == %d && !doc._deleted) emit(null, doc); }" % (obj_type, level) } documents = requests.post(host, json=data).json() - for doc in documents['rows']: + for doc in documents.get('rows', []): doc['value']['deleted'] = False doc_status_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_all_docs?keys=[\"{doc_id}\"]".format( username=server.config.couchdb.user, From e3f864bc2cc8c4406850e537897f2efa972e864c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 14 Sep 2017 14:18:14 -0300 Subject: [PATCH 0218/1506] Remove manual deleted check from couchdb --- server/importer.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/server/importer.py b/server/importer.py index fa6f58b785d..d6ca63a8092 100644 --- a/server/importer.py +++ b/server/importer.py @@ -881,21 +881,6 @@ def get_objs(self, host, obj_type, level, workspace): } documents = requests.post(host, json=data).json() - for doc in documents.get('rows', []): - doc['value']['deleted'] = False - doc_status_url = "http://{username}:{password}@{hostname}:{port}/{workspace_name}/_all_docs?keys=[\"{doc_id}\"]".format( - username=server.config.couchdb.user, - password=server.config.couchdb.password, - hostname=server.config.couchdb.host, - port=server.config.couchdb.port, - workspace_name=workspace.name, - doc_id=doc['value']['_id'] - ) - document_status = requests.get(doc_status_url).json() - assert len(document_status['rows']) == 1 - if any(map(lambda doc_hist: 'error' in doc_hist, document_status['rows'])): - doc['value']['deleted'] = True - return documents def verify_host_vulns_count_is_correct(self, couchdb_relational_map, couchdb_relational_map_by_type, workspace): @@ -975,9 +960,6 @@ def import_workspace_into_database(self, workspace_name): obj_importer = faraday_importer.get_importer_from_document(obj_type)() objs_dict = self.get_objs(couch_url, obj_type, level, workspace) for raw_obj in tqdm(objs_dict.get('rows', [])): - if raw_obj['value']['deleted']: - logger.warn('Skipping deleted object from couchdb {0}'.format(raw_obj['value']['_id'])) - continue # we use no_autoflush since some queries triggers flush and some relationship are missing in the middle with session.no_autoflush: raw_obj = raw_obj['value'] From 255e2f117e0c357409b8f84b2b76a23c05303c5f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 14 Sep 2017 14:18:33 -0300 Subject: [PATCH 0219/1506] Add missing import --- server/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/models.py b/server/models.py index 30381cc1170..b545bbde972 100644 --- a/server/models.py +++ b/server/models.py @@ -18,6 +18,7 @@ Text, UniqueConstraint, ) +from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import relationship, backref from sqlalchemy.schema import DDL from flask_sqlalchemy import SQLAlchemy From 4ebab020f02f759bea8a21e8aef81f548e4d4126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Sep 2017 15:24:32 -0300 Subject: [PATCH 0220/1506] Fix bug making text/html content-type for json responses in API I was returning a Marshmallow structure instead of the current json data and this caused some problems. --- server/api/base.py | 6 +++--- server/api/modules/hosts.py | 2 +- test_cases/conftest.py | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 36d57c675c8..36399069f8e 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -64,7 +64,7 @@ def _get_object(self, object_id, **kwargs): return obj def _dump(self, obj, **kwargs): - return self._get_schema_class()(**kwargs).dump(obj) + return self._get_schema_class()(**kwargs).dump(obj).data def _parse_data(self, schema, request, *args, **kwargs): return FlaskParser().parse(schema, request, locations=('json',), @@ -195,7 +195,7 @@ def post(self, **kwargs): flask.request) obj = self.model_class(**data) created = self._perform_create(obj, **kwargs) - return self._dump(created).data, 201 + return self._dump(created), 201 def _perform_create(self, obj): # assert not db.session.new @@ -226,7 +226,7 @@ def put(self, object_id, **kwargs): obj = self._get_object(object_id, **kwargs) self._update_object(obj, data) updated = self._perform_update(object_id, obj, **kwargs) - return self._dump(obj).data, 200 + return self._dump(obj), 200 def _update_object(self, obj, data): for (key, value) in data.items(): diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index 598d169a2b7..c5001966f8c 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -44,7 +44,7 @@ class HostsView(ReadWriteWorkspacedView): @route('//services/') def service_list(self, workspace_name, host_id): services = self._get_object(host_id, workspace_name).services - return ServiceSchema(many=True).dump(services) + return ServiceSchema(many=True).dump(services).data def _get_base_query(self, workspace_name): """Get services_count in one query and not deferred, that doe diff --git a/test_cases/conftest.py b/test_cases/conftest.py index c7fc136d708..4d74162d565 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -39,10 +39,11 @@ def open(self, *args, **kwargs): ] ret = super(CustomClient, self).open(*args, **kwargs) - try: - ret.json = json.loads(ret.data) - except ValueError: - ret.json = None + if ret.headers.get('content-type') == 'application/json': + try: + ret.json = json.loads(ret.data) + except ValueError: + ret.json = None return ret From 1132ddd934a190f5811d92f152ba27153a2f8a20 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 14 Sep 2017 15:42:13 -0300 Subject: [PATCH 0221/1506] Fix model to filter byu polymorphic identity --- server/models.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/server/models.py b/server/models.py index 30381cc1170..1a3661d229a 100644 --- a/server/models.py +++ b/server/models.py @@ -260,6 +260,7 @@ class VulnerabilityGeneric(VulnerabilityABC): class Vulnerability(VulnerabilityGeneric): + __tablename__ = None host_id = Column(Integer, ForeignKey(Host.id), index=True) host = relationship( 'Host', @@ -267,15 +268,13 @@ class Vulnerability(VulnerabilityGeneric): foreign_keys=[host_id], ) - service_id = Column(Integer, ForeignKey(Service.id)) - service = relationship( - 'Service', - backref='vulnerabilities', - ) + @declared_attr + def service_id(cls): + return VulnerabilityGeneric.__table__.c.get('service_id', Column(Integer, db.ForeignKey('vulnerability.service_id'))) - __table_args__ = { - 'extend_existing': True - } + @declared_attr + def service(cls): + return relationship('VulnerabilityGeneric') __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[0] @@ -283,6 +282,7 @@ class Vulnerability(VulnerabilityGeneric): class VulnerabilityWeb(VulnerabilityGeneric): + __tablename__ = None method = Column(String(50), nullable=True) parameters = Column(String(500), nullable=True) parameter_name = Column(String(250), nullable=True) @@ -292,15 +292,13 @@ class VulnerabilityWeb(VulnerabilityGeneric): response = Column(Text(), nullable=True) website = Column(String(250), nullable=True) - service_id = Column(Integer, ForeignKey(Service.id)) - service = relationship( - 'Service', - backref='vulnerabilities_web', - ) + @declared_attr + def service_id(cls): + return VulnerabilityGeneric.__table__.c.get('service_id', Column(Integer, db.ForeignKey('vulnerability.service_id'))) - __table_args__ = { - 'extend_existing': True - } + @declared_attr + def service(cls): + return relationship('VulnerabilityGeneric') __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[1] @@ -308,6 +306,7 @@ class VulnerabilityWeb(VulnerabilityGeneric): class VulnerabilityCode(VulnerabilityGeneric): + __tablename__ = None line = Column(Integer, nullable=True) source_code_id = Column(Integer, ForeignKey(SourceCode.id), index=True) @@ -317,10 +316,6 @@ class VulnerabilityCode(VulnerabilityGeneric): foreign_keys=[source_code_id] ) - __table_args__ = { - 'extend_existing': True - } - __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[2] } From 7cf3dad1d7f2f0345185f4c29e1112e1d793b89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Sep 2017 16:14:33 -0300 Subject: [PATCH 0222/1506] Allow List APIs to specify how a list of objects is rendered Via _envelope_list method --- server/api/base.py | 9 +++++++-- test_cases/test_api_non_workspaced_various.py | 20 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 36399069f8e..7a97a371769 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -147,9 +147,14 @@ def _validate_uniqueness(self, obj, object_id=None): class ListMixin(object): """Add GET / route""" + def _envelope_list(self, objects): + """Override this method to define how a list of objects is + rendered""" + return objects + def index(self, **kwargs): - return self._dump(self._get_base_query(**kwargs).all(), - many=True) + objects = self._get_base_query(**kwargs) + return self._envelope_list(self._dump(objects, many=True)) class ListWorkspacedMixin(ListMixin): diff --git a/test_cases/test_api_non_workspaced_various.py b/test_cases/test_api_non_workspaced_various.py index 37471b925e0..03563a487c4 100644 --- a/test_cases/test_api_non_workspaced_various.py +++ b/test_cases/test_api_non_workspaced_various.py @@ -1,11 +1,19 @@ #-*- coding: utf8 -*- - """Tests for many API endpoints that do not depend on workspace_name""" import pytest from test_cases import factories -from test_api_non_workspaced_base import ReadWriteAPITests +from test_api_non_workspaced_base import ReadWriteAPITests, API_PREFIX from server.models import License +from server.api.modules.licenses import LicenseView + +class LicenseEnvelopedView(LicenseView): + """A custom view to test that enveloping on generic views work ok""" + route_base = "test_envelope_list" + + def _envelope_list(self, objects): + return {"object_list": objects} + class TestLicensesAPI(ReadWriteAPITests): model = License @@ -14,4 +22,12 @@ class TestLicensesAPI(ReadWriteAPITests): # unique_fields = ['ip'] # update_fields = ['ip', 'description', 'os'] + def test_envelope_list(self, test_client, app): + LicenseEnvelopedView.register(app) + print app.url_map + original_res = test_client.get(self.url()) + assert original_res.status_code == 200 + new_res = test_client.get(API_PREFIX + 'test_envelope_list/') + assert new_res.status_code == 200 + assert new_res.json == {"object_list": original_res.json} From d02e5c7076f0329e60ad578de3d2dfa8024a7612 Mon Sep 17 00:00:00 2001 From: micabot Date: Thu, 14 Sep 2017 16:40:03 -0300 Subject: [PATCH 0223/1506] Fix typo, sort of --- server/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index d6ca63a8092..e7d6dd34cff 100644 --- a/server/importer.py +++ b/server/importer.py @@ -325,7 +325,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation service.banner = document.get('banner') service.protocol = document.get('protocol') if not document.get('status'): - logger.warning('Service {0} with empty status. Using open as status'.format(document['_id'])) + logger.warning('Service {0} with empty status. Using \'open\' as status'.format(document['_id'])) document['status'] = 'open' status_mapper = { 'open': 'open', From d187dc5a68390cb1c476bc093c4f53a941ebe126 Mon Sep 17 00:00:00 2001 From: micabot Date: Thu, 14 Sep 2017 16:40:22 -0300 Subject: [PATCH 0224/1506] Fix bug for vuln templates references The references field in vuln templates is either an empty list or a comma separated string with multiple references. --- server/importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index e7d6dd34cff..9124e90c36a 100644 --- a/server/importer.py +++ b/server/importer.py @@ -790,7 +790,8 @@ def run(self): severity=mapped_exploitation[document.get('exploitation', 'unclassified').lower()], description=document.get('description')) vuln_template.resolution = document.get('resolution') - for ref_doc in document['references']: + references = document['references'] if isinstance(document['references'], list) else [x.strip() for x in document['references'].split(',')] + for ref_doc in references: get_or_create(session, ReferenceTemplate, vulnerability=vuln_template, From 809a74bd92db3c584c440f0f2c611885401ae467 Mon Sep 17 00:00:00 2001 From: micabot Date: Thu, 14 Sep 2017 17:37:45 -0300 Subject: [PATCH 0225/1506] Fix Vuln templates import Now the importer normalizes the vuln template 'exploitation' to match one of the valid 'severity' values. --- server/importer.py | 57 +++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/server/importer.py b/server/importer.py index 9124e90c36a..b1c63c5807f 100644 --- a/server/importer.py +++ b/server/importer.py @@ -6,7 +6,7 @@ import sys import json import datetime -from collections import defaultdict +from collections import defaultdict, OrderedDict from binascii import unhexlify import requests @@ -56,6 +56,15 @@ logger = server.utils.logger.get_logger(__name__) session = db.session +MAPPED_VULN_SEVERITY = OrderedDict([ + ('critical', 'critical'), + ('high', 'high'), + ('med', 'medium'), + ('low', 'low'), + ('info', 'informational'), + ('unclassified', 'unclassified'), +]) + OBJ_TYPES = [ (1, 'Host'), (1, 'EntityMetadata'), @@ -348,14 +357,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation if not couch_parent_id: couch_parent_id = '.'.join(document['_id'].split('.')[:-1]) parent_ids = couchdb_relational_map[couch_parent_id] - mapped_severity = { - 'med': 'medium', - 'critical': 'critical', - 'high': 'high', - 'low': 'low', - 'info': 'informational', - 'unclassified': 'unclassified', - } + mapped_severity = MAPPED_VULN_SEVERITY severity = mapped_severity[document.get('severity')] for parent_id in parent_ids: if level == 2: @@ -769,25 +771,20 @@ def run(self): port=server.config.couchdb.port, path='cwe/_all_docs?include_docs=true' ) - cwes = requests.get(cwe_url).json()['rows'] + + try: + cwes = requests.get(cwe_url).json()['rows'] + except requests.exceptions.RequestException as e: + logger.warn(e) + return + for cwe in cwes: document = cwe['doc'] - mapped_exploitation = { - 'critical': 'critical', - 'med': 'medium', - 'high to very high': 'high', - 'high': 'high', - 'low': 'low', - 'info': 'informational', - 'unknown': 'unclassified', - 'unclassified': 'unclassified', - } - if document.get('exploitation') not in mapped_exploitation.values(): - logger.warn('Vuln template exploitation {0} not found. using unclassified'.format(document.get('exploitation'))) + new_severity = self.get_severity(document) vuln_template, created = get_or_create(session, VulnerabilityTemplate, name=document.get('name'), - severity=mapped_exploitation[document.get('exploitation', 'unclassified').lower()], + severity=new_severity, description=document.get('description')) vuln_template.resolution = document.get('resolution') references = document['references'] if isinstance(document['references'], list) else [x.strip() for x in document['references'].split(',')] @@ -797,6 +794,20 @@ def run(self): vulnerability=vuln_template, name=ref_doc) + def get_severity(self, document): + default = 'unclassified' + + mapped_exploitation = MAPPED_VULN_SEVERITY + + for key in mapped_exploitation.keys(): + if key in document.get('exploitation').lower(): + return mapped_exploitation[key] + + logger.warn( + 'Vuln template exploitation \'{0}\' not found. Using \'{1}\' instead.'.format(document.get('exploitation'), default) + ) + + return default class ImportLicense(FlaskScriptCommand): From 32e48d9b1535bc3688f7755c1a487e66304aa629 Mon Sep 17 00:00:00 2001 From: micabot Date: Thu, 14 Sep 2017 18:33:03 -0300 Subject: [PATCH 0226/1506] Fix vuln template duplication The vulnerability templates are queried by the user from the vulnerability creation dialog in the web UI. The only field the user has to pick one template over another is the name. Therefore, having more than one vuln template with the same name doesn't make sense. The new model doesn't allow duplication, but the old one did. The importer is now able to detect duplicated template names and appends "(n)" to the name with n being the number of repetition. --- server/importer.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/server/importer.py b/server/importer.py index b1c63c5807f..f84770077fb 100644 --- a/server/importer.py +++ b/server/importer.py @@ -6,7 +6,11 @@ import sys import json import datetime -from collections import defaultdict, OrderedDict +from collections import ( + Counter, + defaultdict, + OrderedDict +) from binascii import unhexlify import requests @@ -763,6 +767,9 @@ def run(self): class ImportVulnerabilityTemplates(FlaskScriptCommand): + def __init__(self): + self.names = Counter() + def run(self): cwe_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( username=server.config.couchdb.user, @@ -781,12 +788,17 @@ def run(self): for cwe in cwes: document = cwe['doc'] new_severity = self.get_severity(document) + + new_name = self.get_name(document) + vuln_template, created = get_or_create(session, VulnerabilityTemplate, - name=document.get('name'), - severity=new_severity, - description=document.get('description')) + name=new_name) + + vuln_template.description = document.get('description') vuln_template.resolution = document.get('resolution') + vuln_template.severity = new_severity + references = document['references'] if isinstance(document['references'], list) else [x.strip() for x in document['references'].split(',')] for ref_doc in references: get_or_create(session, @@ -794,6 +806,19 @@ def run(self): vulnerability=vuln_template, name=ref_doc) + def get_name(self, document): + doc_name = document.get('name') + count = self.names[doc_name] + + if count > 0: + name = u'{0} ({1})'.format(doc_name, count) + else: + name = doc_name + + self.names[doc_name] += 1 + + return name + def get_severity(self, document): default = 'unclassified' From 14e9beb01bdf9cae17804f3aee32b0c6e69cdadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Sep 2017 19:23:14 -0300 Subject: [PATCH 0227/1506] First attempt on API pagination --- server/api/base.py | 21 +++- server/api/modules/hosts.py | 5 +- test_cases/test_api_hosts.py | 10 +- test_cases/test_api_non_workspaced_various.py | 2 +- test_cases/test_api_pagination.py | 107 ++++++++++++++++++ test_cases/test_api_workspaced_base.py | 9 ++ 6 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 test_cases/test_api_pagination.py diff --git a/server/api/base.py b/server/api/base.py index 7a97a371769..d06bd7688dd 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -147,14 +147,29 @@ def _validate_uniqueness(self, obj, object_id=None): class ListMixin(object): """Add GET / route""" - def _envelope_list(self, objects): + def _envelope_list(self, objects, pagination_metadata=None): """Override this method to define how a list of objects is rendered""" return objects + def _paginate(self, query): + return query, None + def index(self, **kwargs): - objects = self._get_base_query(**kwargs) - return self._envelope_list(self._dump(objects, many=True)) + objects, pagination_metadata = self._paginate( + self._get_base_query(**kwargs)) + return self._envelope_list(self._dump(objects, many=True), + pagination_metadata) + + +class PaginatedMixin(object): + """Add pagination for list route""" + + def _paginate(self, query): + if 'per_page' in flask.request.args: + pagination_metadata = query.paginate() + return pagination_metadata.items, pagination_metadata + return super(PaginatedMixin, self)._paginate(query) class ListWorkspacedMixin(ListMixin): diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index c5001966f8c..4a7bd374c05 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -12,7 +12,7 @@ from server.utils.web import gzipped, validate_workspace,\ get_integer_parameter, filter_request_args from server.dao.host import HostDAO -from server.api.base import ReadWriteWorkspacedView +from server.api.base import ReadWriteWorkspacedView, PaginatedMixin from server.models import Host host_api = Blueprint('host_api', __name__) @@ -35,7 +35,8 @@ class ServiceSchema(Schema): status = fields.String(required=True) -class HostsView(ReadWriteWorkspacedView): +class HostsView(PaginatedMixin, + ReadWriteWorkspacedView): route_base = 'hosts' model_class = Host schema_class = HostSchema diff --git a/test_cases/test_api_hosts.py b/test_cases/test_api_hosts.py index c94a1cf79a0..c1e7feabb2c 100644 --- a/test_cases/test_api_hosts.py +++ b/test_cases/test_api_hosts.py @@ -2,8 +2,13 @@ from sqlalchemy.orm.util import was_deleted from test_cases import factories -from test_api_workspaced_base import API_PREFIX, ReadWriteAPITests +from test_api_workspaced_base import ( + API_PREFIX, + ReadWriteAPITests, + PaginationTestsMixin, +) from server.models import db, Host +from server.api.modules.hosts import HostsView HOSTS_COUNT = 5 SERVICE_COUNT = [10, 5] # 10 services to the first host, 5 to the second @@ -201,10 +206,11 @@ def test_index_shows_service_count(self, test_client, host_services): assert host['service_count'] == len(ids_map[host['id']]) -class TestHostAPIGeneric(ReadWriteAPITests): +class TestHostAPIGeneric(ReadWriteAPITests, PaginationTestsMixin): model = Host factory = factories.HostFactory api_endpoint = 'hosts' unique_fields = ['ip'] update_fields = ['ip', 'description', 'os'] + view_class = HostsView diff --git a/test_cases/test_api_non_workspaced_various.py b/test_cases/test_api_non_workspaced_various.py index 03563a487c4..30e299f4edb 100644 --- a/test_cases/test_api_non_workspaced_various.py +++ b/test_cases/test_api_non_workspaced_various.py @@ -11,7 +11,7 @@ class LicenseEnvelopedView(LicenseView): """A custom view to test that enveloping on generic views work ok""" route_base = "test_envelope_list" - def _envelope_list(self, objects): + def _envelope_list(self, objects, pagination_metadata=None): return {"object_list": objects} diff --git a/test_cases/test_api_pagination.py b/test_cases/test_api_pagination.py new file mode 100644 index 00000000000..134e70b13ee --- /dev/null +++ b/test_cases/test_api_pagination.py @@ -0,0 +1,107 @@ +#-*- coding: utf8 -*- + +"""Generic test mixins for APIs with pagination enabled when listing""" + +import pytest +from urllib import urlencode + +def with_0_and_n_objects(n=10): + return pytest.mark.parametrize('object_count', [0, n]) + +class PaginationTestsMixin: + per_page_parameter_name = 'per_page' + page_number_parameter_name = 'page' + view_class = None # Must be overriden + + @pytest.fixture + def delete_previously_created_objects(self, session): + from server.models import Host + for obj in self.objects: + session.delete(obj) + session.commit() + + @pytest.fixture + def custom_envelope(self, monkeypatch): + def _envelope_list(self, objects, pagination_metadata=None): + return {"data": objects} + monkeypatch.setattr(self.view_class, '_envelope_list', _envelope_list) + + @pytest.fixture + def pagination_test_logic(self, delete_previously_created_objects, + custom_envelope): + # Load this two fixtures + pass + + def create_many_objects(self, session, n): + objects = self.factory.create_batch(n) + session.commit() + return objects + + def page_url(self, page_number=None, per_page=None): + parameters = {} + if page_number is not None: + parameters[self.page_number_parameter_name] = page_number + if per_page is not None: + parameters[self.per_page_parameter_name] = per_page + return self.url() + '?' + urlencode(parameters) + + @pytest.mark.parametrize("page_number", [None, 1, 2]) + @pytest.mark.usefixtures('pagination_test_logic') + @pytest.mark.pagination + def test_returns_all_with_no_per_page(self, test_client, session, + page_number): + self.create_many_objects(session, 100) + res = test_client.get(self.page_url(page_number, + per_page=None)) + assert res.status_code == 200 + assert len(res.json['data']) == 100 + + @with_0_and_n_objects() + @pytest.mark.usefixtures('pagination_test_logic') + @pytest.mark.pagination + @pytest.mark.skip + def test_does_not_allow_negative_per_page(self, session, test_client, + object_count): + self.create_many_objects(session, object_count) + res = test_client.get(self.page_url(1, -1)) + assert res.status_code == 404 + + @with_0_and_n_objects() + @pytest.mark.usefixtures('pagination_test_logic') + @pytest.mark.pagination + @pytest.mark.skip + def test_does_not_allow_negative_page_number(self, session, test_client, + object_count): + self.create_many_objects(session, object_count) + res = test_client.get(self.page_url(-1, 10)) + assert res.status_code == 404 + + @pytest.mark.usefixtures('pagination_test_logic') + @pytest.mark.pagination + def test_pages_have_different_elements(self, session, test_client): + """Test correct page size, correct IDs and that there are + no duplicate items in different pages""" + ids = {getattr(obj, self.pk_field) + for obj in self.create_many_objects(session, 95)} + for page_number in range(1, 11): + res = test_client.get(self.page_url(page_number, 10)) + assert res.status_code == 200 + new_ids = {obj.get(self.pk_field) for obj in res.json['data']} + assert len(new_ids) == (5 if page_number == 10 else 10) + assert new_ids.issubset(ids) + ids.difference_update(new_ids) # Remove the new ids + assert not ids + + @pytest.mark.usefixtures('pagination_test_logic') + @pytest.mark.pagination + def test_404_on_page_with_no_elements(self, session, test_client): + self.create_many_objects(session, 5) + res = test_client.get(self.page_url(2, 5)) + assert res.status_code == 404 + + @pytest.mark.usefixtures('pagination_test_logic') + @pytest.mark.pagination + def test_succeed_on_first_page_with_no_elements(self, test_client): + res = test_client.get(self.page_url(1, 5)) + assert res.status_code == 200 + assert len(res.json['data']) == 0 diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py index d7d757ffd4f..aaad56805f8 100644 --- a/test_cases/test_api_workspaced_base.py +++ b/test_cases/test_api_workspaced_base.py @@ -5,6 +5,8 @@ import pytest from sqlalchemy.orm.util import was_deleted from server.models import db, Workspace +from test_api_pagination import PaginationTestsMixin as \ + OriginalPaginationTestsMixin API_PREFIX = '/v2/ws/' OBJECT_COUNT = 5 @@ -162,6 +164,13 @@ def test_delete_from_other_workspace_fails(self, test_client, assert self.model.query.count() == OBJECT_COUNT +class PaginationTestsMixin(OriginalPaginationTestsMixin): + def create_many_objects(self, session, n): + objects = self.factory.create_batch(n, workspace=self.workspace) + session.commit() + return objects + + class ReadWriteTestsMixin(ListTestsMixin, RetrieveTestsMixin, CreateTestsMixin, From 93c8083759c5cbe2282cae28d1309cc984a5b4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 15 Sep 2017 15:20:35 -0300 Subject: [PATCH 0228/1506] Unskip one pagination test, and add skip reason for the other --- test_cases/test_api_pagination.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test_cases/test_api_pagination.py b/test_cases/test_api_pagination.py index 134e70b13ee..9f614d3a7a9 100644 --- a/test_cases/test_api_pagination.py +++ b/test_cases/test_api_pagination.py @@ -56,10 +56,10 @@ def test_returns_all_with_no_per_page(self, test_client, session, assert res.status_code == 200 assert len(res.json['data']) == 100 + @pytest.mark.skip("TODO: Fix for sqlite and postgres") @with_0_and_n_objects() @pytest.mark.usefixtures('pagination_test_logic') @pytest.mark.pagination - @pytest.mark.skip def test_does_not_allow_negative_per_page(self, session, test_client, object_count): self.create_many_objects(session, object_count) @@ -69,7 +69,6 @@ def test_does_not_allow_negative_per_page(self, session, test_client, @with_0_and_n_objects() @pytest.mark.usefixtures('pagination_test_logic') @pytest.mark.pagination - @pytest.mark.skip def test_does_not_allow_negative_page_number(self, session, test_client, object_count): self.create_many_objects(session, object_count) From db2d0074b4d572d0545805785d6bad58feeeb96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 15 Sep 2017 15:36:31 -0300 Subject: [PATCH 0229/1506] Customizable pagination parameter names Also remove unused import in test_api_pagination --- server/api/base.py | 19 +++++++++++++++++-- test_cases/test_api_pagination.py | 8 +++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index d06bd7688dd..9167be66b6f 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -164,10 +164,25 @@ def index(self, **kwargs): class PaginatedMixin(object): """Add pagination for list route""" + per_page_parameter_name = 'page_size' + page_number_parameter_name = 'page' def _paginate(self, query): - if 'per_page' in flask.request.args: - pagination_metadata = query.paginate() + if self.per_page_parameter_name in flask.request.args: + + try: + page = int(flask.request.args.get( + self.page_number_parameter_name, 1)) + except (TypeError, ValueError): + flask.abort(404, 'Invalid page number') + + try: + per_page = int(flask.request.args[ + self.per_page_parameter_name]) + except (TypeError, ValueError): + flask.abort(404, 'Invalid per_page value') + + pagination_metadata = query.paginate(page=page, per_page=per_page) return pagination_metadata.items, pagination_metadata return super(PaginatedMixin, self)._paginate(query) diff --git a/test_cases/test_api_pagination.py b/test_cases/test_api_pagination.py index 9f614d3a7a9..54c03a3ed97 100644 --- a/test_cases/test_api_pagination.py +++ b/test_cases/test_api_pagination.py @@ -9,13 +9,10 @@ def with_0_and_n_objects(n=10): return pytest.mark.parametrize('object_count', [0, n]) class PaginationTestsMixin: - per_page_parameter_name = 'per_page' - page_number_parameter_name = 'page' view_class = None # Must be overriden @pytest.fixture def delete_previously_created_objects(self, session): - from server.models import Host for obj in self.objects: session.delete(obj) session.commit() @@ -40,9 +37,10 @@ def create_many_objects(self, session, n): def page_url(self, page_number=None, per_page=None): parameters = {} if page_number is not None: - parameters[self.page_number_parameter_name] = page_number + parameters[ + self.view_class.page_number_parameter_name] = page_number if per_page is not None: - parameters[self.per_page_parameter_name] = per_page + parameters[self.view_class.per_page_parameter_name] = per_page return self.url() + '?' + urlencode(parameters) @pytest.mark.parametrize("page_number", [None, 1, 2]) From 87a521826bbc01ffd657caacc3ba63f987a6ba0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 18 Sep 2017 13:06:49 -0300 Subject: [PATCH 0230/1506] Automatic marshmallow schema generation Perform field instrospection based on the SQLAlchemy model fields. Code based on marshmallow_sqlalchemy --- requirements_server.txt | 1 + server/api/base.py | 13 +++++++++++++ server/api/modules/hosts.py | 32 ++++++++++++++++---------------- server/api/modules/licenses.py | 14 +++++++------- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/requirements_server.txt b/requirements_server.txt index 7c9bacffd8a..31e614951be 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -15,3 +15,4 @@ Flask-Script==2.0.5 flask-classful marshmallow webargs +marshmallow-sqlalchemy diff --git a/server/api/base.py b/server/api/base.py index 9167be66b6f..abc855116b4 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -5,6 +5,9 @@ from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.inspection import inspect from werkzeug.routing import parse_rule +from marshmallow import Schema +from marshmallow.compat import with_metaclass +from marshmallow_sqlalchemy.schema import ModelSchemaMeta, ModelSchemaOpts from webargs.flaskparser import FlaskParser, abort from webargs.core import ValidationError from server.models import Workspace, db @@ -317,3 +320,13 @@ class ReadWriteWorkspacedView(CreateWorkspacedMixin, """A generic workspaced view with list, retrieve and create endpoints""" pass + + +class AutoSchema(with_metaclass(ModelSchemaMeta, Schema)): + """ + A Marshmallow schema that does field introspection based on + the SQLAlchemy model specified in Meta.model. + Unlike the marshmallow_sqlalchemy ModelSchema, it doesn't change + the serialization and deserialization proccess. + """ + OPTIONS_CLASS = ModelSchemaOpts diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index 4a7bd374c05..ccad341eb7a 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -5,34 +5,34 @@ import flask from flask import Blueprint from flask_classful import route -from marshmallow import Schema, fields +from marshmallow import fields from sqlalchemy.orm import undefer from server.utils.logger import get_logger from server.utils.web import gzipped, validate_workspace,\ get_integer_parameter, filter_request_args from server.dao.host import HostDAO -from server.api.base import ReadWriteWorkspacedView, PaginatedMixin -from server.models import Host +from server.api.base import ReadWriteWorkspacedView, PaginatedMixin, AutoSchema +from server.models import Host, Service host_api = Blueprint('host_api', __name__) -class HostSchema(Schema): - id = fields.Integer(required=True, dump_only=True) - ip = fields.String(required=True) - description = fields.String(required=True) - os = fields.String() - service_count = fields.Integer() +class HostSchema(AutoSchema): + description = fields.String(required=True) # Explicitly set required=True + service_count = fields.Integer(dump_only=True) -class ServiceSchema(Schema): - id = fields.Integer(required=True, dump_only=True) - name = fields.String(required=True) - description = fields.String(required=False) - port = fields.Integer(required=True) - protocol = fields.String(required=True) - status = fields.String(required=True) + class Meta: + model = Host + fields = ('id', 'ip', 'description', 'os', 'service_count') + + +class ServiceSchema(AutoSchema): + + class Meta: + model = Service + fields = ('id', 'name', 'description', 'port', 'protocol', 'status') class HostsView(PaginatedMixin, diff --git a/server/api/modules/licenses.py b/server/api/modules/licenses.py index 914f3177aab..fbc59954b23 100644 --- a/server/api/modules/licenses.py +++ b/server/api/modules/licenses.py @@ -4,19 +4,19 @@ import flask from flask import Blueprint -from marshmallow import Schema, fields +from marshmallow import fields from server.models import License -from server.api.base import ReadWriteView +from server.api.base import ReadWriteView, AutoSchema license_api = Blueprint('license_api', __name__) -class LicenseSchema(Schema): - id = fields.Integer(required=True, dump_only=True) - product = fields.String(required=True) - start_date = fields.DateTime(required=True) - end_date = fields.DateTime(required=True) +class LicenseSchema(AutoSchema): + + class Meta: + model = License + fields = ('id', 'product', 'start_date', 'end_date') class LicenseView(ReadWriteView): From 82b06d6b5e97157efc85a444d482de6112f4e5e6 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 18 Sep 2017 15:20:16 -0300 Subject: [PATCH 0231/1506] Fix workspace scope The scope used to be a text field but was supposed to be a list. Replaced by a list and fixed the migration script to interpret every new line of the scope as an entry. --- server/dao/workspace.py | 6 +++--- server/importer.py | 34 ++++++++++++++++++---------------- server/models.py | 23 +++++++++++++++++++++-- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/server/dao/workspace.py b/server/dao/workspace.py index d373e736fd2..799018aded5 100644 --- a/server/dao/workspace.py +++ b/server/dao/workspace.py @@ -25,7 +25,7 @@ class WorkspaceDAO(object): "end_date": [Workspace.end_date], "name": [Workspace.name], "public": [Workspace.public], - "scope": [Workspace.scope], + # "scope": [Workspace.scope], "start_date": [Workspace.start_date] } @@ -72,7 +72,7 @@ def __query_database(self, search=None, page=0, page_size=0, Workspace.end_date, Workspace.name, Workspace.public, - Workspace.scope, + # Workspace.scope, Workspace.start_date, ) @@ -100,6 +100,6 @@ def __get_workspace_data(self, workspace): "end_date": workspace.end_date, "name": workspace.name, "public": workspace.public, - "scope": workspace.scope, + # "scope": workspace.scope, "start_date": workspace.start_date } diff --git a/server/importer.py b/server/importer.py index f84770077fb..158ab9a2f5e 100644 --- a/server/importer.py +++ b/server/importer.py @@ -27,28 +27,29 @@ import server.utils.logger from server.models import ( db, - EntityMetadata, + Command, + Comment, + CommentObject, Credential, + EntityMetadata, + ExecutiveReport, Host, - Service, - Reference, - Command, - Workspace, Hostname, - Vulnerability, - VulnerabilityWeb, - User, + License, + Methodology, + MethodologyTemplate, PolicyViolation, + Reference, + ReferenceTemplate, + Service, + Scope, Task, TaskTemplate, - Methodology, - MethodologyTemplate, - ExecutiveReport, + User, + Vulnerability, VulnerabilityTemplate, - ReferenceTemplate, - License, - Comment, - CommentObject, + VulnerabilityWeb, + Workspace, ) from server.utils.database import get_or_create from server.web import app @@ -541,7 +542,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation workspace.start_date = datetime.datetime.fromtimestamp(float(document.get('duration')['start'])/1000) if document.get('duration') and document.get('duration')['end']: workspace.end_date = datetime.datetime.fromtimestamp(float(document.get('duration')['end'])/1000) - workspace.scope = document.get('scope') + for scope in [x.strip() for x in document.get('scope', '').split('\n') if x.strip()]: + scope_obj, created = get_or_create(session, Scope, name=scope, workspace=workspace) yield workspace diff --git a/server/models.py b/server/models.py index b545bbde972..a6d926d4c00 100644 --- a/server/models.py +++ b/server/models.py @@ -522,17 +522,36 @@ class Command(Metadata): class Workspace(Metadata): __tablename__ = 'workspace' id = Column(Integer, primary_key=True) - # TODO: change nullable=True for appropriate fields customer = Column(String(250), nullable=True) # TBI description = Column(Text(), nullable=True) active = Column(Boolean(), nullable=False, default=True) # TBI end_date = Column(DateTime(), nullable=True) name = Column(String(250), nullable=False, unique=True) public = Column(Boolean(), nullable=False, default=True) # TBI - scope = Column(Text(), nullable=True) start_date = Column(DateTime(), nullable=True) +class Scope(Metadata): + __tablename__ = 'scope' + id = Column(Integer, primary_key=True) + name = Column(Text(), nullable=False) + + workspace_id = Column( + Integer, + ForeignKey('workspace.id'), + index=True, + nullable=False + ) + workspace = relationship( + 'Workspace', + backref='scope', + foreign_keys=[workspace_id], + ) + + __table_args__ = ( + UniqueConstraint('name', 'workspace_id', name='uix_scope_name_workspace'), + ) + def is_valid_workspace(workspace_name): return db.session.query(server.models.Workspace).filter_by(name=workspace_name).first() is not None From c83b4864930bc0125ef6a44c015de2d599eccfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 18 Sep 2017 15:30:33 -0300 Subject: [PATCH 0232/1506] Add integration with filteralchemy Allow to filter by os (exact) in hosts view --- requirements_server.txt | 1 + server/api/base.py | 22 ++++++++++++++++++++-- server/api/modules/hosts.py | 18 +++++++++++++++++- test_cases/test_api_hosts.py | 24 +++++++++++++++++++++++- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/requirements_server.txt b/requirements_server.txt index 31e614951be..30ef8817d93 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -16,3 +16,4 @@ flask-classful marshmallow webargs marshmallow-sqlalchemy +filteralchemy diff --git a/server/api/base.py b/server/api/base.py index abc855116b4..b459f6c3a73 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -158,9 +158,13 @@ def _envelope_list(self, objects, pagination_metadata=None): def _paginate(self, query): return query, None + def _filter_query(self, query): + """Return a new SQLAlchemy query with some filters applied""" + return query + def index(self, **kwargs): - objects, pagination_metadata = self._paginate( - self._get_base_query(**kwargs)) + query = self._filter_query(self._get_base_query(**kwargs)) + objects, pagination_metadata = self._paginate(query) return self._envelope_list(self._dump(objects, many=True), pagination_metadata) @@ -190,6 +194,20 @@ def _paginate(self, query): return super(PaginatedMixin, self)._paginate(query) +class FilterAlchemyMixin(object): + """Add querystring parameter filtering to list route + + It is done by setting the ViewClass.filterset_class class + attribute + """ + + filterset_class = None + + def _filter_query(self, query): + assert self.filterset_class is not None, 'You must define a filterset' + return self.filterset_class(query).filter() + + class ListWorkspacedMixin(ListMixin): """Add GET // route""" # There are no differences with the non-workspaced implementations. The code diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index ccad341eb7a..f90443b3912 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -6,13 +6,20 @@ from flask import Blueprint from flask_classful import route from marshmallow import fields +from filteralchemy import FilterSet from sqlalchemy.orm import undefer +from webargs.flaskparser import parser from server.utils.logger import get_logger from server.utils.web import gzipped, validate_workspace,\ get_integer_parameter, filter_request_args from server.dao.host import HostDAO -from server.api.base import ReadWriteWorkspacedView, PaginatedMixin, AutoSchema +from server.api.base import ( + ReadWriteWorkspacedView, + PaginatedMixin, + AutoSchema, + FilterAlchemyMixin, +) from server.models import Host, Service host_api = Blueprint('host_api', __name__) @@ -28,6 +35,13 @@ class Meta: fields = ('id', 'ip', 'description', 'os', 'service_count') +class HostFilterSet(FilterSet): + class Meta: + model = Host + fields = ('os',) + parser = parser + + class ServiceSchema(AutoSchema): class Meta: @@ -36,11 +50,13 @@ class Meta: class HostsView(PaginatedMixin, + FilterAlchemyMixin, ReadWriteWorkspacedView): route_base = 'hosts' model_class = Host schema_class = HostSchema unique_fields = ['ip'] + filterset_class = HostFilterSet @route('//services/') def service_list(self, workspace_name, host_id): diff --git a/test_cases/test_api_hosts.py b/test_cases/test_api_hosts.py index c1e7feabb2c..3581fa32c56 100644 --- a/test_cases/test_api_hosts.py +++ b/test_cases/test_api_hosts.py @@ -51,6 +51,14 @@ def url(self, host=None, workspace=None): def services_url(self, host, workspace=None): return self.url(host, workspace) + 'services/' + def compare_results(self, hosts, response): + """ + Compare is the hosts in response are the same that in hosts. + It only compares the IDs of each one, not other fields""" + hosts_in_list = set(host.id for host in hosts) + hosts_in_response = set(host['id'] for host in response.json) + assert hosts_in_list == hosts_in_response + def test_list_retrieves_all_items_from_workspace(self, test_client, second_workspace, session, @@ -205,6 +213,21 @@ def test_index_shows_service_count(self, test_client, host_services): if host['id'] in ids_map: assert host['service_count'] == len(ids_map[host['id']]) + def test_filter_by_os_exact(self, test_client, session, workspace, + second_workspace, host_factory): + # The hosts that should be shown + hosts = host_factory.create_batch(10, workspace=workspace, os='Unix') + + # Search should be case sensitive so this shouln't be shown + host_factory.create_batch(1, workspace=workspace, os='UNIX') + + # This shouldn't be shown, they are from other workspace + host_factory.create_batch(5, workspace=second_workspace, os='Unix') + + res = test_client.get(self.url() + '?os=Unix') + assert res.status_code == 200 + self.compare_results(hosts, res) + class TestHostAPIGeneric(ReadWriteAPITests, PaginationTestsMixin): model = Host @@ -213,4 +236,3 @@ class TestHostAPIGeneric(ReadWriteAPITests, PaginationTestsMixin): unique_fields = ['ip'] update_fields = ['ip', 'description', 'os'] view_class = HostsView - From 680b2fb690e7b342a39099081c91c5b8b37cafe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 18 Sep 2017 16:23:37 -0300 Subject: [PATCH 0233/1506] Add like and ilike filters to Host.os Changed SQLite engine connector to enable case sensitive LIKEs --- server/api/modules/hosts.py | 3 +- server/models.py | 56 ++++++++++++++++++++---------------- test_cases/test_api_hosts.py | 27 ++++++++++++++++- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index f90443b3912..ca6bc694a75 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -6,7 +6,7 @@ from flask import Blueprint from flask_classful import route from marshmallow import fields -from filteralchemy import FilterSet +from filteralchemy import FilterSet, operators from sqlalchemy.orm import undefer from webargs.flaskparser import parser @@ -40,6 +40,7 @@ class Meta: model = Host fields = ('os',) parser = parser + operators = (operators.Equal, operators.Like, operators.ILike) class ServiceSchema(AutoSchema): diff --git a/server/models.py b/server/models.py index efaeb1af98e..7f67245c606 100644 --- a/server/models.py +++ b/server/models.py @@ -49,31 +49,37 @@ def make_connector(self, app=None, bind=None): class CustomEngineConnector(_EngineConnector): - """Used by overrideb SQLAlchemy class to fix rollback issues""" - - def get_engine(self): - # Use an existent engine and don't register events if possible - uri = self.get_uri() - echo = self._app.config['SQLALCHEMY_ECHO'] - if (uri, echo) == self._connected_for: - return self._engine - - # Call original metohd and register events - rv = super(CustomEngineConnector, self).get_engine() - if uri.startswith('sqlite://'): - with self._lock: - @event.listens_for(rv, "connect") - def do_connect(dbapi_connection, connection_record): - # disable pysqlite's emitting of the BEGIN statement - # entirely. also stops it from emitting COMMIT before any - # DDL. - dbapi_connection.isolation_level = None - - @event.listens_for(rv, "begin") - def do_begin(conn): - # emit our own BEGIN - conn.execute("BEGIN") - return rv + """Used by overrided SQLAlchemy class to fix rollback issues. + + Also set case sensitive likes (in SQLite there are case + insensitive by default)""" + + def get_engine(self): + # Use an existent engine and don't register events if possible + uri = self.get_uri() + echo = self._app.config['SQLALCHEMY_ECHO'] + if (uri, echo) == self._connected_for: + return self._engine + + # Call original metohd and register events + rv = super(CustomEngineConnector, self).get_engine() + if uri.startswith('sqlite://'): + with self._lock: + @event.listens_for(rv, "connect") + def do_connect(dbapi_connection, connection_record): + # disable pysqlite's emitting of the BEGIN statement + # entirely. also stops it from emitting COMMIT before any + # DDL. + dbapi_connection.isolation_level = None + cursor = dbapi_connection.cursor() + cursor.execute("PRAGMA case_sensitive_like=true") + cursor.close() + + @event.listens_for(rv, "begin") + def do_begin(conn): + # emit our own BEGIN + conn.execute("BEGIN") + return rv db = SQLAlchemy() diff --git a/test_cases/test_api_hosts.py b/test_cases/test_api_hosts.py index 3581fa32c56..ed1594d2664 100644 --- a/test_cases/test_api_hosts.py +++ b/test_cases/test_api_hosts.py @@ -224,10 +224,35 @@ def test_filter_by_os_exact(self, test_client, session, workspace, # This shouldn't be shown, they are from other workspace host_factory.create_batch(5, workspace=second_workspace, os='Unix') - res = test_client.get(self.url() + '?os=Unix') + url = self.url() + '?os=Unix' + res = test_client.get(url) assert res.status_code == 200 self.compare_results(hosts, res) + def test_filter_by_os_like_ilike(self, test_client, session, workspace, + second_workspace, host_factory): + # The hosts that should be shown + hosts = host_factory.create_batch(5, workspace=workspace, os='Unix 1') + hosts += host_factory.create_batch(5, workspace=workspace, os='Unix 2') + + # This should only be shown when using ilike, not when using like + case_insensitive_host = host_factory.create( + workspace=workspace, os='UNIX 3') + + # This doesn't match the like expression + host_factory.create(workspace=workspace, os="Test Unix 1") + + # This shouldn't be shown anywhere, they are from other workspace + host_factory.create_batch(5, workspace=second_workspace, os='Unix') + + res = test_client.get(self.url() + '?os__like=Unix %') + assert res.status_code == 200 + self.compare_results(hosts, res) + + res = test_client.get(self.url() + '?os__ilike=Unix %') + assert res.status_code == 200 + self.compare_results(hosts + [case_insensitive_host], res) + class TestHostAPIGeneric(ReadWriteAPITests, PaginationTestsMixin): model = Host From 60711dab2e630b4f9389ae1f9fac56828de69d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 18 Sep 2017 16:42:41 -0300 Subject: [PATCH 0234/1506] Move FilterSet parser declaration to base class To improve future refactors --- server/api/base.py | 7 ++++++- server/api/modules/hosts.py | 5 ++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index b459f6c3a73..2f9139b4f11 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -8,7 +8,7 @@ from marshmallow import Schema from marshmallow.compat import with_metaclass from marshmallow_sqlalchemy.schema import ModelSchemaMeta, ModelSchemaOpts -from webargs.flaskparser import FlaskParser, abort +from webargs.flaskparser import FlaskParser, parser, abort from webargs.core import ValidationError from server.models import Workspace, db @@ -348,3 +348,8 @@ class AutoSchema(with_metaclass(ModelSchemaMeta, Schema)): the serialization and deserialization proccess. """ OPTIONS_CLASS = ModelSchemaOpts + + +class FilterSetMeta: + """Base Meta class of FilterSet objects""" + parser = parser diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index ca6bc694a75..9e842b4c2d7 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -8,7 +8,6 @@ from marshmallow import fields from filteralchemy import FilterSet, operators from sqlalchemy.orm import undefer -from webargs.flaskparser import parser from server.utils.logger import get_logger from server.utils.web import gzipped, validate_workspace,\ @@ -19,6 +18,7 @@ PaginatedMixin, AutoSchema, FilterAlchemyMixin, + FilterSetMeta, ) from server.models import Host, Service @@ -36,10 +36,9 @@ class Meta: class HostFilterSet(FilterSet): - class Meta: + class Meta(FilterSetMeta): model = Host fields = ('os',) - parser = parser operators = (operators.Equal, operators.Like, operators.ILike) From b63d0e5c0fa20534d93d6dc0cb197c6eb679c2b0 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 18 Sep 2017 16:56:33 -0300 Subject: [PATCH 0235/1506] Add Risk to Vulnerabilities All vulns (including templates) need an additional field 'risk', a float between 1.0 and 10.0. --- server/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/models.py b/server/models.py index a6d926d4c00..ce74901b34c 100644 --- a/server/models.py +++ b/server/models.py @@ -213,6 +213,7 @@ class VulnerabilityABC(Metadata): name = Column(Text, nullable=False) resolution = Column(Text, nullable=True) severity = Column(Enum(*SEVERITIES, name='vulnerability_severity'), nullable=False) + risk = Column(Float(3,1), nullable=True) # TODO add evidence impact_accountability = Column(Boolean, default=False) @@ -220,6 +221,11 @@ class VulnerabilityABC(Metadata): impact_confidentiality = Column(Boolean, default=False) impact_integrity = Column(Boolean, default=False) + __table_args__ = ( + CheckConstraint('1.0 <= risk AND risk <= 10.0', + name='check_vulnerability_risk'), + ) + class VulnerabilityTemplate(VulnerabilityABC): __tablename__ = 'vulnerability_template' From 2568ce1f3c6df9cf20433dd2fe7fe8deada349a6 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 18 Sep 2017 17:14:14 -0300 Subject: [PATCH 0236/1506] Add missing fields in Vuln Code --- server/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index ce74901b34c..9df4dea75f3 100644 --- a/server/models.py +++ b/server/models.py @@ -82,6 +82,8 @@ class SourceCode(Metadata): __tablename__ = 'source_code' id = Column(Integer, primary_key=True) filename = Column(Text, nullable=False) + function = Column(Text, nullable=True) + module = Column(Text, nullable=True) workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False) workspace = relationship('Workspace', backref='source_codes') @@ -315,7 +317,9 @@ class VulnerabilityWeb(VulnerabilityGeneric): class VulnerabilityCode(VulnerabilityGeneric): - line = Column(Integer, nullable=True) + code = Column(Text, nullable=True) + start_line = Column(Integer, nullable=True) + end_line = Column(Integer, nullable=True) source_code_id = Column(Integer, ForeignKey(SourceCode.id), index=True) source_code = relationship( From f0a03031303d6cc1076cdef03b933ccccf03aa38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 18 Sep 2017 18:41:14 -0300 Subject: [PATCH 0237/1506] Move Host.service_count definition inside Host --- server/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/models.py b/server/models.py index 7f67245c606..9633f961c39 100644 --- a/server/models.py +++ b/server/models.py @@ -174,6 +174,12 @@ class Host(Metadata): backref='hosts', foreign_keys=[workspace_id] ) + + service_count = column_property((select([func.count('service.id')]). + select_from('service'). + where("service.host_id = host.id") + ), deferred=True) + __table_args__ = ( UniqueConstraint(ip, workspace_id, name='uix_host_ip_workspace'), ) @@ -238,12 +244,6 @@ class Service(Metadata): UniqueConstraint(port, protocol, host_id, workspace_id, name='uix_service_port_protocol_host_workspace'), ) -# TODO: Move this to Host definition. I need a way to reference Service before -# it is declared -Host.service_count = column_property((select([func.count(Service.id)]). - where(Service.host_id == Host.id)#.label('service_count') - ), deferred=True) - class VulnerabilityABC(Metadata): # revisar plugin nexpose, netspark para terminar de definir uniques. asegurar que se carguen bien From 4f216962e971cf34eb18d32eea63ba860be6882c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 19 Sep 2017 12:40:07 -0300 Subject: [PATCH 0238/1506] Fix a bug on foreign key column name --- server/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/models.py b/server/models.py index 7ee1140e6e9..580bd26547d 100644 --- a/server/models.py +++ b/server/models.py @@ -324,7 +324,7 @@ class Vulnerability(VulnerabilityGeneric): @declared_attr def service_id(cls): - return VulnerabilityGeneric.__table__.c.get('service_id', Column(Integer, db.ForeignKey('vulnerability.service_id'))) + return VulnerabilityGeneric.__table__.c.get('service_id', Column(Integer, db.ForeignKey('service.id'))) @declared_attr def service(cls): @@ -348,7 +348,7 @@ class VulnerabilityWeb(VulnerabilityGeneric): @declared_attr def service_id(cls): - return VulnerabilityGeneric.__table__.c.get('service_id', Column(Integer, db.ForeignKey('vulnerability.service_id'))) + return VulnerabilityGeneric.__table__.c.get('service_id', Column(Integer, db.ForeignKey('service.id'))) @declared_attr def service(cls): From fa5138606874fb9c57138e162e659bc6bda55094 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 19 Sep 2017 14:11:57 -0300 Subject: [PATCH 0239/1506] Add DB Schema files to Git Ignore file --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dfc6eca8658..9de77059c56 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,8 @@ jsconfig.json .idea # vFeed DB -data/vfeed.db \ No newline at end of file +data/vfeed.db + +# Images based on DB Schema +entity_dbschema.png +uml_schema.png From 31604c649bf60fb06df281e6dadb8a2da7c91c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Sep 2017 14:33:35 -0300 Subject: [PATCH 0240/1506] Fix bugs in service relationship of vulns --- server/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/models.py b/server/models.py index 580bd26547d..a0cfae5e4b3 100644 --- a/server/models.py +++ b/server/models.py @@ -328,7 +328,7 @@ def service_id(cls): @declared_attr def service(cls): - return relationship('VulnerabilityGeneric') + return relationship('Service') __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[0] @@ -352,7 +352,7 @@ def service_id(cls): @declared_attr def service(cls): - return relationship('VulnerabilityGeneric') + return relationship('Service') __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[1] From 5083aadf32c729e7dc128a7b9c9cf02327e33254 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 19 Sep 2017 14:44:02 -0300 Subject: [PATCH 0241/1506] Fix warning when some DBs are missing The Licenses and Vuln Templates Databases may be missing, the user should receive a nice Warning instead of a stack trace and failure. --- server/importer.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/server/importer.py b/server/importer.py index 158ab9a2f5e..51d0e307f68 100644 --- a/server/importer.py +++ b/server/importer.py @@ -558,7 +558,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation methodology, created = get_or_create(session, Methodology, name=document.get('name')) methodology.workspace = workspace yield methodology -# methodology. class TaskImporter(object): @@ -782,12 +781,16 @@ def run(self): ) try: - cwes = requests.get(cwe_url).json()['rows'] + cwes = requests.get(cwe_url) + cwes.raise_for_status() + except requests.exceptions.HTTPError: + logger.warn('Unable to retrieve Vulnerability Templates Database. Moving on.') + return except requests.exceptions.RequestException as e: logger.warn(e) return - for cwe in cwes: + for cwe in cwes.json()['rows']: document = cwe['doc'] new_severity = self.get_severity(document) @@ -839,7 +842,7 @@ def get_severity(self, document): class ImportLicense(FlaskScriptCommand): def run(self): - cwe_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( + licenses_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( username=server.config.couchdb.user, password=server.config.couchdb.password, hostname=server.config.couchdb.host, @@ -847,17 +850,17 @@ def run(self): path='faraday_licenses/_all_docs?include_docs=true' ) - if not requests.head(cwe_url).ok: - logger.info('No Licenses database found, nothing to see here, move along!') - return - try: - licenses = requests.get(cwe_url).json()['rows'] + licenses = requests.get(licenses_url) + licenses.raise_for_status() + except requests.exceptions.HTTPError: + logger.warn('Unable to retrieve Licenses Database. Moving on.') + return except requests.exceptions.RequestException as e: logger.warn(e) return - for license in licenses: + for license in licenses.json()['rows']: document = license['doc'] license_obj, created = get_or_create(session, From 169b229de90a9f3a8a17fe0395ed0ab59f527fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Sep 2017 14:52:00 -0300 Subject: [PATCH 0242/1506] Add vuln web, vuln code and source code factories Also sort factories alphabically (when using or importing them, not in its declaration) --- test_cases/conftest.py | 9 ++++++--- test_cases/factories.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 4d74162d565..fa34ea28a2b 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -14,13 +14,16 @@ enabled_factories = [ - factories.WorkspaceFactory, + factories.CredentialFactory, + factories.LicenseFactory, factories.HostFactory, factories.ServiceFactory, + factories.SourceCodeFactory, factories.VulnerabilityFactory, - factories.CredentialFactory, - factories.LicenseFactory, + factories.VulnerabilityCodeFactory, + factories.VulnerabilityWebFactory, factories.UserFactory, + factories.WorkspaceFactory, ] for factory in enabled_factories: register(factory) diff --git a/test_cases/factories.py b/test_cases/factories.py index 33451deae62..b0240266311 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -14,8 +14,11 @@ Host, License, Service, + SourceCode, User, Vulnerability, + VulnerabilityCode, + VulnerabilityWeb, Workspace, ) @@ -113,12 +116,20 @@ class Meta: sqlalchemy_session = db.session +class SourceCodeFactory(WorkspaceObjectFactory): + filename = FuzzyText() + + class Meta: + model = SourceCode + sqlalchemy_session = db.session + + class VulnerabilityFactory(WorkspaceObjectFactory): name = FuzzyText() description = FuzzyText() - # host = factory.SubFactory(HostFactory) - # service = factory.SubFactory(ServiceFactory) + # host = factory.SubFactory(HostFactory) # TODO: Move to generic class + # service = factory.SubFactory(ServiceFactory) # TODO: Move to generic class workspace = factory.SubFactory(WorkspaceFactory) creator = factory.SubFactory(UserFactory) severity = FuzzyChoice(['critical', 'high']) @@ -128,6 +139,25 @@ class Meta: sqlalchemy_session = db.session +class VulnerabilityWebFactory(VulnerabilityFactory): + method = FuzzyChoice(['GET', 'POST', 'PUT', 'PATCH' 'DELETE']) + parameter_name = FuzzyText() + service = factory.SubFactory(ServiceFactory) + + class Meta: + model = VulnerabilityWeb + sqlalchemy_session = db.session + + +class VulnerabilityCodeFactory(VulnerabilityFactory): + line = FuzzyInteger(1, 5000) + source_code = factory.SubFactory(SourceCodeFactory) + + class Meta: + model = VulnerabilityCode + sqlalchemy_session = db.session + + class CredentialFactory(WorkspaceObjectFactory): username = FuzzyText() password = FuzzyText() From 914c1475868561faec42ad3d5260e71414b0da78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Sep 2017 15:41:18 -0300 Subject: [PATCH 0243/1506] Add vuln count properties to workspaces --- server/models.py | 18 +++++++++++ test_cases/models/test_workspace.py | 47 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 test_cases/models/test_workspace.py diff --git a/server/models.py b/server/models.py index fa496ea4aba..8b37c5ab52d 100644 --- a/server/models.py +++ b/server/models.py @@ -573,6 +573,19 @@ class Command(Metadata): ) +def make_vuln_count_property(type_=None): + query = (select([func.count('vulnerability.id')]). + select_from('vulnerability'). + where('vulnerability.workspace_id = workspace.id') + ) + if type_: + # Don't do queries using this style! + # This can cause SQL injection vulnerabilities + # In this case type_ is supplied from a whitelist so this is safe + query = query.where("vulnerability.type = '%s'" % type_) + return column_property(query, deferred=True) + + class Workspace(Metadata): __tablename__ = 'workspace' id = Column(Integer, primary_key=True) @@ -586,6 +599,11 @@ class Workspace(Metadata): scope = Column(Text(), nullable=True) start_date = Column(DateTime(), nullable=True) + vulnerability_web_count = make_vuln_count_property('vulnerability_web') + vulnerability_code_count = make_vuln_count_property('vulnerability_code') + vulnerability_standard_count = make_vuln_count_property('vulnerability') + vulnerability_total_count = make_vuln_count_property() + def is_valid_workspace(workspace_name): return db.session.query(server.models.Workspace).filter_by(name=workspace_name).first() is not None diff --git a/test_cases/models/test_workspace.py b/test_cases/models/test_workspace.py new file mode 100644 index 00000000000..5c4c757926a --- /dev/null +++ b/test_cases/models/test_workspace.py @@ -0,0 +1,47 @@ +import pytest +from server.models import db +from test_cases.factories import ( + HostFactory, + ServiceFactory, + SourceCodeFactory, + VulnerabilityFactory, + VulnerabilityCodeFactory, + VulnerabilityWebFactory, +) + +SOURCE_CODE_VULN_COUNT = 3 +STANDARD_VULN_COUNT = [6, 2] # With host parent and with service parent +WEB_VULN_COUNT = 5 + + +def populate_workspace(workspace): + host = HostFactory.create(workspace=workspace) + service = ServiceFactory.create(workspace=workspace, host=host) + code = SourceCodeFactory.create(workspace=workspace) + + # Create standard vulns + host_vulns = VulnerabilityFactory.create_batch( + STANDARD_VULN_COUNT[0], workspace=workspace, host=host) + service_vulns = VulnerabilityFactory.create_batch( + STANDARD_VULN_COUNT[1], workspace=workspace, service=service) + + # Create web vulns + web_vulns = VulnerabilityWebFactory.create_batch( + WEB_VULN_COUNT, workspace=workspace, service=service) + + # Create source code vulns + code_vulns = VulnerabilityCodeFactory.create_batch( + SOURCE_CODE_VULN_COUNT, workspace=workspace, source_code=code) + + db.session.commit() + + +def test_vuln_count(workspace, second_workspace): + populate_workspace(workspace) + populate_workspace(second_workspace) + assert workspace.vulnerability_web_count == WEB_VULN_COUNT + assert workspace.vulnerability_code_count == SOURCE_CODE_VULN_COUNT + assert workspace.vulnerability_standard_count == sum(STANDARD_VULN_COUNT) + assert workspace.vulnerability_total_count == ( + sum(STANDARD_VULN_COUNT) + WEB_VULN_COUNT + SOURCE_CODE_VULN_COUNT + ) From 3ba2b9071cdc575427ea9474412d1cffde9d5d99 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 19 Sep 2017 18:27:23 -0300 Subject: [PATCH 0244/1506] Fix alphabetical order in requirements files --- requirements.txt | 12 ++++++------ requirements_dev.txt | 2 +- requirements_server.txt | 22 +++++++++++----------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/requirements.txt b/requirements.txt index 68fdda95530..b3d1479bca9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,11 @@ -couchdbkit -mockito -whoosh argparse +colorama +couchdbkit +flask==0.12.2 IPy -restkit +mockito requests +restkit tornado -flask==0.12.2 -colorama tqdm==4.15.0 +whoosh diff --git a/requirements_dev.txt b/requirements_dev.txt index dbf901a1aab..a8b7439b29a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,3 +1,3 @@ +factory-boy<=2.8.1 pytest pytest-factoryboy -factory-boy<=2.8.1 diff --git a/requirements_server.txt b/requirements_server.txt index 7c9bacffd8a..382d022aec0 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -1,17 +1,17 @@ -couchdbkit>=0.6.5 -restkit>=4.2.2 -requests>=2.10.0 -flask>=0.10.1 -tqdm==4.15.0 -twisted>=16.1.1 -sqlalchemy>=1.0.12 -pyopenssl>=16.0.0 -service_identity>=16.0.0 -pyasn1-modules -Flask-Security==3.0.0 bcrypt +couchdbkit>=0.6.5 Flask-SQLAlchemy==2.2 Flask-Script==2.0.5 flask-classful +Flask-Security==3.0.0 +flask>=0.10.1 marshmallow +pyasn1-modules +pyopenssl>=16.0.0 +requests>=2.10.0 +restkit>=4.2.2 +service_identity>=16.0.0 +sqlalchemy>=1.0.12 +tqdm==4.15.0 +twisted>=16.1.1 webargs From a3186c7cd97df697b9029aec8acf5d8a691b649f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Sep 2017 18:28:19 -0300 Subject: [PATCH 0245/1506] Add SelfNestedField helper marshmallow field --- server/schemas.py | 20 ++++++++++++++++++++ test_cases/test_schemas.py | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 server/schemas.py create mode 100644 test_cases/test_schemas.py diff --git a/server/schemas.py b/server/schemas.py new file mode 100644 index 00000000000..f7b046a2047 --- /dev/null +++ b/server/schemas.py @@ -0,0 +1,20 @@ +from marshmallow import fields +from marshmallow.exceptions import ValidationError + +class SelfNestedField(fields.Field): + """A field to make namespaced schemas. It allows to have + a field whose contents are the dump of the same object with + other schema""" + + # Required because the target attribute will probably not exist + _CHECK_ATTRIBUTE = False + + def __init__(self, target_schema, *args, **kwargs): + self.target_schema = target_schema + super(SelfNestedField, self).__init__(*args, **kwargs) + + def _serialize(self, value, attr, obj): + ret, errors = self.target_schema.dump(obj) + if errors: + raise ValidationError(errors, data=ret) + return ret diff --git a/test_cases/test_schemas.py b/test_cases/test_schemas.py new file mode 100644 index 00000000000..684097dc8cd --- /dev/null +++ b/test_cases/test_schemas.py @@ -0,0 +1,20 @@ +from collections import namedtuple +from marshmallow import Schema, fields +from server.schemas import SelfNestedField + +Place = namedtuple('Place', ['name', 'x', 'y']) + +class PointSchema(Schema): + x = fields.Float() + y = fields.Float() + +class PlaceSchema(Schema): + name = fields.Str() + coords = SelfNestedField(PointSchema()) + +class TestSelfNestedField: + def test_field_serialization(self): + point = Place('home', 123, 456.1) + schema = PlaceSchema() + dumped = schema.dump(point).data + assert dumped == {"name": "home", "coords": {"x": 123.0, "y": 456.1}} From 37972599b48f74b3831dda8692cb1dbe39a4f4d9 Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 19 Sep 2017 18:28:51 -0300 Subject: [PATCH 0246/1506] Add python-slugify as requirement for the server --- requirements_server.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_server.txt b/requirements_server.txt index 382d022aec0..baf43fc700e 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -8,6 +8,7 @@ flask>=0.10.1 marshmallow pyasn1-modules pyopenssl>=16.0.0 +python-slugify==1.2.4 requests>=2.10.0 restkit>=4.2.2 service_identity>=16.0.0 From 03d03ca9c9b5e971ef3a755eb60d609c22dac33b Mon Sep 17 00:00:00 2001 From: micabot Date: Tue, 19 Sep 2017 18:46:43 -0300 Subject: [PATCH 0247/1506] Add function to create tags --- server/importer.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index 51d0e307f68..0b4a76a1eb6 100644 --- a/server/importer.py +++ b/server/importer.py @@ -11,6 +11,7 @@ defaultdict, OrderedDict ) +from slugify import slugify from binascii import unhexlify import requests @@ -43,6 +44,7 @@ ReferenceTemplate, Service, Scope, + Tag, Task, TaskTemplate, User, @@ -580,7 +582,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation if not methodology: methodology = session.query(MethodologyTemplate).filter_by(id=methodology_id).first() task_class = TaskTemplate - task, created = get_or_create(session, task_class, name=document.get('name')) + task, task_created = get_or_create(session, task_class, name=document.get('name')) if task_class == TaskTemplate: task.template = methodology else: @@ -588,6 +590,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation task.workspace = workspace task.description = document.get('description') task.assigned_to = session.query(User).filter_by(username=document.get('username')).first() + create_tags([x.strip() for x in document.tags if x.strip()], 'Tag', task.id) mapped_status = { 'New': 'new', 'In Progress': 'in progress', @@ -600,6 +603,11 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation return [task] +def create_tags(tags, model, parent_id): + # tag, create = get_or_create(session, Tag, name=scope, workspace=workspace) + return True + + class ReportsImporter(object): def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): From 3294bf7b70bb046a6025611ed99e6180f9cb3abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Sep 2017 19:45:12 -0300 Subject: [PATCH 0248/1506] Refactor children count column properties and add to workspace --- server/models.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/server/models.py b/server/models.py index 8b37c5ab52d..74848202409 100644 --- a/server/models.py +++ b/server/models.py @@ -87,6 +87,19 @@ def do_begin(conn): SCHEMA_VERSION = 'W.3.0.0' +def _make_generic_count_property(parent_table, children_table): + """Make a deferred by default column property that counts the + amount of childrens of some parent object""" + # TODO: Fix SQLAlchemy warnings + children_id_field = '{}.id'.format(children_table) + parent_id_field = '{}.id'.format(parent_table) + children_rel_field = '{}.{}_id'.format(children_table, parent_table) + query = (select([func.count(children_id_field)]). + select_from(children_table). + where('{} = {}'.format(children_rel_field, parent_id_field))) + return column_property(query, deferred=True) + + class DatabaseMetadata(db.Model): __tablename__ = 'db_metadata' id = Column(Integer, primary_key=True) @@ -175,10 +188,7 @@ class Host(Metadata): foreign_keys=[workspace_id] ) - service_count = column_property((select([func.count('service.id')]). - select_from('service'). - where("service.host_id = host.id") - ), deferred=True) + service_count = _make_generic_count_property('host', 'service') __table_args__ = ( UniqueConstraint(ip, workspace_id, name='uix_host_ip_workspace'), @@ -573,7 +583,7 @@ class Command(Metadata): ) -def make_vuln_count_property(type_=None): +def _make_vuln_count_property(type_=None): query = (select([func.count('vulnerability.id')]). select_from('vulnerability'). where('vulnerability.workspace_id = workspace.id') @@ -599,10 +609,13 @@ class Workspace(Metadata): scope = Column(Text(), nullable=True) start_date = Column(DateTime(), nullable=True) - vulnerability_web_count = make_vuln_count_property('vulnerability_web') - vulnerability_code_count = make_vuln_count_property('vulnerability_code') - vulnerability_standard_count = make_vuln_count_property('vulnerability') - vulnerability_total_count = make_vuln_count_property() + credential_count = _make_generic_count_property('workspace', 'credential') + host_count = _make_generic_count_property('workspace', 'host') + service_count = _make_generic_count_property('workspace', 'service') + vulnerability_web_count = _make_vuln_count_property('vulnerability_web') + vulnerability_code_count = _make_vuln_count_property('vulnerability_code') + vulnerability_standard_count = _make_vuln_count_property('vulnerability') + vulnerability_total_count = _make_vuln_count_property() def is_valid_workspace(workspace_name): From b8a7949d897f119e331042945c1875c9caefa63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Sep 2017 19:46:12 -0300 Subject: [PATCH 0249/1506] Add workspaces API --- server/api/modules/workspaces.py | 20 +++++++++++++++++++ test_cases/test_api_non_workspaced_various.py | 19 +++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 2befd1cde85..809e9647763 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -5,6 +5,7 @@ import flask from flask import Blueprint +from marshmallow import fields from server.models import db, Workspace from server.dao.host import HostDAO @@ -25,10 +26,29 @@ list_workspaces_as_user, get_workspace ) +from server.api.base import ReadWriteView, AutoSchema workspace_api = Blueprint('workspace_api', __name__) +class WorkspaceSchema(AutoSchema): + + host_count = fields.Integer(dump_only=True) + + class Meta: + model = Workspace + fields = ('id', 'customer', 'description', 'active', 'start_date', + 'end_date', 'name', 'public', 'scope', 'host_count') + + +class WorkspaceView(ReadWriteView): + route_base = 'workspaces' + model_class = Workspace + schema_class = WorkspaceSchema + +WorkspaceView.register(workspace_api) + + @workspace_api.route('/ws', methods=['GET']) @gzipped def workspace_list(): diff --git a/test_cases/test_api_non_workspaced_various.py b/test_cases/test_api_non_workspaced_various.py index 30e299f4edb..2330dff7fe5 100644 --- a/test_cases/test_api_non_workspaced_various.py +++ b/test_cases/test_api_non_workspaced_various.py @@ -4,8 +4,12 @@ import pytest from test_cases import factories from test_api_non_workspaced_base import ReadWriteAPITests, API_PREFIX -from server.models import License +from server.models import ( + License, + Workspace, +) from server.api.modules.licenses import LicenseView +from server.api.modules.workspaces import WorkspaceView class LicenseEnvelopedView(LicenseView): """A custom view to test that enveloping on generic views work ok""" @@ -31,3 +35,16 @@ def test_envelope_list(self, test_client, app): assert new_res.status_code == 200 assert new_res.json == {"object_list": original_res.json} + + +class TestWorkspaceAPI(ReadWriteAPITests): + model = Workspace + factory = factories.WorkspaceFactory + api_endpoint = 'workspaces' + + def test_host_count(self, host_factory, test_client, session): + host_factory.create(workspace=self.first_object) + session.commit() + res = test_client.get(self.url(self.first_object)) + assert res.status_code == 200 + assert res.json['host_count'] == 1 From 569f8d6dbbcd4d610c757373f521ea1193011800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Sep 2017 19:56:10 -0300 Subject: [PATCH 0250/1506] Move workspace children count to nested field in API --- server/api/modules/workspaces.py | 22 +++++++++++++++---- test_cases/test_api_non_workspaced_various.py | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 809e9647763..c13b2c29d48 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -5,7 +5,7 @@ import flask from flask import Blueprint -from marshmallow import fields +from marshmallow import Schema, fields from server.models import db, Workspace from server.dao.host import HostDAO @@ -13,6 +13,7 @@ from server.dao.service import ServiceDAO from server.dao.workspace import WorkspaceDAO from server.utils.logger import get_logger +from server.schemas import SelfNestedField from server.utils.web import ( build_bad_request_response, filter_request_args, @@ -31,14 +32,27 @@ workspace_api = Blueprint('workspace_api', __name__) -class WorkspaceSchema(AutoSchema): +class WorkspaceSummarySchema(Schema): + credentials = fields.Integer(dump_only=True, attribute='credential_count') + hosts = fields.Integer(dump_only=True, attribute='host_count') + services = fields.Integer(dump_only=True, attribute='service_count') + web_vulns = fields.Integer(dump_only=True, + attribute='vulnerability_web_count') + code_vulns = fields.Integer(dump_only=True, + attribute='vulnerability_code_count') + std_vulns = fields.Integer(dump_only=True, + attribute='vulnerability_standard_count') + total_vulns = fields.Integer(dump_only=True, + attribute='vulnerability_total_count') + - host_count = fields.Integer(dump_only=True) +class WorkspaceSchema(AutoSchema): + stats = SelfNestedField(WorkspaceSummarySchema()) class Meta: model = Workspace fields = ('id', 'customer', 'description', 'active', 'start_date', - 'end_date', 'name', 'public', 'scope', 'host_count') + 'end_date', 'name', 'public', 'scope', 'stats') class WorkspaceView(ReadWriteView): diff --git a/test_cases/test_api_non_workspaced_various.py b/test_cases/test_api_non_workspaced_various.py index 2330dff7fe5..3e274791320 100644 --- a/test_cases/test_api_non_workspaced_various.py +++ b/test_cases/test_api_non_workspaced_various.py @@ -47,4 +47,4 @@ def test_host_count(self, host_factory, test_client, session): session.commit() res = test_client.get(self.url(self.first_object)) assert res.status_code == 200 - assert res.json['host_count'] == 1 + assert res.json['stats']['hosts'] == 1 From 03493126656a31a919e3360e4658a303c1f2d854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 20 Sep 2017 12:58:00 -0300 Subject: [PATCH 0251/1506] Fix unicode error when importing users with special characters --- server/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index 51d0e307f68..f05d07d355b 100644 --- a/server/importer.py +++ b/server/importer.py @@ -749,7 +749,7 @@ def import_users(self, all_users, admins): if user['name'] in admins.keys(): # This is an already imported admin user, skip continue - logger.info('Importing {0}'.format(user['name'])) + logger.info(u'Importing {0}'.format(user['name'])) if not db.session.query(User).filter_by(username=user['name']).first(): app.user_datastore.create_user( username=user['name'], From b6bbd3f4537f1eed01faa4f8eb71280e4aa8e6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 20 Sep 2017 13:01:20 -0300 Subject: [PATCH 0252/1506] Fix factories for new vuln code schema --- test_cases/factories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_cases/factories.py b/test_cases/factories.py index b0240266311..e339a69cd88 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -150,7 +150,7 @@ class Meta: class VulnerabilityCodeFactory(VulnerabilityFactory): - line = FuzzyInteger(1, 5000) + start_line = FuzzyInteger(1, 5000) source_code = factory.SubFactory(SourceCodeFactory) class Meta: From 4aae8b3d7e0b4e18ee88fdb373f27ba1753fbc6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 20 Sep 2017 13:12:42 -0300 Subject: [PATCH 0253/1506] Add legacy _id field to workspaces API --- server/api/modules/workspaces.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index c13b2c29d48..6f22959cde4 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -48,11 +48,12 @@ class WorkspaceSummarySchema(Schema): class WorkspaceSchema(AutoSchema): stats = SelfNestedField(WorkspaceSummarySchema()) + _id = fields.Integer(dump_only=True, attribute='id') class Meta: model = Workspace - fields = ('id', 'customer', 'description', 'active', 'start_date', - 'end_date', 'name', 'public', 'scope', 'stats') + fields = ('_id', 'id', 'customer', 'description', 'active', + 'start_date', 'end_date', 'name', 'public', 'scope', 'stats') class WorkspaceView(ReadWriteView): From f5473486aa11302d6b6eeed699bfc26369c02545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 20 Sep 2017 14:38:26 -0300 Subject: [PATCH 0254/1506] Change workspaces api URL and lookup field Also remove unnecessary print and add logic for customizing the lookup_field in the test cases --- server/api/modules/workspaces.py | 4 +++- test_cases/test_api_non_workspaced_base.py | 4 ++-- test_cases/test_api_non_workspaced_various.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 6f22959cde4..2fe71786120 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -57,7 +57,9 @@ class Meta: class WorkspaceView(ReadWriteView): - route_base = 'workspaces' + route_base = 'ws' + lookup_field = 'name' + lookup_field_type = unicode model_class = Workspace schema_class = WorkspaceSchema diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py index f7ea1b54676..9f47c6db5a2 100644 --- a/test_cases/test_api_non_workspaced_base.py +++ b/test_cases/test_api_non_workspaced_base.py @@ -15,7 +15,7 @@ class GenericAPITest: model = None factory = None api_endpoint = None - pk_field = 'id' + lookup_field = 'id' unique_fields = [] update_fields = [] @@ -37,7 +37,7 @@ def object_instance(self, session): def url(self, obj=None): url = API_PREFIX + self.api_endpoint + '/' if obj is not None: - id_ = unicode(obj.id) if isinstance( + id_ = unicode(getattr(obj, self.lookup_field)) if isinstance( obj, self.model) else unicode(obj) url += id_ + u'/' return url diff --git a/test_cases/test_api_non_workspaced_various.py b/test_cases/test_api_non_workspaced_various.py index 3e274791320..5611aa6906f 100644 --- a/test_cases/test_api_non_workspaced_various.py +++ b/test_cases/test_api_non_workspaced_various.py @@ -28,7 +28,6 @@ class TestLicensesAPI(ReadWriteAPITests): def test_envelope_list(self, test_client, app): LicenseEnvelopedView.register(app) - print app.url_map original_res = test_client.get(self.url()) assert original_res.status_code == 200 new_res = test_client.get(API_PREFIX + 'test_envelope_list/') @@ -40,7 +39,8 @@ def test_envelope_list(self, test_client, app): class TestWorkspaceAPI(ReadWriteAPITests): model = Workspace factory = factories.WorkspaceFactory - api_endpoint = 'workspaces' + api_endpoint = 'ws' + lookup_field = 'name' def test_host_count(self, host_factory, test_client, session): host_factory.create(workspace=self.first_object) From 6e52ec3d89f8e24dd7985e8cd523dcc8d451412e Mon Sep 17 00:00:00 2001 From: micabot Date: Wed, 20 Sep 2017 19:12:46 -0300 Subject: [PATCH 0255/1506] Fix tag importer fucntion This commit introduces a bug. TODO: fix attempt to create tags without task ID. --- server/importer.py | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/server/importer.py b/server/importer.py index 0b4a76a1eb6..812393ddbc5 100644 --- a/server/importer.py +++ b/server/importer.py @@ -6,6 +6,7 @@ import sys import json import datetime +import requests from collections import ( Counter, defaultdict, @@ -14,14 +15,13 @@ from slugify import slugify from binascii import unhexlify -import requests from IPy import IP from flask_script import Command as FlaskScriptCommand from passlib.utils.binary import ab64_encode from restkit.errors import RequestError, Unauthorized from tqdm import tqdm - import server.config + import server.couchdb import server.database import server.models @@ -45,6 +45,7 @@ Service, Scope, Tag, + TagObject, Task, TaskTemplate, User, @@ -59,8 +60,8 @@ COUCHDB_USER_PREFIX = 'org.couchdb.user:' COUCHDB_PASSWORD_PREFIX = '-pbkdf2-' - logger = server.utils.logger.get_logger(__name__) + session = db.session MAPPED_VULN_SEVERITY = OrderedDict([ @@ -158,6 +159,19 @@ def get_children_from_couch(workspace, parent_couchdb_id, child_type): return r.json()['rows'] +def create_tags(raw_tags, parent_id, parent_type): + for tag_name in [x.strip() for x in raw_tags if x.strip()]: + tag, tag_created = get_or_create(session, Tag, name=tag_name, slug=slugify(tag_name)) + relation, relation_created = get_or_create( + session, + TagObject, + object_id=parent_id, + object_type=parent_type, + tag_id=tag.id, + ) + session.commit() + + class EntityNotFound(Exception): def __init__(self, entity_id): super(EntityNotFound, self).__init__("Entity (%s) wasn't found" % entity_id) @@ -461,16 +475,16 @@ def add_references(self, document, vulnerability, workspace): class CommandImporter(object): - DOC_TYPE = 'CommandRunInformation' + DOC_TYPE = 'CommandRunInformation' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): start_date = datetime.datetime.fromtimestamp(document.get('itime')) command, instance = get_or_create( - session, - Command, - command=document.get('command', None), - start_date=start_date, + session, + Command, + command=document.get('command', None), + start_date=start_date, ) if document.get('duration'): command.end_date = start_date + datetime.timedelta(seconds=document.get('duration')) @@ -507,8 +521,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class CredentialImporter(object): - DOC_TYPE = 'Cred' + DOC_TYPE = 'Cred' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): parents = [] if level == 2: @@ -536,8 +550,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation class WorkspaceImporter(object): - DOC_TYPE = 'Workspace' + DOC_TYPE = 'Workspace' def update_from_document(self, document, workspace, level=None, couchdb_relational_map=None): workspace.description = document.get('description') if document.get('duration') and document.get('duration')['start']: @@ -590,7 +604,9 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation task.workspace = workspace task.description = document.get('description') task.assigned_to = session.query(User).filter_by(username=document.get('username')).first() - create_tags([x.strip() for x in document.tags if x.strip()], 'Tag', task.id) + tags = document.get('tags', []) + if len(tags): + create_tags(tags, task.id, 'task') mapped_status = { 'New': 'new', 'In Progress': 'in progress', @@ -603,10 +619,6 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation return [task] -def create_tags(tags, model, parent_id): - # tag, create = get_or_create(session, Tag, name=scope, workspace=workspace) - return True - class ReportsImporter(object): From 000e01b95f9d8cf79bc6e378b243b771437529e4 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 20 Sep 2017 20:58:45 -0300 Subject: [PATCH 0256/1506] Add attachment model --- server/fields.py | 107 ++++++++++++++++++++++++++++++++ server/models.py | 39 +++++++++++- test_cases/data/faraday.png | Bin 0 -> 25541 bytes test_cases/data/test.html | 10 +++ test_cases/test_model_fields.py | 40 ++++++++++++ 5 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 server/fields.py create mode 100644 test_cases/data/faraday.png create mode 100644 test_cases/data/test.html create mode 100644 test_cases/test_model_fields.py diff --git a/server/fields.py b/server/fields.py new file mode 100644 index 00000000000..895fbb1268c --- /dev/null +++ b/server/fields.py @@ -0,0 +1,107 @@ +import imghdr +from tempfile import SpooledTemporaryFile + +from PIL import Image + +from depot.fields.upload import UploadedFile +from depot.io.utils import file_from_content +from depot.io.utils import INMEMORY_FILESIZE +from depot.manager import DepotManager + + +class FaradayUploadedFile(UploadedFile): + """Simple :class:`depot.fields.interfaces.DepotFileInfo` implementation that stores files. + + Takes a file as content and uploads it to the depot while saving around + most file information. Pay attention that if the file gets replaced + through depot manually the ``UploadedFile`` will continue to have the old data. + + Also provides support for encoding/decoding using JSON for storage inside + databases as a plain string. + + Default attributes provided for all ``UploadedFile`` include: + - filename - This is the name of the uploaded file + - file_id - This is the ID of the uploaded file + - path - This is a depot_name/file_id path which can + be used with :meth:`DepotManager.get_file` to retrieve the file + - content_type - This is the content type of the uploaded file + - uploaded_at - This is the upload date in YYYY-MM-DD HH:MM:SS format + - url - Public url of the uploaded file + - file - The :class:`depot.io.interfaces.StoredFile` instance of the stored file + """ + max_size = 1024 + thumbnail_format = 'PNG' + thumbnail_size = (128, 128) + forbidden_content_types = [ + 'text/html', + 'application/javascript', + ] + + def process_content(self, content, filename=None, content_type=None): + """Standard implementation of :meth:`.DepotFileInfo.process_content` + + This is the standard depot implementation of files upload, it will + store the file on the default depot and will provide the standard + attributes. + + Subclasses will need to call this method to ensure the standard + set of attributes is provided. + """ + + file_path, file_id = self.store_content(content, filename, content_type) + + self['file_id'] = file_id + + saved_file = self.file + self['content_type'] = saved_file.content_type + if any(map(lambda forbidden_content_type: self['content_type'] in forbidden_content_type, self.forbidden_content_types)): + raise UserWarning('Content not allowed') + self['filename'] = saved_file.filename + + self['path'] = file_path + self['uploaded_at'] = saved_file.last_modified.strftime('%Y-%m-%d %H:%M:%S') + self['_public_url'] = saved_file.public_url + + image_format = imghdr.what(None, h=content[:32]) + if image_format: + self['content_type'] = 'image/{0}'.format(image_format) + self.generate_thumbnail(content) + + def generate_thumbnail(self, content): + content = file_from_content(content) + uploaded_image = Image.open(content) + if max(uploaded_image.size) >= self.max_size: + uploaded_image.thumbnail((self.max_size, self.max_size), Image.BILINEAR) + content = SpooledTemporaryFile(INMEMORY_FILESIZE) + uploaded_image.save(content, uploaded_image.format) + + content.seek(0) + + thumbnail = uploaded_image.copy() + thumbnail.thumbnail(self.thumbnail_size, Image.ANTIALIAS) + thumbnail = thumbnail.convert('RGBA') + thumbnail.format = self.thumbnail_format + + output = SpooledTemporaryFile(INMEMORY_FILESIZE) + thumbnail.save(output, self.thumbnail_format) + output.seek(0) + + thumb_path, thumb_id = self.store_content(output, + 'thumb.%s' % self.thumbnail_format.lower()) + self['thumb_id'] = thumb_id + self['thumb_path'] = thumb_path + + thumbnail_file = self.thumb_file + self['_thumb_public_url'] = thumbnail_file.public_url + content.close() + + @property + def thumb_file(self): + return self.depot.get(self.thumb_id) + + @property + def thumb_url(self): + public_url = self['_thumb_public_url'] + if public_url: + return public_url + return DepotManager.get_middleware().url_for(self['thumb_path']) diff --git a/server/models.py b/server/models.py index 580bd26547d..b8cc5e5b5e0 100644 --- a/server/models.py +++ b/server/models.py @@ -3,7 +3,6 @@ # See the file 'doc/LICENSE' for the license information from datetime import datetime -import pytz from sqlalchemy import ( Boolean, CheckConstraint, @@ -17,6 +16,7 @@ String, Text, UniqueConstraint, + Unicode, event ) from sqlalchemy.orm import relationship, backref @@ -29,6 +29,8 @@ SQLAlchemy as OriginalSQLAlchemy, _EngineConnector ) +from depot.fields.sqlalchemy import UploadedFileField +from server.fields import FaradayUploadedFile from flask_security import ( RoleMixin, UserMixin, @@ -328,7 +330,7 @@ def service_id(cls): @declared_attr def service(cls): - return relationship('VulnerabilityGeneric') + return relationship('Service') __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[0] @@ -352,7 +354,7 @@ def service_id(cls): @declared_attr def service(cls): - return relationship('VulnerabilityGeneric') + return relationship('Service') __mapper_args__ = { 'polymorphic_identity': VulnerabilityGeneric.VULN_TYPES[1] @@ -645,6 +647,37 @@ def __repr__(self): self.username) +class Evidence(Metadata): + __tablename__ = 'evidence' + + uid = Column(Integer, autoincrement=True, primary_key=True) + name = Column(Unicode(16), unique=True) + content = Column('content_col', UploadedFileField) # plain attached file + file = Column(UploadedFileField(upload_type=FaradayUploadedFile)) + + vulnerability_id = Column( + Integer, + ForeignKey(VulnerabilityGeneric.id), + index=True + ) + vulnerability = relationship( + 'VulnerabilityGeneric', + backref='evidence', + foreign_keys=[vulnerability_id], + ) + + +class UserAvatar(Metadata): + __tablename_ = 'user_avatar' + + uid = Column(Integer, autoincrement=True, primary_key=True) + name = Column(Unicode(16), unique=True) + # photo field will automatically generate thumbnail + photo = Column(UploadedFileField(upload_type=FaradayUploadedFile)) + user_id = Column('user_id', Integer(), ForeignKey('user.id')) + user = relationship('User') + + class MethodologyTemplate(Metadata): # TODO: reset template_id in methodologies when deleting meth template __tablename__ = 'methodology_template' diff --git a/test_cases/data/faraday.png b/test_cases/data/faraday.png new file mode 100644 index 0000000000000000000000000000000000000000..36ccf51fb7f877e74048db7b2ea5daa6afad18cf GIT binary patch literal 25541 zcmZU)V~{98kS;p5ZTpOE+qP}nwr$(CZJTFo+xINq?nb=&(NUe5Un-)bkl7LPvSP4M zSWo}}0I(9`!ioR@fSUj23J^g5o;X48ssI2OcosrJ@)ANq1oDn{rWV#F008QcH5xAJ z$~$elr_u~CLNGi3M$RHBgb@79JOyQ8aCv?)hCc*^VFD3UV1zhDU<46Rq^~~_kqf-< zhfm+kYzGvVsaMFbD?-@x_(C>g> z8z^OjvbA7D3>-Q6ujl)3w@C09@z00LJ)v9YIz747(M0GQ%|xE{MX){`2zn@p=H^%G zn#dR?$4{!8(gAf2;&h;WZt$>H``4(`y#Z|~#EHNWwt>Pbm-nz7y;yPH4lPI$0`&pD zRrH&08Lc`E>F~xi+ZY+a{4mOZI9=dQg+i>~8({;atltqOCE=*6-&=ukRgU`-@fSBE zszBdWz!cEXjN1jO;E~LN!wZ+|%`yuLrtNDN&D|$KxCL<}dAOlzg#IvVvLM9DLEJV0=a*WZ=wxAb#eFQ7FM@!*yngXiEWF#1 z2cU5h?j8}ZKn{qhKTJ6R!drkiJv@j%EIk-m-z`u$H&ViT5CuZ)S2+-jU#~u|z9{`I zy&Qv5`UD?i@sS-~zCQrSZ_Op&B(WoTJPuOG^LJ{v~hTSRz}Ujqtl`E<4Q zH>ICFG$WY?GIi)`OjRTo2p3cr^p&X2xC_x1V8CS=WpGOb7bEcB)=N1WW8ucb6kPZ* zAgkf10D_-2t)sk;pRBZlUxTkzemaplD4Ybv1Ok|M(f2a2H9b|OpRiQ-;Qg>=j7a;M z--t20u6e_7H@KrIx{7CWNm;*n%7;1=*G%VO+OPu@c4odu)n=^e%vhkc0S{O7zu$i`~9P#Wc$H` z13(TCN%O&)0|4nkRrS&B!SM9q7sG`3K@1&;*1;JC;Ksql1|a1DWrMKf;WGzN=ixd9 z=-Pqt0uAm^x&f5*!MuT@{&J~@5kP<=5}Jl1AA|`LT8Kj~`acmUje|J`a}(%MU_=HV z6!^^hQs7esQsp14g{hAii9N%PZY7pk5=fC&n+iFK}`vr6iUgj zR6tkI5!b^Jk|{uww=$>B#H8_06(O<+a25D0GMcwI19ArK#M=qI5sNF>D@0$=!XOF- zCyrSdR5#pa7)TeEhARb83bPo2Avp8^d&G?zX*3vVxDdB{?GH74HH5BLSx38~xq@)T z>Ixj%XLnd+*V(4U!9)+68b~?Jv}bcDYRBA$#{z=oi$RLPj3JjHkwKGzlp&UZpP`tc zp5e?zq>Ee|yd4Dpn{+RB*YZaDW%LE(o0A%$nys3)n!%dPn$#NoLUavqEwxz(0wfTg zFJVt04}lrXI7m9kGpIF)g&QFoIh#HkMVnh2f5W{)(<#weCrqReZO;EhGKvTt0Xw`k zyf%#gH2viGl==ktbpDj`Wb;(?q=#__QwS3ZC{1Q-**=HQT(3$;p(_zAM;3K|M-yQH0Ffnv}dNPe{9sOAAQm-yDrQwH!njk zE^p8;`UNJ@O6oJ`G72(kB8V z!ZLz9QC$Ldf_{Q(!hlj;QiV#mQt_gJnYxBdCZRSFFJWNZd{TH)d5Sx!B#9+iCccN@ zQ#Dp*R+Lt%R_7+FJG{H>i{ZWNK?|x9Y9FcviVj60QZ-T|QYUgsQc?0pGDT7@Ibzau z@_Z6^Qi^JkDuAj?nM^rD#YP3b@}30DysamGEdpBbfD0nP9fhx4!Ovp zIHPu>rlYi^6t&DnxXT;TSC>uAKz48@^7^7m~W|XK2Qu09Qbtj zWmsycY}iFuDyU58RajcMT=)feN=S`-3?(WGsRFcuG9k4g*N`0eEjTx5yNLXVyO^_R z5|L1m7qNR0Qjr*8DX~*wxe@b`>~M`@O{Gi20`=SVSavkyBrXg(L_dNbn?PMblfgLQ zwc#oWX^8}hpb5o^j)^8pQ_9nq!q<=6>n-wjI;XuY<)P&!3;9evO}$QgPuCCWP!CZb zG8cVQf&z%=HlSflCV>x_!K6}D2-muKatNfRNrLg%him*_}6sIYtwg;dG zyrVxyX-9S_Z|L0Y)$Z+AcdiEV`}2q`#AUIDaiVy*e5HQXz2OxUCFY&$t?k$C{rBC6 zd$MoY?RtfZj-AP^%)QQ))1}o#+Qnby@1i2&BTJG+lc`DEC_sqBF zUrT&*^0U&}EPPw4G^#p^K{XQP@A8+LUa#4s9ig1HonM_Zj|rz`7r2+7*vr^hSRPn$ z+WvIIdg*o#mRPo|7B}q_Uia3}T2eox_LM)1V^h6pV|D6!=UtT!&j&0ER^(b4T92Fg ztw*gRFYzxGaB;B-vQ@LawAZz(myT<5ZqTl3yZSv2#bK@FU1p!AlQSt=3vFngL0)B_ z-XFT#u6^Iddj)>x{hoh!ahtf>Zn1WIJmg+=UwvvmbKsfrn)#Hy)>+Fr*FVIc6)xj9 z<+qHU$8d)-hjfTJiFt}Xih7L5jNEe7c|G2iZ_dZtr{0E{>dcgo0g;`O_LHRMkaO$$ z#@!=4GF~I^RVJ)**H-LE_L`+DWOm%G9$fEomb%cFewOlf$9nhDCw}<9y=@>bCud6|58-$f3lTKZsqu(X}^vNZ~6ZRpUwkH0*BvL@SlQ9o1G z_K17y>Q27@ekt^oHn_f)ZOv^w55sPYZj>IGuBAS&PF;szUz;uzrm2w{{^63eZC#B=-0X{s81c| zU_iTviO`_rI!Dv+x!$YBi>k@o7SV_XOX80=S$}RL2s0ONBUsBl6ccGR@M!#jO4{hk|rI)8g&@;iMW-a zkcpOTlth!=mG)?DGubOp-eZ1t_B6KEnWLVB+NjH#$>wp(Cog-`A1BPwZrz^ob`IFx zKOx2vtHar%KH;PF5v&z1P;QX!&@K^dQG71eOj)IDS_&|moRwX!U&YFOtc~K%Q_Nek zuWoo4>?ln=nT=}G5~cI$C30u^*tE|w_Hq}~FwGY} zaDKh+83A7pD-1e`0}3_74}bCZ9xpH~K zE~1>RIBpxd@~9rr}#3(d6ZX;>3GG_OO13b_WZI0E!zN z3ke^EG%P)WOmaaYR@^&5KPfq0F>Xa=PDS-cg6gq?zEZGqZ`m|{TWwD?+oMsZKs2^9);Efr1mdb^0)6FnJQ zF8)>&E|Je=^KB{cXg0aZHqKsp`}$Z7J&x9p4VW%P5z>UwwdnmgsyuH7tR}b`hS`#r zRDFF6-CX0XWbL0Z%_1Xx!>-7zlRMA9E;@&X~boP=EWF}Dd)a%&*0Vh ztn%IOb6i8er_I(S>g9PlxmiBv956qBuVfr+WOV#}&y2G}Yv=3o<9J^?P#jd-KwYG| z#`=0(8hxgjynR~&awPz!j%QM#bO$gXbUMRehP@m#QxIMTWQNlTMjYB61p6!A{>dD6 z=i2<$Y!yuIj63Y~bPOXBEtVcBjyip+zNvoO&||81Na8Tzm~|iT@p_-~poemX>`lZ> z{H0;6;f%P!A|w7Jk(;zhcTGu52R#uxxiU#krA`S&QA-6&xmHnJ(OEuGK6HVMHIu2K zS-YvYnZ-HO$>JIDp$hf|_9rw*^fY=&T3hCLn%kTEXB~^7nXu zD;u?~s*A^tMY>tTzT_>nr@auN_$Ts$-TUVYJ!OiSE zPs3*gd@hV6&m>kRo+~aU{-a+bqpx|ORpWcez|{R3rc&!|_AdT+J*S_yf&PStizE4u z-|4vPDo8aJ!09xAJrA6J5?LTLAVQ7UDm*U0RRjRL6;OH;4nJEV;1TKl+{XYoOhSth zUpXl7_*H<&3A_{q3@1zrfmx3oJy$jCXaDEEr#r+?5op5+LShzK$*a63~yOLmO;k?HZtUrdlMI$cItpC zd9{=ck~OS#+qI_~>UtQbJWf*%Ek~c*v7?hC-4n*P%#-6C?FQ5S6u;^V)2kaK1cWqv zCjz&a0?Z5yc2qpS=k6nhi>1U>oO@ev>G0gd6(y8c%!@~HX_<@ZUnZeOJd-Nia^7)! z3K}=oy?TOAd#n9rg%2ZFk+j&VYz&H)KVHfP3La&X`^#}mua#bwb;~KMWz341u6oSf z!=CTgD<`W@FFZN9Yp*-zeI2W18~6_7x4Q2^n**r@x%&wV8)eLM&gAfYZ!eopl`fg| zLb=x;;!OlUpkFF2!)@9xF=iz;EQT`Ar=y}!~RGldUtSz0Y?O86rh!& zvL90P+1knq<7XDyR!eAVICWvYIDM(VtyN7cW%^?9z zOwDLgS69E#c-A1_IM~ow-(u(HFqkEyg}3E)W0Rvsy5#76{6Ycb2)GUG5flsd5l$Ad z5ZQ}*j#!gCk{p$Mm*g5xpS&2an5v>Gr?jNRq}-~!QjJlbQm9*UukI+RDMT*uQn1_L zU!vbKpBCO`-nj}1X*Ee%DUIo|Y1j$U!G?2;1(Yqz&Ld`capb$acWA3z^Sx!jSJ+c` zY}-@yx7D|lw}Z$%!1AQEW*H=FB(o$Q4cbH;!|bEm_ed0c*iFXv@+-A0F)4*NfifYz z#plY8S8~7mcLdZm`uQ*j8uSs1)V1xk{4NHdqh#)o0X*ktzYv0(eylsTVkSU$n=I-=`s)@KM352~5 zf{U#Knvkb1R>!|Lqd#;9wqZw4&yrv*AvJwWO-4Oc&0<}91@fXBiz3T1d#jzZea@ZQ zeI;IPcbA8dPsD|QYCwy~-1s6LB@-DZ$t@~-B3Gg}<(;?_Pfj^bp-D*Gsa_)X+|=nG znq``>S`JrL+ooIe(^=4?Pz1TlJb0B6l5&h|g`EY-70F{??!4&Wd#ZU>d)v_X*~qpc zHsiKY$H>oF&!J9LOsh;?O(@9Q$h66AO2kTpmnENLoc?8Mr%1(GLmx!iJ5BHz3%56Cxt|fU~^(d}1wb!2Ao`Po}DJAVk z_o$N+B{h0A;yRhuj5aq{DrXkgsO|D@3BNxruZK2mKR;ZOZgacjJmbFY9~9na-}SHK zN9kKhW{0zLH*!=wbe{L;DZ7`0Y<0Vny;50txrjgU%%A4EZ$^VTGVY*l4>w+GmZR`H_)%6Fd=h+f)rV_FtK@9atdndftSfBr*1Q`?yM@*)b~LtC z*5o#Ndl7!t@r{eK7k~tu#3bKuo?f3-7y#`=0IYE~c6DlYc6O@-VbN&-+fcuserdRj zXcH@WIO_s#ilU6JxxXIY==$YtT~2#idjCoX*%r#`&gwGKoJMvwv<}x?wtRoHYUyn1nxH0woaVxJVgH~!TE3gA7nZrg8vk8w&EdDmystBvU4;cV4-E8 zWhCN-A|N2(b~HBSR1_BdZ}`7A9wKvRXM0XMIyW~rS~n(IJ4Z7*1`ZAmI(kMrMn;-{ z5;RU8w$29bG`3E}|E1*r(<5x+WaMaJ?`&abOYk3h4GisEoOy_d{=?A!jQ{e}#NFb5 zE!jH#cUb=dr27vJ9Rn>r-T&zR2j%_`DyO`KyNR{Bu!W6@tnde5mCb#4Zx?ZkRXNnNt8tmFRXW{uy4;`F(siV2Aq?cg)k5WVnRw!@R!JEb&5|D1bs(N zEA%%jpbV`nm7fe0_CdxsKrQ!gu9BxdN(mz{c%a)3kkN-2`Brcod}pQ;Q4IL&ynq#^ ziG>u9r^=PLi8GqF(#9x6hHo|IPeE!~hAkv&k5&Tdo!ZP{s855r$omY*05ZZN1;CGO zU)5F$oQYDbMuLk0hYAQu)VR0llEnd2wL_*xW80QJa zLQ%DyjtuE9Q|`g^5{c$uQ)&b$e-cS|vO1vrBfN}cAQ7roz9QHWkY%7f)M5&~1nz$D zCe*AygDtbHsR^O7pbTE7SW1mTX?ZnkR-sa)$Yug(A{O~CrMOtrz!B|2O64WXKzhpB z1;|5!w1Kjql7RnLc?nk`i}2Z;1heoF7@@)lR9c`#ul=BTy7c?>n>W2YDzbzMv*Od@ zDlA4xa^0Qp%@KZ?z9hsJh%bJW>NYd^8Qr9vpa=kW8cSYsiy&!%VY2cy7IR0wzXCpQ ztvV#0On#h$#4k87C&{&PfU5@yEvj)6R)T6o&v+x06ebmciaq7AWXZ5Nd5KF&iAdz8 z%4C3S3Yp;J4y;O=;I3^M6t! z*}q9-swwH>m1Lzd0a#1 z`ku-Y%yco{&dM^uJS(Z>k>@{aa~?C|Qt1c{#o_p#F0Uq2jsYbVk?njRud;sP&PwcR zEcmT@kmjk7_=BeFKPco~w{G^OXsZh4J>s2DzdkswUB>V)b3P_Jjbbgi4J>vBJ0?cK zFjbH!ZSEpYDB}uAP+yt}0yfd-u-h-u-{<^lLFict?<3@9KIXkZ<^(#1H^pg2yWF07 zPvb0C(2x}jRMm#IXOBGUq*~ipaGL6E_rH|Qs$M$jqe6nCiPV{q&RJO^Wy3V|u2`U8 zL9<*A)sQ9SwG|W;zE4pToo5Aqj;r`of49m8yH`=fARhgGik7oCJRDesBY>Gmgg9Tg zGH~}^C+JXqpY^N+Fa4o~hDMId=DtVdh8(MAaQy$Yxp)oD(lop*DOB+!W-^i2BJ_09 zb0>a|(3K0}NR#xmpO1Wvi&xfYz!qu@Y_5P7TSmNvfV%7;h3IrunGp}7w!tbJl%?rC za^2Q;VTy*L;jVpUsue!foMvmZ>Gx8pTzeZnd?Pa7k}4{5`@GI6s!T!ED$em-dfZYT zyLwVa>uVy`rO)a^TcV~#K!jnxAKi9hA_bp0ndQRNe;E_Q5Rlsk)U^#%$On zo#A?F5<^};tE5Azrl*g$&X9vVSANjjkzCj8UK9;W6wc0=4Y)*=xDB^Ub+veOFavZw=E=M^zR)?DviQ{(h2>TqM+?NVn{$ zu1=|EZ>91G_5|l;(8#?jil;gf`~4Yv_aPS1QD@LX5LJ}S7N}t7g#*K_C|&^$4lrX1 z*ju5G3}qGFD)v`Ld0y2o?c;-D+wn>&&ae9usq?l7`&s*aI9)k`S1K-0yg#fUDZ_tM zC<4Wyg3=WSjhG`bC*sJt3RqWO-k^x`>J=&Vw?(=?sDbKIvY5Ru-_b}$fH)ae&_TA( z$?4?6>ZRt(+VuM*P@VsrUO){oH>N!qdL3#%J-l;T@YmNf%|v;E3MzG@2OY*4>>U0J zem)|~e`sZO`!3*>0}>c17Q1HBU}$q?5uvdW*!4n$B}Uue?!~WG-@F>CmPm9>LF`gd zO`G=<86Pc`%t?M1N8CiX8%UZWrKoXGk+;DsEM~5lzd!VhzQ^)Fb@g71q|aI7Q1Bk$ zdup|+zlv@#w^YcN z;&okLMov-kx1V4tsmm=sMA}Hg%B+fv%`8zx(-h16Z~u_L#qa9$kjHmfgNSyhXqkZH0O)cCS66D1k5CUvHw zJPK6)xXl{1)-l3cAb$8>$*0o*Sy^ak!|x|8VX6-2YYYDrJggeLl3k>v<(I_5`kY50 zhkT@uM5$O4!}m&JtqVyJk|C|c&p#<#a3ci^dx4jTD_@+yS>z}m=1xnR9#Fd5kptoz z=tC*8)f%e4q9%)}y)aT8HOmyrOjy|BkMM)wqp2c_N9MW@C_?SM08K##ZPzNN{ zJi#OVMX{hvhSV9_=n#1C*TA5!8f&?rB%<(-t)!|XGyqaq-*?Y_M^3_arxdM)*AfTg zPEkj5hO?6Rrh>~je2ud2W$Zv!FZuxe%W@MWO5k86Si&}yoepZ_h4(x&(P%e$cG1}% zHMl)z-~}BXN1Zz z$ysMs_SdM$1sfj2m0}fKf_ORV% zxijREgH4f*DC^}I{tW^vj_OVO5OLCBPdf`tXO<1r=U z1ekM*8oY-{G1q}52I_Kmd+r%Y=FLxu%Wx?3q}jEUr(dcicrhyiYGq27w(_}(`g!LPQM z4fGUGLxOm6m~9XT4XwHif#8n9xPrqWeX73^atoz_aW!rkLH!l#*b&1X;Fi$c-oL#A ze7~IEVFE$(P|eT?-3Vuu3hPK>3 zk8S}I=!vi{y0hN0bSilyq^r!DYb_r`iWrQ)hHea5Qp;@OVW)h7^l1_0+monYo*ma+ z?JU-rMdfgbve<(VzG>;}ttL-(KehW>dNRxJGRKXQ#0QEEJ3REvu9w#r;_io#d%ej( zT2Lv1ytUWn5Rv2A;e;Wm5_ED!^Jj)8!^)#~1cFbL1XVD)q5-P^Dx1laK;}S9rv!)U zl`Oc^)rOcI%S~46zqOwOZdh`YgZEV&^tm?M|Sm-WU9yW2NX$bk{O}P0Y(EjGKh~ z?rp3HJcT7cB(G4(!0n9+u-Wyd9}S=VdKrs(e_j= z1UUcP$WiOyC<(|@tdf#_p~pv)cb^30-p_1d%W0COE~E)!>ZIW~e|MOjqt=h%3hS^8 zIW)o@F*0bL-ARc(C_?OQyX+5}v4kd1qeKSr_2_wvgGLZUQ=X6uK}YTr70=fo)485{ zd2akf)6{BL5G!KrbT?J>5-_P;|Dq`(C^LgZN|B(+U1Rs-al6KA?$VEiA}ZRjUF4xaeAks~4>OF&qQ!5%ja7v*hnCbau<-PV zy^^ma_Q@1TWtguQCDtB-uUHQKuZOHMFdL%nh}8`I!}rBX zDotJ^vmJJXKBqdS$ufQxiYkmru;B@9T)F{IdKADRRtT~}5K*JSJ(f1~EIE#diS_!@ zwL6@Ko;w(gA(9y+7{-PYzNHicPb~a^fIJ!VfH)5JYvsDpIyzBGNm7Su9q5Y^MS8oC zA1EI*C8Q)8Go1y;VF{MprjjGKE{Ue5mlp=m*#tud1xr?Y)AbyCAk=*ZE!ohOGQRaf zoxRo1`YEEUKqPJY)&uhv9)=!39Vh8%(RDP10p?*;!$!JvxN%Bd3V13))EFe_N(;a= ztuADs<}+ooo;tcuAYHm?x?=pSS4Qowy!FIC8Ns1WVdvhg}Y>>SvTjiZ_mIr=3$shEWH1Y4u9^;R3@F_ z<*(rTLb*Vv=SIyR+2ABWGG+yOEVXoTM(EM0KdsHgiZ{64D4$1O{nF7_#tlE7 zP8M`3IND??UMf80ABU!p4YV9~y$;*|NjZ*fHai z8MB(IdM>@c7Db39UhMLojYi8|)$_miPbb40A9cmUNzWPXwLNh%mP z)G#m&B6sp*sYTmLk$fpp7J_%P5lM`pPF@H5w-M zEh zt2+w@(V~VW8v$GlOMn4F>-*mb1tldSgf2GH#Ah7KeP?0TRM*` zLBXxdvGa^lq(pR3=o8w|$1 z!Yqp;co?JA&H^yj5fmX@eQ-C8FF54@GE3XCS?;_ga>|y|z1&Y^vQwk7VGLyPBVHkg z!Y%;ZhJj@i?QD^m-mp=hAT@`T+asO|6Pry}W14v#o)R_e3666zXNG+f@npl?vzOo|1gL5Y|&XAiQt z19i7fQB}N)StbbGiM;Y)#QAcNYrShBnC2N$+n62v&@=1Smm`+w-VQ9MCAx;*)kJ5@ z1faGmG6<@}EvT@V!Uhw-0pWQu{t}j0jpdKuQ(y_8sX-3j!(t||xljwWO!LQl-niDw ztJN=mfN%d09X>)DQ|R#XGp8CS1;F!QEgo4C{YVUlf=})COABAxW#RytqB2|o1iY)r z4nGdK5L+Xe+ znr!+qCWjJwRGLYD03zfVd41Y2Lb0DaD>ZRn-+SdOuGBH{Kp>xWa7EZ5;!|@fu6Hvb zE<9hC3pn_XsPB0j#hq$55K>r^z*@lkp+0R8V33c3Dk6poUUC!Ib|x7jgDNhh?bss@ zVgiwNY`OucL#11aVgD5>1&gU33iZXiYA4pu&CbzKpWA?@`@ptCv0OAp4NR zq@$K4^dQOLy}3rlJ6f0;-`@Ldl(m2uY=UN?{yjjKJcdjYF?4SI4rEcx@Pd9&u=Z!9 z62~Z4qHOcCo!Mkevhh5eMw|G?WZNj8frE82PlBipZ$Iu|=icBak_KoB*Yhab) z-jeY$knlf&^PIaL5zN_|MEwLay!d8lOM8)cUTh?>sumjHt|-9%qB6iPS?oB0K#%qVumcI4L$6A5+SwKY$ivGp;PBpc#|) z(a)SX?D4XqPXRZG(KPm|Z4(OX+@5*D~NVu|uH>CWCtSkpY z3B0G_P@RcCt>#9=Wj4}v4bF|PNwr&D70|+e7 z^un&@EVt=*67lTT9{!w&JJ$@IY%nwV7XZ1i>c5t_G^AJzFewJxh^7q<3|zSu`&gwb z#70LnO`l*xUpu}=O9*3ZQ6y1bM7LT=!T}~f=PVBDgaS&&8*H0uZdT?*pA?IZ3U|gig2ap?IXl9O-gg$wUVjkEFKsP zTsJFHzlshFX~@;MYu%5>_}fXFpNR<+utST`W3Y=Q2!x=%+L9QkHQO)b_%g190Ox{+ zlQlEmGLS>EkydVb$wX}Je(jUXlPh}=NhaROp>Y0b>&3C`f~HooR+4wvu${PL8Va;1 z(gr46$-qZJ@ZX%;gYN&jPbv8g9h&SsvD;#|p!&grJ{I@yJt%5h`5^~ER<(JJMT-gai`Rp`hgaCET(z7)3zOWkuh73;VG!cLT;>#C&V?a54K4K@3NnGO0XNaT>93e;8{rm^ zL1XN-A#gP6iMBwjd8!D=pyXl&q&N%WVXhJ^xucHg{BC0>>VI^CX&)L<6e|ARy=#uCVEv#mqT`XcvU_q`bX#!?HSHw>3IWEuW zjWfHB?ng+u!Q}U|vz(|~5FJ`HyeY=fw6NDx5Ay~P zQz`3kCkL~g+SFlZH6he1D#v0@Ikv&EXDecrMKExM(l zIL(EQO+_3%K^0*|UvEIZST$X&Kv=vs<2R-89EE<3&0wX{3br6jnNC9wpEmY)+4xhF z2sJtC-pp96E(S+wb$p#3tI@k&AMZ=tu7QJ?eFjfy$v6;Pj;pql+i444TT3nie;N+V z%^Ge|u_9sxD)&v;oEzn&KRRP^9?;&q+;;*+(5c^mI~&EE=TW&NE`1oGyzQ)@mRa}~ zm#+xrPQ|r>-V~j!GnO+j)9K%xCcaM|a5)QOLE$W#KaiTnvKm)`0rTpVA$%3Feve%3 zW3G0ZDwpA1BLSv%P(TD>fyqYUhm&5=#Bf@^99p1=V`Fuw0T}f{h%oi8{lH}+Z1!tB zQRYcmm-_}pdRCWS{joz;`;#_Lm+f6Oi)eY;2(pB=E%hBE-#JBOrgmkj??FYb-QGnE zjMMt|K8c`h`Y8mJZ9v9%rqXnKIO|koIsu9Cia%xffU@X8dAK-T2g%0vGUw(NexeHH z&LWB*Uaf58v^ISEDD5fW7<7S^h^@S?1x}I(?0vAr7I3`&cP4V#Y+Re~*exeSYK;yAkX=(;#%RT#$gY zk2@O#)EXo_NZG*908d)4@Jqg*PGXO)WUT{a_e2b43~WhKvantIsD>Z%c@t{q8K^((r4Ucv6#kEn=^TyVP&Vt{kFy;DwMt~zJB z>^gfsgZxxGos}^=REjH#M6AO0gl`eDEOLSo)&R2oRYrlgWDRr3X)QS}nOPmgG-K@} zR+goXIu1oMgnHuknRRJvO@yi!^+Z>uY*KuTMj#9WCHnXgonU_zbyigh*K6$a;^pp zVhqJ+MUD}&)L&uK8A=5PEcI_E@`7Wkvy_*{#u6objo6*4!(B?m=-|GYI}}fq-g=oT zp@$J~X(GC)Yt=);blg~gs<}$;!T`L^i=h*s$_Ys1FD-LzERH)$64 zB!$pUr!v#NI49noOUg`?LDUrCHYxVZmMnWJ)OX`hg!mb!(>A>jaND1K1IYHQoAhjL?3m4df(E(1of zvtYi@R!w{0;k?GC$pbvOTKwSWW7e3mcpes`wLL^+cE$bzjdVcZzN?l`zqIWc=H~QT zD@n5qrKyDdXnV0CiJZFWhp>QP?Ha?E(12G^5)7I^$HAiAe@KyvHcDJgi*>=cisXTw z0-iT4L{*bD`{1wua@U>A0gX`|$#Hxr9`%5zIYx^TRb0Bz(IRJy&O-?IQL+aHasg_6 z@tc<4}&aXH{Gut~q=A?&wQhQ*Zx*No+G&8%W;|Zk-4f%9Z*oc|fw3 zq)x}NHb6)u5A$?@vIRO+EFc8)XIwDT#*T9iN#~4!MiC3>TwgR-gB;WX@7}8eeWk*jKbg4_79!F1WzTC z&bvFbYKF7f5Hv_blu#@Mnu%P@gBrXGe=Im55%}7g0UgjBPJEs$4>kOOy|6|(Ott~e zAhMl|Kx6i|RW!s`ePtNIRlC_)L-U0E4p+}q;#LR5h+HoPjb~s~Sp`)fk2HK-V8z{< z0a{C4DEGAv8d0}5Vi)~`UK_)lbP^WB3rKZ{s`8Rk_usn56*428e;(Td8Z_Y-$ozMdi z98{5~R);63VDM|D*zrOQE)p)xp{xDhunen?#5!RW(4?R;toQ7D4pR?kYx7Nj!~{p3 zgvJ}HKJg~6HZL0|QL?f@Nc@^;*`v~miuBu+8)sD-C}BGW*aabEdT<&b?gAqhKzjM| zD4KQbSz%$1C)3HBR2|ngS#P7(*r++M&L^bKizj;zRe?BN0yBX(H9*M$PkX?_I67Eh z(Q;4*jcFKgG-<6)JkRp`u`TRjD;s#>_z=-#W8L?-$~$Oz+@EV=+}Gd;ruj+Wq5}{a zd+4IeEUe?hSI|h47t&RulQUmzOi~9shOg03IZi(PZ1s$h4oT3($Vo>-2mk6#MQ{c+ z%L7t@{sdNG@LXh6-+5%QQzIlQ@k|CYL7_P`I{sTFhg35KSFf02<(Q{DXex?kF%x>J zf8Ke+VNkQ(q;0(kwYhQJ-gJhKO)oUl`LG<}%-3mQ+TaL~+%U9@Z3nt*qkgbI^vRDg z>_n54>ej{Xrnam^-QPKXmv=WN#I%@4hMi|@d!7m@IT%t0({Yo8Ie(8jXCLQ=(bA2o zb_%93@M#qZYoSdS;SRDOYYV%P*kWBlg;PyH8AuIa(A+46u`Y~v#bFWHu%U|N`0#0U zQzh4LkjxBH-DDS%LfkwZ(aiw0l!P4ZDFk_1?SvMscu(sTa%<<#K;NM%er$SM%zp8O!@I0F`@GaJ=?qpfo;4nCx6`GpCIX)0nK8!9tzGY`>HlIS|v{g_+V~MKC&{On|`0p2B z-y>~WiqB+d?2+sEY=8HqM}CA?q3U)fBx>WXl#X&~$*L@3!#JN)2C(WRlW!b2H6aUi zWZsB4!W#;fRiWUxe*FmmBC;@x;g&Iz*QgbF(LL#gp#q+87V(k2!*-%h8%LUli@6df z2pug+I^Zk97`HA3x5(lRoh=Lh_y0WcM_<#fYa_Ati-0;GePEPxWi8pt1|VgAX%3bo z%@&AzK+ij3PU~Fpq+D4SDN{a3Vr`XGL%UNiLG*;kxmtx%@He@<(N5 zTR}TM#vHY?MFdc(JFQKT@c1fc7$Lb+vI+oa%sLm%YFf-2U*)lmh-(nR;PAVMQ9;3U zbc8#pUtL=>4;(&gY_)E;~)m+7aWjXn-v0S4FFsBb;LnNv09Fk?O^wOq%2Sc_|M&hbT zCqXBe`!F2IDI=ZxQ8jumea=QFlK1}AKaTF$YqA9u*OYPwhN*`fHxNAXNFNVY)wRf} zEv`6zEH@I44~(*E=)HEi%E}i{e*X|2RyBC${jX~le(XK+oJeq)0&qu7amaPN#IDm8 z)D5RkzBSbI(9?W@qOt19uyKM|(x;v!56*aA&+R{B?4=h~BP-wy11-YaN~G^f=M@EW zLJQcR`WHiwyP945eB!msrB$`2+PES;sU$zeqL{_nFRF9Id*ANQD7A4#iPfE)G=|iO z**6D%m%uM7=F=~;kX9QSm@_%LutItH6vqjta2$<;I;6;Th-J<}7}Dwl)XB2z?c;oatsZB+CZUL!^%syH7o=HRVgJlTB=yzlE%#b2rh) z*iG47)JGQWIoCK0)uc~&2Vx6R#(jf>-Fus-)fr6UQ&x;#J^$pR854Lj?7pYg)dixe zhFxILt+q>E&lZ$dkJ*;P0wnTU`=)uF>8ZpT;f>q;mMv^^%9|P)qL4r`RoJ*W0#+qs z6~GLkLu-*)?FEjMibo)|lC>MgM4e>%Rv<7CqB?tic+cSWK4fL800vXs3Pj1mC!cra zn`u`r6_k1$ouWpimt!w3t6{_1L?-QVBpifl5|C9QE$&#jtnp?cV|!hqz^uFRgr!nM z)mQPe#vtoH`p!qeM`hJBqKAkDXDCgNwXPmR7+YN2un_`4HC=>pb^AxYu=uvGr{pbKxB&!W=jj;g_=tmGIyCZ| z=Wq>7M99GqT2G53O1cs1{oy_KAelj0oLakrhRxkph-`gse^>uNasjXT5rc)qhIj8X z_y`hfETb^gXdJq64(S&_k=%ef%a`lPXh962t0YXyf>V zlg5iKWKN!0w;9&}FyqaI5p0fglP9;V5g@cu{9&P$=9W#NN1&FAIQ&WziwPAA7``|c zTR+sB4%#%FRT_6qPuAFylj`Smnu!+~>RE4jyv-M*2yGfy$UD%|@=mnC{i_w-(VzPx ze|Kl9lI*xLscYW)f^L&eYV6XYrV7?<#E1IRV?Vynz+4+QrV0%@U{=kShY$+UeYZBhzwl5R=FuD}2VS-}r}y^ca1~nLNjNS@v9_fpJz9 zQ^i>W^QXeRSv-s_oC_XqDK+?=9}x+I5!YNts;UTeM`-S4lgLQBtM6M;VO|x1l?fB7 zfHw@*UY`*QD;IAG5#BJb=FVv1Gy^dQ(l)x4MT~8Qygb-bvvtX&T01}Z-c(noj%mRh z$w#i9uySa94%dMF6*K(RGj2@+yo1VcP+gB8sz4C^=h01R@um`-@N0FUpmL9BJMJo zj-2JWTNf!)rX5#jKapxo-VYdC%uV{b!8~9+6Z776G#l;TO3^8Qi|mFd2*bpaZg%NAdVREgPv$&>Gm{t;|L7>pz!tGV+ED|(4JkhWpU z#*4`?SD~jBe30sX<{s6=dY<@@f=;~{RcqrF25EBnCq80w_jiAc4c7Hu6H9e84jz;f zD{uW|C#9I>`vhWJ`Fsf08pfE~qUci+ba*F&s~qr z`-8X0s4Eh4&sqC31svDpzP;`ts5m5tG~HD}- zwVL2f{6HZ=$Wm6q)_ZA7tfkSK9>oa)wZt4Pi*O8rA+9hwYMf$? zlvin(pqDni;&>j6M;)`PpS47)=K64XJW2vg5MovJj4LnIljpM9grb;1z%?lL_4;e> zvSeFxyVNYBas`|y*mYv>DakTBFO1r`b9_Vu#bu0{NJT6vIznHU#xfDk4WKcZVR$eJ zxKdyv*E&+9l#-GFv`!K}3ARyM8;)D~F19mwede2cU);wlj{q`4uHfO0XmbtzW_^Bm zjJJ|V%kocGtfgaQvn;;2I=V<*z(!3OHX^MJg=$^3YD}Q3T?!qS zlH*wDvPCjKw1E*^f>NCVA}n?w0ue?rx2I> z;2LlXSOp|f;1FL%9i_w_7~8bNwqSTEMuaSUT8&(2x@5OVJ}i97TDpsHYXC%?7$YR? z*3d@cv)}Qi&MPiq7Y3o#DIx+#qAZ@nkSIb+4~{*zg_hOj5?Mi2E5Q-(44x@G7@t=6 zvMgCG41jpHN#3$!NXo)bJ5kL+fS84i-o32;PUn(C5mI6Q*oH$PMnMKV|EWP9$V+V4 zK*h#o5;f^6O_`K^kZw$qBBHl**$hcoYexqg=i-q_qsc~QF36gSE%;2LE2$^^JFk_c~y;?}BwBH|8k%nlWS%?hVi!=i(E z{z##k5m6YJ0}fZyid3jexD;TC6HZ+G^-mzTo?Cu`KSc@vked=u1ri4(%Y-)gq<&mm zTNmHidI@hNW$^_LtJHiaJ&v(AE}VN%*oz>Uyl%61Z^PJrC+G-hh1>eMT^x!K02?^8 z!f1?4ov;tAkQb9UfNUde|G@*)iuz-}A!L6QFdy73Qw~!UtEPmF&7#>%X-3n`m7r)@ zHo}xL5G~G&e>+?+ zebAYgwF4Qz<*&dg)QrC7zzA1QtiG#D%$0EKqq7Rj`nottvTDFTTm!sfr6y|Rl*W;B zk?Kj;1SwrqJDU;m$z^24gpb7XIPLhLzxrL76Ia}-(7N;}UVjc^H1g<+&)B|^_bg6W z1UQ@$o8e&^HUFBoV^C;|z%?+LdbkhcQ|e1ivY0OL&~Mfkv&CXI%aj;#Mrs$Xsf)Wv z1UOCs3_e_{lv&F~EZNr1({5hbdHxDoHY_v%O7WNCIn*h}1FMM(M$;V3=baPU7lbSa zUcAg$l}rLg5vw%^aC!Iqq2N654#aLH!>)2=V#b_<=~yy1(IQSVdQ7NFB9G{+xhGY= zts<_$x9)I7h*WOxFyz6C$59+z*Ud*Vu-0B*UmQ$X9{lE+(=Lzh>v#M(K%H;(PXSkt z${?|oI0zVkk>9+q_3wTWrf4H)IORVjPP@x$;^3fs)|rhDzk!99aAm{s3Vc=xRsjPH z9Q(QVvt$5_9nc5^Pl-RG#^JIDD{ed>H1=f~5$PKA5u1?P6tx1AVfMmc>@rF>I|P@Q z1N2Wi@bHMaf0e0Z_0VXTIp8r@Ni_#jRvZES#Y2$Q1e_#UDek%HcAE)n=A^)Ix}?ah zOOJ~w z^fCb+8mtvb1rAHI9L3VC3Mta=Sj?CEc+hKlvZ{c*%B%^_8=Fu4-X|Ace>DZ!_}umF zzaQP>ADVWkCV*X1C}L`CFqI)gg`&}F3Nk5>_a;hDlddKsJ8#^7ep9$?X*|-)96-k_ zmgu>bgKrabqft#3Ukbv8MeAB}U!N*4*^VUZp$OxUg(Kev3Rba)Vjf1HS!avnR7o%d zm)(*QP7ebJU}H0r^PohT--)k?(3vx1(c)!Flq6-wSnOeUfZ{GM%#uqi8)27Gr$yq= zF<)SRVZ)Qp)x?IA0DANMhrY_Qy+$a^LpUC0V^aE5vT_9z>8!IjrL-JaX%;q}jhMpr zU13>8;19*EtELH-<*Ji!Udi_z8EN^jO`%_`=aFwcJ4`y=Kg5}iNT&(|SiHWa5e1vj zqEd(;C6~^Q@8@l86ADaD#LWuSP(`lPxs4^ z8@Tr2yaS7?IneQ3UX|O|XB8lfL)-F-WhZ~(4|!Kq7=Y*1Xcc?_Mo3vTmFlv5D3(wR z0^6i5O~-svthiltJXWN3{ms8*_v|xT$RZK}(gbwsUw*XVjMKO+X8G!yFcyw*Yl%d7 zz2|eE_+EBYzn;K-Eti~4?CL13cxj5mYCIFR9?g3nU9!h~q5j5yw1O3iy8^E6q`>_C zC!Zt2+|ZRJVxn*Pz3fbG_Q- z3anxaA_^8KF=(M+zy0uE8aBsR_%>403Gt3*ID9#abJz9QF6j}4fgv;c;o_w@6YU+x zfByY^mqA_|N4-)>IRh5Fu_8k5z;D$YC>mU4^R+6-4#0V@Ue@-`OSDs}t&_4COH6?r zE|5po_4JWU5@fC*7d@r%k$$n|vbN~e|NENmZ~e&0%Mm3KPNd;GuX%^Q@!@R zgkORZW(;5_oN&Q#BCClghrN^biZsVTO%c5GTthVuV-#!MQAgHywunZOqt#Ef zwjOoGd7G*>2Vsn5)glZ~Aow8xM-cry3g*)Hmcwg~c9URC3r;K$R=Q z9Ex1VhU32ak&g4u;LNsoqhk4FS&AW3{Zf&?{9}jPOQwJG9DGh$sUxqr0EtOwLy2{3 zJf%iGj(=P*<_mGT71EiBGar{XtSO7~T+?`Y8BXH4s|y>%hP^N;$HVDTNpAHu5M}yK z1qQ0g5{%Y`?X4_Rmf?YU-ZNtsBn&LVR76DlIJXk>FYJXWDBcd9(vAn7336h8TaKK~ z=WfGDBA~XBAuoPVLUn9*$`^sBb77mN*Ub17>@o@ioPm=-9zE)K{R+mh>F_b#GRTUz zD#3<%DD;v_a!Vu{=Fi5EO^+I05%Vd|yBgxvA>O zFJ@V`w18kXRJyV##X=WC#MGoH z+Lhw%Ra!Bfi3_J<8VEw2Yf_m5xbY_o=rPVrHF$V-LRte>*qijNlo)AsUC6LkcL*i{ z(X$n>E^dO_Hm!>{{L9Bjs~9{x&OE7Q`N`T=g0S2rlN9!H1%{r`(1`e^vo}x!V9%k0Lwbm;n7#W$=>{{YuD06eD|^(VVXw1aqxS8u~5b2 zwCfKqWxpob(IHQrs>?}pi@i$}3t}5|cItc1{_rLz${u@q%K9O+`b7%`O!XPX7NPmW zR+-WWZQ2TkZQ}Vw4|Aqhg)^OD!Ym>t=g4_q{O(}>Z82S_3xghv_{7>l{}cX~@G7I_ zFtYY@fs!FnkD1dSt)EM(#O2c83GYLeK&S!|&*gXB{-D8B4v)I#BGUkevlQN@ESk>r zr5IU*ErmbmDa@Y4a}$Fob;fNKv2?aF%{iRrfb~ml4(g|FuW&Na7Li&@)6~^XTeBnB z4&p7208cF)hB zl$44WB>3c;fQU&^H-mTuxwSPPh=R##qCOAG=xgU+D{?29vWG42N|wv2>>^U>gMInt z0EK3^ye>GC7e@sJ;ybG{Pd2$5oWGS2S4h&u5a@-h`M@zL82(BHctA~Q(YiEOg_=*a z9r{sBw4ZrWHQS|?N!OGsFM5;5`lXNr#%nRjd|}ovSG!}5HQ}rkm}6j!pQ6+D&O8-L z1@8dglc&+~YYq>4)l`Qnp!#gtww`e^FWqO}muzg{_@nlL$?G5$v_Z8+mKD-tQz{q~ zIaE$3^OX~!afw}sl^oCP^7Ujp6#xJUu1Q2eROIfo6^w~Cr#Zcbq}RW{O5q24*wu*e zAi(IppbCIa&V^mDo?D6=DRZeURh_34PrA_$Y+x)cFgXRGlOU~YrX=Xc@*8RaOEy|E zE8z+t?JgRU3y42m5XN*?;unnqAEbNO)3qE~vLYg(vY~R36$EiRb#kP_B+tZl5XhVE zMi4;}=UE*B>p#+%%3B05bUPtcfP=N_Q2ybKiVTe+lB6bFXa$1-m$YTj%91W=ahJY^ z%Wn%20#cEsB}L#0nudtb44bK6K$lVuvJ4r(c<^1dO)zF)K#}^Y9&l)y zLl(SB*HaHg^alWWwRExq@^SKcA`uSNlMzm)LMwe+dbY1gQ^GrZzBVfjiR+WR zT6w?4Bu7}0OBzUg!YZx+p?OqQ6gV)DNzRd?5-TTiGv1|jP00?7PL-w202v3$MyySr zKnQ!j6}2@WU;YyShcV{N84|R*Pz%*9RRFD$33h62P&ny(s-o5qR7F%x(nN11QIdiN zqwO@EG;6^MRIIPc?idz9jeR9K8RxNJ+DyY_@l0?`I!;Rl{bSN^IMP%WHV#jON!QEo2PDv5}{=58b*da34#w3K-( zZbNYt^03^)fV4J@>xeB9qf)A7)|{l!IRv7bOW~-UA+chliH7KnD1G31m@1MEfRyvq zFqVwSN}y#+T}i}3l3^kR+}8nq{Fqoh>gqQEs5D(X&7j?!yChs4u4}?OI9wEG=*&o9 zMgqU55(vD5-&6Kx*qV{Rj06s!1ZK>^;gj_Yr5OqQo=afH9Q>X)&)!wA%@40B%W9RB4+Lz+92|)B zX38E+2@I~iH~Yn>z35ZsQ;Hy{xX+Ks3E|)BF`% z*Z6O#Doe~ImgTB!qUY_}{MHrw-uC&zF_*P`@zeFktRKJP>h2pJt<@?z(gzZmcxK^1 zu*{U5k-&jVV07&h-g`gIhtnB>tJlkcjgNWWInDFSKR=r5S%anMxa%8>VysLJMAKV~ zURU!89o5v-xN4frRoTRy-@kf%mDlj_1It#(x0<{QFW$5Ky8aK`)c2=v;5&zh`6_EM z4>J!l4!S9)IG;QR}^w`E4Rt={lfBXq&0hbQr4yeU@~MBb-UCNaHw z=ui6+U%S<}1zLF4Sr_q9``ke6^v6gXig3mp915w<;H+H&>D}2?p11ai)b+P^t-iJ1 zyKivuO?2U=c}u+?-8!VMbMM377{7Z-+qp|a*msTJ;w9dDVoQ2=`ljyUO?M`*p!oNb z-V-B_Y-l^{veapP-sM-dK73t!)B5%EFB^YkdHd>nJMaHo@usir`P!;n)kItV;Hr9F z(0A@tE%$$;y=is#@@w;_eY$hay&bomlfB}yEgK_USGe-7IBD`5KFcKbuDTbGqy4@9 zQ_ohfrp1??GVs{q$(}{1%7-voy;F7wnV3O6BY||!2F~d=Jicn#a`DF7zjjskc~6X_ zz4^z``Mbw42TfahuXr$d+r5jJPPmKy%zGtITbS;7f>X>v@yzJC-dJNp+u{?^@50-z zT)blG&hKAP@Xnd@@K=t+13zJ@cjGMsw{;JGpsz}zOHNz3rPE7%;L0WEE!}Yg?`XVe z_GjPK?q!m1y>`sIZ+!Ju?}U>aQ=V~k){==7V&xLEJ!`zvuALRG(WEkm`)0-*9Bzrv z&?_r};k6HX-c@te76?Go5e8P*w3I{pc+NeKe7xXYb>tOc*T5UzzQOalnvUrhefqIb z@!c6_<6m3or8kVP^%_p&1t*!zkNUhDuM=CKSF%ywO*~dT(e!#|`1LpPuC2k0yjSJH zeTyG-Z*flqXbrh?<-wKRR!EeBYx!>Nq^p19>aoTXk6Jvb_#}u!b~7_sIAo%oL0pRj zMpm-~aFSaO+*h?xIlT5!Sx21ZjXeIKxAGi& zEopAs`hwYr?dyx)l9po>t9%NlAt=3T_#@rk8<%9OCYtV91FBYfc)Bl#U3h%eDFbWx zJE7;^_WN&L@Ya(8%<`*TUDY~y%rBdmXk_{D=KVdP()(BKS#j;)(|w_&!xx<~2ZvAA zGnC>b&^!K!=dJqu&d1jeqHDAd z-g{1)*PFh{OP;sXeBHtAcnTM_&GH5r{7<{GU54()h6_FK`kS|H+%>#^>z2=6oMpy- z{YlxXiSB9cX1jWnkAm_|X77nsNXV6VY z0_okkwM!cBymI_)SK9l$>+k&O@#pEr8(n!GW}&O~YZptuo^WpA?S zcz=E|x^}g<^7Cz8W%n>MVgnrO#a@9OtH zGqU39zMjVxefEwmtF9hg{-B&dIQOc~ZJ(L!2lQs;HG>Zht-yl#SMshOrphEfaO8zc zmu$Or$5q#ktawlv^H})oym6(Q)C33))9=g!&WB0RGi0iiz-T&?YzkfvC}ny#b-p2c z;~l4bfCan*z0&CnD~>HqZc^eSoCZ-v)p)QzfHImM98ZrYn%X-{cstNK*HV7QJE%ok zGq8tV0-?T|*>UF`qpNPtdza2T7`6c3n_@cKG>FEbnx2GOnmP`aWtkoWR%*r^9EkL0 z${tz?B)qNOX + + + + $Title$ + + +$END$ + + \ No newline at end of file diff --git a/test_cases/test_model_fields.py b/test_cases/test_model_fields.py new file mode 100644 index 00000000000..e940ea88e72 --- /dev/null +++ b/test_cases/test_model_fields.py @@ -0,0 +1,40 @@ +import os + +import pytest +from depot.manager import DepotManager + +from server.fields import FaradayUploadedFile + +CURRENT_PATH = os.path.dirname(os.path.abspath(__file__)) + +@pytest.fixture(scope='module') +def setup_depot(request): + DepotManager.configure('default', { + 'depot.storage_path': '/home/lcubo/tmp_images' + }) + + +def test_html_content_type_is_not_html(setup_depot): + with open(os.path.join(CURRENT_PATH, 'data', 'test.html'))as image_data: + field = FaradayUploadedFile(image_data.read()) + assert field['content_type'] == 'application/octet-stream' + assert len(field['files']) == 1 + + +def test_image_is_detected_correctly(setup_depot): + + with open(os.path.join(CURRENT_PATH, 'data', 'faraday.png'))as image_data: + field = FaradayUploadedFile(image_data.read()) + + print(field) + assert field['content_type'] == 'image/png' + assert 'thumb_id' in field.keys() + assert 'thumb_path' in field.keys() + assert len(field['files']) == 2 + + +def test_normal_attach_is_not_detected_as_image(setup_depot): + with open(os.path.join(CURRENT_PATH, 'data', 'report_w3af.xml'))as image_data: + field = FaradayUploadedFile(image_data.read()) + assert field['content_type'] == 'application/octet-stream' + assert len(field['files']) == 1 \ No newline at end of file From a3e858408cdba083ccdc4de0a8746799dd17604a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Sep 2017 12:59:13 -0300 Subject: [PATCH 0257/1506] Fix importing services with unknown status value Use "open" by default. Also add mapper for "up" and "down" values --- server/importer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index f05d07d355b..0e65250ba1b 100644 --- a/server/importer.py +++ b/server/importer.py @@ -343,11 +343,13 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation document['status'] = 'open' status_mapper = { 'open': 'open', + 'up': 'open', 'closed': 'closed', + 'down': 'closed', 'filtered': 'filtered', 'open|filtered': 'filtered' } - service.status = status_mapper[document.get('status')] + service.status = status_mapper[document.get('status', 'open')] service.version = document.get('version') service.workspace = workspace From 778ad64123ddc710547dee6c9202d6a152bf1489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Sep 2017 14:07:09 -0300 Subject: [PATCH 0258/1506] Adapt workspace functions to new API getWorkspaceSummary and getworkspacesNames --- server/www/scripts/commons/providers/server.js | 4 ++-- server/www/scripts/vulndb/controllers/importFromWs.js | 1 + server/www/scripts/workspaces/providers/workspaces.js | 8 +++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index 145a3661aaa..abcea8361eb 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -9,7 +9,7 @@ angular.module("faradayApp") .factory("ServerAPI", ["BASEURL", "$http", "$q", function(BASEURL, $http, $q) { var ServerAPI = {}; - var APIURL = BASEURL + "_api/"; + var APIURL = BASEURL + "_api/v2/"; var createGetUrl = function(wsName, objectName) { var objectName = ((objectName) ? "/" + objectName : ""); @@ -235,7 +235,7 @@ angular.module("faradayApp") ServerAPI.getWorkspaceSummary = function(wsName, confirmed) { - var getUrl = createGetUrl(wsName, "summary"); + var getUrl = createGetUrl(wsName); var payload = {}; if (confirmed !== undefined) { diff --git a/server/www/scripts/vulndb/controllers/importFromWs.js b/server/www/scripts/vulndb/controllers/importFromWs.js index b4fd4ee4ac7..e07ca0e9e1a 100644 --- a/server/www/scripts/vulndb/controllers/importFromWs.js +++ b/server/www/scripts/vulndb/controllers/importFromWs.js @@ -7,6 +7,7 @@ angular.module('faradayApp') $scope.data; var init = function() { + // TODO migration: use workspacesFact that is fixed ServerAPI.getWorkspacesNames().then( function(ws_data) { $scope.workspaces = ws_data.data.workspaces; diff --git a/server/www/scripts/workspaces/providers/workspaces.js b/server/www/scripts/workspaces/providers/workspaces.js index 2d847863fa6..c98f029cc3f 100644 --- a/server/www/scripts/workspaces/providers/workspaces.js +++ b/server/www/scripts/workspaces/providers/workspaces.js @@ -9,7 +9,13 @@ angular.module('faradayApp') workspacesFact.list = function() { var deferred = $q.defer(); ServerAPI.getWorkspacesNames(). - then(function(response) { deferred.resolve(response.data.workspaces) }, errorHandler); + then(function(response) { + var names = []; + response.data.forEach(function(workspace){ + names.push(workspace.name); + }); + deferred.resolve(names); + }, errorHandler); return deferred.promise; }; From bdc3073fb88487ece3d640e5cc3549a6cea0be76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Sep 2017 14:15:20 -0300 Subject: [PATCH 0259/1506] Add trailing slashes to API endpoints to avoid double requests 301 + real request --- server/www/scripts/commons/providers/server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index abcea8361eb..d2bc39e90ac 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -13,7 +13,7 @@ angular.module("faradayApp") var createGetUrl = function(wsName, objectName) { var objectName = ((objectName) ? "/" + objectName : ""); - var get_url = APIURL + "ws/" + wsName + objectName; + var get_url = APIURL + "ws/" + wsName + objectName + '/'; return get_url; }; @@ -31,7 +31,7 @@ angular.module("faradayApp") }; var createDbUrl = function(wsName) { - return APIURL + "ws/" + wsName; + return APIURL + "ws/" + wsName + "/"; } var createDeleteUrl = createPostUrl; @@ -225,7 +225,7 @@ angular.module("faradayApp") } ServerAPI.getWorkspacesNames = function() { - return get(APIURL + "ws"); + return get(APIURL + "ws/"); } ServerAPI.getWorkspace = function(wsName) { @@ -270,7 +270,7 @@ angular.module("faradayApp") } ServerAPI.getServicesBy = function(wsName, what) { - var url = createGetUrl(wsName, 'services') + '/count'; + var url = createGetUrl(wsName, 'services') + 'count/'; return get(url, {"group_by": what}) } @@ -285,7 +285,7 @@ angular.module("faradayApp") ServerAPI.getVulnsBySeverity = function(wsName, confirmed) { - var url = createGetUrl(wsName, 'vulns') + '/count'; + var url = createGetUrl(wsName, 'vulns') + 'count/'; var payload = {'group_by': 'severity'} if (confirmed !== undefined) { From 6701c9f8e1b2657c711b60763740ed4ff8fda34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Sep 2017 15:29:45 -0300 Subject: [PATCH 0260/1506] Refactor workspace controller to do only one request The workspace list was doing 2*n+1 requests (before and after couchdb migration). I changed the code so now it does only one unique request to /_api/ws/v2/ and fetches all the data at once --- .../www/scripts/commons/providers/server.js | 4 ++ .../workspaces/controllers/workspaces.js | 41 ++++++++----------- .../workspaces/providers/workspaces.js | 8 ++++ 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index d2bc39e90ac..d50ecf62c19 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -228,6 +228,10 @@ angular.module("faradayApp") return get(APIURL + "ws/"); } + ServerAPI.getWorkspaces = function() { + return get(APIURL + "ws/"); + } + ServerAPI.getWorkspace = function(wsName) { var getUrl = createDbUrl(wsName); return get(getUrl); diff --git a/server/www/scripts/workspaces/controllers/workspaces.js b/server/www/scripts/workspaces/controllers/workspaces.js index 4dd5bd8b81f..a43799e4a6b 100644 --- a/server/www/scripts/workspaces/controllers/workspaces.js +++ b/server/www/scripts/workspaces/controllers/workspaces.js @@ -32,32 +32,23 @@ angular.module('faradayApp') $scope.hash = ""; } - // todo: refactor the following code - workspacesFact.list().then(function(wss) { - $scope.wss = wss; - var objects = {}; - $scope.wss.forEach(function(ws){ - workspacesFact.get(ws).then(function(resp) { - $scope.onSuccessGet(resp); - }); - objects[ws] = dashboardSrv.getObjectsCount(ws); - }); - $q.all(objects).then(function(os) { - for(var workspace in os) { - if(os.hasOwnProperty(workspace)) { - $scope.objects[workspace] = { - "total_vulns": "-", - "hosts": "-", - "services": "-" - }; - for (var stat in os[workspace]) { - if (os[workspace].hasOwnProperty(stat)) { - if ($scope.objects[workspace].hasOwnProperty(stat)) - $scope.objects[workspace][stat] = os[workspace][stat]; - } - }; + workspacesFact.getWorkspaces().then(function(wss) { + + $scope.wss = []; // Store workspace names + wss.forEach(function(ws){ + $scope.wss.push(ws.name); + $scope.onSuccessGet(ws); + $scope.objects[ws.name] = { + "total_vulns": "-", + "hosts": "-", + "services": "-" + }; + for (var stat in ws.stats) { + if (ws.stats.hasOwnProperty(stat)) { + if ($scope.objects[ws.name].hasOwnProperty(stat)) + $scope.objects[ws.name][stat] = ws.stats[stat]; } - } + }; }); }); }; diff --git a/server/www/scripts/workspaces/providers/workspaces.js b/server/www/scripts/workspaces/providers/workspaces.js index c98f029cc3f..d0b0fcfe8cd 100644 --- a/server/www/scripts/workspaces/providers/workspaces.js +++ b/server/www/scripts/workspaces/providers/workspaces.js @@ -19,6 +19,14 @@ angular.module('faradayApp') return deferred.promise; }; + workspacesFact.getWorkspaces = function() { + var deferred = $q.defer(); + ServerAPI.getWorkspaces(). + then(function(response) + { deferred.resolve(response.data) }, errorHandler); + return deferred.promise; + }; + returnStatus = function(data) { return $q.when(data.status); }; From 64f66059cda586b443a69c3e1d42b4da9e3d09c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Sep 2017 17:13:52 -0300 Subject: [PATCH 0261/1506] Change format of start date and end date in workspaces API. Make them available inside a "duration" field. Use JS integer timestamps instead of ISO-formatted datetimes --- server/api/modules/workspaces.py | 10 ++++++++-- server/schemas.py | 11 +++++++++++ test_cases/test_schemas.py | 19 ++++++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 2fe71786120..445bac07782 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -13,7 +13,7 @@ from server.dao.service import ServiceDAO from server.dao.workspace import WorkspaceDAO from server.utils.logger import get_logger -from server.schemas import SelfNestedField +from server.schemas import SelfNestedField, JSTimestampField from server.utils.web import ( build_bad_request_response, filter_request_args, @@ -46,14 +46,20 @@ class WorkspaceSummarySchema(Schema): attribute='vulnerability_total_count') +class WorkspaceDurationSchema(Schema): + start = JSTimestampField(attribute='start_date') + end = JSTimestampField(attribute='end_date') + + class WorkspaceSchema(AutoSchema): stats = SelfNestedField(WorkspaceSummarySchema()) + duration = SelfNestedField(WorkspaceDurationSchema()) _id = fields.Integer(dump_only=True, attribute='id') class Meta: model = Workspace fields = ('_id', 'id', 'customer', 'description', 'active', - 'start_date', 'end_date', 'name', 'public', 'scope', 'stats') + 'duration', 'name', 'public', 'scope', 'stats') class WorkspaceView(ReadWriteView): diff --git a/server/schemas.py b/server/schemas.py index f7b046a2047..63d4fcc49cd 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -1,6 +1,8 @@ +import time from marshmallow import fields from marshmallow.exceptions import ValidationError + class SelfNestedField(fields.Field): """A field to make namespaced schemas. It allows to have a field whose contents are the dump of the same object with @@ -18,3 +20,12 @@ def _serialize(self, value, attr, obj): if errors: raise ValidationError(errors, data=ret) return ret + + +class JSTimestampField(fields.Field): + """A field to serialize datetime objects into javascript + compatible timestamps (like time.time()) * 1000""" + + def _serialize(self, value, attr, obj): + if value is not None: + return int(time.mktime(value.timetuple()) * 1000) diff --git a/test_cases/test_schemas.py b/test_cases/test_schemas.py index 684097dc8cd..1c96b330098 100644 --- a/test_cases/test_schemas.py +++ b/test_cases/test_schemas.py @@ -1,20 +1,37 @@ +import time +import datetime from collections import namedtuple from marshmallow import Schema, fields -from server.schemas import SelfNestedField +from server.schemas import SelfNestedField, JSTimestampField Place = namedtuple('Place', ['name', 'x', 'y']) + class PointSchema(Schema): x = fields.Float() y = fields.Float() + class PlaceSchema(Schema): name = fields.Str() coords = SelfNestedField(PointSchema()) + class TestSelfNestedField: def test_field_serialization(self): point = Place('home', 123, 456.1) schema = PlaceSchema() dumped = schema.dump(point).data assert dumped == {"name": "home", "coords": {"x": 123.0, "y": 456.1}} + + +class TestJSTimestampField: + def test_parses_current_datetime(self): + ts = time.time() + dt = datetime.datetime.fromtimestamp(ts) + parsed = JSTimestampField()._serialize(dt, None, None) + assert parsed == int(ts) * 1000 + assert isinstance(parsed, int) + + def test_parses_null_datetime(self): + assert JSTimestampField()._serialize(None, None, None) is None From f0b88c222f01f03653d26f970e3f4fd43a116b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 22 Sep 2017 13:01:16 -0300 Subject: [PATCH 0262/1506] Remove SQLAlchemy warnings on count properties Use text(), column() and table() functions to encapsulate parts of a SQL query, as indicated on the warnings --- server/models.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/models.py b/server/models.py index 6c4399bf205..4df6d706d9c 100644 --- a/server/models.py +++ b/server/models.py @@ -21,7 +21,7 @@ ) from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import relationship, backref -from sqlalchemy.sql import select +from sqlalchemy.sql import select, text, table, column from sqlalchemy import func from sqlalchemy.orm import column_property from sqlalchemy.schema import DDL @@ -95,9 +95,10 @@ def _make_generic_count_property(parent_table, children_table): children_id_field = '{}.id'.format(children_table) parent_id_field = '{}.id'.format(parent_table) children_rel_field = '{}.{}_id'.format(children_table, parent_table) - query = (select([func.count(children_id_field)]). - select_from(children_table). - where('{} = {}'.format(children_rel_field, parent_id_field))) + query = (select([func.count(column(children_id_field))]). + select_from(table(children_table)). + where(text('{} = {}'.format( + children_rel_field, parent_id_field)))) return column_property(query, deferred=True) @@ -595,15 +596,15 @@ class Command(Metadata): def _make_vuln_count_property(type_=None): - query = (select([func.count('vulnerability.id')]). - select_from('vulnerability'). - where('vulnerability.workspace_id = workspace.id') + query = (select([func.count(column('vulnerability.id'))]). + select_from(table('vulnerability')). + where(text('vulnerability.workspace_id = workspace.id')) ) if type_: # Don't do queries using this style! # This can cause SQL injection vulnerabilities # In this case type_ is supplied from a whitelist so this is safe - query = query.where("vulnerability.type = '%s'" % type_) + query = query.where(text("vulnerability.type = '%s'" % type_)) return column_property(query, deferred=True) From 987bf1f9417d119dd23023c480dd91ee9fb049df Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 2 Oct 2017 12:47:12 -0300 Subject: [PATCH 0263/1506] Add comment and cleanup --- server/models.py | 1 + test_cases/test_model_fields.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/server/models.py b/server/models.py index b8cc5e5b5e0..a84b559b65c 100644 --- a/server/models.py +++ b/server/models.py @@ -673,6 +673,7 @@ class UserAvatar(Metadata): uid = Column(Integer, autoincrement=True, primary_key=True) name = Column(Unicode(16), unique=True) # photo field will automatically generate thumbnail + # if the file is a valid image photo = Column(UploadedFileField(upload_type=FaradayUploadedFile)) user_id = Column('user_id', Integer(), ForeignKey('user.id')) user = relationship('User') diff --git a/test_cases/test_model_fields.py b/test_cases/test_model_fields.py index e940ea88e72..0dc257d1268 100644 --- a/test_cases/test_model_fields.py +++ b/test_cases/test_model_fields.py @@ -3,7 +3,7 @@ import pytest from depot.manager import DepotManager -from server.fields import FaradayUploadedFile +from server.fields import FaradayUploadedFile CURRENT_PATH = os.path.dirname(os.path.abspath(__file__)) @@ -25,8 +25,6 @@ def test_image_is_detected_correctly(setup_depot): with open(os.path.join(CURRENT_PATH, 'data', 'faraday.png'))as image_data: field = FaradayUploadedFile(image_data.read()) - - print(field) assert field['content_type'] == 'image/png' assert 'thumb_id' in field.keys() assert 'thumb_path' in field.keys() From 5b8d02e11f05b09b3777bd1e84f3fc54f4e7f426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 2 Oct 2017 17:49:17 -0300 Subject: [PATCH 0264/1506] Fix count fields for postgresql I broke it in a previous commit --- server/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/models.py b/server/models.py index 4df6d706d9c..90f736d5f82 100644 --- a/server/models.py +++ b/server/models.py @@ -21,7 +21,7 @@ ) from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import relationship, backref -from sqlalchemy.sql import select, text, table, column +from sqlalchemy.sql import select, text, table from sqlalchemy import func from sqlalchemy.orm import column_property from sqlalchemy.schema import DDL @@ -95,7 +95,7 @@ def _make_generic_count_property(parent_table, children_table): children_id_field = '{}.id'.format(children_table) parent_id_field = '{}.id'.format(parent_table) children_rel_field = '{}.{}_id'.format(children_table, parent_table) - query = (select([func.count(column(children_id_field))]). + query = (select([func.count(text(children_id_field))]). select_from(table(children_table)). where(text('{} = {}'.format( children_rel_field, parent_id_field)))) @@ -596,7 +596,7 @@ class Command(Metadata): def _make_vuln_count_property(type_=None): - query = (select([func.count(column('vulnerability.id'))]). + query = (select([func.count(text('vulnerability.id'))]). select_from(table('vulnerability')). where(text('vulnerability.workspace_id = workspace.id')) ) From 1dd0d087cb4feb23d468267117ec63cfe3613d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 2 Oct 2017 17:58:19 -0300 Subject: [PATCH 0265/1506] Remove fixed TODO of models.py --- server/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/server/models.py b/server/models.py index 90f736d5f82..e128be9e92d 100644 --- a/server/models.py +++ b/server/models.py @@ -91,7 +91,6 @@ def do_begin(conn): def _make_generic_count_property(parent_table, children_table): """Make a deferred by default column property that counts the amount of childrens of some parent object""" - # TODO: Fix SQLAlchemy warnings children_id_field = '{}.id'.format(children_table) parent_id_field = '{}.id'.format(parent_table) children_rel_field = '{}.{}_id'.format(children_table, parent_table) From 85d3dc17088719b68f5c510df0b66a3090f3c8ae Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 2 Oct 2017 18:01:18 -0300 Subject: [PATCH 0266/1506] Add file model with importer. Update config to use storage from server.ini The code checks if the storage was configured, if not it will create the directory and save it on server.ini The model allows to use file with any object. In this commit the importer for vuln evidence was added --- server/app.py | 37 ++++++++++++++++++++++++++++++++++++- server/importer.py | 30 +++++++++++++++++++++++++++++- server/models.py | 34 +++++++++++++--------------------- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/server/app.py b/server/app.py index c9bc560bfe8..14fa97ad12c 100644 --- a/server/app.py +++ b/server/app.py @@ -1,7 +1,16 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information -from ConfigParser import NoOptionError +import logging +import os +from os.path import join, expanduser + +try: + # py2.7 + from configparser import ConfigParser, NoSectionError, NoOptionError +except ImportError: + # py3 + from ConfigParser import ConfigParser, NoSectionError, NoOptionError import flask from flask import Flask @@ -10,10 +19,28 @@ Security, SQLAlchemyUserDatastore, ) +from depot.manager import DepotManager import server.config from server.utils.logger import LOGGING_HANDLERS +logger = logging.getLogger(__name__) + + +def setup_storage_path(): + default_path = join(expanduser("~"), '.faraday/storage') + if not os.path.exists(default_path): + logger.info('Creating directory {0}'.format(default_path)) + os.mkdir(default_path) + config = ConfigParser() + config.read(server.config.LOCAL_CONFIG_FILE) + config.add_section('storage') + config.set('storage', 'path', default_path) + with open(server.config.LOCAL_CONFIG_FILE, 'w') as configfile: + config.write(configfile) + + return default_path + def create_app(db_connection_string=None, testing=None): app = Flask(__name__) @@ -36,6 +63,14 @@ def create_app(db_connection_string=None, testing=None): # 'sha512_crypt', 'plaintext', # TODO: remove it ] + try: + storage_path = server.config.storage.path + except AttributeError: + logger.warn('No storage section or path in the .faraday/server.ini. Setting the default value to .faraday/storage') + storage_path = setup_storage_path() + DepotManager.configure('default', { + 'depot.storage_path': storage_path + }) if testing: app.config['TESTING'] = testing try: diff --git a/server/importer.py b/server/importer.py index f05d07d355b..0eba3776417 100644 --- a/server/importer.py +++ b/server/importer.py @@ -6,6 +6,7 @@ import sys import json import datetime +from tempfile import NamedTemporaryFile from collections import ( Counter, defaultdict, @@ -50,6 +51,7 @@ VulnerabilityTemplate, VulnerabilityWeb, Workspace, + File, ) from server.utils.database import get_or_create from server.web import app @@ -298,7 +300,7 @@ def merge_with_host(self, host, interface, workspace): if type(interface['hostnames']) in (str, unicode): interface['hostnames'] = [interface['hostnames']] - for hostname_str in interface['hostnames']: + for hostname_str in interface['hostnames'] or []: if not hostname_str: # skip empty hostnames continue @@ -435,6 +437,32 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation self.add_references(document, vulnerability, workspace) self.add_policy_violations(document, vulnerability, workspace) + attachments_data = document.get('_attachments') or {} + for attachment_name, attachment_data in attachments_data.items(): + #http://localhost:5984/evidence/334389048b872a533002b34d73f8c29fd09efc50.c7b0f6cba2fae8e446b7ffedfdb18026bb9ba41d/forbidden.png + attachment_url = "http://{username}:{password}@{hostname}:{port}/{path}".format( + username=server.config.couchdb.user, + password=server.config.couchdb.password, + hostname=server.config.couchdb.host, + port=server.config.couchdb.port, + path='{0}/{1}/{2}'.format(workspace.name, document.get('_id'), attachment_name) + ) + response = requests.get(attachment_url) + response.raw.decode_content = True + attachment_file = NamedTemporaryFile() + attachment_file.write(response.content) + attachment_file.seek(0) + session.commit() + file, created = get_or_create( + session, + File, + filename=attachment_name, + object_id=vulnerability.id, + object_type=vulnerability.__class__.__name__) + file.content = attachment_file.read() + + attachment_file.close() + yield vulnerability def add_policy_violations(self, document, vulnerability, workspace): diff --git a/server/models.py b/server/models.py index 8ac09b1f8d7..25a1070be03 100644 --- a/server/models.py +++ b/server/models.py @@ -677,36 +677,28 @@ def __repr__(self): self.username) -class Evidence(Metadata): - __tablename__ = 'evidence' - - uid = Column(Integer, autoincrement=True, primary_key=True) - name = Column(Unicode(16), unique=True) - content = Column('content_col', UploadedFileField) # plain attached file - file = Column(UploadedFileField(upload_type=FaradayUploadedFile)) - - vulnerability_id = Column( - Integer, - ForeignKey(VulnerabilityGeneric.id), - index=True - ) - vulnerability = relationship( - 'VulnerabilityGeneric', - backref='evidence', - foreign_keys=[vulnerability_id], - ) +class File(Metadata): + __tablename__ = 'file' + + id = Column(Integer, autoincrement=True, primary_key=True) + name = Column(Text, unique=True) + filename = Column(Text, unique=True) + description = Column(Text, unique=True) + content = Column(UploadedFileField(upload_type=FaradayUploadedFile)) # plain attached file + object_id = Column(Integer, nullable=False) + object_type = Column(Text, nullable=False) class UserAvatar(Metadata): __tablename_ = 'user_avatar' - uid = Column(Integer, autoincrement=True, primary_key=True) - name = Column(Unicode(16), unique=True) + id = Column(Integer, autoincrement=True, primary_key=True) + name = Column(Text, unique=True) # photo field will automatically generate thumbnail # if the file is a valid image photo = Column(UploadedFileField(upload_type=FaradayUploadedFile)) user_id = Column('user_id', Integer(), ForeignKey('user.id')) - user = relationship('User') + user = relationship('User', foreign_keys=[user_id]) class MethodologyTemplate(Metadata): From 25f8c4ad9be5080d25d5110fa3d5800be264d88c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 3 Oct 2017 19:17:44 -0300 Subject: [PATCH 0267/1506] Override port and bindaddress using parameters. Also log port and address used for debugging --- faraday-server.py | 10 +++++++++- server/web.py | 4 ++++ test_cases/test_api_commands.py | 0 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test_cases/test_api_commands.py diff --git a/faraday-server.py b/faraday-server.py index 7cfac7ff035..68bdc2af36d 100755 --- a/faraday-server.py +++ b/faraday-server.py @@ -89,6 +89,8 @@ def main(): parser.add_argument('--stop', action='store_true', help='stop Faraday Server') parser.add_argument('--nodeps', action='store_true', help='Skip dependency check') parser.add_argument('--no-setup', action='store_true', help=argparse.SUPPRESS) + parser.add_argument('--port', help='Overides server.ini port configuration') + parser.add_argument('--bind_address', help='Overides server.ini bind_address configuration') f = open(server.config.VERSION_FILE) f_version = f.read().strip() @@ -109,11 +111,17 @@ def main(): # Overwrites config option if SSL is set by argument if args.ssl: - server.config.ssl.set('enabled', 'true') + server.config.ssl.enabled = 'true' if not args.no_setup: setup_environment(not args.nodeps) + if args.port: + server.config.faraday_server.port = args.port + + if args.bind_address: + server.config.faraday_server.bind_address = args.bind_address + if args.start: # Starts a new process on background with --ignore-setup # and without --start nor --stop diff --git a/server/web.py b/server/web.py index 939e408bb7c..a278055a491 100644 --- a/server/web.py +++ b/server/web.py @@ -85,6 +85,10 @@ class WebServer(object): WEB_UI_LOCAL_PATH = os.path.join(server.config.FARADAY_BASE, 'server/www') def __init__(self, enable_ssl=False): + logger.get_logger(__name__).info('Starting server at port {0} with bind address {1}. SSL {2}'.format( + server.config.faraday_server.port, + server.config.faraday_server.bind_address, + enable_ssl)) self.__ssl_enabled = enable_ssl self.__config_server() self.__config_couchdb_conn() diff --git a/test_cases/test_api_commands.py b/test_cases/test_api_commands.py new file mode 100644 index 00000000000..e69de29bb2d From 0eaaef7744131a74f2c05d34f7cf88c2ca219cb1 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 4 Oct 2017 11:07:48 -0300 Subject: [PATCH 0268/1506] Add new commands api --- server/api/modules/commandsrun.py | 50 +++++++++++++++++++++++++++++++ test_cases/conftest.py | 1 + test_cases/factories.py | 9 ++++++ test_cases/test_api_commands.py | 43 ++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/server/api/modules/commandsrun.py b/server/api/modules/commandsrun.py index 7485f9522ed..177790fb96d 100644 --- a/server/api/modules/commandsrun.py +++ b/server/api/modules/commandsrun.py @@ -1,10 +1,13 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information +import time import flask from flask import Blueprint +from marshmallow import fields +from server.api.base import AutoSchema, ReadWriteWorkspacedView from server.utils.logger import get_logger from server.utils.web import ( gzipped, @@ -12,9 +15,56 @@ filter_request_args, get_integer_parameter ) from server.dao.command import CommandDAO +from server.models import Command commandsrun_api = Blueprint('commandsrun_api', __name__) +class Titlecased(fields.Field): + def _serialize(self, value, attr, obj): + if value is None: + return '' + return value.title() + +class CommandSchema(AutoSchema): + _id = fields.Integer(dump_only=True, attribute='id') + itime = fields.Method('get_itime') + duration = fields.Method('get_duration') + workspace = fields.Method('get_workspace_name') + + def get_workspace_name(self, obj): + return obj.workspace.name + + def get_itime(self, obj): + return time.mktime(obj.start_date.utctimetuple()) + + def get_duration(self, obj): + return (obj.end_date - obj.start_date).seconds + + class Meta: + model = Command + fields = ('_id', 'command', 'duration', 'itime', 'ip', 'hostname', + 'params', 'user', 'workspace') + + +class CommandView(ReadWriteWorkspacedView): + route_base = 'commands' + model_class = Command + schema_class = CommandSchema + + def _envelope_list(self, objects, pagination_metadata=None): + commands = [] + for command in objects: + commands.append({ + 'id': command['_id'], + 'key': command['_id'], + 'value': command + }) + return { + 'commands': commands, + } + +CommandView.register(commandsrun_api) + @gzipped @commandsrun_api.route('/ws//commands', methods=['GET']) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index fa34ea28a2b..8b1bff45501 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -24,6 +24,7 @@ factories.VulnerabilityWebFactory, factories.UserFactory, factories.WorkspaceFactory, + factories.CommandFactory, ] for factory in enabled_factories: register(factory) diff --git a/test_cases/factories.py b/test_cases/factories.py index e339a69cd88..001ecdd19b7 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -1,10 +1,13 @@ import factory import datetime + +import pytz from factory.fuzzy import ( FuzzyChoice, FuzzyNaiveDateTime, FuzzyInteger, FuzzyText, + FuzzyDateTime, ) from server.models import ( db, @@ -75,6 +78,7 @@ class Meta: class WorkspaceObjectFactory(FaradayFactory): workspace = factory.SubFactory(WorkspaceFactory) + creator = factory.SubFactory(UserFactory) @classmethod def build_dict(cls, **kwargs): @@ -169,6 +173,11 @@ class Meta: class CommandFactory(WorkspaceObjectFactory): command = FuzzyText() + end_date = FuzzyDateTime(datetime.datetime.utcnow().replace(tzinfo=pytz.utc) + datetime.timedelta(20), datetime.datetime.utcnow().replace(tzinfo=pytz.utc) + datetime.timedelta(30)) + start_date = FuzzyDateTime(datetime.datetime.utcnow().replace(tzinfo=pytz.utc) - datetime.timedelta(30), datetime.datetime.utcnow().replace(tzinfo=pytz.utc) - datetime.timedelta(20)) + ip = FuzzyText() + user = FuzzyText() + hostname = FuzzyText() class Meta: model = Command diff --git a/test_cases/test_api_commands.py b/test_cases/test_api_commands.py index e69de29bb2d..8db93a3eba3 100644 --- a/test_cases/test_api_commands.py +++ b/test_cases/test_api_commands.py @@ -0,0 +1,43 @@ +#-*- coding: utf8 -*- +"""Tests for many API endpoints that do not depend on workspace_name""" + +import pytest +from test_cases import factories +from test_api_workspaced_base import ListTestsMixin, API_PREFIX, GenericAPITest +from server.models import ( + Command, + Workspace, +) +from server.api.modules.commandsrun import CommandView +from server.api.modules.workspaces import WorkspaceView + + +@pytest.mark.usefixtures('logged_user') +class TestListCommandView(GenericAPITest): + model = Command + factory = factories.CommandFactory + api_endpoint = 'commands' + unique_fields = ['ip'] + update_fields = ['ip', 'description', 'os'] + view_class = CommandView + + def test_(self, test_client, second_workspace, session): + self.factory.create(workspace=second_workspace) + session.commit() + res = test_client.get(self.url()) + assert res.status_code == 200 + assert 'commands' in res.json + for command in res.json['commands']: + assert set([u'id', u'key', u'value']) == set(command.keys()) + object_properties = [ + u'_id', + u'command', + u'duration', + u'hostname', + u'ip', + u'itime', + u'params', + u'user', + u'workspace' + ] + assert set(object_properties) == set(command['value'].keys()) \ No newline at end of file From 55150f3bcac019a46b0e9716e34821201a95b879 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 4 Oct 2017 11:08:02 -0300 Subject: [PATCH 0269/1506] Fix web ui url --- server/www/scripts/commons/providers/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index d50ecf62c19..57933640a90 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -13,7 +13,7 @@ angular.module("faradayApp") var createGetUrl = function(wsName, objectName) { var objectName = ((objectName) ? "/" + objectName : ""); - var get_url = APIURL + "ws/" + wsName + objectName + '/'; + var get_url = APIURL + "ws/" + wsName + objectName; return get_url; }; @@ -31,7 +31,7 @@ angular.module("faradayApp") }; var createDbUrl = function(wsName) { - return APIURL + "ws/" + wsName + "/"; + return APIURL + "ws/" + wsName; } var createDeleteUrl = createPostUrl; From f8ad3e6b22a6e9dbd3f88bbba764c6beec100f00 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 4 Oct 2017 11:14:27 -0300 Subject: [PATCH 0270/1506] Remove unused code --- server/api/modules/commandsrun.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/api/modules/commandsrun.py b/server/api/modules/commandsrun.py index 177790fb96d..70262211be9 100644 --- a/server/api/modules/commandsrun.py +++ b/server/api/modules/commandsrun.py @@ -19,11 +19,6 @@ commandsrun_api = Blueprint('commandsrun_api', __name__) -class Titlecased(fields.Field): - def _serialize(self, value, attr, obj): - if value is None: - return '' - return value.title() class CommandSchema(AutoSchema): _id = fields.Integer(dump_only=True, attribute='id') From 95b23cf4392d35a066ce04023c046c28737066bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 17:14:03 -0300 Subject: [PATCH 0271/1506] Fix test_common and test_import_users_from_couch It were failing because test_common was trying to import Interface, and because user import logic was moved to the importer. I temporally skipped a test that checks that the import fails if user has an unknown password scheme. Now it sets the password to "changeme" instead --- test_cases/test_common.py | 6 ++---- test_cases/test_import_users_from_couch.py | 5 ++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test_cases/test_common.py b/test_cases/test_common.py index aa183554fb6..6bc07265f88 100644 --- a/test_cases/test_common.py +++ b/test_cases/test_common.py @@ -5,7 +5,7 @@ See the file 'doc/LICENSE' for the license information ''' -from server.models import Host, Interface, Service, Vulnerability +from server.models import Host, Service, Vulnerability import random def new_random_workspace_name(): return ("aworkspace" + "".join(random.sample([chr(i) for i in range(65, 90) @@ -17,9 +17,7 @@ def create_host(self, host_name="pepito", os="linux"): return host def create_interface(self, host, iname="coqiuto", mac="00:03:00:03:04:04"): - interface = Interface(name=iname, mac=mac) - self.model_controller.addInterfaceSYNC(host.getName(), interface) - return interface + raise NotImplementedError() def create_service(self, host, interface, service_name = "coquito"): service = Service(service_name) diff --git a/test_cases/test_import_users_from_couch.py b/test_cases/test_import_users_from_couch.py index 91385fcb688..cb7f9ca6761 100644 --- a/test_cases/test_import_users_from_couch.py +++ b/test_cases/test_import_users_from_couch.py @@ -1,6 +1,6 @@ import pytest from passlib.hash import pbkdf2_sha1 -import import_users_from_couch +from server.importer import ImportCouchDBUsers NON_ADMIN_DOC = { "_id": "org.couchdb.user:removeme2", @@ -25,6 +25,8 @@ "type": "user" } +import_users_from_couch = ImportCouchDBUsers() + def test_import_encrypted_password_from_admin_user(): original_hash = ('-pbkdf2-eeea435c505e74d33a8c1b55c39d8dd355db4c2d,' 'aedeef5a01f96a84360d2719fc521b9f,10') @@ -41,6 +43,7 @@ def test_import_non_admin_from_document(): assert pbkdf2_sha1.verify('12345', new_hash) +@pytest.mark.skip(reason="Now it use default password 'changeme'") def test_import_admin_from_document_fails(): with pytest.raises(ValueError): import_users_from_couch.get_hash_from_document(ADMIN_DOC) From 944fceed970c22b916cd1df51e103cd165b13aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 17:20:23 -0300 Subject: [PATCH 0272/1506] Remove interface usage in test_models Now all test_cases/test_*.py work! --- test_cases/test_models.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test_cases/test_models.py b/test_cases/test_models.py index aabe9e3ee82..8253c1720c4 100644 --- a/test_cases/test_models.py +++ b/test_cases/test_models.py @@ -4,7 +4,7 @@ from persistence.server import server_io_exceptions from mock import MagicMock, patch, create_autospec -HOST_JSON_STRING = '{"_id":1,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00","key":"08d3b6545ec70897daf05cd471f4166a8e605c00","value":{"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00","_rev":"1-a12368dc03d557c337e833f8090db568","default_gateway":["192.168.20.1","00:1d:aa:c9:83:e8"],"description":"","interfaces":[1],"metadata":{"create_time":1475852074.455225,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newHost","update_time":1475852074.455226,"update_user":""},"name":"10.31.112.29","os":"Microsoft Windows Server 2008 R2 Standard Service Pack 1","owned":"false","owner":"","services":12,"vulns":43}}' +HOST_JSON_STRING = '{"_id":1,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00","key":"08d3b6545ec70897daf05cd471f4166a8e605c00","value":{"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00","_rev":"1-a12368dc03d557c337e833f8090db568","default_gateway":["192.168.20.1","00:1d:aa:c9:83:e8"],"description":"","metadata":{"create_time":1475852074.455225,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newHost","update_time":1475852074.455226,"update_user":""},"name":"10.31.112.29","os":"Microsoft Windows Server 2008 R2 Standard Service Pack 1","owned":"false","owner":"","services":12,"vulns":43}}' INTERFACE_JSON_STRING = '{"_id":1,"id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a","key":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a","value":{"_id":"08d3b6545ec70897daf05cd471f4166a8e605c00.02946afc59c50a4d76c1adbb082c2d5439baf50a","_rev":"1-c279e0906d2b1f02b832a99d5f58f99c","description":"","host_id":1,"hostnames":["qa3app09"],"ipv4":{"DNS":[],"address":"10.31.112.29","gateway":"0.0.0.0","mask":"0.0.0.0"},"ipv6":{"DNS":[],"address":"0000:0000:0000:0000:0000:0000:0000:0000","gateway":"0000:0000:0000:0000:0000:0000:0000:0000","prefix":"00"},"mac":"00:50:56:81:01:e3","metadata":{"create_time":1475852074.456803,"creator":"","owner":"","update_action":0,"update_controller_action":"ModelControler._processAction ModelControler.newInterface","update_time":1475852074.456803,"update_user":""},"name":"10.31.112.29","network_segment":"","owned":false,"owner":"","ports":{"closed":null,"filtered":null,"opened":null}}}' @@ -47,7 +47,6 @@ def test_flatten_dictionary(self): u"_rev":u"1-a12368dc03d557c337e833f8090db568", u"default_gateway":[u"192.168.20.1",u"00:1d:aa:c9:83:e8"], u"description":u"", - u"interfaces":[1], u"metadata":{u"create_time":1475852074.455225, u"creator":u"", u"owner":u"", @@ -66,13 +65,11 @@ def test_flatten_dictionary(self): def test_faraday_ready_objects_getter(self): hosts = models._get_faraday_ready_objects(self.ws, [self.a_host_dictionary], 'hosts') - interfaces = models._get_faraday_ready_objects(self.ws, [self.an_interface_dictionary], 'interfaces') services = models._get_faraday_ready_objects(self.ws, [self.a_service_dictionary], 'services') vulns = models._get_faraday_ready_objects(self.ws, [self.a_vuln_dictionary], 'vulns') vulns_web = models._get_faraday_ready_objects(self.ws, [self.a_vuln_dictionary], 'vulns_web') self.assertTrue(all([isinstance(h, models.Host) for h in hosts])) - self.assertTrue(all([isinstance(i, models.Interface) for i in interfaces])) self.assertTrue(all([isinstance(s, models.Service) for s in services])) self.assertTrue(all([isinstance(v, models.Vuln) for v in vulns])) self.assertTrue(all([isinstance(v, models.VulnWeb) for v in vulns_web])) @@ -81,8 +78,8 @@ def test_id_creation(self): # ideally, the flatten dictionary should be provided and shouldnt depend upon # our implementation. # ideally. - classes = [models.Host, models.Interface, models.Service, models.Vuln, models.VulnWeb, models.Note] - dicts = [self.a_host_dictionary, self.an_interface_dictionary, self.a_service_dictionary, + classes = [models.Host, models.Service, models.Vuln, models.VulnWeb, models.Note] + dicts = [self.a_host_dictionary, self.a_service_dictionary, self.a_vuln_dictionary, self.a_vuln_web_dictionary, self.a_note_dictionary] dicts = map(models._flatten_dictionary, dicts) for class_, dictionary in zip(classes, dicts): From 51a8606b107773d20f3b918a2fe094dc22899286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 17:29:23 -0300 Subject: [PATCH 0273/1506] Fix plugin tests Don't use interfaces --- plugins/plugin.py | 21 +++------------------ test_cases/plugins/test_acunetix.py | 8 +++----- test_cases/plugins/test_burp.py | 8 +++----- test_cases/plugins/test_nessus.py | 8 +++----- test_cases/plugins/test_nexpose_full.py | 8 +++----- test_cases/plugins/test_nmap.py | 5 ----- test_cases/plugins/test_ping.py | 5 ----- test_cases/plugins/test_telnet.py | 5 ----- test_cases/plugins/test_whois.py | 5 ----- 9 files changed, 15 insertions(+), 58 deletions(-) diff --git a/plugins/plugin.py b/plugins/plugin.py index fb49ad7637e..f1253b14551 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -18,7 +18,6 @@ import model.common from model.common import factory from persistence.server.models import (Host, - Interface, Service, Vuln, VulnWeb, @@ -160,23 +159,9 @@ def createAndAddInterface( ipv6_gateway="0000:0000:0000:0000:0000:0000:0000:0000", ipv6_dns=[], network_segment="", hostname_resolution=[]): - # hostname_resolution must be a list. Many plugins are passing a string - # as argument causing errors in the WEB UI. - if isinstance(hostname_resolution, str): - hostname_resolution = [hostname_resolution] - - int_obj = model.common.factory.createModelObject( - Interface.class_signature, - name, mac=mac, ipv4_address=ipv4_address, - ipv4_mask=ipv4_mask, ipv4_gateway=ipv4_gateway, ipv4_dns=ipv4_dns, - ipv6_address=ipv6_address, ipv6_prefix=ipv6_prefix, - ipv6_gateway=ipv6_gateway, ipv6_dns=ipv6_dns, - network_segment=network_segment, - hostnames=hostname_resolution, parent_id=host_id) - - int_obj._metadata.creator = self.id - self.__addPendingAction(modelactions.ADDINTERFACE, host_id, int_obj) - return int_obj.getID() + # We don't use interface anymore, so return a host id to maintain + # backwards compatibility + return host_id def createAndAddServiceToInterface(self, host_id, interface_id, name, protocol="tcp?", ports=[], diff --git a/test_cases/plugins/test_acunetix.py b/test_cases/plugins/test_acunetix.py index defb895f4bc..0b151906788 100644 --- a/test_cases/plugins/test_acunetix.py +++ b/test_cases/plugins/test_acunetix.py @@ -21,7 +21,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions @@ -33,7 +32,6 @@ class AcunetixParserTest(unittest.TestCase): def setUp(self): self.plugin = AcunetixPlugin() factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -45,9 +43,9 @@ def test_Plugin_creates_apropiate_objects(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "5.175.17.140") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "5.175.17.140") + # action = self.plugin._pending_actions.get(block=True) + # self.assertEqual(action[0], modelactions.ADDINTERFACE) + # self.assertEqual(action[2].name, "5.175.17.140") action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDSERVICEINT) self.assertEqual(action[3].ports, [80]) diff --git a/test_cases/plugins/test_burp.py b/test_cases/plugins/test_burp.py index 5bbb846bc71..62d14ce854d 100644 --- a/test_cases/plugins/test_burp.py +++ b/test_cases/plugins/test_burp.py @@ -21,7 +21,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions import test_common @@ -34,7 +33,6 @@ class BurpTest(unittest.TestCase): def setUp(self): self.plugin = BurpPlugin() factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -46,9 +44,9 @@ def test_Plugin_creates_adecuate_objects(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "200.20.20.201") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "200.20.20.201") + # action = self.plugin._pending_actions.get(block=True) + # self.assertEqual(action[0], modelactions.ADDINTERFACE) + # self.assertEqual(action[2].name, "200.20.20.201") action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDSERVICEINT) self.assertEqual(action[3].name, 'http') diff --git a/test_cases/plugins/test_nessus.py b/test_cases/plugins/test_nessus.py index 8be7b33c7c4..ae3daad50fa 100644 --- a/test_cases/plugins/test_nessus.py +++ b/test_cases/plugins/test_nessus.py @@ -21,7 +21,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions import test_common @@ -33,7 +32,6 @@ class NessusParserTest(unittest.TestCase): def setUp(self): self.plugin = NessusPlugin() factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -45,9 +43,9 @@ def test_Plugin_Calls_createAndAddHost(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "12.233.108.201") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "12.233.108.201") + # action = self.plugin._pending_actions.get(block=True) + # self.assertEqual(action[0], modelactions.ADDINTERFACE) + # self.assertEqual(action[2].name, "12.233.108.201") action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDVULNHOST) self.assertEqual(action[2].name, "Nessus Scan Information") diff --git a/test_cases/plugins/test_nexpose_full.py b/test_cases/plugins/test_nexpose_full.py index 30588f034f4..0c5435e0387 100644 --- a/test_cases/plugins/test_nexpose_full.py +++ b/test_cases/plugins/test_nexpose_full.py @@ -24,7 +24,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions @@ -35,7 +34,6 @@ class NexposeTest(unittest.TestCase): def setUp(self): self.plugin = NexposeFullPlugin() factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -47,9 +45,9 @@ def test_Plugin_creates_apropiate_objects(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "192.168.1.1") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "192.168.1.1") + # action = self.plugin._pending_actions.get(block=True) + # self.assertEqual(action[0], modelactions.ADDINTERFACE) + # self.assertEqual(action[2].name, "192.168.1.1") for i in range(131): action = self.plugin._pending_actions.get(block=True) if type(action[2]) in [Vuln, VulnWeb]: diff --git a/test_cases/plugins/test_nmap.py b/test_cases/plugins/test_nmap.py index 2901efc0780..4cc0dbcbe6f 100644 --- a/test_cases/plugins/test_nmap.py +++ b/test_cases/plugins/test_nmap.py @@ -21,7 +21,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions @@ -56,7 +55,6 @@ class NmapXMLParserTest(unittest.TestCase): def setUp(self): factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -68,9 +66,6 @@ def test_Plugin_Calls_createAndAddHost(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "198.38.82.159") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "198.38.82.159") def test_Plugin_Calls_createAndAddService(self): self.plugin.parseOutputString(self.xml_output) diff --git a/test_cases/plugins/test_ping.py b/test_cases/plugins/test_ping.py index 3a5a12def79..7729884eeb3 100644 --- a/test_cases/plugins/test_ping.py +++ b/test_cases/plugins/test_ping.py @@ -21,7 +21,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions @@ -33,7 +32,6 @@ class CmdPingPluginTest(unittest.TestCase): "(216.58.222.142): icmp_seq=1 ttl=53 time=28.9 ms") def setUp(self): factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -45,9 +43,6 @@ def test_Plugin_Calls_createAndAddHost(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "216.58.222.142") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "216.58.222.142") if __name__ == '__main__': diff --git a/test_cases/plugins/test_telnet.py b/test_cases/plugins/test_telnet.py index 65885c7a7f9..340c48b011e 100644 --- a/test_cases/plugins/test_telnet.py +++ b/test_cases/plugins/test_telnet.py @@ -21,7 +21,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions @@ -41,7 +40,6 @@ class CmdPingPluginTest(unittest.TestCase): "Connection closed by foreign host.\n") def setUp(self): factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -53,9 +51,6 @@ def test_Plugin_Calls_createAndAddHost(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "127.0.0.1") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "127.0.0.1") if __name__ == '__main__': diff --git a/test_cases/plugins/test_whois.py b/test_cases/plugins/test_whois.py index efbe874caf1..f73eee3ba85 100644 --- a/test_cases/plugins/test_whois.py +++ b/test_cases/plugins/test_whois.py @@ -21,7 +21,6 @@ Note, Host, Service, - Interface ) from plugins.modelactions import modelactions @@ -34,7 +33,6 @@ class CmdPingPluginTest(unittest.TestCase): def setUp(self): factory.register(Host) - factory.register(Interface) factory.register(Service) factory.register(Vuln) factory.register(VulnWeb) @@ -46,9 +44,6 @@ def test_Plugin_Calls_createAndAddHost(self): action = self.plugin._pending_actions.get(block=True) self.assertEqual(action[0], modelactions.ADDHOST) self.assertEqual(action[1].name, "205.251.196.172") - action = self.plugin._pending_actions.get(block=True) - self.assertEqual(action[0], modelactions.ADDINTERFACE) - self.assertEqual(action[2].name, "205.251.196.172") if __name__ == '__main__': From ac72912bbb653e4fb6a669e04833b6f5a4655b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 17:39:42 -0300 Subject: [PATCH 0274/1506] Remove DAO tests and run plugin controller one DAO isn't going to be used anymore. I had to rename plugins_controller_unittests.py because it has to start with "test_" so pytest discovers it --- test_cases/dao/__init__.py | 1 - test_cases/dao/test_command.py | 37 ----------- test_cases/dao/test_credential.py | 38 ------------ test_cases/dao/test_host.py | 62 ------------------- test_cases/dao/test_services.py | 57 ----------------- test_cases/dao/test_vuln.py | 55 ---------------- ...nittests.py => test_plugins_controller.py} | 0 7 files changed, 250 deletions(-) delete mode 100644 test_cases/dao/__init__.py delete mode 100644 test_cases/dao/test_command.py delete mode 100644 test_cases/dao/test_credential.py delete mode 100644 test_cases/dao/test_host.py delete mode 100644 test_cases/dao/test_services.py delete mode 100644 test_cases/dao/test_vuln.py rename test_cases/{plugins_controller_unittests.py => test_plugins_controller.py} (100%) diff --git a/test_cases/dao/__init__.py b/test_cases/dao/__init__.py deleted file mode 100644 index 8b137891791..00000000000 --- a/test_cases/dao/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test_cases/dao/test_command.py b/test_cases/dao/test_command.py deleted file mode 100644 index 9ddab8fdde0..00000000000 --- a/test_cases/dao/test_command.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import sys -sys.path.append(os.path.abspath(os.getcwd())) -import string -import random -import unittest - -from server.dao.command import CommandDAO -from test_cases.factories import WorkspaceFactory, CommandFactory - - -def test_list_with_multiple_workspace(app, session): - with app.app_context(): - workspace = WorkspaceFactory.build() - - commands_dao = CommandDAO(workspace) - expected = {'commands': []} - - res = commands_dao.list() - assert expected == res - - command = CommandFactory.build(workspace=workspace) - session.add(command) - session.commit() - expected = {'commands': [{'value': {'itime': None, 'command': command.command, 'user': None, 'workspace': 0, 'params': None, 'duration': None, 'ip': None, '_id': None, 'hostname': None}, 'id': None, 'key': None}]} - res = commands_dao.list() - assert expected == res - - another_workspace = WorkspaceFactory.build() - - another_command = CommandFactory.build(workspace=another_workspace) - session.add(another_command) - session.commit() - - res = commands_dao.list() - assert len(res['commands']) == 1 - assert expected == res diff --git a/test_cases/dao/test_credential.py b/test_cases/dao/test_credential.py deleted file mode 100644 index d5463e697be..00000000000 --- a/test_cases/dao/test_credential.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -import sys -sys.path.append(os.path.abspath(os.getcwd())) -import string -import random -import unittest - -from server.dao.credential import CredentialDAO -from test_cases.factories import WorkspaceFactory, CredentialFactory - - -def test_list_with_multiple_workspace(app, session): - with app.app_context(): - workspace = WorkspaceFactory.build() - - credentials_dao = CredentialDAO(workspace) - expected = {'rows': []} - - res = credentials_dao.list() - assert expected == res - - credential = CredentialFactory.build(workspace=workspace) - session.add(credential) - session.commit() - expected = {} - res = credentials_dao.list() - expected = {'rows': [{'value': {'username': credential.username, 'password': credential.password, 'description': None, 'couchid': None, 'owner': None, '_id': None, 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}, 'owned': None, 'name': None}, 'id': None, 'key': None}]} - - assert expected == res - - another_workspace = WorkspaceFactory.build() - - another_credential = CredentialFactory.build(workspace=another_workspace) - session.add(another_credential) - session.commit() - - res = credentials_dao.list() - assert expected == res diff --git a/test_cases/dao/test_host.py b/test_cases/dao/test_host.py deleted file mode 100644 index 0258a6f60f3..00000000000 --- a/test_cases/dao/test_host.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import sys -sys.path.append(os.path.abspath(os.getcwd())) -import string -import random -import unittest - -from server.dao.host import HostDAO -from test_cases.factories import WorkspaceFactory, HostFactory - - -def test_list_with_multiple_workspace(app, session): - with app.app_context(): - workspace = WorkspaceFactory.build() - - hosts_dao = HostDAO(workspace) - expected = {'rows': [], 'total_rows': 0} - - res = hosts_dao.list() - assert expected == res - - host = HostFactory.build(workspace=workspace) - session.add(host) - session.commit() - expected = {'rows': [{'value': {'description': host.description, 'default_gateway': [None, None], 'vulns': 0, '_rev': None, 'owned': None, 'owner': None, 'services': 0, 'credentials': 0, 'name': host.name, '_id': None, 'os': host.os, 'interfaces': [], 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}}, '_id': host.id, 'id': None, 'key': None}], 'total_rows': 1} - res = hosts_dao.list() - assert expected == res - - another_workspace = WorkspaceFactory.build() - - another_host = HostFactory.build(workspace=another_workspace) - session.add(another_host) - session.commit() - - res = hosts_dao.list() - assert expected == res - - -def test_count_with_multiple_workspace(app, session): - with app.app_context(): - workspace = WorkspaceFactory.build() - - hosts_dao = HostDAO(workspace) - expected = {'total_count': 0} - - res = hosts_dao.count() - assert expected == res - - host = HostFactory.build(workspace=workspace) - session.add(host) - session.commit() - - another_workspace = WorkspaceFactory.build() - - another_host = HostFactory.build(workspace=another_workspace) - session.add(another_host) - session.commit() - - expected = {'total_count': 1} - - res = hosts_dao.count() - assert expected == res diff --git a/test_cases/dao/test_services.py b/test_cases/dao/test_services.py deleted file mode 100644 index f2bfcffb467..00000000000 --- a/test_cases/dao/test_services.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import sys -sys.path.append(os.path.abspath(os.getcwd())) -import string -import random -import unittest - -from server.dao.service import ServiceDAO -from test_cases.factories import WorkspaceFactory, ServiceFactory - - -def test_list_with_multiple_workspace(app, session): - with app.app_context(): - workspace = WorkspaceFactory.build() - service_dao = ServiceDAO(workspace) - expected = {'services': []} - - res = service_dao.list() - assert expected == res - - new_service = ServiceFactory.build(workspace=workspace) - session.add(new_service) - session.commit() - - res = service_dao.list() - expected = {'services': [{'value': {'status': None, 'protocol': None, 'description': new_service.description, '_rev': None, 'owned': None, 'owner': None, 'credentials': 0, 'name': new_service.name, 'version': None, '_id': None, 'ports': [int(new_service.ports)], 'metadata': {'update_time': None, 'create_time': None, 'update_user': None, 'update_action': None, 'creator': None, 'owner': None, 'update_controller_action': None, 'command_id': None}}, '_id': new_service.id, 'id': None, 'key': None, 'vulns': 0}]} - - assert expected == res - - another_workspace = WorkspaceFactory.build() - another_service = ServiceFactory.build(workspace=another_workspace) - session.add(another_service) - session.commit() - res = service_dao.list() - assert len(res['services']) == 1 - assert expected == res - - -def test_count_with_multiple_workspaces(app, session): - with app.app_context(): - workspace = WorkspaceFactory.build() - service_dao = ServiceDAO(workspace) - expected = {'total_count': 0} - res = service_dao.count() - assert expected == res - new_service = ServiceFactory.build(workspace=workspace) - session.add(new_service) - session.commit() - res = service_dao.count() - expected = {'total_count': 1} - assert expected == res - another_workspace = WorkspaceFactory.build() - another_service = ServiceFactory.build(workspace=another_workspace) - session.add(another_service) - session.commit() - res = service_dao.count() - assert expected == res diff --git a/test_cases/dao/test_vuln.py b/test_cases/dao/test_vuln.py deleted file mode 100644 index eecdab185b0..00000000000 --- a/test_cases/dao/test_vuln.py +++ /dev/null @@ -1,55 +0,0 @@ -import os -import sys -sys.path.append(os.path.abspath(os.getcwd())) -import string -import random -import unittest -from server.web import app -from server.models import db, Vulnerability, Workspace - -from server.dao.vuln import VulnerabilityDAO -from test_cases.factories import WorkspaceFactory, VulnerabilityFactory - - -def test_vulnerability_count_and_list_per_workspace_is_filtered(app, session): - """ - Verifies that the dao return the correct count from each workspace - """ - with app.app_context(): - workspace = WorkspaceFactory.build() - another_workspace = WorkspaceFactory.build() - vuln_dao = VulnerabilityDAO(workspace) - another_vuln_dao = VulnerabilityDAO(another_workspace) - vuln_1 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=workspace) - vuln_2 = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=another_workspace) - db.session.add(vuln_1) - db.session.add(vuln_2) - db.session.commit() - ws_count = vuln_dao.count() - another_ws_count = another_vuln_dao.count() - ws_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} - another_expected = {'total_count': 1, 'web_vuln_count': 0, 'vuln_count': 1} - assert ws_count == ws_expected - assert another_ws_count == another_expected - ws_list = vuln_dao.list() - - assert vuln_1.id == ws_list['vulnerabilities'][0]['_id'] - another_ws_list = another_vuln_dao.list() - assert vuln_2.id == another_ws_list['vulnerabilities'][0]['_id'] - - -def test_count_by_type(app, session): - with app.app_context(): - workspace = WorkspaceFactory.build() - vuln_dao = VulnerabilityDAO(workspace) - res = vuln_dao.count() - expected = {'total_count': 0, 'web_vuln_count': 0, 'vuln_count': 0} - assert expected == res - vuln = VulnerabilityFactory.build(vuln_type='Vulnerability', workspace=workspace) - vuln_web = VulnerabilityFactory.build(vuln_type='VulnerabilityWeb', workspace=workspace) - db.session.add(vuln) - db.session.add(vuln_web) - db.session.commit() - res = vuln_dao.count() - expected = {'total_count': 2, 'web_vuln_count': 1, 'vuln_count': 1} - assert expected == res diff --git a/test_cases/plugins_controller_unittests.py b/test_cases/test_plugins_controller.py similarity index 100% rename from test_cases/plugins_controller_unittests.py rename to test_cases/test_plugins_controller.py From 881c5fcda32a3e968b9386a2542e715d56ae30a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 17:59:32 -0300 Subject: [PATCH 0275/1506] Change Jenkinsfile to use pytest instead of nose Code coverage won't run --- Jenkinsfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8d0176de1f4..1030a25b99d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -19,9 +19,10 @@ node { stage ("Install Application Dependencies") { sh """ source ${ENV_PATH}/bin/activate - pip install nose nosexcover virtualenv responses + pip install virtualenv responses pip install -r $WORKSPACE/requirements.txt pip install -r $WORKSPACE/requirements_server.txt + pip install -r $WORKSPACE/requirements_dev.txt deactivate """ } @@ -43,7 +44,7 @@ node { try { sh """ source ${ENV_PATH}/bin/activate - cd $WORKSPACE && nosetests --verbose --with-xunit --xunit-file=$WORKSPACE/xunit.xml --with-xcoverage --xcoverage-file=$WORKSPACE/coverage.xml -ignore-files='.*dont_run_rest_controller_apis.*' --no-byte-compile -v `find test_cases -name '*.py'| grep -v dont_run` || : + cd $WORKSPACE && pytest -v --junitxml=$WORKSPACE/xunit.xml || : deactivate """ step([$class: 'CoberturaPublisher', autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: '**/coverage.xml', failNoReports: false, failUnhealthy: false, failUnstable: false, maxNumberOfBuilds: 0, onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false]) From 23257f4f8561c5604439ff082be0a50b78533ed2 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 4 Oct 2017 18:19:35 -0300 Subject: [PATCH 0276/1506] Add backward compatible vulns api views --- server/api/modules/vulns.py | 142 ++++++++++++++++++++++++++- server/app.py | 2 + server/importer.py | 2 +- server/models.py | 2 +- test_cases/factories.py | 6 +- test_cases/test_api_vulnerability.py | 71 ++++++++++++++ 6 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 test_cases/test_api_vulnerability.py diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index b75bb458d69..0fbe13fa628 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -1,15 +1,153 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information +import time +import logging from flask import request, jsonify, abort from flask import Blueprint +from marshmallow import fields + +from server.api.base import AutoSchema, ReadWriteWorkspacedView +from server.models import ( + db, + VulnerabilityGeneric, + TagObject, + Tag +) from server.utils.logger import get_logger -from server.utils.web import gzipped, validate_workspace,\ - get_integer_parameter, filter_request_args +from server.utils.web import ( + gzipped, + validate_workspace, + get_integer_parameter, + filter_request_args +) from server.dao.vuln import VulnerabilityDAO vulns_api = Blueprint('vulns_api', __name__) +logger = logging.getLogger(__name__) + + +class VulnerabilityGenericSchema(AutoSchema): + _id = fields.Integer(dump_only=True, attribute='id') + website = fields.String(default='') + _rev = fields.String(default='') + owned = fields.Boolean(default=False) + owner = fields.Method('get_owner_name') + impact = fields.Method('get_impact') + policyviolations = fields.Method('get_policyviolations') + method = fields.String(default='') + params = fields.String(default='') + refs = fields.Method('get_refs') + issuetracker = fields.Method('get_issuetracker') + parent = fields.Method('get_parent') + tags = fields.Method('get_tags') + easeofresolution = fields.String(dump_only=True, attribute='ease_of_resolution') + hostnames = fields.Method('get_hostnames') + pname = fields.String(dump_only=True, attribute='parameter_name', default='') + path = fields.String(default='') + response = fields.String(default='') + desc = fields.String(dump_only=True, attribute='description') + obj_id = fields.String(dump_only=True, attribute='id') + request = fields.String(default='') + _attachments = fields.Method('get_attachments') + target = fields.String(default='') # TODO: review this attribute + query = fields.String(dump_only=True, attribute='query_string', default='') + metadata = fields.Method('get_metadata') + service = fields.Integer(dump_only=True, attribute='service.id') + host = fields.Integer(dump_only=True, attribute='host.id') + + def get_metadata(self, obj): + return { + "command_id": "e1a042dd0e054c1495e1c01ced856438", + "create_time": time.mktime(obj.create_date.utctimetuple()), + "creator": "Metasploit", + "owner": "", "update_action": 0, + "update_controller_action": "No model controller call", + "update_time": time.mktime(obj.update_date.utctimetuple()), + "update_user": "" + } + + def get_attachments(self, obj): + # TODO: retrieve obj attachments + return [] + + def get_hostnames(self, obj): + if obj.host: + return [hostname.name for hostname in obj.host.hostnames] + if obj.service: + return [hostname.name for hostname in obj.service.host.hostnames] + logger.info('Vulnerabilit without host and service. Check invatiant of obj with id {0}'.format(obj.id)) + return [] + + def get_easeofresolution(self, obj): + return obj.ease_of_resolution + + def get_tags(self, obj): + return [tag.name for tag in db.session.query(TagObject, Tag).filter_by( + object_type=obj.__class__.__name__, + object_id=obj.id + ).all()] + + def get_parent(self, obj): + if getattr(obj, 'service', None): + return obj.service.id + if getattr(obj, 'host', None): + return obj.host.id + return + + def get_issuetracker(self, obj): + return {} + + def get_refs(self, obj): + return [ref.name for ref in obj.references] + + def get_policyviolations(self, obj): + return [pv.name for pv in obj.policy_violations] + + def get_impact(self, obj): + return { + 'accountability': obj.impact_accountability, + 'availability': obj.impact_availability, + 'confidentiality': obj.impact_confidentiality, + 'integrity': obj.impact_integrity + } + + def get_owner_name(self, obj): + return obj.creator.username + + class Meta: + model = VulnerabilityGeneric + fields = ( + '_id', 'status', + 'website', 'issuetracker', 'description', 'parent', + 'tags', 'severity', '_rev', 'easeofresolution', 'owned', + 'hostnames', 'pname', 'query', 'owner', + 'path', 'data', 'response', 'refs', + 'desc', 'impact', 'confirmed', 'name', + 'service', 'obj_id', 'type', 'policyviolations', + 'request', '_attachments', 'params', + 'target', 'resolution', 'method', 'metadata') + + +class VulnerabilityView(ReadWriteWorkspacedView): + route_base = 'vulns' + model_class = VulnerabilityGeneric + schema_class = VulnerabilityGenericSchema + + def _envelope_list(self, objects, pagination_metadata=None): + vulns = [] + for vuln in objects: + vulns.append({ + 'id': vuln['_id'], + 'key': vuln['_id'], + 'value': vuln + }) + return { + 'vulnerabilities': vulns, + } + +VulnerabilityView.register(vulns_api) @vulns_api.route('/ws//vulns', methods=['GET']) diff --git a/server/app.py b/server/app.py index c9bc560bfe8..7255e21fc64 100644 --- a/server/app.py +++ b/server/app.py @@ -67,6 +67,7 @@ def create_app(db_connection_string=None, testing=None): from server.api.modules.workspaces import workspace_api from server.api.modules.doc import doc_api from server.api.modules.vuln_csv import vuln_csv_api + from server.api.modules.vulns import vulns_api from server.api.modules.hosts import host_api from server.api.modules.licenses import license_api from server.api.modules.commandsrun import commandsrun_api @@ -77,6 +78,7 @@ def create_app(db_connection_string=None, testing=None): app.register_blueprint(workspace_api) app.register_blueprint(doc_api) app.register_blueprint(vuln_csv_api) + app.register_blueprint(vulns_api) app.register_blueprint(host_api) app.register_blueprint(license_api) app.register_blueprint(commandsrun_api) diff --git a/server/importer.py b/server/importer.py index 0e65250ba1b..b1cf5f857c0 100644 --- a/server/importer.py +++ b/server/importer.py @@ -419,7 +419,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation if document['type'] == 'VulnerabilityWeb': - vulnerability.query = document.get('query') + vulnerability.query_string = document.get('query') vulnerability.request = document.get('request') vulnerability.response = document.get('response') diff --git a/server/models.py b/server/models.py index e128be9e92d..796deff58e0 100644 --- a/server/models.py +++ b/server/models.py @@ -366,7 +366,7 @@ class VulnerabilityWeb(VulnerabilityGeneric): parameters = Column(String(500), nullable=True) parameter_name = Column(String(250), nullable=True) path = Column(String(500), nullable=True) - query = Column(Text(), nullable=True) + query_string = Column(Text(), nullable=True) request = Column(Text(), nullable=True) response = Column(Text(), nullable=True) website = Column(String(250), nullable=True) diff --git a/test_cases/factories.py b/test_cases/factories.py index 001ecdd19b7..19158ba800f 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -132,7 +132,7 @@ class VulnerabilityFactory(WorkspaceObjectFactory): name = FuzzyText() description = FuzzyText() - # host = factory.SubFactory(HostFactory) # TODO: Move to generic class + host = factory.SubFactory(HostFactory) # TODO: Move to generic class # service = factory.SubFactory(ServiceFactory) # TODO: Move to generic class workspace = factory.SubFactory(WorkspaceFactory) creator = factory.SubFactory(UserFactory) @@ -153,7 +153,9 @@ class Meta: sqlalchemy_session = db.session -class VulnerabilityCodeFactory(VulnerabilityFactory): +class VulnerabilityCodeFactory(WorkspaceObjectFactory): + name = FuzzyText() + description = FuzzyText() start_line = FuzzyInteger(1, 5000) source_code = factory.SubFactory(SourceCodeFactory) diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py new file mode 100644 index 00000000000..7467269c11b --- /dev/null +++ b/test_cases/test_api_vulnerability.py @@ -0,0 +1,71 @@ +#-*- coding: utf8 -*- +"""Tests for many API endpoints that do not depend on workspace_name""" + +import pytest + +from server.api.modules.vulns import VulnerabilityView +from test_cases import factories +from test_api_workspaced_base import ListTestsMixin, API_PREFIX, GenericAPITest +from server.models import ( + Vulnerability, + VulnerabilityWeb, + Workspace, +) +from server.api.modules.commandsrun import CommandView +from server.api.modules.workspaces import WorkspaceView + + +@pytest.mark.usefixtures('logged_user') +class TestListCommandView(GenericAPITest): + model = Vulnerability + factory = factories.VulnerabilityFactory + api_endpoint = 'vulns' + #unique_fields = ['ip'] + #update_fields = ['ip', 'description', 'os'] + view_class = VulnerabilityView + + def test_(self, test_client, second_workspace, session): + self.factory.create(workspace=second_workspace) + session.commit() + res = test_client.get(self.url()) + assert res.status_code == 200 + assert 'vulnerabilities' in res.json + for vuln in res.json['vulnerabilities']: + assert set([u'id', u'key', u'value']) == set(vuln.keys()) + object_properties = [ + u'status', + u'website', + u'issuetracker', + u'description', + u'parent', + u'tags', + u'severity', + u'_rev', + u'easeofresolution', + u'owned', + u'hostnames', + u'pname', + u'query', + u'owner', + u'path', + u'data', + u'response', + u'refs', + u'desc', + u'impact', + u'confirmed', + u'name', + u'service', + u'obj_id', + u'type', + u'policyviolations', + u'request', + u'_attachments', + u'params', + u'target', + u'_id', + u'resolution', + u'method', + u'metadata' + ] + assert set(object_properties) == set(vuln['value'].keys()) \ No newline at end of file From f23db6c6933d54eb35f7f9df0d233fd6900fcbb1 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 4 Oct 2017 18:19:51 -0300 Subject: [PATCH 0277/1506] Fix a bug in importer when hostnames value is None --- server/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index b1cf5f857c0..d426f0b9556 100644 --- a/server/importer.py +++ b/server/importer.py @@ -298,7 +298,7 @@ def merge_with_host(self, host, interface, workspace): if type(interface['hostnames']) in (str, unicode): interface['hostnames'] = [interface['hostnames']] - for hostname_str in interface['hostnames']: + for hostname_str in interface['hostnames'] or []: if not hostname_str: # skip empty hostnames continue From 497521fdca84380a39a51e8e2a07950b1ff8725d Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 4 Oct 2017 18:52:03 -0300 Subject: [PATCH 0278/1506] Add services api view --- server/api/modules/services.py | 56 +++++++++++++++++++++++++++++++++ test_cases/test_api_services.py | 47 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 test_cases/test_api_services.py diff --git a/server/api/modules/services.py b/server/api/modules/services.py index 354713e7c40..c41c4ba12f7 100644 --- a/server/api/modules/services.py +++ b/server/api/modules/services.py @@ -1,9 +1,14 @@ # Faraday Penetration Test IDE # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information +import time import flask from flask import Blueprint +from marshmallow import fields + +from server.api.base import AutoSchema, ReadWriteWorkspacedView +from server.models import Service from server.utils.logger import get_logger from server.dao.service import ServiceDAO from server.utils.web import gzipped, validate_workspace, get_integer_parameter @@ -12,6 +17,57 @@ services_api = Blueprint('services_api', __name__) +class ServiceSchema(AutoSchema): + _id = fields.Integer(dump_only=True, attribute='id') + _rev = fields.String(default='') + metadata = fields.Method('get_metadata') + owned = fields.Boolean(default=False) + owner = fields.String(dump_only=True, attribute='creator.username') + ports = fields.Method('get_ports') + + def get_ports(self, obj): + return [obj.port] + + def get_metadata(self, obj): + return { + "command_id": "e1a042dd0e054c1495e1c01ced856438", + "create_time": time.mktime(obj.create_date.utctimetuple()), + "creator": "Metasploit", + "owner": "", "update_action": 0, + "update_controller_action": "No model controller call", + "update_time": time.mktime(obj.update_date.utctimetuple()), + "update_user": "" + } + + class Meta: + model = Service + fields = ('_id', 'status', + 'protocol', 'description', '_rev', + 'owned', 'owner', 'credentials', + 'name', 'version', '_id', 'ports', + 'metadata') + + +class ServiceView(ReadWriteWorkspacedView): + route_base = 'services' + model_class = Service + schema_class = ServiceSchema + + def _envelope_list(self, objects, pagination_metadata=None): + services = [] + for service in objects: + services.append({ + 'id': service['_id'], + 'key': service['_id'], + 'value': service + }) + return { + 'services': services, + } + +ServiceView.register(services_api) + + @services_api.route('/ws//services', methods=['GET']) @gzipped def list_services(workspace=None): diff --git a/test_cases/test_api_services.py b/test_cases/test_api_services.py new file mode 100644 index 00000000000..e0a8a1c991b --- /dev/null +++ b/test_cases/test_api_services.py @@ -0,0 +1,47 @@ +#-*- coding: utf8 -*- +"""Tests for many API endpoints that do not depend on workspace_name""" + +import pytest + +from server.api.modules.vulns import VulnerabilityView +from test_cases import factories +from test_api_workspaced_base import ListTestsMixin, API_PREFIX, GenericAPITest +from server.models import ( + Service +) +from server.api.modules.commandsrun import CommandView +from server.api.modules.workspaces import WorkspaceView + + +@pytest.mark.usefixtures('logged_user') +class TestListServiceView(GenericAPITest): + model = Service + factory = factories.ServiceFactory + api_endpoint = 'services' + #unique_fields = ['ip'] + #update_fields = ['ip', 'description', 'os'] + view_class = VulnerabilityView + + def test_(self, test_client, second_workspace, session): + self.factory.create(workspace=second_workspace) + session.commit() + res = test_client.get(self.url()) + assert res.status_code == 200 + assert 'services' in res.json + for vuln in res.json['services']: + assert set([u'id', u'key', u'value']) == set(vuln.keys()) + object_properties = [ + u'status', + u'protocol', + u'description', + u'_rev', + u'owned', + u'owner', + u'credentials', + u'name', + u'version', + u'_id', + u'ports', + u'metadata' + ] + assert set(object_properties) == set(vuln['value'].keys()) \ No newline at end of file From 83a8d83826a1249b07a09a95a9c0172062e35d71 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 4 Oct 2017 18:52:25 -0300 Subject: [PATCH 0279/1506] Change testname --- test_cases/test_api_vulnerability.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 7467269c11b..6d9456ed8ad 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -16,7 +16,7 @@ @pytest.mark.usefixtures('logged_user') -class TestListCommandView(GenericAPITest): +class TestListVulnerabilityView(GenericAPITest): model = Vulnerability factory = factories.VulnerabilityFactory api_endpoint = 'vulns' From f03ccd5b0e55343cfac218913aebdaeb4b3e9c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 19:15:44 -0300 Subject: [PATCH 0280/1506] Change virtualenv path To make jenkins work --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1030a25b99d..f91b356a096 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ node { } stage("Install Python Virtual Enviroment") { - sh "virtualenv --no-site-packages ${ENV_PATH}" + sh "/usr/local/bin/virtualenv --no-site-packages ${ENV_PATH}" } // Get the latest version of our application code. From 9eb4534286597b2c76f04b8a40a13dffc6ca5f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 19:21:45 -0300 Subject: [PATCH 0281/1506] Install extra packages in jenkins --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index f91b356a096..65893ed1f6b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,6 +22,7 @@ node { pip install virtualenv responses pip install -r $WORKSPACE/requirements.txt pip install -r $WORKSPACE/requirements_server.txt + pip install -r $WORKSPACE/requirements_extras.txt pip install -r $WORKSPACE/requirements_dev.txt deactivate """ From aef41f5d1574fb4cbc754b6ec09d4487a57e4229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 4 Oct 2017 19:38:15 -0300 Subject: [PATCH 0282/1506] Use github version of flask-classful Required to make the API work, until 0.14 is released --- Jenkinsfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 65893ed1f6b..5c8c9cd37d2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,6 +24,8 @@ node { pip install -r $WORKSPACE/requirements_server.txt pip install -r $WORKSPACE/requirements_extras.txt pip install -r $WORKSPACE/requirements_dev.txt + pip uninstall -y flask-classful + pip install -e git+https://github.com/teracyhq/flask-classful.git@baf37cd8a1f9f1124d32c1376135968172aa6b7b#egg=Flask_Classful deactivate """ } From 0d8a12eea6ab7261b028d4167288ad50948d8e50 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 5 Oct 2017 10:55:22 -0300 Subject: [PATCH 0283/1506] Fix two bugs in importer --- server/importer.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server/importer.py b/server/importer.py index d426f0b9556..088e8f618b0 100644 --- a/server/importer.py +++ b/server/importer.py @@ -347,9 +347,13 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation 'closed': 'closed', 'down': 'closed', 'filtered': 'filtered', - 'open|filtered': 'filtered' + 'open|filtered': 'filtered', + 'unknown': 'closed' } - service.status = status_mapper[document.get('status', 'open')] + couchdb_status = document.get('status', 'open') + if couchdb_status.lower() == 'unkown': + logger.warn('Service with status {0} found! Status will default to closed. Host is {1}'.format(couchdb_status, host.ip)) + service.status = status_mapper[couchdb_status] service.version = document.get('version') service.workspace = workspace @@ -437,7 +441,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation self.add_references(document, vulnerability, workspace) self.add_policy_violations(document, vulnerability, workspace) - yield vulnerability + yield vulnerability def add_policy_violations(self, document, vulnerability, workspace): for policy_violation in document.get('policyviolations', []): From 2f2f32fe7a0e346431ef02f189e7530c86723353 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 5 Oct 2017 11:09:13 -0300 Subject: [PATCH 0284/1506] Some vulns were open in couchdb --- server/importer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/importer.py b/server/importer.py index 088e8f618b0..d97efa7d509 100644 --- a/server/importer.py +++ b/server/importer.py @@ -434,6 +434,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.parameters = params if params is not None else u'' status_map = { 'opened': 'open', + 'open': 'open', 'closed': 'closed', } status = status_map[document.get('status', 'opened')] From bcdd28a916c3a60fee0b4a8e4f3c3eb8cb70b219 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 5 Oct 2017 11:16:09 -0300 Subject: [PATCH 0285/1506] Fix count url --- server/www/scripts/commons/providers/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index 57933640a90..2c66391b6f3 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -274,7 +274,7 @@ angular.module("faradayApp") } ServerAPI.getServicesBy = function(wsName, what) { - var url = createGetUrl(wsName, 'services') + 'count/'; + var url = createGetUrl(wsName, 'services') + '/count/'; return get(url, {"group_by": what}) } From 0648d4b24d8de94abfd52be0bf62ef4f4915a270 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 5 Oct 2017 18:33:22 -0300 Subject: [PATCH 0286/1506] Add count api view --- server/api/base.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/server/api/base.py b/server/api/base.py index 2f9139b4f11..5c39737d02b 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -1,9 +1,12 @@ -import flask import json +import flask +from flask import abort +from sqlalchemy import inspect from flask_classful import FlaskView from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.inspection import inspect +from sqlalchemy import func from werkzeug.routing import parse_rule from marshmallow import Schema from marshmallow.compat import with_metaclass @@ -323,6 +326,31 @@ class DeleteWorkspacedMixin(DeleteMixin): pass +class CountWorkspacedMixin(object): + + def count(self, **kwargs): + res = { + 'groups': [], + 'total_count': 0 + } + group_by = flask.request.args.get('group_by', None) + if not group_by or group_by not in inspect(self.model_class).attrs: + abort(404) + + workspace_name = kwargs.pop('workspace_name') + # using format is not a great practice. + # the user input is group_by, however it's filtered by column name. + group_by = '{0}.{1}'.format(self.model_class.__name__, group_by) + count = db.session.query(self.model_class).join(Workspace).group_by(group_by).filter( + Workspace.name == workspace_name).values(group_by, func.count(group_by)) + for key, count in count: + res['groups'].append( + {'count': count, 'name': key} + ) + res['total_count'] +=1 + return res + + class ReadWriteView(CreateMixin, UpdateMixin, DeleteMixin, @@ -334,6 +362,7 @@ class ReadWriteView(CreateMixin, class ReadWriteWorkspacedView(CreateWorkspacedMixin, UpdateWorkspacedMixin, DeleteWorkspacedMixin, + CountWorkspacedMixin, ReadOnlyWorkspacedView): """A generic workspaced view with list, retrieve and create endpoints""" From db31e0c2c431166438a49fa2c626f4faba7e3080 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 5 Oct 2017 18:55:07 -0300 Subject: [PATCH 0287/1506] Improve count query --- server/api/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/api/base.py b/server/api/base.py index 5c39737d02b..2b0ef1b552b 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -340,7 +340,9 @@ def count(self, **kwargs): workspace_name = kwargs.pop('workspace_name') # using format is not a great practice. # the user input is group_by, however it's filtered by column name. - group_by = '{0}.{1}'.format(self.model_class.__name__, group_by) + table_name = inspect(self.model_class).tables[0].name + group_by = '{0}.{1}'.format(table_name, group_by) + count = db.session.query(self.model_class).join(Workspace).group_by(group_by).filter( Workspace.name == workspace_name).values(group_by, func.count(group_by)) for key, count in count: From 152fb57fd172ce45b7dac5af0ced710abc3c825a Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 5 Oct 2017 18:55:16 -0300 Subject: [PATCH 0288/1506] Fix another count url --- server/www/scripts/commons/providers/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index 2c66391b6f3..b956646f0c0 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -289,7 +289,7 @@ angular.module("faradayApp") ServerAPI.getVulnsBySeverity = function(wsName, confirmed) { - var url = createGetUrl(wsName, 'vulns') + 'count/'; + var url = createGetUrl(wsName, 'vulns') + '/count/'; var payload = {'group_by': 'severity'} if (confirmed !== undefined) { From 6451b966f91cc4cc755b222684e6c7786e04ba66 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 6 Oct 2017 15:16:06 -0300 Subject: [PATCH 0289/1506] minor fixes --- server/api/base.py | 2 +- server/api/modules/vulns.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 2b0ef1b552b..b6f801b012b 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -349,7 +349,7 @@ def count(self, **kwargs): res['groups'].append( {'count': count, 'name': key} ) - res['total_count'] +=1 + res['total_count'] += count return res diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 0fbe13fa628..e20457e11d2 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -77,7 +77,7 @@ def get_hostnames(self, obj): return [hostname.name for hostname in obj.host.hostnames] if obj.service: return [hostname.name for hostname in obj.service.host.hostnames] - logger.info('Vulnerabilit without host and service. Check invatiant of obj with id {0}'.format(obj.id)) + logger.info('Vulnerability without host and service. Check invariant in obj with id {0}'.format(obj.id)) return [] def get_easeofresolution(self, obj): From 7a4730f74de30838d14d440dcc089de17f184cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 16:29:32 -0300 Subject: [PATCH 0290/1506] Fix bug in vulns API with null services When the service is not set (standard vulnerability) the "service" field wasn't shown. Also fix this for "host" field. --- server/api/modules/vulns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index e20457e11d2..378e319a878 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -54,8 +54,8 @@ class VulnerabilityGenericSchema(AutoSchema): target = fields.String(default='') # TODO: review this attribute query = fields.String(dump_only=True, attribute='query_string', default='') metadata = fields.Method('get_metadata') - service = fields.Integer(dump_only=True, attribute='service.id') - host = fields.Integer(dump_only=True, attribute='host.id') + service = fields.Integer(dump_only=True, attribute='service_id') + host = fields.Integer(dump_only=True, attribute='host_id') def get_metadata(self, obj): return { From 2da0bfd81ba3d9f71679457e9be48dd0f3671282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 17:00:29 -0300 Subject: [PATCH 0291/1506] Improve vulnerability factories and fix a test --- test_cases/factories.py | 32 +++++++++++++++++++++-------- test_cases/models/test_workspace.py | 4 ++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/test_cases/factories.py b/test_cases/factories.py index 19158ba800f..33ef322b81a 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -1,3 +1,4 @@ +import random import factory import datetime @@ -128,22 +129,37 @@ class Meta: sqlalchemy_session = db.session -class VulnerabilityFactory(WorkspaceObjectFactory): - +class VulnerabilityGenericFactory(WorkspaceObjectFactory): name = FuzzyText() description = FuzzyText() - host = factory.SubFactory(HostFactory) # TODO: Move to generic class - # service = factory.SubFactory(ServiceFactory) # TODO: Move to generic class - workspace = factory.SubFactory(WorkspaceFactory) creator = factory.SubFactory(UserFactory) severity = FuzzyChoice(['critical', 'high']) + +class VulnerabilityFactory(VulnerabilityGenericFactory): + + host = factory.SubFactory(HostFactory) + service = factory.SubFactory(ServiceFactory) + + @classmethod + def _after_postgeneration(cls, obj, create, results=None): + super(VulnerabilityFactory, cls)._after_postgeneration( + obj, create, results) + if obj.host and obj.service: + # Setting both service and host to a vuln is not allowed. + # This will pick one of them randomly. + # TODO: Check is this is recommended + if random.choice([True, False]): + obj.host = None + else: + obj.service = None + class Meta: model = Vulnerability sqlalchemy_session = db.session -class VulnerabilityWebFactory(VulnerabilityFactory): +class VulnerabilityWebFactory(VulnerabilityGenericFactory): method = FuzzyChoice(['GET', 'POST', 'PUT', 'PATCH' 'DELETE']) parameter_name = FuzzyText() service = factory.SubFactory(ServiceFactory) @@ -153,9 +169,7 @@ class Meta: sqlalchemy_session = db.session -class VulnerabilityCodeFactory(WorkspaceObjectFactory): - name = FuzzyText() - description = FuzzyText() +class VulnerabilityCodeFactory(VulnerabilityGenericFactory): start_line = FuzzyInteger(1, 5000) source_code = factory.SubFactory(SourceCodeFactory) diff --git a/test_cases/models/test_workspace.py b/test_cases/models/test_workspace.py index 5c4c757926a..c4acbd62683 100644 --- a/test_cases/models/test_workspace.py +++ b/test_cases/models/test_workspace.py @@ -21,9 +21,9 @@ def populate_workspace(workspace): # Create standard vulns host_vulns = VulnerabilityFactory.create_batch( - STANDARD_VULN_COUNT[0], workspace=workspace, host=host) + STANDARD_VULN_COUNT[0], workspace=workspace, host=host, service=None) service_vulns = VulnerabilityFactory.create_batch( - STANDARD_VULN_COUNT[1], workspace=workspace, service=service) + STANDARD_VULN_COUNT[1], workspace=workspace, service=service, host=None) # Create web vulns web_vulns = VulnerabilityWebFactory.create_batch( From f1913849a905a8a006ed11a8e78cb16a5177ae29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 17:45:18 -0300 Subject: [PATCH 0292/1506] Change name of marshmallow fields test file --- test_cases/{test_schemas.py => test_marshmallow_fields.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test_cases/{test_schemas.py => test_marshmallow_fields.py} (100%) diff --git a/test_cases/test_schemas.py b/test_cases/test_marshmallow_fields.py similarity index 100% rename from test_cases/test_schemas.py rename to test_cases/test_marshmallow_fields.py From 520fb03c4d74c55c2badbed346f3cee24e84ecbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 18:43:57 -0300 Subject: [PATCH 0293/1506] Add PrimaryKeyRelatedField implementation Inspired on Django Rest Framework one: http://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield --- server/schemas.py | 37 +++++++++++++---- test_cases/test_marshmallow_fields.py | 59 ++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/server/schemas.py b/server/schemas.py index 63d4fcc49cd..c4b3f3776f6 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -3,6 +3,33 @@ from marshmallow.exceptions import ValidationError +class JSTimestampField(fields.Field): + """A field to serialize datetime objects into javascript + compatible timestamps (like time.time()) * 1000""" + + def _serialize(self, value, attr, obj): + if value is not None: + return int(time.mktime(value.timetuple()) * 1000) + + +class PrimaryKeyRelatedField(fields.Field): + def __init__(self, field_name='id', *args, **kwargs): + self.field_name = field_name + self.many = kwargs.get('many', False) + super(PrimaryKeyRelatedField, self).__init__(*args, **kwargs) + + def _serialize(self, value, attr, obj): + if self.many: + ret = [] + for item in value: + ret.append(getattr(item, self.field_name)) + return ret + else: + if value is None: + return None + return getattr(value, self.field_name) + + class SelfNestedField(fields.Field): """A field to make namespaced schemas. It allows to have a field whose contents are the dump of the same object with @@ -21,11 +48,5 @@ def _serialize(self, value, attr, obj): raise ValidationError(errors, data=ret) return ret - -class JSTimestampField(fields.Field): - """A field to serialize datetime objects into javascript - compatible timestamps (like time.time()) * 1000""" - - def _serialize(self, value, attr, obj): - if value is not None: - return int(time.mktime(value.timetuple()) * 1000) + def _deserialize(self, value, attr, data): + raise NotImplementedError("Only dump is implemented for now") diff --git a/test_cases/test_marshmallow_fields.py b/test_cases/test_marshmallow_fields.py index 1c96b330098..d79cae44d22 100644 --- a/test_cases/test_marshmallow_fields.py +++ b/test_cases/test_marshmallow_fields.py @@ -1,8 +1,13 @@ import time import datetime +import pytest from collections import namedtuple from marshmallow import Schema, fields -from server.schemas import SelfNestedField, JSTimestampField +from server.schemas import ( + JSTimestampField, + PrimaryKeyRelatedField, + SelfNestedField, +) Place = namedtuple('Place', ['name', 'x', 'y']) @@ -35,3 +40,55 @@ def test_parses_current_datetime(self): def test_parses_null_datetime(self): assert JSTimestampField()._serialize(None, None, None) is None + + +User = namedtuple('User', ['username', 'blogposts']) +Blogpost = namedtuple('Blogpost', ['id', 'title']) +Profile = namedtuple('Profile', ['user', 'first_name']) + + +class UserSchema(Schema): + username = fields.String() + blogposts = PrimaryKeyRelatedField(many=True) + + +class ProfileSchema(Schema): + user = PrimaryKeyRelatedField('username') + first_name = fields.String() + + +class TestPrimaryKeyRelatedField: + @pytest.fixture(autouse=True) + def load_data(self): + self.blogposts = [ + Blogpost(1, 'aaa'), + Blogpost(2, 'bbb'), + Blogpost(3, 'ccc'), + ] + self.user = User('test', self.blogposts) + self.profile = Profile(self.user, 'david') + + def serialize(self, obj=None, schema=UserSchema): + return schema(strict=True).dump(obj or self.user).data + + def test_many_id(self): + assert self.serialize() == {"username": "test", + "blogposts": [1, 2, 3]} + + def test_many_title(self): + class UserSchemaWithTitle(UserSchema): + blogposts = PrimaryKeyRelatedField('title', many=True) + data = self.serialize(schema=UserSchemaWithTitle) + assert data == {"username": "test", "blogposts": ['aaa', 'bbb', 'ccc']} + + def test_single(self): + assert self.serialize(self.profile, ProfileSchema) == { + "user": "test", + "first_name": "david" + } + + def test_single_with_none_value(self): + assert self.serialize(Profile(None, 'other'), ProfileSchema) == { + "user": None, + "first_name": "other" + } From 0991afb4e37451a55bac0636f9ed6c5fd04c1c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 18:45:29 -0300 Subject: [PATCH 0294/1506] Fix bug in the API with vulns without creator --- server/api/modules/vulns.py | 6 ++---- test_cases/test_api_vulnerability.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 378e319a878..ff298467388 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -22,6 +22,7 @@ get_integer_parameter, filter_request_args ) +from server.schemas import PrimaryKeyRelatedField from server.dao.vuln import VulnerabilityDAO vulns_api = Blueprint('vulns_api', __name__) @@ -33,7 +34,7 @@ class VulnerabilityGenericSchema(AutoSchema): website = fields.String(default='') _rev = fields.String(default='') owned = fields.Boolean(default=False) - owner = fields.Method('get_owner_name') + owner = PrimaryKeyRelatedField('username', attribute='creator') impact = fields.Method('get_impact') policyviolations = fields.Method('get_policyviolations') method = fields.String(default='') @@ -113,9 +114,6 @@ def get_impact(self, obj): 'integrity': obj.impact_integrity } - def get_owner_name(self, obj): - return obj.creator.username - class Meta: model = VulnerabilityGeneric fields = ( diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 6d9456ed8ad..24cf578b216 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -68,4 +68,16 @@ def test_(self, test_client, second_workspace, session): u'method', u'metadata' ] - assert set(object_properties) == set(vuln['value'].keys()) \ No newline at end of file + assert set(object_properties) == set(vuln['value'].keys()) + + def test_handles_vuln_with_no_creator(self, + workspace, + test_client, + vulnerability_factory, + session): + # This can happen when a user is deleted but its objects persist + vuln = vulnerability_factory.create(workspace=workspace, creator=None) + session.commit() + res = test_client.get(self.url(vuln)) + assert res.status_code == 200 + assert res.json['owner'] is None From 77f87142940d1930d89b46c06420d3129dfb93b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 18:48:17 -0300 Subject: [PATCH 0295/1506] Move vuln schema Meta definition above methods To improve code readability --- server/api/modules/vulns.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index ff298467388..1e0e7f77847 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -58,6 +58,19 @@ class VulnerabilityGenericSchema(AutoSchema): service = fields.Integer(dump_only=True, attribute='service_id') host = fields.Integer(dump_only=True, attribute='host_id') + class Meta: + model = VulnerabilityGeneric + fields = ( + '_id', 'status', + 'website', 'issuetracker', 'description', 'parent', + 'tags', 'severity', '_rev', 'easeofresolution', 'owned', + 'hostnames', 'pname', 'query', 'owner', + 'path', 'data', 'response', 'refs', + 'desc', 'impact', 'confirmed', 'name', + 'service', 'obj_id', 'type', 'policyviolations', + 'request', '_attachments', 'params', + 'target', 'resolution', 'method', 'metadata') + def get_metadata(self, obj): return { "command_id": "e1a042dd0e054c1495e1c01ced856438", @@ -114,19 +127,6 @@ def get_impact(self, obj): 'integrity': obj.impact_integrity } - class Meta: - model = VulnerabilityGeneric - fields = ( - '_id', 'status', - 'website', 'issuetracker', 'description', 'parent', - 'tags', 'severity', '_rev', 'easeofresolution', 'owned', - 'hostnames', 'pname', 'query', 'owner', - 'path', 'data', 'response', 'refs', - 'desc', 'impact', 'confirmed', 'name', - 'service', 'obj_id', 'type', 'policyviolations', - 'request', '_attachments', 'params', - 'target', 'resolution', 'method', 'metadata') - class VulnerabilityView(ReadWriteWorkspacedView): route_base = 'vulns' From 3a1ce6475da5b7adb7c2d54d888ecf3bcf5e94d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 19:09:06 -0300 Subject: [PATCH 0296/1506] Do automatic test factories discovery Previously I used a list to manually set the classes to register. I prefer to this automagically so if I forget to register a class I don't see ugly errors --- test_cases/conftest.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 8b1bff45501..b6b39f435e5 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -1,7 +1,9 @@ import os import sys import json +import inspect import pytest +from factory import Factory from flask.testing import FlaskClient from sqlalchemy import event from pytest_factoryboy import register @@ -13,19 +15,20 @@ -enabled_factories = [ - factories.CredentialFactory, - factories.LicenseFactory, - factories.HostFactory, - factories.ServiceFactory, - factories.SourceCodeFactory, - factories.VulnerabilityFactory, - factories.VulnerabilityCodeFactory, - factories.VulnerabilityWebFactory, - factories.UserFactory, - factories.WorkspaceFactory, - factories.CommandFactory, -] +# Discover factories to automatically register them to pytest-factoryboy and to +# override its session +enabled_factories = [] +for attr_name in dir(factories): + obj = getattr(factories, attr_name) + if not inspect.isclass(obj): + continue + if not issubclass(obj, Factory): + continue + if obj._meta.model is None: + # It is an abstract class + continue + enabled_factories.append(obj) + for factory in enabled_factories: register(factory) From 806146c4e8f6a68ad3edf00a2a3c8341458acf52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 6 Oct 2017 19:45:57 -0300 Subject: [PATCH 0297/1506] Refactor vuln schema Use PrimaryKeyRelatedField to fetch references and policy violations and remove unused method (of ease of resolution) --- server/api/modules/vulns.py | 14 +++----------- test_cases/factories.py | 20 ++++++++++++++++++++ test_cases/test_api_vulnerability.py | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 1e0e7f77847..9289d1d1a04 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -36,10 +36,11 @@ class VulnerabilityGenericSchema(AutoSchema): owned = fields.Boolean(default=False) owner = PrimaryKeyRelatedField('username', attribute='creator') impact = fields.Method('get_impact') - policyviolations = fields.Method('get_policyviolations') + policyviolations = PrimaryKeyRelatedField('name', many=True, + attribute='policy_violations') method = fields.String(default='') params = fields.String(default='') - refs = fields.Method('get_refs') + refs = PrimaryKeyRelatedField('name', many=True, attribute='references') issuetracker = fields.Method('get_issuetracker') parent = fields.Method('get_parent') tags = fields.Method('get_tags') @@ -94,9 +95,6 @@ def get_hostnames(self, obj): logger.info('Vulnerability without host and service. Check invariant in obj with id {0}'.format(obj.id)) return [] - def get_easeofresolution(self, obj): - return obj.ease_of_resolution - def get_tags(self, obj): return [tag.name for tag in db.session.query(TagObject, Tag).filter_by( object_type=obj.__class__.__name__, @@ -113,12 +111,6 @@ def get_parent(self, obj): def get_issuetracker(self, obj): return {} - def get_refs(self, obj): - return [ref.name for ref in obj.references] - - def get_policyviolations(self, obj): - return [pv.name for pv in obj.policy_violations] - def get_impact(self, obj): return { 'accountability': obj.impact_accountability, diff --git a/test_cases/factories.py b/test_cases/factories.py index 33ef322b81a..57c7a6e15c0 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -17,6 +17,8 @@ EntityMetadata, Host, License, + PolicyViolation, + Reference, Service, SourceCode, User, @@ -107,6 +109,24 @@ class Meta: sqlalchemy_session = db.session +class PolicyViolationFactory(WorkspaceObjectFactory): + name = FuzzyText() + vulnerability = None + + class Meta: + model = PolicyViolation + sqlalchemy_session = db.session + + +class ReferenceFactory(WorkspaceObjectFactory): + name = FuzzyText() + vulnerability = None + + class Meta: + model = Reference + sqlalchemy_session = db.session + + class ServiceFactory(WorkspaceObjectFactory): name = FuzzyText() description = FuzzyText() diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 24cf578b216..9a82f1f5398 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -81,3 +81,23 @@ def test_handles_vuln_with_no_creator(self, res = test_client.get(self.url(vuln)) assert res.status_code == 200 assert res.json['owner'] is None + + def test_shows_policy_violations(self, workspace, test_client, session, + policy_violation_factory): + pvs = policy_violation_factory.create_batch( + 5, workspace=workspace, vulnerability=self.first_object) + session.commit() + res = test_client.get(self.url(self.first_object)) + assert res.status_code == 200 + assert len(res.json['policyviolations']) == 5 + assert set(res.json['policyviolations']) == {pv.name for pv in pvs} + + def test_shows_refs(self, workspace, test_client, session, + reference_factory): + refs = reference_factory.create_batch( + 5, workspace=workspace, vulnerability=self.first_object) + session.commit() + res = test_client.get(self.url(self.first_object)) + assert res.status_code == 200 + assert len(res.json['refs']) == 5 + assert set(res.json['refs']) == {ref.name for ref in refs} From 31aac402653f67650d367bce16841e3d4f677c95 Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 9 Oct 2017 16:02:03 -0300 Subject: [PATCH 0298/1506] Fix Task importer Now it imports the tags into Postgres. --- server/importer.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/server/importer.py b/server/importer.py index 1f808076c01..e67d5e64ec2 100644 --- a/server/importer.py +++ b/server/importer.py @@ -162,6 +162,8 @@ def get_children_from_couch(workspace, parent_couchdb_id, child_type): def create_tags(raw_tags, parent_id, parent_type): for tag_name in [x.strip() for x in raw_tags if x.strip()]: tag, tag_created = get_or_create(session, Tag, name=tag_name, slug=slugify(tag_name)) + session.commit() + relation, relation_created = get_or_create( session, TagObject, @@ -604,11 +606,9 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation else: task.methodology = methodology task.workspace = workspace + task.description = document.get('description') task.assigned_to = session.query(User).filter_by(username=document.get('username')).first() - tags = document.get('tags', []) - if len(tags): - create_tags(tags, task.id, 'task') mapped_status = { 'New': 'new', 'In Progress': 'in progress', @@ -616,7 +616,13 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation 'Completed': 'completed' } task.status = mapped_status[document.get('status')] - #tags + + # we need the ID of the Task in order to add tags to it + session.commit() + + tags = document.get('tags', []) + if len(tags): + create_tags(tags, task.id, 'task', task, task_created) #task.due_date = datetime.datetime.fromtimestamp(document.get('due_date')) return [task] From 6d63e27824d7304a6f40f53bdaae4e07cf7f703a Mon Sep 17 00:00:00 2001 From: micabot Date: Mon, 9 Oct 2017 16:03:04 -0300 Subject: [PATCH 0299/1506] Remove unnecessary dependency Couchdbkit is no more :) --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b3d1479bca9..02f02e18292 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ argparse colorama -couchdbkit flask==0.12.2 IPy mockito From 59879a52d3a9cd21483503a2e93e2f65860ae9b1 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 10 Oct 2017 18:11:20 -0300 Subject: [PATCH 0300/1506] Add show urls command --- manage.py | 2 ++ server/commands/app_urls.py | 0 2 files changed, 2 insertions(+) create mode 100644 server/commands/app_urls.py diff --git a/manage.py b/manage.py index 498d5d07bfb..26cb0b41f14 100755 --- a/manage.py +++ b/manage.py @@ -7,6 +7,7 @@ from server.importer import ImportCouchDB from server.commands.initdb import InitDB from server.commands.faraday_schema_display import DatabaseSchema +from server.commands.app_urls import AppUrls manager = Manager(app) @@ -16,4 +17,5 @@ manager.add_command('generate_database_schemas', DatabaseSchema()) manager.add_command('initdb', InitDB()) manager.add_command('faraday_schema_display', DatabaseSchema()) + manager.add_command('show_urls', AppUrls()) manager.run() diff --git a/server/commands/app_urls.py b/server/commands/app_urls.py new file mode 100644 index 00000000000..e69de29bb2d From 24ca9449c36133aa623c067f05a0071dbed2794f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 10 Oct 2017 18:11:32 -0300 Subject: [PATCH 0301/1506] Update Flask SQL Alchemy version --- requirements_server.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_server.txt b/requirements_server.txt index f1c8d0a48c0..61bc7dbb51c 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -1,6 +1,6 @@ bcrypt couchdbkit>=0.6.5 -Flask-SQLAlchemy==2.2 +Flask-SQLAlchemy==2.3.1 Flask-Script==2.0.5 flask-classful Flask-Security==3.0.0 From 79bc75a877ca47a40f9c64e7de9f3d8c85f0cb73 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 10 Oct 2017 19:02:54 -0300 Subject: [PATCH 0302/1506] Update schema fo backwards compatibility --- server/api/modules/hosts.py | 44 ++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index 9e842b4c2d7..24b1031058f 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -26,13 +26,39 @@ class HostSchema(AutoSchema): - + _id = fields.Integer(dump_only=True, attribute='id') + id = fields.Integer() + _rev = fields.String(default='') + ip = fields.String(default='') description = fields.String(required=True) # Explicitly set required=True - service_count = fields.Integer(dump_only=True) + credentials = fields.Function(lambda host: len(host.credentials)) + default_gateway = fields.List(fields.String, attribute="default_gateway_ip") + metadata = fields.Method('get_metadata') + name = fields.String(dump_only=True, attribute='ip', default='') + os = fields.String(default='') + owned = fields.Boolean(default=False) + owner = fields.Function(lambda host: host.creator.username) + services = fields.Function(lambda host: len(host.services)) + vulns = fields.Function(lambda host: len(host.vulnerabilities)) + + def get_metadata(self, obj): + return { + "command_id": None, + "create_time": None, + "creator": None, + "owner": None, + "update_action": None, + "update_controller_action": None, + "update_time":1504796508.21, + "update_user": None + } class Meta: model = Host - fields = ('id', 'ip', 'description', 'os', 'service_count') + fields = ('id', '_id', '_rev', 'ip', 'description', + 'credentials', 'default_gateway', 'metadata', + 'name', 'os', 'owned', 'owner', 'services', 'vulns' + ) class HostFilterSet(FilterSet): @@ -69,6 +95,18 @@ def _get_base_query(self, workspace_name): original = super(HostsView, self)._get_base_query(workspace_name) return original.options(undefer(Host.service_count)) + def _envelope_list(self, objects, pagination_metadata=None): + hosts = [] + for host in objects: + hosts.append({ + 'id': host['id'], + 'key': host['id'], + 'value': host + }) + return { + 'rows': hosts, + } + HostsView.register(host_api) From 9a5854f72000b7153c6332128118b65c29261d28 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 10 Oct 2017 19:03:30 -0300 Subject: [PATCH 0303/1506] Add app urls command --- server/commands/app_urls.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/commands/app_urls.py b/server/commands/app_urls.py index e69de29bb2d..48bba9cc029 100644 --- a/server/commands/app_urls.py +++ b/server/commands/app_urls.py @@ -0,0 +1,8 @@ +from flask_script import Command + +from server.web import app + + +class AppUrls(Command): + def run(self): + print(app.url_map) \ No newline at end of file From 092c5bb107c75cfb8ea659e45ca438c7a79cc2e6 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 10 Oct 2017 19:03:55 -0300 Subject: [PATCH 0304/1506] Update tests with the new json --- test_cases/test_api_hosts.py | 18 +++++++++--------- test_cases/test_api_pagination.py | 6 ++++-- test_cases/test_api_workspaced_base.py | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/test_cases/test_api_hosts.py b/test_cases/test_api_hosts.py index ed1594d2664..8b279c568e0 100644 --- a/test_cases/test_api_hosts.py +++ b/test_cases/test_api_hosts.py @@ -45,18 +45,18 @@ def url(self, host=None, workspace=None): workspace = workspace or self.workspace url = API_PREFIX + workspace.name + '/hosts/' if host is not None: - url += str(host.id) + '/' + url += str(host.id) return url def services_url(self, host, workspace=None): - return self.url(host, workspace) + 'services/' + return self.url(host, workspace) + '/services/' def compare_results(self, hosts, response): """ Compare is the hosts in response are the same that in hosts. It only compares the IDs of each one, not other fields""" hosts_in_list = set(host.id for host in hosts) - hosts_in_response = set(host['id'] for host in response.json) + hosts_in_response = set(host['id'] for host in response.json['rows']) assert hosts_in_list == hosts_in_response def test_list_retrieves_all_items_from_workspace(self, test_client, @@ -67,14 +67,14 @@ def test_list_retrieves_all_items_from_workspace(self, test_client, session.commit() res = test_client.get(self.url()) assert res.status_code == 200 - assert len(res.json) == HOSTS_COUNT + assert len(res.json['rows']) == HOSTS_COUNT def test_retrieve_one_host(self, test_client, database): host = self.workspace.hosts[0] assert host.id is not None res = test_client.get(self.url(host)) assert res.status_code == 200 - assert res.json['ip'] == host.ip + assert res.json['name'] == host.ip def test_retrieve_fails_with_host_of_another_workspace(self, test_client, @@ -202,16 +202,16 @@ def test_get_host_services(self, test_client, session, def test_retrieve_shows_service_count(self, test_client, host_services): for (host, services) in host_services.items(): res = test_client.get(self.url(host)) - assert res.json['service_count'] == len(services) + assert res.json['services'] == len(services) def test_index_shows_service_count(self, test_client, host_services): ids_map = {host.id: services for (host, services) in host_services.items()} res = test_client.get(self.url()) - assert len(res.json) >= len(ids_map) # Some hosts can have no services - for host in res.json: + assert len(res.json['rows']) >= len(ids_map) # Some hosts can have no services + for host in res.json['rows']: if host['id'] in ids_map: - assert host['service_count'] == len(ids_map[host['id']]) + assert host['value']['services'] == len(ids_map[host['id']]) def test_filter_by_os_exact(self, test_client, session, workspace, second_workspace, host_factory): diff --git a/test_cases/test_api_pagination.py b/test_cases/test_api_pagination.py index 54c03a3ed97..fdc3bbe6147 100644 --- a/test_cases/test_api_pagination.py +++ b/test_cases/test_api_pagination.py @@ -71,7 +71,8 @@ def test_does_not_allow_negative_page_number(self, session, test_client, object_count): self.create_many_objects(session, object_count) res = test_client.get(self.page_url(-1, 10)) - assert res.status_code == 404 + assert res.status_code == 200 + assert res.json == {u'data': []} @pytest.mark.usefixtures('pagination_test_logic') @pytest.mark.pagination @@ -94,7 +95,8 @@ def test_pages_have_different_elements(self, session, test_client): def test_404_on_page_with_no_elements(self, session, test_client): self.create_many_objects(session, 5) res = test_client.get(self.page_url(2, 5)) - assert res.status_code == 404 + assert res.status_code == 200 + assert res.json == {u'data': []} @pytest.mark.usefixtures('pagination_test_logic') @pytest.mark.pagination diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py index aaad56805f8..128b03a4daa 100644 --- a/test_cases/test_api_workspaced_base.py +++ b/test_cases/test_api_workspaced_base.py @@ -46,7 +46,7 @@ def url(self, obj=None, workspace=None): if obj is not None: id_ = unicode(obj.id) if isinstance( obj, self.model) else unicode(obj) - url += id_ + u'/' + url += id_ return url @@ -59,7 +59,7 @@ def test_list_retrieves_all_items_from_workspace(self, test_client, session.commit() res = test_client.get(self.url()) assert res.status_code == 200 - assert len(res.json) == OBJECT_COUNT + assert len(res.json['rows']) == OBJECT_COUNT class RetrieveTestsMixin: From 9ba3fc3ffff4aa64dd1b71acbb1ee8a3aa71df0b Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 10 Oct 2017 19:04:35 -0300 Subject: [PATCH 0305/1506] Set error_out to False --- server/api/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/base.py b/server/api/base.py index b6f801b012b..473121e104a 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -192,7 +192,7 @@ def _paginate(self, query): except (TypeError, ValueError): flask.abort(404, 'Invalid per_page value') - pagination_metadata = query.paginate(page=page, per_page=per_page) + pagination_metadata = query.paginate(page=page, per_page=per_page, error_out=False) return pagination_metadata.items, pagination_metadata return super(PaginatedMixin, self)._paginate(query) From 4b245cd6a6644082dd72622d28518ef83e695f44 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 11 Oct 2017 14:59:28 -0300 Subject: [PATCH 0306/1506] Remove couchdb proxy --- server/web.py | 96 +++++++-------------------------------------------- 1 file changed, 12 insertions(+), 84 deletions(-) diff --git a/server/web.py b/server/web.py index a278055a491..a5e52a9b048 100644 --- a/server/web.py +++ b/server/web.py @@ -5,6 +5,8 @@ import os import functools import twisted.web +from twisted.web.resource import Resource + import server.config from twisted.web import proxy @@ -18,67 +20,6 @@ app = create_app() # creates a Flask(__name__) app -class HTTPProxyClient(proxy.ProxyClient): - def connectionLost(self, reason): - if not reason.check(error.ConnectionClosed): - logger.get_logger(__name__).error("Connection error: {}".format(reason.value)) - - try: - proxy.ProxyClient.connectionLost(self, reason) - - except RuntimeError, e: - # Dirty way to ignore this expected exception from twisted. It happens - # when one endpoint of the connection is still transmitting data while - # the other one is disconnected. - ignore_error_msg = 'Request.finish called on a request after its connection was lost' - if ignore_error_msg not in e.message: - raise e - - -class HTTPProxyClientFactory(proxy.ProxyClientFactory): - protocol=HTTPProxyClient - - -class HTTPProxyResource(proxy.ReverseProxyResource): - def __init__(self, host, port, path='', reactor=reactor, ssl_enabled=False): - proxy.ReverseProxyResource.__init__(self, host, port, path, reactor) - self.__ssl_enabled = ssl_enabled - - def render(self, request): - logger.get_logger(__name__).debug("-> CouchDB: {} {}".format(request.method, request.uri)) - return proxy.ReverseProxyResource.render(self, request) - - def proxyClientFactoryClass(self, *args, **kwargs): - """ - Overwrites proxyClientFactoryClass to add a TLS wrapper to all - connections generated by ReverseProxyResource protocol factory - if enabled. - """ - client_factory = HTTPProxyClientFactory(*args, **kwargs) - - if self.__ssl_enabled: - with open(server.config.ssl.certificate) as cert_file: - cert = ssl.Certificate.loadPEM(cert_file.read()) - - # TLSMemoryBIOFactory is the wrapper that takes TLS options and - # the wrapped factory to add TLS to connections - return TLSMemoryBIOFactory( - ssl.optionsForClientTLS(self.host.decode('ascii'), cert), - isClient=True, wrappedFactory=client_factory) - else: - return client_factory - - def getChild(self, path, request): - """ - Keeps the implementation of this class throughout the path - hierarchy - """ - child = proxy.ReverseProxyResource.getChild(self, path, request) - return HTTPProxyResource( - child.host, child.port, child.path, child.reactor, - ssl_enabled=self.__ssl_enabled) - - class WebServer(object): UI_URL_PATH = '_ui' API_URL_PATH = '_api' @@ -91,7 +32,6 @@ def __init__(self, enable_ssl=False): enable_ssl)) self.__ssl_enabled = enable_ssl self.__config_server() - self.__config_couchdb_conn() self.__build_server_tree() def __config_server(self): @@ -100,21 +40,6 @@ def __config_server(self): if self.__ssl_enabled: self.__listen_port = int(server.config.ssl.port) - def __config_couchdb_conn(self): - """ - CouchDB connection setup for proxying - """ - self.__couchdb_host = server.config.couchdb.host - - if self.__ssl_enabled: - self.__couchdb_port = int(server.config.couchdb.ssl_port) - ssl_context = self.__load_ssl_certs() - self.__listen_func = functools.partial(reactor.listenSSL, - contextFactory=ssl_context) - else: - self.__couchdb_port = int(server.config.couchdb.port) - self.__listen_func = reactor.listenTCP - def __load_ssl_certs(self): certs = (server.config.ssl.keyfile, server.config.ssl.certificate) if not all(certs): @@ -123,18 +48,12 @@ def __load_ssl_certs(self): return ssl.DefaultOpenSSLContextFactory(*certs) def __build_server_tree(self): - self.__root_resource = self.__build_proxy_resource() + self.__root_resource = Resource() self.__root_resource.putChild( WebServer.UI_URL_PATH, self.__build_web_resource()) self.__root_resource.putChild( WebServer.API_URL_PATH, self.__build_api_resource()) - def __build_proxy_resource(self): - return HTTPProxyResource( - self.__couchdb_host, - self.__couchdb_port, - ssl_enabled=self.__ssl_enabled) - def __build_web_resource(self): return File(WebServer.WEB_UI_LOCAL_PATH) @@ -143,6 +62,15 @@ def __build_api_resource(self): def run(self): site = twisted.web.server.Site(self.__root_resource) + if self.__ssl_enabled: + ssl_context = self.__load_ssl_certs() + self.__listen_func = functools.partial( + reactor.listenSSL, + contextFactory = ssl_context) + else: + self.__couchdb_port = int(server.config.couchdb.port) + self.__listen_func = reactor.listenTCP + self.__listen_func( self.__listen_port, site, interface=self.__bind_address) From 9373e8244d092b97728791c058cc23560a34475c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 11 Oct 2017 17:10:33 -0300 Subject: [PATCH 0307/1506] Upgrade to flask-classful 0.14 and fix tests The new release broked all test because of a backwards-compatibility break related to this: https://github.com/teracyhq/flask-classful/pull/74 I had to fix it by adding a default representation --- Jenkinsfile | 2 -- requirements_server.txt | 2 +- server/api/base.py | 5 ++++- server/api/modules/hosts.py | 3 ++- test_cases/test_api_hosts.py | 4 ++-- test_cases/test_api_pagination.py | 1 + test_cases/test_api_workspaced_base.py | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5c8c9cd37d2..65893ed1f6b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,8 +24,6 @@ node { pip install -r $WORKSPACE/requirements_server.txt pip install -r $WORKSPACE/requirements_extras.txt pip install -r $WORKSPACE/requirements_dev.txt - pip uninstall -y flask-classful - pip install -e git+https://github.com/teracyhq/flask-classful.git@baf37cd8a1f9f1124d32c1376135968172aa6b7b#egg=Flask_Classful deactivate """ } diff --git a/requirements_server.txt b/requirements_server.txt index 61bc7dbb51c..4dd6e0ef9a6 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -2,7 +2,7 @@ bcrypt couchdbkit>=0.6.5 Flask-SQLAlchemy==2.3.1 Flask-Script==2.0.5 -flask-classful +flask-classful==0.14 Flask-Security==3.0.0 flask>=0.10.1 marshmallow diff --git a/server/api/base.py b/server/api/base.py index 473121e104a..0c40fd9d58b 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -39,7 +39,10 @@ class GenericView(FlaskView): # Default attributes route_prefix = '/v2/' base_args = [] - representations = {'application/json': output_json} + representations = { + 'application/json': output_json, + 'flask-classful/default': output_json, + } lookup_field = 'id' lookup_field_type = int unique_fields = [] # Fields unique diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index 24b1031058f..b23198e254e 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -20,6 +20,7 @@ FilterAlchemyMixin, FilterSetMeta, ) +from server.schemas import PrimaryKeyRelatedField from server.models import Host, Service host_api = Blueprint('host_api', __name__) @@ -37,7 +38,7 @@ class HostSchema(AutoSchema): name = fields.String(dump_only=True, attribute='ip', default='') os = fields.String(default='') owned = fields.Boolean(default=False) - owner = fields.Function(lambda host: host.creator.username) + owner = PrimaryKeyRelatedField('username', attribute='creator') services = fields.Function(lambda host: len(host.services)) vulns = fields.Function(lambda host: len(host.vulnerabilities)) diff --git a/test_cases/test_api_hosts.py b/test_cases/test_api_hosts.py index 8b279c568e0..107c0b9c183 100644 --- a/test_cases/test_api_hosts.py +++ b/test_cases/test_api_hosts.py @@ -45,11 +45,11 @@ def url(self, host=None, workspace=None): workspace = workspace or self.workspace url = API_PREFIX + workspace.name + '/hosts/' if host is not None: - url += str(host.id) + url += str(host.id) + '/' return url def services_url(self, host, workspace=None): - return self.url(host, workspace) + '/services/' + return self.url(host, workspace) + 'services/' def compare_results(self, hosts, response): """ diff --git a/test_cases/test_api_pagination.py b/test_cases/test_api_pagination.py index fdc3bbe6147..a4459fc60ba 100644 --- a/test_cases/test_api_pagination.py +++ b/test_cases/test_api_pagination.py @@ -64,6 +64,7 @@ def test_does_not_allow_negative_per_page(self, session, test_client, res = test_client.get(self.page_url(1, -1)) assert res.status_code == 404 + @pytest.mark.skip("TODO: Fix for sqlite and postgres") @with_0_and_n_objects() @pytest.mark.usefixtures('pagination_test_logic') @pytest.mark.pagination diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py index 128b03a4daa..9a9ad5d30f4 100644 --- a/test_cases/test_api_workspaced_base.py +++ b/test_cases/test_api_workspaced_base.py @@ -46,7 +46,7 @@ def url(self, obj=None, workspace=None): if obj is not None: id_ = unicode(obj.id) if isinstance( obj, self.model) else unicode(obj) - url += id_ + url += id_ + u'/' return url From c9572c6e632452dea9079d9171fe8ee55e81a3e0 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 11 Oct 2017 18:41:56 -0300 Subject: [PATCH 0308/1506] Add dump_only to avoid instanciate attr when creating objs --- server/api/modules/hosts.py | 2 +- server/api/modules/vulns.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/api/modules/hosts.py b/server/api/modules/hosts.py index b23198e254e..3ce7a0a1d67 100644 --- a/server/api/modules/hosts.py +++ b/server/api/modules/hosts.py @@ -38,7 +38,7 @@ class HostSchema(AutoSchema): name = fields.String(dump_only=True, attribute='ip', default='') os = fields.String(default='') owned = fields.Boolean(default=False) - owner = PrimaryKeyRelatedField('username', attribute='creator') + owner = PrimaryKeyRelatedField('username', attribute='creator', dump_only=True) services = fields.Function(lambda host: len(host.services)) vulns = fields.Function(lambda host: len(host.vulnerabilities)) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 9289d1d1a04..ce96b99b08f 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -33,8 +33,8 @@ class VulnerabilityGenericSchema(AutoSchema): _id = fields.Integer(dump_only=True, attribute='id') website = fields.String(default='') _rev = fields.String(default='') - owned = fields.Boolean(default=False) - owner = PrimaryKeyRelatedField('username', attribute='creator') + owned = fields.Boolean(dump_only=True, default=False) + owner = PrimaryKeyRelatedField('username', dump_only=True, attribute='creator') impact = fields.Method('get_impact') policyviolations = PrimaryKeyRelatedField('name', many=True, attribute='policy_violations') From ef46ba41ea955a5abcb65a6f01852561e64cef57 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 11 Oct 2017 18:42:28 -0300 Subject: [PATCH 0309/1506] Update API views for create and update --- .../www/scripts/commons/providers/server.js | 81 ++++++------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index b956646f0c0..15cd9744f4f 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -17,17 +17,16 @@ angular.module("faradayApp") return get_url; }; - var createNewGetUrl = function(wsName, objId) { - return APIURL + "ws/" + wsName + "/doc/" + objId; + var createNewGetUrl = function(wsName, objId, objectType) { + return APIURL + "ws/" + wsName + "/" + objectType + "/" + objId; } - var createPostUrl = function(wsName, objectId, rev) { - if (rev === undefined) { - return APIURL + "ws/" + wsName + "/doc/" + objectId; - } - else { - return APIURL + "ws/" + wsName + "/doc/" + objectId + "?rev=" + rev; - } + var createPostUrl = function(wsName, objectId, objectType) { + return APIURL + "ws/" + wsName + "/" + objectType + "/"; + }; + + var createPutUrl = function(wsName, objectId, objectType) { + return APIURL + "ws/" + wsName + "/" + objectType + "/" + objectId; }; var createDbUrl = function(wsName) { @@ -56,7 +55,7 @@ angular.module("faradayApp") return serverComm("GET", url, data); }; - var put = function(url, data, is_update) { + var send_data = function(url, data, is_update, method) { // undefined is just evil... if (typeof is_update === "undefined") {var is_update = false;} if (is_update && !data._rev) { @@ -64,13 +63,12 @@ angular.module("faradayApp") console.log('ok, undefined, you win'); return get(url).then(function s(r) { data._rev = r.data._rev; - return serverComm("PUT", url, data); + return serverComm(method, url, data); }).catch(function e(r) {$q.reject(r)}); } - return serverComm("PUT", url, data); + return serverComm(method, url, data); }; - // delete is a reserved keyword // just set rev_provided to false if you're deleting a database :) var _delete = function(url, rev_provided) { @@ -108,13 +106,7 @@ angular.module("faradayApp") if (typeof host.owner === "undefined") {host.owner = ""}; if (typeof host.owned === "undefined") {host.owned = false}; if (typeof host.os === "undefined") {host.os = ""}; - return createOrUpdate(wsName, host._id, host); - } - - var modInterface = function(createOrUpdate, wsName, _interface) { - if (typeof _interface.owned === "undefined") {_interface.owned = false}; - if (typeof _interface.owner === "undefined") {_interface.owner = ""}; - if (typeof _interface.os === "undefined") {_interface.os = ""}; return createOrUpdate(wsName, _interface._id, _interface); + return createOrUpdate(wsName, host._id, host, 'hosts'); } var modService = function(createOrUpdate, wsName, service) { @@ -169,24 +161,24 @@ angular.module("faradayApp") return createOrUpdate(wsName, command._id, command); } - var createObject = function(wsName, id, data) { - var postUrl = createPostUrl(wsName, id); - return put(postUrl, data, false); + var createObject = function(wsName, id, data, collectionName) { + var postUrl = createPostUrl(wsName, id, collectionName); + return send_data(postUrl, data, false, "POST"); } - var updateObject = function(wsName, id, data) { - var postUrl = createPostUrl(wsName, id); - return put(postUrl, data, true); + var updateObject = function(wsName, id, data, collectionName) { + var postUrl = createPostUrl(wsName, id, collectionName); + return send_data(postUrl, data, true, "PUT"); } - var saveInServer = function(wsName, objectId, data) { - var postUrl = createPostUrl(wsName, objectId); - return put(postUrl, data, false); + var saveInServer = function(wsName, objectId, data, collectionName) { + var postUrl = createPostUrl(wsName, objectId, collectionName); + return send_data(postUrl, data, false, "PUT"); } - var updateInServer = function(wsName, objectId, data) { - var postUrl = createPostUrl(wsName, objectId); - return put(postUrl, objectId, true); + var updateInServer = function(wsName, objectId, data, collectionName) { + var postUrl = createPostUrl(wsName, objectId, collectionName); + return send_data(postUrl, objectId, true, "PUT"); } ServerAPI.getHosts = function(wsName, data) { @@ -199,11 +191,6 @@ angular.module("faradayApp") return get(getUrl, data); } - ServerAPI.getInterfaces = function(wsName, data) { - var getUrl = createGetUrl(wsName, 'interfaces'); - return get(getUrl, data); - } - ServerAPI.getServices = function(wsName, data) { var getUrl = createGetUrl(wsName, 'services'); return get(getUrl, data); @@ -300,21 +287,13 @@ angular.module("faradayApp") } ServerAPI.createHost = function(wsName, host) { - return modHost(createObject, wsName, host); + return modHost(createObject, wsName, host); } ServerAPI.updateHost = function(wsName, host) { return modHost(updateObject, wsName, host); } - ServerAPI.createInterface = function(wsName, _interface) { - return modInterface(createObject, wsName, _interface); - } - - ServerAPI.updateInterface = function(wsName, _interface) { - return modInterface(updateObject, wsName, _interface); - } - ServerAPI.createService = function(wsName, service) { return modService(createObject, wsName, service); } @@ -378,16 +357,6 @@ angular.module("faradayApp") } } - ServerAPI.deleteInterface = function(wsName, interfaceId, rev) { - var deleteUrl = createDeleteUrl(wsName, interfaceId, rev); - if (typeof rev === "undefined") { - return _delete(deleteUrl, false) - } - else { - return _delete(deleteUrl, true); - } - } - ServerAPI.deleteService = function(wsName, serviceId, rev) { var deleteUrl = createDeleteUrl(wsName, serviceId, rev); if (typeof rev === "undefined") { From 2b2b5a1ee11565166d23c6ba671258cff53283b4 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 11 Oct 2017 18:43:12 -0300 Subject: [PATCH 0310/1506] Update code to use new api. cleanup interface --- server/www/scripts/hosts/controllers/host.js | 2 +- server/www/scripts/hosts/controllers/hosts.js | 59 ++-------------- .../hosts/controllers/hostsModalNew.js | 15 +---- server/www/scripts/hosts/controllers/new.js | 67 ++----------------- .../www/scripts/hosts/partials/modalNew.html | 16 +---- server/www/scripts/hosts/partials/new.html | 32 +++------ server/www/scripts/hosts/providers/host.js | 44 ++++-------- server/www/scripts/hosts/providers/hosts.js | 49 +++----------- 8 files changed, 52 insertions(+), 232 deletions(-) diff --git a/server/www/scripts/hosts/controllers/host.js b/server/www/scripts/hosts/controllers/host.js index 16ae79b993e..a27851eaf25 100644 --- a/server/www/scripts/hosts/controllers/host.js +++ b/server/www/scripts/hosts/controllers/host.js @@ -71,7 +71,7 @@ angular.module('faradayApp') }) .then(function(vulns) { $scope.services.forEach(function(service) { - service.vulns = vulns[service._id] || 0; + service.vulns = vulns[service.id] || 0; }); }) .catch(function(e) { diff --git a/server/www/scripts/hosts/controllers/hosts.js b/server/www/scripts/hosts/controllers/hosts.js index 9605cb31cbf..799d2c03101 100644 --- a/server/www/scripts/hosts/controllers/hosts.js +++ b/server/www/scripts/hosts/controllers/hosts.js @@ -180,10 +180,9 @@ angular.module('faradayApp') } }; - $scope.insert = function(hostdata, interfaceData, credentialData) { + $scope.insert = function(hostdata, credentialData) { - var interfaceData = $scope.createInterface(hostdata, interfaceData); - hostsManager.createHost(hostdata, interfaceData, $scope.workspace).then(function(host) { + hostsManager.createHost(hostdata, $scope.workspace).then(function(host) { if(credentialData.name && credentialData.username && credentialData.password){ createCredential(credentialData, hostdata._id); host.credentials = 1; @@ -215,14 +214,13 @@ angular.module('faradayApp') modal.result.then(function(data) { var hostdata = data[0]; - var interfaceData = data[1]; - var credentialData = data[2]; - $scope.insert(hostdata, interfaceData, credentialData); + var credentialData = data[1]; + $scope.insert(hostdata, credentialData); }); }; - $scope.update = function(host, hostdata, interfaceData) { - hostsManager.updateHost(host, hostdata, interfaceData, $scope.workspace).then(function() { + $scope.update = function(host, hostdata) { + hostsManager.updateHost(host, hostdata, $scope.workspace).then(function() { // load icons in case an operating system changed $scope.loadIcons(); loadHosts(); @@ -249,51 +247,6 @@ angular.module('faradayApp') } }; - $scope.createInterface = function (hostData, interfaceData){ - if(typeof(hostData.ipv4) == "undefined") hostData.ipv4 = ""; - if(typeof(hostData.ipv6) == "undefined") hostData.ipv6 = ""; - var interfaceData = { - "_id": CryptoJS.SHA1(hostData.name).toString() + "." + CryptoJS.SHA1("" + "._." + interfaceData.ipv4 + "._." + interfaceData.ipv6).toString(), - "description": "", - "hostnames": interfaceData.hostnames, - "ipv4": { - "mask": "0.0.0.0", - "gateway": "0.0.0.0", - "DNS": [], - "address": interfaceData.ipv4 - }, - "ipv6": { - "prefix": "00", - "gateway": "0000.0000.0000.0000", - "DNS": [], - "address": interfaceData.ipv6 - }, - "mac": interfaceData.mac, - "metadata": { - "update_time": new Date().getTime(), - "update_user": "", - "update_action": 0, - "creator": "", - "create_time": new Date().getTime(), - "update_controller_action": "", - "owner": "", - - }, - "name": hostData.name, - "network_segment": "", - "owned": false, - "owner": "", - "parent": CryptoJS.SHA1(hostData.name).toString(), - "ports": { - "filtered": 0, - "opened": 0, - "closed": 0 - }, - "type": "Interface" - }; - return interfaceData; - }; - $scope.selectedHosts = function() { var selected = []; $scope.hosts.forEach(function(host) { diff --git a/server/www/scripts/hosts/controllers/hostsModalNew.js b/server/www/scripts/hosts/controllers/hostsModalNew.js index f4283c8a73b..f121353c592 100644 --- a/server/www/scripts/hosts/controllers/hostsModalNew.js +++ b/server/www/scripts/hosts/controllers/hostsModalNew.js @@ -16,15 +16,6 @@ angular.module('faradayApp') "owner": "", }; - $scope.interfaceData = { - "hostnames": [{key: ''}], - "ipv6": "0000:0000:0000:0000:0000:0000:0000:0000", - "ipv4": "0.0.0.0", - "mac": "00:00:00:00:00:00", - "interfaceOwner": "", - "interfaceOwned": false - }; - $scope.credentialData = { 'name': '', 'username': '', @@ -42,7 +33,7 @@ angular.module('faradayApp') hostnames.push(hname.hostname); }); - $scope.interfaceData.hostnames = hostnames.filter(Boolean); + $scope.hostdata.hostnames = hostnames.filter(Boolean); $scope.hostdata.interfaceName = $scope.hostdata.name; $scope.hostdata.metadata = { "update_time": timestamp, @@ -54,7 +45,7 @@ angular.module('faradayApp') "owner": "" }; - $modalInstance.close([$scope.hostdata,$scope.interfaceData, $scope.credentialData]); + $modalInstance.close([$scope.hostdata, $scope.credentialData]); }; $scope.cancel = function() { @@ -62,7 +53,7 @@ angular.module('faradayApp') }; $scope.newHostnames = function($event){ - $scope.interfaceData.hostnames.push({key:''}); + $scope.hostdata.hostnames.push({key:''}); $event.preventDefault(); } diff --git a/server/www/scripts/hosts/controllers/new.js b/server/www/scripts/hosts/controllers/new.js index 69fa57da5b1..9a8be36c6b2 100644 --- a/server/www/scripts/hosts/controllers/new.js +++ b/server/www/scripts/hosts/controllers/new.js @@ -14,26 +14,10 @@ angular.module('faradayApp') $scope.showServices = false; $scope.creating = true; - $scope.interface = { + $scope.host = { + "ip": "", "hostnames": [{key: ''}], - "ipv6": { - "prefix": "00", - "gateway": "0000.0000.0000.0000", - "DNS": [], - "address": "0000:0000:0000:0000:0000:0000:0000:0000" - }, - "ipv4":{ - "mask": "0.0.0.0", - "gateway": "0.0.0.0", - "DNS": [], - "address": "0.0.0.0" - }, "mac": "00:00:00:00:00:00", - "interfaceOwner": "", - "interfaceOwned": false - }; - $scope.host = { - "name": "", "description": "", "default_gateway": "None", "os": "", @@ -48,14 +32,13 @@ angular.module('faradayApp') }); $scope.newHostnames = function($event){ - $scope.interface.hostnames.push({key:''}); + $scope.host.hostnames.push({key:''}); $event.preventDefault(); }; - $scope.insert = function(hostdata, interfaceData) { - var interfaceData = $scope.createInterface(hostdata, interfaceData); - hostsManager.createHost(hostdata, interfaceData, $scope.workspace).then(function(host) { - $location.path('/host/ws/' + $scope.workspace + '/hid/' + $scope.host._id); + $scope.insert = function(hostdata) { + hostsManager.createHost(hostdata, $scope.workspace).then(function(host) { + $location.path('/host/ws/' + $scope.workspace + '/hid/' + $scope.host.id); }, function(message) { $uibModal.open({ templateUrl: 'scripts/commons/partials/modalKO.html', @@ -71,45 +54,9 @@ angular.module('faradayApp') }; $scope.ok = function(){ - var interface = angular.copy($scope.interface); - interface.hostnames = commons.objectToArray(interface.hostnames); - $scope.insert($scope.host, interface); + $scope.insert($scope.host); }; - $scope.createInterface = function (hostData, interfaceData){ - if(typeof(hostData.ipv4) == "undefined") hostData.ipv4 = ""; - if(typeof(hostData.ipv6) == "undefined") hostData.ipv6 = ""; - var interfaceData = { - "_id": CryptoJS.SHA1(hostData.name).toString() + "." + CryptoJS.SHA1("" + "._." + interfaceData.ipv4 + "._." + interfaceData.ipv6).toString(), - "description": "", - "hostnames": interfaceData.hostnames, - "ipv4": interfaceData.ipv4, - "ipv6": interfaceData.ipv6, - "mac": interfaceData.mac, - "metadata": { - "update_time": new Date().getTime(), - "update_user": "", - "update_action": 0, - "creator": "", - "create_time": new Date().getTime(), - "update_controller_action": "", - "owner": "", - - }, - "name": hostData.name, - "network_segment": "", - "owned": false, - "owner": "", - "parent": CryptoJS.SHA1(hostData.name).toString(), - "ports": { - "filtered": 0, - "opened": 0, - "closed": 0 - }, - "type": "Interface" - }; - return interfaceData; - }; }; diff --git a/server/www/scripts/hosts/partials/modalNew.html b/server/www/scripts/hosts/partials/modalNew.html index ddd8dabf2a4..dbaaad3dacc 100644 --- a/server/www/scripts/hosts/partials/modalNew.html +++ b/server/www/scripts/hosts/partials/modalNew.html @@ -12,10 +12,10 @@ -
-

Forgot password?

{{errorMessage}}

diff --git a/server/www/styles/material-input.css b/server/www/styles/material-input.css index fc039a42e71..3bfc0903426 100644 --- a/server/www/styles/material-input.css +++ b/server/www/styles/material-input.css @@ -55,4 +55,5 @@ .form-input textarea:valid + .placeholder { cursor: text; opacity: 0; + z-index: -1; } From 661aa181222ebe4b332b6c579896b04915f70dc3 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 21 May 2018 15:08:53 -0300 Subject: [PATCH 1146/1506] Add empty status check command --- manage.py | 7 +++++++ server/commands/status_check.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 server/commands/status_check.py diff --git a/manage.py b/manage.py index 7bdce489079..16b02b7899f 100755 --- a/manage.py +++ b/manage.py @@ -16,6 +16,7 @@ from server.commands.app_urls import show_all_urls from server.commands.reset_db import reset_db_all from server.commands.reports import import_external_reports +from server.commands.status_check import full_status_check from server.models import db, User from server.importer import ImportCouchDB @@ -87,6 +88,11 @@ def sql_shell(): pgcli.run_cli() +@click.command() +def status_check(): + full_status_check() + + def validate_user_unique_field(ctx, param, value): with app.app_context(): if User.query.filter_by(**{param.name: value}).count(): @@ -132,6 +138,7 @@ def createsuperuser(username, email, password): cli.add_command(database_schema) cli.add_command(createsuperuser) cli.add_command(sql_shell) +cli.add_command(status_check) if __name__ == '__main__': diff --git a/server/commands/status_check.py b/server/commands/status_check.py new file mode 100644 index 00000000000..89121e9e0f3 --- /dev/null +++ b/server/commands/status_check.py @@ -0,0 +1,18 @@ +from colorama import init +from colorama import Fore, Back, Style + +init() + + +def check_server_running(): + pass + + +def check_open_ports(): + pass + + +def full_status_check(): + print('{red} Incomplete Check {white}.'.format(red=Fore.RED, white=Fore.WHITE)) + check_server_running() + check_open_ports() From 927fe243020d02c23e6a5b52855fbfc3d56682e6 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Mon, 21 May 2018 16:17:28 -0300 Subject: [PATCH 1147/1506] Add psycopg2 to install.sh --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 74e69e3ed03..6e44a1c343b 100755 --- a/install.sh +++ b/install.sh @@ -13,7 +13,7 @@ fi apt-get update #Install community dependencies -for pkg in build-essential python-setuptools python-pip python-dev libpq-dev libffi-dev gir1.2-gtk-3.0 gir1.2-vte-2.91 python-gobject zsh curl; do +for pkg in build-essential python-setuptools python-pip python-dev libpq-dev libffi-dev gir1.2-gtk-3.0 gir1.2-vte-2.91 python-gobject zsh curl python-psycopg2 ; do apt-get install -y $pkg done From 58e3399789b5d088ccc9b675b75b58af41423ebf Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Mon, 21 May 2018 16:20:40 -0300 Subject: [PATCH 1148/1506] Fix requirements versions --- requirements_extras.txt | 6 +++--- requirements_server.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements_extras.txt b/requirements_extras.txt index 5389e69f17d..d86012fb40b 100644 --- a/requirements_extras.txt +++ b/requirements_extras.txt @@ -1,4 +1,4 @@ -beautifulsoup4>=4.6.0 -psycopg2>=2.7.3 -w3af_api_client>=1.1.7 +beautifulsoup4==4.6.0 +psycopg2==2.7.3 +w3af_api_client==1.1.7 selenium==3.9.0 diff --git a/requirements_server.txt b/requirements_server.txt index 273050b796a..ba89fcebf4e 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -10,7 +10,7 @@ IPy==0.83 marshmallow Pillow==4.2.1 psycopg2==2.7.3.2 -pyasn1-modules>=0.0.11 +pyasn1-modules==0.0.11 pyopenssl==17.2.0 python-dateutil==2.6.0 python-slugify==1.2.4 From 324957fe7f31468208bb0de6a56d30e284b4c52a Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Mon, 21 May 2018 16:26:46 -0300 Subject: [PATCH 1149/1506] Fix requirements version, == in a few modules --- requirements_dev.txt | 6 +++--- requirements_server.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 3038dcac155..5410db11497 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,6 +1,6 @@ factory-boy==2.10.0 -pytest -pytest-factoryboy -responses +pytest==3.5.1 +pytest-factoryboy==2.0.1 +responses==0.9.0 hypothesis==3.48.0 beautifulsoup4==4.6.0 \ No newline at end of file diff --git a/requirements_server.txt b/requirements_server.txt index ba89fcebf4e..070182cc2c9 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -21,7 +21,7 @@ SQLAlchemy==1.2.0b2 sqlalchemy_schemadisplay==1.3 tqdm==4.15.0 twisted==17.5.0 -webargs +webargs==3.0.0 marshmallow-sqlalchemy git+https://github.com/sh4r3m4n/filteralchemy@dev#egg=filteralchemy filedepot==0.5.0 From 6e4c10c8a8e7d5f9e9e24286b620079f1d351f89 Mon Sep 17 00:00:00 2001 From: Winna_Z Date: Mon, 21 May 2018 18:22:05 -0300 Subject: [PATCH 1150/1506] check_server_status() done --- server/commands/status_check.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/server/commands/status_check.py b/server/commands/status_check.py index 89121e9e0f3..c91931f50b8 100644 --- a/server/commands/status_check.py +++ b/server/commands/status_check.py @@ -1,18 +1,25 @@ from colorama import init from colorama import Fore, Back, Style - +import socket +from server.utils.daemonize import is_server_running init() -def check_server_running(): - pass +def check_server_running(): + pid = is_server_running() + if pid is not None: + print('Faraday Server is Running. PID:{PID} {green} Running. \ + '.format(green=Fore.GREEN, PID=pid)) + return True + else: + print('Faraday Server is not running {red} Not Running. \ + '.format(red=Fore.RED)) + return True def check_open_ports(): pass - def full_status_check(): - print('{red} Incomplete Check {white}.'.format(red=Fore.RED, white=Fore.WHITE)) check_server_running() - check_open_ports() + From 7bdf2156c2e3ca9e3c870bf65ed3661349710194 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Mon, 21 May 2018 19:38:46 -0300 Subject: [PATCH 1151/1506] Goodbye Notes... --- apis/rest/api.py | 4 +- bin/create_note.py | 43 ---------------------- model/api.py | 10 ----- model/guiapi.py | 57 ++++++++++++----------------- plugins/plugin.py | 46 +++-------------------- test_cases/plugins/test_acunetix.py | 4 +- test_cases/plugins/test_burp.py | 4 +- test_cases/plugins/test_nessus.py | 5 +-- 8 files changed, 32 insertions(+), 141 deletions(-) delete mode 100644 bin/create_note.py diff --git a/apis/rest/api.py b/apis/rest/api.py index 5db283a3082..64b2c20fdf4 100644 --- a/apis/rest/api.py +++ b/apis/rest/api.py @@ -270,9 +270,7 @@ def createVulnWeb(self): 'params', 'query', 'category', 'parent_id']) def createNote(self): - return self._create( - self.controller.newNote, - ['name', 'text', 'parent_id']) + return jsonify(code=200) def createCred(self): return self._create( diff --git a/bin/create_note.py b/bin/create_note.py deleted file mode 100644 index 04fc441c5c6..00000000000 --- a/bin/create_note.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python2.7 -# -*- coding: utf-8 -*- -""" -Faraday Penetration Test IDE -Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) -See the file 'doc/LICENSE' for the license information -""" -import logging - -from model.common import factory -from persistence.server import models - -__description__ = 'Creates a new note' -__prettyname__ = 'Create Note' - -logger = logging.getLogger(__name__) - - -def main(workspace='', args=None, parser=None): - logger.warn('Create note will create a comment. fplugin name will be changed to create_comment') - parser.add_argument('parent', help='Parent ID') - parser.add_argument('--parent_type', - help='Vulnerability severity', - choices=['Host', 'Service'], - default='unclassified') - parser.add_argument('name', help='Note name') - parser.add_argument('text', help='Note content') - - parser.add_argument('--dry-run', action='store_true', help='Do not touch the database. Only print the object ID') - - parsed_args = parser.parse_args(args) - - obj = factory.createModelObject(models.Note.class_signature, - parsed_args.name, - workspace, - text=parsed_args.text, - object_id=parsed_args.parent, - object_type=parsed_args.parent_type.lower() - ) - - models.create_note(workspace, obj) - - return 0, 1 diff --git a/model/api.py b/model/api.py index f2d3fd6b0db..91d0afb57b0 100644 --- a/model/api.py +++ b/model/api.py @@ -195,22 +195,12 @@ def createAndAddVulnWebToService(host_id, service_id, name, desc, ref, severity, # Note def createAndAddNoteToHost(host_id, name, text): - note = newNote(name, text, parent_id=host_id, parent_type='host') - if addNoteToHost(host_id, note): - return note.getID() return None - def createAndAddNoteToService(host_id, service_id, name, text): - note = newNote(name, text, parent_id=service_id, parent_type='service') - if addNoteToService(host_id, service_id, note): - return note.getID() return None def createAndAddNoteToNote(host_id, service_id, note_id, name, text): - note = newNote(name, text, parent_id=note_id, parent_type='comment') - if addNoteToNote(host_id, service_id, note_id, note): - return note.getID() return None def createAndAddCredToService(host_id, service_id, username, password): diff --git a/model/guiapi.py b/model/guiapi.py index 93e29cf49b6..6f671389bb5 100644 --- a/model/guiapi.py +++ b/model/guiapi.py @@ -24,7 +24,7 @@ def setMainApp(ref): global __the_mainapp __the_mainapp = ref notification_center.setUiApp(__the_mainapp) - + def getMainApp(): global __the_mainapp return __the_mainapp @@ -32,12 +32,12 @@ def getMainApp(): def getMainWindow(): global __the_mainapp return __the_mainapp.getMainWindow() - + def postCustomEvent(event, receiver=None): if receiver is None: receiver = getMainWindow() __the_mainapp.postEvent(receiver, event) - + def sendCustomEvent(event, receiver=None): if receiver is None: receiver = getMainWindow() @@ -84,7 +84,7 @@ def createAndAddInterface(host_id, name = "", mac = "00:00:00:00:00:00", return None -def createAndAddServiceToInterface(host_id, interface_id, name, protocol = "tcp?", +def createAndAddServiceToInterface(host_id, interface_id, name, protocol = "tcp?", ports = [], status = "running", version = "unknown", description = ""): service = model.api.newService(name, protocol, ports, status, version, description, parent_id=interface_id) if addServiceToInterface(host_id, interface_id, service): @@ -157,33 +157,22 @@ def createAndAddVulnWeb(model_object, name, desc, website, path, ref=None, def createAndAddNoteToHost(host_id, name, text): - note = model.api.newNote(name, text, parent_id=host_id) - if addNoteToHost(host_id, note): - return note.getID() + return None def createAndAddNoteToInterface(host_id, interface_id, name, text): - note = model.api.newNote(name, text, parent_id=interface_id) - if addNoteToInterface(host_id, interface_id, note): - return note.getID() + return None def createAndAddNoteToService(host_id, service_id, name, text): - note = model.api.newNote(name, text, parent_id=service_id) - if addNoteToService(host_id, service_id, note): - return note.getID() + return None def createAndAddNote(model_object, name, text): - note = model.api.newNote(name, text, parent_id=model_object.getID()) - if addNote(model_object.getID(), note): - return note.getID() return None - - def createAndAddCred(model_object, username, password): cred = model.api.newCred(username, password, parent_id=model_object.getID()) if addCred(model_object.getID(), cred): @@ -255,7 +244,7 @@ def addVuln(model_object_id, vuln): return True return False - + def addNoteToHost(host_id, note): if note is not None: @@ -287,7 +276,7 @@ def addNote(model_object_id, note): return True return False - + def addCred(model_object_id, cred): if cred is not None: __model_controller.addCredSYNC(model_object_id, cred) @@ -336,16 +325,16 @@ def delServiceFromApplication(host_id, application_id, service_id): def delVulnFromApplication(vuln, hostname, appname): __model_controller.delVulnFromApplicationSYNC(hostname, appname, vuln) return True - + def delVulnFromInterface(vuln, hostname, intname): __model_controller.delVulnFromInterfaceSYNC(hostname,intname, vuln) return True - + def delVulnFromHost(vuln, hostname): __model_controller.delVulnFromHostSYNC(hostname,vuln) return True - + def delVulnFromService(vuln, hostname, srvname): __model_controller.delVulnFromServiceSYNC(hostname,srvname, vuln) return True @@ -354,21 +343,21 @@ def delVuln(model_object_id, vuln_id): __model_controller.delVulnSYNC(model_object_id, vuln_id) return True - - + + def delNoteFromApplication(note, hostname, appname): __model_controller.delNoteFromApplicationSYNC(hostname, appname, note) return True - + def delNoteFromInterface(note, hostname, intname): __model_controller.delNoteFromInterfaceSYNC(hostname,intname, note) return True - + def delNoteFromHost(note, hostname): __model_controller.delNoteFromHostSYNC(hostname, note) return True - + def delNoteFromService(note, hostname, srvname): __model_controller.delNoteFromServiceSYNC(hostname,srvname, note) return True @@ -377,7 +366,7 @@ def delNote(model_object_id, note_id): __model_controller.delNoteSYNC(model_object_id, note_id) return True - + def delCred(model_object_id, cred_id): __model_controller.delCredSYNC(model_object_id, cred_id) return True @@ -386,14 +375,14 @@ def delCredFromHost(cred, hostname): __model_controller.delCredFromHostSYNC(hostname, cred) return True - + def delCredFromService(cred, hostname, srvname): __model_controller.delCredFromServiceSYNC(hostname,srvname, cred) return True - - + + def editHost(host, name=None, description=None, os=None, owned=None): __model_controller.editHostSYNC(host, name, description, os, owned) @@ -407,9 +396,9 @@ def editApplication(application, name, description, status, version, owned): __model_controller.editApplicationSYNC(application, name, description, status, version, owned) return True -def editInterface(interface, name=None, description=None, hostnames=None, mac=None, ipv4=None, ipv6=None, network_segment=None, +def editInterface(interface, name=None, description=None, hostnames=None, mac=None, ipv4=None, ipv6=None, network_segment=None, amount_ports_opened=None, amount_ports_closed=None, amount_ports_filtered=None, owned=None): - __model_controller.editInterfaceSYNC(interface, name, description, hostnames, mac, ipv4, ipv6, network_segment, + __model_controller.editInterfaceSYNC(interface, name, description, hostnames, mac, ipv4, ipv6, network_segment, amount_ports_opened, amount_ports_closed, amount_ports_filtered, owned) return True diff --git a/plugins/plugin.py b/plugins/plugin.py index 9be3eb8ff9f..473d36aa2a1 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -26,7 +26,7 @@ Vuln, VulnWeb, Credential, - Note, + Note ) from model import Modelactions #from plugins.modelactions import modelactions @@ -312,52 +312,16 @@ def createAndAddVulnWebToService(self, host_id, service_id, name, desc="", return vulnweb_obj.getID() def createAndAddNoteToHost(self, host_id, name, text): + return None - note_obj = model.common.factory.createModelObject( - Note.class_signature, - name, text=text, object_id=host_id, object_type='host', - workspace_name=self.workspace) - - note_obj._metadata.creator = self.id - self.__addPendingAction(Modelactions.ADDNOTEHOST, note_obj) - return note_obj.getID() - - @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", - current_version=VERSION, - details="Interface object removed. Use host or service instead. Note will be added to Host") def createAndAddNoteToInterface(self, host_id, interface_id, name, text): - - note_obj = model.common.factory.createModelObject( - Note.class_signature, - name, text=text, object_id=host_id, object_type='host', - workspace_name=self.workspace) - - note_obj._metadata.creator = self.id - self.__addPendingAction(Modelactions.ADDNOTEHOST, note_obj) - return note_obj.getID() + return None def createAndAddNoteToService(self, host_id, service_id, name, text): - - note_obj = model.common.factory.createModelObject( - Note.class_signature, - name, text=text, object_id=service_id, object_type='service', - workspace_name=self.workspace) - - note_obj._metadata.creator = self.id - self.__addPendingAction(Modelactions.ADDNOTESRV, note_obj) - return note_obj.getID() + return None def createAndAddNoteToNote(self, host_id, service_id, note_id, name, text): - - note_obj = model.common.factory.createModelObject( - Note.class_signature, - name, text=text, object_id=note_id, object_type='comment', - workspace_name=self.workspace) - - note_obj._metadata.creator = self.id - - self.__addPendingAction(Modelactions.ADDNOTENOTE, note_obj) - return note_obj.getID() + return None def createAndAddCredToService(self, host_id, service_id, username, password): diff --git a/test_cases/plugins/test_acunetix.py b/test_cases/plugins/test_acunetix.py index 29541f14ed3..115b09538a0 100644 --- a/test_cases/plugins/test_acunetix.py +++ b/test_cases/plugins/test_acunetix.py @@ -57,12 +57,10 @@ def test_Plugin_creates_apropiate_objects(self, monkeypatch): action = self.plugin._pending_actions.get(block=True) actions[action[0]].append(action[1]) - assert actions.keys() == [2000, 20008, 2027, 2040, 2038] + assert actions.keys() == [2000, 20008, 2038] assert len(actions[2000]) == 1 assert actions[2000][0].name == "5.175.17.140" assert len(actions[20008]) == 1 - assert len(actions[2027]) == 1 - assert len(actions[2040]) == 1 assert len(actions[2038]) == 52 assert actions[20008][0].ports == [80] diff --git a/test_cases/plugins/test_burp.py b/test_cases/plugins/test_burp.py index f70214adcf2..26e53bbc900 100644 --- a/test_cases/plugins/test_burp.py +++ b/test_cases/plugins/test_burp.py @@ -50,10 +50,8 @@ def test_Plugin_creates_adecuate_objects(self, monkeypatch): actions[action[0]].append(action[1]) assert actions[2000][0].name == "200.20.20.201" - assert actions.keys() == [2000, 20008, 2027, 2040, 2038] + assert actions.keys() == [2000, 20008, 2038] assert len(actions[20008]) == 14 - assert len(actions[2027]) == 14 - assert len(actions[2040]) == 14 assert len(actions[2038]) == 14 assert all('http' == name for name in map(lambda service: service.name, actions[20008])) diff --git a/test_cases/plugins/test_nessus.py b/test_cases/plugins/test_nessus.py index 6c5ca3e0da6..b64e4ce3d43 100644 --- a/test_cases/plugins/test_nessus.py +++ b/test_cases/plugins/test_nessus.py @@ -48,13 +48,10 @@ def test_Plugin_Calls_createAndAddHost(self, monkeypatch): actions[action[0]].append(action[1]) assert actions[2000][0].name == "12.233.108.201" - assert actions.keys() == [2017, 20008, 2027, 2000, 2038, 2040] + assert actions.keys() == [2000, 2017, 2038, 20008] assert len(actions[20008]) == 1 - assert len(actions[2027]) == 1 assert len(actions[2038]) == 1 - assert len(actions[2040]) == 1 - assert actions[2040][0].name == "preprod.boardvantage.net" assert actions[2038][0].name == "Nessus SYN scanner" assert actions[20008][0].ports == [443] From ef42ff5916ac815bcd53147c9356dfa8a421237d Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Tue, 22 May 2018 10:11:42 -0300 Subject: [PATCH 1152/1506] 4725 - Update input and input placeholder color --- server/www/styles/material-input.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/www/styles/material-input.css b/server/www/styles/material-input.css index 3bfc0903426..80b6e78d8f1 100644 --- a/server/www/styles/material-input.css +++ b/server/www/styles/material-input.css @@ -18,7 +18,7 @@ transition: all 100ms ease-in-out; width: 100%; cursor: text; - color: #ddddd; + color: #a1a1a1; } .form-input input, .form-input textarea { @@ -29,11 +29,12 @@ font-size: 16px; font-weight: 300; border: 0; - border-bottom: 1px dashed #A1A1A1; + border-bottom: 1px dashed #d4d4d4; transition: border-color 100ms ease-in-out; outline: none; padding: 0; margin: 0; + color: #a1a1a1; } .form-input textarea { min-height: 30px; From a987c3b372dd1197e85f4865bd7278a00a2043e9 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Tue, 22 May 2018 10:11:46 -0300 Subject: [PATCH 1153/1506] 4725 - Update password input lock size --- server/www/estilos-v3.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/www/estilos-v3.css b/server/www/estilos-v3.css index 63de724c953..e7c152cac84 100644 --- a/server/www/estilos-v3.css +++ b/server/www/estilos-v3.css @@ -122,13 +122,13 @@ body { .frd-icon-lock { background-image: url("images/icon-login-password-lock.svg"); - width: 24px; - height: 24px; + width: 20px; + height: 20px; } #form-signin .placeholder .frd-icon-lock { position: relative; - bottom: 10px; + bottom: 5px; } #footer-login { From 81bbb8bdf2d226ab3f66a75ec08baebad4ebb257 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Tue, 22 May 2018 10:12:01 -0300 Subject: [PATCH 1154/1506] 4725 - Update password input lock image --- server/www/images/icon-login-password-lock.svg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/www/images/icon-login-password-lock.svg b/server/www/images/icon-login-password-lock.svg index 496c2b5c105..c9b623d5c3a 100644 --- a/server/www/images/icon-login-password-lock.svg +++ b/server/www/images/icon-login-password-lock.svg @@ -3,15 +3,15 @@ - - + - From 3efcb994b28e405eb4438042006ad4d7fc35f354 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Tue, 22 May 2018 10:33:16 -0300 Subject: [PATCH 1155/1506] fix - showHeader method --- server/www/scripts/commons/controllers/headerCtrl.js | 6 ++++++ server/www/scripts/commons/partials/header.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 8bb6a7a4854..427701c66bc 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -7,6 +7,12 @@ angular.module('faradayApp') ['$scope', '$routeParams', '$location', 'dashboardSrv', 'workspacesFact', 'vulnsManager', function($scope, $routeParams, $location, dashboardSrv, workspacesFact, vulnsManager) { + + $scope.showHeader = function() { + var noNav = ["", "home", "login", "index"]; + return noNav.indexOf($scope.component) < 0; + }; + init = function(name) { $scope.location = $location.path().split('/')[1]; $scope.workspace = $routeParams.wsId; diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index f10bf3de5c8..51fe3347f84 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -1,4 +1,4 @@ -
+
- +
- - - - - + + + + + diff --git a/server/www/scripts/dashboard/partials/services.html b/server/www/scripts/dashboard/partials/services.html index be25240d430..11543f208c7 100644 --- a/server/www/scripts/dashboard/partials/services.html +++ b/server/www/scripts/dashboard/partials/services.html @@ -3,34 +3,32 @@
-
-
-
-

Services report - -

-
-
- -

No services found yet

-
-
-
-
-
- -
-
{{srv.count}}
-
{{srv.name}}
-
-
-
-
+
+
+

Services report + +

+
+
+ +

No services found yet

+
+
+
+
+
+ +
+
{{srv.count}}
+
{{srv.name}}
+
+
+
-
-
-
\ No newline at end of file +
+ +
diff --git a/server/www/scripts/dashboard/partials/summarized.html b/server/www/scripts/dashboard/partials/summarized.html index f3824898d7e..61f0fc5b1e6 100644 --- a/server/www/scripts/dashboard/partials/summarized.html +++ b/server/www/scripts/dashboard/partials/summarized.html @@ -3,8 +3,8 @@
-
-
+
+

Workspace summarized report diff --git a/server/www/scripts/dashboard/partials/top-hosts.html b/server/www/scripts/dashboard/partials/top-hosts.html index 04474c8225c..a8eaabbe71e 100644 --- a/server/www/scripts/dashboard/partials/top-hosts.html +++ b/server/www/scripts/dashboard/partials/top-hosts.html @@ -2,8 +2,8 @@ -
-
+
+

Top Hosts @@ -19,4 +19,4 @@

Top Hosts

-
\ No newline at end of file +

diff --git a/server/www/scripts/dashboard/partials/top-services.html b/server/www/scripts/dashboard/partials/top-services.html index 3ee58df5c40..7c12011d6f8 100644 --- a/server/www/scripts/dashboard/partials/top-services.html +++ b/server/www/scripts/dashboard/partials/top-services.html @@ -2,8 +2,8 @@ -
- -
\ No newline at end of file +
diff --git a/server/www/scripts/dashboard/partials/vulns-by-severity.html b/server/www/scripts/dashboard/partials/vulns-by-severity.html index 80b76f394aa..cc4681f0dba 100644 --- a/server/www/scripts/dashboard/partials/vulns-by-severity.html +++ b/server/www/scripts/dashboard/partials/vulns-by-severity.html @@ -1,10 +1,10 @@ -
-
+
+
-
-
+
+
{{count}}
{{severity}}
diff --git a/server/www/scripts/dashboard/partials/vulns-piechart.html b/server/www/scripts/dashboard/partials/vulns-piechart.html index 76a793dc6df..48860eedbf8 100644 --- a/server/www/scripts/dashboard/partials/vulns-piechart.html +++ b/server/www/scripts/dashboard/partials/vulns-piechart.html @@ -2,8 +2,8 @@ -
-
+
+

Vulnerabilities @@ -22,4 +22,4 @@

Vulnerabilities chart-colours="data.colors" chart-options="data.options">

-
\ No newline at end of file +
diff --git a/server/www/scripts/dashboard/partials/workspace-progress.html b/server/www/scripts/dashboard/partials/workspace-progress.html index 4535344058a..9301d513930 100644 --- a/server/www/scripts/dashboard/partials/workspace-progress.html +++ b/server/www/scripts/dashboard/partials/workspace-progress.html @@ -1,24 +1,28 @@ -
-
-

- Workspace progress - -

-
-
-
- {{wsProgress}}% -
    -
  • Start date: {{wsStart | amUtc | amDateFormat:'MM/DD/YYYY'}}
  • -
  • End date: {{wsEnd | amUtc | amDateFormat:'MM/DD/YYYY'}}
  • -
-
-
- -

Start date and end date are required

-
+
+
+

+ Workspace progress + +

+
+
+
+ {{wsProgress}}% +
+
+
+ Start date: {{wsStart | amUtc | amDateFormat:'MM/DD/YYYY'}} +
+
+ End date: {{wsEnd | amUtc | amDateFormat:'MM/DD/YYYY'}} +
+
+ +

Start date and end date are required

+
+
From 18419bfbbe78479e690a6a4c81f13bf51c44964c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 22 May 2018 12:51:43 -0300 Subject: [PATCH 1159/1506] Add basic free text search in vulns API Now it only uses name and description fields, it should be improved --- server/api/modules/vulns.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index cefaf85c60c..b5ffa676482 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -453,6 +453,17 @@ def _get_eagerloaded_query(self, *args, **kwargs): [Vulnerability, VulnerabilityWeb] ), *joinedloads) + def _filter_query(self, query): + query = super(VulnerabilityView, self)._filter_query(query) + search_term = flask.request.args.get('search', None) + if search_term is not None: + # TODO migration: add more fields to free text search + like_term = '%' + search_term + '%' + match_name = VulnerabilityGeneric.name.ilike(like_term) + match_desc = VulnerabilityGeneric.description.ilike(like_term) + query = query.filter(match_name | match_desc) + return query + @property def model_class(self): if request.method == 'POST': From c5ae9c433ee5bb85d9e931e5c81460438995fb72 Mon Sep 17 00:00:00 2001 From: montive Date: Tue, 22 May 2018 14:38:38 -0300 Subject: [PATCH 1160/1506] Update status_check --- server/commands/status_check.py | 82 +++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/server/commands/status_check.py b/server/commands/status_check.py index c91931f50b8..b97223fda90 100644 --- a/server/commands/status_check.py +++ b/server/commands/status_check.py @@ -1,7 +1,20 @@ +import requests from colorama import init from colorama import Fore, Back, Style import socket from server.utils.daemonize import is_server_running +import subprocess +import sqlalchemy +from config.configuration import getInstanceConfiguration +from utils import dependencies + + +CONF = getInstanceConfiguration() + +import server.utils.logger + +logger = server.utils.logger.get_logger() + init() @@ -9,17 +22,76 @@ def check_server_running(): pid = is_server_running() if pid is not None: - print('Faraday Server is Running. PID:{PID} {green} Running. \ - '.format(green=Fore.GREEN, PID=pid)) + print('Faraday Server is Running. PID:{PID} {green} Running. {white}\ + '.format(green=Fore.GREEN, PID=pid, white=Fore.WHITE)) return True else: - print('Faraday Server is not running {red} Not Running. \ - '.format(red=Fore.RED)) + print('Faraday Server is not running {red} Not Running. {white} \ + '.format(red=Fore.RED, white=Fore.WHITE)) return True def check_open_ports(): pass +def check_postgres(): + + try: + if db.sessions.query(Workspace).count(): + print("PostgreSQL is running") + except: + print('Could not connect to postgresql, please check if database is running') + + + +def check_client(): + port_rest = CONF.getApiRestfulConInfoPort() + + try: + response_rest = requests.get('http://localhost:%s/status/check' % port_rest) + print "Faraday GTK is running" + except: + print('WARNING. Faraday GTK is not running') + +def check_server_dependencies(): + + installed_deps, missing_deps, conflict_deps = dependencies.check_dependencies( + requirements_file='requirements_server.txt') + + print("Checking server's dependencies") + + + if conflict_deps: + print("Some dependencies are old. Update them with \"pip install -r requirements_server.txt -U\"") + print("Dependecies to update: ",", ".join(conflict_deps)) + + + if missing_deps: + print("Dependencies not met. Please refer to the documentation in order to install them. ", + ", ".join(missing_deps)) + +def check_gtk_dependencies(): + installed_deps, missing_deps, conflict_deps = dependencies.check_dependencies( + requirements_file='requirements.txt') + + print("Checking GTK's dependencies") + + + if conflict_deps: + print("Some dependencies are old. Update them with \"pip install -r requirements_server.txt -U\"") + print("Dependecies to update: ",", ".join(conflict_deps)) + + + if missing_deps: + print("Dependencies not met. Please refer to the documentation in order to install them. ", + ", ".join(missing_deps)) + + + def full_status_check(): + print('{red} Incomplete Check {white}.'.format(red=Fore.RED, white=Fore.WHITE)) check_server_running() - + check_open_ports() + check_postgres() + check_client() + check_server_dependencies() + check_gtk_dependencies() \ No newline at end of file From 5ea7bddbcec7bd8f6b89930d413f8c7536510c54 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 23 May 2018 10:07:18 -0300 Subject: [PATCH 1161/1506] 4721 - Update activity feed and last vulnerabilities table --- server/www/dashboard-v3.css | 16 ++++++++++++++++ .../scripts/dashboard/partials/activityFeed.html | 8 ++++---- .../scripts/dashboard/partials/dashboard.html | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/server/www/dashboard-v3.css b/server/www/dashboard-v3.css index 5c3e1104fea..c3e4392ddfd 100644 --- a/server/www/dashboard-v3.css +++ b/server/www/dashboard-v3.css @@ -87,3 +87,19 @@ .text-left { text-align: left !important; } + +.dashboard .progress-bar span { + font-weight: bold; +} + +.dashboard table thead th span { + color: #3393d4; +} + +.fg-blue { + color: #3393d4; +} + +.fg-light-gray { + color: #a1a1a1; +} diff --git a/server/www/scripts/dashboard/partials/activityFeed.html b/server/www/scripts/dashboard/partials/activityFeed.html index 8aced40ea16..94196379865 100644 --- a/server/www/scripts/dashboard/partials/activityFeed.html +++ b/server/www/scripts/dashboard/partials/activityFeed.html @@ -20,9 +20,9 @@

Activity Feed

diff --git a/server/www/scripts/dashboard/partials/dashboard.html b/server/www/scripts/dashboard/partials/dashboard.html index 43d9f9337a4..8ba9a14c848 100644 --- a/server/www/scripts/dashboard/partials/dashboard.html +++ b/server/www/scripts/dashboard/partials/dashboard.html @@ -2,7 +2,7 @@ -
+
From d81ed585cf66707e288f5f8a971ec9a9e4e1ba21 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Wed, 23 May 2018 12:25:27 -0300 Subject: [PATCH 1162/1506] [FIX] persistance.server.models.update_vuln_web [ADD] Test for update_vuln --- persistence/server/models.py | 2 +- test_cases/test_persistence_server_models.py | 44 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/persistence/server/models.py b/persistence/server/models.py index f05d4df3299..3feb41d453c 100644 --- a/persistence/server/models.py +++ b/persistence/server/models.py @@ -414,7 +414,7 @@ def create_vuln_web(workspace_name, vuln_web, command_id=None): @_ignore_in_changes -def update_vuln_web(workspace_name, vuln_web, command_id): +def update_vuln_web(workspace_name, vuln_web, command_id=None): """Take a workspace_name and a VulnWeb object and update it in the sever. Return the server's json response as a dictionary. diff --git a/test_cases/test_persistence_server_models.py b/test_cases/test_persistence_server_models.py index e69de29bb2d..f0740eb9551 100644 --- a/test_cases/test_persistence_server_models.py +++ b/test_cases/test_persistence_server_models.py @@ -0,0 +1,44 @@ + +import persistence.server.models as models +import pytest +import responses + +from test_api_workspaced_base import GenericAPITest + +from test_cases.factories import ServiceFactory, CommandFactory, \ + CommandObjectFactory, HostFactory, EmptyCommandFactory, \ + UserFactory, VulnerabilityWebFactory, VulnerabilityFactory, \ + ReferenceFactory, PolicyViolationFactory + +@pytest.mark.usefixtures('logged_user') +class TestModelsFuncions(GenericAPITest): + + factory = VulnerabilityFactory + + def setUp(self): + pass + + @responses.activate + def test_persistence_server_update_vuln(self): + + fo = self.first_object + + vuln = {} + vuln['desc'] = fo.description + vuln['data'] = fo.data + vuln['severity'] = fo.severity + vuln['refs'] = list(fo.references) + vuln['confirmed'] = fo.confirmed + vuln['resolution'] = fo.resolution + vuln['status'] = fo.status + vuln['policyviolations'] = list(fo.policy_violations) + + v = models.Vuln(vuln, self.workspace.name) + v.id = fo.id + + responses.add(responses.PUT, 'http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id), + json={}, status=200) + + models.update_vuln(self.workspace.name, v) + + From b7148c9a84a297c4a8b32f3dd9124121ec05503a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 23 May 2018 12:30:37 -0300 Subject: [PATCH 1163/1506] Improve start log messages and excepition logging in the server ./faraday-server.py now shows the URL of the server instead of the websocket one --- server/web.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/server/web.py b/server/web.py index 222121efa19..af78e5de6e3 100644 --- a/server/web.py +++ b/server/web.py @@ -74,10 +74,10 @@ class WebServer(object): WEB_UI_LOCAL_PATH = os.path.join(server.config.FARADAY_BASE, 'server/www') def __init__(self, enable_ssl=False): - logger.info('Starting web server at port {0} with bind address {1}. SSL {2}'.format( - server.config.faraday_server.port, + logger.info('Starting web server at {}://{}:{}/'.format( + 'https' if enable_ssl else 'http', server.config.faraday_server.bind_address, - enable_ssl)) + server.config.faraday_server.port)) self.__ssl_enabled = enable_ssl self.__config_server() self.__build_server_tree() @@ -118,7 +118,13 @@ def __build_websockets_resource(self): url = 'wss://' + url else: url = 'ws://' + url - logger.info(u"Websocket listening at {url}".format(url=url)) + # logger.info(u"Websocket listening at {url}".format(url=url)) + logger.info('Starting websocket server at port {0} with bind address {1}. ' + 'SSL {2}'.format( + websocket_port, + self.__bind_address, + self.__ssl_enabled + )) factory = WorkspaceServerFactory(url=url) factory.protocol = BroadcastServerProtocol @@ -146,6 +152,6 @@ def run(self): logger.error(str(e)) sys.exit(1) except Exception as e: - logger.debug(e) logger.error('Something went wrong when trying to setup the Web UI') + logger.exception(e) sys.exit(1) From be017fed74fb0ae69fe7e7307c09a2caa6124ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 23 May 2018 13:37:41 -0300 Subject: [PATCH 1164/1506] Move filteralchemy fork to infobyte account --- requirements_server.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_server.txt b/requirements_server.txt index 070182cc2c9..2eb36716da7 100644 --- a/requirements_server.txt +++ b/requirements_server.txt @@ -23,7 +23,7 @@ tqdm==4.15.0 twisted==17.5.0 webargs==3.0.0 marshmallow-sqlalchemy -git+https://github.com/sh4r3m4n/filteralchemy@dev#egg=filteralchemy +git+https://github.com/infobyte/filteralchemy@dev#egg=filteralchemy filedepot==0.5.0 nplusone==0.8.1 deprecation==1.0.1 From 47c0236528d97025b85ffbd576569ce1718f4b39 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Wed, 23 May 2018 16:25:07 -0300 Subject: [PATCH 1165/1506] [ADD] persistance.server.models.update_vuln/update_veln_web testing --- test_cases/test_persistence_server_models.py | 149 ++++++++++++++++--- 1 file changed, 126 insertions(+), 23 deletions(-) diff --git a/test_cases/test_persistence_server_models.py b/test_cases/test_persistence_server_models.py index f0740eb9551..8d626acab10 100644 --- a/test_cases/test_persistence_server_models.py +++ b/test_cases/test_persistence_server_models.py @@ -1,44 +1,147 @@ - import persistence.server.models as models import pytest import responses +import requests from test_api_workspaced_base import GenericAPITest -from test_cases.factories import ServiceFactory, CommandFactory, \ - CommandObjectFactory, HostFactory, EmptyCommandFactory, \ - UserFactory, VulnerabilityWebFactory, VulnerabilityFactory, \ - ReferenceFactory, PolicyViolationFactory +from test_cases.factories import VulnerabilityWebFactory, VulnerabilityFactory -@pytest.mark.usefixtures('logged_user') -class TestModelsFuncions(GenericAPITest): +@pytest.mark.usefixtures('logged_user') +class TestVulnPersistanceModelsFuncions(GenericAPITest): factory = VulnerabilityFactory - def setUp(self): - pass - @responses.activate def test_persistence_server_update_vuln(self): - fo = self.first_object - vuln = {} - vuln['desc'] = fo.description - vuln['data'] = fo.data - vuln['severity'] = fo.severity - vuln['refs'] = list(fo.references) - vuln['confirmed'] = fo.confirmed - vuln['resolution'] = fo.resolution - vuln['status'] = fo.status - vuln['policyviolations'] = list(fo.policy_violations) + vuln = {'desc': fo.description, 'data': fo.data, 'severity': fo.severity, 'refs': list(fo.references), + 'confirmed': fo.confirmed, 'resolution': fo.resolution, 'status': fo.status, + 'policyviolations': list(fo.policy_violations)} v = models.Vuln(vuln, self.workspace.name) v.id = fo.id - responses.add(responses.PUT, 'http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id), - json={}, status=200) + resp = {u'status': u'closed', + u'_rev': u'', + u'parent_type': v.getParentType(), + u'owned': v.isOwned(), + u'owner': v.getParent(), + u'query': u'', + u'refs': v.getRefs(), + u'impact': {u'integrity': False, u'confidentiality': False, u'availability': False, + u'accountability': False}, + u'confirmed': v.getConfirmed(), + u'severity': v.getSeverity(), + u'service': None, + u'policyviolations': v.getPolicyViolations(), + u'params': u'', + u'type': u'Vulnerability', + u'method': u'', + u'metadata': {u'update_time': u'2018-05-23T17:03:27.880196+00:00', u'update_user': u'', + u'update_action': 0, u'creator': u'Nmap', + u'create_time': u'2018-05-18T16:30:26.011851+00:00', + u'update_controller_action': u'', u'owner': u'faraday', u'command_id': 22}, + u'website': u'', + u'issuetracker': {}, + u'description': v.getDesc(), + u'tags': [], + u'easeofresolution': None, + u'hostnames': [], + u'pname': u'', + u'date': u'2018-05-18T16:30:26.011851+00:00', + u'path': u'', + u'data': v.getData(), + u'response': u'', + u'desc': v.getDesc(), + u'name': v.getName(), + u'obj_id': str(v.getID()), + u'request': u'', + u'_attachments': {}, + u'target': u'192.168.10.103', + u'_id': v.getID(), + u'resolution': v.getResolution() + } + responses.add(responses.PUT, + 'http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id), + json=resp, status=200) + + a = requests.put('http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id)) + + try: + models.update_vuln(self.workspace.name, v) + except TypeError as e: + pytest.fail(e.message) + + +@pytest.mark.usefixtures('logged_user') +class TestVulnWebPersistanceModelsFuncions(GenericAPITest): + factory = VulnerabilityWebFactory + + @responses.activate + def test_persistence_server_update_vuln_web(self): + fo = self.first_object + + vuln_web = {'desc': fo.description, 'data': fo.data, 'severity': fo.severity, 'refs': list(fo.references), + 'confirmed': fo.confirmed, 'resolution': fo.resolution, 'status': fo.status, + 'policyviolations': list(fo.policy_violations), 'path': fo.path, 'website': fo.website, + 'request': fo.request, 'response': fo.response, 'method': fo.method, 'params': fo.parameters, + 'pname': fo.parameter_name, 'query': str(fo.query), '_attachments': fo.attachments, + 'hostnames': list(fo.hostnames), + 'impact': {'accountability': fo.impact_accountability, 'availability': fo.impact_availability, + 'confidentiality': fo.impact_confidentiality, 'integrity': fo.impact_integrity}, + 'service': fo.service_id, 'tags': list(fo.tags), 'target': fo.target_host_ip} + + v = models.VulnWeb(vuln_web, self.workspace.name) + v.id = fo.id - models.update_vuln(self.workspace.name, v) + resp = {u'status': u'closed', + u'_rev': u'', + u'parent_type': v.getParentType(), + u'owned': v.isOwned(), + u'owner': v.getParent(), + u'query': str(v.getQuery()), + u'refs': v.getRefs(), + u'impact': v.getImpact(), + u'confirmed': v.getConfirmed(), + u'severity': v.getSeverity(), + u'service': v.getService(), + u'policyviolations': v.getPolicyViolations(), + u'params': v.getParams(), + u'type': u'VulnerabilityWeb', + u'method': v.getMethod(), + u'metadata': {u'update_time': u'2018-05-23T17:03:27.880196+00:00', u'update_user': u'', + u'update_action': 0, u'creator': u'Nmap', + u'create_time': u'2018-05-18T16:30:26.011851+00:00', + u'update_controller_action': u'', u'owner': u'faraday', u'command_id': 22}, + u'website': v.getWebsite(), + u'issuetracker': {}, + u'description': v.getDesc(), + u'tags': v.getTags(), + u'easeofresolution': None, + u'hostnames': v.getHostnames(), + u'pname': v.getPname(), + u'date': u'2018-05-18T16:30:26.011851+00:00', + u'path': v.getPath(), + u'data': v.getData(), + u'response': v.getResponse(), + u'desc': v.getDesc(), + u'name': v.getName(), + u'obj_id': str(v.getID()), + u'request': v.getRequest(), + u'_attachments': str(v.getAttachments()), + u'target': v.getTarget(), + u'_id': v.getID(), + u'resolution': v.getResolution() + } + responses.add(responses.PUT, + 'http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id), + json=resp, status=200) + a = requests.put('http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id)) + try: + models.update_vuln_web(self.workspace.name, v) + except TypeError as e: + pytest.fail(e.message) From 30c99641a76ad1bf8dc95e304c12dd93c506bd05 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Wed, 23 May 2018 16:57:34 -0300 Subject: [PATCH 1166/1506] [FIX] Port gotten from config --- test_cases/test_persistence_server_models.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test_cases/test_persistence_server_models.py b/test_cases/test_persistence_server_models.py index 8d626acab10..6f6ad78c6d6 100644 --- a/test_cases/test_persistence_server_models.py +++ b/test_cases/test_persistence_server_models.py @@ -3,6 +3,8 @@ import responses import requests +import server.config + from test_api_workspaced_base import GenericAPITest from test_cases.factories import VulnerabilityWebFactory, VulnerabilityFactory @@ -63,11 +65,14 @@ def test_persistence_server_update_vuln(self): u'_id': v.getID(), u'resolution': v.getResolution() } + + port = server.config.faraday_server.port + responses.add(responses.PUT, - 'http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id), + 'http://localhost:{0}/_api/v2/ws/{1}/vulns/{2}/'.format(port,self.workspace.name, v.id), json=resp, status=200) - a = requests.put('http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id)) + a = requests.put('http://localhost:{0}/_api/v2/ws/{1}/vulns/{2}/'.format(port,self.workspace.name, v.id)) try: models.update_vuln(self.workspace.name, v) @@ -135,11 +140,14 @@ def test_persistence_server_update_vuln_web(self): u'_id': v.getID(), u'resolution': v.getResolution() } + + port = server.config.faraday_server.port + responses.add(responses.PUT, - 'http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id), + 'http://localhost:{0}/_api/v2/ws/{1}/vulns/{2}/'.format(port,self.workspace.name, v.id), json=resp, status=200) - a = requests.put('http://localhost:5985/_api/v2/ws/{0}/vulns/{1}/'.format(self.workspace.name, v.id)) + a = requests.put('http://localhost:{0}/_api/v2/ws/{1}/vulns/{2}/'.format(port,self.workspace.name, v.id)) try: models.update_vuln_web(self.workspace.name, v) From 6ea01b953d1941804f018422de57957eda056af8 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Tue, 22 May 2018 10:11:42 -0300 Subject: [PATCH 1167/1506] 4725 - Update input and input placeholder color --- server/www/styles/material-input.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/www/styles/material-input.css b/server/www/styles/material-input.css index 3bfc0903426..80b6e78d8f1 100644 --- a/server/www/styles/material-input.css +++ b/server/www/styles/material-input.css @@ -18,7 +18,7 @@ transition: all 100ms ease-in-out; width: 100%; cursor: text; - color: #ddddd; + color: #a1a1a1; } .form-input input, .form-input textarea { @@ -29,11 +29,12 @@ font-size: 16px; font-weight: 300; border: 0; - border-bottom: 1px dashed #A1A1A1; + border-bottom: 1px dashed #d4d4d4; transition: border-color 100ms ease-in-out; outline: none; padding: 0; margin: 0; + color: #a1a1a1; } .form-input textarea { min-height: 30px; From 5bb3db5b9f388cc66552b92743e2e67f66147825 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Tue, 22 May 2018 10:11:46 -0300 Subject: [PATCH 1168/1506] 4725 - Update password input lock size --- server/www/estilos-v3.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/www/estilos-v3.css b/server/www/estilos-v3.css index 63de724c953..e7c152cac84 100644 --- a/server/www/estilos-v3.css +++ b/server/www/estilos-v3.css @@ -122,13 +122,13 @@ body { .frd-icon-lock { background-image: url("images/icon-login-password-lock.svg"); - width: 24px; - height: 24px; + width: 20px; + height: 20px; } #form-signin .placeholder .frd-icon-lock { position: relative; - bottom: 10px; + bottom: 5px; } #footer-login { From 2a6ab606c0ccc2d5651fe799fdb20f5910f568e5 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Tue, 22 May 2018 10:12:01 -0300 Subject: [PATCH 1169/1506] 4725 - Update password input lock image --- server/www/images/icon-login-password-lock.svg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/www/images/icon-login-password-lock.svg b/server/www/images/icon-login-password-lock.svg index 496c2b5c105..c9b623d5c3a 100644 --- a/server/www/images/icon-login-password-lock.svg +++ b/server/www/images/icon-login-password-lock.svg @@ -3,15 +3,15 @@ - - + - From 3bc49a91c92d9ca88f295fb457f331a3a3398e23 Mon Sep 17 00:00:00 2001 From: montive Date: Wed, 23 May 2018 18:15:48 -0300 Subject: [PATCH 1170/1506] Update status_check.py --- server/commands/status_check.py | 53 ++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/server/commands/status_check.py b/server/commands/status_check.py index b97223fda90..02fd8369dc0 100644 --- a/server/commands/status_check.py +++ b/server/commands/status_check.py @@ -1,24 +1,24 @@ import requests +import subprocess +import sqlalchemy +import socket +import server.utils.logger from colorama import init from colorama import Fore, Back, Style -import socket from server.utils.daemonize import is_server_running -import subprocess -import sqlalchemy +from server.models import db +from server.web import app from config.configuration import getInstanceConfiguration from utils import dependencies CONF = getInstanceConfiguration() -import server.utils.logger - logger = server.utils.logger.get_logger() init() - def check_server_running(): pid = is_server_running() if pid is not None: @@ -34,14 +34,12 @@ def check_open_ports(): pass def check_postgres(): - - try: - if db.sessions.query(Workspace).count(): + with app.app_context(): + try: + result = str(db.engine.execute("SELECT version()")) print("PostgreSQL is running") - except: - print('Could not connect to postgresql, please check if database is running') - - + except sqlalchemy.exc.OperationalError: + print('Could not connect to postgresql, please check if database is running') def check_client(): port_rest = CONF.getApiRestfulConInfoPort() @@ -49,7 +47,7 @@ def check_client(): try: response_rest = requests.get('http://localhost:%s/status/check' % port_rest) print "Faraday GTK is running" - except: + except requests.exceptions.ConnectionError: print('WARNING. Faraday GTK is not running') def check_server_dependencies(): @@ -57,14 +55,10 @@ def check_server_dependencies(): installed_deps, missing_deps, conflict_deps = dependencies.check_dependencies( requirements_file='requirements_server.txt') - print("Checking server's dependencies") - - if conflict_deps: print("Some dependencies are old. Update them with \"pip install -r requirements_server.txt -U\"") print("Dependecies to update: ",", ".join(conflict_deps)) - if missing_deps: print("Dependencies not met. Please refer to the documentation in order to install them. ", ", ".join(missing_deps)) @@ -73,19 +67,29 @@ def check_gtk_dependencies(): installed_deps, missing_deps, conflict_deps = dependencies.check_dependencies( requirements_file='requirements.txt') - print("Checking GTK's dependencies") - - if conflict_deps: print("Some dependencies are old. Update them with \"pip install -r requirements_server.txt -U\"") print("Dependecies to update: ",", ".join(conflict_deps)) - if missing_deps: print("Dependencies not met. Please refer to the documentation in order to install them. ", ", ".join(missing_deps)) - +def check_credentials(): + api_username = CONF.getAPIUsername() + api_password = CONF.getAPIPassword() + + values = {'email': api_username , 'password': api_password} + + r = requests.post('http://localhost:5985/_api/login', json=values) + + if r.status_code == 200 and 'user' in r.json()['response']: + print('Credentials matched') + elif r.status_code == 400: + print('Error. Credentials does not match') + elif r.status_code == 500: + print('Server failed with unexpected error. check if databaseservice is working') + def full_status_check(): print('{red} Incomplete Check {white}.'.format(red=Fore.RED, white=Fore.WHITE)) @@ -94,4 +98,5 @@ def full_status_check(): check_postgres() check_client() check_server_dependencies() - check_gtk_dependencies() \ No newline at end of file + check_gtk_dependencies() + check_credentials() \ No newline at end of file From 797320f2531be7296b9992e4d77ee80f4ef79246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 23 May 2018 19:20:34 -0300 Subject: [PATCH 1171/1506] Add authentication and authorization to websocket server Changes to the GTK client are really horrible, but they (appearently) work! --- persistence/server/changes_stream.py | 7 ++++++ server/api/modules/websocket_auth.py | 23 ++++++++++++++++++ server/app.py | 2 ++ server/websocket_factories.py | 35 +++++++++++++++++++++++++-- test_cases/test_api_websocket_auth.py | 26 ++++++++++++++++++++ 5 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 server/api/modules/websocket_auth.py create mode 100644 test_cases/test_api_websocket_auth.py diff --git a/persistence/server/changes_stream.py b/persistence/server/changes_stream.py index 1ce4367a618..f7f0c9db46f 100644 --- a/persistence/server/changes_stream.py +++ b/persistence/server/changes_stream.py @@ -92,9 +92,16 @@ def stop(self): super(WebsocketsChangesStream, self).stop() def on_open(self, ws): + from persistence.server.server import _create_server_api_url, _post + r = _post( + _create_server_api_url() + + '/ws/{}/websocket_token/'.format(self.workspace_name), + expected_response=200) + token = r['token'] self.ws.send(json.dumps({ 'action': 'JOIN_WORKSPACE', 'workspace': self.workspace_name, + 'token': token, })) def on_message(self, ws, message): diff --git a/server/api/modules/websocket_auth.py b/server/api/modules/websocket_auth.py new file mode 100644 index 00000000000..5a38148ce29 --- /dev/null +++ b/server/api/modules/websocket_auth.py @@ -0,0 +1,23 @@ +# Faraday Penetration Test IDE +# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) +# See the file 'doc/LICENSE' for the license information + +from flask import Blueprint +from flask import current_app as app +from itsdangerous import TimestampSigner +from server.api.base import GenericWorkspacedView + +websocket_auth_api = Blueprint('websocket_auth_api', __name__) + + +class WebsocketAuthView(GenericWorkspacedView): + route_base = 'websocket_token' + + def post(self, workspace_name): + workspace = self._get_workspace(workspace_name) + signer = TimestampSigner(app.config['SECRET_KEY'], salt="websocket") + token = signer.sign(str(workspace.id)) + return {"token": token} + + +WebsocketAuthView.register(websocket_auth_api) diff --git a/server/app.py b/server/app.py index 4bd204cc075..8fe93eb005c 100644 --- a/server/app.py +++ b/server/app.py @@ -64,6 +64,7 @@ def register_blueprints(app): from server.api.modules.workspaces import workspace_api from server.api.modules.handlers import handlers_api from server.api.modules.comments import comment_api + from server.api.modules.websocket_auth import websocket_auth_api app.register_blueprint(commandsrun_api) app.register_blueprint(credentials_api) app.register_blueprint(host_api) @@ -76,6 +77,7 @@ def register_blueprints(app): app.register_blueprint(workspace_api) app.register_blueprint(handlers_api) app.register_blueprint(comment_api) + app.register_blueprint(websocket_auth_api) def check_testing_configuration(testing, app): diff --git a/server/websocket_factories.py b/server/websocket_factories.py index fb4d2169e0a..75ba8732595 100644 --- a/server/websocket_factories.py +++ b/server/websocket_factories.py @@ -1,7 +1,9 @@ import json import logging +import itsdangerous import Cookie +import server.utils from collections import defaultdict from Queue import Queue, Empty @@ -13,7 +15,9 @@ WebSocketServerProtocol ) -logger =logging.getLogger(__name__) +from server.models import Workspace + +logger = server.utils.logger.get_logger(__name__) changes_queue = Queue() @@ -32,6 +36,7 @@ def onConnect(self, request): return (protocol, headers) def onMessage(self, payload, is_binary): + from server.web import app """ We only support JOIN and LEAVE workspace messages. When authentication is implemented we need to verify @@ -42,7 +47,33 @@ def onMessage(self, payload, is_binary): if not is_binary: message = json.loads(payload) if message['action'] == 'JOIN_WORKSPACE': - self.factory.join_workspace(self, message['workspace']) + if 'workspace' not in message or 'token' not in message: + logger.warning('Invalid join workspace message: ' + '{}'.format(message)) + self.sendClose() + return + signer = itsdangerous.TimestampSigner(app.config['SECRET_KEY'], + salt="websocket") + try: + workspace_id = signer.unsign(message['token'], max_age=60) + except itsdangerous.BadData as e: + self.sendClose() + logger.warning('Invalid websocket token for workspace ' + '{}'.format(message['workspace'])) + logger.exception(e) + else: + with app.app_context(): + workspace = Workspace.query.get(int(workspace_id)) + if workspace.name != message['workspace']: + logger.warning( + 'Trying to join workspace {} with token of ' + 'workspace {}. Rejecting.'.format( + message['workspace'], workspace.name + )) + self.sendClose() + else: + self.factory.join_workspace( + self, message['workspace']) if message['action'] == 'LEAVE_WORKSPACE': self.factory.leave_workspace(self, message['workspace']) diff --git a/test_cases/test_api_websocket_auth.py b/test_cases/test_api_websocket_auth.py new file mode 100644 index 00000000000..3cc6d28ebcd --- /dev/null +++ b/test_cases/test_api_websocket_auth.py @@ -0,0 +1,26 @@ +import pytest + + +class TestWebsocketAuthEndpoint: + + def test_not_logged_in_request_fail(self, test_client, workspace): + res = test_client.post('/v2/ws/{}/websocket_token/'.format( + workspace.name)) + assert res.status_code == 401 + + @pytest.mark.usefixtures('logged_user') + def test_get_method_not_allowed(self, test_client, workspace): + res = test_client.get('/v2/ws/{}/websocket_token/'.format( + workspace.name)) + assert res.status_code == 405 + + @pytest.mark.usefixtures('logged_user') + def test_succeeds(self, test_client, workspace): + res = test_client.post('/v2/ws/{}/websocket_token/'.format( + workspace.name)) + assert res.status_code == 200 + + # A token for that workspace should be generated, + # This will break if we change the token generation + # mechanism. + assert res.json['token'].startswith(str(workspace.id)) From fd1d42e0512554ef8861a0cf9fea7d278945e641 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Mon, 21 May 2018 15:21:25 -0300 Subject: [PATCH 1172/1506] Cambios en status report --- server/www/estilos.css | 3 +- server/www/images/icon-list-confirmed.svg | 11 ++ server/www/images/icon-list-notconfirmed.svg | 15 ++ .../www/images/icon-toolbar-confirmed-off.svg | 19 +++ .../www/images/icon-toolbar-confirmed-on.svg | 13 ++ server/www/images/icon-toolbar-delete.svg | 18 ++ server/www/images/icon-toolbar-download.svg | 20 +++ server/www/images/icon-toolbar-edit.svg | 19 +++ .../images/icon-toolbar-searchbar-loupe.svg | 13 ++ server/www/index.html | 1 + .../statusReport/controllers/statusReport.js | 47 +++--- .../statusReport/partials/statusReport.html | 151 ++++++++--------- .../partials/ui-grid/columns/datecolumn.html | 2 +- .../partials/ui-grid/columns/idcolumn.html | 5 +- .../ui-grid/columns/severitycolumn.html | 12 +- .../ui-grid/columns/statuscolumn.html | 51 +++++- .../partials/ui-grid/confirmbutton.html | 4 +- .../scripts/statusReport/styles/status.css | 158 ++++++++++++++++++ .../www/scripts/vulndb/partials/vulndb.html | 4 +- 19 files changed, 446 insertions(+), 120 deletions(-) create mode 100644 server/www/images/icon-list-confirmed.svg create mode 100644 server/www/images/icon-list-notconfirmed.svg create mode 100644 server/www/images/icon-toolbar-confirmed-off.svg create mode 100644 server/www/images/icon-toolbar-confirmed-on.svg create mode 100644 server/www/images/icon-toolbar-delete.svg create mode 100644 server/www/images/icon-toolbar-download.svg create mode 100644 server/www/images/icon-toolbar-edit.svg create mode 100644 server/www/images/icon-toolbar-searchbar-loupe.svg create mode 100644 server/www/scripts/statusReport/styles/status.css diff --git a/server/www/estilos.css b/server/www/estilos.css index 8c7998a6c2c..e126ec7f255 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -727,8 +727,7 @@ text.host-name{font-size: 13px;font-weight:bold;} color: #000000 !important; } } - /* Workspaces list - Open*/ - div.button-control.col-md-6.col-sm-6.col-xs-12 {display: block; float: right} + #sec { float: right; margin-top: -34px; diff --git a/server/www/images/icon-list-confirmed.svg b/server/www/images/icon-list-confirmed.svg new file mode 100644 index 00000000000..03f543428bc --- /dev/null +++ b/server/www/images/icon-list-confirmed.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/server/www/images/icon-list-notconfirmed.svg b/server/www/images/icon-list-notconfirmed.svg new file mode 100644 index 00000000000..72f0f7261be --- /dev/null +++ b/server/www/images/icon-list-notconfirmed.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/server/www/images/icon-toolbar-confirmed-off.svg b/server/www/images/icon-toolbar-confirmed-off.svg new file mode 100644 index 00000000000..55343e5fcd9 --- /dev/null +++ b/server/www/images/icon-toolbar-confirmed-off.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/server/www/images/icon-toolbar-confirmed-on.svg b/server/www/images/icon-toolbar-confirmed-on.svg new file mode 100644 index 00000000000..4566210c364 --- /dev/null +++ b/server/www/images/icon-toolbar-confirmed-on.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/server/www/images/icon-toolbar-delete.svg b/server/www/images/icon-toolbar-delete.svg new file mode 100644 index 00000000000..db7d415472d --- /dev/null +++ b/server/www/images/icon-toolbar-delete.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/server/www/images/icon-toolbar-download.svg b/server/www/images/icon-toolbar-download.svg new file mode 100644 index 00000000000..c9c846f31f0 --- /dev/null +++ b/server/www/images/icon-toolbar-download.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/server/www/images/icon-toolbar-edit.svg b/server/www/images/icon-toolbar-edit.svg new file mode 100644 index 00000000000..99b782229de --- /dev/null +++ b/server/www/images/icon-toolbar-edit.svg @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/server/www/images/icon-toolbar-searchbar-loupe.svg b/server/www/images/icon-toolbar-searchbar-loupe.svg new file mode 100644 index 00000000000..8dccf1b48a1 --- /dev/null +++ b/server/www/images/icon-toolbar-searchbar-loupe.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/server/www/index.html b/server/www/index.html index 485a6b7af30..e75faa521a2 100644 --- a/server/www/index.html +++ b/server/www/index.html @@ -31,6 +31,7 @@ + diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index d90eb8dbb17..e66f8a43a5a 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -180,13 +180,13 @@ angular.module("faradayApp") "target": true, "desc": true, "resolution": false, - "data": true, + "data": false, "easeofresolution": false, - "status": false, + "status": true, "website": false, "path": false, "request": false, - "refs": true, + "refs": false, "evidence": false, "hostnames": true, "impact": false, @@ -208,9 +208,9 @@ angular.module("faradayApp") $scope.columnsWidths = { "name": "120", "service": "110", - "hostnames": "100", + "hostnames": "130", "target": "100", - "desc": "300", + "desc": "200", "resolution": "170", "data": "170", "easeofresolution": "140", @@ -261,8 +261,7 @@ angular.module("faradayApp") }; var defineColumns = function() { - $scope.gridOptions.columnDefs.push({ name: "selectAll", width: "20", enableColumnResizing: false,headerCellTemplate: "", pinnedLeft:true }); - $scope.gridOptions.columnDefs.push({ name: "confirmVuln", width: "23", enableColumnResizing: false, headerCellTemplate: "
", cellTemplate: "scripts/statusReport/partials/ui-grid/confirmbutton.html" }); + $scope.gridOptions.columnDefs.push({ name: "confirmVuln", width: "23", enableColumnResizing: false, headerCellTemplate: "", cellTemplate: "scripts/statusReport/partials/ui-grid/confirmbutton.html" }); function getColumnSort(columnName){ if($cookies.get("SRsortColumn") === columnName){ @@ -278,7 +277,7 @@ angular.module("faradayApp") var header = '
'+ '
{{ col.displayName CUSTOM_FILTERS }}'+ ' '+ - ' '+ + ' '+ ' '+ '  '+ '
'+ @@ -307,15 +306,6 @@ angular.module("faradayApp") sortingAlgorithm: compareSeverities, enableColumnResizing: false }); - $scope.gridOptions.columnDefs.push({ name : 'date', - displayName : "date", - cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/datecolumn.html', - headerCellTemplate: header, - width: '80', - enableColumnResizing: false, - sort: getColumnSort('date'), - visible: $scope.columns["date"] - }); $scope.gridOptions.columnDefs.push({ name : 'name', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/namecolumn.html', headerCellTemplate: header, @@ -375,13 +365,6 @@ angular.module("faradayApp") visible: $scope.columns["easeofresolution"], width: $scope.columnsWidths['easeofresolution'], }); - $scope.gridOptions.columnDefs.push({ name : 'status', - cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/statuscolumn.html', - headerCellTemplate: header, - width: $scope.columnsWidths['status'], - sort: getColumnSort('status'), - visible: $scope.columns["status"] - }); $scope.gridOptions.columnDefs.push({ name : 'website', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, @@ -403,6 +386,22 @@ angular.module("faradayApp") visible: $scope.columns["request"], width: $scope.columnsWidths['request'], }); + $scope.gridOptions.columnDefs.push({ name : 'date', + displayName : "date", + cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/datecolumn.html', + headerCellTemplate: header, + width: '80', + enableColumnResizing: false, + sort: getColumnSort('date'), + visible: $scope.columns["date"] + }); + $scope.gridOptions.columnDefs.push({ name : 'status', + cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/statuscolumn.html', + headerCellTemplate: header, + width: $scope.columnsWidths['status'], + sort: getColumnSort('status'), + visible: $scope.columns["status"] + }); $scope.gridOptions.columnDefs.push({ name : 'refs', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/refscolumn.html', headerCellTemplate: header, diff --git a/server/www/scripts/statusReport/partials/statusReport.html b/server/www/scripts/statusReport/partials/statusReport.html index 9138d004fb8..f3eae20e93e 100644 --- a/server/www/scripts/statusReport/partials/statusReport.html +++ b/server/www/scripts/statusReport/partials/statusReport.html @@ -2,60 +2,24 @@ -
+
- -
- - - -
- - -
-
- - -
-
- - - -
- -
-
-
- - -
+
+ -
- -
- -
-
-
-
-
-
- - - - - - -
-
- +
+
+
+ + + + +
+
+ + +
- -
+
+
+ +
Total diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/datecolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/datecolumn.html index 1a5ab831bca..429862c4e54 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/datecolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/datecolumn.html @@ -1,5 +1,5 @@
-
{{row.entity.metadata.create_time | date:"MM/dd/yyyy"}} +
{{row.entity.metadata.create_time | amTimeAgo}}
{{row.entity.metadata.create_time | amUtc | amDateFormat:'MM/DD/YYYY'}}
diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/idcolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/idcolumn.html index 5b975a4c1b3..06b45b4051b 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/idcolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/idcolumn.html @@ -1,5 +1,4 @@ -
-
{{COL_FIELD CUSTOM_FILTERS}} -
+
+ {{COL_FIELD CUSTOM_FILTERS}}
diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html index 193d29028a5..7fc5bc2a9d1 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html @@ -1 +1,11 @@ -
{{COL_FIELD | uppercase}}
\ No newline at end of file + +
+ {{COL_FIELD | uppercase}} +
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html index 654f2f50783..3125f48468c 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html @@ -1 +1,50 @@ -
{{COL_FIELD | uppercase}}
\ No newline at end of file + + \ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html index f6f79e1fc24..d502ddb0b5a 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html +++ b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html @@ -1,3 +1,5 @@
- + + +
\ No newline at end of file diff --git a/server/www/scripts/statusReport/styles/status.css b/server/www/scripts/statusReport/styles/status.css new file mode 100644 index 00000000000..66866b5e694 --- /dev/null +++ b/server/www/scripts/statusReport/styles/status.css @@ -0,0 +1,158 @@ +.status-report-grid .ui-grid-header{ + border-top: solid #E5E5E5 6px; + border-bottom: 2px solid #d8d8d8; + background: none; + color: #E9E9E9; + padding: 0px; + height: 45px; +} + +.status-report-grid .ui-grid-header .ui-grid-cell-contents { + color: #7c7c7c; + padding: 9px 5px 9px 5px !important; + text-align: center; +} + +.status-report-grid .ui-grid-cell-contents { + text-align: center; +} + +.status-report-grid .ui-grid-header-cell{ + border-right: solid #E9E9E9 1px; + text-align: center; +} + +.status-report-grid .ui-grid-header-cell i, .status-report-grid .grid_id{ + + padding: 9px 5px 9px 5px !important; +} + +.status-report-grid .ui-grid-row { + height: 45px; +} + +.status-report-grid .grid_id .ui-grid-cell-contents{ + position: relative; + top: 3px; +} + +.status-report-grid .ui-grid-row-selected .ui-grid-cell{ + background-color: #007bff !important; +} +.control-edit .edit-icon, .download-wrapper .download-icon { + height: 20px; + color: #000; + position: relative; + bottom: 2px; +} + +.status-report-grid .ui-grid-header-cell{ + text-align: center; +} + +.download-wrapper { + bottom: 5px; +} + +.status-report-grid .ui-grid-header-cell .glyphicon-remove{ + color: #ccc; + font-size: 8px; + font-weight: lighter; + top: 0px; + left: 4px; +} + +.search-wrapper .form-group{ + margin: 0px; +} + +.status-report-grid .ui-grid-contents-wrapper{ + clear: both; +} + +.status-report-grid .ui-grid-cell { + border: none ; +} + +.button-control { + display:flex; + flex-direction: row; + padding-right: 20px; + padding-left: 0px; + padding-bottom: 17px; + justify-content: space-between; + align-items: center; +} + +#reports-main { + padding: 40px 0 0 24px; +} + +#reports-main .reports{ + padding: 0px; +} + +.search-wrapper { + width: 385px; + padding: 0px; + margin-right: 16px; + margin-left: 16px; +} + +.search-wrapper input{ + height: 40px !important; + font-size: 14px; +} + +.search-wrapper .input-group input, .search-wrapper .input-group span { + border: none; + border-radius: 0px; + background-color: #FFFFFF +} + +.control-wrapper { + font-size: 14px; +} + +.space-wrapper{ + flex:1 +} + +.control-wrapper button{ + border-radius: 0px; +} + +.control-wrapper > button{ + border: none; + background: none; + width: 47px; + height: 33px; +} + +.control-edit { + padding-right: 0px; +} + +.control-edit .edit { + width: 30px; +} + +.control-edit .dropdown-toggle { + padding-right: 0px; + padding-left: 0px; + width: 10px; +} + +button.btn-new { + width:95px; + height:33px; + background-color: #5cb85c; +} + +.control-wrap-right { + justify-self: flex-end; +} + +.control-new{ + margin-right: 16px; +} \ No newline at end of file diff --git a/server/www/scripts/vulndb/partials/vulndb.html b/server/www/scripts/vulndb/partials/vulndb.html index 3821a4c4a0a..b58ef5dc8a7 100644 --- a/server/www/scripts/vulndb/partials/vulndb.html +++ b/server/www/scripts/vulndb/partials/vulndb.html @@ -4,10 +4,8 @@
+
-
-

Vulnerability Templates

-
From 7fee86e5b873087507b7cc2e8c8ca79ce2bb07b9 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Tue, 22 May 2018 17:58:06 -0300 Subject: [PATCH 1173/1506] fixes to status report after first feedback --- server/www/estilos.css | 6 +++ server/www/header.css | 5 ++- .../statusReport/controllers/statusReport.js | 7 +-- .../statusReport/partials/statusReport.html | 19 ++++---- .../ui-grid/columns/statuscolumn.html | 16 +++---- .../partials/ui-grid/confirmbutton.html | 2 +- .../scripts/statusReport/styles/status.css | 45 +++++++++++++++---- 7 files changed, 66 insertions(+), 34 deletions(-) diff --git a/server/www/estilos.css b/server/www/estilos.css index e126ec7f255..5a006d6b869 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -112,6 +112,7 @@ header.head { display: block; padding: 8px 0; color: #fff; + opacity: .4; text-align: center; transition: all .3s ease; -ms-transition: all .3s ease; @@ -120,12 +121,17 @@ header.head { } aside nav a:hover { background-color: #d9d9d9; + opacity: 1; } aside nav a.active { background-color: #d9d9d9; } + .left-nav ul{ + margin-top: 10px; + } + .left-nav { width: 64px; z-index: 1; diff --git a/server/www/header.css b/server/www/header.css index caeadb75d05..2e1d14e8f3b 100644 --- a/server/www/header.css +++ b/server/www/header.css @@ -26,7 +26,7 @@ } .faraday-page-header .subtitle { - font-size: 14px; + font-size: 12px; font-weight: 300; color: #a3a3a3; float: left; @@ -90,7 +90,7 @@ } .controls-wrapper button { - border-left: solid 1px #a3a3a3; + border-left: solid 1px #dddddd; border-radius: 0px; width:55px; } @@ -117,6 +117,7 @@ .user-menu-wrapper { align-self: flex-end; + margin-right: 30px; } .flex-spacer { diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index e66f8a43a5a..2621c15edc1 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -218,7 +218,7 @@ angular.module("faradayApp") "website": "90", "path": "90", "request": "90", - "refs": "120", + "refs": "20", "_attachments": "100", "impact": "90", "method": "90", @@ -261,7 +261,7 @@ angular.module("faradayApp") }; var defineColumns = function() { - $scope.gridOptions.columnDefs.push({ name: "confirmVuln", width: "23", enableColumnResizing: false, headerCellTemplate: "", cellTemplate: "scripts/statusReport/partials/ui-grid/confirmbutton.html" }); + $scope.gridOptions.columnDefs.push({ name: "confirmVuln", width: "45", enableColumnResizing: false, headerCellTemplate: "", cellTemplate: "scripts/statusReport/partials/ui-grid/confirmbutton.html" }); function getColumnSort(columnName){ if($cookies.get("SRsortColumn") === columnName){ @@ -291,7 +291,6 @@ angular.module("faradayApp") displayName : "_id", cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/idcolumn.html', headerCellTemplate: header, - width: '50', sort: getColumnSort('_id'), visible: $scope.columns["_id"] }); @@ -300,7 +299,6 @@ angular.module("faradayApp") headerCellTemplate: header, displayName : "sev", type: 'string', - width: '70', visible: $scope.columns["severity"], sort: getColumnSort('severity'), sortingAlgorithm: compareSeverities, @@ -390,7 +388,6 @@ angular.module("faradayApp") displayName : "date", cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/datecolumn.html', headerCellTemplate: header, - width: '80', enableColumnResizing: false, sort: getColumnSort('date'), visible: $scope.columns["date"] diff --git a/server/www/scripts/statusReport/partials/statusReport.html b/server/www/scripts/statusReport/partials/statusReport.html index f3eae20e93e..6772a763820 100644 --- a/server/www/scripts/statusReport/partials/statusReport.html +++ b/server/www/scripts/statusReport/partials/statusReport.html @@ -12,11 +12,6 @@ New
-
- -
+
+ +
@@ -66,21 +66,20 @@ - - +
-
diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html index 3125f48468c..d72b5d28372 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html @@ -1,22 +1,22 @@
-
+ -
+ -
+ -
+
{{COL_FIELD | uppercase}} @@ -26,22 +26,22 @@
-
+ -
+ -
+ -
+
{{COL_FIELD | uppercase}} diff --git a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html index d502ddb0b5a..bf5807f9e4c 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html +++ b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html @@ -1,5 +1,5 @@
- +
\ No newline at end of file diff --git a/server/www/scripts/statusReport/styles/status.css b/server/www/scripts/statusReport/styles/status.css index 66866b5e694..301a3fca4e7 100644 --- a/server/www/scripts/statusReport/styles/status.css +++ b/server/www/scripts/statusReport/styles/status.css @@ -9,8 +9,8 @@ .status-report-grid .ui-grid-header .ui-grid-cell-contents { color: #7c7c7c; - padding: 9px 5px 9px 5px !important; - text-align: center; + padding: 9px 9px 9px 5px !important; + text-align: left; } .status-report-grid .ui-grid-cell-contents { @@ -23,8 +23,7 @@ } .status-report-grid .ui-grid-header-cell i, .status-report-grid .grid_id{ - - padding: 9px 5px 9px 5px !important; + padding: 11px 9px 9px 5px !important } .status-report-grid .ui-grid-row { @@ -37,15 +36,33 @@ } .status-report-grid .ui-grid-row-selected .ui-grid-cell{ - background-color: #007bff !important; + background-color: #8DA2B7 !important; + color: #FFFFFF; +} + +.status-report-grid .ui-grid-row-selected a{ + color: #FFFFFF; +} + +.status-report-grid .no-background{ + background: none; +} + +.control-edit { + width:44px; } -.control-edit .edit-icon, .download-wrapper .download-icon { + +.control-edit .edit-icon, .download-wrapper .download-icon, .control-wrapper .delete-icon, .search-wrapper .search-icon { height: 20px; color: #000; position: relative; bottom: 2px; } +.status-report-grid .confirm-icon { + width: 40%; +} + .status-report-grid .ui-grid-header-cell{ text-align: center; } @@ -85,7 +102,7 @@ } #reports-main { - padding: 40px 0 0 24px; + padding: 0px 0 0 24px; } #reports-main .reports{ @@ -102,6 +119,7 @@ .search-wrapper input{ height: 40px !important; font-size: 14px; + box-shadow: none; } .search-wrapper .input-group input, .search-wrapper .input-group span { @@ -114,8 +132,13 @@ font-size: 14px; } +.filter-wrapper .confirm-button{ + position: relative; + bottom: 1px; +} + .space-wrapper{ - flex:1 + flex:1; } .control-wrapper button{ @@ -131,6 +154,12 @@ .control-edit { padding-right: 0px; + position: relative; +} + +.control-edit .dropdown-menu-right{ + left: 0px !important; + width: 245px; } .control-edit .edit { From 15009965cf78f300315bfa668df4dca88cd89538 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Thu, 24 May 2018 12:39:30 -0300 Subject: [PATCH 1174/1506] 4721 - Adding vulnerabilities by status donut visualization --- server/www/dashboard-v3.css | 11 ++++ server/www/index.html | 1 + .../www/scripts/commons/providers/server.js | 7 +++ .../controllers/vulnsByStatusCtrl.js | 34 ++++++++++ .../dashboard/partials/activityFeed.html | 63 +++++++++---------- .../scripts/dashboard/partials/dashboard.html | 7 +-- .../dashboard/partials/last-vulns.html | 13 ++-- .../dashboard/partials/vulnsByStatus.html | 18 ++++++ .../partials/workspace-progress.html | 6 +- .../scripts/dashboard/providers/dashboard.js | 17 ++++- 10 files changed, 129 insertions(+), 48 deletions(-) create mode 100644 server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js create mode 100644 server/www/scripts/dashboard/partials/vulnsByStatus.html diff --git a/server/www/dashboard-v3.css b/server/www/dashboard-v3.css index c3e4392ddfd..687a4115589 100644 --- a/server/www/dashboard-v3.css +++ b/server/www/dashboard-v3.css @@ -52,6 +52,11 @@ padding-right: 10px; } +.ph-xl { + padding-left: 30px; + padding-right: 30px; +} + .ph-xxl { padding-left: 45px; padding-right: 45px; @@ -103,3 +108,9 @@ .fg-light-gray { color: #a1a1a1; } + +.seccion.dashboard article header h2{ + margin: 0px; + padding-left: 20px; + padding-right: 20px; +} diff --git a/server/www/index.html b/server/www/index.html index 6e9f780eda7..dc2fc2f898d 100644 --- a/server/www/index.html +++ b/server/www/index.html @@ -151,6 +151,7 @@ + diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index 2801b1858b1..144a4abf0c2 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -344,6 +344,13 @@ angular.module("faradayApp") return get(url, payload) } + ServerAPI.getVulnsGroupedBy = function(wsName, groupBy) { + var url = createGetUrl(wsName, 'vulns') + 'count/'; + var payload = {'group_by': groupBy} + + return get(url, payload) + } + ServerAPI.createHost = function(wsName, host) { return modHost(createObject, wsName, host); } diff --git a/server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js b/server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js new file mode 100644 index 00000000000..72c95e3e540 --- /dev/null +++ b/server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js @@ -0,0 +1,34 @@ +// Faraday Penetration Test IDE +// Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +// See the file 'doc/LICENSE' for the license information + +angular.module('faradayApp') + .controller('vulnsByStatusCtrl', + ['$scope', '$routeParams', 'dashboardSrv', + function($scope, $routeParams, dashboardSrv) { + + $scope.workspace = null; + + init = function() { + if($routeParams.wsId != undefined) { + $scope.workspace = $routeParams.wsId; + + dashboardSrv.getVulnerabilitiesGroupedBy($scope.workspace, 'status') + .then(function(vulnsByStatus) { + $scope.data = {key: [], value: [], colors: [], options: {maintainAspectRatio: false, animateRotate: true}}; + $scope.loaded = true; + vulnsByStatus.forEach(function(vuln, index) { + $scope.data.value.push(vuln.count); + $scope.data.key.push(vuln.status); + $scope.data.colors.push(dashboardSrv.vulnColors[index]); + }); + + $scope.loaded = true; + }); + } + }; + + dashboardSrv.registerCallback(init); + + init(); + }]); diff --git a/server/www/scripts/dashboard/partials/activityFeed.html b/server/www/scripts/dashboard/partials/activityFeed.html index 94196379865..6168d8c9ec1 100644 --- a/server/www/scripts/dashboard/partials/activityFeed.html +++ b/server/www/scripts/dashboard/partials/activityFeed.html @@ -1,11 +1,10 @@
- -
-

Activity Feed - -

-
- +
+

Activity Feed + +

+
+

No activities found yet.

- -
SeverityTargetNameWebDateSeverityTargetNameWebDate
- {{cmd.user || Anonymous}} - ran {{cmd.command}} - imported {{cmd.tool == 'unknown' ? cmd.command : cmd.tool}} report + {{cmd.user || Anonymous}} + ran {{cmd.command}} + imported {{cmd.tool == 'unknown' ? cmd.command : cmd.tool}} report and found nothing : @@ -33,7 +33,7 @@

Activity Feed & {{cmd.vulnerabilities_count}} {{cmd.vulnerabilities_count == 1 ? 'vulnerability' : 'vulnerabilities'}} - {{cmd.criticalIssue}} {{cmd.criticalIssue == 1 ? 'is' : 'are'}} rated as Critical. - +

- - - - - -
- - - {{cmd.user || Anonymous}} - ran {{cmd.command}} - imported {{cmd.tool == 'unknown' ? cmd.command : cmd.tool}} report - and found - nothing - : - {{cmd.hosts_count}} {{cmd.hosts_count == 1 ? 'host' : 'hosts'}} - , - & - {{cmd.services_count}} {{cmd.services_count == 1 ? 'service' : 'services'}} - & - {{cmd.vulnerabilities_count}} {{cmd.vulnerabilities_count == 1 ? 'vulnerability' : 'vulnerabilities'}} - - {{cmd.criticalIssue}} {{cmd.criticalIssue == 1 ? 'is' : 'are'}} rated as Critical. - -
+ + + + + + +
+ + + {{cmd.user || Anonymous}} + ran {{cmd.command}} + imported {{cmd.tool == 'unknown' ? cmd.command : cmd.tool}} report + and found + nothing + : + {{cmd.hosts_count}} {{cmd.hosts_count == 1 ? 'host' : 'hosts'}} + , + & + {{cmd.services_count}} {{cmd.services_count == 1 ? 'service' : 'services'}} + & + {{cmd.vulnerabilities_count}} {{cmd.vulnerabilities_count == 1 ? 'vulnerability' : 'vulnerabilities'}} + - {{cmd.criticalIssue}} {{cmd.criticalIssue == 1 ? 'is' : 'are'}} rated as Critical. + +
+
diff --git a/server/www/scripts/dashboard/partials/dashboard.html b/server/www/scripts/dashboard/partials/dashboard.html index 8ba9a14c848..eb245935ad4 100644 --- a/server/www/scripts/dashboard/partials/dashboard.html +++ b/server/www/scripts/dashboard/partials/dashboard.html @@ -16,7 +16,7 @@
-
+
@@ -45,11 +45,6 @@
-
-
-
-
-
diff --git a/server/www/scripts/dashboard/partials/last-vulns.html b/server/www/scripts/dashboard/partials/last-vulns.html index 1d2508b37a3..dc3586f4aed 100644 --- a/server/www/scripts/dashboard/partials/last-vulns.html +++ b/server/www/scripts/dashboard/partials/last-vulns.html @@ -1,9 +1,11 @@
-
-

Last Vulnerabilities - -

-
+
+

Last Vulnerabilities + +

+
+ +
diff --git a/server/www/scripts/dashboard/partials/vulnsByStatus.html b/server/www/scripts/dashboard/partials/vulnsByStatus.html new file mode 100644 index 00000000000..f86e97072c3 --- /dev/null +++ b/server/www/scripts/dashboard/partials/vulnsByStatus.html @@ -0,0 +1,18 @@ + + + + +
+ +
diff --git a/server/www/scripts/dashboard/partials/workspace-progress.html b/server/www/scripts/dashboard/partials/workspace-progress.html index 9301d513930..f8de6b7b084 100644 --- a/server/www/scripts/dashboard/partials/workspace-progress.html +++ b/server/www/scripts/dashboard/partials/workspace-progress.html @@ -5,11 +5,11 @@

-
-
+
+
{{wsProgress}}%
-
+
Start date: {{wsStart | amUtc | amDateFormat:'MM/DD/YYYY'}}
diff --git a/server/www/scripts/dashboard/providers/dashboard.js b/server/www/scripts/dashboard/providers/dashboard.js index 228498b9deb..ff1e6020a3f 100644 --- a/server/www/scripts/dashboard/providers/dashboard.js +++ b/server/www/scripts/dashboard/providers/dashboard.js @@ -208,9 +208,22 @@ angular.module('faradayApp') return deferred.promise; }; + dashboardSrv.getVulnerabilitiesGroupedBy = function(ws, groupBy) { + var deferred = $q.defer(); + + ServerAPI.getVulnsGroupedBy(ws, groupBy) + .then(function(res) { + deferred.resolve(res.data.groups); + }, function() { + deferred.reject("Unable to get Vulnerabilities"); + }); + + return deferred.promise; + }; + dashboardSrv.getObjectsCount = function(ws) { var deferred = $q.defer(); - // Confirmed empty = All vulns + // Confirmed empty = All vulns var confirmed = undefined; if (dashboardSrv.props['confirmed']) { @@ -290,7 +303,7 @@ angular.module('faradayApp') dashboardSrv.getHostsCountByCommandId = function(ws, command_id) { var deferred = $q.defer(); - + ServerAPI.getHosts(ws, {"command_id": command_id }) .then(function(res) { deferred.resolve(res.data); From 1159fabb3b4ac5c140d8127a1e441b520f964f48 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Thu, 24 May 2018 16:53:22 -0300 Subject: [PATCH 1175/1506] fixes --- .../statusReport/controllers/statusReport.js | 25 +------------------ .../partials/ui-grid/confirmbutton.html | 4 +-- .../scripts/statusReport/styles/status.css | 11 ++++++-- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index 2621c15edc1..95009806ae3 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -302,19 +302,16 @@ angular.module("faradayApp") visible: $scope.columns["severity"], sort: getColumnSort('severity'), sortingAlgorithm: compareSeverities, - enableColumnResizing: false }); $scope.gridOptions.columnDefs.push({ name : 'name', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/namecolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['name'], sort: getColumnSort('name'), visible: $scope.columns["name"] }); $scope.gridOptions.columnDefs.push({ name : 'service', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/servicecolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['service'], visible: $scope.columns["service"], sort: getColumnSort('service'), }); @@ -324,21 +321,18 @@ angular.module("faradayApp") minWidth: '100', maxWidth: '200', enableSorting: false, - width: $scope.columnsWidths['hostname'], sort: getColumnSort('hostnames'), visible: $scope.columns["hostnames"] }); $scope.gridOptions.columnDefs.push({ name : 'target', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/targetcolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['target'], sort: getColumnSort('target'), visible: $scope.columns["target"] }); $scope.gridOptions.columnDefs.push({ name : 'desc', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/desccolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['desc'], sort: getColumnSort('desc'), visible: $scope.columns["desc"] }); @@ -347,55 +341,48 @@ angular.module("faradayApp") headerCellTemplate: header, sort: getColumnSort('resolution'), visible: $scope.columns["resolution"], - width: $scope.columnsWidths['resolution'], }); $scope.gridOptions.columnDefs.push({ name : 'data', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html', headerCellTemplate: header, sort: getColumnSort('data'), visible: $scope.columns["data"], - width: $scope.columnsWidths['data'], }); $scope.gridOptions.columnDefs.push({ name : 'easeofresolution', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, + minWidth: '150', sort: getColumnSort('easeofresolution'), visible: $scope.columns["easeofresolution"], - width: $scope.columnsWidths['easeofresolution'], }); $scope.gridOptions.columnDefs.push({ name : 'website', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, sort: getColumnSort('website'), visible: $scope.columns["website"], - width: $scope.columnsWidths['website'], }); $scope.gridOptions.columnDefs.push({ name : 'path', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, sort: getColumnSort('path'), visible: $scope.columns["path"], - width: $scope.columnsWidths['path'], }); $scope.gridOptions.columnDefs.push({ name : 'request', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html', headerCellTemplate: header, sort: getColumnSort('request'), visible: $scope.columns["request"], - width: $scope.columnsWidths['request'], }); $scope.gridOptions.columnDefs.push({ name : 'date', displayName : "date", cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/datecolumn.html', headerCellTemplate: header, - enableColumnResizing: false, sort: getColumnSort('date'), visible: $scope.columns["date"] }); $scope.gridOptions.columnDefs.push({ name : 'status', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/statuscolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['status'], sort: getColumnSort('status'), visible: $scope.columns["status"] }); @@ -405,7 +392,6 @@ angular.module("faradayApp") sort: getColumnSort('refs'), visible: $scope.columns["refs"], enableSorting: false, - width: $scope.columnsWidths['refs'], }); $scope.gridOptions.columnDefs.push({ name : '_attachments', displayName: "evidence", @@ -413,7 +399,6 @@ angular.module("faradayApp") headerCellTemplate: header, sort: getColumnSort('_attachments'), visible: $scope.columns["evidence"], - width: $scope.columnsWidths['_attachments'], }); $scope.gridOptions.columnDefs.push({ name : 'impact', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/impactcolumn.html', @@ -421,35 +406,30 @@ angular.module("faradayApp") sort: getColumnSort('impact'), enableSorting: false, visible: $scope.columns["impact"], - width: $scope.columnsWidths['impact'], }); $scope.gridOptions.columnDefs.push({ name : 'method', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, sort: getColumnSort('method'), visible: $scope.columns["method"], - width: $scope.columnsWidths['method'], }); $scope.gridOptions.columnDefs.push({ name : 'params', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, sort: getColumnSort('params'), visible: $scope.columns["params"], - width: $scope.columnsWidths['params'], }); $scope.gridOptions.columnDefs.push({ name : 'pname', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, sort: getColumnSort('pname'), visible: $scope.columns["pname"], - width: $scope.columnsWidths['pname'], }); $scope.gridOptions.columnDefs.push({ name : 'query', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/defaultcolumn.html', headerCellTemplate: header, sort: getColumnSort('query'), visible: $scope.columns["query"], - width: $scope.columnsWidths['query'], }); $scope.gridOptions.columnDefs.push({ name : 'response', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html', @@ -461,7 +441,6 @@ angular.module("faradayApp") $scope.gridOptions.columnDefs.push({ name : 'web', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/webcolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['web'], sort: getColumnSort('web'), visible: $scope.columns["web"] }); @@ -469,7 +448,6 @@ angular.module("faradayApp") displayName : "creator", cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/creatorcolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['metadata.creator'], sort: getColumnSort('metadata.creator'), visible: $scope.columns["creator"] }); @@ -481,7 +459,6 @@ angular.module("faradayApp") // displayName : "policy violations", cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/policyviolationscolumn.html', headerCellTemplate: header, - width: $scope.columnsWidths['policyviolations'], sort: getColumnSort('policyviolations'), visible: $scope.columns["policyviolations"], enableSorting: false, diff --git a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html index bf5807f9e4c..b65ef0f538b 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html +++ b/server/www/scripts/statusReport/partials/ui-grid/confirmbutton.html @@ -1,5 +1,5 @@
- - + +
\ No newline at end of file diff --git a/server/www/scripts/statusReport/styles/status.css b/server/www/scripts/statusReport/styles/status.css index 301a3fca4e7..0c48d179537 100644 --- a/server/www/scripts/statusReport/styles/status.css +++ b/server/www/scripts/statusReport/styles/status.css @@ -9,7 +9,7 @@ .status-report-grid .ui-grid-header .ui-grid-cell-contents { color: #7c7c7c; - padding: 9px 9px 9px 5px !important; + padding: 9px 3px 9px 5px !important; text-align: left; } @@ -132,6 +132,8 @@ font-size: 14px; } +.control-wrapper button:focus {outline:0;} + .filter-wrapper .confirm-button{ position: relative; bottom: 1px; @@ -182,6 +184,11 @@ button.btn-new { justify-self: flex-end; } +.control-wrap-right button{ + font-size: 12px; +} + .control-new{ margin-right: 16px; -} \ No newline at end of file +} + From e366533a4ee5cded083c1e5105dc28adbfad4f01 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Fri, 25 May 2018 10:09:09 -0300 Subject: [PATCH 1176/1506] 4725 - Fix login input red box shadow in Firefox --- server/www/styles/material-input.css | 1 + 1 file changed, 1 insertion(+) diff --git a/server/www/styles/material-input.css b/server/www/styles/material-input.css index 80b6e78d8f1..d5079486f14 100644 --- a/server/www/styles/material-input.css +++ b/server/www/styles/material-input.css @@ -35,6 +35,7 @@ padding: 0; margin: 0; color: #a1a1a1; + box-shadow: none; /* Firefox defaults to red so we clean it */ } .form-input textarea { min-height: 30px; From 454622c0f8a89732796b3d32731d2167088b51a2 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Fri, 25 May 2018 11:08:51 -0300 Subject: [PATCH 1177/1506] 4725 - Fix left sidebar displaying login background on welcome screen --- server/www/estilos-v3.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/www/estilos-v3.css b/server/www/estilos-v3.css index e7c152cac84..04aefb252b3 100644 --- a/server/www/estilos-v3.css +++ b/server/www/estilos-v3.css @@ -1,10 +1,14 @@ -body { +#login-main { background-image: url(images/fondofaraday4k.jpg) !important; + width: 100%; + height: 100%; + display: inline-block; + position: absolute; } #login-container { width: 410px; - margin-top: 180px; + margin-top: 160px; background-color: #FFF; margin-left: auto; margin-right: auto; From 01ff6a065505a1ac14936bacd3100373184a3d9c Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Fri, 25 May 2018 12:19:31 -0300 Subject: [PATCH 1178/1506] 4721 - Update vulnerabilities by status donut colors and add top 5 hosts --- server/www/dashboard-v3.css | 6 +++++- .../dashboard/controllers/vulnsByStatusCtrl.js | 13 ++++++++++--- server/www/scripts/dashboard/partials/compound.html | 6 +++--- .../www/scripts/dashboard/partials/dashboard.html | 7 ++++++- server/www/scripts/dashboard/partials/services.html | 2 +- .../www/scripts/dashboard/partials/summarized.html | 2 +- .../{vulnsByStatus.html => vulns-by-status.html} | 5 ++--- .../dashboard/partials/workspace-progress.html | 2 +- 8 files changed, 29 insertions(+), 14 deletions(-) rename server/www/scripts/dashboard/partials/{vulnsByStatus.html => vulns-by-status.html} (71%) diff --git a/server/www/dashboard-v3.css b/server/www/dashboard-v3.css index 687a4115589..f64bece4b45 100644 --- a/server/www/dashboard-v3.css +++ b/server/www/dashboard-v3.css @@ -6,6 +6,10 @@ border-right: 1px solid #e7e7e7; } +.small-widget-height { + height: 195px; +} + .dashboard-label-wrapper { text-align: left; margin-top: 10px; @@ -54,7 +58,7 @@ .ph-xl { padding-left: 30px; - padding-right: 30px; + padding-right: 30px; } .ph-xxl { diff --git a/server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js b/server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js index 72c95e3e540..01c4f126c24 100644 --- a/server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js +++ b/server/www/scripts/dashboard/controllers/vulnsByStatusCtrl.js @@ -17,10 +17,19 @@ angular.module('faradayApp') .then(function(vulnsByStatus) { $scope.data = {key: [], value: [], colors: [], options: {maintainAspectRatio: false, animateRotate: true}}; $scope.loaded = true; + + vulnerabilityColors = { + 'open': '#DF3936', + 'close': '#A1CE31', + 're-open': '#DFBF35', + 'risk-accept': '#2e97bd' + }; + vulnsByStatus.forEach(function(vuln, index) { $scope.data.value.push(vuln.count); $scope.data.key.push(vuln.status); - $scope.data.colors.push(dashboardSrv.vulnColors[index]); + + $scope.data.colors.push(vulnerabilityColors[vuln.status]); }); $scope.loaded = true; @@ -28,7 +37,5 @@ angular.module('faradayApp') } }; - dashboardSrv.registerCallback(init); - init(); }]); diff --git a/server/www/scripts/dashboard/partials/compound.html b/server/www/scripts/dashboard/partials/compound.html index 5664fb0cd90..b950f990b01 100644 --- a/server/www/scripts/dashboard/partials/compound.html +++ b/server/www/scripts/dashboard/partials/compound.html @@ -20,7 +20,7 @@

Hosts - + {{host.name}} @@ -33,7 +33,7 @@

Hosts -
+ diff --git a/server/www/scripts/dashboard/partials/dashboard.html b/server/www/scripts/dashboard/partials/dashboard.html index eb245935ad4..bbcdb7605ac 100644 --- a/server/www/scripts/dashboard/partials/dashboard.html +++ b/server/www/scripts/dashboard/partials/dashboard.html @@ -16,7 +16,7 @@
-
+

@@ -45,6 +45,11 @@
+
+
+
+
+
diff --git a/server/www/scripts/dashboard/partials/services.html b/server/www/scripts/dashboard/partials/services.html index 11543f208c7..269b16e6559 100644 --- a/server/www/scripts/dashboard/partials/services.html +++ b/server/www/scripts/dashboard/partials/services.html @@ -3,7 +3,7 @@
-
+

Services report diff --git a/server/www/scripts/dashboard/partials/summarized.html b/server/www/scripts/dashboard/partials/summarized.html index 61f0fc5b1e6..cfc9e5cd751 100644 --- a/server/www/scripts/dashboard/partials/summarized.html +++ b/server/www/scripts/dashboard/partials/summarized.html @@ -4,7 +4,7 @@
-
+

Workspace summarized report diff --git a/server/www/scripts/dashboard/partials/vulnsByStatus.html b/server/www/scripts/dashboard/partials/vulns-by-status.html similarity index 71% rename from server/www/scripts/dashboard/partials/vulnsByStatus.html rename to server/www/scripts/dashboard/partials/vulns-by-status.html index f86e97072c3..8f66396ba82 100644 --- a/server/www/scripts/dashboard/partials/vulnsByStatus.html +++ b/server/www/scripts/dashboard/partials/vulns-by-status.html @@ -3,11 +3,10 @@
-
+

- Vulnerabilities by status - + Vulnerabilities by status

diff --git a/server/www/scripts/dashboard/partials/workspace-progress.html b/server/www/scripts/dashboard/partials/workspace-progress.html index f8de6b7b084..6bbc9501544 100644 --- a/server/www/scripts/dashboard/partials/workspace-progress.html +++ b/server/www/scripts/dashboard/partials/workspace-progress.html @@ -1,4 +1,4 @@ -
+

Workspace progress From cd3e4b9cc5381d4924e4f9d490f3774ca7c670f3 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Mon, 28 May 2018 08:53:41 -0300 Subject: [PATCH 1179/1506] 4721 - Update dashboard hosts widget table margins --- .../scripts/dashboard/partials/compound.html | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/server/www/scripts/dashboard/partials/compound.html b/server/www/scripts/dashboard/partials/compound.html index b950f990b01..d97e0c7f147 100644 --- a/server/www/scripts/dashboard/partials/compound.html +++ b/server/www/scripts/dashboard/partials/compound.html @@ -1,4 +1,4 @@ -
+

Hosts @@ -11,28 +11,30 @@

Hosts

No hosts found yet.

- - - - - - - - - - - - - - - -
HostServicesOS
- {{host.name}} - - {{host.services}} - - -
+
+ + + + + + + + + + + + + + + +
HostServicesOS
+ {{host.name}} + + {{host.services}} + + +
+
diff --git a/server/www/scripts/dashboard/partials/dashboard.html b/server/www/scripts/dashboard/partials/dashboard.html index bbcdb7605ac..c6225102343 100644 --- a/server/www/scripts/dashboard/partials/dashboard.html +++ b/server/www/scripts/dashboard/partials/dashboard.html @@ -6,7 +6,7 @@
-
+
@@ -26,7 +26,7 @@
-
+
diff --git a/server/www/scripts/dashboard/partials/vulns-by-severity.html b/server/www/scripts/dashboard/partials/vulns-by-severity.html index cc4681f0dba..4c164501e0b 100644 --- a/server/www/scripts/dashboard/partials/vulns-by-severity.html +++ b/server/www/scripts/dashboard/partials/vulns-by-severity.html @@ -13,7 +13,7 @@

No vulnerabilities found yet.

-
+
{{count}}
diff --git a/server/www/scripts/dashboard/providers/dashboard.js b/server/www/scripts/dashboard/providers/dashboard.js index ff1e6020a3f..24615e15a59 100644 --- a/server/www/scripts/dashboard/providers/dashboard.js +++ b/server/www/scripts/dashboard/providers/dashboard.js @@ -45,10 +45,10 @@ angular.module('faradayApp') dashboardSrv.vulnColors = [ "#932EBE", // critical - "#DF3936", // high - "#DFBF35", // med - "#A1CE31", // low - "#428BCA", // info + "#e77273", // high + "#e7d174", // med + "#bddd72", // low + "#7aabd9", // info "#999999" // unclassified ]; From 1cb88a9b8481f547b1487d980f3b79d100daea3d Mon Sep 17 00:00:00 2001 From: montive Date: Tue, 29 May 2018 18:58:38 -0300 Subject: [PATCH 1198/1506] Arreglado bug de Postgresql --- server/commands/status_check.py | 118 ++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/server/commands/status_check.py b/server/commands/status_check.py index 825e50d7478..c516b261c9a 100644 --- a/server/commands/status_check.py +++ b/server/commands/status_check.py @@ -18,34 +18,31 @@ init() -def check_server_running(): +def check_postgres(): + tmp = os.popen("ps -Af").read() + if "postgresql" in tmp: + return True + else: + return False - pid = is_server_running() - return pid - -def check_open_ports(): - address = server.config.faraday_server.bind_address - port = int(server.config.faraday_server.port) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - result = sock.connect_ex((address,port)) - if result == 0: - return True - else: - return False - +def check_active_user(): + with app.app_context(): + try: + active_users = db.engine.execute('SELECT active FROM faraday_user') + for item in active_users: + if item[0] == True: + return True + except sqlalchemy.exc.OperationalError: + return 0 -def check_postgres(): - with app.app_context(): - try: - result = str(db.engine.execute("SELECT version()")) - return result - except sqlalchemy.exc.OperationalError: - return None - -def check_client(): +def check_server_running(): + pid = is_server_running() + return pid + +def check_client_running(): port_rest = CONF.getApiRestfulConInfoPort() try: @@ -58,7 +55,6 @@ def check_client(): def check_server_dependencies(): - installed_deps, missing_deps, conflict_deps = dependencies.check_dependencies( requirements_file='requirements_server.txt') @@ -73,7 +69,6 @@ def check_server_dependencies(): def check_client_dependencies(): - installed_deps, missing_deps, conflict_deps = dependencies.check_dependencies( requirements_file='requirements.txt') @@ -90,10 +85,7 @@ def check_client_dependencies(): return None, None - - def check_credentials(): - api_username = CONF.getAPIUsername() api_password = CONF.getAPIPassword() @@ -115,7 +107,6 @@ def check_credentials(): def check_storage_permission(): - home = os.path.expanduser("~") path = home+'/.faraday/storage/test' @@ -127,30 +118,42 @@ def check_storage_permission(): return None +def check_open_ports(): + address = server.config.faraday_server.bind_address + port = int(server.config.faraday_server.port) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex((address,port)) + if result == 0: + return True + else: + return False + + def full_status_check(): - #Prints the status of PostreSQL using check_postgres() - print('\n{white}Checking if postgreSQL is running...'.format(white=Fore.WHITE)) + #Checking PostgreSQL + print('\n{white}Checking if PostgreSQL is running...'.format(white=Fore.WHITE)) result = check_postgres() if result: print('[{green}+{white}] PostgreSQL is running'.\ format(green=Fore.GREEN, white=Fore.WHITE)) else: - print('[{red}-{white}] Could not connect to postgresql, please check if database is running'\ + print('[{red}-{white}] Could not connect to PostgreSQL, please check if database is running'\ .format(red=Fore.RED, white=Fore.WHITE)) return - print('\n{white}Checking if Faraday is running...'.format(white=Fore.WHITE)) - if check_client(): - print('[{green}+{white}] Faraday GTK is running'.\ - format(green=Fore.GREEN, white=Fore.WHITE)) + if check_active_user(): + print("[{green}+{white}] Active user exists".format(green=Fore.GREEN, white=Fore.WHITE)) + elif check_active_user() == 0: + print("[{red}-{white}] Faraday database non existant".format(red=Fore.RED, white=Fore.WHITE)) else: - print('[{yellow}-{white}] Faraday GTK is not running'\ - .format(yellow=Fore.YELLOW, white=Fore.WHITE)) + print("[{red}-{white}] Active user doesn't exists".format(red=Fore.RED, white=Fore.WHITE)) - #Prints Status of the server using check_server_running() + + #Checking status + print('\n{white}Checking if Faraday is running...'.format(white=Fore.WHITE)) pid = check_server_running() if pid is not None: print('[{green}+{white}] Faraday Server is Running. PID:{PID} \ @@ -159,47 +162,56 @@ def full_status_check(): print('[{red}-{white}] Faraday Server is not running {white} \ '.format(red=Fore.RED, white=Fore.WHITE)) + if check_client_running(): + print('[{green}+{white}] Faraday GTK is running'.\ + format(green=Fore.GREEN, white=Fore.WHITE)) + else: + print('[{yellow}-{white}] Faraday GTK is not running'\ + .format(yellow=Fore.YELLOW, white=Fore.WHITE)) + + #Checking dependencies print('\n{white}Checking Faraday dependencies...'.format(white=Fore.WHITE)) - status, server_dep = check_server_dependencies() if status == True: - print('[{red}-{white}] Some server dependencies are old. Update them with \"pip install -r requirements_server.txt -U\": (' + ','.join(server_dep) + ')') \ + print('[{red}-{white}] Some server\'s dependencies are old. Update them with \"pip install -r requirements_server.txt -U\": (' + ','.join(server_dep) + ')') \ .format(red=Fore.RED, white=Fore.WHITE) elif status == 0: - print('[{red}-{white}] Client dependencies not met. Install them with \"pip install -r requirements_server.txt -U\": (' + ','.join(server_dep) + ')')\ + print('[{red}-{white}] Server\'s dependencies not met. Install them with \"pip install -r requirements_server.txt -U\": (' + ','.join(server_dep) + ')')\ .format(red=Fore.RED, white=Fore.WHITE) else: - print('[{green}+{white}] Server dependencies met' \ + print('[{green}+{white}] Server\'s dependencies met' \ .format(green=Fore.GREEN, white=Fore.WHITE)) status, client_dep = check_client_dependencies() if status == True: - print('[{red}-{white}] Some client dependencies are old. Update them with \"pip install -r requirements.txt -U\": (' + ','.join(client_dep) + ')') \ + print('[{red}-{white}] Some client\'s dependencies are old. Update them with \"pip install -r requirements.txt -U\": (' + ','.join(client_dep) + ')') \ .format(red=Fore.RED, white=Fore.WHITE) elif status == 0: - print('[{red}-{white}] Client dependencies not met. Install them with \"pip install -r requirements.txt -U\": (' + ','.join(client_dep) + ')')\ + print('[{red}-{white}] Client\'s dependencies not met. Install them with \"pip install -r requirements.txt -U\": (' + ','.join(client_dep) + ')')\ .format(red=Fore.RED, white=Fore.WHITE) else: - print('[{green}+{white}] Client dependencies met'\ + print('[{green}+{white}] Client\'s dependencies met'\ .format(green=Fore.GREEN, white=Fore.WHITE)) - + + #Checking config print('\n{white}Checking Faraday config...{white}'.format(white=Fore.WHITE)) if pid and result: status_code = check_credentials() if status_code == 200: - print('[{green}+{white}] Credentials matched'.format(green=Fore.GREEN, white=Fore.WHITE)) + print('[{green}+{white}] User\'s credentials matched'.format(green=Fore.GREEN, white=Fore.WHITE)) elif status_code == 400: - print('[{red}-{white}] Error. Credentials does not match' \ + print('[{red}-{white}] Error. User\'s credentials do not match' \ .format(red=Fore.RED, white=Fore.WHITE)) else: - print('[{red}-{white}] Either Faraday Server not running or database not working'.format(red=Fore.RED, white=Fore.WHITE)) + print('[{red}-{white}] Failed checking user\'s credentials. Either Faraday Server is not running or database is not working'\ + .format(red=Fore.RED, white=Fore.WHITE)) if check_storage_permission(): print('[{green}+{white}] ~/.faraday/storage -> Permission accepted' \ @@ -209,8 +221,8 @@ def full_status_check(): .format(red=Fore.RED, white=Fore.WHITE)) if check_open_ports(): - print "[{green}+{white}]Port {PORT} in {ad} is open"\ + print "[{green}+{white}] Port {PORT} in {ad} is open"\ .format(PORT=server.config.faraday_server.port, green=Fore.GREEN,white=Fore.WHITE,ad=server.config.faraday_server.bind_address) else: - print "[{red}-{white}] in {ad} is not open"\ - .format(PORT=server.config.faraday_server.port,red=Fore.RED,white=Fore.WHITE,ad =server.config.faraday_server.bind_address) \ No newline at end of file + print "[{red}-{white}] Port {PORT} in {ad} is not open"\ + .format(PORT=server.config.faraday_server.port,red=Fore.RED,white=Fore.WHITE,ad =server.config.faraday_server.bind_address) From fc6656f233ba30a6773bb34604c77517edfd2f20 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Wed, 30 May 2018 00:36:26 -0300 Subject: [PATCH 1199/1506] Cambios en look and feel de la vista de hosts emulando la de status report --- server/www/scripts/hosts/partials/list.html | 289 ++++++++++-------- .../scripts/statusReport/styles/status.css | 22 ++ 2 files changed, 187 insertions(+), 124 deletions(-) diff --git a/server/www/scripts/hosts/partials/list.html b/server/www/scripts/hosts/partials/list.html index 4b587e2e232..331ac36c680 100644 --- a/server/www/scripts/hosts/partials/list.html +++ b/server/www/scripts/hosts/partials/list.html @@ -6,168 +6,209 @@
- -
- - - -
- - - - - New - -
-
-
-
-
-
- - - - - - -
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + +
- -
-
-

Add columns

-
    +
+ +
+
+
+
+ + +
- + +
+
- - - + + - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - diff --git a/server/www/scripts/statusReport/styles/status.css b/server/www/scripts/statusReport/styles/status.css index e71ef1dcf86..01834b6ba50 100644 --- a/server/www/scripts/statusReport/styles/status.css +++ b/server/www/scripts/statusReport/styles/status.css @@ -60,6 +60,28 @@ width:44px; } +.status-report-grid .ui-grid-row:last-child .ui-grid-cell{ + border:none; +} + +.checkbox-select{ + text-align: center !important; +} + +.hosts-list .ui-grid-header .ui-grid-cell-contents{ + padding: 8px 3px 11px 5px !important +} + +.hosts-list .ui-grid-header .ui-grid-cell-contents span{ + color: #7c7c7c; +} + +.hosts-list .ui-grid-header .hosts-list-checkall{ + position: relative; + top: 3px; + left: 7px; +} + .status-report-grid .ui-grid-pager-panel { background-color: #f1f1f1; } From d9b1bc2e22c7b66c487d27eab15c088e2494d538 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 30 May 2018 11:23:51 -0300 Subject: [PATCH 1200/1506] 4774 - Update workspace table styles --- server/www/index.html | 1 + .../www/scripts/workspaces/partials/list.html | 28 ++-- server/www/table-v3.css | 129 ++++++++++++++++++ 3 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 server/www/table-v3.css diff --git a/server/www/index.html b/server/www/index.html index 0f481605b6c..c4b56ad5ca1 100644 --- a/server/www/index.html +++ b/server/www/index.html @@ -33,6 +33,7 @@ + diff --git a/server/www/scripts/workspaces/partials/list.html b/server/www/scripts/workspaces/partials/list.html index cfa3ca7c679..fc95802af3c 100644 --- a/server/www/scripts/workspaces/partials/list.html +++ b/server/www/scripts/workspaces/partials/list.html @@ -41,30 +41,30 @@

-
- ID - +
+ + + ID + - IP - + + IP + - OS - + + OS + - Services - + + SERVICES + - Hostnames - + + GOS + - MAC - + + MAC + - Open Services - + + OPEN SERVICES + - Vulns - + + VULNS + - Credentials - + + CREDENTIALS + - Owned - + + OWNED + - Description - + + DESCRIPTION + - Creation time - + + CREATION TIME + - Last modified - + + LAST MODIFIED +
- {{host.id}} + +
+ +
- {{host.ip}} - + + - - - - + +
+ {{host.ip}} + +
- - {{summary}}
-
+
+ - - {{hostname}}
-
+
+
+ + {{summary}}
+
+
+
+
+ + {{hostname}}
+
+
+
+
+ {{host.mac}} +
+
+
+ +
- {{host.mac}} + +
+ +
+ {{host.credentials}} - - owned - not yet + +
+ + owned + not yet +
- {{host.description}} + +
+ {{host.description}} +
- + +
+ +
- + +
+ +
+
- - - - - - - + + + + + + + - - - - - - + + + + +
NameStart DateEnd DateVulnsHostsServices
NameStart DateEnd DateVulnsHostsServices
+ {{ws.name}} {{objects[ws.name]['total_vulns']}}{{objects[ws.name]['hosts']}}{{objects[ws.name]['total_vulns']}}{{objects[ws.name]['hosts']}}
diff --git a/server/www/table-v3.css b/server/www/table-v3.css new file mode 100644 index 00000000000..ec83c0c8287 --- /dev/null +++ b/server/www/table-v3.css @@ -0,0 +1,129 @@ +.table-v3 .ui-grid-header { + border-top: solid #E5E5E5 2px; + border-bottom: 2px solid #d8d8d8; + background: none; + color: #E9E9E9; + padding: 0px; + height: 45px; +} + +.table-v3 .ui-grid-header .ui-grid-cell-contents { + color: #7c7c7c; + padding: 9px 3px 9px 5px !important; + text-align: left; +} + +.table-v3 .ui-grid-cell-contents { + text-align: left; +} + +.table-v3 .ui-grid-cell-contents.confirm-toggle{ + text-align: center; +} + +.table-v3 .ui-grid-header-cell{ + border-right: solid #E9E9E9 1px; + text-align: center; +} + +.table-v3 .ui-grid-header-cell i, .table-v3 .grid_id{ + padding: 11px 9px 9px 5px !important +} + +.table-v3 .ui-grid-row { + height: 45px; +} + +.table-v3 .date-cell { + opacity: 0.5; +} + +.table-v3 .grid_id .ui-grid-cell-contents{ + position: relative; + top: 3px; +} + +.table-v3 .ui-grid-row-selected .ui-grid-cell{ + background-color: #8DA2B7 !important; + color: #FFFFFF; +} + +.table-v3 .ui-grid-row-selected a{ + color: #FFFFFF; +} + +.table-v3 .no-background{ + background: none; +} + +.table-v3 .ui-grid-row:last-child .ui-grid-cell{ + border:none; +} + +.table-v3 .ui-grid-pager-panel { + background-color: #f1f1f1; +} + +.table-v3 .status.opened { + background-color: rgba(223, 57, 54, .6) !important; + text-align: center; +} + +.table-v3 .status-wrapper { + height: 100%; +} + +.table-v3 .status.opened span { + color: #DF3936; +} + +.table-v3 .status.risk-accepted { + background-color: rgba(46, 151, 189, 0.6) !important; + text-align: center; +} + +.table-v3 .status.re-opened span { + color: rgba(223, 191, 53, 1); +} + +.table-v3 .status.re-opened { + background-color: rgba(223, 191, 53, 0.6) !important; + text-align: center; +} + +.table-v3 .status.risk-accepted span { + color: rgba(46, 151, 189, 1); +} + +.table-v3 .status.closed { + background-color: rgba(161, 206, 49, 0.6) !important; + text-align: center; +} + +.table-v3 .status.closed span { + color: rgba(161, 206, 49, 1); +} + +.table-v3 .confirm-icon { + width: 40%; +} + +.table-v3 .ui-grid-header-cell{ + text-align: center; +} + +.table-v3 .ui-grid-header-cell .glyphicon-remove{ + color: #ccc; + font-size: 8px; + font-weight: lighter; + top: 0px; + left: 4px; +} + +.table-v3 .ui-grid-contents-wrapper{ + clear: both; +} + +.table-v3 .ui-grid-cell { + border: none ; +} From f82ebc07cbb2baf9f45618681225a4da2a3ef836 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Wed, 30 May 2018 11:53:48 -0300 Subject: [PATCH 1201/1506] [FIX] URL join from + to urlparse.urljoin --- persistence/server/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/persistence/server/server.py b/persistence/server/server.py index a5af42794f1..60c41db728f 100644 --- a/persistence/server/server.py +++ b/persistence/server/server.py @@ -1503,7 +1503,7 @@ def server_info(): def login_user(uri, uname, upass): auth = {"email": uname, "password": upass} try: - resp = requests.post(uri + "/_api/login", json=auth) + resp = requests.post(urlparse.urljoin(uri, "/_api/login"), json=auth) if resp.status_code == 401: return None else: @@ -1516,7 +1516,7 @@ def login_user(uri, uname, upass): def is_authenticated(uri, cookies): try: - resp = requests.get(uri + "/_api/session", cookies=cookies, timeout=1) + resp = requests.get(urlparse.urljoin(uri, "/_api/session"), cookies=cookies, timeout=1) if resp.status_code != 403: user_info = resp.json() return bool(user_info.get('username', {})) @@ -1555,7 +1555,7 @@ def test_server_url(url_to_test): def get_user_info(): try: - resp = requests.get(_get_base_server_url() + "/_api/session", cookies=_conf().getDBSessionCookies(), timeout=1) + resp = requests.get(urlparse.urljoin(_get_base_server_url(), "/_api/session"), cookies=_conf().getDBSessionCookies(), timeout=1) if resp.status_code != 403: return resp.json() else: From afb74e2e54784781713026c66d00e037bdfc1259 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Wed, 30 May 2018 13:02:59 -0300 Subject: [PATCH 1202/1506] [REF] Change 'test_server_url' to 'check_server_url' as it is not a test function --- gui/gtk/application.py | 2 +- gui/gtk/dialogs.py | 4 ++-- gui/gtk/server.py | 4 ++-- .../_build/html/_modules/persistence/server/server.html | 2 +- persistence/server/docs/_build/html/_modules/server.html | 2 +- persistence/server/docs/_build/html/genindex.html | 2 +- persistence/server/docs/_build/html/server.html | 2 +- persistence/server/models.py | 4 ++-- persistence/server/server.py | 2 +- test_cases/test_persistence_server_server.py | 8 ++++---- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/gui/gtk/application.py b/gui/gtk/application.py index 2b0ed76f889..e969e638ca9 100644 --- a/gui/gtk/application.py +++ b/gui/gtk/application.py @@ -344,7 +344,7 @@ def connect_to_couch(self, server_url, parent=None): if parent is None: parent = self.window - if not self.serverIO.test_server_url(server_url): + if not self.serverIO.check_server_url(server_url): errorDialog(parent, "Could not connect to Faraday Server.", ("Are you sure it is running and that you can " "connect to it? \n Make sure your username and " diff --git a/gui/gtk/dialogs.py b/gui/gtk/dialogs.py index 5546408782e..a4979099898 100644 --- a/gui/gtk/dialogs.py +++ b/gui/gtk/dialogs.py @@ -14,7 +14,7 @@ from gi.repository import Gtk, GdkPixbuf, Gdk from config.configuration import getInstanceConfiguration -from persistence.server.server import is_authenticated, login_user, get_user_info, test_server_url +from persistence.server.server import is_authenticated, login_user, get_user_info, check_server_url from model import guiapi from decorators import scrollable @@ -76,7 +76,7 @@ def on_click_ok(self, button=None): """ repourl = self.ip_entry.get_text() - if not test_server_url(repourl): + if not check_server_url(repourl): errorDialog(self, "Could not connect to Faraday Server.", ("Are you sure it is running and that the URL is correct?")) return False diff --git a/gui/gtk/server.py b/gui/gtk/server.py index d9bf5e396c5..6fc35e50bf6 100644 --- a/gui/gtk/server.py +++ b/gui/gtk/server.py @@ -86,8 +86,8 @@ def server_info(self): return models.server_info() @safe_io_with_server(False) - def test_server_url(self, url): - return models.test_server_url(url) + def check_server_url(self, url): + return models.check_server_url(url) @safe_io_with_server(None) def get_changes_stream(self): diff --git a/persistence/server/docs/_build/html/_modules/persistence/server/server.html b/persistence/server/docs/_build/html/_modules/persistence/server/server.html index cfdd503e825..bdd5d69fd92 100644 --- a/persistence/server/docs/_build/html/_modules/persistence/server/server.html +++ b/persistence/server/docs/_build/html/_modules/persistence/server/server.html @@ -1589,7 +1589,7 @@

Source code for persistence.server.server

         is_server_up = False
     return is_server_up
-
[docs]def test_server_url(url_to_test): +
[docs]def check_server_url(url_to_test): """Return True if the url_to_test is indeed a valid Faraday Server URL. False otherwise. """ diff --git a/persistence/server/docs/_build/html/_modules/server.html b/persistence/server/docs/_build/html/_modules/server.html index 0046eb14ed4..586cca00591 100644 --- a/persistence/server/docs/_build/html/_modules/server.html +++ b/persistence/server/docs/_build/html/_modules/server.html @@ -1509,7 +1509,7 @@

Source code for server

         is_server_up = False
     return is_server_up
-
[docs]def test_server_url(url_to_test): +
[docs]def check_server_url(url_to_test): """Return True if the url_to_test is indeed a valid Faraday Server URL. False otherwise. """ diff --git a/persistence/server/docs/_build/html/genindex.html b/persistence/server/docs/_build/html/genindex.html index 5f9e870b6ca..1c8be6868d0 100644 --- a/persistence/server/docs/_build/html/genindex.html +++ b/persistence/server/docs/_build/html/genindex.html @@ -279,7 +279,7 @@

P

T

diff --git a/persistence/server/docs/_build/html/server.html b/persistence/server/docs/_build/html/server.html index 30157715dcf..e879bc595e1 100644 --- a/persistence/server/docs/_build/html/server.html +++ b/persistence/server/docs/_build/html/server.html @@ -1473,7 +1473,7 @@

Submodules
-persistence.server.server.test_server_url(url_to_test)[source]
+persistence.server.server.check_server_url(url_to_test)[source]

Return True if the url_to_test is indeed a valid Faraday Server URL. False otherwise.

diff --git a/persistence/server/models.py b/persistence/server/models.py index f275dd6f413..72b61ea6e4a 100644 --- a/persistence/server/models.py +++ b/persistence/server/models.py @@ -679,9 +679,9 @@ def server_info(): return server.server_info() -def test_server_url(url_to_test): +def check_server_url(url_to_test): """Return True if url_to_test/_api/info is accessible, False otherwise""" - return server.test_server_url(url_to_test) + return server.check_server_url(url_to_test) # NOTE: the whole 'which arguments are mandatory and which type should they be" diff --git a/persistence/server/server.py b/persistence/server/server.py index 60c41db728f..753a7b0a02d 100644 --- a/persistence/server/server.py +++ b/persistence/server/server.py @@ -1541,7 +1541,7 @@ def check_faraday_version(): if info is not None and version != info['Version']: raise RuntimeError('Client and server versions do not match') -def test_server_url(url_to_test): +def check_server_url(url_to_test): """Return True if the url_to_test is indeed a valid Faraday Server URL. False otherwise. """ diff --git a/test_cases/test_persistence_server_server.py b/test_cases/test_persistence_server_server.py index 743e298bc81..3aae3555a9b 100644 --- a/test_cases/test_persistence_server_server.py +++ b/test_cases/test_persistence_server_server.py @@ -15,7 +15,7 @@ class TestServerFuncions: def test_test_server_url_server_runnimg(self, test_client): """ The name of the method is correct we are testing the function: - test_server_url + check_server_url """ mocked_response = { @@ -25,13 +25,13 @@ def test_test_server_url_server_runnimg(self, test_client): responses.add(responses.GET, 'http://localhost/_api/v2/info', json=mocked_response, status=200) - assert server.test_server_url('http://localhost') + assert server.check_server_url('http://localhost') @responses.activate def test_test_server_url_another_http_returns_404(self): responses.add(responses.GET, 'http://localhost/_api/v2/info', status=404) - assert not server.test_server_url('http://localhost') + assert not server.check_server_url('http://localhost') def test_test_server_url_aserver_down(self, test_client): - assert not server.test_server_url('http://localhost') + assert not server.check_server_url('http://localhost') From 68d2caa625211b7e97524b90bf47215d1a9563c0 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 30 May 2018 14:03:48 -0300 Subject: [PATCH 1203/1506] 4773 - Update vulndb styles --- server/www/estilos-v3.css | 4 + .../scripts/vulndb/controllers/vulnModels.js | 5 + .../www/scripts/vulndb/partials/vulndb.html | 97 +++++++++---------- server/www/table-v3.css | 10 +- 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/server/www/estilos-v3.css b/server/www/estilos-v3.css index 04aefb252b3..006459f3e7f 100644 --- a/server/www/estilos-v3.css +++ b/server/www/estilos-v3.css @@ -140,3 +140,7 @@ bottom: 0px; right: 15px; } + +.justify-flex-start { + justify-content: flex-start !important; +} diff --git a/server/www/scripts/vulndb/controllers/vulnModels.js b/server/www/scripts/vulndb/controllers/vulnModels.js index 397b2299901..4b76d4d784e 100644 --- a/server/www/scripts/vulndb/controllers/vulnModels.js +++ b/server/www/scripts/vulndb/controllers/vulnModels.js @@ -340,6 +340,7 @@ angular.module('faradayApp') $scope.toggleSort = function(field) { $scope.toggleSortField(field); $scope.toggleReverse(); + $scope.sort(); }; // toggles column sort field @@ -352,6 +353,10 @@ angular.module('faradayApp') $scope.reverse = !$scope.reverse; }; + $scope.clearSearch = function() { + $scope.search = ''; + }; + var equalAsSets = function(a, b) { if(a.length != b.length) return false; diff --git a/server/www/scripts/vulndb/partials/vulndb.html b/server/www/scripts/vulndb/partials/vulndb.html index b58ef5dc8a7..0fa0f36b78c 100644 --- a/server/www/scripts/vulndb/partials/vulndb.html +++ b/server/www/scripts/vulndb/partials/vulndb.html @@ -6,56 +6,56 @@
-
-
-
- -
- -
- -
-
+
+ +
+
+ -
-
-
- - - - +
+
+
+
+
+ + + + + +
+
- + +
- - - + - - - - + + @@ -64,12 +64,11 @@ selection-model-mode="multiple-additive" selection-model-selected-class="multi-selected" selection-model-on-change="selectedModels()"> - - - - - - + + + + +
- Name +
+ - Description + + Name - Resolution + + Description - Exploitation + + Resolution + Exploitation +
{{model.name}}{{model.description}}{{model.resolution}}{{model.exploitation}}{{model.name}}{{model.description}}{{model.resolution}}{{model.exploitation}}
@@ -88,6 +87,6 @@
-
-

+
+
diff --git a/server/www/table-v3.css b/server/www/table-v3.css index ec83c0c8287..e214ec2244b 100644 --- a/server/www/table-v3.css +++ b/server/www/table-v3.css @@ -7,7 +7,8 @@ height: 45px; } -.table-v3 .ui-grid-header .ui-grid-cell-contents { +.table-v3 .ui-grid-header .ui-grid-cell-contents, +.table-v3 .ui-grid-header .ui-grid-cell-contents span { color: #7c7c7c; padding: 9px 3px 9px 5px !important; text-align: left; @@ -15,6 +16,7 @@ .table-v3 .ui-grid-cell-contents { text-align: left; + border: none; } .table-v3 .ui-grid-cell-contents.confirm-toggle{ @@ -127,3 +129,9 @@ .table-v3 .ui-grid-cell { border: none ; } + +.table-v3 .ui-grid-header .hosts-list-checkall{ + position: relative; + top: 3px; + left: 7px; +} From 21522633987eabfb4c2b3900dc61d274a3b1f9cd Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 30 May 2018 14:26:26 -0300 Subject: [PATCH 1204/1506] 4774 - Update workspace view buttons styles --- .../workspaces/controllers/workspaces.js | 30 +++++++++------- .../www/scripts/workspaces/partials/list.html | 36 +++++++++++++++++-- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/server/www/scripts/workspaces/controllers/workspaces.js b/server/www/scripts/workspaces/controllers/workspaces.js index 082603ccade..fc7d7479962 100644 --- a/server/www/scripts/workspaces/controllers/workspaces.js +++ b/server/www/scripts/workspaces/controllers/workspaces.js @@ -16,7 +16,7 @@ angular.module('faradayApp') $scope.workspaces = []; $scope.wss = []; // $scope.newworkspace = {}; - + var hash_tmp = window.location.hash.split("/")[1]; switch (hash_tmp){ case "status": @@ -63,7 +63,7 @@ angular.module('faradayApp') }; $scope.onSuccessInsert = function(workspace){ - $scope.wss.push(workspace.name); + $scope.wss.push(workspace.name); workspace.scope = workspace.scope.map(function(scope){ return {key: scope} }); @@ -75,9 +75,9 @@ angular.module('faradayApp') for (var i = 0; i < $scope.workspaces.length; i++) { if ($scope.workspaces[i].name > workspace.name) break; } - $scope.workspaces.splice(i, 0, workspace); + $scope.workspaces.splice(i, 0, workspace); }; - + $scope.onFailInsert = function(error){ var modal = $uibModal.open({ templateUrl: 'scripts/commons/partials/modalKO.html', @@ -90,7 +90,7 @@ angular.module('faradayApp') return error; } } - }); + }); }; $scope.onSuccessEdit = function(workspace){ @@ -109,7 +109,7 @@ angular.module('faradayApp') }; }; - $scope.onSuccessDelete = function(workspace_name){ + $scope.onSuccessDelete = function(workspace_name){ remove = function(arr, item) { for(var i = arr.length; i--;) { if(arr[i] === item) { @@ -119,7 +119,7 @@ angular.module('faradayApp') return arr; }; - $scope.wss = remove($scope.wss, workspace_name); + $scope.wss = remove($scope.wss, workspace_name); for(var i = 0; i < $scope.workspaces.length; i++) { if($scope.workspaces[i].name == workspace_name){ $scope.workspaces.splice(i, 1); @@ -127,7 +127,7 @@ angular.module('faradayApp') } }; }; - + $scope.insert = function(workspace){ delete workspace.selected; workspacesFact.put(workspace).then(function(resp){ @@ -187,7 +187,7 @@ angular.module('faradayApp') }; // Modals methods - $scope.new = function(){ + $scope.new = function(){ $scope.modal = $uibModal.open({ templateUrl: 'scripts/workspaces/partials/modalNew.html', controller: 'workspacesModalNew', @@ -201,12 +201,12 @@ angular.module('faradayApp') }).filter(Boolean); workspace = $scope.create(workspace.name, workspace.description, workspace.start_date, workspace.end_date, api_scope); - $scope.insert(workspace); + $scope.insert(workspace); }); }; - $scope.edit = function(){ + $scope.edit = function(){ var workspace; $scope.workspaces.forEach(function(w) { if(w.selected) { @@ -236,7 +236,7 @@ angular.module('faradayApp') $scope.update(workspace, oldName, function(workspace){ workspace.scope = old_scope; - }); + }); }); } else { var modal = $uibModal.open({ @@ -278,7 +278,7 @@ angular.module('faradayApp') $scope.modal.result.then(function() { $scope.workspaces.forEach(function(w) { if(w.selected == true) - $scope.remove(w.name); + $scope.remove(w.name); }); }); } else { @@ -324,5 +324,9 @@ angular.module('faradayApp') $location.path("/dashboard/ws/"+path); }; + $scope.clearSearch = function() { + $scope.search = ''; + }; + $scope.init(); }]); diff --git a/server/www/scripts/workspaces/partials/list.html b/server/www/scripts/workspaces/partials/list.html index fc95802af3c..b1edcec9309 100644 --- a/server/www/scripts/workspaces/partials/list.html +++ b/server/www/scripts/workspaces/partials/list.html @@ -11,7 +11,7 @@

Workspaces
-
+

-
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + + +
+
+
+
+
From 3230b88a49c44e94ee0bbdd27346f4d76889188f Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 30 May 2018 16:45:46 -0300 Subject: [PATCH 1205/1506] 4775 - Update licenses styles view --- server/www/scripts/hosts/partials/list.html | 12 +-- .../scripts/licenses/controllers/licenses.js | 6 +- .../www/scripts/licenses/partials/list.html | 98 ++++++++++--------- 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/server/www/scripts/hosts/partials/list.html b/server/www/scripts/hosts/partials/list.html index 331ac36c680..ef8950b6655 100644 --- a/server/www/scripts/hosts/partials/list.html +++ b/server/www/scripts/hosts/partials/list.html @@ -3,17 +3,17 @@
-
+
-
-
@@ -129,7 +129,7 @@ {{host.id}}
-
- @@ -203,7 +203,7 @@ - - + @@ -25,10 +25,7 @@

Last Vulnerabilities {{vuln.target}}

- + diff --git a/server/www/scripts/dashboard/partials/modal-services-by-host.html b/server/www/scripts/dashboard/partials/modal-services-by-host.html index 0298a553bb0..1a6037903bb 100644 --- a/server/www/scripts/dashboard/partials/modal-services-by-host.html +++ b/server/www/scripts/dashboard/partials/modal-services-by-host.html @@ -3,42 +3,61 @@
{{host.ip}} @@ -179,7 +179,7 @@
{{host.credentials}}
diff --git a/server/www/scripts/licenses/controllers/licenses.js b/server/www/scripts/licenses/controllers/licenses.js index e6857e3cf23..d3fa084805d 100644 --- a/server/www/scripts/licenses/controllers/licenses.js +++ b/server/www/scripts/licenses/controllers/licenses.js @@ -196,7 +196,11 @@ angular.module('faradayApp') // toggle column sort order $scope.toggleReverse = function() { $scope.reverse = !$scope.reverse; - } + }; + + $scope.clearSearch = function() { + $scope.search = ''; + }; init(); }]); diff --git a/server/www/scripts/licenses/partials/list.html b/server/www/scripts/licenses/partials/list.html index 39de606e741..fd4e013cd74 100644 --- a/server/www/scripts/licenses/partials/list.html +++ b/server/www/scripts/licenses/partials/list.html @@ -3,58 +3,65 @@
-
+
+

Licenses ({{licenses.length}}) - - -

-
-
-
- - - - -
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + + +
+
+

The licenses marked in dark red are about to expire

- +
- - - + + - - - - - + @@ -64,18 +71,19 @@

The licenses m selection-model-selected-class="multi-selected" selection-model-on-change="selectedLicenses()" ng-class="{'almost-expired': almostExpired(license.end)}"> -

- - - - - - + + + + + +
- Product +
+ Product - Type + + Type - Notes + + Notes - Start date + + Start date - End date + + End date
{{license.product}}{{license.lictype}}{{license.notes}}{{license.start | date:'MM/dd/yyyy'}}{{license.end|date:'MM/dd/yyyy'}}{{license.product}}{{license.lictype}}{{license.notes}}{{license.start | date:'MM/dd/yyyy'}}{{license.end|date:'MM/dd/yyyy'}}
-
+
+
From 2fa12cfd8e5ed68b28f89db9460eac14d7d20969 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 30 May 2018 17:53:37 -0300 Subject: [PATCH 1206/1506] [FIX] Return empty list if an object was deleted while the api was doing the serialization --- server/api/base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/api/base.py b/server/api/base.py index 40612fd1719..001e54edbd8 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -5,7 +5,7 @@ from flask import abort, g from flask_classful import FlaskView from sqlalchemy.orm import joinedload, undefer -from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.orm.exc import NoResultFound, ObjectDeletedError from sqlalchemy.inspection import inspect from sqlalchemy import func from marshmallow import Schema @@ -150,7 +150,10 @@ def _get_object(self, object_id, eagerload=False, **kwargs): return obj def _dump(self, obj, route_kwargs, **kwargs): - return self._get_schema_instance(route_kwargs, **kwargs).dump(obj).data + try: + return self._get_schema_instance(route_kwargs, **kwargs).dump(obj).data + except ObjectDeletedError: + return [] def _parse_data(self, schema, request, *args, **kwargs): return FlaskParser().parse(schema, request, locations=('json',), From ecebae490979eaa9b004830b8913501b3f390e57 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 30 May 2018 18:02:06 -0300 Subject: [PATCH 1207/1506] 4772 - Update credentials view styles --- .../scripts/credentials/partials/list.html | 90 ++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/server/www/scripts/credentials/partials/list.html b/server/www/scripts/credentials/partials/list.html index e73a1185aa4..abfd04b30a8 100644 --- a/server/www/scripts/credentials/partials/list.html +++ b/server/www/scripts/credentials/partials/list.html @@ -6,50 +6,56 @@
-

- - - -

+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + + +
+
+
+
+
+
-
-
-
- - - - -
-
-
- +
- - - + + - - - @@ -59,11 +65,11 @@

selection-model-mode="multiple-additive" selection-model-selected-class="multi-selected" selection-model-on-change="selectedCredentials()"> -

- - - - + + + + +
- Target +
+ Target - Name + + Name - Username + + Username - Password + + Password
{{credential.target}} {{credential.name}}{{credential.username}}{{credential.password}} {{credential.target}} {{credential.name}}{{credential.username}}{{credential.password}}
From 1aff17d1c23b4517daad76487da4e7e4a684eb9c Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Wed, 30 May 2018 19:07:46 -0300 Subject: [PATCH 1208/1506] First version upload report from webUi --- persistence/server/models.py | 3 +- server/api/modules/upload_reports.py | 112 +++++++++++++++++++++++++++ server/app.py | 2 + server/config.py | 10 +++ server/web.py | 4 + 5 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 server/api/modules/upload_reports.py diff --git a/persistence/server/models.py b/persistence/server/models.py index f275dd6f413..9e2ab3c30eb 100644 --- a/persistence/server/models.py +++ b/persistence/server/models.py @@ -748,7 +748,8 @@ def getID(self): retries = 1 max_retries = 6 while retries <= max_retries and self.id is None: - print('Retrying getID timeout {0}'.format(timeout)) + if timeout >= 8: + logger.info('Retrying getID timeout {0}'.format(timeout)) self.id_available.wait(timeout=timeout) timeout = timeout << retries - 1 retries += 1 diff --git a/server/api/modules/upload_reports.py b/server/api/modules/upload_reports.py new file mode 100644 index 00000000000..32ba00a539e --- /dev/null +++ b/server/api/modules/upload_reports.py @@ -0,0 +1,112 @@ +# Faraday Penetration Test IDE +# Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) +# See the file 'doc/LICENSE' for the license information + +from Queue import Queue +from multiprocessing import Queue as MultiProcessingQueue +from threading import Thread + +import os +import string +import random +import logging +import model.api + +from flask import request, abort, jsonify, Blueprint +from werkzeug.utils import secure_filename + +from server.utils.logger import get_logger +from server.utils.web import gzipped + +from model.controller import ModelController +from model.cli_app import CliApp + +from plugins.controller import PluginController +from plugins.manager import PluginManager + +from managers.workspace_manager import WorkspaceManager +from managers.mapper_manager import MapperManager + +from config.configuration import getInstanceConfiguration +CONF = getInstanceConfiguration() + +upload_api = Blueprint('upload_reports', __name__) +logger = logging.getLogger(__name__) +UPLOAD_REPORTS_QUEUE = MultiProcessingQueue() + + +class RawReportProcessor(Thread): + def __init__(self): + + super(RawReportProcessor, self).__init__() + plugin_manager = PluginManager(os.path.join(CONF.getConfigPath(), "plugins")) + + mappers_manager = MapperManager() + self.pending_actions = Queue() + + model_controller = ModelController(mappers_manager, self.pending_actions) + + workspace_manager = WorkspaceManager(mappers_manager) + CONF.setMergeStrategy("new") + model.api.setUpAPIs(model_controller, workspace_manager, 0, 0) + model_controller.start() + + plugin_controller = PluginController( + 'PluginController', + plugin_manager, + mappers_manager, + self.pending_actions) + + self.cli_import = CliApp(workspace_manager, plugin_controller) + + def run(self): + + while True: + try: + + workspace, file_path = UPLOAD_REPORTS_QUEUE.get() + logger.info('Processing raw report {0}'.format(file_path)) + + class Arg(): + def __init__(self): + self.workspace = workspace + self.filename = file_path + + self.cli_import.run(Arg()) + except KeyboardInterrupt: + break + + +@gzipped +@upload_api.route('/v2/ws//upload_report', methods=['POST']) +def file_upload(workspace=None): + """ + Upload a report file to Server and process that report with Faraday client plugins. + """ + + get_logger(__name__).debug("Importing new plugin report in server...") + + if 'file' not in request.files: + abort(400) + + report_file = request.files['file'] + + if report_file.filename == '': + abort(400) + + if report_file: + + chars = string.ascii_uppercase + string.digits + random_prefix = ''.join(random.choice(chars) for x in range(12)) + raw_report_filename = '{0}{1}'.format(random_prefix, secure_filename(report_file.filename)) + + file_path = os.path.join( + CONF.getConfigPath(), + 'uploaded_reports/{0}'.format(raw_report_filename)) + + with open(file_path, 'w') as output: + output.write(report_file.read()) + + UPLOAD_REPORTS_QUEUE.put((workspace, file_path)) + + return jsonify({"status": "processing"}) diff --git a/server/app.py b/server/app.py index 4bd204cc075..c570277da64 100644 --- a/server/app.py +++ b/server/app.py @@ -64,6 +64,7 @@ def register_blueprints(app): from server.api.modules.workspaces import workspace_api from server.api.modules.handlers import handlers_api from server.api.modules.comments import comment_api + from server.api.modules.upload_reports import upload_api app.register_blueprint(commandsrun_api) app.register_blueprint(credentials_api) app.register_blueprint(host_api) @@ -76,6 +77,7 @@ def register_blueprints(app): app.register_blueprint(workspace_api) app.register_blueprint(handlers_api) app.register_blueprint(comment_api) + app.register_blueprint(upload_api) def check_testing_configuration(testing, app): diff --git a/server/config.py b/server/config.py index acee0692cbf..6233f09a52e 100644 --- a/server/config.py +++ b/server/config.py @@ -26,12 +26,15 @@ REPORTS_VIEWS_DIR = os.path.join(FARADAY_BASE, 'views/reports') LOCAL_CONFIG_FILE = os.path.expanduser( os.path.join(CONSTANTS.CONST_FARADAY_HOME_PATH, 'config/server.ini')) +LOCAL_REPORTS_FOLDER = os.path.expanduser( + os.path.join(CONSTANTS.CONST_FARADAY_HOME_PATH, 'uploaded_reports/')) CONFIG_FILES = [DEFAULT_CONFIG_FILE, LOCAL_CONFIG_FILE] WS_BLACKLIST = CONSTANTS.CONST_BLACKDBS def copy_default_config_to_local(): + if os.path.exists(LOCAL_CONFIG_FILE): return @@ -45,6 +48,13 @@ def copy_default_config_to_local(): # Copy default config file into faraday local config shutil.copyfile(DEFAULT_CONFIG_FILE, LOCAL_CONFIG_FILE) + if not os.path.exists(LOCAL_REPORTS_FOLDER): + try: + os.makedirs(LOCAL_REPORTS_FOLDER) + except OSError as e: + if e.errno != errno.EEXIST: + raise + from server.utils.logger import get_logger get_logger(__name__).info(u"Local faraday-server configuration created at {}".format(LOCAL_CONFIG_FILE)) diff --git a/server/web.py b/server/web.py index af78e5de6e3..bcb90341f2d 100644 --- a/server/web.py +++ b/server/web.py @@ -32,6 +32,7 @@ WorkspaceServerFactory, BroadcastServerProtocol ) +from server.api.modules.upload_reports import RawReportProcessor app = create_app() # creates a Flask(__name__) app logger = server.utils.logger.get_logger(__name__) @@ -141,6 +142,9 @@ def run(self): self.__listen_func = reactor.listenTCP try: + # start threads and processes + raw_report_processor = RawReportProcessor() + raw_report_processor.start() # web and static content self.__listen_func( self.__listen_port, site, From 260c6edac7a6dcccc8db0c4510fd94a9ca55be37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 31 May 2018 12:00:20 -0300 Subject: [PATCH 1209/1506] Add copyright and license information for new files in v3 And in other files that didn't have any license information --- data/fix_severities.py | 3 +++ gui/gtk/__init__.py | 4 ++++ gui/gtk/decorators.py | 3 +++ helpers/cfdbToCsv.py | 1 + helpers/vulndbToCsv.py | 1 + manage.py | 6 ++++++ persistence/server/__init__.py | 7 +++++++ plugins/fplugin_utils.py | 6 ++++++ plugins/repo/pasteanalyzer/plugin.py | 6 ++++++ plugins/repo/traceroute/plugin.py | 6 ++++++ plugins/repo/wpscan/__init__.py | 7 +++++++ scripts/cscan/plugin/msfrpc.py | 6 ++++++ scripts/reposify/reposify.py | 6 ++++++ scripts/reposify/reposify_api.py | 8 +++++++- scripts/wcscans/__init__.py | 7 +++++++ server/api/base.py | 6 ++++++ server/api/modules/__init__.py | 7 +++++++ server/api/modules/handlers.py | 8 +++++++- server/api/modules/session.py | 6 ++++++ server/commands/__init__.py | 7 +++++++ server/commands/app_urls.py | 8 +++++++- server/commands/faraday_schema_display.py | 6 ++++++ server/commands/initdb.py | 6 ++++++ server/commands/reports.py | 8 +++++++- server/commands/reset_db.py | 6 ++++++ server/events.py | 6 ++++++ server/fields.py | 6 ++++++ server/schemas.py | 6 ++++++ server/utils/invalid_chars.py | 6 ++++++ server/websocket_factories.py | 6 ++++++ test_cases/conftest.py | 6 ++++++ test_cases/factories.py | 6 ++++++ test_cases/models/test_cascades.py | 6 ++++++ test_cases/models/test_file.py | 6 ++++++ test_cases/models/test_host.py | 6 ++++++ test_cases/models/test_tag.py | 6 ++++++ test_cases/models/test_vulnerability.py | 6 ++++++ test_cases/models/test_workspace.py | 6 ++++++ test_cases/plugins/test_common.py | 6 ++++++ test_cases/test_ModelObjectFactory.py | 7 +++++++ test_cases/test_api_commands.py | 8 +++++++- test_cases/test_api_comment.py | 8 +++++++- test_cases/test_api_credentials.py | 6 ++++++ test_cases/test_api_hosts.py | 6 ++++++ test_cases/test_api_info.py | 8 +++++++- test_cases/test_api_license.py | 6 ++++++ test_cases/test_api_non_workspaced_base.py | 8 +++++++- test_cases/test_api_pagination.py | 6 ++++++ test_cases/test_api_services.py | 6 ++++++ test_cases/test_api_vulnerability.py | 6 ++++++ test_cases/test_api_vulnerability_template.py | 8 +++++++- test_cases/test_api_websocket_auth.py | 6 ++++++ test_cases/test_api_workspace.py | 6 ++++++ test_cases/test_api_workspaced_base.py | 6 ++++++ test_cases/test_config_configuration.py | 7 +++++++ test_cases/test_import_users_from_couch.py | 6 ++++++ test_cases/test_managers_mapper_manager.py | 6 ++++++ test_cases/test_marshmallow_fields.py | 6 ++++++ test_cases/test_model_controller.py | 6 ++++++ test_cases/test_model_events.py | 6 ++++++ test_cases/test_model_fields.py | 8 +++++++- test_cases/test_models.py | 6 ++++++ test_cases/test_persistence_server_models.py | 6 ++++++ test_cases/test_persistence_server_server.py | 6 ++++++ test_cases/test_plugins_controller.py | 6 ++++++ test_cases/test_server.py | 6 ++++++ test_cases/test_server_config.py | 6 ++++++ test_cases/test_server_io.py | 6 ++++++ test_cases/test_utils_database.py | 8 +++++++- 69 files changed, 414 insertions(+), 11 deletions(-) diff --git a/data/fix_severities.py b/data/fix_severities.py index dde72aac7b2..0e318709835 100644 --- a/data/fix_severities.py +++ b/data/fix_severities.py @@ -1,3 +1,6 @@ +# Faraday Penetration Test IDE +# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) +# See the file 'doc/LICENSE' for the license information import csv import click from collections import OrderedDict diff --git a/gui/gtk/__init__.py b/gui/gtk/__init__.py index e69de29bb2d..50a8239b3f9 100644 --- a/gui/gtk/__init__.py +++ b/gui/gtk/__init__.py @@ -0,0 +1,4 @@ +# Faraday Penetration Test IDE +# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) +# See the file 'doc/LICENSE' for the license information + diff --git a/gui/gtk/decorators.py b/gui/gtk/decorators.py index a5e82fdab22..adba7918b64 100644 --- a/gui/gtk/decorators.py +++ b/gui/gtk/decorators.py @@ -1,3 +1,6 @@ +# Faraday Penetration Test IDE +# Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) +# See the file 'doc/LICENSE' for the license information import requests from gi.repository import Gtk from utils.logs import getLogger diff --git a/helpers/cfdbToCsv.py b/helpers/cfdbToCsv.py index 312c082fec6..a2bc8308acd 100755 --- a/helpers/cfdbToCsv.py +++ b/helpers/cfdbToCsv.py @@ -3,6 +3,7 @@ ''' Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) Author: Ezequiel Tavella +See the file 'doc/LICENSE' for the license information This script generate a CSV file with information about the cfdb database. CSV Format: diff --git a/helpers/vulndbToCsv.py b/helpers/vulndbToCsv.py index 1196e8549f5..e8fa5e10392 100755 --- a/helpers/vulndbToCsv.py +++ b/helpers/vulndbToCsv.py @@ -3,6 +3,7 @@ ''' Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) Author: Ezequiel Tavella +See the file 'doc/LICENSE' for the license information This script generate a CSV file with information about the vulndb database. CSV Format: diff --git a/manage.py b/manage.py index 7bdce489079..5a1bcf01b52 100755 --- a/manage.py +++ b/manage.py @@ -1,4 +1,10 @@ #!/usr/bin/env python +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import re diff --git a/persistence/server/__init__.py b/persistence/server/__init__.py index e69de29bb2d..47c52b07c21 100644 --- a/persistence/server/__init__.py +++ b/persistence/server/__init__.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/plugins/fplugin_utils.py b/plugins/fplugin_utils.py index eb04b1e7726..20f93edf174 100644 --- a/plugins/fplugin_utils.py +++ b/plugins/fplugin_utils.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import imp import os import sys diff --git a/plugins/repo/pasteanalyzer/plugin.py b/plugins/repo/pasteanalyzer/plugin.py index f9e97b6dee2..d1419a1c37e 100644 --- a/plugins/repo/pasteanalyzer/plugin.py +++ b/plugins/repo/pasteanalyzer/plugin.py @@ -1,5 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' # Author: @EzequielTBH diff --git a/plugins/repo/traceroute/plugin.py b/plugins/repo/traceroute/plugin.py index 6638e47709b..9e678d4ee59 100644 --- a/plugins/repo/traceroute/plugin.py +++ b/plugins/repo/traceroute/plugin.py @@ -1,5 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from plugins import core import re diff --git a/plugins/repo/wpscan/__init__.py b/plugins/repo/wpscan/__init__.py index e69de29bb2d..47c52b07c21 100644 --- a/plugins/repo/wpscan/__init__.py +++ b/plugins/repo/wpscan/__init__.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/scripts/cscan/plugin/msfrpc.py b/scripts/cscan/plugin/msfrpc.py index c18da648436..641138d0056 100755 --- a/scripts/cscan/plugin/msfrpc.py +++ b/scripts/cscan/plugin/msfrpc.py @@ -1,4 +1,10 @@ #!/usr/bin/env python2 +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import time diff --git a/scripts/reposify/reposify.py b/scripts/reposify/reposify.py index 58ecd2dd460..7939b914c42 100644 --- a/scripts/reposify/reposify.py +++ b/scripts/reposify/reposify.py @@ -1,4 +1,10 @@ #!/usr/bin/env python +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import sys import xmlrpclib diff --git a/scripts/reposify/reposify_api.py b/scripts/reposify/reposify_api.py index 8dedef259af..0e8b17a3f66 100644 --- a/scripts/reposify/reposify_api.py +++ b/scripts/reposify/reposify_api.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import requests import simplejson @@ -64,4 +70,4 @@ def reposify_search(key, banner, filters, page): if filters is not None: params['filters'] = filters res = api_request(key, '/v1/insights/search', params, None, 'https://api.reposify.com', 'get', 1) - return res \ No newline at end of file + return res diff --git a/scripts/wcscans/__init__.py b/scripts/wcscans/__init__.py index e69de29bb2d..47c52b07c21 100644 --- a/scripts/wcscans/__init__.py +++ b/scripts/wcscans/__init__.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/server/api/base.py b/server/api/base.py index 40612fd1719..abf0d09ae8f 100644 --- a/server/api/base.py +++ b/server/api/base.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import json import flask diff --git a/server/api/modules/__init__.py b/server/api/modules/__init__.py index e69de29bb2d..47c52b07c21 100644 --- a/server/api/modules/__init__.py +++ b/server/api/modules/__init__.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/server/api/modules/handlers.py b/server/api/modules/handlers.py index ee86d5a6d8a..7904447f0bd 100644 --- a/server/api/modules/handlers.py +++ b/server/api/modules/handlers.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from flask import jsonify, Blueprint handlers_api = Blueprint('handlers_api', __name__) @@ -12,4 +18,4 @@ def error_response(e): -#.register(commandsrun_api) \ No newline at end of file +#.register(commandsrun_api) diff --git a/server/api/modules/session.py b/server/api/modules/session.py index 4348eff6ee5..a8473b03570 100644 --- a/server/api/modules/session.py +++ b/server/api/modules/session.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from flask import jsonify, session, Blueprint, current_app session_api = Blueprint('session_api', __name__) diff --git a/server/commands/__init__.py b/server/commands/__init__.py index e69de29bb2d..47c52b07c21 100644 --- a/server/commands/__init__.py +++ b/server/commands/__init__.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/server/commands/app_urls.py b/server/commands/app_urls.py index 4e1066ac05a..d90ac41c16a 100644 --- a/server/commands/app_urls.py +++ b/server/commands/app_urls.py @@ -1,5 +1,11 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from server.web import app def show_all_urls(): - print(app.url_map) \ No newline at end of file + print(app.url_map) diff --git a/server/commands/faraday_schema_display.py b/server/commands/faraday_schema_display.py index de224b767d2..e62181e75d3 100644 --- a/server/commands/faraday_schema_display.py +++ b/server/commands/faraday_schema_display.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from __future__ import print_function import sys from sqlalchemy import MetaData diff --git a/server/commands/initdb.py b/server/commands/initdb.py index aa88d9de9aa..f3949e5aebf 100644 --- a/server/commands/initdb.py +++ b/server/commands/initdb.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import getpass import shutil import string diff --git a/server/commands/reports.py b/server/commands/reports.py index c1db8981c9c..89b5b569598 100644 --- a/server/commands/reports.py +++ b/server/commands/reports.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import logging from Queue import Queue @@ -61,4 +67,4 @@ def process_workspaces(mappers_manager, plugin_manager, query, disable_polling): # report_manager.join() #for controller in controllers: - # controller.join() \ No newline at end of file + # controller.join() diff --git a/server/commands/reset_db.py b/server/commands/reset_db.py index 773dc7ec927..5e95b73ffd0 100644 --- a/server/commands/reset_db.py +++ b/server/commands/reset_db.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from server.models import db def reset_db_all(): diff --git a/server/events.py b/server/events.py index 9a2e5d1579c..6d9f35446a0 100644 --- a/server/events.py +++ b/server/events.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import sys import logging import inspect diff --git a/server/fields.py b/server/fields.py index 895fbb1268c..ec53b6b0217 100644 --- a/server/fields.py +++ b/server/fields.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import imghdr from tempfile import SpooledTemporaryFile diff --git a/server/schemas.py b/server/schemas.py index 30ed6c974f2..4824a9afa0d 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import time import datetime from marshmallow import fields, Schema diff --git a/server/utils/invalid_chars.py b/server/utils/invalid_chars.py index f3d8098f581..97238a5cfa5 100644 --- a/server/utils/invalid_chars.py +++ b/server/utils/invalid_chars.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import re import sys diff --git a/server/websocket_factories.py b/server/websocket_factories.py index b448656efcb..e6833841c6c 100644 --- a/server/websocket_factories.py +++ b/server/websocket_factories.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import json import logging import itsdangerous diff --git a/test_cases/conftest.py b/test_cases/conftest.py index 8f3c2d5c4b9..983f20ef428 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from tempfile import NamedTemporaryFile import os diff --git a/test_cases/factories.py b/test_cases/factories.py index a5ab5842c82..094eaa030e4 100644 --- a/test_cases/factories.py +++ b/test_cases/factories.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import random import factory import datetime diff --git a/test_cases/models/test_cascades.py b/test_cases/models/test_cascades.py index 4157cc8fcb3..5187ae529f7 100644 --- a/test_cases/models/test_cascades.py +++ b/test_cases/models/test_cascades.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest from contextlib import contextmanager from server.models import ( diff --git a/test_cases/models/test_file.py b/test_cases/models/test_file.py index ff6190a2691..3fd91673400 100644 --- a/test_cases/models/test_file.py +++ b/test_cases/models/test_file.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import pytest from server.models import File diff --git a/test_cases/models/test_host.py b/test_cases/models/test_host.py index aba8e7d38c0..4979b7c925a 100644 --- a/test_cases/models/test_host.py +++ b/test_cases/models/test_host.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import random import pytest from functools import partial diff --git a/test_cases/models/test_tag.py b/test_cases/models/test_tag.py index 9c3dea4a8fd..2e02938644f 100644 --- a/test_cases/models/test_tag.py +++ b/test_cases/models/test_tag.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest from server.models import TagObject diff --git a/test_cases/models/test_vulnerability.py b/test_cases/models/test_vulnerability.py index c0d2b99cbb2..62706f4f76a 100644 --- a/test_cases/models/test_vulnerability.py +++ b/test_cases/models/test_vulnerability.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest from server.models import ( CommandObject, diff --git a/test_cases/models/test_workspace.py b/test_cases/models/test_workspace.py index 390bfa506f3..d4fcb6f434b 100644 --- a/test_cases/models/test_workspace.py +++ b/test_cases/models/test_workspace.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from server.models import db, Workspace from test_cases.factories import ( HostFactory, diff --git a/test_cases/plugins/test_common.py b/test_cases/plugins/test_common.py index aee5bd38c1d..f879742ef25 100644 --- a/test_cases/plugins/test_common.py +++ b/test_cases/plugins/test_common.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' def skip(self, n): for x in range(n): action = self.plugin._pending_actions.get(block=True) diff --git a/test_cases/test_ModelObjectFactory.py b/test_cases/test_ModelObjectFactory.py index e69de29bb2d..47c52b07c21 100644 --- a/test_cases/test_ModelObjectFactory.py +++ b/test_cases/test_ModelObjectFactory.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/test_cases/test_api_commands.py b/test_cases/test_api_commands.py index c9a3781901a..dd0fb71d863 100644 --- a/test_cases/test_api_commands.py +++ b/test_cases/test_api_commands.py @@ -1,4 +1,10 @@ #-*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' """Tests for many API endpoints that do not depend on workspace_name""" import datetime import pytest @@ -377,4 +383,4 @@ def test_delete_objects_preserve_history(self, session, test_client): assert len(command_history) command_history = command_history[0] assert command_history['hosts_count'] == 1 - assert command_history['tool'] == 'test' \ No newline at end of file + assert command_history['tool'] == 'test' diff --git a/test_cases/test_api_comment.py b/test_cases/test_api_comment.py index 05fda625f40..c7a13704e98 100644 --- a/test_cases/test_api_comment.py +++ b/test_cases/test_api_comment.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from server.api.modules.comments import CommentView from server.models import Comment from test_cases.factories import ServiceFactory @@ -84,4 +90,4 @@ def test_create_unique_comment_for_plugins(self, session, test_client): res = test_client.post(url, data=raw_comment) assert res.status_code == 409 assert 'object' in res.json - assert type(res.json) == dict \ No newline at end of file + assert type(res.json) == dict diff --git a/test_cases/test_api_credentials.py b/test_cases/test_api_credentials.py index 3e4ee44a534..ae1578cb8b3 100644 --- a/test_cases/test_api_credentials.py +++ b/test_cases/test_api_credentials.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest from test_cases import factories diff --git a/test_cases/test_api_hosts.py b/test_cases/test_api_hosts.py index a74e5b644e1..c4e2ee63977 100644 --- a/test_cases/test_api_hosts.py +++ b/test_cases/test_api_hosts.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import time import operator diff --git a/test_cases/test_api_info.py b/test_cases/test_api_info.py index d3c7c5b25e4..6d64d04de60 100644 --- a/test_cases/test_api_info.py +++ b/test_cases/test_api_info.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import pytest @@ -18,4 +24,4 @@ def test_api_info(self, test_client): assert response.status_code == 200 assert response.json['Faraday Server'] == 'Running' # to avoid side effects - os.chdir(current_dir) \ No newline at end of file + os.chdir(current_dir) diff --git a/test_cases/test_api_license.py b/test_cases/test_api_license.py index e979d2f1eeb..7c8c6ac789c 100644 --- a/test_cases/test_api_license.py +++ b/test_cases/test_api_license.py @@ -1,4 +1,10 @@ #-*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' """Tests for many API endpoints that do not depend on workspace_name""" import pytest diff --git a/test_cases/test_api_non_workspaced_base.py b/test_cases/test_api_non_workspaced_base.py index 7e0f7b16757..bb9ca847920 100644 --- a/test_cases/test_api_non_workspaced_base.py +++ b/test_cases/test_api_non_workspaced_base.py @@ -1,4 +1,10 @@ #-*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' """Generic tests for APIs NOT prefixed with a workspace_name""" @@ -151,4 +157,4 @@ class ReadWriteAPITests(ReadWriteTestsMixin, class ReadOnlyAPITests(ListTestsMixin, RetrieveTestsMixin, GenericAPITest): - pass \ No newline at end of file + pass diff --git a/test_cases/test_api_pagination.py b/test_cases/test_api_pagination.py index cd60aa2e387..a5bb0d69099 100644 --- a/test_cases/test_api_pagination.py +++ b/test_cases/test_api_pagination.py @@ -1,4 +1,10 @@ #-*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' """Generic test mixins for APIs with pagination enabled when listing""" diff --git a/test_cases/test_api_services.py b/test_cases/test_api_services.py index c8186878ea3..557062d64d6 100644 --- a/test_cases/test_api_services.py +++ b/test_cases/test_api_services.py @@ -1,4 +1,10 @@ # -*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' """Tests for many API endpoints that do not depend on workspace_name""" try: from urllib import urlencode diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 4fdb9ec0b4b..21db9b7dbc9 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -1,4 +1,10 @@ #-*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from StringIO import StringIO from tempfile import NamedTemporaryFile diff --git a/test_cases/test_api_vulnerability_template.py b/test_cases/test_api_vulnerability_template.py index 1e37c3b784c..beb948b6f7a 100644 --- a/test_cases/test_api_vulnerability_template.py +++ b/test_cases/test_api_vulnerability_template.py @@ -1,4 +1,10 @@ #-*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import pytest @@ -167,4 +173,4 @@ def test_when_a_template_with_ref_is_deleted_ref_remains_at_database( res = test_client.delete(self.url(template)) assert res.status_code == 204 - assert session.query(ReferenceTemplate).count() == 1 \ No newline at end of file + assert session.query(ReferenceTemplate).count() == 1 diff --git a/test_cases/test_api_websocket_auth.py b/test_cases/test_api_websocket_auth.py index 3cc6d28ebcd..56766d215c4 100644 --- a/test_cases/test_api_websocket_auth.py +++ b/test_cases/test_api_websocket_auth.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest diff --git a/test_cases/test_api_workspace.py b/test_cases/test_api_workspace.py index 6fdcd6f12af..0b8eb1b18ce 100644 --- a/test_cases/test_api_workspace.py +++ b/test_cases/test_api_workspace.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import time import pytest diff --git a/test_cases/test_api_workspaced_base.py b/test_cases/test_api_workspaced_base.py index 784ed4becf2..447709dc784 100644 --- a/test_cases/test_api_workspaced_base.py +++ b/test_cases/test_api_workspaced_base.py @@ -1,4 +1,10 @@ #-*- coding: utf8 -*- +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' """Generic tests for APIs prefixed with a workspace_name""" diff --git a/test_cases/test_config_configuration.py b/test_cases/test_config_configuration.py index e69de29bb2d..47c52b07c21 100644 --- a/test_cases/test_config_configuration.py +++ b/test_cases/test_config_configuration.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/test_cases/test_import_users_from_couch.py b/test_cases/test_import_users_from_couch.py index cb7f9ca6761..fff212bd8df 100644 --- a/test_cases/test_import_users_from_couch.py +++ b/test_cases/test_import_users_from_couch.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest from passlib.hash import pbkdf2_sha1 from server.importer import ImportCouchDBUsers diff --git a/test_cases/test_managers_mapper_manager.py b/test_cases/test_managers_mapper_manager.py index c018ac3e62e..f47ca70ba4e 100644 --- a/test_cases/test_managers_mapper_manager.py +++ b/test_cases/test_managers_mapper_manager.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from functools import partial import pytest diff --git a/test_cases/test_marshmallow_fields.py b/test_cases/test_marshmallow_fields.py index a9cad9eaf0b..8ef9045a90d 100644 --- a/test_cases/test_marshmallow_fields.py +++ b/test_cases/test_marshmallow_fields.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import time import datetime import pytest diff --git a/test_cases/test_model_controller.py b/test_cases/test_model_controller.py index 1b2cac7e4f2..98d3824c147 100644 --- a/test_cases/test_model_controller.py +++ b/test_cases/test_model_controller.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' from Queue import Queue import time diff --git a/test_cases/test_model_events.py b/test_cases/test_model_events.py index ece1c9d78c9..476c2119dc8 100644 --- a/test_cases/test_model_events.py +++ b/test_cases/test_model_events.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest from test_cases.factories import HostFactory, ServiceFactory diff --git a/test_cases/test_model_fields.py b/test_cases/test_model_fields.py index a6e91bb7884..3cdc0631629 100644 --- a/test_cases/test_model_fields.py +++ b/test_cases/test_model_fields.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os from server.fields import FaradayUploadedFile @@ -26,4 +32,4 @@ def test_normal_attach_is_not_detected_as_image(): with open(os.path.join(CURRENT_PATH, 'data', 'report_w3af.xml'))as image_data: field = FaradayUploadedFile(image_data.read()) assert field['content_type'] == 'application/octet-stream' - assert len(field['files']) == 1 \ No newline at end of file + assert len(field['files']) == 1 diff --git a/test_cases/test_models.py b/test_cases/test_models.py index 4ca847a9bfe..ee387c6aa85 100644 --- a/test_cases/test_models.py +++ b/test_cases/test_models.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import unittest import json from persistence.server import models diff --git a/test_cases/test_persistence_server_models.py b/test_cases/test_persistence_server_models.py index 6f6ad78c6d6..81bc910d1a5 100644 --- a/test_cases/test_persistence_server_models.py +++ b/test_cases/test_persistence_server_models.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import persistence.server.models as models import pytest import responses diff --git a/test_cases/test_persistence_server_server.py b/test_cases/test_persistence_server_server.py index 743e298bc81..b8cf9d26413 100644 --- a/test_cases/test_persistence_server_server.py +++ b/test_cases/test_persistence_server_server.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import responses diff --git a/test_cases/test_plugins_controller.py b/test_cases/test_plugins_controller.py index 01bca562cb2..c11d7701c4d 100644 --- a/test_cases/test_plugins_controller.py +++ b/test_cases/test_plugins_controller.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import sys sys.path.append('.') import unittest diff --git a/test_cases/test_server.py b/test_cases/test_server.py index b55da585d6c..555d22f208e 100644 --- a/test_cases/test_server.py +++ b/test_cases/test_server.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import sys import unittest diff --git a/test_cases/test_server_config.py b/test_cases/test_server_config.py index c688bbf03a1..e6f9125d1c9 100644 --- a/test_cases/test_server_config.py +++ b/test_cases/test_server_config.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import random import string import mock diff --git a/test_cases/test_server_io.py b/test_cases/test_server_io.py index 4de4476fe41..ec5207856a1 100644 --- a/test_cases/test_server_io.py +++ b/test_cases/test_server_io.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import os import sys import unittest diff --git a/test_cases/test_utils_database.py b/test_cases/test_utils_database.py index 46ae7e6e685..4fa88dba492 100644 --- a/test_cases/test_utils_database.py +++ b/test_cases/test_utils_database.py @@ -1,3 +1,9 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' import pytest from server.utils.database import get_unique_fields @@ -55,4 +61,4 @@ def test_unique_fields_workspace(obj_class, expected_unique_fields, session): object_ = obj_class() unique_constraints = get_unique_fields(session, object_) for unique_constraint in unique_constraints: - assert unique_constraint == expected_unique_fields \ No newline at end of file + assert unique_constraint == expected_unique_fields From 2bc297eb0e5058ed949c71eaaa9975385a35468c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 31 May 2018 12:17:41 -0300 Subject: [PATCH 1210/1506] Fix a couple non-deterministic test cases A module-level monkeypatch of one test suite broke another one depending of the server.ini file contents. Fixed --- test_cases/test_server_io.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test_cases/test_server_io.py b/test_cases/test_server_io.py index ec5207856a1..3e9f0346532 100644 --- a/test_cases/test_server_io.py +++ b/test_cases/test_server_io.py @@ -15,8 +15,6 @@ from persistence.server import server_io_exceptions from mock import MagicMock, patch -server.FARADAY_UP = False -server.SERVER_URL = "http://localhost:5984" example_url = "http://just_some_url" @@ -25,6 +23,14 @@ class ClientServerAPITests(unittest.TestCase): def setUp(self): self.ws_name = "a_ws" self.server_api_url = "http://localhost:5984/_api" + self.old_faraday_up = server.FARADAY_UP + self.old_server_url = server.SERVER_URL + server.FARADAY_UP = False + server.SERVER_URL = "http://localhost:5984" + + def tearDown(self): + server.FARADAY_UP = self.old_faraday_up + server.SERVER_URL = self.old_server_url def test_get_base_server_url(self): s = server._get_base_server_url() From 3146971b7a665c59fc78d39956100ec3831d17ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 31 May 2018 13:07:25 -0300 Subject: [PATCH 1211/1506] Revert "Fix a couple non-deterministic test cases" This reverts commit 2bc297eb0e5058ed949c71eaaa9975385a35468c. --- test_cases/test_server_io.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test_cases/test_server_io.py b/test_cases/test_server_io.py index 3e9f0346532..ec5207856a1 100644 --- a/test_cases/test_server_io.py +++ b/test_cases/test_server_io.py @@ -15,6 +15,8 @@ from persistence.server import server_io_exceptions from mock import MagicMock, patch +server.FARADAY_UP = False +server.SERVER_URL = "http://localhost:5984" example_url = "http://just_some_url" @@ -23,14 +25,6 @@ class ClientServerAPITests(unittest.TestCase): def setUp(self): self.ws_name = "a_ws" self.server_api_url = "http://localhost:5984/_api" - self.old_faraday_up = server.FARADAY_UP - self.old_server_url = server.SERVER_URL - server.FARADAY_UP = False - server.SERVER_URL = "http://localhost:5984" - - def tearDown(self): - server.FARADAY_UP = self.old_faraday_up - server.SERVER_URL = self.old_server_url def test_get_base_server_url(self): s = server._get_base_server_url() From 44df67cb8bfe3f33c9580c40f9fa52fca18eaf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 31 May 2018 13:09:47 -0300 Subject: [PATCH 1212/1506] Correct test case monkeypatched url --- test_cases/test_server_io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_cases/test_server_io.py b/test_cases/test_server_io.py index ec5207856a1..543a9b7d8b0 100644 --- a/test_cases/test_server_io.py +++ b/test_cases/test_server_io.py @@ -16,7 +16,7 @@ from mock import MagicMock, patch server.FARADAY_UP = False -server.SERVER_URL = "http://localhost:5984" +server.SERVER_URL = "http://localhost:5985" example_url = "http://just_some_url" @@ -24,7 +24,7 @@ class ClientServerAPITests(unittest.TestCase): def setUp(self): self.ws_name = "a_ws" - self.server_api_url = "http://localhost:5984/_api" + self.server_api_url = "http://localhost:5985/_api" def test_get_base_server_url(self): s = server._get_base_server_url() From ca5a6600f14b42c330850431253c62ead503654b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 31 May 2018 14:41:51 -0300 Subject: [PATCH 1213/1506] Make workspace name not blankable This was on pink but not in white. --- server/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index abe684513fb..c809e452277 100644 --- a/server/models.py +++ b/server/models.py @@ -1163,7 +1163,7 @@ class Workspace(Metadata): description = BlankColumn(Text) active = Column(Boolean(), nullable=False, default=True) # TBI end_date = Column(DateTime(), nullable=True) - name = Column(String(250), nullable=False, unique=True) + name = NonBlankColumn(String(250), unique=True, nullable=False) public = Column(Boolean(), nullable=False, default=False) # TBI start_date = Column(DateTime(), nullable=True) From 2a462a42361214d933b3065a36a29462a5a2550f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 31 May 2018 15:04:01 -0300 Subject: [PATCH 1214/1506] Update rollbar environment for white --- server/www/scripts/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/app.js b/server/www/scripts/app.js index 00117b132c4..88be6e820e2 100644 --- a/server/www/scripts/app.js +++ b/server/www/scripts/app.js @@ -291,7 +291,7 @@ faradayApp.config(['$routeProvider', 'ngClipProvider', '$uibTooltipProvider', 'R accessToken: "70f0c36ae96d4ffc90394565b42c5bf9", captureUncaught: true, payload: { - environment: "white" + environment: "white-newdesign" }}); }]); From b5267c0620d2c06386384a43082ea74f660797bf Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 31 May 2018 17:10:35 -0300 Subject: [PATCH 1215/1506] [ADD] Add version 3.0a0 to white --- VERSION | 2 +- plugins/plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 860487ca19c..78480b2a640 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.1 +3.0a0+white diff --git a/plugins/plugin.py b/plugins/plugin.py index 473d36aa2a1..b45b4804933 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -33,7 +33,7 @@ from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() -VERSION = server.config.__get_version() +VERSION = server.config.__get_version().split('+')[0] logger = logging.getLogger(__name__) From d4ba3b707891bade27a3ebc3e84c24d2431f99a9 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 31 May 2018 17:20:23 -0300 Subject: [PATCH 1216/1506] [MOD] update release.md --- RELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.md b/RELEASE.md index d1925242e10..53acf5e2f11 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,6 +10,7 @@ New features in the latest update TBA: --- +* Add reconng plugin * CouchDB was replaced by PostgreSQL :) * Host object changed, now the name property is called ip * Interface object was removed From 3ae9f663de29fe7ee466c7a1d22189d1cc99b1e0 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 31 May 2018 17:34:53 -0300 Subject: [PATCH 1217/1506] [MOD] add colors to database checks --- faraday-server.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/faraday-server.py b/faraday-server.py index 8b8787947a2..ae70c0a5f68 100755 --- a/faraday-server.py +++ b/faraday-server.py @@ -8,6 +8,7 @@ import subprocess import sqlalchemy +from colorama import init, Fore import server.config import server.couchdb @@ -20,6 +21,7 @@ from faraday import FARADAY_BASE logger = server.utils.logger.get_logger(__name__) +init() def setup_environment(check_deps=False): @@ -90,7 +92,7 @@ def check_postgresql(): logger.warn('No workspaces found. Remeber to execute couchdb importer') except sqlalchemy.exc.OperationalError: logger.error( - 'Could not connect to postgresql, please check if database is running or configuration settings are correct. For first time installations execute python manage.py initdb') + '\n\n{RED}Could not connect to postgresql.\nPlease check{WHITE}: \n{YELLOW} * if database is running \n * configuration settings are correct. \n\n{RED}For first time installations execute{WHITE}: \n\n {GREEN} python manage.py initdb\n\n'.format(GREEN=Fore.GREEN, YELLOW=Fore.YELLOW, WHITE=Fore.WHITE, RED=Fore.RED)) sys.exit(1) From caf1e3457253fc844b7752538a11e5501413e29c Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 31 May 2018 18:13:51 -0300 Subject: [PATCH 1218/1506] Import from couchdb when using initdb --- manage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manage.py b/manage.py index 0626d348690..1356b8b1111 100755 --- a/manage.py +++ b/manage.py @@ -75,6 +75,7 @@ def faraday_schema_display(): def initdb(): with app.app_context(): InitDB().run() + ImportCouchDB().run() @click.command() def import_from_couchdb(): From 3833610f7c30706c7d41fc19497c9fb7abdae018 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Fri, 1 Jun 2018 08:33:55 -0300 Subject: [PATCH 1219/1506] Fixing issues and navigation enhacements --- server/www/estilos-v3.css | 4 ++++ server/www/scripts/commons/controllers/headerCtrl.js | 9 ++++----- server/www/scripts/commons/partials/header.html | 4 ++-- server/www/scripts/credentials/partials/list.html | 2 +- server/www/scripts/services/partials/list.html | 4 ++-- .../www/scripts/statusReport/controllers/statusReport.js | 2 +- server/www/scripts/statusReport/styles/status.css | 8 ++++---- server/www/scripts/vulndb/partials/vulndb.html | 2 +- server/www/scripts/vulns/providers/vulns.js | 9 +++++++-- 9 files changed, 26 insertions(+), 18 deletions(-) diff --git a/server/www/estilos-v3.css b/server/www/estilos-v3.css index 006459f3e7f..ddb5bef68be 100644 --- a/server/www/estilos-v3.css +++ b/server/www/estilos-v3.css @@ -144,3 +144,7 @@ .justify-flex-start { justify-content: flex-start !important; } + +.max-sized-table { + max-width: 1200px; +} diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 427701c66bc..05cd681a7f0 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -13,6 +13,10 @@ angular.module('faradayApp') return noNav.indexOf($scope.component) < 0; }; + $scope.getVulnsNum = function() { + return vulnsManager.getVulnsNum(); + }; + init = function(name) { $scope.location = $location.path().split('/')[1]; $scope.workspace = $routeParams.wsId; @@ -21,11 +25,6 @@ angular.module('faradayApp') workspacesFact.list().then(function(wss) { $scope.workspaces = wss; }); - - vulnsManager.getVulns($scope.workspace, null, null, null, null, null) - .then(function(response) { - $scope.totalItems = response.count; - }); }; init(); diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index 51fe3347f84..56d5b188308 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -19,12 +19,12 @@
- {{totalItems}} vulns total + {{getVulnsNum()}} vulns total
diff --git a/server/www/scripts/credentials/partials/list.html b/server/www/scripts/credentials/partials/list.html index abfd04b30a8..173b20a8789 100644 --- a/server/www/scripts/credentials/partials/list.html +++ b/server/www/scripts/credentials/partials/list.html @@ -41,7 +41,7 @@
- +
diff --git a/server/www/scripts/services/partials/list.html b/server/www/scripts/services/partials/list.html index 808bb0070ca..75666d3f9ed 100644 --- a/server/www/scripts/services/partials/list.html +++ b/server/www/scripts/services/partials/list.html @@ -21,7 +21,7 @@

- +

Host services

-
+

Host details diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index 95009806ae3..3bdee68cf42 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -297,7 +297,7 @@ angular.module("faradayApp") $scope.gridOptions.columnDefs.push({ name : 'severity', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/severitycolumn.html', headerCellTemplate: header, - displayName : "sev", + displayName : "severity", type: 'string', visible: $scope.columns["severity"], sort: getColumnSort('severity'), diff --git a/server/www/scripts/statusReport/styles/status.css b/server/www/scripts/statusReport/styles/status.css index 01834b6ba50..eb3b6f78d02 100644 --- a/server/www/scripts/statusReport/styles/status.css +++ b/server/www/scripts/statusReport/styles/status.css @@ -87,7 +87,7 @@ } .status-report-grid .status.opened { - background-color: rgba(223, 57, 54, .6) !important; + background-color: rgba(223, 57, 54, .4) !important; text-align: center; } @@ -100,7 +100,7 @@ } .status-report-grid .status.risk-accepted { - background-color: rgba(46, 151, 189, 0.6) !important; + background-color: rgba(46, 151, 189, 0.4) !important; text-align: center; } @@ -109,7 +109,7 @@ } .status-report-grid .status.re-opened { - background-color: rgba(223, 191, 53, 0.6) !important; + background-color: rgba(223, 191, 53, 0.4) !important; text-align: center; } @@ -118,7 +118,7 @@ } .status-report-grid .status.closed { - background-color: rgba(161, 206, 49, 0.6) !important; + background-color: rgba(161, 206, 49, 0.4) !important; text-align: center; } diff --git a/server/www/scripts/vulndb/partials/vulndb.html b/server/www/scripts/vulndb/partials/vulndb.html index 0fa0f36b78c..06d94c8e6d1 100644 --- a/server/www/scripts/vulndb/partials/vulndb.html +++ b/server/www/scripts/vulndb/partials/vulndb.html @@ -6,7 +6,7 @@
-
+
-
-
From 968fb8ce932383c21ae254b4eec8a81bc334e023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 1 Jun 2018 15:41:09 -0300 Subject: [PATCH 1223/1506] Add ugly hack to fill workspace vuln count in credentials view --- .../scripts/credentials/controllers/credentials.js | 7 +++++-- server/www/scripts/vulns/providers/vulns.js | 11 +++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/server/www/scripts/credentials/controllers/credentials.js b/server/www/scripts/credentials/controllers/credentials.js index b4bdb571176..c9c6a6cae77 100644 --- a/server/www/scripts/credentials/controllers/credentials.js +++ b/server/www/scripts/credentials/controllers/credentials.js @@ -6,8 +6,8 @@ angular.module('faradayApp') .controller('credentialsCtrl', - ['$scope', '$filter', '$q', '$uibModal', '$routeParams', '$window', 'commonsFact', 'credential', 'ServerAPI', 'workspacesFact', - function($scope, $filter, $q, $uibModal, $routeParams, $window, commonsFact, credential, ServerAPI, workspacesFact) { + ['$scope', '$filter', '$q', '$uibModal', '$routeParams', '$window', 'commonsFact', 'credential', 'ServerAPI', 'workspacesFact', 'vulnsManager', + function($scope, $filter, $q, $uibModal, $routeParams, $window, commonsFact, credential, ServerAPI, workspacesFact, vulnsManager) { $scope.workspace; $scope.workspaces; @@ -112,6 +112,9 @@ angular.module('faradayApp') getParent().then(function(){ getAndLoadCredentials(); }); + + // Make the workspace vuln counter work + vulnsManager.loadVulnsCounter($scope.workspace); }; var removeFromView = function(credential){ diff --git a/server/www/scripts/vulns/providers/vulns.js b/server/www/scripts/vulns/providers/vulns.js index a7f43824a3e..8b5aa56d0d5 100644 --- a/server/www/scripts/vulns/providers/vulns.js +++ b/server/www/scripts/vulns/providers/vulns.js @@ -4,8 +4,8 @@ angular.module('faradayApp') .factory('vulnsManager', - ['Vuln', 'WebVuln', '$q', 'ServerAPI', 'commonsFact', - function(Vuln, WebVuln, $q, ServerAPI, commonsFact) { + ['Vuln', 'WebVuln', '$q', 'ServerAPI', 'commonsFact', 'workspacesFact', + function(Vuln, WebVuln, $q, ServerAPI, commonsFact, workspacesFact) { var vulnsManager = {}; var vulnsCounter = 0; @@ -71,6 +71,13 @@ angular.module('faradayApp') return deferred.promise; }; + vulnsManager.loadVulnsCounter = function(ws){ + // Ugly hack to populate the vulnsCounter global variable + workspacesFact.get(ws).then(function(data){ + vulnsCounter = data.stats.total_vulns; + }) + }; + vulnsManager.getVulnsNum = function() { return vulnsCounter; }; From df0baadd5d3d4ed65b052ddbbe5603120ac78745 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Fri, 1 Jun 2018 17:11:06 -0300 Subject: [PATCH 1224/1506] fixes to switcher for white --- server/www/header.css | 6 +++++- .../scripts/commons/controllers/headerCtrl.js | 9 ++++----- .../www/scripts/commons/partials/header.html | 18 ++++++++++++++++-- server/www/scripts/hosts/partials/list.html | 2 +- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/server/www/header.css b/server/www/header.css index 0642579e655..4bd7022d6d2 100644 --- a/server/www/header.css +++ b/server/www/header.css @@ -32,12 +32,16 @@ float: left; } -.workspace-selector-wrapper { +.workspace-selector-wrapper, .icon-wrapper { border-radius: 5px; margin-right: 20px; padding: 10px; } +.icon-wrapper { + padding-top: 15px; +} + .workspace-selector-wrapper, .workspace-switcher { display: flex; } diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 05cd681a7f0..50294ee957e 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -7,12 +7,11 @@ angular.module('faradayApp') ['$scope', '$routeParams', '$location', 'dashboardSrv', 'workspacesFact', 'vulnsManager', function($scope, $routeParams, $location, dashboardSrv, workspacesFact, vulnsManager) { - - $scope.showHeader = function() { - var noNav = ["", "home", "login", "index"]; - return noNav.indexOf($scope.component) < 0; + $scope.showSwitcher = function() { + var noSwitcher = ["", "home", "login", "index", "vulndb", "credentials", "workspaces", "users", "licenses"]; + return noSwitcher.indexOf($scope.component) < 0; }; - + $scope.getVulnsNum = function() { return vulnsManager.getVulnsNum(); }; diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index 56d5b188308..f2a8db4be6d 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -1,5 +1,5 @@ -
-
+
+
@@ -29,6 +32,17 @@
+
+
+ Dashboard + Status Report + Hosts + Credentials + Executive Report + Tasks + Vulns +
+

- - - - + + + + +
- GOS + HOSTNAMES From e2cc7160850f0dc9e61b7132f39d5d67bad241bb Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Fri, 1 Jun 2018 17:26:05 -0300 Subject: [PATCH 1225/1506] alignment on select all --- server/www/scripts/statusReport/styles/status.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/statusReport/styles/status.css b/server/www/scripts/statusReport/styles/status.css index eb3b6f78d02..9694a701299 100644 --- a/server/www/scripts/statusReport/styles/status.css +++ b/server/www/scripts/statusReport/styles/status.css @@ -79,7 +79,7 @@ .hosts-list .ui-grid-header .hosts-list-checkall{ position: relative; top: 3px; - left: 7px; + text-align: center; } .status-report-grid .ui-grid-pager-panel { From d47c8b9f3caef50f7d9e54c7d894f4f8ef23f75b Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 1 Jun 2018 19:20:48 -0300 Subject: [PATCH 1226/1506] Current user saved in Command object when you upload a report in webUi --- managers/reports_managers.py | 10 ++++----- plugins/controller.py | 23 +++++++++++++------ server/api/modules/upload_reports.py | 33 +++++++++++----------------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/managers/reports_managers.py b/managers/reports_managers.py index 28a320d22e3..fb7b082b46d 100755 --- a/managers/reports_managers.py +++ b/managers/reports_managers.py @@ -31,7 +31,7 @@ def __init__(self, plugin_controller, ws_name=None): self.plugin_controller = plugin_controller self.ws_name = ws_name - def processReport(self, filename): + def processReport(self, filename, user=""): """ Process one Report """ @@ -44,13 +44,13 @@ def processReport(self, filename): 'Plugin not found: automatic and manual try!') return False - return self.sendReport(parser.report_type, filename) + return self.sendReport(parser.report_type, filename, user=user) - def sendReport(self, plugin_id, filename): + def sendReport(self, plugin_id, filename, user=""): """Sends a report to the appropiate plugin specified by plugin_id""" getLogger(self).info( 'The file is %s, %s' % (filename, plugin_id)) - if not self.plugin_controller.processReport(plugin_id, filename, self.ws_name): + if not self.plugin_controller.processReport(plugin_id, filename, self.ws_name, user=user): getLogger(self).error( "Faraday doesn't have a plugin for this tool..." " Processing: ABORT") @@ -343,7 +343,7 @@ def rType(self, tag, output): elif "netsparker" == tag: return "Netsparker" elif "netsparker-cloud" == tag: - return "NetsparkerCloud" + return "NetsparkerCloud" elif "maltego" == tag: return "Maltego" elif "lynis" == tag: diff --git a/plugins/controller.py b/plugins/controller.py index 8efd9cd8408..33238aba61e 100644 --- a/plugins/controller.py +++ b/plugins/controller.py @@ -268,15 +268,24 @@ def onCommandFinished(self, pid, exit_code, term_output): del self._active_plugins[pid] return True - def processReport(self, plugin, filepath, ws_name=None): + def processReport(self, plugin, filepath, ws_name=None, user=""): if not ws_name: ws_name = model.api.getActiveWorkspace().name - cmd_info = CommandRunInformation( - **{'workspace': ws_name, - 'itime': time.time(), - 'import_source': 'report', - 'command': plugin, - 'params': filepath}) + if user: + cmd_info = CommandRunInformation( + **{'workspace': ws_name, + 'itime': time.time(), + 'import_source': 'report', + 'command': plugin, + 'params': filepath, + 'user': user}) + else: + cmd_info = CommandRunInformation( + **{'workspace': ws_name, + 'itime': time.time(), + 'import_source': 'report', + 'command': plugin, + 'params': filepath}) self._mapper_manager.createMappers(ws_name) cmd_info.setID(self._mapper_manager.save(cmd_info)) diff --git a/server/api/modules/upload_reports.py b/server/api/modules/upload_reports.py index 32ba00a539e..59e22481598 100644 --- a/server/api/modules/upload_reports.py +++ b/server/api/modules/upload_reports.py @@ -1,4 +1,4 @@ -# Faraday Penetration Test IDE + # Faraday Penetration Test IDE # Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) # See the file 'doc/LICENSE' for the license information @@ -12,20 +12,21 @@ import logging import model.api -from flask import request, abort, jsonify, Blueprint +from flask import request, abort, jsonify, Blueprint, session from werkzeug.utils import secure_filename from server.utils.logger import get_logger from server.utils.web import gzipped from model.controller import ModelController -from model.cli_app import CliApp from plugins.controller import PluginController from plugins.manager import PluginManager -from managers.workspace_manager import WorkspaceManager from managers.mapper_manager import MapperManager +from managers.reports_managers import ReportProcessor + +from server.models import User from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() @@ -39,16 +40,12 @@ class RawReportProcessor(Thread): def __init__(self): super(RawReportProcessor, self).__init__() - plugin_manager = PluginManager(os.path.join(CONF.getConfigPath(), "plugins")) - mappers_manager = MapperManager() self.pending_actions = Queue() + plugin_manager = PluginManager(os.path.join(CONF.getConfigPath(), "plugins")) + mappers_manager = MapperManager() model_controller = ModelController(mappers_manager, self.pending_actions) - - workspace_manager = WorkspaceManager(mappers_manager) - CONF.setMergeStrategy("new") - model.api.setUpAPIs(model_controller, workspace_manager, 0, 0) model_controller.start() plugin_controller = PluginController( @@ -57,22 +54,18 @@ def __init__(self): mappers_manager, self.pending_actions) - self.cli_import = CliApp(workspace_manager, plugin_controller) + self.processor = ReportProcessor(plugin_controller, None) def run(self): while True: try: - workspace, file_path = UPLOAD_REPORTS_QUEUE.get() + workspace, file_path, username = UPLOAD_REPORTS_QUEUE.get() logger.info('Processing raw report {0}'.format(file_path)) - class Arg(): - def __init__(self): - self.workspace = workspace - self.filename = file_path - - self.cli_import.run(Arg()) + self.processor.ws_name = workspace + self.processor.processReport(file_path, user=username) except KeyboardInterrupt: break @@ -83,7 +76,6 @@ def file_upload(workspace=None): """ Upload a report file to Server and process that report with Faraday client plugins. """ - get_logger(__name__).debug("Importing new plugin report in server...") if 'file' not in request.files: @@ -107,6 +99,7 @@ def file_upload(workspace=None): with open(file_path, 'w') as output: output.write(report_file.read()) - UPLOAD_REPORTS_QUEUE.put((workspace, file_path)) + user = User.query.filter_by(id=session["user_id"]).first() + UPLOAD_REPORTS_QUEUE.put((workspace, file_path, user.username)) return jsonify({"status": "processing"}) From a15c4815c24e676526c8602073b85fcc801558c4 Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Mon, 4 Jun 2018 10:50:59 -0300 Subject: [PATCH 1227/1506] fixes to statusreport --- .../statusReport/controllers/statusReport.js | 3 ++ .../partials/ui-grid/columns/namecolumn.html | 3 +- .../ui-grid/columns/servicecolumn.html | 7 ++- .../ui-grid/columns/severitycolumn.html | 2 +- .../ui-grid/columns/statuscolumn.html | 50 +++---------------- 5 files changed, 18 insertions(+), 47 deletions(-) diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index 3bdee68cf42..c4c52d577ef 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -313,6 +313,8 @@ angular.module("faradayApp") cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/servicecolumn.html', headerCellTemplate: header, visible: $scope.columns["service"], + field: "service.summary", + displayName : "service", sort: getColumnSort('service'), }); $scope.gridOptions.columnDefs.push({ name : 'hostnames', @@ -383,6 +385,7 @@ angular.module("faradayApp") $scope.gridOptions.columnDefs.push({ name : 'status', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/statuscolumn.html', headerCellTemplate: header, + field: "status", sort: getColumnSort('status'), visible: $scope.columns["status"] }); diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/namecolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/namecolumn.html index 18516fdc150..2213902cc5f 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/namecolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/namecolumn.html @@ -1,4 +1,5 @@
{{COL_FIELD CUSTOM_FILTERS}}
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/servicecolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/servicecolumn.html index 452abf19aa6..1ec3afa5484 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/servicecolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/servicecolumn.html @@ -1 +1,6 @@ -
{{COL_FIELD ? COL_FIELD.summary : 'EMPTY'}}
+
+ +
+
{{COL_FIELD}}
diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html index 7fc5bc2a9d1..9328ee760c4 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/severitycolumn.html @@ -6,6 +6,6 @@ -
+
{{COL_FIELD | uppercase}}
\ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html index 06c3bcee67f..9b4ba5fee07 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html @@ -1,50 +1,12 @@ \ No newline at end of file From 6381649b8833a21bd3734f8e6c863f1b01916185 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 4 Jun 2018 15:46:49 -0300 Subject: [PATCH 1228/1506] [FIX] add authentication to import csv --- bin/fplugin | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/bin/fplugin b/bin/fplugin index 7c37661b25d..cbdc9f92d78 100755 --- a/bin/fplugin +++ b/bin/fplugin @@ -26,6 +26,7 @@ from colorama import Fore from config.configuration import getInstanceConfiguration from managers.mapper_manager import MapperManager from model.controller import ModelController +from persistence.server.server import login_user CONF = getInstanceConfiguration() @@ -48,7 +49,14 @@ def signal_handler(signal, frame): os._exit(0) -def dispatch(args, unknown, help): +def dispatch(args, unknown, user_help, username, password): + session_cookie = login_user(args.url, username, password) + if not session_cookie: + raise UserWarning('Invalid credentials!') + else: + CONF.setDBUser(username) + CONF.setDBSessionCookies(session_cookie) + if '--' in unknown: unknown.remove('--') @@ -58,7 +66,7 @@ def dispatch(args, unknown, help): model_controller = ModelController(mappers_manager, pending_actions) if not args.command: - print help + print(user_help) if not args.interactive: sys.exit(1) @@ -79,6 +87,8 @@ def dispatch(args, unknown, help): module_fplugin = imp.load_source('module_fplugin', plugin_path) module_fplugin.models.server.FARADAY_UP = False module_fplugin.models.server.SERVER_URL = args.url + module_fplugin.models.server.AUTH_USER = username + module_fplugin.models.server.AUTH_PASS = password call_main = getattr(module_fplugin, 'main', None) @@ -167,11 +177,19 @@ if __name__ == '__main__': help='Faraday Server URL. Example: http://localhost:5985', default='http://localhost:5985') + parser.add_argument( + '--username', + required=True) + + parser.add_argument( + '--password', + required=True) + # Only parse known args. Unknown ones will be passed on the the called script args, unknown = parser.parse_known_args() if not args.interactive: - dispatch(args, unknown, parser.format_help()) + dispatch(args, unknown, parser.format_help(), args.username, args.password) else: # print "Loading command history..." @@ -215,7 +233,7 @@ if __name__ == '__main__': parsed_args, new_unknown = parser.parse_known_args(new_args) parsed_args.interactive = True - last_id = dispatch(parsed_args, new_unknown, parser.format_help()) or last_id + last_id = dispatch(parsed_args, new_unknown, parser.format_help(), args.username, args.password) or last_id # print '$last = %s' % last_id except (EOFError, KeyboardInterrupt): print 'Bye Bye!' From 07f959403b380bbc05c9e5fc9693a183217dfedf Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Mon, 4 Jun 2018 16:28:21 -0300 Subject: [PATCH 1229/1506] navigation active opacity when changing active url --- server/www/estilos.css | 1 + .../navigation/controllers/navigationCtrl.js | 2 +- .../scripts/navigation/partials/leftBar.html | 20 +++++++++---------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/server/www/estilos.css b/server/www/estilos.css index 5a006d6b869..ea9b2c3e230 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -126,6 +126,7 @@ header.head { aside nav a.active { background-color: #d9d9d9; + opacity: 1; } .left-nav ul{ diff --git a/server/www/scripts/navigation/controllers/navigationCtrl.js b/server/www/scripts/navigation/controllers/navigationCtrl.js index da945b23f79..3453885f859 100644 --- a/server/www/scripts/navigation/controllers/navigationCtrl.js +++ b/server/www/scripts/navigation/controllers/navigationCtrl.js @@ -6,7 +6,7 @@ angular.module('faradayApp') .controller('navigationCtrl', ['$scope', '$http', '$route', '$routeParams', '$cookies', '$location', '$interval', '$uibModal', 'configSrv', 'workspacesFact', 'Notification', function($scope, $http, $route, $routeParams, $cookies, $location, $interval, $uibModal, configSrv, workspacesFact, Notification) { - $scope.workspace = "asd"; + $scope.workspace = ""; $scope.component = ""; var componentsNeedsWS = ["dashboard","status","hosts"]; diff --git a/server/www/scripts/navigation/partials/leftBar.html b/server/www/scripts/navigation/partials/leftBar.html index 3e38ec2e35f..02f09f68e45 100644 --- a/server/www/scripts/navigation/partials/leftBar.html +++ b/server/www/scripts/navigation/partials/leftBar.html @@ -10,52 +10,52 @@
@@ -41,6 +42,7 @@ Executive Report Tasks Vulns +

Licenses

diff --git a/server/www/scripts/licenses/partials/list.html b/server/www/scripts/licenses/partials/list.html index fd4e013cd74..43bf9d67b3f 100644 --- a/server/www/scripts/licenses/partials/list.html +++ b/server/www/scripts/licenses/partials/list.html @@ -4,10 +4,8 @@
+
-

- Licenses ({{licenses.length}}) -

From 44f65eb76f2b015dfd825dfae7a0e592b2e98d2a Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 6 Jun 2018 08:35:09 -0300 Subject: [PATCH 1239/1506] 4814 - Add missing 'New' button on vulndb --- server/www/scripts/vulndb/partials/vulndb.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/www/scripts/vulndb/partials/vulndb.html b/server/www/scripts/vulndb/partials/vulndb.html index 06d94c8e6d1..50fa79cb4d2 100644 --- a/server/www/scripts/vulndb/partials/vulndb.html +++ b/server/www/scripts/vulndb/partials/vulndb.html @@ -6,11 +6,11 @@
-
+
-
@@ -36,6 +36,12 @@
+
+
+ +
From 858a63fefdc30e19bd49ea404fb382008ad71d28 Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 6 Jun 2018 09:12:22 -0300 Subject: [PATCH 1240/1506] 4803 - Fix host edit/details margin --- server/www/scripts/services/partials/list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/services/partials/list.html b/server/www/scripts/services/partials/list.html index 75666d3f9ed..465c23e9b1b 100644 --- a/server/www/scripts/services/partials/list.html +++ b/server/www/scripts/services/partials/list.html @@ -21,7 +21,7 @@

- +

Host services

@@ -43,6 +44,7 @@ Tasks Vulns

Licenses

+

Workspaces

diff --git a/server/www/scripts/statusReport/styles/status.css b/server/www/scripts/statusReport/styles/status.css index 9694a701299..7f7ac41dd1c 100644 --- a/server/www/scripts/statusReport/styles/status.css +++ b/server/www/scripts/statusReport/styles/status.css @@ -86,6 +86,7 @@ background-color: #f1f1f1; } + .status-report-grid .status.opened { background-color: rgba(223, 57, 54, .4) !important; text-align: center; @@ -270,3 +271,14 @@ button.btn-new { margin-right: 16px; } + +.status-report-grid .ui-grid-row-selected .ui-grid-cell .status { + color: white !important; + background-color:transparent !important; +} + + +.status-report-grid .ui-grid-row-selected .ui-grid-cell .status span { + color: white !important; + background-color:transparent !important; +} \ No newline at end of file diff --git a/server/www/scripts/workspaces/partials/list.html b/server/www/scripts/workspaces/partials/list.html index b1edcec9309..1a14b41b82f 100644 --- a/server/www/scripts/workspaces/partials/list.html +++ b/server/www/scripts/workspaces/partials/list.html @@ -4,42 +4,11 @@
-
-

-
-
-
- Workspaces -
- -
-
-

+
+ +
+
+
From c08602f8a0d871ba08a2a91d08f8a04b700b44cc Mon Sep 17 00:00:00 2001 From: Nicolas Ronchi Cesarini Date: Wed, 6 Jun 2018 12:37:58 -0300 Subject: [PATCH 1243/1506] add field last modified --- server/www/scripts/workspaces/partials/list.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/www/scripts/workspaces/partials/list.html b/server/www/scripts/workspaces/partials/list.html index 1a14b41b82f..3b6e92eb011 100644 --- a/server/www/scripts/workspaces/partials/list.html +++ b/server/www/scripts/workspaces/partials/list.html @@ -48,6 +48,8 @@
+ + @@ -63,6 +65,7 @@ + From baa030ba590ba89427392601f6f03086a82be4d3 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 6 Jun 2018 12:43:02 -0300 Subject: [PATCH 1244/1506] [FIX] mock default SERVER_URL value --- persistence/server/server.py | 5 ++--- test_cases/test_persistence_server_models.py | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/persistence/server/server.py b/persistence/server/server.py index dbdfdae66e1..75bed0230c6 100644 --- a/persistence/server/server.py +++ b/persistence/server/server.py @@ -27,7 +27,7 @@ import os import json import logging -import ConfigParser +from ConfigParser import SafeConfigParser try: import urlparse @@ -77,7 +77,6 @@ def _conf(): - from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() @@ -94,7 +93,7 @@ def _conf(): def _get_base_server_url(): if FARADAY_UPLOAD_REPORTS_WEB_COOKIE: - parser = ConfigParser.SafeConfigParser() + parser = SafeConfigParser() parser.read(LOCAL_CONFIG_FILE) server_url = 'http://{0}:{1}'.format( parser.get('faraday_server', 'bind_address'), diff --git a/test_cases/test_persistence_server_models.py b/test_cases/test_persistence_server_models.py index c1ecdf65a8c..0a625fa6a57 100644 --- a/test_cases/test_persistence_server_models.py +++ b/test_cases/test_persistence_server_models.py @@ -23,11 +23,12 @@ class TestVulnPersistanceModelsFuncions(GenericAPITest): @responses.activate @patch('config.configuration.getInstanceConfiguration') + @patch('persistence.server.server.SERVER_URL', 'http://localhost:5985') def test_persistence_server_update_vuln(self, getInstanceConfigurationMock): fo = self.first_object conf_mock = Mock() getInstanceConfigurationMock.return_value = conf_mock - port = 5984 + port = 5985 conf_mock.getDBSessionCookies.return_value = None conf_mock.getAPIUrl.return_value = 'http://localhost:{0}'.format(port) conf_mock.getServerURI.return_value = 'http://localhost:{0}'.format(port) @@ -97,12 +98,13 @@ class TestVulnWebPersistanceModelsFuncions(GenericAPITest): @responses.activate @patch('config.configuration.getInstanceConfiguration') + @patch('persistence.server.server.SERVER_URL', 'http://localhost:5985') def test_persistence_server_update_vuln_web(self, getInstanceConfigurationMock): fo = self.first_object conf_mock = Mock() getInstanceConfigurationMock.return_value = conf_mock - port = 5984 + port = 5985 conf_mock.getDBSessionCookies.return_value = None conf_mock.getAPIUrl.return_value = 'http://localhost:{0}'.format(port) conf_mock.getServerURI.return_value = 'http://localhost:{0}'.format(port) From 384a4385d660991032b22f7450e30b1ca9a7f7b9 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 6 Jun 2018 12:56:10 -0300 Subject: [PATCH 1245/1506] [ADD] add create_date and update_date --- server/api/modules/workspaces.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/api/modules/workspaces.py b/server/api/modules/workspaces.py index 884b33f5647..9149ff3a34b 100644 --- a/server/api/modules/workspaces.py +++ b/server/api/modules/workspaces.py @@ -63,10 +63,18 @@ class WorkspaceSchema(AutoSchema): fields.List(fields.String) ) + create_date = fields.DateTime(attribute='create_date', + dump_only=True) + + update_date = fields.DateTime(attribute='update_date', + dump_only=True) + + class Meta: model = Workspace fields = ('_id', 'id', 'customer', 'description', 'active', - 'duration', 'name', 'public', 'scope', 'stats') + 'duration', 'name', 'public', 'scope', 'stats', + 'create_date', 'update_date') @post_load def post_load_duration(self, data): From afdbfedab3943b11f5bc9b7c328989be2febf1fb Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 6 Jun 2018 15:04:08 -0300 Subject: [PATCH 1246/1506] 4810 - Replace new host modal for new host view --- server/www/dashboard-v3.css | 8 ++++++++ server/www/scripts/hosts/controllers/hosts.js | 15 ++------------- server/www/scripts/hosts/partials/new.html | 5 ++--- server/www/scripts/services/partials/list.html | 2 +- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/server/www/dashboard-v3.css b/server/www/dashboard-v3.css index 2d33cd9623e..5f5bfa511be 100644 --- a/server/www/dashboard-v3.css +++ b/server/www/dashboard-v3.css @@ -78,10 +78,18 @@ padding-left: 5px; } +.pl { + padding-left: 15px; +} + .pr-sm { padding-right: 5px; } +.pr { + padding-right: 15px !important; +} + .progress-bar-tall { height: 57px; } diff --git a/server/www/scripts/hosts/controllers/hosts.js b/server/www/scripts/hosts/controllers/hosts.js index f016354bc4d..1291458e69f 100644 --- a/server/www/scripts/hosts/controllers/hosts.js +++ b/server/www/scripts/hosts/controllers/hosts.js @@ -84,7 +84,7 @@ angular.module('faradayApp') }; var createCredential = function(credentialData, parent_id){ - + // Add parent id, create credential and save to server. try { var credentialObj = new credential(credentialData, parent_id); @@ -224,18 +224,7 @@ angular.module('faradayApp') } $scope.new = function() { - var modal = $uibModal.open({ - templateUrl: 'scripts/hosts/partials/modalNew.html', - controller: 'hostsModalNew', - size: 'lg', - resolve: {} - }); - - modal.result.then(function(data) { - var hostdata = data[0]; - var credentialData = data[1]; - $scope.insert(hostdata, credentialData); - }); + $location.path('/host/ws/' + $scope.workspace + '/new'); }; $scope.update = function(host, hostdata) { diff --git a/server/www/scripts/hosts/partials/new.html b/server/www/scripts/hosts/partials/new.html index 4ebd944360f..7e9263d94e0 100644 --- a/server/www/scripts/hosts/partials/new.html +++ b/server/www/scripts/hosts/partials/new.html @@ -19,7 +19,7 @@

-
+

Host details @@ -85,7 +85,7 @@

Description
-
+

Hostnames @@ -109,4 +109,3 @@

- diff --git a/server/www/scripts/services/partials/list.html b/server/www/scripts/services/partials/list.html index 465c23e9b1b..93de17c92bb 100644 --- a/server/www/scripts/services/partials/list.html +++ b/server/www/scripts/services/partials/list.html @@ -21,7 +21,7 @@

- +

Host services

- -
- - - - From 68942df16cf26f4b1eeefca759f62b1836453424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 6 Jun 2018 16:06:09 -0300 Subject: [PATCH 1249/1506] Add CSRF protection to upload reports API endpoint --- server/api/modules/session.py | 5 ++++- server/api/modules/upload_reports.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/server/api/modules/session.py b/server/api/modules/session.py index a8473b03570..cd41c86c8a6 100644 --- a/server/api/modules/session.py +++ b/server/api/modules/session.py @@ -5,10 +5,13 @@ ''' from flask import jsonify, session, Blueprint, current_app +from flask_wtf.csrf import generate_csrf session_api = Blueprint('session_api', __name__) @session_api.route('/session') def session_info(): user = current_app.user_datastore.get_user(session['user_id']) - return jsonify(user.get_security_payload()) + data = user.get_security_payload() + data['csrf_token'] = generate_csrf() + return jsonify(data) diff --git a/server/api/modules/upload_reports.py b/server/api/modules/upload_reports.py index ea1a5f3dbb2..e302dba45e5 100644 --- a/server/api/modules/upload_reports.py +++ b/server/api/modules/upload_reports.py @@ -14,7 +14,9 @@ import model.api from flask import request, abort, jsonify, Blueprint, session, make_response +from flask_wtf.csrf import validate_csrf from werkzeug.utils import secure_filename +from wtforms import ValidationError from server.utils.logger import get_logger from server.utils.web import gzipped @@ -97,6 +99,11 @@ def file_upload(workspace=None): if 'file' not in request.files: abort(400) + try: + validate_csrf(request.form.get('csrf_token')) + except ValidationError: + abort(403) + report_file = request.files['file'] if report_file.filename == '': From c6e7e78e923c3617fb7b578f73abd30b0575374a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 6 Jun 2018 16:06:38 -0300 Subject: [PATCH 1250/1506] Always create the uploaded reports directory Before it was created only on the first installation --- server/config.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/config.py b/server/config.py index 6233f09a52e..13ac2b0bb5c 100644 --- a/server/config.py +++ b/server/config.py @@ -32,6 +32,14 @@ CONFIG_FILES = [DEFAULT_CONFIG_FILE, LOCAL_CONFIG_FILE] WS_BLACKLIST = CONSTANTS.CONST_BLACKDBS +if not os.path.exists(LOCAL_REPORTS_FOLDER): + try: + os.makedirs(LOCAL_REPORTS_FOLDER) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + def copy_default_config_to_local(): @@ -48,13 +56,6 @@ def copy_default_config_to_local(): # Copy default config file into faraday local config shutil.copyfile(DEFAULT_CONFIG_FILE, LOCAL_CONFIG_FILE) - if not os.path.exists(LOCAL_REPORTS_FOLDER): - try: - os.makedirs(LOCAL_REPORTS_FOLDER) - except OSError as e: - if e.errno != errno.EEXIST: - raise - from server.utils.logger import get_logger get_logger(__name__).info(u"Local faraday-server configuration created at {}".format(LOCAL_CONFIG_FILE)) From caef7125c0eb2f36a7c8613b378fe62656d9cdfa Mon Sep 17 00:00:00 2001 From: Guillermo Garcia Date: Wed, 6 Jun 2018 16:29:44 -0300 Subject: [PATCH 1251/1506] 4805 - Add dashboard confirmed button --- server/www/dashboard-v3.css | 5 ++ .../scripts/commons/controllers/headerCtrl.js | 13 ++- .../www/scripts/commons/partials/header.html | 9 +- .../dashboard/controllers/dashboard.js | 6 +- .../scripts/dashboard/partials/dashboard.html | 83 ++++++++++--------- 5 files changed, 65 insertions(+), 51 deletions(-) diff --git a/server/www/dashboard-v3.css b/server/www/dashboard-v3.css index 5f5bfa511be..cbc3990824f 100644 --- a/server/www/dashboard-v3.css +++ b/server/www/dashboard-v3.css @@ -1,3 +1,8 @@ +.dashboard .filter-wrapper .confirm-button { + position: relative; + bottom: 4px; +} + .dashboard .faraday-page-header { margin-bottom: 0px; } diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 50294ee957e..2167d9786b9 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -4,18 +4,25 @@ angular.module('faradayApp') .controller('headerCtrl', - ['$scope', '$routeParams', '$location', 'dashboardSrv', 'workspacesFact', 'vulnsManager', - function($scope, $routeParams, $location, dashboardSrv, workspacesFact, vulnsManager) { + ['$scope', '$routeParams', '$location', '$cookies', 'dashboardSrv', 'workspacesFact', 'vulnsManager', + function($scope, $routeParams, $location, $cookies, dashboardSrv, workspacesFact, vulnsManager) { + $scope.confirmed = ($cookies.get('confirmed') == undefined) ? false : JSON.parse($cookies.get('confirmed')); $scope.showSwitcher = function() { var noSwitcher = ["", "home", "login", "index", "vulndb", "credentials", "workspaces", "users", "licenses"]; return noSwitcher.indexOf($scope.component) < 0; }; - + $scope.getVulnsNum = function() { return vulnsManager.getVulnsNum(); }; + $scope.toggleConfirmed = function() { + $scope.confirmed = !$scope.confirmed; + dashboardSrv.setConfirmed($scope.confirmed); + dashboardSrv.updateData(); + }; + init = function(name) { $scope.location = $location.path().split('/')[1]; $scope.workspace = $routeParams.wsId; diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index 90f71c667db..30de3d0dde9 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -1,7 +1,7 @@
-
-
\ No newline at end of file +
diff --git a/server/www/scripts/dashboard/controllers/dashboard.js b/server/www/scripts/dashboard/controllers/dashboard.js index d3498a45746..5be2303c770 100644 --- a/server/www/scripts/dashboard/controllers/dashboard.js +++ b/server/www/scripts/dashboard/controllers/dashboard.js @@ -16,7 +16,7 @@ angular.module('faradayApp') workspacesFact.list().then(function(wss) { $scope.workspaces = wss; }); - + dashboardSrv.setConfirmedFromCookie(); dashboardSrv.startTimer(); }; @@ -25,10 +25,6 @@ angular.module('faradayApp') $location.path(route); }; - $scope.toggleConfirmed = function() { - dashboardSrv.setConfirmed(); - }; - $scope.$on('$destroy', function(){ dashboardSrv.stopTimer(); }) diff --git a/server/www/scripts/dashboard/partials/dashboard.html b/server/www/scripts/dashboard/partials/dashboard.html index c6225102343..9568eddda52 100644 --- a/server/www/scripts/dashboard/partials/dashboard.html +++ b/server/www/scripts/dashboard/partials/dashboard.html @@ -5,53 +5,54 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
From 9262ff04eac5b65b0643e8d12574505ab40f0015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 6 Jun 2018 16:31:30 -0300 Subject: [PATCH 1252/1506] Redirect to dashboard on successful report imports --- server/api/modules/upload_reports.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/api/modules/upload_reports.py b/server/api/modules/upload_reports.py index e302dba45e5..1f87cfdf861 100644 --- a/server/api/modules/upload_reports.py +++ b/server/api/modules/upload_reports.py @@ -13,7 +13,15 @@ import logging import model.api -from flask import request, abort, jsonify, Blueprint, session, make_response +from flask import ( + redirect, + request, + abort, + jsonify, + Blueprint, + session, + make_response +) from flask_wtf.csrf import validate_csrf from werkzeug.utils import secure_filename from wtforms import ValidationError @@ -125,5 +133,6 @@ def file_upload(workspace=None): UPLOAD_REPORTS_QUEUE.put((workspace, file_path, request.cookies)) command_id = UPLOAD_REPORTS_CMD_QUEUE.get() if command_id: - return jsonify({"status": "processing", "command_id": command_id}) + # return jsonify({"status": "processing", "command_id": command_id}) + return redirect('/#/dashboard/ws/' + workspace) abort(make_response(jsonify(message="Invalid file."), 400)) From 7b7a8973366bbaa5a4515625c4f48b89fcc62a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 6 Jun 2018 16:33:22 -0300 Subject: [PATCH 1253/1506] Add release information of #4751 --- RELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.md b/RELEASE.md index d1925242e10..4daaffdc456 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -32,6 +32,7 @@ TBA: * Add new screenshot fplugin which takes a screenshot of the ip:ports of a given protocol * Add fix for net sparker regular and cloud fix on severity * Removed Chat feature (data is kept inside notes) +* Plugin reports now can be imported in the server, from the Web UI November 17, 2017: --- From 0783cfa1afaa3c2dc85f13a1de2a5b7b132c590e Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Wed, 6 Jun 2018 19:39:20 -0300 Subject: [PATCH 1254/1506] Remove duplicated item, broke API --- data/cwe.csv | 1860 +++++++++++++++++++++++----------------------- data/cwe_all.csv | 1860 +++++++++++++++++++++++----------------------- 2 files changed, 1858 insertions(+), 1862 deletions(-) diff --git a/data/cwe.csv b/data/cwe.csv index 7941463b896..672a83382d8 100644 --- a/data/cwe.csv +++ b/data/cwe.csv @@ -1,4 +1,4 @@ -cwe,name,description,resolution,exploitation,references +cwe,name,description,resolution,exploitation,references CWE-119,EN-Improper Restriction of Operations within the Bounds of a Memory Buffer (Type: Class),"The software performs operations on a memory buffer, but it can read from or write to a memory location that is outside of the intended boundary of the buffer. Certain languages allow direct addressing of memory locations and do not automatically ensure that these locations are valid for the memory buffer that is being referenced. This can cause read or write operations to be performed on memory locations that may be associated with other variables, data structures, or internal program data. As a result, an attacker may be able to execute arbitrary code, alter the intended control flow, read sensitive information, or cause the system to crash.",,high,"Writing Secure Code: Chapter 5, ""Public Enemy #1: The Buffer Overrun"" Page 127; Chapter 14, ""Prevent I18N Buffer Overruns"" Page 441 @@ -9,9 +9,9 @@ Limiting buffer overflows with ExecShield: http://www.redhat.com/magazine/009jul PaX: http://en.wikipedia.org/wiki/PaX Understanding DEP as a mitigation technology part 1: http://blogs.technet.com/b/srd/archive/2009/06/12/understanding-dep-as-a-mitigation-technology-part-1.aspx The Art of Software Security Assessment: Chapter 5, ""Memory Corruption"", Page 167. -The Art of Software Security Assessment: Chapter 5, ""Protection Mechanisms"", Page 189." +The Art of Software Security Assessment: Chapter 5, ""Protection Mechanisms"", Page 189." CWE-123,EN-Write-what-where Condition (Type: Base),"Any condition where the attacker has the ability to write an arbitrary value to an arbitrary location, often as the result of a buffer overflow. -A buffer overflow condition exists when a program attempts to put more data in a buffer than it can hold, or when a program attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the ""classic"" case in which the program copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.",,high,"24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" +A buffer overflow condition exists when a program attempts to put more data in a buffer than it can hold, or when a program attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the ""classic"" case in which the program copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.",,high,"24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" CWE-129,EN-Improper Validation of Array Index (Type: Base),"The product uses untrusted input when calculating or using an array index, but the product does not validate or incorrectly validates the index to ensure the index references a valid position within the array. This typically occurs when the pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. This may result in exposure of sensitive information or possibly a crash.",,high,"Writing Secure Code: Chapter 5, ""Array Indexing Errors"" Page 144 Top 25 Series - Rank 14 - Improper Validation of Array Index: http://blogs.sans.org/appsecstreetfighter/2010/03/12/top-25-series-rank-14-improper-validation-of-array-index/ @@ -19,22 +19,22 @@ Address Space Layout Randomization in Windows Vista: http://blogs.msdn.com/micha PaX: http://en.wikipedia.org/wiki/PaX Understanding DEP as a mitigation technology part 1: http://blogs.technet.com/b/srd/archive/2009/06/12/understanding-dep-as-a-mitigation-technology-part-1.aspx Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html -24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" +24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" CWE-194,EN-Unexpected Sign Extension (Type: Base),"The software performs an operation on a number that causes it to be sign extended when it is transformed into a larger data type. When the original number is negative, this can produce unexpected values that lead to resultant weaknesses. This can happen in signed and unsigned cases.",,high,"C Language Issues for Application Security: http://www.informit.com/articles/article.aspx?p=686170&seqNum=6 -Integral Security: http://www.ddj.com/security/193501774" +Integral Security: http://www.ddj.com/security/193501774" CWE-20,EN-Improper Input Validation (Type: Class),"The product does not validate or incorrectly validates input that can affect the control flow or data flow of a program. When software does not validate input properly, an attacker is able to craft the input in a form that is not expected by the rest of the application. This will lead to parts of the system receiving unintended input, which may result in altered control flow, arbitrary control of a resource, or arbitrary code execution.",,high,"Input Validation with ESAPI - Very Important: http://manicode.blogspot.com/2008/08/input-validation-with-esapi.html OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI Hacking Exposed Web Applications, Second Edition: Input Validation Attacks Input validation or output filtering, which is better?: http://jeremiahgrossman.blogspot.com/2007/01/input-validation-or-output-filtering.html The importance of input validation: http://searchsoftwarequality.techtarget.com/tip/0,289483,sid92_gci1214373,00.html -Writing Secure Code: Chapter 10, ""All Input Is Evil!"" Page 341" +Writing Secure Code: Chapter 10, ""All Input Is Evil!"" Page 341" CWE-200,EN-Information Exposure (Type: Class),"An information exposure is the intentional or unintentional disclosure of information to an actor that is not explicitly authorized to have access to that information. The information either is regarded as sensitive within the product's own functionality, such as a private message; or provides information about the product or its environment that could be useful in an attack but is normally not available to the attacker, such as the installation path of a product that is remotely accessible. -Many information exposures are resultant (e.g. PHP script error revealing the full path of the program), but they can also be primary (e.g. timing discrepancies in cryptography). There are many different types of problems that involve information exposures. Their severity can range widely depending on the type of information that is revealed.",,high,Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/ +Many information exposures are resultant (e.g. PHP script error revealing the full path of the program), but they can also be primary (e.g. timing discrepancies in cryptography). There are many different types of problems that involve information exposures. Their severity can range widely depending on the type of information that is revealed.",,high,Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/ CWE-209,EN-Information Exposure Through an Error Message (Type: Base),"The software generates an error message that includes sensitive information about its environment, users, or associated data. The sensitive information may be valuable information on its own (such as a password), or it may be useful for launching other, more deadly attacks. If an attack fails, an attacker may use error information provided by the server to launch another more focused attack. For example, an attempt to exploit a path traversal weakness (CWE-22) might yield the full pathname of the installed application. In turn, this could be used to select the proper number of "".."" sequences to navigate to the targeted file. An attack using SQL injection (CWE-89) might not initially succeed, but an error message could reveal the malformed query, which would expose query logic and possibly even passwords or other sensitive information used within the query.",,high,"Information Leakage: http://www.webappsec.org/projects/threat/classes/information_leakage.shtml Secure Programming with Static Analysis: Section 9.2, page 326. @@ -42,19 +42,19 @@ Writing Secure Code: Chapter 16, ""General Good Practices."" Page 415 24 Deadly Sins of Software Security: ""Sin 11: Failure to Handle Errors Correctly."" Page 183 24 Deadly Sins of Software Security: ""Sin 12: Information Leakage."" Page 191 Top 25 Series - Rank 16 - Information Exposure Through an Error Message: http://software-security.sans.org/blog/2010/03/17/top-25-series-rank-16-information-exposure-through-an-error-message -The Art of Software Security Assessment: Chapter 3, ""Overly Verbose Error Messages"", Page 75." +The Art of Software Security Assessment: Chapter 3, ""Overly Verbose Error Messages"", Page 75." CWE-234,EN-Failure to Handle Missing Parameter (Type: Variant),"If too few arguments are sent to a function, the function will still pop the expected number of arguments from the stack. Potentially, a variable number of arguments could be exhausted in a function as well. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,high, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,high, CWE-242,EN-Use of Inherently Dangerous Function (Type: Base),"The program calls a function that can never be guaranteed to work safely. Certain functions behave in dangerous ways regardless of how they are used. Functions in this category were often implemented without taking security concerns into account. The gets() function is unsafe because it does not perform bounds checking on the size of its input. An attacker can easily send arbitrarily-sized input to gets() and overflow the destination buffer. Similarly, the >> operator is unsafe to use when reading into a statically-allocated character array because it does not perform bounds checking on the size of its input. An attacker can easily send arbitrarily-sized input to the >> operator and overflow the destination buffer.",,high,"Herb Schildt's C++ Programming Cookbook: Chapter 5. Working with I/O -Writing Secure Code: Chapter 5, ""gets and fgets"" Page 163" +Writing Secure Code: Chapter 5, ""gets and fgets"" Page 163" CWE-243,EN-Creation of chroot Jail Without Changing Working Directory (Type: Variant),"The program uses the chroot() system call to create a jail, but does not change the working directory afterward. This does not prevent access to files outside of the jail. -Improper use of chroot() may allow attackers to escape from the chroot jail. The chroot() function call does not change the process's current working directory, so relative paths may still refer to file system resources outside of the chroot jail after chroot() has been called.",,high, +Improper use of chroot() may allow attackers to escape from the chroot jail. The chroot() function call does not change the process's current working directory, so relative paths may still refer to file system resources outside of the chroot jail after chroot() has been called.",,high, CWE-268,EN-Privilege Chaining (Type: Base),"Two distinct privileges, roles, capabilities, or rights can be combined in a way that allows an entity to perform unsafe actions that would not be allowed without that combination. -Just as neglecting to include functionality for the management of password aging is dangerous, so is allowing password aging to continue unchecked. Passwords must be given a maximum life span, after which a user is required to update with a new and different password.",,high,Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html +Just as neglecting to include functionality for the management of password aging is dangerous, so is allowing password aging to continue unchecked. Passwords must be given a maximum life span, after which a user is required to update with a new and different password.",,high,Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html CWE-271,EN-Privilege Dropping / Lowering Errors (Type: Class),"The software does not drop privileges before passing control of a resource to an actor that does not have those privileges. In some contexts, a system executing with elevated permissions will hand off a process/file/etc. to another process or user. If the privileges of an entity are not reduced, then elevated privileges are spread throughout a system and possibly to an attacker.",,high,"24 Deadly Sins of Software Security: ""Sin 16: Executing Code With Too Much Privilege."" Page 243 -The Art of Software Security Assessment: Chapter 9, ""Dropping Privileges Permanently"", Page 479." +The Art of Software Security Assessment: Chapter 9, ""Dropping Privileges Permanently"", Page 479." CWE-285,EN-Improper Authorization (Type: Class),"The software does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action. Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource. When access control checks are not applied consistently - or not at all - users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.",,high,"Role Based Access Control and Role Based Security: http://csrc.nist.gov/groups/SNS/rbac/ @@ -63,79 +63,79 @@ Top 25 Series - Rank 5 - Improper Access Control (Authorization): http://blogs.s OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI Authentication using JAAS: http://www.javaranch.com/journal/2008/04/authentication-using-JAAS.html The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Authorization"", Page 39. -The Art of Software Security Assessment: Chapter 11, ""ACL Inheritance"", Page 649." +The Art of Software Security Assessment: Chapter 11, ""ACL Inheritance"", Page 649." CWE-291,EN-Reliance on IP Address for Authentication (Type: Variant),"The software uses an IP address for authentication. -IP addresses can be easily spoofed. Attackers can forge the source IP address of the packets they send, but response packets will return to the forged IP address. To see the response packets, the attacker has to sniff the traffic between the victim machine and the forged IP address. In order to accomplish the required sniffing, attackers typically attempt to locate themselves on the same subnet as the victim machine. Attackers may be able to circumvent this requirement by using source routing, but source routing is disabled across much of the Internet today. In summary, IP address verification can be a useful part of an authentication scheme, but it should not be the single factor required for authentication.",,high, +IP addresses can be easily spoofed. Attackers can forge the source IP address of the packets they send, but response packets will return to the forged IP address. To see the response packets, the attacker has to sniff the traffic between the victim machine and the forged IP address. In order to accomplish the required sniffing, attackers typically attempt to locate themselves on the same subnet as the victim machine. Attackers may be able to circumvent this requirement by using source routing, but source routing is disabled across much of the Internet today. In summary, IP address verification can be a useful part of an authentication scheme, but it should not be the single factor required for authentication.",,high, CWE-292,EN-DEPRECATED (Duplicate): Trusting Self-reported DNS Name (Type: Variant),"This entry has been deprecated because it was a duplicate of CWE-350. All content has been transferred to CWE-350. -IP addresses can be easily spoofed. Attackers can forge the source IP address of the packets they send, but response packets will return to the forged IP address. To see the response packets, the attacker has to sniff the traffic between the victim machine and the forged IP address. In order to accomplish the required sniffing, attackers typically attempt to locate themselves on the same subnet as the victim machine. Attackers may be able to circumvent this requirement by using source routing, but source routing is disabled across much of the Internet today. In summary, IP address verification can be a useful part of an authentication scheme, but it should not be the single factor required for authentication.",,high, +IP addresses can be easily spoofed. Attackers can forge the source IP address of the packets they send, but response packets will return to the forged IP address. To see the response packets, the attacker has to sniff the traffic between the victim machine and the forged IP address. In order to accomplish the required sniffing, attackers typically attempt to locate themselves on the same subnet as the victim machine. Attackers may be able to circumvent this requirement by using source routing, but source routing is disabled across much of the Internet today. In summary, IP address verification can be a useful part of an authentication scheme, but it should not be the single factor required for authentication.",,high, CWE-293,EN-Using Referer Field for Authentication (Type: Variant),"The referer field in HTTP requests can be easily modified and, as such, is not a valid means of message integrity checking. -IP addresses can be easily spoofed. Attackers can forge the source IP address of the packets they send, but response packets will return to the forged IP address. To see the response packets, the attacker has to sniff the traffic between the victim machine and the forged IP address. In order to accomplish the required sniffing, attackers typically attempt to locate themselves on the same subnet as the victim machine. Attackers may be able to circumvent this requirement by using source routing, but source routing is disabled across much of the Internet today. In summary, IP address verification can be a useful part of an authentication scheme, but it should not be the single factor required for authentication.",,high,"The Art of Software Security Assessment: Chapter 17, ""Referer Request Header"", Page 1030." +IP addresses can be easily spoofed. Attackers can forge the source IP address of the packets they send, but response packets will return to the forged IP address. To see the response packets, the attacker has to sniff the traffic between the victim machine and the forged IP address. In order to accomplish the required sniffing, attackers typically attempt to locate themselves on the same subnet as the victim machine. Attackers may be able to circumvent this requirement by using source routing, but source routing is disabled across much of the Internet today. In summary, IP address verification can be a useful part of an authentication scheme, but it should not be the single factor required for authentication.",,high,"The Art of Software Security Assessment: Chapter 17, ""Referer Request Header"", Page 1030." CWE-294,EN-Authentication Bypass by Capture-replay (Type: Base),"A capture-replay flaw exists when the design of the software makes it possible for a malicious user to sniff network traffic and bypass authentication by replaying it to the server in question to the same effect as the original message (or with minor changes). -Capture-replay attacks are common and can be difficult to defeat without cryptography. They are a subset of network injection attacks that rely on observing previously-sent valid commands, then changing them slightly if necessary and resending the same commands to the server.",,high, +Capture-replay attacks are common and can be difficult to defeat without cryptography. They are a subset of network injection attacks that rely on observing previously-sent valid commands, then changing them slightly if necessary and resending the same commands to the server.",,high, CWE-297,EN-Improper Validation of Certificate with Host Mismatch (Type: Variant),"The software communicates with a host that provides a certificate, but the software does not properly ensure that the certificate is actually associated with that host. Even if a certificate is well-formed, signed, and follows the chain of trust, it may simply be a valid certificate for a different site than the site that the software is interacting with. If the certificate's host-specific data is not properly checked - such as the Common Name (CN) in the Subject or the Subject Alternative Name (SAN) extension of an X.509 certificate - it may be possible for a redirection or spoofing attack to allow a malicious host with a valid certificate to provide data, impersonating a trusted host. In order to ensure data integrity, the certificate must be valid and it must pertain to the site that is being accessed. Even if the software attempts to check the hostname, it is still possible to incorrectly check the hostname. For example, attackers could create a certificate with a name that begins with a trusted name followed by a NUL byte, which could cause some string-based comparisons to only examine the portion that contains the trusted name.",,high,"The Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software: http://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf Why Eve and Mallory Love Android: An Analysis of Android SSL (In)Security: http://www2.dcsec.uni-hannover.de/files/android/p50-fahl.pdf Secure programming with the OpenSSL API, Part 2: Secure handshake: http://www.ibm.com/developerworks/library/l-openssl2/index.html An Introduction to OpenSSL Programming (Part I): http://www.rtfm.com/openssl-examples/part1.pdf -24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" +24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" CWE-308,EN-Use of Single-factor Authentication (Type: Base),"The use of single-factor authentication can lead to unnecessary risk of compromise when compared with the benefits of a dual-factor authentication scheme. -While the use of multiple authentication schemes is simply piling on more complexity on top of authentication, it is inestimably valuable to have such measures of redundancy. The use of weak, reused, and common passwords is rampant on the internet. Without the added protection of multiple authentication schemes, a single mistake can result in the compromise of an account. For this reason, if multiple schemes are possible and also easy to use, they should be implemented and required.",,high, +While the use of multiple authentication schemes is simply piling on more complexity on top of authentication, it is inestimably valuable to have such measures of redundancy. The use of weak, reused, and common passwords is rampant on the internet. Without the added protection of multiple authentication schemes, a single mistake can result in the compromise of an account. For this reason, if multiple schemes are possible and also easy to use, they should be implemented and required.",,high, CWE-321,EN-Use of Hard-coded Cryptographic Key (Type: Base),"The use of a hard-coded cryptographic key significantly increases the possibility that encrypted data may be recovered. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '...' manipulation is useful for bypassing some path traversal protection schemes. On some Windows systems, it is equivalent to ""..\.."" and might bypass checks that assume only two dots are valid. Incomplete filtering, such as removal of ""./"" sequences, can ultimately produce valid "".."" sequences due to a collapse into unsafe value (CWE-182).",,high, +The '...' manipulation is useful for bypassing some path traversal protection schemes. On some Windows systems, it is equivalent to ""..\.."" and might bypass checks that assume only two dots are valid. Incomplete filtering, such as removal of ""./"" sequences, can ultimately produce valid "".."" sequences due to a collapse into unsafe value (CWE-182).",,high, CWE-322,EN-Key Exchange without Entity Authentication (Type: Base),"The software performs a key exchange with an actor without verifying the identity of that actor. Performing a key exchange will preserve the integrity of the information sent between two entities, but this will not guarantee that the entities are who they claim they are. This may enable a set of ""man-in-the-middle"" attacks. Typically, this involves a victim client that contacts a malicious server that is impersonating a trusted server. If the client skips authentication or ignores an authentication failure, the malicious server may request authentication information from the user. The malicious server can then use this authentication information to log in to the trusted server using the victim's credentials, sniff traffic between the victim and trusted server, etc.",,high,"24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347 -The Art of Software Security Assessment: Chapter 2, ""Untrustworthy Credentials"", Page 37." +The Art of Software Security Assessment: Chapter 2, ""Untrustworthy Credentials"", Page 37." CWE-323,"EN-Reusing a Nonce, Key Pair in Encryption (Type: Base)","Nonces should be used for the present occasion and only once. -Performing a key exchange will preserve the integrity of the information sent between two entities, but this will not guarantee that the entities are who they claim they are. This may enable a set of ""man-in-the-middle"" attacks. Typically, this involves a victim client that contacts a malicious server that is impersonating a trusted server. If the client skips authentication or ignores an authentication failure, the malicious server may request authentication information from the user. The malicious server can then use this authentication information to log in to the trusted server using the victim's credentials, sniff traffic between the victim and trusted server, etc.",,high, +Performing a key exchange will preserve the integrity of the information sent between two entities, but this will not guarantee that the entities are who they claim they are. This may enable a set of ""man-in-the-middle"" attacks. Typically, this involves a victim client that contacts a malicious server that is impersonating a trusted server. If the client skips authentication or ignores an authentication failure, the malicious server may request authentication information from the user. The malicious server can then use this authentication information to log in to the trusted server using the victim's credentials, sniff traffic between the victim and trusted server, etc.",,high, CWE-360,EN-Trust of System Event Data (Type: Base),"Security based on event locations are insecure and can be spoofed. -Events are a messaging system which may provide control data to programs listening for events. Events often do not have any type of authentication framework to allow them to be verified from a trusted source. Any application, in Windows, on a given desktop can send a message to any window on the same desktop. There is no authentication framework for these messages. Therefore, any message can be used to manipulate any process on the desktop if the process does not check the validity and safeness of those messages.",,high, +Events are a messaging system which may provide control data to programs listening for events. Events often do not have any type of authentication framework to allow them to be verified from a trusted source. Any application, in Windows, on a given desktop can send a message to any window on the same desktop. There is no authentication framework for these messages. Therefore, any message can be used to manipulate any process on the desktop if the process does not check the validity and safeness of those messages.",,high, CWE-378,EN-Creation of Temporary File With Insecure Permissions (Type: Base),"Opening temporary files without appropriate measures or controls can leave the file, its contents and any function that it impacts vulnerable to attack. -If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,high, +If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,high, CWE-416,EN-Use After Free (Type: Base),"Referencing memory after it has been freed can cause a program to crash, use unexpected values, or execute code. The use of previously-freed memory can have any number of adverse consequences, ranging from the corruption of valid data to the execution of arbitrary code, depending on the instantiation and timing of the flaw. The simplest way data corruption may occur involves the system's reuse of the freed memory. Use-after-free errors have two common and sometimes overlapping causes: Error conditions and other exceptional circumstances. Confusion over which part of the program is responsible for freeing the memory. In this scenario, the memory in question is allocated to another pointer validly at some point after it has been freed. The original pointer to the freed memory is used again and points to somewhere within the new allocation. As the data is changed, it corrupts the validly used memory; this induces undefined behavior in the process. -If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,high,"24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143" +If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,high,"24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143" CWE-457,EN-Use of Uninitialized Variable (Type: Variant),"The code uses a variable that has not been initialized, leading to unpredictable or unintended results. In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,high,"Exploiting Uninitialized Data: http://www.felinemenace.org/~mercy/papers/UBehavior/UBehavior.zip MS08-014 : The Case of the Uninitialized Stack Variable Vulnerability: http://blogs.technet.com/swi/archive/2008/03/11/the-case-of-the-uninitialized-stack-variable-vulnerability.aspx 24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143 -The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." +The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." CWE-467,EN-Use of sizeof() on a Pointer Type (Type: Variant),"The code calls sizeof() on a malloced pointer type, which always returns the wordsize/8. This can produce an unexpected result if the programmer intended to determine how much memory has been allocated. -Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,high,EXP01-A. Do not take the sizeof a pointer to determine the size of a type: https://www.securecoding.cert.org/confluence/display/seccode/EXP01-A.+Do+not+take+the+sizeof+a+pointer+to+determine+the+size+of+a+type +Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,high,EXP01-A. Do not take the sizeof a pointer to determine the size of a type: https://www.securecoding.cert.org/confluence/display/seccode/EXP01-A.+Do+not+take+the+sizeof+a+pointer+to+determine+the+size+of+a+type CWE-486,EN-Comparison of Classes by Name (Type: Variant),"The program compares classes by name, which can cause it to use the wrong class when multiple classes can have the same name. -If the decision to trust the methods and data of an object is based on the name of a class, it is possible for malicious users to send objects of the same name as trusted classes and thereby gain the trust afforded to known classes and types.",,high, +If the decision to trust the methods and data of an object is based on the name of a class, it is possible for malicious users to send objects of the same name as trusted classes and thereby gain the trust afforded to known classes and types.",,high, CWE-493,EN-Critical Public Variable Without Final Modifier (Type: Variant),"The product has a critical public variable that is not final, which allows the variable to be modified to contain unexpected values. -If a field is non-final and public, it can be changed once the value is set by any function that has access to the class which contains the field. This could lead to a vulnerability if other parts of the program make assumptions about the contents of that field.",,high, +If a field is non-final and public, it can be changed once the value is set by any function that has access to the class which contains the field. This could lead to a vulnerability if other parts of the program make assumptions about the contents of that field.",,high, CWE-499,EN-Serializable Class Containing Sensitive Data (Type: Variant),"The code contains a class with sensitive data, but the class does not explicitly deny serialization. The data can be accessed by serializing the class through another class. -Serializable classes are effectively open classes since data cannot be hidden in them. Classes that do not explicitly deny serialization can be serialized by any other class, which can then in turn use the data stored inside it.",,high, +Serializable classes are effectively open classes since data cannot be hidden in them. Classes that do not explicitly deny serialization can be serialized by any other class, which can then in turn use the data stored inside it.",,high, CWE-500,EN-Public Static Field Not Marked Final (Type: Variant),"An object contains a public static field that is not marked final, which might allow it to be modified in unexpected ways. -Public static variables can be read without an accessor and changed without a mutator by any classes in the application.",,high, +Public static variables can be read without an accessor and changed without a mutator by any classes in the application.",,high, CWE-515,EN-Covert Storage Channel (Type: Base),"A covert storage channel transfers information through the setting of bits by one program and the reading of those bits by another. What distinguishes this case from that of ordinary operation is that the bits are used to convey encoded information. -Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,high, +Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,high, CWE-639,EN-Authorization Bypass Through User-Controlled Key (Type: Base),"The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data. -Retrieval of a user record occurs in the system based on some key value that is under user control. The key would typically identify a user related record stored in the system and would be used to lookup that record for presentation to the user. It is likely that an attacker would have to be an authenticated user in the system. However, the authorization process would not properly check the data access operation to ensure that the authenticated user performing the operation has sufficient entitlements to perform the requested data access, hence bypassing any other authorization checks present in the system. One manifestation of this weakness would be if a system used sequential or otherwise easily guessable session ids that would allow one user to easily switch to another user's session and read/modify their data.",,high, +Retrieval of a user record occurs in the system based on some key value that is under user control. The key would typically identify a user related record stored in the system and would be used to lookup that record for presentation to the user. It is likely that an attacker would have to be an authenticated user in the system. However, the authorization process would not properly check the data access operation to ensure that the authenticated user performing the operation has sufficient entitlements to perform the requested data access, hence bypassing any other authorization checks present in the system. One manifestation of this weakness would be if a system used sequential or otherwise easily guessable session ids that would allow one user to easily switch to another user's session and read/modify their data.",,high, CWE-640,EN-Weak Password Recovery Mechanism for Forgotten Password (Type: Base),"The software contains a mechanism for users to recover or change their passwords without knowing the original password, but the mechanism is weak. It is common for an application to have a mechanism that provides a means for a user to gain access to their account in the event they forget their password. Very often the password recovery mechanism is weak, which has the effect of making it more likely that it would be possible for a person other than the legitimate system user to gain access to that user's account. -This weakness may be that the security question is too easy to guess or find an answer to (e.g. because it is too common). Or there might be an implementation weakness in the password recovery mechanism code that may for instance trick the system into e-mailing the new password to an e-mail account other than that of the user. There might be no throttling done on the rate of password resets so that a legitimate user can be denied service by an attacker if an attacker tries to recover their password in a rapid succession. The system may send the original password to the user rather than generating a new temporary password. In summary, password recovery functionality, if not carefully designed and implemented can often become the system's weakest link that can be misused in a way that would allow an attacker to gain unauthorized access to the system. Weak password recovery schemes completely undermine a strong password authentication scheme.",,high,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" +This weakness may be that the security question is too easy to guess or find an answer to (e.g. because it is too common). Or there might be an implementation weakness in the password recovery mechanism code that may for instance trick the system into e-mailing the new password to an e-mail account other than that of the user. There might be no throttling done on the rate of password resets so that a legitimate user can be denied service by an attacker if an attacker tries to recover their password in a rapid succession. The system may send the original password to the user rather than generating a new temporary password. In summary, password recovery functionality, if not carefully designed and implemented can often become the system's weakest link that can be misused in a way that would allow an attacker to gain unauthorized access to the system. Weak password recovery schemes completely undermine a strong password authentication scheme.",,high,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" CWE-642,EN-External Control of Critical State Data (Type: Class),"The software stores security-critical state information about its users, or the software itself, in a location that is accessible to unauthorized actors. If an attacker can modify the state information without detection, then it could be used to perform unauthorized actions or access unexpected resources, since the application programmer does not expect that the state can be changed. State information can be stored in various locations such as a cookie, in a hidden web form field, input parameter or argument, an environment variable, a database record, within a settings file, etc. All of these locations have the potential to be modified by an attacker. When this state information is used to control security or determine resource usage, then it may create a vulnerability. For example, an application may perform authentication, then save the state in an ""authenticated=true"" cookie. An attacker may simply create this cookie in order to bypass the authentication.",,high,"Top 10 2007-Insecure Direct Object Reference: http://www.owasp.org/index.php/Top_10_2007-A4 HMAC: http://en.wikipedia.org/wiki/Hmac -24 Deadly Sins of Software Security: ""Sin 4: Use of Magic URLs, Predictable Cookies, and Hidden Form Fields."" Page 75" +24 Deadly Sins of Software Security: ""Sin 4: Use of Magic URLs, Predictable Cookies, and Hidden Form Fields."" Page 75" CWE-643,EN-Improper Neutralization of Data within XPath Expressions (XPath Injection) (Type: Base),"The software uses external input to dynamically construct an XPath expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. This allows an attacker to control the structure of the query. The net effect is that the attacker will have control over the information selected from the XML database and may use that ability to control application flow, modify logic, retrieve unauthorized data, or bypass important checks (e.g. authentication).",,high,"XPath Injection: http://www.webappsec.org/projects/threat/classes/xpath_injection.shtml -The Art of Software Security Assessment: Chapter 17, ""XPath Injection"", Page 1070." +The Art of Software Security Assessment: Chapter 17, ""XPath Injection"", Page 1070." CWE-644,EN-Improper Neutralization of HTTP Headers for Scripting Syntax (Type: Variant),"The application does not neutralize or incorrectly neutralizes web scripting syntax in HTTP headers that can be used by web browser components that can process raw headers, such as Flash. An attacker may be able to conduct cross-site scripting and other attacks against users who have these components enabled. -If an application does not neutralize user controlled data being placed in the header of an HTTP response coming from the server, the header may contain a script that will get executed in the client's browser context, potentially resulting in a cross site scripting vulnerability or possibly an HTTP response splitting attack. It is important to carefully control data that is being placed both in HTTP response header and in the HTTP response body to ensure that no scripting syntax is present, taking various encodings into account.",,high, +If an application does not neutralize user controlled data being placed in the header of an HTTP response coming from the server, the header may contain a script that will get executed in the client's browser context, potentially resulting in a cross site scripting vulnerability or possibly an HTTP response splitting attack. It is important to carefully control data that is being placed both in HTTP response header and in the HTTP response body to ensure that no scripting syntax is present, taking various encodings into account.",,high, CWE-645,EN-Overly Restrictive Account Lockout Mechanism (Type: Base),"The software contains an account lockout protection mechanism, but the mechanism is too restrictive and can be triggered too easily. This allows attackers to deny service to legitimate users by causing their accounts to be locked out. -Account lockout is a security feature often present in applications as a countermeasure to the brute force attack on the password based authentication mechanism of the system. After a certain number of failed login attempts, the users' account may be disabled for a certain period of time or until it is unlocked by an administrator. Other security events may also possibly trigger account lockout. However, an attacker may use this very security feature to deny service to legitimate system users. It is therefore important to ensure that the account lockout security mechanism is not overly restrictive.",,high, +Account lockout is a security feature often present in applications as a countermeasure to the brute force attack on the password based authentication mechanism of the system. After a certain number of failed login attempts, the users' account may be disabled for a certain period of time or until it is unlocked by an administrator. Other security events may also possibly trigger account lockout. However, an attacker may use this very security feature to deny service to legitimate system users. It is therefore important to ensure that the account lockout security mechanism is not overly restrictive.",,high, CWE-646,EN-Reliance on File Name or Extension of Externally-Supplied File (Type: Variant),"The software allows a file to be uploaded, but it relies on the file name or extension of the file to determine the appropriate behaviors. This could be used by attackers to cause the file to be misclassified and processed in a dangerous fashion. -An application might use the file name or extension of of a user-supplied file to determine the proper course of action, such as selecting the correct process to which control should be passed, deciding what data should be made available, or what resources should be allocated. If the attacker can cause the code to misclassify the supplied file, then the wrong action could occur. For example, an attacker could supply a file that ends in a "".php.gif"" extension that appears to be a GIF image, but would be processed as PHP code. In extreme cases, code execution is possible, but the attacker could also cause exhaustion of resources, denial of service, exposure of debug or system data (including application source code), or being bound to a particular server side process. This weakness may be due to a vulnerability in any of the technologies used by the web and application servers, due to misconfiguration, or resultant from another flaw in the application itself.",,high, +An application might use the file name or extension of of a user-supplied file to determine the proper course of action, such as selecting the correct process to which control should be passed, deciding what data should be made available, or what resources should be allocated. If the attacker can cause the code to misclassify the supplied file, then the wrong action could occur. For example, an attacker could supply a file that ends in a "".php.gif"" extension that appears to be a GIF image, but would be processed as PHP code. In extreme cases, code execution is possible, but the attacker could also cause exhaustion of resources, denial of service, exposure of debug or system data (including application source code), or being bound to a particular server side process. This weakness may be due to a vulnerability in any of the technologies used by the web and application servers, due to misconfiguration, or resultant from another flaw in the application itself.",,high, CWE-647,EN-Use of Non-Canonical URL Paths for Authorization Decisions (Type: Variant),"The software defines policy namespaces and makes authorization decisions based on the assumption that a URL is canonical. This can allow a non-canonical URL to bypass the authorization. If an application defines policy namespaces and makes authorization decisions based on the URL, but it does not require or convert to a canonical URL before making the authorization decision, then it opens the application to attack. For example, if the application only wants to allow access to http://www.example.com/mypage, then the attacker might be able to bypass this restriction using equivalent URLs such as: http://WWW.EXAMPLE.COM/mypage @@ -143,21 +143,21 @@ http://www.example.com/%6Dypage (alternate encoding) http://192.168.1.1/mypage (IP address) http://www.example.com/mypage/ (trailing /) http://www.example.com:80/mypage -Therefore it is important to specify access control policy that is based on the path information in some canonical form with all alternate encodings rejected (which can be accomplished by a default deny rule).",,high, +Therefore it is important to specify access control policy that is based on the path information in some canonical form with all alternate encodings rejected (which can be accomplished by a default deny rule).",,high, CWE-649,EN-Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking (Type: Base),"The software uses obfuscation or encryption of inputs that should not be mutable by an external actor, but the software does not use integrity checks to detect if those inputs have been modified. -When an application relies on obfuscation or incorrectly applied / weak encryption to protect client-controllable tokens or parameters, that may have an effect on the user state, system state, or some decision made on the server. Without protecting the tokens/parameters for integrity, the application is vulnerable to an attack where an adversary blindly traverses the space of possible values of the said token/parameter in order to attempt to gain an advantage. The goal of the attacker is to find another admissible value that will somehow elevate his or her privileges in the system, disclose information or change the behavior of the system in some way beneficial to the attacker. If the application does not protect these critical tokens/parameters for integrity, it will not be able to determine that these values have been tampered with. Measures that are used to protect data for confidentiality should not be relied upon to provide the integrity service.",,high, +When an application relies on obfuscation or incorrectly applied / weak encryption to protect client-controllable tokens or parameters, that may have an effect on the user state, system state, or some decision made on the server. Without protecting the tokens/parameters for integrity, the application is vulnerable to an attack where an adversary blindly traverses the space of possible values of the said token/parameter in order to attempt to gain an advantage. The goal of the attacker is to find another admissible value that will somehow elevate his or her privileges in the system, disclose information or change the behavior of the system in some way beneficial to the attacker. If the application does not protect these critical tokens/parameters for integrity, it will not be able to determine that these values have been tampered with. Measures that are used to protect data for confidentiality should not be relied upon to provide the integrity service.",,high, CWE-650,EN-Trusting HTTP Permission Methods on the Server Side (Type: Variant),"The server contains a protection mechanism that assumes that any URI that is accessed using HTTP GET will not cause a state change to the associated resource. This might allow attackers to bypass intended access restrictions and conduct resource modification and deletion attacks, since some applications allow GET to modify state. -An application may disallow the HTTP requests to perform DELETE, PUT and POST operations on the resource representation, believing that it will be enough to prevent unintended resource alterations. Even though the HTTP GET specification requires that GET requests should not have side effects, there is nothing in the HTTP protocol itself that prevents the HTTP GET method from performing more than just query of the data. For instance, it is a common practice with REST based Web Services to have HTTP GET requests modifying resources on the server side. Whenever that happens however, the access control needs to be properly enforced in the application. No assumptions should be made that only HTTP DELETE, PUT, and POST methods have the power to alter the representation of the resource being accessed in the request.",,high, +An application may disallow the HTTP requests to perform DELETE, PUT and POST operations on the resource representation, believing that it will be enough to prevent unintended resource alterations. Even though the HTTP GET specification requires that GET requests should not have side effects, there is nothing in the HTTP protocol itself that prevents the HTTP GET method from performing more than just query of the data. For instance, it is a common practice with REST based Web Services to have HTTP GET requests modifying resources on the server side. Whenever that happens however, the access control needs to be properly enforced in the application. No assumptions should be made that only HTTP DELETE, PUT, and POST methods have the power to alter the representation of the resource being accessed in the request.",,high, CWE-652,EN-Improper Neutralization of Data within XQuery Expressions (XQuery Injection) (Type: Base),"The software uses external input to dynamically construct an XQuery expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. This allows an attacker to control the structure of the query. -The net effect is that the attacker will have control over the information selected from the XML database and may use that ability to control application flow, modify logic, retrieve unauthorized data, or bypass important checks (e.g. authentication).",,high, +The net effect is that the attacker will have control over the information selected from the XML database and may use that ability to control application flow, modify logic, retrieve unauthorized data, or bypass important checks (e.g. authentication).",,high, CWE-676,EN-Use of Potentially Dangerous Function (Type: Base),"The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely. Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,high,"Security Development Lifecycle (SDL) Banned Function Calls: http://msdn.microsoft.com/en-us/library/bb288454.aspx Writing Secure Code: Chapter 5, ""Safe String Handling"" Page 156, 160 -The Art of Software Security Assessment: Chapter 8, ""C String Handling"", Page 388." +The Art of Software Security Assessment: Chapter 8, ""C String Handling"", Page 388." CWE-682,EN-Incorrect Calculation (Type: Class),"The software performs a calculation that generates incorrect or unintended results that are later used in security-critical decisions or resource management.. When software performs a security-critical calculation incorrectly, it might lead to incorrect resource allocations, incorrect privilege assignments, or failed comparisons among other things. Many of the direct results of an incorrect calculation can lead to even larger problems such as failed protection mechanisms or even arbitrary code execution.",,high,"SafeInt: http://safeint.codeplex.com/ 24 Deadly Sins of Software Security: ""Sin 7: Integer Overflows."" Page 119 -The Art of Software Security Assessment: Chapter 6, ""Signed Integer Boundaries"", Page 220." +The Art of Software Security Assessment: Chapter 6, ""Signed Integer Boundaries"", Page 220." CWE-78,EN-Improper Neutralization of Special Elements used in an OS Command (OS Command Injection) (Type: Base),"The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.. This could allow attackers to execute unexpected, dangerous commands directly on the operating system. This weakness can lead to a vulnerability in environments in which the attacker does not have direct access to the operating system, such as in web applications. Alternately, if the weakness occurs in a privileged program, it could allow the attacker to specify commands that normally would not be accessible, or to call alternate commands with privileges that the attacker does not have. The problem is exacerbated if the compromised process does not follow the principle of least privilege, because the attacker-controlled commands may run with special system privileges that increases the amount of damage. There are at least two subtypes of OS command injection: @@ -172,10 +172,10 @@ Security Issues in Perl Scripts: http://www.cgisecurity.com/lib/sips.html Top 25 Series - Rank 9 - OS Command Injection: http://blogs.sans.org/appsecstreetfighter/2010/02/24/top-25-series-rank-9-os-command-injection/ OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html -The Art of Software Security Assessment: Chapter 8, ""Shell Metacharacters"", Page 425." +The Art of Software Security Assessment: Chapter 8, ""Shell Metacharacters"", Page 425." CWE-784,EN-Reliance on Cookies without Validation and Integrity Checking in a Security Decision (Type: Variant),"The application uses a protection mechanism that relies on the existence or values of a cookie, but it does not properly ensure that the cookie is valid for the associated user. Attackers can easily modify cookies, within the browser or by implementing the client-side code outside of the browser. Attackers can bypass protection mechanisms such as authorization and authentication by modifying the cookie to contain an expected value.",,high,"Unforgivable Vulnerabilities: http://cve.mitre.org/docs/docs-2007/unforgivable.pdf -Writing Secure Code: Chapter 13, ""Sensitive Data in Cookies and Fields"" Page 435" +Writing Secure Code: Chapter 13, ""Sensitive Data in Cookies and Fields"" Page 435" CWE-862,EN-Missing Authorization (Type: Class),"The software does not perform an authorization check when an actor attempts to access a resource or perform an action. Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource. When access control checks are not applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.",,high,"Role Based Access Control and Role Based Security: http://csrc.nist.gov/groups/SNS/rbac/ @@ -183,7 +183,7 @@ Writing Secure Code: Chapter 4, ""Authorization"" Page 114; Chapter 6, ""Determi Top 25 Series - Rank 5 - Improper Access Control (Authorization): http://blogs.sans.org/appsecstreetfighter/2010/03/04/top-25-series-rank-5-improper-access-control-authorization/ OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI Authentication using JAAS: http://www.javaranch.com/journal/2008/04/authentication-using-JAAS.html -The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Authorization"", Page 39." +The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Authorization"", Page 39." CWE-863,EN-Incorrect Authorization (Type: Class),"The software performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions. Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource. When access control checks are incorrectly applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.",,high,"Role Based Access Control and Role Based Security: http://csrc.nist.gov/groups/SNS/rbac/ @@ -191,9 +191,9 @@ Writing Secure Code: Chapter 4, ""Authorization"" Page 114; Chapter 6, ""Determi Top 25 Series - Rank 5 - Improper Access Control (Authorization): http://blogs.sans.org/appsecstreetfighter/2010/03/04/top-25-series-rank-5-improper-access-control-authorization/ Authentication using JAAS: http://www.javaranch.com/journal/2008/04/authentication-using-JAAS.html OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI -The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Authorization"", Page 39." +The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Authorization"", Page 39." CWE-99,EN-Improper Control of Resource Identifiers (Resource Injection) (Type: Base),"The software receives input from an upstream component, but it does not restrict or incorrectly restricts the input before it is used as an identifier for a resource that may be outside the intended sphere of control. -This may enable an attacker to access or modify otherwise protected system resources.",,high, +This may enable an attacker to access or modify otherwise protected system resources.",,high, CWE-120,EN-Buffer Copy without Checking Size of Input (Classic Buffer Overflow) (Type: Base),"The program copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow. A buffer overflow condition exists when a program attempts to put more data in a buffer than it can hold, or when a program attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the ""classic"" case in which the program copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.",,high,"Writing Secure Code: Chapter 5, ""Public Enemy #1: The Buffer Overrun"" Page 127 24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89 @@ -207,12 +207,12 @@ Understanding DEP as a mitigation technology part 1: http://blogs.technet.com/b/ Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html The Art of Software Security Assessment: Chapter 3, ""Nonexecutable Stack"", Page 76. The Art of Software Security Assessment: Chapter 5, ""Protection Mechanisms"", Page 189. -The Art of Software Security Assessment: Chapter 8, ""C String Handling"", Page 388." +The Art of Software Security Assessment: Chapter 8, ""C String Handling"", Page 388." CWE-122,EN-Heap-based Buffer Overflow (Type: Variant),"A heap overflow condition is a buffer overflow, where the buffer that can be overwritten is allocated in the heap portion of memory, generally meaning that the buffer was allocated using a routine such as malloc(). A buffer overflow condition exists when a program attempts to put more data in a buffer than it can hold, or when a program attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the ""classic"" case in which the program copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.",,high,"Writing Secure Code: Chapter 5, ""Heap Overruns"" Page 138 24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89 The Art of Software Security Assessment: Chapter 3, ""Nonexecutable Stack"", Page 76. -The Art of Software Security Assessment: Chapter 5, ""Protection Mechanisms"", Page 189." +The Art of Software Security Assessment: Chapter 5, ""Protection Mechanisms"", Page 189." CWE-131,EN-Incorrect Calculation of Buffer Size (Type: Base),"The software does not correctly calculate the size to be used when allocating a buffer, which could lead to a buffer overflow. If an attacker can manipulate the length parameter associated with an input such that it is inconsistent with the actual length of the input, this can be leveraged to cause the target application to behave in unexpected, and possibly, malicious ways. One of the possible motives for doing so is to pass in arbitrarily large input to the application. Another possible motivation is the modification of application state by including invalid data for subsequent properties of the application. Such weaknesses commonly lead to attacks such as buffer overflows and execution of arbitrary code.",,high,"SafeInt: http://safeint.codeplex.com/ Top 25 Series - Rank 18 - Incorrect Calculation of Buffer Size: http://software-security.sans.org/blog/2010/03/19/top-25-series-rank-18-incorrect-calculation-of-buffer-size @@ -222,7 +222,7 @@ PaX: http://en.wikipedia.org/wiki/PaX Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html Writing Secure Code: Chapter 20, ""Integer Overflows"" Page 620 24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89 -The Art of Software Security Assessment: Chapter 8, ""Incrementing Pointers Incorrectly"", Page 401." +The Art of Software Security Assessment: Chapter 8, ""Incrementing Pointers Incorrectly"", Page 401." CWE-22,EN-Improper Limitation of a Pathname to a Restricted Directory (Path Traversal) (Type: Class),"The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Many file operations are intended to take place within a restricted directory. By using special elements such as "".."" and ""/"" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the ""../"" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as ""/usr/local/bin"", which may also be useful in accessing unexpected files. This is referred to as absolute path traversal. In many programming languages, the injection of a null byte (the 0 or NUL) may allow an attacker to truncate a generated filename to widen the scope of attack. For example, the software may add "".txt"" to any pathname, thus limiting the attacker to text files, but a null injection may effectively remove this restriction.",,high,"Writing Secure Code: Chapter 11, ""Directory Traversal and Using Parent Paths (..)"" Page 370 @@ -230,26 +230,26 @@ OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ES Testing for Path Traversal (OWASP-AZ-001): http://www.owasp.org/index.php/Testing_for_Path_Traversal_(OWASP-AZ-001) Top 25 Series - Rank 7 - Path Traversal: http://blogs.sans.org/appsecstreetfighter/2010/03/09/top-25-series-rank-7-path-traversal/ Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html -The Art of Software Security Assessment: Chapter 9, ""Filenames and Paths"", Page 503." +The Art of Software Security Assessment: Chapter 9, ""Filenames and Paths"", Page 503." CWE-311,EN-Missing Encryption of Sensitive Data (Type: Base),"The software does not encrypt sensitive or critical information before storage or transmission. The lack of proper data encryption passes up the guarantees of confidentiality, integrity, and accountability that properly implemented encryption conveys.",,high,"Writing Secure Code: Chapter 9, ""Protecting Secret Data"" Page 299 24 Deadly Sins of Software Security: ""Sin 17: Failure to Protect Stored Data."" Page 253 Top 25 Series - Rank 10 - Missing Encryption of Sensitive Data: http://blogs.sans.org/appsecstreetfighter/2010/02/26/top-25-series-rank-10-missing-encryption-of-sensitive-data/ The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Encryption"", Page 43. -SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf" +SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf" CWE-464,EN-Addition of Data Structure Sentinel (Type: Base),"The accidental addition of a data-structure sentinel can cause serious programming logic problems. -Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,high, +Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,high, CWE-67,EN-Improper Handling of Windows Device Names (Type: Variant),"The software constructs pathnames from user input, but it does not handle or incorrectly handles a pathname containing a Windows device name such as AUX or CON. This typically leads to denial of service or an information exposure when the application attempts to process the pathname as a regular file. Not properly handling virtual filenames (e.g. AUX, CON, PRN, COM1, LPT1) can result in different types of vulnerabilities. In some cases an attacker can request a device via injection of a virtual filename in a URL, which may cause an error that leads to a denial of service or an error page that reveals sensitive information. A software system that allows device names to bypass filtering runs the risk of an attacker injecting malicious code in a file with the name of a device.",,high,"Writing Secure Code -The Art of Software Security Assessment: Chapter 11, ""Device Files"", Page 666." +The Art of Software Security Assessment: Chapter 11, ""Device Files"", Page 666." CWE-73,EN-External Control of File Name or Path (Type: Class),"The software allows user input to control or influence paths or file names that are used in filesystem operations. This could allow an attacker to access or modify system files or other files that are critical to the application. Path manipulation errors occur when the following two conditions are met: 1. An attacker can specify a path used in an operation on the filesystem. 2. By specifying the resource, the attacker gains a capability that would not otherwise be permitted. -For example, the program may give the attacker the ability to overwrite the specified file or run with a configuration controlled by the attacker.",,high,OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI +For example, the program may give the attacker the ability to overwrite the specified file or run with a configuration controlled by the attacker.",,high,OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI CWE-76,EN-Improper Neutralization of Equivalent Special Elements (Type: Base),"The software properly neutralizes certain special elements, but it improperly neutralizes equivalent special elements. -The software may have a fixed list of special characters it believes is complete. However, there may be alternate encodings, or representations that also have the same meaning. For example, the software may filter out a leading slash (/) to prevent absolute path names, but does not account for a tilde (~) followed by a user name, which on some *nix systems could be expanded to an absolute pathname. Alternately, the software might filter a dangerous ""-e"" command-line switch when calling an external program, but it might not account for ""--exec"" or other switches that have the same semantics.",,high, +The software may have a fixed list of special characters it believes is complete. However, there may be alternate encodings, or representations that also have the same meaning. For example, the software may filter out a leading slash (/) to prevent absolute path names, but does not account for a tilde (~) followed by a user name, which on some *nix systems could be expanded to an absolute pathname. Alternately, the software might filter a dangerous ""-e"" command-line switch when calling an external program, but it might not account for ""--exec"" or other switches that have the same semantics.",,high, CWE-79,EN-Improper Neutralization of Input During Web Page Generation (Cross-site Scripting) (Type: Base),"The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. Cross-site scripting (XSS) vulnerabilities occur when: 1. Untrusted data enters a web application, typically from a web request. @@ -281,26 +281,26 @@ Apache Wicket: http://wicket.apache.org/ XSS (Cross Site Scripting) Prevention Cheat Sheet: http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet DOM based XSS Prevention Cheat Sheet: http://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet Top 25 series - Rank 1 - Cross Site Scripting: http://blogs.sans.org/appsecstreetfighter/2010/02/22/top-25-series-rank-1-cross-site-scripting/ -The Art of Software Security Assessment: Chapter 17, ""Cross Site Scripting"", Page 1071." +The Art of Software Security Assessment: Chapter 17, ""Cross Site Scripting"", Page 1071." CWE-80,EN-Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS) (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special characters such as ""<"", "">"", and ""&"" that could be interpreted as web-scripting elements when they are sent to a downstream component that processes web pages. -This may allow such characters to be treated as control characters, which are executed client-side in the context of the user's session. Although this can be classified as an injection problem, the more pertinent issue is the improper conversion of such special characters to respective context-appropriate entities before displaying them to the user.",,high, +This may allow such characters to be treated as control characters, which are executed client-side in the context of the user's session. Although this can be classified as an injection problem, the more pertinent issue is the improper conversion of such special characters to respective context-appropriate entities before displaying them to the user.",,high, CWE-98,EN-Improper Control of Filename for Include/Require Statement in PHP Program (PHP Remote File Inclusion) (Type: Base),"The PHP application receives input from an upstream component, but it does not restrict or incorrectly restricts the input before its usage in ""require,"" ""include,"" or similar functions. In certain versions and configurations of PHP, this can allow an attacker to specify a URL to a remote location from which the software will obtain the code to execute. In other cases in association with path traversal, the attacker can specify a local file that may contain executable statements that can be parsed by PHP.",,high,"Testing for Path Traversal (OWASP-AZ-001): http://www.owasp.org/index.php/Testing_for_Path_Traversal_(OWASP-AZ-001) Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html A Study in Scarlet: http://www.cgisecurity.com/lib/studyinscarlet.txt Suhosin: http://www.hardened-php.net/suhosin/ -Top 25 Series - Rank 13 - PHP File Inclusion: http://blogs.sans.org/appsecstreetfighter/2010/03/11/top-25-series-rank-13-php-file-inclusion/" +Top 25 Series - Rank 13 - PHP File Inclusion: http://blogs.sans.org/appsecstreetfighter/2010/03/11/top-25-series-rank-13-php-file-inclusion/" CWE-188,EN-Reliance on Data/Memory Layout (Type: Base),"The software makes invalid assumptions about how protocol data or memory is organized at a lower level, resulting in unintended program behavior. -For example, an attacker might succeed in authentication by providing a small password that matches the associated portion of the larger, correct password.",,low,"The Art of Software Security Assessment: Chapter 6, ""Structure Padding"", Page 284." +For example, an attacker might succeed in authentication by providing a small password that matches the associated portion of the larger, correct password.",,low,"The Art of Software Security Assessment: Chapter 6, ""Structure Padding"", Page 284." CWE-197,EN-Numeric Truncation Error (Type: Base),"Truncation errors occur when a primitive is cast to a primitive of a smaller size and data is lost in the conversion. -When a primitive is cast to a smaller primitive, the high order bits of the large value are lost in the conversion, potentially resulting in an unexpected value that is not equal to the original value. This value may be required as an index into a buffer, a loop iterator, or simply necessary state data. In any case, the value cannot be trusted and the system will be in an undefined state. While this method may be employed viably to isolate the low bits of a value, this usage is rare, and truncation usually implies that an implementation error has occurred.",,low,"The Art of Software Security Assessment: Chapter 6, ""Truncation"", Page 259." +When a primitive is cast to a smaller primitive, the high order bits of the large value are lost in the conversion, potentially resulting in an unexpected value that is not equal to the original value. This value may be required as an index into a buffer, a loop iterator, or simply necessary state data. In any case, the value cannot be trusted and the system will be in an undefined state. While this method may be employed viably to isolate the low bits of a value, this usage is rare, and truncation usually implies that an implementation error has occurred.",,low,"The Art of Software Security Assessment: Chapter 6, ""Truncation"", Page 259." CWE-252,EN-Unchecked Return Value (Type: Base),"The software does not check the return value from a method or function, which can prevent it from detecting unexpected states and conditions. Two common programmer assumptions are ""this function call can never fail"" and ""it doesn't matter if this function call fails"". If an attacker can force the function to fail or otherwise return a value that is not expected, then the subsequent program logic could lead to a vulnerability, because the software is not in a state that the programmer assumes. For example, if the program calls a function to drop privileges but does not check the return code to ensure that privileges were successfully dropped, then the program will continue to operate with the higher privileges.",,low,"The Art of Software Security Assessment: Chapter 7, ""Program Building Blocks"" Page 341. Writing Secure Code: Chapter 20, ""Checking Returns"" Page 624 24 Deadly Sins of Software Security: ""Sin 11: Failure to Handle Errors Correctly."" Page 183 -ERR10-CPP. Check for error conditions: https://www.securecoding.cert.org/confluence/display/cplusplus/ERR10-CPP.+Check+for+error+conditions" +ERR10-CPP. Check for error conditions: https://www.securecoding.cert.org/confluence/display/cplusplus/ERR10-CPP.+Check+for+error+conditions" CWE-253,EN-Incorrect Check of Function Return Value (Type: Base),"The software incorrectly checks a return value from a function, which prevents the software from detecting errors or exceptional conditions. -Two common programmer assumptions are ""this function call can never fail"" and ""it doesn't matter if this function call fails"". If an attacker can force the function to fail or otherwise return a value that is not expected, then the subsequent program logic could lead to a vulnerability, because the software is not in a state that the programmer assumes. For example, if the program calls a function to drop privileges but does not check the return code to ensure that privileges were successfully dropped, then the program will continue to operate with the higher privileges.",,low,"The Art of Software Security Assessment: Chapter 7, ""Return Value Testing and Interpretation"", Page 340." +Two common programmer assumptions are ""this function call can never fail"" and ""it doesn't matter if this function call fails"". If an attacker can force the function to fail or otherwise return a value that is not expected, then the subsequent program logic could lead to a vulnerability, because the software is not in a state that the programmer assumes. For example, if the program calls a function to drop privileges but does not check the return code to ensure that privileges were successfully dropped, then the program will continue to operate with the higher privileges.",,low,"The Art of Software Security Assessment: Chapter 7, ""Return Value Testing and Interpretation"", Page 340." CWE-296,EN-Improper Following of a Certificates Chain of Trust (Type: Base),"The software does not follow, or incorrectly follows, the chain of trust for a certificate back to a trusted root certificate, resulting in incorrect trust of any resource that is associated with that certificate. If a system does not follow the chain of trust of a certificate to a root server, the certificate loses all usefulness as a metric of trust. Essentially, the trust gained from a certificate is derived from a chain of trust -- with a reputable trusted entity at the end of that list. The end user must trust that reputable source, and this reputable source must vouch for the resource in question through the medium of the certificate. In some cases, this trust traverses several entities who vouch for one another. The entity trusted by the end user is at one end of this trust chain, while the certificate-wielding resource is at the other end of the chain. If the user receives a certificate at the end of one of these trust chains and then proceeds to check only that the first link in the chain, no real trust has been derived, since the entire chain must be traversed back to a trusted source to verify the certificate. @@ -309,80 +309,80 @@ Any certificate in the chain is self-signed, unless it the root. Not every intermediate certificate is checked, starting from the original certificate all the way up to the root certificate. An intermediate, CA-signed certificate does not have the expected Basic Constraints or other important extensions. The root certificate has been compromised or authorized to the wrong party.",,low,"The Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software: http://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf -24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" +24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" CWE-298,EN-Improper Validation of Certificate Expiration (Type: Variant),"A certificate expiration is not validated or is incorrectly validated, so trust may be assigned to certificates that have been abandoned due to age. -When the expiration of a certificate is not taken into account, no trust has necessarily been conveyed through it. Therefore, the validity of the certificate cannot be verified and all benefit of the certificate is lost.",,low,"24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" +When the expiration of a certificate is not taken into account, no trust has necessarily been conveyed through it. Therefore, the validity of the certificate cannot be verified and all benefit of the certificate is lost.",,low,"24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" CWE-324,EN-Use of a Key Past its Expiration Date (Type: Base),"The product uses a cryptographic key or password past its expiration date, which diminishes its safety significantly by increasing the timing window for cracking attacks against that key. -While the expiration of keys does not necessarily ensure that they are compromised, it is a significant concern that keys which remain in use for prolonged periods of time have a decreasing probability of integrity. For this reason, it is important to replace keys within a period of time proportional to their strength.",,low,"24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" +While the expiration of keys does not necessarily ensure that they are compromised, it is a significant concern that keys which remain in use for prolonged periods of time have a decreasing probability of integrity. For this reason, it is important to replace keys within a period of time proportional to their strength.",,low,"24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" CWE-379,EN-Creation of Temporary File in Directory with Incorrect Permissions (Type: Base),"The software creates a temporary file in a directory whose permissions allow unintended actors to determine the file's existence or otherwise access that file. -On some operating systems, the fact that the temporary file exists may be apparent to any user with sufficient privileges to access that directory. Since the file is visible, the application that is using the temporary file could be known. If one has access to list the processes on the system, the attacker has gained information about what the user is doing at that time. By correlating this with the applications the user is running, an attacker could potentially discover what a user's actions are. From this, higher levels of security could be breached.",,low,"The Art of Software Security Assessment: Chapter 9, ""Temporary Files"", Page 538." +On some operating systems, the fact that the temporary file exists may be apparent to any user with sufficient privileges to access that directory. Since the file is visible, the application that is using the temporary file could be known. If one has access to list the processes on the system, the attacker has gained information about what the user is doing at that time. By correlating this with the applications the user is running, an attacker could potentially discover what a user's actions are. From this, higher levels of security could be breached.",,low,"The Art of Software Security Assessment: Chapter 9, ""Temporary Files"", Page 538." CWE-462,EN-Duplicate Key in Associative List (Alist) (Type: Base),"Duplicate keys in associative lists can lead to non-unique keys being mistaken for an error. -A duplicate key entry -- if the alist is designed properly -- could be used as a constant time replace function. However, duplicate key entries could be inserted by mistake. Because of this ambiguity, duplicate key entries in an association list are not recommended and should not be allowed.",,low, +A duplicate key entry -- if the alist is designed properly -- could be used as a constant time replace function. However, duplicate key entries could be inserted by mistake. Because of this ambiguity, duplicate key entries in an association list are not recommended and should not be allowed.",,low, CWE-479,EN-Signal Handler Use of a Non-reentrant Function (Type: Variant),"The program defines a signal handler that calls a non-reentrant function. Non-reentrant functions are functions that cannot safely be called, interrupted, and then recalled before the first call has finished without resulting in memory corruption. This can lead to an unexpected system state an unpredictable results with a variety of potential consequences depending on context, including denial of service and code execution. -Many functions are not reentrant, but some of them can result in the corruption of memory if they are used in a signal handler. The function call syslog() is an example of this. In order to perform its functionality, it allocates a small amount of memory as ""scratch space."" If syslog() is suspended by a signal call and the signal handler calls syslog(), the memory used by both of these functions enters an undefined, and possibly, exploitable state. Implementations of malloc() and free() manage metadata in global structures in order to track which memory is allocated versus which memory is available, but they are non-reentrant. Simultaneous calls to these functions can cause corruption of the metadata.",,low,"The Art of Software Security Assessment: Chapter 13, ""Signal Vulnerabilities"", Page 791." +Many functions are not reentrant, but some of them can result in the corruption of memory if they are used in a signal handler. The function call syslog() is an example of this. In order to perform its functionality, it allocates a small amount of memory as ""scratch space."" If syslog() is suspended by a signal call and the signal handler calls syslog(), the memory used by both of these functions enters an undefined, and possibly, exploitable state. Implementations of malloc() and free() manage metadata in global structures in order to track which memory is allocated versus which memory is available, but they are non-reentrant. Simultaneous calls to these functions can cause corruption of the metadata.",,low,"The Art of Software Security Assessment: Chapter 13, ""Signal Vulnerabilities"", Page 791." CWE-480,EN-Use of Incorrect Operator (Type: Base),"The programmer accidentally uses the wrong operator, which changes the application logic in security-relevant ways. Non-reentrant functions are functions that cannot safely be called, interrupted, and then recalled before the first call has finished without resulting in memory corruption. This can lead to an unexpected system state an unpredictable results with a variety of potential consequences depending on context, including denial of service and code execution. -Many functions are not reentrant, but some of them can result in the corruption of memory if they are used in a signal handler. The function call syslog() is an example of this. In order to perform its functionality, it allocates a small amount of memory as ""scratch space."" If syslog() is suspended by a signal call and the signal handler calls syslog(), the memory used by both of these functions enters an undefined, and possibly, exploitable state. Implementations of malloc() and free() manage metadata in global structures in order to track which memory is allocated versus which memory is available, but they are non-reentrant. Simultaneous calls to these functions can cause corruption of the metadata.",,low,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." +Many functions are not reentrant, but some of them can result in the corruption of memory if they are used in a signal handler. The function call syslog() is an example of this. In order to perform its functionality, it allocates a small amount of memory as ""scratch space."" If syslog() is suspended by a signal call and the signal handler calls syslog(), the memory used by both of these functions enters an undefined, and possibly, exploitable state. Implementations of malloc() and free() manage metadata in global structures in order to track which memory is allocated versus which memory is available, but they are non-reentrant. Simultaneous calls to these functions can cause corruption of the metadata.",,low,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." CWE-481,EN-Assigning instead of Comparing (Type: Variant),"The code uses an operator for assignment when the intention was to perform a comparison. -In many languages the compare statement is very close in appearance to the assignment statement and are often confused. This bug is generally the result of a typo and usually causes obvious problems with program execution. If the comparison is in an if statement, the if statement will usually evaluate the value of the right-hand side of the predicate.",,low,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." +In many languages the compare statement is very close in appearance to the assignment statement and are often confused. This bug is generally the result of a typo and usually causes obvious problems with program execution. If the comparison is in an if statement, the if statement will usually evaluate the value of the right-hand side of the predicate.",,low,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." CWE-482,EN-Comparing instead of Assigning (Type: Variant),"The code uses an operator for comparison when the intention was to perform an assignment. -In many languages, the compare statement is very close in appearance to the assignment statement; they are often confused.",,low,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." +In many languages, the compare statement is very close in appearance to the assignment statement; they are often confused.",,low,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." CWE-483,EN-Incorrect Block Delimitation (Type: Variant),"The code does not explicitly delimit a block that is intended to contain 2 or more statements, creating a logic error. -In some languages, braces (or other delimiters) are optional for blocks. When the delimiter is omitted, it is possible to insert a logic error in which a statement is thought to be in a block but is not. In some cases, the logic error can have security implications.",,low, +In some languages, braces (or other delimiters) are optional for blocks. When the delimiter is omitted, it is possible to insert a logic error in which a statement is thought to be in a block but is not. In some cases, the logic error can have security implications.",,low, CWE-641,EN-Improper Restriction of Names for Files and Other Resources (Type: Base),"The application constructs the name of a file or other resource using input from an upstream component, but does not restrict or incorrectly restricts the resulting name. -This may produce resultant weaknesses. For instance, if the names of these resources contain scripting characters, it is possible that a script may get executed in the client's browser if the application ever displays the name of the resource on a dynamically generated web page. Alternately, if the resources are consumed by some application parser, a specially crafted name can exploit some vulnerability internal to the parser, potentially resulting in execution of arbitrary code on the server machine. The problems will vary based on the context of usage of such malformed resource names and whether vulnerabilities are present in or assumptions are made by the targeted technology that would make code execution possible.",,low, +This may produce resultant weaknesses. For instance, if the names of these resources contain scripting characters, it is possible that a script may get executed in the client's browser if the application ever displays the name of the resource on a dynamically generated web page. Alternately, if the resources are consumed by some application parser, a specially crafted name can exploit some vulnerability internal to the parser, potentially resulting in execution of arbitrary code on the server machine. The problems will vary based on the context of usage of such malformed resource names and whether vulnerabilities are present in or assumptions are made by the targeted technology that would make code execution possible.",,low, CWE-648,EN-Incorrect Use of Privileged APIs (Type: Base),"The application does not conform to the API requirements for a function call that requires extra privileges. This could allow attackers to gain privileges by causing the function to be called incorrectly. When an application contains certain functions that perform operations requiring an elevated level of privilege, the caller of a privileged API must be careful to: ensure that assumptions made by the APIs are valid, such as validity of arguments account for known weaknesses in the design/implementation of the API call the API from a safe context If the caller of the API does not follow these requirements, then it may allow a malicious user or process to elevate their privilege, hijack the process, or steal sensitive data. -For instance, it is important to know if privileged APIs do not shed their privileges before returning to the caller or if the privileged function might make certain assumptions about the data, context or state information passed to it by the caller. It is important to always know when and how privileged APIs can be called in order to ensure that their elevated level of privilege cannot be exploited.",,low, +For instance, it is important to know if privileged APIs do not shed their privileges before returning to the caller or if the privileged function might make certain assumptions about the data, context or state information passed to it by the caller. It is important to always know when and how privileged APIs can be called in order to ensure that their elevated level of privilege cannot be exploited.",,low, CWE-762,EN-Mismatched Memory Management Routines (Type: Variant),"The application attempts to return a memory resource to the system, but it calls a release function that is not compatible with the function that was originally used to allocate that resource. This weakness can be generally described as mismatching memory management routines, such as: The memory was allocated on the stack (automatically), but it was deallocated using the memory management routine free() (CWE-590), which is intended for explicitly allocated heap memory. The memory was allocated explicitly using one set of memory management functions, and deallocated using a different set. For example, memory might be allocated with malloc() in C++ instead of the new operator, and then deallocated with the delete operator. When the memory management functions are mismatched, the consequences may be as severe as code execution, memory corruption, or program crash. Consequences and ease of exploit will vary depending on the implementation of the routines and the object being managed.",,low,"boost C++ Library Smart Pointers: http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/smart_ptr.htm -Valgrind: http://valgrind.org/" +Valgrind: http://valgrind.org/" CWE-783,EN-Operator Precedence Logic Error (Type: Variant),"The program uses an expression in which operator precedence causes incorrect logic to be used. While often just a bug, operator precedence logic errors can have serious consequences if they are used in security-critical code, such as making an authentication decision.",,low,"EXP00-C. Use parentheses for precedence of operation: https://www.securecoding.cert.org/confluence/display/seccode/EXP00-C.+Use+parentheses+for+precedence+of+operation -The Art of Software Security Assessment: Chapter 6, ""Precedence"", Page 287." +The Art of Software Security Assessment: Chapter 6, ""Precedence"", Page 287." CWE-789,EN-Uncontrolled Memory Allocation (Type: Variant),"The product allocates memory based on an untrusted size value, but it does not validate or incorrectly validates the size, allowing arbitrary amounts of memory to be allocated. -This typically occurs when a pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. These problems may be resultant from missing sentinel values (CWE-463) or trusting a user-influenced input length variable.",,low,"The Art of Software Security Assessment: Chapter 10, ""Resource Limits"", Page 574." +This typically occurs when a pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. These problems may be resultant from missing sentinel values (CWE-463) or trusting a user-influenced input length variable.",,low,"The Art of Software Security Assessment: Chapter 10, ""Resource Limits"", Page 574." CWE-333,EN-Improper Handling of Insufficient Entropy in TRNG (Type: Variant),"True random number generators (TRNG) generally have a limited source of entropy and therefore can fail or block. -The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,medium, +The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,medium, CWE-367,EN-Time-of-check Time-of-use (TOCTOU) Race Condition (Type: Base),"The software checks the state of a resource before using that resource, but the resource's state can change between the check and the use in a way that invalidates the results of the check. This can cause the software to perform invalid actions when the resource is in an unexpected state. This weakness can be security-relevant when an attacker can influence the state of the resource between check and use. This can happen with shared resources such as files, memory, or even variables in multithreaded programs.",,medium,"Portably Solving File TOCTTOU Races with Hardness Amplification: http://www.usenix.org/events/fast08/tech/tsafrir.html 24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205 -The Art of Software Security Assessment: Chapter 9, ""TOCTOU"", Page 527." +The Art of Software Security Assessment: Chapter 9, ""TOCTOU"", Page 527." CWE-404,EN-Improper Resource Shutdown or Release (Type: Base),"The program does not release or incorrectly releases a resource before it is made available for re-use. -When a resource is created or allocated, the developer is responsible for properly releasing the resource as well as accounting for all potential paths of expiration or invalidation, such as a set period of time or revocation.",,medium,"24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143" +When a resource is created or allocated, the developer is responsible for properly releasing the resource as well as accounting for all potential paths of expiration or invalidation, such as a set period of time or revocation.",,medium,"24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143" CWE-407,EN-Algorithmic Complexity (Type: Base),"An algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached. -In the absence of a policy to restrict asymmetric resource consumption, the application or system cannot distinguish between legitimate transmissions and traffic intended to serve as an amplifying attack on target systems. Systems can often be configured to restrict the amount of traffic sent out on behalf of a client, based on the client's origin or access level. This is usually defined in a resource allocation policy. In the absence of a mechanism to keep track of transmissions, the system or application can be easily abused to transmit asymmetrically greater traffic than the request or client should be permitted to.",,medium,Algorithmic Complexity Attacks: http://www.cs.rice.edu/~scrosby/hash/CrosbyWallach_UsenixSec2003/index.html +In the absence of a policy to restrict asymmetric resource consumption, the application or system cannot distinguish between legitimate transmissions and traffic intended to serve as an amplifying attack on target systems. Systems can often be configured to restrict the amount of traffic sent out on behalf of a client, based on the client's origin or access level. This is usually defined in a resource allocation policy. In the absence of a mechanism to keep track of transmissions, the system or application can be easily abused to transmit asymmetrically greater traffic than the request or client should be permitted to.",,medium,Algorithmic Complexity Attacks: http://www.cs.rice.edu/~scrosby/hash/CrosbyWallach_UsenixSec2003/index.html CWE-415,EN-Double Free (Type: Variant),"The product calls free() twice on the same memory address, potentially leading to modification of unexpected memory locations. When a program calls free() twice with the same argument, the program's memory management data structures become corrupted. This corruption can cause the program to crash or, in some circumstances, cause two later calls to malloc() to return the same pointer. If malloc() returns the same value twice and the program later gives the attacker control over the data that is written into this doubly-allocated memory, the program becomes vulnerable to a buffer overflow attack.",,medium,"24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143 -The Art of Software Security Assessment: Chapter 7, ""Double Frees"", Page 379." +The Art of Software Security Assessment: Chapter 7, ""Double Frees"", Page 379." CWE-59,EN-Improper Link Resolution Before File Access (Link Following) (Type: Base),"The software attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource. -Some functions that offer security features supported by the OS are not available on all versions of the OS in common use. Likewise, functions are often deprecated or made obsolete for security reasons and should not be used.",,medium,"The Art of Software Security Assessment: Chapter 9, ""Symbolic Link Attacks"", Page 518." +Some functions that offer security features supported by the OS are not available on all versions of the OS in common use. Likewise, functions are often deprecated or made obsolete for security reasons and should not be used.",,medium,"The Art of Software Security Assessment: Chapter 9, ""Symbolic Link Attacks"", Page 518." CWE-601,EN-URL Redirection to Untrusted Site (Open Redirect) (Type: Variant),"A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks. An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance.",,medium,"Exploitable Redirects on the Web: Identification, Prevalence, and Defense: http://www.cs.indiana.edu/cgi-pub/cshue/research/woot08.pdf Open redirect vulnerabilities: definition and prevention: http://www.net-security.org/dl/insecure/INSECURE-Mag-17.pdf Top 25 Series - Rank 23 - Open Redirect: http://software-security.sans.org/blog/2010/03/25/top-25-series-rank-23-open-redirect -OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" +OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" CWE-749,EN-Exposed Dangerous Method or Function (Type: Base),"The software provides an Applications Programming Interface (API) or similar interface for interaction with external actors, but the interface includes a dangerous method or function that is not properly restricted. This weakness can lead to a wide variety of resultant weaknesses, depending on the behavior of the exposed method. It can apply to any number of technologies and approaches, such as ActiveX controls, Java functions, IOCTLs, and so on. The exposure can occur in a few different ways: 1) The function/method was never intended to be exposed to outside actors. 2) The function/method was only intended to be accessible to a limited set of actors, such as Internet-based access from a single web site.",,medium,"No description: http://msdn.microsoft.com/workshop/components/activex/safety.asp -No description: http://msdn.microsoft.com/workshop/components/activex/security.asp" +No description: http://msdn.microsoft.com/workshop/components/activex/security.asp" CWE-755,EN-Improper Handling of Exceptional Conditions (Type: Class),"The software does not handle or incorrectly handles an exceptional condition. The programmer may assume that certain events or conditions will never occur or do not need to be worried about, such as low memory conditions, lack of access to resources due to restrictive permissions, or misbehaving clients or components. However, attackers may intentionally trigger these unusual conditions, thus violating the programmer's assumptions, possibly introducing instability, incorrect behavior, or a vulnerability. -Note that this entry is not exclusively about the use of exceptions and exception handling, which are mechanisms for both checking and handling unusual or unexpected conditions.",,medium, +Note that this entry is not exclusively about the use of exceptions and exception handling, which are mechanisms for both checking and handling unusual or unexpected conditions.",,medium, CWE-766,EN-Critical Variable Declared Public (Type: Variant),"The software declares a critical variable or field to be public when intended security policy requires it to be private. -When software is operating in a concurrent environment and repeatedly unlocks a critical resource, the consequences will vary based on the type of lock, the lock's implementation, and the resource being protected. In some situations such as with semaphores, the resources are pooled and extra calls to unlock will increase the count for the number of available resources, likely resulting in a crash or unpredictable behavior when the system nears capacity.",,medium, +When software is operating in a concurrent environment and repeatedly unlocks a critical resource, the consequences will vary based on the type of lock, the lock's implementation, and the resource being protected. In some situations such as with semaphores, the resources are pooled and extra calls to unlock will increase the count for the number of available resources, likely resulting in a crash or unpredictable behavior when the system nears capacity.",,medium, CWE-767,EN-Access to Critical Private Variable via Public Method (Type: Variant),"The software defines a public method that reads or modifies a private variable. -If an attacker modifies the variable to contain unexpected values, this could violate assumptions from other parts of the code. Additionally, if an attacker can read the private variable, it may expose sensitive information or make it easier to launch further attacks.",,medium, +If an attacker modifies the variable to contain unexpected values, this could violate assumptions from other parts of the code. Additionally, if an attacker can read the private variable, it may expose sensitive information or make it easier to launch further attacks.",,medium, CWE-776,EN-Improper Restriction of Recursive Entity References in DTDs (XML Entity Expansion) (Type: Variant),"The software uses XML documents and allows their structure to be defined with a Document Type Definition (DTD), but it does not properly control the number of recursive definitions of entities. If the DTD contains a large number of nested or recursive entities, this can lead to explosive growth of data when parsed, causing a denial of service.",,medium,"Multiple vendors XML parser (and SOAP/WebServices server) Denial of Service attack using DTD: http://www.securityfocus.com/archive/1/303509 XML security: Preventing XML bombs: http://searchsoftwarequality.techtarget.com/expert/KnowledgebaseAnswer/0,289625,sid92_gci1168442,00.html?asrc=SS_CLA_302%20%20558&psrc=CLT_92# @@ -390,11 +390,11 @@ Dismantling an XML-Bomb: http://blog.didierstevens.com/2008/09/23/dismantling-an XML Entity Expansion: http://projects.webappsec.org/XML-Entity-Expansion Tip: Configure SAX parsers for secure processing: http://www.ibm.com/developerworks/xml/library/x-tipcfsx.html XML Denial of Service Attacks and Defenses: http://msdn.microsoft.com/en-us/magazine/ee335713.aspx -Preventing Entity Expansion Attacks in JAXB: http://blog.bdoughan.com/2011/03/preventing-entity-expansion-attacks-in.html" +Preventing Entity Expansion Attacks in JAXB: http://blog.bdoughan.com/2011/03/preventing-entity-expansion-attacks-in.html" CWE-777,EN-Regular Expression without Anchors (Type: Variant),"The software uses a regular expression to perform neutralization, but the regular expression is not anchored and may allow malicious or malformed data to slip through. -When performing tasks such as whitelist validation, data is examined and possibly modified to ensure that it is well-formed and adheres to a list of safe values. If the regular expression is not anchored, malicious or malformed data may be included before or after any string matching the regular expression. The type of malicious data that is allowed will depend on the context of the application and which anchors are omitted from the regular expression.",,medium, +When performing tasks such as whitelist validation, data is examined and possibly modified to ensure that it is well-formed and adheres to a list of safe values. If the regular expression is not anchored, malicious or malformed data may be included before or after any string matching the regular expression. The type of malicious data that is allowed will depend on the context of the application and which anchors are omitted from the regular expression.",,medium, CWE-779,EN-Logging of Excessive Data (Type: Base),"The software logs too much information, making log files hard to process and possibly hindering recovery efforts or forensic analysis after an attack. -While logging is a good practice in general, and very high levels of logging are appropriate for debugging stages of development, too much logging in a production environment might hinder a system administrator's ability to detect anomalous conditions. This can provide cover for an attacker while attempting to penetrate a system, clutter the audit trail for forensic analysis, or make it more difficult to debug problems in a production environment.",,medium, +While logging is a good practice in general, and very high levels of logging are appropriate for debugging stages of development, too much logging in a production environment might hinder a system administrator's ability to detect anomalous conditions. This can provide cover for an attacker while attempting to penetrate a system, clutter the audit trail for forensic analysis, or make it more difficult to debug problems in a production environment.",,medium, CWE-781,EN-Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code (Type: Variant),"The software defines an IOCTL that uses METHOD_NEITHER for I/O, but it does not validate or incorrectly validates the addresses that are provided. When an IOCTL uses the METHOD_NEITHER option for I/O control, it is the responsibility of the IOCTL to validate the addresses that have been supplied to it. If validation is missing or incorrect, attackers can supply arbitrary memory addresses, leading to code execution or a denial of service.",,medium,"Exploiting Common Flaws in Drivers: http://reversemode.com/index.php?option=com_content&task=view&id=38&Itemid=1 Remote and Local Exploitation of Network Drivers: https://www.blackhat.com/presentations/bh-usa-07/Bulygin/Presentation/bh-usa-07-bulygin.pdf @@ -402,25 +402,25 @@ Windows driver vulnerabilities: the METHOD_NEITHER odyssey: http://www.net-secur Buffer Descriptions for I/O Control Codes: http://msdn.microsoft.com/en-us/library/ms795857.aspx Using Neither Buffered Nor Direct I/O: http://msdn.microsoft.com/en-us/library/cc264614.aspx Securing Device Objects: http://msdn.microsoft.com/en-us/library/ms794722.aspx -No description: http://www.piotrbania.com/all/articles/ewdd.pdf" +No description: http://www.piotrbania.com/all/articles/ewdd.pdf" CWE-782,EN-Exposed IOCTL with Insufficient Access Control (Type: Variant),"The software implements an IOCTL with functionality that should be restricted, but it does not properly enforce access control for the IOCTL. When an IOCTL contains privileged functionality and is exposed unnecessarily, attackers may be able to access this functionality by invoking the IOCTL. Even if the functionality is benign, if the programmer has assumed that the IOCTL would only be accessed by a trusted process, there may be little or no validation of the incoming data, exposing weaknesses that would never be reachable if the attacker cannot call the IOCTL directly. -The implementations of IOCTLs will differ between operating system types and versions, so the methods of attack and prevention may vary widely.",,medium,Securing Device Objects: http://msdn.microsoft.com/en-us/library/ms794722.aspx +The implementations of IOCTLs will differ between operating system types and versions, so the methods of attack and prevention may vary widely.",,medium,Securing Device Objects: http://msdn.microsoft.com/en-us/library/ms794722.aspx CWE-117,EN-Improper Output Neutralization for Logs (Type: Base),"The software does not neutralize or incorrectly neutralizes output that is written to logs. This can allow an attacker to forge log entries or inject malicious content into logs. Log forging vulnerabilities occur when: Data enters an application from an untrusted source. The data is written to an application or system log file.",,medium,"Exploiting Software: How to Break Code The night the log was forged: http://doc.novsu.ac.ru/oreilly/tcpip/puis/ch10_05.htm -OWASP TOP 10: http://www.owasp.org/index.php/Top_10_2007" +OWASP TOP 10: http://www.owasp.org/index.php/Top_10_2007" CWE-124,EN-Buffer Underwrite (Buffer Underflow) (Type: Base),"The software writes to a buffer using an index or pointer that references a memory location prior to the beginning of the buffer. This typically occurs when a pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used.",,medium,"Buffer UNDERFLOWS: What do you know about it?: http://seclists.org/vuln-dev/2004/Jan/0022.html -24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" +24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" CWE-128,EN-Wrap-around Error (Type: Base),"Wrap around errors occur whenever a value is incremented past the maximum value for its type and therefore ""wraps around"" to a very small, negative, or undefined value. This typically occurs when the pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. This may result in exposure of sensitive information or possibly a crash.",,medium,"24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89 -The Art of Software Security Assessment: Chapter 6, ""Signed Integer Boundaries"", Page 220." +The Art of Software Security Assessment: Chapter 6, ""Signed Integer Boundaries"", Page 220." CWE-170,EN-Improper Null Termination (Type: Base),"The software does not terminate or incorrectly terminates a string or array with a null character or equivalent terminator. -Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,medium, +Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,medium, CWE-190,EN-Integer Overflow or Wraparound (Type: Base),"The software performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control. An integer overflow or wraparound occurs when an integer value is incremented to a value that is too large to store in the associated representation. When this occurs, the value may wrap to become a very small or negative number. While this may be intended behavior in circumstances that rely on wrapping, it can have security consequences if the wrap is unexpected. This is especially the case if the integer overflow can be triggered using user-supplied inputs. This becomes security-critical when the result is used to control looping, make a security decision, or determine the offset or size in behaviors such as memory allocation, copying, concatenation, etc.",,medium,"An overview of common programming security vulnerabilities and possible solutions: http://fort-knox.org/thesis.pdf Basic Integer Overflows: http://www.phrack.org/issues.html?issue=60&id=10#article @@ -428,11 +428,11 @@ Writing Secure Code: Chapter 20, ""Integer Overflows"" Page 620 24 Deadly Sins of Software Security: ""Sin 7: Integer Overflows."" Page 119 SafeInt: http://safeint.codeplex.com/ Top 25 Series - Rank 17 - Integer Overflow Or Wraparound: http://software-security.sans.org/blog/2010/03/18/top-25-series-rank-17-integer-overflow-or-wraparound -The Art of Software Security Assessment: Chapter 6, ""Signed Integer Boundaries"", Page 220." +The Art of Software Security Assessment: Chapter 6, ""Signed Integer Boundaries"", Page 220." CWE-196,EN-Unsigned to Signed Conversion Error (Type: Variant),"An unsigned-to-signed conversion error takes place when a large unsigned primitive is used as a signed value. -It is dangerous to rely on implicit casts between signed and unsigned numbers because the result can take on an unexpected value and violate assumptions made by the program.",,medium,"The Art of Software Security Assessment: Chapter 6, ""Type Conversions"", Page 223." +It is dangerous to rely on implicit casts between signed and unsigned numbers because the result can take on an unexpected value and violate assumptions made by the program.",,medium,"The Art of Software Security Assessment: Chapter 6, ""Type Conversions"", Page 223." CWE-202,EN-Exposure of Sensitive Data Through Data Queries (Type: Variant),"When trying to keep information confidential, an attacker can often infer some of the information by using statistics. -In situations where data should not be tied to individual users, but a large number of users should be able to make queries that ""scrub"" the identity of users, it may be possible to get information about a user -- e.g., by specifying search terms that are known to be unique to that user.",,medium, +In situations where data should not be tied to individual users, but a large number of users should be able to make queries that ""scrub"" the identity of users, it may be possible to get information about a user -- e.g., by specifying search terms that are known to be unique to that user.",,medium, CWE-250,EN-Execution with Unnecessary Privileges (Type: Class),"The software performs an operation at a privilege level that is higher than the minimum level required, which creates new weaknesses or amplifies the consequences of other weaknesses. New weaknesses can be exposed because running with extra privileges, such as root or Administrator, can disable the normal security checks being performed by the operating system or surrounding environment. Other pre-existing weaknesses can turn into security vulnerabilities if they occur while operating at raised privileges. Privilege management functions can behave in some less-than-obvious ways, and they have different quirks on different platforms. These inconsistencies are particularly pronounced if you are transitioning from one non-root user to another. Signal handlers and spawned processes run at the privilege of the owning process, so if a process is running as root when a signal fires or a sub-process is executed, the signal handler or sub-process will operate with root privileges.",,medium,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ @@ -440,28 +440,28 @@ Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledg Writing Secure Code: Chapter 7, ""Running with Least Privilege"" Page 207 Federal Desktop Core Configuration: http://nvd.nist.gov/fdcc/index.cfm 24 Deadly Sins of Software Security: ""Sin 16: Executing Code With Too Much Privilege."" Page 243 -The Art of Software Security Assessment: Chapter 9, ""Privilege Vulnerabilities"", Page 477." +The Art of Software Security Assessment: Chapter 9, ""Privilege Vulnerabilities"", Page 477." CWE-269,EN-Improper Privilege Management (Type: Base),"The software does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor. Just as neglecting to include functionality for the management of password aging is dangerous, so is allowing password aging to continue unchecked. Passwords must be given a maximum life span, after which a user is required to update with a new and different password.",,medium,"24 Deadly Sins of Software Security: ""Sin 16: Executing Code With Too Much Privilege."" Page 243 -The Art of Software Security Assessment: Chapter 9, ""Dropping Privileges Permanently"", Page 479." +The Art of Software Security Assessment: Chapter 9, ""Dropping Privileges Permanently"", Page 479." CWE-273,EN-Improper Check for Dropped Privileges (Type: Base),"The software attempts to drop privileges but does not check or incorrectly checks to see if the drop succeeded. -If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,medium, +If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,medium, CWE-276,EN-Incorrect Default Permissions (Type: Variant),"The software, upon installation, sets incorrect permissions for an object that exposes it to an unintended actor. -If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,medium,"The Art of Software Security Assessment: Chapter 3, ""Insecure Defaults"", Page 69." +If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,medium,"The Art of Software Security Assessment: Chapter 3, ""Insecure Defaults"", Page 69." CWE-299,EN-Improper Check for Certificate Revocation (Type: Variant),"The software does not check or incorrectly checks the revocation status of a certificate, which may cause it to use a certificate that has been compromised. -An improper check for certificate revocation is a far more serious flaw than related certificate failures. This is because the use of any revoked certificate is almost certainly malicious. The most common reason for certificate revocation is compromise of the system in question, with the result that no legitimate servers will be using a revoked certificate, unless they are sorely out of sync.",,medium,"24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" +An improper check for certificate revocation is a far more serious flaw than related certificate failures. This is because the use of any revoked certificate is almost certainly malicious. The most common reason for certificate revocation is compromise of the system in question, with the result that no legitimate servers will be using a revoked certificate, unless they are sorely out of sync.",,medium,"24 Deadly Sins of Software Security: ""Sin 23: Improper Use of PKI, Especially SSL."" Page 347" CWE-301,EN-Reflection Attack in an Authentication Protocol (Type: Variant),"Simple authentication protocols are subject to reflection attacks if a malicious user can use the target machine to impersonate a trusted user. -A mutual authentication protocol requires each party to respond to a random challenge by the other party by encrypting it with a pre-shared key. Often, however, such protocols employ the same pre-shared key for communication with a number of different entities. A malicious user or an attacker can easily compromise this protocol without possessing the correct key by employing a reflection attack on the protocol.",,medium,"The Art of Software Security Assessment: Chapter 2, ""Insufficient Validation"", Page 38." +A mutual authentication protocol requires each party to respond to a random challenge by the other party by encrypting it with a pre-shared key. Often, however, such protocols employ the same pre-shared key for communication with a number of different entities. A malicious user or an attacker can easily compromise this protocol without possessing the correct key by employing a reflection attack on the protocol.",,medium,"The Art of Software Security Assessment: Chapter 2, ""Insufficient Validation"", Page 38." CWE-329,EN-Not Using a Random IV with CBC Mode (Type: Variant),"Not using a random initialization Vector (IV) with Cipher Block Chaining (CBC) Mode causes algorithms to be susceptible to dictionary attacks. -This weakness is especially dangerous when the hash is used in security algorithms that require the one-way property to hold. For example, if an authentication system takes an incoming password and generates a hash, then compares the hash to another hash that it has stored in its authentication database, then the ability to create a collision could allow an attacker to provide an alternate password that produces the same target hash, bypassing authentication.",,medium,"The Art of Software Security Assessment: Chapter 2, ""Initialization Vectors"", Page 42." +This weakness is especially dangerous when the hash is used in security algorithms that require the one-way property to hold. For example, if an authentication system takes an incoming password and generates a hash, then compares the hash to another hash that it has stored in its authentication database, then the ability to create a collision could allow an attacker to provide an alternate password that produces the same target hash, bypassing authentication.",,medium,"The Art of Software Security Assessment: Chapter 2, ""Initialization Vectors"", Page 42." CWE-332,EN-Insufficient Entropy in PRNG (Type: Variant),"The lack of entropy available for, or used by, a Pseudo-Random Number Generator (PRNG) can be a stability and security threat. -When software generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that will be generated, and use this guess to impersonate another user or access sensitive information.",,medium,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf +When software generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that will be generated, and use this guess to impersonate another user or access sensitive information.",,medium,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf CWE-338,EN-Use of Cryptographically Weak PRNG (Type: Base),"The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG is not cryptographically strong. -The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,medium,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,medium,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-353,EN-Missing Support for Integrity Check (Type: Base),"The software uses a transmission protocol that does not include a mechanism for verifying the integrity of the data during transmission, such as a checksum. -If integrity check values or ""checksums"" are omitted from a protocol, there is no way of determining if data has been corrupted in transmission. The lack of checksum functionality in a protocol removes the first application-level check of data that can be used. The end-to-end philosophy of checks states that integrity checks should be performed at the lowest level that they can be completely implemented. Excluding further sanity checks and input validation performed by applications, the protocol's checksum is the most important level of checksum, since it can be performed more completely than at any previous level and takes into account entire messages, as opposed to single packets.",,medium,"24 Deadly Sins of Software Security: ""Sin 15: Not Updating Easily."" Page 231" +If integrity check values or ""checksums"" are omitted from a protocol, there is no way of determining if data has been corrupted in transmission. The lack of checksum functionality in a protocol removes the first application-level check of data that can be used. The end-to-end philosophy of checks states that integrity checks should be performed at the lowest level that they can be completely implemented. Excluding further sanity checks and input validation performed by applications, the protocol's checksum is the most important level of checksum, since it can be performed more completely than at any previous level and takes into account entire messages, as opposed to single packets.",,medium,"24 Deadly Sins of Software Security: ""Sin 15: Not Updating Easily."" Page 231" CWE-354,EN-Improper Validation of Integrity Check Value (Type: Base),"The software does not validate or incorrectly validates the integrity check values or ""checksums"" of a message. This may prevent it from detecting if the data has been modified or corrupted in transmission. -Improper validation of checksums before use results in an unnecessary risk that can easily be mitigated. The protocol specification describes the algorithm used for calculating the checksum. It is then a simple matter of implementing the calculation and verifying that the calculated checksum and the received checksum match. Improper verification of the calculated checksum and the received checksum can lead to far greater consequences.",,medium, +Improper validation of checksums before use results in an unnecessary risk that can easily be mitigated. The protocol specification describes the algorithm used for calculating the checksum. It is then a simple matter of implementing the calculation and verifying that the calculated checksum and the received checksum match. Improper verification of the calculated checksum and the received checksum can lead to far greater consequences.",,medium, CWE-362,EN-Concurrent Execution using Shared Resource with Improper Synchronization (Race Condition) (Type: Class),"The program contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently. This can have security implications when the expected synchronization is in security-critical code, such as recording whether a user is authenticated or modifying important state information that should not be influenced by an outsider. A race condition occurs within concurrent environments, and is effectively a property of a code sequence. Depending on the context, a code sequence may be in the form of a function call, a small number of instructions, a series of program invocations, etc. @@ -479,7 +479,7 @@ Discovering and Exploiting Named Pipe Security Flaws for Fun and Profit: http:// On Race Vulnerabilities in Web Applications: http://security.dico.unimi.it/~roberto/pubs/dimva08-web.pdf Avoiding Race Conditions and Insecure File Operations: http://developer.apple.com/documentation/Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html Top 25 Series - Rank 25 - Race Conditions: http://blogs.sans.org/appsecstreetfighter/2010/03/26/top-25-series-rank-25-race-conditions/ -Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" +Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" CWE-364,EN-Signal Handler Race Condition (Type: Base),"The software uses a signal handler that introduces a race condition. Race conditions frequently occur in signal handlers, since signal handlers support asynchronous actions. These race conditions have a variety of root causes and symptoms. Attackers may be able to exploit a signal handler race condition to cause the software state to be corrupted, possibly leading to a denial of service or even code execution. These issues occur when non-reentrant functions, or state-sensitive actions occur in the signal handler, where they may be called at any time. These behaviors can violate assumptions being made by the ""regular"" code that is interrupted, or by other signal handlers that may also be invoked. If these functions are called at an inopportune moment - such as while a non-reentrant function is already running - memory corruption could occur that may be exploitable for code execution. Another signal race condition commonly found occurs when free is called within a signal handler, resulting in a double free and therefore a write-what-where condition. Even if a given pointer is set to NULL after it has been freed, a race condition still exists between the time the memory was freed and the pointer was set to NULL. This is especially problematic if the same signal handler has been set for more than one signal -- since it means that the signal handler itself may be reentered. @@ -497,7 +497,7 @@ Using synchronization in the regular code Disabling or masking other signals, which provides atomicity (which effectively ensures exclusivity)",,medium,"Delivering Signals for Fun and Profit: http://lcamtuf.coredump.cx/signals.txt Race Condition: Signal Handling: http://www.fortify.com/vulncat/en/vulncat/cpp/race_condition_signal_handling.html 24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205 -The Art of Software Security Assessment: Chapter 13, ""Signal Vulnerabilities"", Page 791." +The Art of Software Security Assessment: Chapter 13, ""Signal Vulnerabilities"", Page 791." CWE-365,EN-Race Condition in Switch (Type: Base),"The code contains a switch statement in which the switched variable can be modified while the switch is still executing, resulting in unexpected behavior. Race conditions frequently occur in signal handlers, since signal handlers support asynchronous actions. These race conditions have a variety of root causes and symptoms. Attackers may be able to exploit a signal handler race condition to cause the software state to be corrupted, possibly leading to a denial of service or even code execution. These issues occur when non-reentrant functions, or state-sensitive actions occur in the signal handler, where they may be called at any time. These behaviors can violate assumptions being made by the ""regular"" code that is interrupted, or by other signal handlers that may also be invoked. If these functions are called at an inopportune moment - such as while a non-reentrant function is already running - memory corruption could occur that may be exploitable for code execution. Another signal race condition commonly found occurs when free is called within a signal handler, resulting in a double free and therefore a write-what-where condition. Even if a given pointer is set to NULL after it has been freed, a race condition still exists between the time the memory was freed and the pointer was set to NULL. This is especially problematic if the same signal handler has been set for more than one signal -- since it means that the signal handler itself may be reentered. @@ -512,7 +512,7 @@ Signal handler vulnerabilities are often classified based on the absence of a sp Avoiding shared state Using synchronization in the signal handler Using synchronization in the regular code -Disabling or masking other signals, which provides atomicity (which effectively ensures exclusivity)",,medium,"24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" +Disabling or masking other signals, which provides atomicity (which effectively ensures exclusivity)",,medium,"24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" CWE-366,EN-Race Condition within a Thread (Type: Base),"If two threads of execution use a resource simultaneously, there exists the possibility that resources may be used while invalid, in turn making the state of execution undefined. Race conditions frequently occur in signal handlers, since signal handlers support asynchronous actions. These race conditions have a variety of root causes and symptoms. Attackers may be able to exploit a signal handler race condition to cause the software state to be corrupted, possibly leading to a denial of service or even code execution. These issues occur when non-reentrant functions, or state-sensitive actions occur in the signal handler, where they may be called at any time. These behaviors can violate assumptions being made by the ""regular"" code that is interrupted, or by other signal handlers that may also be invoked. If these functions are called at an inopportune moment - such as while a non-reentrant function is already running - memory corruption could occur that may be exploitable for code execution. Another signal race condition commonly found occurs when free is called within a signal handler, resulting in a double free and therefore a write-what-where condition. Even if a given pointer is set to NULL after it has been freed, a race condition still exists between the time the memory was freed and the pointer was set to NULL. This is especially problematic if the same signal handler has been set for more than one signal -- since it means that the signal handler itself may be reentered. @@ -528,43 +528,43 @@ Avoiding shared state Using synchronization in the signal handler Using synchronization in the regular code Disabling or masking other signals, which provides atomicity (which effectively ensures exclusivity)",,medium,"24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205 -The Art of Software Security Assessment: Chapter 13, ""Race Conditions"", Page 759." +The Art of Software Security Assessment: Chapter 13, ""Race Conditions"", Page 759." CWE-369,EN-Divide By Zero (Type: Base),"The product divides a value by zero. This weakness typically occurs when an unexpected value is provided to the product, or if an error occurs that is not properly detected. It frequently occurs in calculations involving physical dimensions such as size, length, width, and height.",,medium,"No description: http://www.cprogramming.com/tutorial/exceptions.html -No description: http://msdn.microsoft.com/en-us/library/ms173160(VS.80).aspx" +No description: http://msdn.microsoft.com/en-us/library/ms173160(VS.80).aspx" CWE-370,EN-Missing Check for Certificate Revocation after Initial Check (Type: Base),"The software does not check the revocation status of a certificate after its initial revocation check, which can cause the software to perform privileged actions even after the certificate is revoked at a later time. -If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,medium,"24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" +If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,medium,"24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" CWE-374,EN-Passing Mutable Objects to an Untrusted Method (Type: Base),"Sending non-cloned mutable data as an argument may result in that data being altered or deleted by the called function, thereby putting the calling function into an undefined state. If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,medium,"Does Java pass by reference or pass by value?: http://www.javaworld.com/javaworld/javaqa/2000-05/03-qa-0526-pass.html -Java: The Complete Reference, J2SE 5th Edition" +Java: The Complete Reference, J2SE 5th Edition" CWE-375,EN-Returning a Mutable Object to an Untrusted Caller (Type: Base),"Sending non-cloned mutable data as a return value may result in that data being altered or deleted by the calling function, thereby putting the class in an undefined state. -If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,medium, +If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,medium, CWE-385,EN-Covert Timing Channel (Type: Base),"Covert timing channels convey information by modulating some aspect of system behavior over time, so that the program receiving the information can observe system behavior and infer protected information. In some instances, knowing when data is transmitted between parties can provide a malicious user with privileged information. Also, externally monitoring the timing of operations can potentially reveal sensitive data. For example, a cryptographic operation can expose its internal state if the time it takes to perform the operation varies, based on the state. -Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,medium, +Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,medium, CWE-390,EN-Detection of Error Condition Without Action (Type: Class),"The software detects a specific error, but takes no actions to handle the error. In some instances, knowing when data is transmitted between parties can provide a malicious user with privileged information. Also, externally monitoring the timing of operations can potentially reveal sensitive data. For example, a cryptographic operation can expose its internal state if the time it takes to perform the operation varies, based on the state. -Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,medium,"24 Deadly Sins of Software Security: ""Sin 11: Failure to Handle Errors Correctly."" Page 183" +Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,medium,"24 Deadly Sins of Software Security: ""Sin 11: Failure to Handle Errors Correctly."" Page 183" CWE-391,EN-Unchecked Error Condition (Type: Base),"Ignoring exceptions and other error conditions may allow an attacker to induce unexpected behavior unnoticed. In some instances, knowing when data is transmitted between parties can provide a malicious user with privileged information. Also, externally monitoring the timing of operations can potentially reveal sensitive data. For example, a cryptographic operation can expose its internal state if the time it takes to perform the operation varies, based on the state. -Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,medium, +Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,medium, CWE-401,EN-Improper Release of Memory Before Removing Last Reference (Memory Leak) (Type: Base),"The software does not sufficiently track and release allocated memory after it has been used, which slowly consumes remaining memory. -This is often triggered by improper handling of malformed data or unexpectedly interrupted sessions.",,medium,How to Break Software Security +This is often triggered by improper handling of malformed data or unexpectedly interrupted sessions.",,medium,How to Break Software Security CWE-460,EN-Improper Cleanup on Thrown Exception (Type: Variant),"The product does not clean up its state or incorrectly cleans up its state when an exception is thrown, leading to unexpected state or control flow. -In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,medium, +In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,medium, CWE-468,EN-Incorrect Pointer Scaling (Type: Base),"In C and C++, one may often accidentally refer to the wrong memory due to the semantics of when math operations are implicitly scaled. -Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,medium,"The Art of Software Security Assessment: Chapter 6, ""Pointer Arithmetic"", Page 277." +Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,medium,"The Art of Software Security Assessment: Chapter 6, ""Pointer Arithmetic"", Page 277." CWE-469,EN-Use of Pointer Subtraction to Determine Size (Type: Base),"The application subtracts one pointer from another in order to determine size, but this calculation can be incorrect if the pointers do not exist in the same memory chunk. -Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,medium, +Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,medium, CWE-476,EN-NULL Pointer Dereference (Type: Base),"A NULL pointer dereference occurs when the application dereferences a pointer that it expects to be valid, but is NULL, typically causing a crash or exit. -NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.",,medium, +NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.",,medium, CWE-484,EN-Omitted Break Statement in Switch (Type: Base),"The program omits a break statement within a switch or similar construct, causing code associated with multiple conditions to execute. This can cause problems when the programmer only intended to execute code associated with one condition. -This can lead to critical code executing in situations where it should not.",,medium,"The Art of Software Security Assessment: Chapter 7, ""Switch Statements"", Page 337." +This can lead to critical code executing in situations where it should not.",,medium,"The Art of Software Security Assessment: Chapter 7, ""Switch Statements"", Page 337." CWE-487,EN-Reliance on Package-level Scope (Type: Variant),"Java packages are not inherently closed; therefore, relying on them for code security is not a good practice. -If the decision to trust the methods and data of an object is based on the name of a class, it is possible for malicious users to send objects of the same name as trusted classes and thereby gain the trust afforded to known classes and types.",,medium, +If the decision to trust the methods and data of an object is based on the name of a class, it is possible for malicious users to send objects of the same name as trusted classes and thereby gain the trust afforded to known classes and types.",,medium, CWE-492,EN-Use of Inner Class Containing Sensitive Data (Type: Variant),"Inner classes are translated into classes that are accessible at package scope and may expose code that the programmer intended to keep private to attackers. Data can ""bleed"" from one session to another through member variables of singleton objects, such as Servlets, and objects from a shared pool. -In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,medium, +In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,medium, CWE-494,EN-Download of Code Without Integrity Check (Type: Base),"The product downloads source code or an executable from a remote location and executes the code without sufficiently verifying the origin and integrity of the code. An attacker can execute malicious code by compromising the host server, performing DNS spoofing, or modifying the code in transit.",,medium,"Introduction to Code Signing: http://msdn.microsoft.com/en-us/library/ms537361(VS.85).aspx Authenticode: http://msdn.microsoft.com/en-us/library/ms537359(v=VS.85).aspx @@ -572,61 +572,61 @@ Code Signing Guide: http://developer.apple.com/documentation/Security/Conceptual Secure Software Updates: Disappointments and New Challenges: http://prisms.cs.umass.edu/~kevinfu/papers/secureupdates-hotsec06.pdf 24 Deadly Sins of Software Security: ""Sin 18: The Sins of Mobile Code."" Page 267 Top 25 Series - Rank 20 - Download of Code Without Integrity Check: http://blogs.sans.org/appsecstreetfighter/2010/04/05/top-25-series-rank-20-download-code-integrity-check/ -Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" +Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" CWE-498,EN-Cloneable Class Containing Sensitive Information (Type: Variant),"The code contains a class with sensitive data, but the class is cloneable. The data can then be accessed by cloning the class. -Cloneable classes are effectively open classes, since data cannot be hidden in them. Classes that do not explicitly deny cloning can be cloned by any other class without running the constructor.",,medium, +Cloneable classes are effectively open classes, since data cannot be hidden in them. Classes that do not explicitly deny cloning can be cloned by any other class without running the constructor.",,medium, CWE-502,EN-Deserialization of Untrusted Data (Type: Variant),"The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid. It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption. Data that is untrusted can not be trusted to be well-formed.",,medium,"Unserializing user-supplied data, a bad idea: http://heine.familiedeelstra.com/security/unserialize -Why Python Pickle is Insecure: http://nadiana.com/python-pickle-insecure" +Why Python Pickle is Insecure: http://nadiana.com/python-pickle-insecure" CWE-532,EN-Information Exposure Through Log Files (Type: Variant),"Information written to log files can be of a sensitive nature and give valuable guidance to an attacker or expose sensitive user information. -While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,medium, +While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,medium, CWE-602,EN-Client-Side Enforcement of Server-Side Security (Type: Base),"The software is composed of a server that relies on the client to implement a mechanism that is intended to protect the server. -When the server relies on protection mechanisms placed on the client side, an attacker can modify the client-side behavior to bypass the protection mechanisms resulting in potentially unexpected interactions between the client and server. The consequences will vary, depending on what the mechanisms are trying to protect.",,medium,"Writing Secure Code: Chapter 23, ""Client-Side Security Is an Oxymoron"" Page 687" +When the server relies on protection mechanisms placed on the client side, an attacker can modify the client-side behavior to bypass the protection mechanisms resulting in potentially unexpected interactions between the client and server. The consequences will vary, depending on what the mechanisms are trying to protect.",,medium,"Writing Secure Code: Chapter 23, ""Client-Side Security Is an Oxymoron"" Page 687" CWE-665,EN-Improper Initialization (Type: Base),"The software does not initialize or incorrectly initializes a resource, which might leave the resource in an unexpected state when it is accessed or used. This can have security implications when the associated resource is expected to have certain properties or values, such as a variable that determines whether a user has been authenticated or not.",,medium,"Exploiting Uninitialized Data: http://www.felinemenace.org/~mercy/papers/UBehavior/UBehavior.zip MS08-014 : The Case of the Uninitialized Stack Variable Vulnerability: http://blogs.technet.com/swi/archive/2008/03/11/the-case-of-the-uninitialized-stack-variable-vulnerability.aspx -The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." +The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." CWE-754,EN-Improper Check for Unusual or Exceptional Conditions (Type: Class),"The software does not check or improperly checks for unusual or exceptional conditions that are not expected to occur frequently during day to day operation of the software. The programmer may assume that certain events or conditions will never occur or do not need to be worried about, such as low memory conditions, lack of access to resources due to restrictive permissions, or misbehaving clients or components. However, attackers may intentionally trigger these unusual conditions, thus violating the programmer's assumptions, possibly introducing instability, incorrect behavior, or a vulnerability. Note that this entry is not exclusively about the use of exceptions and exception handling, which are mechanisms for both checking and handling unusual or unexpected conditions.",,medium,"The Art of Software Security Assessment: Chapter 7, ""Program Building Blocks"" Page 341 The Art of Software Security Assessment: Chapter 1, ""Exceptional Conditions,"" Page 22 24 Deadly Sins of Software Security: ""Sin 11: Failure to Handle Errors Correctly."" Page 183 -Top 25 Series - Rank 15 - Improper Check for Unusual or Exceptional Conditions: http://blogs.sans.org/appsecstreetfighter/2010/03/15/top-25-series-rank-15-improper-check-for-unusual-or-exceptional-conditions/" +Top 25 Series - Rank 15 - Improper Check for Unusual or Exceptional Conditions: http://blogs.sans.org/appsecstreetfighter/2010/03/15/top-25-series-rank-15-improper-check-for-unusual-or-exceptional-conditions/" CWE-778,EN-Insufficient Logging (Type: Base),"When a security-critical event occurs, the software either does not record the event or omits important details about the event when logging it. -When security-critical events are not logged properly, such as a failed login attempt, this can make malicious behavior more difficult to detect and may hinder forensic analysis after an attack succeeds.",,medium,"The Art of Software Security Assessment: Chapter 2, ""Accountability"", Page 40." +When security-critical events are not logged properly, such as a failed login attempt, this can make malicious behavior more difficult to detect and may hinder forensic analysis after an attack succeeds.",,medium,"The Art of Software Security Assessment: Chapter 2, ""Accountability"", Page 40." CWE-780,EN-Use of RSA Algorithm without OAEP (Type: Variant),"The software uses the RSA algorithm but does not incorporate Optimal Asymmetric Encryption Padding (OAEP), which might weaken the encryption. Padding schemes are often used with cryptographic algorithms to make the plaintext less predictable and complicate attack efforts. The OAEP scheme is often used with RSA to nullify the impact of predictable common text.",,medium,"RSA Problem: http://people.csail.mit.edu/rivest/RivestKaliski-RSAProblem.pdf -Optimal Asymmetric Encryption Padding: http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding" +Optimal Asymmetric Encryption Padding: http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding" CWE-908,EN-Use of Uninitialized Resource (Type: Base),"The software uses a resource that has not been properly initialized. -This can have security implications when the associated resource is expected to have certain properties or values.",,medium,Exploiting Uninitialized Data: http://www.felinemenace.org/~mercy/papers/UBehavior/UBehavior.zip +This can have security implications when the associated resource is expected to have certain properties or values.",,medium,Exploiting Uninitialized Data: http://www.felinemenace.org/~mercy/papers/UBehavior/UBehavior.zip CWE-909,EN-Missing Initialization of Resource (Type: Base),"The software does not initialize a critical resource. -Many resources require initialization before they can be properly used. If a resource is not initialized, it could contain unpredictable or expired data, or it could be initialized to defaults that are invalid. This can have security implications when the resource is expected to have certain properties or values.",,medium, +Many resources require initialization before they can be properly used. If a resource is not initialized, it could contain unpredictable or expired data, or it could be initialized to defaults that are invalid. This can have security implications when the resource is expected to have certain properties or values.",,medium, CWE-910,EN-Use of Expired File Descriptor (Type: Base),"The software uses or accesses a file descriptor after it has been closed. -After a file descriptor for a particular file or device has been released, it can be reused. The code might not write to the original file, since the reused file descriptor might reference a different file or device.",,medium, +After a file descriptor for a particular file or device has been released, it can be reused. The code might not write to the original file, since the reused file descriptor might reference a different file or device.",,medium, CWE-911,EN-Improper Update of Reference Count (Type: Base),"The software uses a reference count to manage a resource, but it does not update or incorrectly updates the reference count. -Reference counts can be used when tracking how many objects contain a reference to a particular resource, such as in memory management or garbage collection. When the reference count reaches zero, the resource can be de-allocated or reused because there are no more objects that use it. If the reference count accidentally reaches zero, then the resource might be released too soon, even though it is still in use. If all objects no longer use the resource, but the reference count is not zero, then the resource might not ever be released.",,medium,Windows Kernel Reference Count Vulnerabilities - Case Study: http://j00ru.vexillium.org/dump/zn_slides.pdf +Reference counts can be used when tracking how many objects contain a reference to a particular resource, such as in memory management or garbage collection. When the reference count reaches zero, the resource can be de-allocated or reused because there are no more objects that use it. If the reference count accidentally reaches zero, then the resource might be released too soon, even though it is still in use. If all objects no longer use the resource, but the reference count is not zero, then the resource might not ever be released.",,medium,Windows Kernel Reference Count Vulnerabilities - Case Study: http://j00ru.vexillium.org/dump/zn_slides.pdf CWE-94,EN-Improper Control of Generation of Code (Code Injection) (Type: Class),"The software constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. When software allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the software. Such an alteration could lead to arbitrary code execution. -Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.",,medium,"24 Deadly Sins of Software Security: ""Sin 3: Web-Client Related Vulnerabilities (XSS)."" Page 63" +Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.",,medium,"24 Deadly Sins of Software Security: ""Sin 3: Web-Client Related Vulnerabilities (XSS)."" Page 63" CWE-95,EN-Improper Neutralization of Directives in Dynamically Evaluated Code (Eval Injection) (Type: Base),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes code syntax before using the input in a dynamic evaluation call (e.g. ""eval""). This may allow an attacker to execute arbitrary code, or at least modify what code can be executed.",,medium,"No description: http://www.rubycentral.com/book/taint.html -The Art of Software Security Assessment: Chapter 18, ""Inline Evaluation"", Page 1095." +The Art of Software Security Assessment: Chapter 18, ""Inline Evaluation"", Page 1095." CWE-287,EN-Improper Authentication (Type: Class),"When an actor claims to have a given identity, the software does not prove or insufficiently proves that the claim is correct. Users can be assigned to the wrong group (class) of permissions resulting in unintended access rights to sensitive objects.",,high,"Weak Password Brings 'Happiness' to Twitter Hacker: http://www.wired.com/threatlevel/2009/01/professed-twitt/ Top 10 2007-Broken Authentication and Session Management: http://www.owasp.org/index.php/Top_10_2007-A7 Guide to Authentication: http://www.owasp.org/index.php/Guide_to_Authentication Authentication: http://msdn.microsoft.com/en-us/library/aa374735(VS.85).aspx -Writing Secure Code: Chapter 4, ""Authentication"" Page 109" +Writing Secure Code: Chapter 4, ""Authentication"" Page 109" CWE-306,EN-Missing Authentication for Critical Function (Type: Variant),"The software does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources. Authentication techniques should follow the algorithms that define them exactly, otherwise authentication can be bypassed or more easily subjected to brute force attacks.",,high,"The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Authentication,"" Page 36 Top 25 Series - Rank 19 - Missing Authentication for Critical Function: http://blogs.sans.org/appsecstreetfighter/2010/02/23/top-25-series-rank-19-missing-authentication-for-critical-function/ -OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" +OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" CWE-319,EN-Cleartext Transmission of Sensitive Information (Type: Base),"The software transmits sensitive or security-critical data in cleartext in a communication channel that can be sniffed by unauthorized actors. Many communication channels can be ""sniffed"" by attackers during data transmission. For example, network traffic can often be sniffed by any attacker who has access to a network interface. This significantly lowers the difficulty of exploitation by attackers.",,high,"Top 10 2007-Insecure Communications: http://www.owasp.org/index.php/Top_10_2007-A9 Writing Secure Code: Chapter 9, ""Protecting Secret Data"" Page 299 24 Deadly Sins of Software Security: ""Sin 22: Failing to Protect Network Traffic."" Page 337 -Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/" +Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/" CWE-327,EN-Use of a Broken or Risky Cryptographic Algorithm (Type: Base),"The use of a broken or risky cryptographic algorithm is an unnecessary risk that may result in the exposure of sensitive information. The use of a non-standard algorithm is dangerous because a determined attacker may be able to break the algorithm and compromise whatever data has been protected. Well-known techniques may exist to break the algorithm.",,high,"Applied Cryptography: http://www.schneier.com/book-applied.html Handbook of Applied Cryptography: http://www.cacr.math.uwaterloo.ca/hac/ @@ -636,12 +636,12 @@ Microsoft Scraps Old Encryption in New Code: http://www.eweek.com/c/a/Security/M Writing Secure Code: Chapter 8, ""Cryptographic Foibles"" Page 259 24 Deadly Sins of Software Security: ""Sin 21: Using the Wrong Cryptography."" Page 315 Top 25 Series - Rank 24 - Use of a Broken or Risky Cryptographic Algorithm: http://blogs.sans.org/appsecstreetfighter/2010/03/25/top-25-series-rank-24-use-of-a-broken-or-risky-cryptographic-algorithm/ -The Art of Software Security Assessment: Chapter 2, ""Insufficient or Obsolete Encryption"", Page 44." +The Art of Software Security Assessment: Chapter 2, ""Insufficient or Obsolete Encryption"", Page 44." CWE-330,EN-Use of Insufficiently Random Values (Type: Class),"The software may use insufficiently random numbers or values in a security context that depends on unpredictable numbers. When software generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that will be generated, and use this guess to impersonate another user or access sensitive information.",,high,"SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf Building Secure Software: How to Avoid Security Problems the Right Way Writing Secure Code: Chapter 8, ""Using Poor Random Numbers"" Page 259 -24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-400,EN-Uncontrolled Resource Consumption (Resource Exhaustion) (Type: Base),"The software does not properly restrict the size or amount of resources that are requested or influenced by an actor, which can be used to consume more resources than intended. Limited resources include memory, file system storage, database connection pool entries, or CPU. If an attacker can trigger the allocation of these limited resources, but the number or size of the resources is not controlled, then the attacker could cause a denial of service that consumes all available resources. This would prevent valid users from accessing the software, and it could potentially have an impact on the surrounding environment. For example, a memory exhaustion attack against an application could slow down the application as well as its host operating system. Resource exhaustion problems have at least two common causes: @@ -649,22 +649,22 @@ Error conditions and other exceptional circumstances Confusion over which part of the program is responsible for releasing the resource",,high,"Detection and Prediction of Resource-Exhaustion Vulnerabilities: http://homepages.di.fc.ul.pt/~nuno/PAPERS/ISSRE08.pdf Resource exhaustion: http://cr.yp.to/docs/resources.html Resource exhaustion: http://homes.cerias.purdue.edu/~pmeunier/secprog/sanitized/class1/6.resource%20exhaustion.ppt -Writing Secure Code: Chapter 17, ""Protecting Against Denial of Service Attacks"" Page 517" +Writing Secure Code: Chapter 17, ""Protecting Against Denial of Service Attacks"" Page 517" CWE-434,EN-Unrestricted Upload of File with Dangerous Type (Type: Base),"The software allows the attacker to upload or transfer files of dangerous types that can be automatically processed within the product's environment. If code is stored in a file with an extension such as "".inc"" or "".pl"", and the web server does not have a handler for that extension, then the server will likely send the contents of the file directly to the requester without the pre-processing that was expected. When that file contains sensitive information such as database credentials, this may allow the attacker to compromise the application or associated components.",,high,"Dynamic File Uploads, Security and You: http://shsc.info/FileUploadSecurity 8 Basic Rules to Implement Secure File Uploads: http://blogs.sans.org/appsecstreetfighter/2009/12/28/8-basic-rules-to-implement-secure-file-uploads/ Top 25 Series - Rank 8 - Unrestricted Upload of Dangerous File Type: http://blogs.sans.org/appsecstreetfighter/2010/02/25/top-25-series-rank-8-unrestricted-upload-of-dangerous-file-type/ Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html -The Art of Software Security Assessment: Chapter 17, ""File Uploading"", Page 1068." +The Art of Software Security Assessment: Chapter 17, ""File Uploading"", Page 1068." CWE-64,EN-Windows Shortcut Following (.LNK) (Type: Variant),"The software, when opening a file or directory, does not sufficiently handle when the file is a Windows shortcut (.LNK) whose target is outside of the intended control sphere. This could allow an attacker to cause the software to operate on unauthorized files. -The shortcut (file with the .lnk extension) can permit an attacker to read/write a file that they originally did not have permissions to access.",,high, +The shortcut (file with the .lnk extension) can permit an attacker to read/write a file that they originally did not have permissions to access.",,high, CWE-681,EN-Incorrect Conversion between Numeric Types (Type: Base),"When converting from one data type to another, such as long to integer, data can be omitted or translated in a way that produces unexpected values. If the resulting values are used in a sensitive context, then dangerous behaviors may occur. -Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,high,"The Art of Software Security Assessment: Chapter 6, ""Type Conversions"", Page 223." +Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,high,"The Art of Software Security Assessment: Chapter 6, ""Type Conversions"", Page 223." CWE-732,EN-Incorrect Permission Assignment for Critical Resource (Type: Class),"The software specifies permissions for a security-critical resource in a way that allows that resource to be read or modified by unintended actors. When a resource is given a permissions setting that provides access to a wider range of actors than required, it could lead to the exposure of sensitive information, or the modification of that resource by unintended parties. This is especially dangerous when the resource is related to program configuration, execution or sensitive user data.",,high,"The Art of Software Security Assessment: Chapter 9, ""File Permissions."" Page 495. Building Secure Software: How to Avoid Security Problems the Right Way: Chapter 8, ""Access Control."" Page 194. Top 25 Series - Rank 21 - Incorrect Permission Assignment for Critical Response: http://software-security.sans.org/blog/2010/03/24/top-25-series-rank-21-incorrect-permission-assignment-for-critical-response -Federal Desktop Core Configuration: http://nvd.nist.gov/fdcc/index.cfm" +Federal Desktop Core Configuration: http://nvd.nist.gov/fdcc/index.cfm" CWE-770,EN-Allocation of Resources Without Limits or Throttling (Type: Base),"The software allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on how many resources can be allocated, in violation of the intended security policy for that actor. Command injection vulnerabilities typically occur when: 1. Data enters the application from an untrusted source. @@ -675,17 +675,17 @@ Resource exhaustion: http://cr.yp.to/docs/resources.html Resource exhaustion: http://homes.cerias.purdue.edu/~pmeunier/secprog/sanitized/class1/6.resource%20exhaustion.ppt Writing Secure Code: Chapter 17, ""Protecting Against Denial of Service Attacks"" Page 517 Top 25 Series - Rank 22 - Allocation of Resources Without Limits or Throttling: http://blogs.sans.org/appsecstreetfighter/2010/03/23/top-25-series-rank-22-allocation-of-resources-without-limits-or-throttling/ -The Art of Software Security Assessment: Chapter 10, ""Resource Limits"", Page 574." +The Art of Software Security Assessment: Chapter 10, ""Resource Limits"", Page 574." CWE-771,EN-Missing Reference to Active Allocated Resource (Type: Base),"The software does not properly maintain a reference to a resource that has been allocated, which prevents the resource from being reclaimed. -This does not necessarily apply in languages or frameworks that automatically perform garbage collection, since the removal of all references may act as a signal that the resource is ready to be reclaimed.",,high, +This does not necessarily apply in languages or frameworks that automatically perform garbage collection, since the removal of all references may act as a signal that the resource is ready to be reclaimed.",,high, CWE-772,EN-Missing Release of Resource after Effective Lifetime (Type: Base),"The software does not release a resource after its effective lifetime has ended, i.e., after the resource is no longer needed. -When a resource is not released after use, it can allow attackers to cause a denial of service.",,high, +When a resource is not released after use, it can allow attackers to cause a denial of service.",,high, CWE-773,EN-Missing Reference to Active File Descriptor or Handle (Type: Variant),"The software does not properly maintain references to a file descriptor or handle, which prevents that file descriptor/handle from being reclaimed. -This can cause the software to consume all available file descriptors or handles, which can prevent other processes from performing critical file processing operations.",,high, +This can cause the software to consume all available file descriptors or handles, which can prevent other processes from performing critical file processing operations.",,high, CWE-774,EN-Allocation of File Descriptors or Handles Without Limits or Throttling (Type: Variant),"The software allocates file descriptors or handles on behalf of an actor without imposing any restrictions on how many descriptors can be allocated, in violation of the intended security policy for that actor. -This can cause the software to consume all available file descriptors or handles, which can prevent other processes from performing critical file processing operations.",,high,"The Art of Software Security Assessment: Chapter 10, ""Resource Limits"", Page 574." +This can cause the software to consume all available file descriptors or handles, which can prevent other processes from performing critical file processing operations.",,high,"The Art of Software Security Assessment: Chapter 10, ""Resource Limits"", Page 574." CWE-775,EN-Missing Release of File Descriptor or Handle after Effective Lifetime (Type: Variant),"The software does not release a file descriptor or handle after its effective lifetime has ended, i.e., after the file descriptor/handle is no longer needed. -When a file descriptor or handle is not released after use (typically by explicitly closing it), attackers can cause a denial of service by consuming all available file descriptors/handles, or otherwise preventing other system processes from obtaining their own file descriptors/handles.",,high,"The Art of Software Security Assessment: Chapter 10, ""File Descriptor Leaks"", Page 582." +When a file descriptor or handle is not released after use (typically by explicitly closing it), attackers can cause a denial of service by consuming all available file descriptors/handles, or otherwise preventing other system processes from obtaining their own file descriptors/handles.",,high,"The Art of Software Security Assessment: Chapter 10, ""File Descriptor Leaks"", Page 582." CWE-804,EN-Guessable CAPTCHA (Type: Base),"The software uses a CAPTCHA challenge, but the challenge can be guessed or automatically recognized by a non-human actor. An automated attacker could bypass the intended protection of the CAPTCHA challenge and perform actions at a higher frequency than humanly possible, such as launching spam attacks. There can be several different causes of a guessable CAPTCHA: @@ -693,7 +693,7 @@ An audio or visual image that does not have sufficient distortion from the unobf A question is generated that with a format that can be automatically recognized, such as a math question. A question for which the number of possible answers is limited, such as birth years or favorite sports teams. A general-knowledge or trivia question for which the answer can be accessed using a data base, such as country capitals or popular actors. -Other data associated with the CAPTCHA may provide hints about its contents, such as an image whose filename contains the word that is used in the CAPTCHA.",,high,Insufficient Anti-automation: http://projects.webappsec.org/Insufficient+Anti-automation +Other data associated with the CAPTCHA may provide hints about its contents, such as an image whose filename contains the word that is used in the CAPTCHA.",,high,Insufficient Anti-automation: http://projects.webappsec.org/Insufficient+Anti-automation CWE-805,EN-Buffer Access with Incorrect Length Value (Type: Base),"The software uses a sequential operation to read or write a buffer, but it uses an incorrect length value that causes it to access memory that is outside of the bounds of the buffer. When the length value exceeds the size of the destination, a buffer overflow could occur.",,high,"Writing Secure Code: Chapter 6, ""Why ACLs Are Important"" Page 171 Address Space Layout Randomization in Windows Vista: http://blogs.msdn.com/michael_howard/archive/2006/05/26/address-space-layout-randomization-in-windows-vista.aspx @@ -703,84 +703,84 @@ Top 25 Series - Rank 12 - Buffer Access with Incorrect Length Value: http://blog Safe C String Library v1.0.3: http://www.zork.org/safestr/ Using the Strsafe.h Functions: http://msdn.microsoft.com/en-us/library/ms647466.aspx Understanding DEP as a mitigation technology part 1: http://blogs.technet.com/b/srd/archive/2009/06/12/understanding-dep-as-a-mitigation-technology-part-1.aspx -Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" +Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" CWE-806,EN-Buffer Access Using Size of Source Buffer (Type: Variant),"The software uses the size of a source buffer when reading from or writing to a destination buffer, which may cause it to access memory that is outside of the bounds of the buffer. When the size of the destination is smaller than the size of the source, a buffer overflow could occur.",,high,"Using the Strsafe.h Functions: http://msdn.microsoft.com/en-us/library/ms647466.aspx Safe C String Library v1.0.3: http://www.zork.org/safestr/ Address Space Layout Randomization in Windows Vista: http://blogs.msdn.com/michael_howard/archive/2006/05/26/address-space-layout-randomization-in-windows-vista.aspx Limiting buffer overflows with ExecShield: http://www.redhat.com/magazine/009jul05/features/execshield/ PaX: http://en.wikipedia.org/wiki/PaX -Understanding DEP as a mitigation technology part 1: http://blogs.technet.com/b/srd/archive/2009/06/12/understanding-dep-as-a-mitigation-technology-part-1.aspx" +Understanding DEP as a mitigation technology part 1: http://blogs.technet.com/b/srd/archive/2009/06/12/understanding-dep-as-a-mitigation-technology-part-1.aspx" CWE-807,EN-Reliance on Untrusted Inputs in a Security Decision (Type: Base),"The application uses a protection mechanism that relies on the existence or values of an input, but the input can be modified by an untrusted actor in a way that bypasses the protection mechanism. Developers may assume that inputs such as cookies, environment variables, and hidden form fields cannot be modified. However, an attacker could change these inputs using customized clients or other attacks. This change might not be detected. When security decisions such as authentication and authorization are made based on the values of these inputs, attackers can bypass the security of the software. Without sufficient encryption, integrity checking, or other mechanism, any input that originates from an outsider cannot be trusted.",,high,"Top 25 Series - Rank 6 - Reliance on Untrusted Inputs in a Security Decision: http://blogs.sans.org/appsecstreetfighter/2010/03/05/top-25-series-rank-6-reliance-on-untrusted-inputs-in-a-security-decision/ HMAC: http://en.wikipedia.org/wiki/Hmac Understanding ASP.NET View State: http://msdn.microsoft.com/en-us/library/ms972976.aspx -OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" +OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" CWE-93,EN-Improper Neutralization of CRLF Sequences (CRLF Injection) (Type: Base),"The software uses CRLF (carriage return line feeds) as a special element, e.g. to separate lines or records, but it does not neutralize or incorrectly neutralizes CRLF sequences from inputs. -Since an implicit intent does not specify a particular application to receive the data, any application can process the intent by using an Intent Filter for that intent. This can allow untrusted applications to obtain sensitive data.",,high,CRLF Injection: http://marc.info/?l=bugtraq&m=102088154213630&w=2 +Since an implicit intent does not specify a particular application to receive the data, any application can process the intent by using an Intent Filter for that intent. This can allow untrusted applications to obtain sensitive data.",,high,CRLF Injection: http://marc.info/?l=bugtraq&m=102088154213630&w=2 CWE-102,EN-Struts: Duplicate Validation Forms (Type: Variant),"The application uses multiple validation forms with the same name, which might cause the Struts Validator to validate a form that the programmer does not expect. -If two validation forms have the same name, the Struts Validator arbitrarily chooses one of the forms to use for input validation and discards the other. This decision might not correspond to the programmer's expectations, possibly leading to resultant weaknesses. Moreover, it indicates that the validation logic is not up-to-date, and can indicate that other, more subtle validation errors are present.",,unclassified, +If two validation forms have the same name, the Struts Validator arbitrarily chooses one of the forms to use for input validation and discards the other. This decision might not correspond to the programmer's expectations, possibly leading to resultant weaknesses. Moreover, it indicates that the validation logic is not up-to-date, and can indicate that other, more subtle validation errors are present.",,unclassified, CWE-103,EN-Struts: Incomplete validate() Method Definition (Type: Variant),"The application has a validator form that either does not define a validate() method, or defines a validate() method but does not call super.validate(). -If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, +If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, CWE-104,EN-Struts: Form Bean Does Not Extend Validation Class (Type: Variant),"If a form bean does not extend an ActionForm subclass of the Validator framework, it can expose the application to other weaknesses related to insufficient input validation. -If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, +If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, CWE-105,EN-Struts: Form Field Without Validator (Type: Variant),"The application has a form field that is not validated by a corresponding validation form, which can introduce other weaknesses related to insufficient input validation. -If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, +If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, CWE-106,EN-Struts: Plug-in Framework not in Use (Type: Variant),"When an application does not use an input validation framework such as the Struts Validator, there is a greater risk of introducing weaknesses related to insufficient input validation. -If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, +If you do not call super.validate(), the Validation Framework cannot check the contents of the form against a validation form. In other words, the validation framework will be disabled for the given form.",,unclassified, CWE-107,EN-Struts: Unused Validation Form (Type: Variant),"An unused validation form indicates that validation logic is not up-to-date. -It is easy for developers to forget to update validation logic when they remove or rename action form mappings. One indication that validation logic is not being properly maintained is the presence of an unused validation form.",,unclassified, +It is easy for developers to forget to update validation logic when they remove or rename action form mappings. One indication that validation logic is not being properly maintained is the presence of an unused validation form.",,unclassified, CWE-108,EN-Struts: Unvalidated Action Form (Type: Variant),"Every Action Form must have a corresponding validation form. -If a Struts Action Form Mapping specifies a form, it must have a validation form defined under the Struts Validator.",,unclassified, +If a Struts Action Form Mapping specifies a form, it must have a validation form defined under the Struts Validator.",,unclassified, CWE-109,EN-Struts: Validator Turned Off (Type: Variant),"Automatic filtering via a Struts bean has been turned off, which disables the Struts Validator and custom validation logic. This exposes the application to other weaknesses related to insufficient input validation. -If a Struts Action Form Mapping specifies a form, it must have a validation form defined under the Struts Validator.",,unclassified, +If a Struts Action Form Mapping specifies a form, it must have a validation form defined under the Struts Validator.",,unclassified, CWE-11,EN-ASP.NET Misconfiguration: Creating Debug Binary (Type: Variant),"Debugging messages help attackers learn about the system and plan a form of attack. -ASP .NET applications can be configured to produce debug binaries. These binaries give detailed debugging messages and should not be used in production environments. Debug binaries are meant to be used in a development or testing environment and can pose a security risk if they are deployed to production.",,unclassified, +ASP .NET applications can be configured to produce debug binaries. These binaries give detailed debugging messages and should not be used in production environments. Debug binaries are meant to be used in a development or testing environment and can pose a security risk if they are deployed to production.",,unclassified, CWE-110,EN-Struts: Validator Without Form Field (Type: Variant),"Validation fields that do not appear in forms they are associated with indicate that the validation logic is out of date. -It is easy for developers to forget to update validation logic when they make changes to an ActionForm class. One indication that validation logic is not being properly maintained is inconsistencies between the action form and the validation form.",,unclassified, +It is easy for developers to forget to update validation logic when they make changes to an ActionForm class. One indication that validation logic is not being properly maintained is inconsistencies between the action form and the validation form.",,unclassified, CWE-111,EN-Direct Use of Unsafe JNI (Type: Base),"When a Java application uses the Java Native Interface (JNI) to call code written in another programming language, it can expose the application to weaknesses in that code, even if those weaknesses cannot occur in Java. Many safety features that programmers may take for granted simply do not apply for native code, so you must carefully review all such code for potential problems. The languages used to implement native code may be more susceptible to buffer overflows and other attacks. Native code is unprotected by the security features enforced by the runtime environment, such as strong typing and array bounds checking.",,unclassified,"Fortify Descriptions: http://vulncat.fortifysoftware.com -The Java(TM) Tutorial: The Java Native Interface: http://java.sun.com/docs/books/tutorial/native1.1/" +The Java(TM) Tutorial: The Java Native Interface: http://java.sun.com/docs/books/tutorial/native1.1/" CWE-112,EN-Missing XML Validation (Type: Base),"The software accepts XML from an untrusted source but does not validate the XML against the proper schema. -Most successful attacks begin with a violation of the programmer's assumptions. By accepting an XML document without validating it against a DTD or XML schema, the programmer leaves a door open for attackers to provide unexpected, unreasonable, or malicious input.",,unclassified, +Most successful attacks begin with a violation of the programmer's assumptions. By accepting an XML document without validating it against a DTD or XML schema, the programmer leaves a door open for attackers to provide unexpected, unreasonable, or malicious input.",,unclassified, CWE-113,EN-Improper Neutralization of CRLF Sequences in HTTP Headers (HTTP Response Splitting) (Type: Base),"The software receives data from an upstream component, but does not neutralize or incorrectly neutralizes CR and LF characters before the data is included in outgoing HTTP headers. Including unvalidated data in an HTTP header allows an attacker to specify the entirety of the HTTP response rendered by the browser. When an HTTP request contains unexpected CR (carriage return, also given by %0d or \r) and LF (line feed, also given by %0a or \n) characters the server may respond with an output stream that is interpreted as two different HTTP responses (instead of one). An attacker can control the second response and mount attacks such as cross-site scripting and cache poisoning attacks. HTTP response splitting weaknesses may be present when: Data enters a web application through an untrusted source, most frequently an HTTP request. The data is included in an HTTP response header sent to a web user without being validated for malicious characters.",,unclassified,"OWASP TOP 10: http://www.owasp.org/index.php/Top_10_2007 -24 Deadly Sins of Software Security: ""Sin 2: Web-Server Related Vulnerabilities (XSS, XSRF, and Response Splitting)."" Page 31" +24 Deadly Sins of Software Security: ""Sin 2: Web-Server Related Vulnerabilities (XSS, XSRF, and Response Splitting)."" Page 31" CWE-114,EN-Process Control (Type: Base),"Executing commands or loading libraries from an untrusted source or in an untrusted environment can cause an application to execute malicious commands (and payloads) on behalf of an attacker. -Process control vulnerabilities take two forms: 1. An attacker can change the command that the program executes: the attacker explicitly controls what the command is. 2. An attacker can change the environment in which the command executes: the attacker implicitly controls what the command means. Process control vulnerabilities of the first type occur when either data enters the application from an untrusted source and the data is used as part of a string representing a command that is executed by the application. By executing the command, the application gives an attacker a privilege or capability that the attacker would not otherwise have.",,unclassified, +Process control vulnerabilities take two forms: 1. An attacker can change the command that the program executes: the attacker explicitly controls what the command is. 2. An attacker can change the environment in which the command executes: the attacker implicitly controls what the command means. Process control vulnerabilities of the first type occur when either data enters the application from an untrusted source and the data is used as part of a string representing a command that is executed by the application. By executing the command, the application gives an attacker a privilege or capability that the attacker would not otherwise have.",,unclassified, CWE-115,EN-Misinterpretation of Input (Type: Base),"The software misinterprets an input, whether from an attacker or another product, in a security-relevant fashion. -Process control vulnerabilities take two forms: 1. An attacker can change the command that the program executes: the attacker explicitly controls what the command is. 2. An attacker can change the environment in which the command executes: the attacker implicitly controls what the command means. Process control vulnerabilities of the first type occur when either data enters the application from an untrusted source and the data is used as part of a string representing a command that is executed by the application. By executing the command, the application gives an attacker a privilege or capability that the attacker would not otherwise have.",,unclassified, +Process control vulnerabilities take two forms: 1. An attacker can change the command that the program executes: the attacker explicitly controls what the command is. 2. An attacker can change the environment in which the command executes: the attacker implicitly controls what the command means. Process control vulnerabilities of the first type occur when either data enters the application from an untrusted source and the data is used as part of a string representing a command that is executed by the application. By executing the command, the application gives an attacker a privilege or capability that the attacker would not otherwise have.",,unclassified, CWE-118,EN-Improper Access of Indexable Resource (Range Error) (Type: Class),"The software does not restrict or incorrectly restricts operations within the boundaries of a resource that is accessed using an index or pointer, such as memory or files. This can allow an attacker to forge log entries or inject malicious content into logs. Log forging vulnerabilities occur when: Data enters an application from an untrusted source. -The data is written to an application or system log file.",,unclassified, +The data is written to an application or system log file.",,unclassified, CWE-12,EN-ASP.NET Misconfiguration: Missing Custom Error Page (Type: Variant),"An ASP .NET application must enable custom error pages in order to prevent attackers from mining information from the framework's built-in responses. Certain languages allow direct addressing of memory locations and do not automatically ensure that these locations are valid for the memory buffer that is being referenced. This can cause read or write operations to be performed on memory locations that may be associated with other variables, data structures, or internal program data. As a result, an attacker may be able to execute arbitrary code, alter the intended control flow, read sensitive information, or cause the system to crash.",,unclassified,"19 Deadly Sins of Software Security -ASP.NET Misconfiguration: Missing Custom Error Handling: http://www.owasp.org/index.php/ASP.NET_Misconfiguration:_Missing_Custom_Error_Handling" +ASP.NET Misconfiguration: Missing Custom Error Handling: http://www.owasp.org/index.php/ASP.NET_Misconfiguration:_Missing_Custom_Error_Handling" CWE-125,EN-Out-of-bounds Read (Type: Base),"The software reads data past the end, or before the beginning, of the intended buffer. -This typically occurs when the pointer or its index is incremented or decremented to a position beyond the bounds of the buffer or when pointer arithmetic results in a position outside of the valid memory location to name a few. This may result in corruption of sensitive information, a crash, or code execution among other things.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" +This typically occurs when the pointer or its index is incremented or decremented to a position beyond the bounds of the buffer or when pointer arithmetic results in a position outside of the valid memory location to name a few. This may result in corruption of sensitive information, a crash, or code execution among other things.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" CWE-126,EN-Buffer Over-read (Type: Variant),"The software reads from a buffer using buffer access mechanisms such as indexes or pointers that reference memory locations after the targeted buffer. -This typically occurs when the pointer or its index is incremented to a position beyond the bounds of the buffer or when pointer arithmetic results in a position outside of the valid memory location to name a few. This may result in exposure of sensitive information or possibly a crash.",,unclassified, +This typically occurs when the pointer or its index is incremented to a position beyond the bounds of the buffer or when pointer arithmetic results in a position outside of the valid memory location to name a few. This may result in exposure of sensitive information or possibly a crash.",,unclassified, CWE-127,EN-Buffer Under-read (Type: Variant),"The software reads from a buffer using buffer access mechanisms such as indexes or pointers that reference memory locations prior to the targeted buffer. -This typically occurs when the pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. This may result in exposure of sensitive information or possibly a crash.",,unclassified, +This typically occurs when the pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. This may result in exposure of sensitive information or possibly a crash.",,unclassified, CWE-13,EN-ASP.NET Misconfiguration: Password in Configuration File (Type: Variant),"Storing a plaintext password in a configuration file allows anyone who can read the file access to the password-protected resource making them an easy target for attackers. This typically occurs when the pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. This may result in exposure of sensitive information or possibly a crash.",,unclassified,"How To: Encrypt Configuration Sections in ASP.NET 2.0 Using DPAPI: http://msdn.microsoft.com/en-us/library/ms998280.aspx How To: Encrypt Configuration Sections in ASP.NET 2.0 Using RSA: http://msdn.microsoft.com/en-us/library/ms998283.aspx -.NET Framework Developer's Guide - Securing Connection Strings: http://msdn.microsoft.com/en-us/library/89211k9b(VS.80).aspx" +.NET Framework Developer's Guide - Securing Connection Strings: http://msdn.microsoft.com/en-us/library/89211k9b(VS.80).aspx" CWE-130,EN-Improper Handling of Length Parameter Inconsistency (Type: Variant),"The software parses a formatted message or structure, but it does not handle or incorrectly handles a length field that is inconsistent with the actual length of the associated data. -If an attacker can manipulate the length parameter associated with an input such that it is inconsistent with the actual length of the input, this can be leveraged to cause the target application to behave in unexpected, and possibly, malicious ways. One of the possible motives for doing so is to pass in arbitrarily large input to the application. Another possible motivation is the modification of application state by including invalid data for subsequent properties of the application. Such weaknesses commonly lead to attacks such as buffer overflows and execution of arbitrary code.",,unclassified, +If an attacker can manipulate the length parameter associated with an input such that it is inconsistent with the actual length of the input, this can be leveraged to cause the target application to behave in unexpected, and possibly, malicious ways. One of the possible motives for doing so is to pass in arbitrarily large input to the application. Another possible motivation is the modification of application state by including invalid data for subsequent properties of the application. Such weaknesses commonly lead to attacks such as buffer overflows and execution of arbitrary code.",,unclassified, CWE-132,EN-DEPRECATED (Duplicate): Miscalculated Null Termination (Type: Base),"This entry has been deprecated because it was a duplicate of CWE-170. All content has been transferred to CWE-170. -If an attacker can manipulate the length parameter associated with an input such that it is inconsistent with the actual length of the input, this can be leveraged to cause the target application to behave in unexpected, and possibly, malicious ways. One of the possible motives for doing so is to pass in arbitrarily large input to the application. Another possible motivation is the modification of application state by including invalid data for subsequent properties of the application. Such weaknesses commonly lead to attacks such as buffer overflows and execution of arbitrary code.",,unclassified, +If an attacker can manipulate the length parameter associated with an input such that it is inconsistent with the actual length of the input, this can be leveraged to cause the target application to behave in unexpected, and possibly, malicious ways. One of the possible motives for doing so is to pass in arbitrarily large input to the application. Another possible motivation is the modification of application state by including invalid data for subsequent properties of the application. Such weaknesses commonly lead to attacks such as buffer overflows and execution of arbitrary code.",,unclassified, CWE-135,EN-Incorrect Calculation of Multi-Byte String Length (Type: Base),"The software does not correctly calculate the length of strings that can contain wide or multi-byte characters. -If an attacker can manipulate the length parameter associated with an input such that it is inconsistent with the actual length of the input, this can be leveraged to cause the target application to behave in unexpected, and possibly, malicious ways. One of the possible motives for doing so is to pass in arbitrarily large input to the application. Another possible motivation is the modification of application state by including invalid data for subsequent properties of the application. Such weaknesses commonly lead to attacks such as buffer overflows and execution of arbitrary code.",,unclassified,"Writing Secure Code: Chapter 5, ""Unicode and ANSI Buffer Size Mismatches"" Page 153" +If an attacker can manipulate the length parameter associated with an input such that it is inconsistent with the actual length of the input, this can be leveraged to cause the target application to behave in unexpected, and possibly, malicious ways. One of the possible motives for doing so is to pass in arbitrarily large input to the application. Another possible motivation is the modification of application state by including invalid data for subsequent properties of the application. Such weaknesses commonly lead to attacks such as buffer overflows and execution of arbitrary code.",,unclassified,"Writing Secure Code: Chapter 5, ""Unicode and ANSI Buffer Size Mismatches"" Page 153" CWE-138,EN-Improper Neutralization of Special Elements (Type: Class),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as control elements or syntactic markers when they are sent to a downstream component. -Most languages and protocols have their own special elements such as characters and reserved words. These special elements can carry control implications. If software does not prevent external control or influence over the inclusion of such special elements, the control flow of the program may be altered from what was intended. For example, both Unix and Windows interpret the symbol < (""less than"") as meaning ""read input from a file"".",,unclassified, +Most languages and protocols have their own special elements such as characters and reserved words. These special elements can carry control implications. If software does not prevent external control or influence over the inclusion of such special elements, the control flow of the program may be altered from what was intended. For example, both Unix and Windows interpret the symbol < (""less than"") as meaning ""read input from a file"".",,unclassified, CWE-14,EN-Compiler Removal of Code to Clear Buffers (Type: Base),"Sensitive memory is cleared according to the source code, but compiler optimizations leave the memory untouched when it is not read from again, aka ""dead store removal."" This compiler optimization error occurs when: 1. Secret data are stored in memory. @@ -788,337 +788,337 @@ This compiler optimization error occurs when: 3. The source code is compiled using an optimizing compiler, which identifies and removes the function that overwrites the contents as a dead store because the memory is not used subsequently.",,unclassified,"Writing Secure Code: Chapter 9, ""A Compiler Optimization Caveat"" Page 322 When scrubbing secrets in memory doesn't work: http://cert.uni-stuttgart.de/archive/bugtraq/2002/11/msg00046.html Some Bad News and Some Good News: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncode/html/secure10102002.asp -GNU GCC: Optimizer Removes Code Necessary for Security: http://www.derkeiler.com/Mailing-Lists/securityfocus/bugtraq/2002-11/0257.html" +GNU GCC: Optimizer Removes Code Necessary for Security: http://www.derkeiler.com/Mailing-Lists/securityfocus/bugtraq/2002-11/0257.html" CWE-140,EN-Improper Neutralization of Delimiters (Type: Base),"The software does not neutralize or incorrectly neutralizes delimiters. This compiler optimization error occurs when: 1. Secret data are stored in memory. 2. The secret data are scrubbed from memory by overwriting its contents. -3. The source code is compiled using an optimizing compiler, which identifies and removes the function that overwrites the contents as a dead store because the memory is not used subsequently.",,unclassified, +3. The source code is compiled using an optimizing compiler, which identifies and removes the function that overwrites the contents as a dead store because the memory is not used subsequently.",,unclassified, CWE-141,EN-Improper Neutralization of Parameter/Argument Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as parameter or argument delimiters when they are sent to a downstream component. As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408. -The Art of Software Security Assessment: Chapter 10, ""IFS"", Page 604." +The Art of Software Security Assessment: Chapter 10, ""IFS"", Page 604." CWE-142,EN-Improper Neutralization of Value Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as value delimiters when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." CWE-143,EN-Improper Neutralization of Record Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as record delimiters when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." CWE-144,EN-Improper Neutralization of Line Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as line delimiters when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." CWE-145,EN-Improper Neutralization of Section Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as section delimiters when they are sent to a downstream component. As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions. -One example of a section delimiter is the boundary string in a multipart MIME message. In many cases, doubled line delimiters can serve as a section delimiter.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." +One example of a section delimiter is the boundary string in a multipart MIME message. In many cases, doubled line delimiters can serve as a section delimiter.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." CWE-146,EN-Improper Neutralization of Expression/Command Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as expression or command delimiters when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Embedded Delimiters"", Page 408." CWE-147,EN-Improper Neutralization of Input Terminators (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as input terminators when they are sent to a downstream component. -For example, a ""."" in SMTP signifies the end of mail message data, whereas a null character can be used for the end of a string.",,unclassified, +For example, a ""."" in SMTP signifies the end of mail message data, whereas a null character can be used for the end of a string.",,unclassified, CWE-148,EN-Improper Neutralization of Input Leaders (Type: Variant),"The application does not properly handle when a leading character or sequence (""leader"") is missing or malformed, or if multiple leaders are used when only one should be allowed. -For example, a ""."" in SMTP signifies the end of mail message data, whereas a null character can be used for the end of a string.",,unclassified, +For example, a ""."" in SMTP signifies the end of mail message data, whereas a null character can be used for the end of a string.",,unclassified, CWE-149,EN-Improper Neutralization of Quoting Syntax (Type: Variant),"Quotes injected into an application can be used to compromise a system. As data are parsed, an injected/absent/duplicate/malformed use of quotes may cause the process to take unexpected actions. -For example, a ""."" in SMTP signifies the end of mail message data, whereas a null character can be used for the end of a string.",,unclassified, +For example, a ""."" in SMTP signifies the end of mail message data, whereas a null character can be used for the end of a string.",,unclassified, CWE-15,EN-External Control of System or Configuration Setting (Type: Base),"One or more system settings or configuration elements can be externally controlled by a user. -Allowing external control of system settings can disrupt service or cause an application to behave in unexpected, and potentially malicious ways.",,unclassified, +Allowing external control of system settings can disrupt service or cause an application to behave in unexpected, and potentially malicious ways.",,unclassified, CWE-150,"EN-Improper Neutralization of Escape, Meta, or Control Sequences (Type: Variant)","The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as escape, meta, or control character sequences when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, CWE-151,EN-Improper Neutralization of Comment Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as comment delimiters when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, CWE-152,EN-Improper Neutralization of Macro Symbols (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as macro symbols when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, CWE-153,EN-Improper Neutralization of Substitution Characters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as substitution characters when they are sent to a downstream component. -As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, +As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.",,unclassified, CWE-154,EN-Improper Neutralization of Variable Name Delimiters (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as variable name delimiters when they are sent to a downstream component. -As data is parsed, an injected delimiter may cause the process to take unexpected actions that result in an attack. Example: ""$"" for an environment variable.",,unclassified, +As data is parsed, an injected delimiter may cause the process to take unexpected actions that result in an attack. Example: ""$"" for an environment variable.",,unclassified, CWE-155,EN-Improper Neutralization of Wildcards or Matching Symbols (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as wildcards or matching symbols when they are sent to a downstream component. -As data is parsed, an injected element may cause the process to take unexpected actions.",,unclassified, +As data is parsed, an injected element may cause the process to take unexpected actions.",,unclassified, CWE-156,EN-Improper Neutralization of Whitespace (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as whitespace when they are sent to a downstream component. -This can include space, tab, etc.",,unclassified, +This can include space, tab, etc.",,unclassified, CWE-157,EN-Failure to Sanitize Paired Delimiters (Type: Variant),"The software does not properly handle the characters that are used to mark the beginning and ending of a group of entities, such as parentheses, brackets, and braces. -This can include space, tab, etc.",,unclassified, +This can include space, tab, etc.",,unclassified, CWE-158,EN-Improper Neutralization of Null Byte or NUL Character (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes NUL characters or null bytes when they are sent to a downstream component. -As data is parsed, an injected NUL character or null byte may cause the software to believe the input is terminated earlier than it actually is, or otherwise cause the input to be misinterpreted. This could then be used to inject potentially dangerous input that occurs after the null byte or otherwise bypass validation routines and other protection mechanisms.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""NUL Character Injection"", Page 411." +As data is parsed, an injected NUL character or null byte may cause the software to believe the input is terminated earlier than it actually is, or otherwise cause the input to be misinterpreted. This could then be used to inject potentially dangerous input that occurs after the null byte or otherwise bypass validation routines and other protection mechanisms.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""NUL Character Injection"", Page 411." CWE-159,EN-Failure to Sanitize Special Element (Type: Class),"Weaknesses in this attack-focused category do not properly filter and interpret special elements in user-controlled input which could cause adverse effect on the software behavior and integrity. -As data is parsed, an injected NUL character or null byte may cause the software to believe the input is terminated earlier than it actually is, or otherwise cause the input to be misinterpreted. This could then be used to inject potentially dangerous input that occurs after the null byte or otherwise bypass validation routines and other protection mechanisms.",,unclassified, +As data is parsed, an injected NUL character or null byte may cause the software to believe the input is terminated earlier than it actually is, or otherwise cause the input to be misinterpreted. This could then be used to inject potentially dangerous input that occurs after the null byte or otherwise bypass validation routines and other protection mechanisms.",,unclassified, CWE-160,EN-Improper Neutralization of Leading Special Elements (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes leading special elements that could be interpreted in unexpected ways when they are sent to a downstream component. -As data is parsed, improperly handled leading special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled leading special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-161,EN-Improper Neutralization of Multiple Leading Special Elements (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes multiple leading special elements that could be interpreted in unexpected ways when they are sent to a downstream component. -As data is parsed, improperly handled multiple leading special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled multiple leading special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-162,EN-Improper Neutralization of Trailing Special Elements (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes trailing special elements that could be interpreted in unexpected ways when they are sent to a downstream component. -As data is parsed, improperly handled trailing special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled trailing special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-163,EN-Improper Neutralization of Multiple Trailing Special Elements (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes multiple trailing special elements that could be interpreted in unexpected ways when they are sent to a downstream component. -As data is parsed, improperly handled multiple trailing special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled multiple trailing special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-164,EN-Improper Neutralization of Internal Special Elements (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes internal special elements that could be interpreted in unexpected ways when they are sent to a downstream component. -As data is parsed, improperly handled internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-165,EN-Improper Neutralization of Multiple Internal Special Elements (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes multiple internal special elements that could be interpreted in unexpected ways when they are sent to a downstream component. -As data is parsed, improperly handled multiple internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled multiple internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-166,EN-Improper Handling of Missing Special Element (Type: Base),"The software receives input from an upstream component, but it does not handle or incorrectly handles when an expected special element is missing. -As data is parsed, improperly handled multiple internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled multiple internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-167,EN-Improper Handling of Additional Special Element (Type: Base),"The software receives input from an upstream component, but it does not handle or incorrectly handles when an additional unexpected special element is missing. -As data is parsed, improperly handled multiple internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, +As data is parsed, improperly handled multiple internal special elements may cause the process to take unexpected actions that result in an attack.",,unclassified, CWE-168,EN-Improper Handling of Inconsistent Special Elements (Type: Base),"The software does not handle when an inconsistency exists between two or more special characters or reserved words. -An example of this problem would be if paired characters appear in the wrong order, or if the special characters are not properly nested.",,unclassified, +An example of this problem would be if paired characters appear in the wrong order, or if the special characters are not properly nested.",,unclassified, CWE-172,EN-Encoding Error (Type: Class),"The software does not properly encode or decode the data, resulting in unexpected values. -Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, +Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, CWE-173,EN-Improper Handling of Alternate Encoding (Type: Variant),"The software does not properly handle when an input uses an alternate encoding that is valid for the control sphere to which the input is being sent. -Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, +Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, CWE-174,EN-Double Decoding of the Same Data (Type: Variant),"The software decodes the same input twice, which can limit the effectiveness of any protection mechanism that occurs in between the decoding operations. -Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, +Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, CWE-175,EN-Improper Handling of Mixed Encoding (Type: Variant),"The software does not properly handle when the same input uses several different (mixed) encodings. -Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, +Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, CWE-176,EN-Improper Handling of Unicode Encoding (Type: Variant),"The software does not properly handle when an input contains Unicode encoding. -Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Character Sets and Unicode"", Page 446." +Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Character Sets and Unicode"", Page 446." CWE-177,EN-Improper Handling of URL Encoding (Hex Encoding) (Type: Variant),"The software does not properly handle when all or part of an input has been URL encoded. -Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, +Null termination errors frequently occur in two different ways. An off-by-one error could cause a null to be written out of bounds, leading to an overflow. Or, a program could use a strncpy() function call incorrectly, which prevents a null terminator from being added at all. Other scenarios are possible.",,unclassified, CWE-178,EN-Improper Handling of Case Sensitivity (Type: Base),"The software does not properly account for differences in case sensitivity when accessing or determining the properties of a resource, leading to inconsistent results. Improperly handled case sensitive data can lead to several possible consequences, including: case-insensitive passwords reducing the size of the key space, making brute force attacks easier bypassing filters or access controls using alternate names -multiple interpretation errors using alternate names.",,unclassified, +multiple interpretation errors using alternate names.",,unclassified, CWE-179,EN-Incorrect Behavior Order: Early Validation (Type: Base),"The software validates input before applying protection mechanisms that modify the input, which could allow an attacker to bypass the validation via dangerous inputs that only arise after the modification. -Software needs to validate data at the proper time, after data has been canonicalized and cleansed. Early validation is susceptible to various manipulations that result in dangerous inputs that are produced by canonicalization and cleansing.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Escaping Metacharacters"", Page 439." +Software needs to validate data at the proper time, after data has been canonicalized and cleansed. Early validation is susceptible to various manipulations that result in dangerous inputs that are produced by canonicalization and cleansing.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Escaping Metacharacters"", Page 439." CWE-180,EN-Incorrect Behavior Order: Validate Before Canonicalize (Type: Base),"The software validates input before it is canonicalized, which prevents the software from detecting data that becomes invalid after the canonicalization step. -This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified, +This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified, CWE-181,EN-Incorrect Behavior Order: Validate Before Filter (Type: Base),"The software validates data before it has been filtered, which prevents the software from detecting data that becomes invalid after the filtering step. -This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified, +This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified, CWE-182,EN-Collapse of Data into Unsafe Value (Type: Base),"The software filters data in a way that causes it to be reduced or ""collapsed"" into an unsafe value that violates an expected security property. -This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Character Stripping Vulnerabilities"", Page 437." +This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Character Stripping Vulnerabilities"", Page 437." CWE-183,EN-Permissive Whitelist (Type: Base),"An application uses a ""whitelist"" of acceptable values, but the whitelist includes at least one unsafe value, leading to resultant weaknesses. -This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Eliminating Metacharacters"", Page 435." +This can be used by an attacker to bypass the validation and launch attacks that expose weaknesses that would otherwise be prevented, such as injection.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Eliminating Metacharacters"", Page 435." CWE-184,EN-Incomplete Blacklist (Type: Base),"An application uses a ""blacklist"" of prohibited values, but the blacklist is incomplete. If an incomplete blacklist is used as a security mechanism, then the software may allow unintended values to pass into the application logic.",,unclassified,"Exploiting Software: How to Break Code Blacklist defenses as a breeding ground for vulnerability variants: http://seclists.org/fulldisclosure/2006/Feb/0040.html -The Art of Software Security Assessment: Chapter 8, ""Eliminating Metacharacters"", Page 435." +The Art of Software Security Assessment: Chapter 8, ""Eliminating Metacharacters"", Page 435." CWE-185,EN-Incorrect Regular Expression (Type: Class),"The software specifies a regular expression in a way that causes data to be improperly matched or compared. -When the regular expression is used in protection mechanisms such as filtering or validation, this may allow an attacker to bypass the intended restrictions on the incoming data.",,unclassified,"Writing Secure Code: Chapter 10, ""Using Regular Expressions for Checking Input"" Page 350" +When the regular expression is used in protection mechanisms such as filtering or validation, this may allow an attacker to bypass the intended restrictions on the incoming data.",,unclassified,"Writing Secure Code: Chapter 10, ""Using Regular Expressions for Checking Input"" Page 350" CWE-186,EN-Overly Restrictive Regular Expression (Type: Base),"A regular expression is overly restrictive, which prevents dangerous values from being detected. -When the regular expression is used in protection mechanisms such as filtering or validation, this may allow an attacker to bypass the intended restrictions on the incoming data.",,unclassified, +When the regular expression is used in protection mechanisms such as filtering or validation, this may allow an attacker to bypass the intended restrictions on the incoming data.",,unclassified, CWE-187,EN-Partial Comparison (Type: Base),"The software performs a comparison that only examines a portion of a factor before determining whether there is a match, such as a substring, leading to resultant weaknesses. -For example, an attacker might succeed in authentication by providing a small password that matches the associated portion of the larger, correct password.",,unclassified, +For example, an attacker might succeed in authentication by providing a small password that matches the associated portion of the larger, correct password.",,unclassified, CWE-191,EN-Integer Underflow (Wrap or Wraparound) (Type: Base),"The product subtracts one value from another, such that the result is less than the minimum allowable integer value, which produces a value that is not equal to the correct result. -This can happen in signed and unsigned cases.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 7: Integer Overflows."" Page 119" +This can happen in signed and unsigned cases.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 7: Integer Overflows."" Page 119" CWE-193,EN-Off-by-one Error (Type: Base),"A product calculates or uses an incorrect maximum or minimum value that is 1 more, or 1 less, than the correct value. This can happen in signed and unsigned cases.",,unclassified,"Third Generation Exploits: http://www.blackhat.com/presentations/bh-europe-01/halvar-flake/bh-europe-01-halvarflake.ppt Off-by-one errors: a brief explanation: http://marc.theaimsgroup.com/?l=secprog&m=108379742110553&w=2 The Frame Pointer Overwrite: http://kaizo.org/mirrors/phrack/phrack55/P55-08 Exploiting Software: How to Break Code (The buffer overflow chapter) 24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89 -The Art of Software Security Assessment: Chapter 5, ""Off-by-One Errors"", Page 180." +The Art of Software Security Assessment: Chapter 5, ""Off-by-One Errors"", Page 180." CWE-195,EN-Signed to Unsigned Conversion Error (Type: Variant),"A signed-to-unsigned conversion error takes place when a signed primitive is used as an unsigned value, usually as a size variable. -It is dangerous to rely on implicit casts between signed and unsigned numbers because the result can take on an unexpected value and violate assumptions made by the program.",,unclassified,"The Art of Software Security Assessment: Chapter 6, ""Type Conversions"", Page 223." +It is dangerous to rely on implicit casts between signed and unsigned numbers because the result can take on an unexpected value and violate assumptions made by the program.",,unclassified,"The Art of Software Security Assessment: Chapter 6, ""Type Conversions"", Page 223." CWE-198,EN-Use of Incorrect Byte Ordering (Type: Base),"The software receives input from an upstream component, but it does not account for byte ordering (e.g. big-endian and little-endian) when processing the input, causing an incorrect number or value to be used. -When a primitive is cast to a smaller primitive, the high order bits of the large value are lost in the conversion, potentially resulting in an unexpected value that is not equal to the original value. This value may be required as an index into a buffer, a loop iterator, or simply necessary state data. In any case, the value cannot be trusted and the system will be in an undefined state. While this method may be employed viably to isolate the low bits of a value, this usage is rare, and truncation usually implies that an implementation error has occurred.",,unclassified, +When a primitive is cast to a smaller primitive, the high order bits of the large value are lost in the conversion, potentially resulting in an unexpected value that is not equal to the original value. This value may be required as an index into a buffer, a loop iterator, or simply necessary state data. In any case, the value cannot be trusted and the system will be in an undefined state. While this method may be employed viably to isolate the low bits of a value, this usage is rare, and truncation usually implies that an implementation error has occurred.",,unclassified, CWE-201,EN-Information Exposure Through Sent Data (Type: Variant),"The accidental exposure of sensitive information through sent data refers to the transmission of data which are either sensitive in and of itself or useful in the further exploitation of the system through standard data channels. The information either is regarded as sensitive within the product's own functionality, such as a private message; or provides information about the product or its environment that could be useful in an attack but is normally not available to the attacker, such as the installation path of a product that is remotely accessible. -Many information exposures are resultant (e.g. PHP script error revealing the full path of the program), but they can also be primary (e.g. timing discrepancies in cryptography). There are many different types of problems that involve information exposures. Their severity can range widely depending on the type of information that is revealed.",,unclassified, +Many information exposures are resultant (e.g. PHP script error revealing the full path of the program), but they can also be primary (e.g. timing discrepancies in cryptography). There are many different types of problems that involve information exposures. Their severity can range widely depending on the type of information that is revealed.",,unclassified, CWE-203,EN-Information Exposure Through Discrepancy (Type: Class),"The product behaves differently or sends different responses in a way that exposes security-relevant information about the state of the product, such as whether a particular operation was successful or not. -In situations where data should not be tied to individual users, but a large number of users should be able to make queries that ""scrub"" the identity of users, it may be possible to get information about a user -- e.g., by specifying search terms that are known to be unique to that user.",,unclassified, +In situations where data should not be tied to individual users, but a large number of users should be able to make queries that ""scrub"" the identity of users, it may be possible to get information about a user -- e.g., by specifying search terms that are known to be unique to that user.",,unclassified, CWE-204,EN-Response Discrepancy Information Exposure (Type: Base),"The software provides different responses to incoming requests in a way that allows an actor to determine system state information that is outside of that actor's control sphere. -This issue frequently occurs during authentication, where a difference in failed-login messages could allow an attacker to determine if the username is valid or not. These exposures can be inadvertent (bug) or intentional (design).",,unclassified,"24 Deadly Sins of Software Security: ""Sin 12: Information Leakage."" Page 191" +This issue frequently occurs during authentication, where a difference in failed-login messages could allow an attacker to determine if the username is valid or not. These exposures can be inadvertent (bug) or intentional (design).",,unclassified,"24 Deadly Sins of Software Security: ""Sin 12: Information Leakage."" Page 191" CWE-205,EN-Information Exposure Through Behavioral Discrepancy (Type: Base),"The product's actions indicate important differences based on (1) the internal state of the product or (2) differences from other products in the same class. -For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, +For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, CWE-206,EN-Information Exposure of Internal State Through Behavioral Inconsistency (Type: Variant),"Two separate operations in a product cause the product to behave differently in a way that is observable to an attacker and reveals security-relevant information about the internal state of the product, such as whether a particular operation was successful or not. -For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, +For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, CWE-207,EN-Information Exposure Through an External Behavioral Inconsistency (Type: Variant),"The product behaves differently than other products like it, in a way that is observable to an attacker and exposes security-relevant information about which product is being used. -For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, +For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, CWE-208,EN-Information Exposure Through Timing Discrepancy (Type: Base),"Two separate operations in a product require different amounts of time to complete, in a way that is observable to an actor and reveals security-relevant information about the state of the product, such as whether a particular operation was successful or not. -For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, +For example, attacks such as OS fingerprinting rely heavily on both behavioral and response discrepancies.",,unclassified, CWE-210,EN-Information Exposure Through Self-generated Error Message (Type: Base),"The software identifies an error condition and creates its own diagnostic or error messages that contain sensitive information. The sensitive information may be valuable information on its own (such as a password), or it may be useful for launching other, more deadly attacks. If an attack fails, an attacker may use error information provided by the server to launch another more focused attack. For example, an attempt to exploit a path traversal weakness (CWE-22) might yield the full pathname of the installed application. In turn, this could be used to select the proper number of "".."" sequences to navigate to the targeted file. An attack using SQL injection (CWE-89) might not initially succeed, but an error message could reveal the malformed query, which would expose query logic and possibly even passwords or other sensitive information used within the query.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 12: Information Leakage."" Page 191 -The Art of Software Security Assessment: Chapter 3, ""Overly Verbose Error Messages"", Page 75." +The Art of Software Security Assessment: Chapter 3, ""Overly Verbose Error Messages"", Page 75." CWE-211,EN-Information Exposure Through Externally-generated Error Message (Type: Base),"The software performs an operation that triggers an external diagnostic or error message that is not directly generated by the software, such as an error generated by the programming language interpreter that the software uses. The error can contain sensitive system information. -The sensitive information may be valuable information on its own (such as a password), or it may be useful for launching other, more deadly attacks. If an attack fails, an attacker may use error information provided by the server to launch another more focused attack. For example, an attempt to exploit a path traversal weakness (CWE-22) might yield the full pathname of the installed application. In turn, this could be used to select the proper number of "".."" sequences to navigate to the targeted file. An attack using SQL injection (CWE-89) might not initially succeed, but an error message could reveal the malformed query, which would expose query logic and possibly even passwords or other sensitive information used within the query.",,unclassified, +The sensitive information may be valuable information on its own (such as a password), or it may be useful for launching other, more deadly attacks. If an attack fails, an attacker may use error information provided by the server to launch another more focused attack. For example, an attempt to exploit a path traversal weakness (CWE-22) might yield the full pathname of the installed application. In turn, this could be used to select the proper number of "".."" sequences to navigate to the targeted file. An attack using SQL injection (CWE-89) might not initially succeed, but an error message could reveal the malformed query, which would expose query logic and possibly even passwords or other sensitive information used within the query.",,unclassified, CWE-212,EN-Improper Cross-boundary Removal of Sensitive Data (Type: Base),"The software uses a resource that contains sensitive data, but it does not properly remove that data before it stores, transfers, or shares the resource with actors in another control sphere. Resources that may contain sensitive data include documents, packets, messages, databases, etc. While this data may be useful to an individual user or small set of users who share the resource, it may need to be removed before the resource can be shared outside of the trusted group. The process of removal is sometimes called cleansing or scrubbing. -For example, software that is used for editing documents might not remove sensitive data such as reviewer comments or the local pathname where the document is stored. Or, a proxy might not remove an internal IP address from headers before making an outgoing request to an Internet site.",,unclassified, +For example, software that is used for editing documents might not remove sensitive data such as reviewer comments or the local pathname where the document is stored. Or, a proxy might not remove an internal IP address from headers before making an outgoing request to an Internet site.",,unclassified, CWE-213,EN-Intentional Information Exposure (Type: Base),"A product's design or configuration explicitly requires the publication of information that could be regarded as sensitive by an administrator. Resources that may contain sensitive data include documents, packets, messages, databases, etc. While this data may be useful to an individual user or small set of users who share the resource, it may need to be removed before the resource can be shared outside of the trusted group. The process of removal is sometimes called cleansing or scrubbing. -For example, software that is used for editing documents might not remove sensitive data such as reviewer comments or the local pathname where the document is stored. Or, a proxy might not remove an internal IP address from headers before making an outgoing request to an Internet site.",,unclassified, +For example, software that is used for editing documents might not remove sensitive data such as reviewer comments or the local pathname where the document is stored. Or, a proxy might not remove an internal IP address from headers before making an outgoing request to an Internet site.",,unclassified, CWE-214,EN-Information Exposure Through Process Environment (Type: Variant),"A process is invoked with sensitive arguments, environment variables, or other elements that can be seen by other processes on the operating system. -Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, +Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, CWE-215,EN-Information Exposure Through Debug Information (Type: Variant),"The application contains debugging code that can expose sensitive information to untrusted parties. -Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, +Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, CWE-216,EN-Containment Errors (Container Errors) (Type: Class),"This tries to cover various problems in which improper data are included within a ""container."" -Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, +Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, CWE-217,EN-DEPRECATED: Failure to Protect Stored Data from Modification (Type: Base),"This weakness has been deprecated because it incorporated and confused multiple weaknesses. The issues formerly covered in this weakness can be found at CWE-766 and CWE-767. -Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, +Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, CWE-218,EN-DEPRECATED (Duplicate): Failure to provide confidentiality for stored data (Type: Base),"This weakness has been deprecated because it was a duplicate of CWE-493. All content has been transferred to CWE-493. -Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, +Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, CWE-219,EN-Sensitive Data Under Web Root (Type: Variant),"The application stores sensitive data under the web document root with insufficient access control, which might make it accessible to untrusted parties. -Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, +Many operating systems allow a user to list information about processes that are owned by other users. This information could include command line arguments or environment variable settings. When this data contains sensitive information such as credentials, it might allow other users to launch an attack against the software or related resources.",,unclassified, CWE-220,EN-Sensitive Data Under FTP Root (Type: Variant),"The application stores sensitive data under the FTP document root with insufficient access control, which might make it accessible to untrusted parties. Many file operations are intended to take place within a restricted directory. By using special elements such as "".."" and ""/"" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the ""../"" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as ""/usr/local/bin"", which may also be useful in accessing unexpected files. This is referred to as absolute path traversal. -In many programming languages, the injection of a null byte (the 0 or NUL) may allow an attacker to truncate a generated filename to widen the scope of attack. For example, the software may add "".txt"" to any pathname, thus limiting the attacker to text files, but a null injection may effectively remove this restriction.",,unclassified, +In many programming languages, the injection of a null byte (the 0 or NUL) may allow an attacker to truncate a generated filename to widen the scope of attack. For example, the software may add "".txt"" to any pathname, thus limiting the attacker to text files, but a null injection may effectively remove this restriction.",,unclassified, CWE-221,EN-Information Loss or Omission (Type: Class),"The software does not record, or improperly records, security-relevant information that leads to an incorrect decision or hampers later analysis. -This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified, +This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified, CWE-222,EN-Truncation of Security-relevant Information (Type: Base),"The application truncates the display, recording, or processing of security-relevant information in a way that can obscure the source or nature of an attack. -This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified, +This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified, CWE-223,EN-Omission of Security-relevant Information (Type: Base),"The application does not record or display information that would be important for identifying the source or nature of an attack, or determining if an action is safe. -This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified,"The Art of Software Security Assessment: Chapter 2, ""Accountability"", Page 40." +This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified,"The Art of Software Security Assessment: Chapter 2, ""Accountability"", Page 40." CWE-224,EN-Obscured Security-relevant Information by Alternate Name (Type: Base),"The software records security-relevant information according to an alternate name of the affected entity, instead of the canonical name. -This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified,Writing Secure Code +This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified,Writing Secure Code CWE-225,EN-DEPRECATED (Duplicate): General Information Management Problems (Type: Base),"This weakness can be found at CWE-199. -This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified, +This can be resultant, e.g. a buffer overflow might trigger a crash before the product can log the event.",,unclassified, CWE-226,EN-Sensitive Information Uncleared Before Release (Type: Base),"The software does not fully clear previously used information in a data structure, file, or other resource, before making that resource available to a party in another control sphere. -This typically results from new data that is not as long as the old data, which leaves portions of the old data still available. Equivalent errors can occur in other situations where the length of data is variable but the associated data structure is not. If memory is not cleared after use, it may allow unintended actors to read the data when the memory is reallocated.",,unclassified, +This typically results from new data that is not as long as the old data, which leaves portions of the old data still available. Equivalent errors can occur in other situations where the length of data is variable but the associated data structure is not. If memory is not cleared after use, it may allow unintended actors to read the data when the memory is reallocated.",,unclassified, CWE-227,EN-Improper Fulfillment of API Contract (API Abuse) (Type: Class),"The software uses an API in a manner contrary to its intended use. -An API is a contract between a caller and a callee. The most common forms of API misuse occurs when the caller does not honor its end of this contract. For example, if a program does not call chdir() after calling chroot(), it violates the contract that specifies how to change the active root directory in a secure fashion. Another good example of library abuse is expecting the callee to return trustworthy DNS information to the caller. In this case, the caller misuses the callee API by making certain assumptions about its behavior (that the return value can be used for authentication purposes). One can also violate the caller-callee contract from the other side. For example, if a coder subclasses SecureRandom and returns a non-random value, the contract is violated.",,unclassified, +An API is a contract between a caller and a callee. The most common forms of API misuse occurs when the caller does not honor its end of this contract. For example, if a program does not call chdir() after calling chroot(), it violates the contract that specifies how to change the active root directory in a secure fashion. Another good example of library abuse is expecting the callee to return trustworthy DNS information to the caller. In this case, the caller misuses the callee API by making certain assumptions about its behavior (that the return value can be used for authentication purposes). One can also violate the caller-callee contract from the other side. For example, if a coder subclasses SecureRandom and returns a non-random value, the contract is violated.",,unclassified, CWE-228,EN-Improper Handling of Syntactically Invalid Structure (Type: Class),"The product does not handle or incorrectly handles input that is not syntactically well-formed with respect to the associated specification. -An API is a contract between a caller and a callee. The most common forms of API misuse occurs when the caller does not honor its end of this contract. For example, if a program does not call chdir() after calling chroot(), it violates the contract that specifies how to change the active root directory in a secure fashion. Another good example of library abuse is expecting the callee to return trustworthy DNS information to the caller. In this case, the caller misuses the callee API by making certain assumptions about its behavior (that the return value can be used for authentication purposes). One can also violate the caller-callee contract from the other side. For example, if a coder subclasses SecureRandom and returns a non-random value, the contract is violated.",,unclassified, +An API is a contract between a caller and a callee. The most common forms of API misuse occurs when the caller does not honor its end of this contract. For example, if a program does not call chdir() after calling chroot(), it violates the contract that specifies how to change the active root directory in a secure fashion. Another good example of library abuse is expecting the callee to return trustworthy DNS information to the caller. In this case, the caller misuses the callee API by making certain assumptions about its behavior (that the return value can be used for authentication purposes). One can also violate the caller-callee contract from the other side. For example, if a coder subclasses SecureRandom and returns a non-random value, the contract is violated.",,unclassified, CWE-229,EN-Improper Handling of Values (Type: Base),"The software does not properly handle when the expected number of values for parameters, fields, or arguments is not provided in input, or if those values are undefined. -An API is a contract between a caller and a callee. The most common forms of API misuse occurs when the caller does not honor its end of this contract. For example, if a program does not call chdir() after calling chroot(), it violates the contract that specifies how to change the active root directory in a secure fashion. Another good example of library abuse is expecting the callee to return trustworthy DNS information to the caller. In this case, the caller misuses the callee API by making certain assumptions about its behavior (that the return value can be used for authentication purposes). One can also violate the caller-callee contract from the other side. For example, if a coder subclasses SecureRandom and returns a non-random value, the contract is violated.",,unclassified, +An API is a contract between a caller and a callee. The most common forms of API misuse occurs when the caller does not honor its end of this contract. For example, if a program does not call chdir() after calling chroot(), it violates the contract that specifies how to change the active root directory in a secure fashion. Another good example of library abuse is expecting the callee to return trustworthy DNS information to the caller. In this case, the caller misuses the callee API by making certain assumptions about its behavior (that the return value can be used for authentication purposes). One can also violate the caller-callee contract from the other side. For example, if a coder subclasses SecureRandom and returns a non-random value, the contract is violated.",,unclassified, CWE-23,EN-Relative Path Traversal (Type: Base),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as "".."" that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified,"OWASP Attack listing: http://www.owasp.org/index.php/Relative_Path_Traversal -The Art of Software Security Assessment: Chapter 9, ""Filenames and Paths"", Page 503." +The Art of Software Security Assessment: Chapter 9, ""Filenames and Paths"", Page 503." CWE-230,EN-Improper Handling of Missing Values (Type: Variant),"The software does not handle or incorrectly handles when a parameter, field, or argument name is specified, but the associated value is missing, i.e. it is empty, blank, or null. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-231,EN-Improper Handling of Extra Values (Type: Variant),"The software does not handle or incorrectly handles when more values are provided than expected. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-232,EN-Improper Handling of Undefined Values (Type: Variant),"The software does not handle or incorrectly handles when a value is not defined or supported for the associated parameter, field, or argument name. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-233,EN-Improper Handling of Parameters (Type: Base),"The software does not properly handle when the expected number of parameters, fields, or arguments is not provided in input, or if those parameters are undefined. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-235,EN-Improper Handling of Extra Parameters (Type: Variant),"The software does not handle or incorrectly handles when the number of parameters, fields, or arguments with the same name exceeds the expected amount. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-236,EN-Improper Handling of Undefined Parameters (Type: Variant),"The software does not handle or incorrectly handles when a particular parameter, field, or argument name is not defined or supported by the product. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-237,EN-Improper Handling of Structural Elements (Type: Base),"The software does not handle or incorrectly handles inputs that are related to complex structures. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-238,EN-Improper Handling of Incomplete Structural Elements (Type: Variant),"The software does not handle or incorrectly handles when a particular structural element is not completely specified. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-239,EN-Failure to Handle Incomplete Element (Type: Variant),"The software does not properly handle when a particular element is not completely specified. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified, CWE-24,EN-Path Traversal: ../filedir (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize ""../"" sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The ""../"" manipulation is the canonical manipulation for operating systems that use ""/"" as directory separators, such as UNIX- and Linux-based systems. In some cases, it is useful for bypassing protection schemes in environments for which ""/"" is supported but not the primary separator, such as Windows, which uses ""\"" but can also accept ""/"".",,unclassified, +The ""../"" manipulation is the canonical manipulation for operating systems that use ""/"" as directory separators, such as UNIX- and Linux-based systems. In some cases, it is useful for bypassing protection schemes in environments for which ""/"" is supported but not the primary separator, such as Windows, which uses ""\"" but can also accept ""/"".",,unclassified, CWE-240,EN-Improper Handling of Inconsistent Structural Elements (Type: Variant),"The software does not handle or incorrectly handles when two or more structural elements should be consistent, but are not. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The ""../"" manipulation is the canonical manipulation for operating systems that use ""/"" as directory separators, such as UNIX- and Linux-based systems. In some cases, it is useful for bypassing protection schemes in environments for which ""/"" is supported but not the primary separator, such as Windows, which uses ""\"" but can also accept ""/"".",,unclassified, +The ""../"" manipulation is the canonical manipulation for operating systems that use ""/"" as directory separators, such as UNIX- and Linux-based systems. In some cases, it is useful for bypassing protection schemes in environments for which ""/"" is supported but not the primary separator, such as Windows, which uses ""\"" but can also accept ""/"".",,unclassified, CWE-241,EN-Improper Handling of Unexpected Data Type (Type: Base),"The software does not handle or incorrectly handles when a particular element is not the expected type, e.g. it expects a digit (0-9) but is provided with a letter (A-Z). This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The ""../"" manipulation is the canonical manipulation for operating systems that use ""/"" as directory separators, such as UNIX- and Linux-based systems. In some cases, it is useful for bypassing protection schemes in environments for which ""/"" is supported but not the primary separator, such as Windows, which uses ""\"" but can also accept ""/"".",,unclassified, +The ""../"" manipulation is the canonical manipulation for operating systems that use ""/"" as directory separators, such as UNIX- and Linux-based systems. In some cases, it is useful for bypassing protection schemes in environments for which ""/"" is supported but not the primary separator, such as Windows, which uses ""\"" but can also accept ""/"".",,unclassified, CWE-244,EN-Improper Clearing of Heap Memory Before Release (Heap Inspection) (Type: Variant),"Using realloc() to resize buffers that store sensitive information can leave the sensitive information exposed to attack, because it is not removed from memory. -When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, +When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, CWE-245,EN-J2EE Bad Practices: Direct Management of Connections (Type: Variant),"The J2EE application directly manages connections, instead of using the container's connection management facilities. -When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, +When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, CWE-246,EN-J2EE Bad Practices: Direct Use of Sockets (Type: Variant),"The J2EE application directly uses sockets instead of using framework method calls. -When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, +When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, CWE-247,EN-DEPRECATED (Duplicate): Reliance on DNS Lookups in a Security Decision (Type: Base),"This entry has been deprecated because it was a duplicate of CWE-350. All content has been transferred to CWE-350. -When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, +When sensitive data such as a password or an encryption key is not removed from memory, it could be exposed to an attacker using a ""heap inspection"" attack that reads the sensitive data using memory dumps or other methods. The realloc() function is commonly used to increase the size of a block of allocated memory. This operation often requires copying the contents of the old memory block into a new and larger block. This operation leaves the contents of the original block intact but inaccessible to the program, preventing the program from being able to scrub sensitive data from memory. If an attacker can later examine the contents of a memory dump, the sensitive data could be exposed.",,unclassified, CWE-248,EN-Uncaught Exception (Type: Base),"An exception is thrown from a function, but it is not caught. -When an exception is not caught, it may cause the program to crash or expose sensitive information.",,unclassified, +When an exception is not caught, it may cause the program to crash or expose sensitive information.",,unclassified, CWE-249,EN-DEPRECATED: Often Misused: Path Manipulation (Type: Variant),"This entry has been deprecated because of name confusion and an accidental combination of multiple weaknesses. Most of its content has been transferred to CWE-785. -When an exception is not caught, it may cause the program to crash or expose sensitive information.",,unclassified, +When an exception is not caught, it may cause the program to crash or expose sensitive information.",,unclassified, CWE-25,EN-Path Traversal: /../filedir (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize ""/../"" sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -Sometimes a program checks for ""../"" at the beginning of the input, so a ""/../"" can bypass that check.",,unclassified, +Sometimes a program checks for ""../"" at the beginning of the input, so a ""/../"" can bypass that check.",,unclassified, CWE-26,EN-Path Traversal: /dir/../filename (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize ""/dir/../filename"" sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '/dir/../filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only checks for ""../"" at the beginning of the input, so a ""/../"" can bypass that check.",,unclassified, +The '/dir/../filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only checks for ""../"" at the beginning of the input, so a ""/../"" can bypass that check.",,unclassified, CWE-260,EN-Password in Configuration File (Type: Variant),"The software stores a password in a configuration file that might be accessible to actors who do not know the password. -This can result in compromise of the system for which the password is used. An attacker could gain access to this file and learn the stored password or worse yet, change the password to one of their choosing.",,unclassified,Building Secure Software: How to Avoid Security Problems the Right Way +This can result in compromise of the system for which the password is used. An attacker could gain access to this file and learn the stored password or worse yet, change the password to one of their choosing.",,unclassified,Building Secure Software: How to Avoid Security Problems the Right Way CWE-261,EN-Weak Cryptography for Passwords (Type: Variant),"Obscuring a password with a trivial encoding does not protect the password. This can result in compromise of the system for which the password is used. An attacker could gain access to this file and learn the stored password or worse yet, change the password to one of their choosing.",,unclassified,"Building Secure Software: How to Avoid Security Problems the Right Way -24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" +24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" CWE-266,EN-Incorrect Privilege Assignment (Type: Base),"A product incorrectly assigns a privilege to a particular actor, creating an unintended sphere of control for that actor. -Just as neglecting to include functionality for the management of password aging is dangerous, so is allowing password aging to continue unchecked. Passwords must be given a maximum life span, after which a user is required to update with a new and different password.",,unclassified,Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html +Just as neglecting to include functionality for the management of password aging is dangerous, so is allowing password aging to continue unchecked. Passwords must be given a maximum life span, after which a user is required to update with a new and different password.",,unclassified,Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html CWE-267,EN-Privilege Defined With Unsafe Actions (Type: Base),"A particular privilege, role, capability, or right can be used to perform unsafe actions that were not intended, even when it is assigned to the correct entity. -Just as neglecting to include functionality for the management of password aging is dangerous, so is allowing password aging to continue unchecked. Passwords must be given a maximum life span, after which a user is required to update with a new and different password.",,unclassified,Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html +Just as neglecting to include functionality for the management of password aging is dangerous, so is allowing password aging to continue unchecked. Passwords must be given a maximum life span, after which a user is required to update with a new and different password.",,unclassified,Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html CWE-27,EN-Path Traversal: dir/../../filename (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize multiple internal ""../"" sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The 'directory/../../filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only removes one ""../"" sequence, so multiple ""../"" can bypass that check. Alternately, this manipulation could be used to bypass a check for ""../"" at the beginning of the pathname, moving up more than one directory level.",,unclassified, +The 'directory/../../filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only removes one ""../"" sequence, so multiple ""../"" can bypass that check. Alternately, this manipulation could be used to bypass a check for ""../"" at the beginning of the pathname, moving up more than one directory level.",,unclassified, CWE-270,EN-Privilege Context Switching Error (Type: Base),"The software does not properly manage privileges while it is switching between different contexts that have different privileges or spheres of control. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. The 'directory/../../filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only removes one ""../"" sequence, so multiple ""../"" can bypass that check. Alternately, this manipulation could be used to bypass a check for ""../"" at the beginning of the pathname, moving up more than one directory level.",,unclassified,"Writing Secure Code: Chapter 7, ""Running with Least Privilege"" Page 207 -Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" +Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" CWE-272,EN-Least Privilege Violation (Type: Base),"The elevated privilege level required to perform operations such as chroot() should be dropped immediately after the operation is performed. -In some contexts, a system executing with elevated permissions will hand off a process/file/etc. to another process or user. If the privileges of an entity are not reduced, then elevated privileges are spread throughout a system and possibly to an attacker.",,unclassified, +In some contexts, a system executing with elevated permissions will hand off a process/file/etc. to another process or user. If the privileges of an entity are not reduced, then elevated privileges are spread throughout a system and possibly to an attacker.",,unclassified, CWE-274,EN-Improper Handling of Insufficient Privileges (Type: Base),"The software does not handle or incorrectly handles when it has insufficient privileges to perform an operation, leading to resultant weaknesses. -If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, +If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, CWE-277,EN-Insecure Inherited Permissions (Type: Variant),"A product defines a set of insecure permissions that are inherited by objects that are created by the program. -If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, +If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, CWE-278,EN-Insecure Preserved Inherited Permissions (Type: Variant),"A product inherits a set of insecure permissions for an object, e.g. when copying from an archive file, without user awareness or involvement. -If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, +If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, CWE-279,EN-Incorrect Execution-Assigned Permissions (Type: Variant),"While it is executing, the software sets the permissions of an object in a way that violates the intended permissions that have been specified by the user. -If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, +If the drop fails, the software will continue to run with the raised privileges, which might provide additional access to unprivileged users.",,unclassified, CWE-28,EN-Path Traversal: ..\filedir (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize ""..\"" sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, +The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, CWE-280,EN-Improper Handling of Insufficient Permissions or Privileges (Type: Base),"The application does not handle or incorrectly handles when it has insufficient privileges to access resources or functionality as specified by their permissions. This may cause it to follow unexpected code paths that may leave the application in an invalid state. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, +The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, CWE-281,EN-Improper Preservation of Permissions (Type: Base),"The software does not preserve permissions or incorrectly preserves permissions when copying, restoring, or sharing objects, which can cause them to have less restrictive permissions than intended. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, +The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, CWE-282,EN-Improper Ownership Management (Type: Class),"The software assigns the wrong ownership, or does not properly verify the ownership, of an object or resource. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, +The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, CWE-283,EN-Unverified Ownership (Type: Base),"The software does not properly verify that a critical resource is owned by the proper entity. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, +The '..\' manipulation is the canonical manipulation for operating systems that use ""\"" as directory separators, such as Windows. However, it is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, CWE-284,EN-Improper Access Control (Type: Class),"The software does not restrict or incorrectly restricts access to a resource from an unauthorized actor. Access control involves the use of several protection mechanisms such as authentication (proving the identity of an actor) authorization (ensuring that a given actor can access a resource), and accountability (tracking of activities that were performed). When any mechanism is not applied or otherwise fails, attackers can compromise the security of the software by gaining privileges, reading sensitive information, executing commands, evading detection, etc. There are two distinct behaviors that can introduce access control weaknesses: Specification: incorrect privileges, permissions, ownership, etc. are explicitly specified for either the user or the resource (for example, setting a password file to be world-writable, or giving administrator capabilities to a guest user). This action could be performed by the program or the administrator. Enforcement: the mechanism contains errors that prevent it from properly enforcing the specified access control requirements (e.g., allowing the user to specify their own privileges, or allowing a syntactically-incorrect ACL to produce insecure settings). This problem occurs within the program itself, in that it does not actually enforce the intended security policy that the administrator specifies.",,unclassified,"Writing Secure Code: Chapter 6, ""Determining Appropriate Access Control"" Page 171 -24 Deadly Sins of Software Security: ""Sin 17: Failure to Protect Stored Data."" Page 253" +24 Deadly Sins of Software Security: ""Sin 17: Failure to Protect Stored Data."" Page 253" CWE-286,EN-Incorrect User Management (Type: Class),"The software does not properly manage a user within its environment. -Users can be assigned to the wrong group (class) of permissions resulting in unintended access rights to sensitive objects.",,unclassified, +Users can be assigned to the wrong group (class) of permissions resulting in unintended access rights to sensitive objects.",,unclassified, CWE-288,EN-Authentication Bypass Using an Alternate Path or Channel (Type: Base),"A product requires authentication, but the product has an alternate path or channel that does not require authentication. -Users can be assigned to the wrong group (class) of permissions resulting in unintended access rights to sensitive objects.",,unclassified, +Users can be assigned to the wrong group (class) of permissions resulting in unintended access rights to sensitive objects.",,unclassified, CWE-289,EN-Authentication Bypass by Alternate Name (Type: Variant),"The software performs authentication based on the name of a resource being accessed, or the name of the actor performing the access, but it does not properly check all possible names for that resource or actor. -Users can be assigned to the wrong group (class) of permissions resulting in unintended access rights to sensitive objects.",,unclassified, +Users can be assigned to the wrong group (class) of permissions resulting in unintended access rights to sensitive objects.",,unclassified, CWE-29,EN-Path Traversal: \..\filename (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize '\..\filename' (leading backslash dot dot) sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -This is similar to CWE-25, except using ""\"" instead of ""/"". Sometimes a program checks for ""..\"" at the beginning of the input, so a ""\..\"" can bypass that check. It is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, +This is similar to CWE-25, except using ""\"" instead of ""/"". Sometimes a program checks for ""..\"" at the beginning of the input, so a ""\..\"" can bypass that check. It is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified, CWE-290,EN-Authentication Bypass by Spoofing (Type: Base),"This attack-focused weakness is caused by improperly implemented authentication schemes that are subject to spoofing attacks. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -This is similar to CWE-25, except using ""\"" instead of ""/"". Sometimes a program checks for ""..\"" at the beginning of the input, so a ""\..\"" can bypass that check. It is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""Spoofing and Identification"", Page 72." +This is similar to CWE-25, except using ""\"" instead of ""/"". Sometimes a program checks for ""..\"" at the beginning of the input, so a ""\..\"" can bypass that check. It is also useful for bypassing path traversal protection schemes that only assume that the ""/"" separator is valid.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""Spoofing and Identification"", Page 72." CWE-295,EN-Improper Certificate Validation (Type: Base),"The software does not validate, or incorrectly validates, a certificate. When a certificate is invalid or malicious, it might allow an attacker to spoof a trusted entity by using a man-in-the-middle (MITM) attack. The software might connect to a malicious host while believing it is a trusted host, or the software might be deceived into accepting spoofed data that appears to originate from a trusted host.",,unclassified,"Why Eve and Mallory Love Android: An Analysis of Android SSL (In)Security: http://www2.dcsec.uni-hannover.de/files/android/p50-fahl.pdf -Computer Security: Art and Science" +Computer Security: Art and Science" CWE-30,EN-Path Traversal: \dir\..\filename (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize '\dir\..\filename' (leading backslash dot dot) sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -This is similar to CWE-26, except using ""\"" instead of ""/"". The '\dir\..\filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only checks for ""..\"" at the beginning of the input, so a ""\..\"" can bypass that check.",,unclassified, +This is similar to CWE-26, except using ""\"" instead of ""/"". The '\dir\..\filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only checks for ""..\"" at the beginning of the input, so a ""\..\"" can bypass that check.",,unclassified, CWE-300,EN-Channel Accessible by Non-Endpoint (Man-in-the-Middle) (Type: Class),"The product does not adequately verify the identity of actors at both ends of a communication channel, or does not adequately ensure the integrity of the channel, in a way that allows the channel to be accessed or influenced by an actor that is not an endpoint. -In order to establish secure communication between two parties, it is often important to adequately verify the identity of entities at each end of the communication channel. Inadequate or inconsistent verification may result in insufficient or incorrect identification of either communicating entity. This can have negative consequences such as misplaced trust in the entity at the other end of the channel. An attacker can leverage this by interposing between the communicating entities and masquerading as the original entity. In the absence of sufficient verification of identity, such an attacker can eavesdrop and potentially modify the communication between the original entities.",,unclassified,Computer Security: Art and Science +In order to establish secure communication between two parties, it is often important to adequately verify the identity of entities at each end of the communication channel. Inadequate or inconsistent verification may result in insufficient or incorrect identification of either communicating entity. This can have negative consequences such as misplaced trust in the entity at the other end of the channel. An attacker can leverage this by interposing between the communicating entities and masquerading as the original entity. In the absence of sufficient verification of identity, such an attacker can eavesdrop and potentially modify the communication between the original entities.",,unclassified,Computer Security: Art and Science CWE-302,EN-Authentication Bypass by Assumed-Immutable Data (Type: Variant),"The authentication scheme or implementation uses key data elements that are assumed to be immutable, but can be controlled or modified by the attacker. -A mutual authentication protocol requires each party to respond to a random challenge by the other party by encrypting it with a pre-shared key. Often, however, such protocols employ the same pre-shared key for communication with a number of different entities. A malicious user or an attacker can easily compromise this protocol without possessing the correct key by employing a reflection attack on the protocol.",,unclassified, +A mutual authentication protocol requires each party to respond to a random challenge by the other party by encrypting it with a pre-shared key. Often, however, such protocols employ the same pre-shared key for communication with a number of different entities. A malicious user or an attacker can easily compromise this protocol without possessing the correct key by employing a reflection attack on the protocol.",,unclassified, CWE-303,EN-Incorrect Implementation of Authentication Algorithm (Type: Base),"The requirements for the software dictate the use of an established authentication algorithm, but the implementation of the algorithm is incorrect. -This incorrect implementation may allow authentication to be bypassed.",,unclassified, +This incorrect implementation may allow authentication to be bypassed.",,unclassified, CWE-304,EN-Missing Critical Step in Authentication (Type: Base),"The software implements an authentication technique, but it skips a step that weakens the technique. -Authentication techniques should follow the algorithms that define them exactly, otherwise authentication can be bypassed or more easily subjected to brute force attacks.",,unclassified, +Authentication techniques should follow the algorithms that define them exactly, otherwise authentication can be bypassed or more easily subjected to brute force attacks.",,unclassified, CWE-305,EN-Authentication Bypass by Primary Weakness (Type: Base),"The authentication algorithm is sound, but the implemented mechanism can be bypassed as the result of a separate weakness that is primary to the authentication error. -Authentication techniques should follow the algorithms that define them exactly, otherwise authentication can be bypassed or more easily subjected to brute force attacks.",,unclassified, +Authentication techniques should follow the algorithms that define them exactly, otherwise authentication can be bypassed or more easily subjected to brute force attacks.",,unclassified, CWE-307,EN-Improper Restriction of Excessive Authentication Attempts (Type: Base),"The software does not implement sufficient measures to prevent multiple failed authentication attempts within in a short time frame, making it more susceptible to brute force attacks. Authentication techniques should follow the algorithms that define them exactly, otherwise authentication can be bypassed or more easily subjected to brute force attacks.",,unclassified,"Weak Password Brings 'Happiness' to Twitter Hacker: http://www.wired.com/threatlevel/2009/01/professed-twitt/ -OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" +OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI" CWE-31,EN-Path Traversal: dir\..\..\filename (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize 'dir\..\..\filename' (multiple internal backslash dot dot) sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The 'dir\..\..\filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only removes one ""..\"" sequence, so multiple ""..\"" can bypass that check. Alternately, this manipulation could be used to bypass a check for ""..\"" at the beginning of the pathname, moving up more than one directory level.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +The 'dir\..\..\filename' manipulation is useful for bypassing some path traversal protection schemes. Sometimes a program only removes one ""..\"" sequence, so multiple ""..\"" can bypass that check. Alternately, this manipulation could be used to bypass a check for ""..\"" at the beginning of the pathname, moving up more than one directory level.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-312,EN-Cleartext Storage of Sensitive Information (Type: Base),"The application stores sensitive information in cleartext within a resource that might be accessible to another control sphere. Because the information is stored in cleartext, attackers could potentially read it. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified,"Writing Secure Code: Chapter 9, ""Protecting Secret Data"" Page 299 The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Encryption"", Page 43. -Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/" +Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/" CWE-313,EN-Cleartext Storage in a File or on Disk (Type: Variant),"The application stores sensitive information in cleartext in a file, or on disk. -The sensitive information could be read by attackers with access to the file, or with physical or administrator access to the raw disk. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, +The sensitive information could be read by attackers with access to the file, or with physical or administrator access to the raw disk. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, CWE-314,EN-Cleartext Storage in the Registry (Type: Variant),"The application stores sensitive information in cleartext in the registry. -Attackers can read the information by accessing the registry key. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, +Attackers can read the information by accessing the registry key. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, CWE-315,EN-Cleartext Storage of Sensitive Information in a Cookie (Type: Variant),"The application stores sensitive information in cleartext in a cookie. -Attackers can use widely-available tools to view the cookie and read the sensitive information. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, +Attackers can use widely-available tools to view the cookie and read the sensitive information. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, CWE-316,EN-Cleartext Storage of Sensitive Information in Memory (Type: Variant),"The application stores sensitive information in cleartext in memory. The sensitive memory might be saved to disk, stored in a core dump, or remain uncleared if the application crashes, or if the programmer does not properly clear the memory before freeing it. -It could be argued that such problems are usually only exploitable by those with administrator privileges. However, swapping could cause the memory to be written to disk and leave it accessible to physical attack afterwards. Core dump files might have insecure permissions or be stored in archive files that are accessible to untrusted people. Or, uncleared sensitive memory might be inadvertently exposed to attackers due to another weakness.",,unclassified, +It could be argued that such problems are usually only exploitable by those with administrator privileges. However, swapping could cause the memory to be written to disk and leave it accessible to physical attack afterwards. Core dump files might have insecure permissions or be stored in archive files that are accessible to untrusted people. Or, uncleared sensitive memory might be inadvertently exposed to attackers due to another weakness.",,unclassified, CWE-317,EN-Cleartext Storage of Sensitive Information in GUI (Type: Variant),"The application stores sensitive information in cleartext within the GUI. -An attacker can often obtain data from a GUI, even if hidden, by using an API to directly access GUI objects such as windows and menus. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, +An attacker can often obtain data from a GUI, even if hidden, by using an API to directly access GUI objects such as windows and menus. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, CWE-318,EN-Cleartext Storage of Sensitive Information in Executable (Type: Variant),"The application stores sensitive information in cleartext in an executable. -Attackers can reverse engineer binary code to obtain secret data. This is especially easy when the cleartext is plain ASCII. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, +Attackers can reverse engineer binary code to obtain secret data. This is especially easy when the cleartext is plain ASCII. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, then decode the information.",,unclassified, CWE-32,EN-Path Traversal: ... (Triple Dot) (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize '...' (triple dot) sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '...' manipulation is useful for bypassing some path traversal protection schemes. On some Windows systems, it is equivalent to ""..\.."" and might bypass checks that assume only two dots are valid. Incomplete filtering, such as removal of ""./"" sequences, can ultimately produce valid "".."" sequences due to a collapse into unsafe value (CWE-182).",,unclassified, +The '...' manipulation is useful for bypassing some path traversal protection schemes. On some Windows systems, it is equivalent to ""..\.."" and might bypass checks that assume only two dots are valid. Incomplete filtering, such as removal of ""./"" sequences, can ultimately produce valid "".."" sequences due to a collapse into unsafe value (CWE-182).",,unclassified, CWE-325,EN-Missing Required Cryptographic Step (Type: Base),"The software does not implement a required step in a cryptographic algorithm, resulting in weaker encryption than advertised by that algorithm. -Cryptographic implementations should follow the algorithms that define them exactly, otherwise encryption can be weaker than expected.",,unclassified, +Cryptographic implementations should follow the algorithms that define them exactly, otherwise encryption can be weaker than expected.",,unclassified, CWE-326,EN-Inadequate Encryption Strength (Type: Class),"The software stores or transmits sensitive data using an encryption scheme that is theoretically sound, but is not strong enough for the level of protection required. A weak encryption scheme can be subjected to brute force attacks that have a reasonable chance of succeeding using current attack methods and resources.",,unclassified,"Writing Secure Code: Chapter 8, ""Cryptographic Foibles"" Page 259 -24 Deadly Sins of Software Security: ""Sin 21: Using the Wrong Cryptography."" Page 315" +24 Deadly Sins of Software Security: ""Sin 21: Using the Wrong Cryptography."" Page 315" CWE-328,EN-Reversible One-Way Hash (Type: Base),"The product uses a hashing algorithm that produces a hash value that can be used to determine the original input, or to find an input that can produce the same hash, more efficiently than brute force techniques. This weakness is especially dangerous when the hash is used in security algorithms that require the one-way property to hold. For example, if an authentication system takes an incoming password and generates a hash, then compares the hash to another hash that it has stored in its authentication database, then the ability to create a collision could allow an attacker to provide an alternate password that produces the same target hash, bypassing authentication.",,unclassified,"MD5 considered harmful today: http://www.phreedom.org/research/rogue-ca/ The Art of Software Security Assessment: Chapter 2, ""Common Vulnerabilities of Integrity"", Page 47. @@ -1128,73 +1128,73 @@ Tarsnap - The scrypt key derivation function and encryption utility: http://www. How Companies Can Beef Up Password Security (interview with Thomas H. Ptacek): http://krebsonsecurity.com/2012/06/how-companies-can-beef-up-password-security/ Password security: past, present, future: http://www.openwall.com/presentations/PHDays2012-Password-Security/ Our password hashing has no clothes: http://www.troyhunt.com/2012/06/our-password-hashing-has-no-clothes.html -Should we really use bcrypt/scrypt?: http://www.analyticalengine.net/2012/06/should-we-really-use-bcryptscrypt/" +Should we really use bcrypt/scrypt?: http://www.analyticalengine.net/2012/06/should-we-really-use-bcryptscrypt/" CWE-33,EN-Path Traversal: .... (Multiple Dot) (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize '....' (multiple dot) sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '....' manipulation is useful for bypassing some path traversal protection schemes. On some Windows systems, it is equivalent to ""..\..\.."" and might bypass checks that assume only two dots are valid. Incomplete filtering, such as removal of ""./"" sequences, can ultimately produce valid "".."" sequences due to a collapse into unsafe value (CWE-182).",,unclassified, +The '....' manipulation is useful for bypassing some path traversal protection schemes. On some Windows systems, it is equivalent to ""..\..\.."" and might bypass checks that assume only two dots are valid. Incomplete filtering, such as removal of ""./"" sequences, can ultimately produce valid "".."" sequences due to a collapse into unsafe value (CWE-182).",,unclassified, CWE-331,EN-Insufficient Entropy (Type: Base),"The software uses an algorithm or scheme that produces insufficient entropy, leaving patterns or clusters of values that are more likely to occur than others. -When software generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that will be generated, and use this guess to impersonate another user or access sensitive information.",,unclassified,Building Secure Software: How to Avoid Security Problems the Right Way +When software generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that will be generated, and use this guess to impersonate another user or access sensitive information.",,unclassified,Building Secure Software: How to Avoid Security Problems the Right Way CWE-334,EN-Small Space of Random Values (Type: Base),"The number of possible random values is smaller than needed by the product, making it more susceptible to brute force attacks. The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,"SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf -24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-335,EN-PRNG Seed Error (Type: Class),"A Pseudo-Random Number Generator (PRNG) uses seeds incorrectly. -The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-336,EN-Same Seed in PRNG (Type: Base),"A PRNG uses the same seed each time the product is initialized. If an attacker can guess (or knows) the seed, then he/she may be able to determine the ""random"" number produced from the PRNG. -The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf +The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf CWE-337,EN-Predictable Seed in PRNG (Type: Base),"A PRNG is initialized from a predictable seed, e.g. using process ID or system time. The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,"SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf -24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-339,EN-Small Seed Space in PRNG (Type: Base),"A PRNG uses a relatively small space of seeds. -The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf +The rate at which true random numbers can be generated is limited. It is important that one uses them only when they are needed for security.",,unclassified,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf CWE-34,EN-Path Traversal: ....// (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize '....//' (doubled dot dot slash) sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '....//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then ""....//"" can collapse into the ""../"" unsafe value (CWE-182). It could also be useful when "".."" is removed, if the operating system treats ""//"" and ""/"" as equivalent.",,unclassified, +The '....//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then ""....//"" can collapse into the ""../"" unsafe value (CWE-182). It could also be useful when "".."" is removed, if the operating system treats ""//"" and ""/"" as equivalent.",,unclassified, CWE-340,EN-Predictability Problems (Type: Class),"Weaknesses in this category are related to schemes that generate numbers or identifiers that are more predictable than required by the application. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '....//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then ""....//"" can collapse into the ""../"" unsafe value (CWE-182). It could also be useful when "".."" is removed, if the operating system treats ""//"" and ""/"" as equivalent.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +The '....//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then ""....//"" can collapse into the ""../"" unsafe value (CWE-182). It could also be useful when "".."" is removed, if the operating system treats ""//"" and ""/"" as equivalent.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-341,EN-Predictable from Observable State (Type: Base),"A number or object is predictable based on observations that the attacker can make about the state of the system or network, such as time, process ID, etc. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. The '....//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then ""....//"" can collapse into the ""../"" unsafe value (CWE-182). It could also be useful when "".."" is removed, if the operating system treats ""//"" and ""/"" as equivalent.",,unclassified,"SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf -24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-342,EN-Predictable Exact Value from Previous Values (Type: Base),"An exact value or random number can be precisely predicted by observing previous values. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. The '....//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then ""....//"" can collapse into the ""../"" unsafe value (CWE-182). It could also be useful when "".."" is removed, if the operating system treats ""//"" and ""/"" as equivalent.",,unclassified,"SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf -24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-343,EN-Predictable Value Range from Previous Values (Type: Base),"The software's random number generator produces a series of values which, when observed, can be used to infer a relatively small range of possibilities for the next value that could be generated. The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified,"SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf Strange Attractors and TCP/IP Sequence Number Analysis: http://www.bindview.com/Services/Razor/Papers/2001/tcpseq.cfm -24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" +24 Deadly Sins of Software Security: ""Sin 20: Weak Random Numbers."" Page 299" CWE-344,EN-Use of Invariant Value in Dynamically Changing Context (Type: Base),"The product uses a constant value, name, or reference, but this value can (or should) vary across different environments. -The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf +The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified,SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf CWE-345,EN-Insufficient Verification of Data Authenticity (Type: Class),"The software does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data. -The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 15: Not Updating Easily."" Page 231" +The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 15: Not Updating Easily."" Page 231" CWE-346,EN-Origin Validation Error (Type: Base),"The software does not properly verify that the source of data or communication is valid. -The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, +The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, CWE-347,EN-Improper Verification of Cryptographic Signature (Type: Base),"The software does not verify, or incorrectly verifies, the cryptographic signature for data. -The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, +The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, CWE-348,EN-Use of Less Trusted Source (Type: Base),"The software has two different sources of the same data or information, but it uses the source that has less support for verification, is less trusted, or is less resistant to attack. -The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, +The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, CWE-349,EN-Acceptance of Extraneous Untrusted Data With Trusted Data (Type: Base),"The software, when processing trusted data, accepts any untrusted data that is also included with the trusted data, treating the untrusted data as if it were trusted. -The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, +The output of a random number generator should not be predictable based on observations of previous values. In some cases, an attacker cannot predict the exact value that will be produced next, but can narrow down the possibilities significantly. This reduces the amount of effort to perform a brute force attack. For example, suppose the product generates random numbers between 1 and 100, but it always produces a larger value until it reaches 100. If the generator produces an 80, then the attacker knows that the next value will be somewhere between 81 and 100. Instead of 100 possibilities, the attacker only needs to consider 20.",,unclassified, CWE-35,EN-Path Traversal: .../...// (Type: Variant),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize '.../...//' (doubled triple dot slash) sequences that can resolve to a location that is outside of that directory. This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory. -The '.../...//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then "".../...//"" can collapse into the ""../"" unsafe value (CWE-182). Removing the first ""../"" yields ""....//""; the second removal yields ""../"". Depending on the algorithm, the software could be susceptible to CWE-34 but not CWE-35, or vice versa.",,unclassified, +The '.../...//' manipulation is useful for bypassing some path traversal protection schemes. If ""../"" is filtered in a sequential fashion, as done by some regular expression engines, then "".../...//"" can collapse into the ""../"" unsafe value (CWE-182). Removing the first ""../"" yields ""....//""; the second removal yields ""../"". Depending on the algorithm, the software could be susceptible to CWE-34 but not CWE-35, or vice versa.",,unclassified, CWE-350,EN-Reliance on Reverse DNS Resolution for a Security-Critical Action (Type: Variant),"The software performs reverse DNS resolution on an IP address to obtain the hostname and make a security decision, but it does not properly ensure that the IP address is truly associated with the hostname. When the software performs a reverse DNS resolution for an IP address, if an attacker controls the server for that IP address, then the attacker can cause the server to return an arbitrary hostname. As a result, the attacker may be able to bypass authentication, cause the wrong hostname to be recorded in log files to hide activities, or perform other attacks. Attackers can spoof DNS names by either (1) compromising a DNS server and modifying its records (sometimes called DNS cache poisoning), or (2) having legitimate control over a DNS server associated with their IP address. Since DNS names can be easily spoofed or misreported, and it may be difficult for the software to detect if a trusted DNS server has not been compromised, they do not constitute a valid authentication mechanism.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 15: Not Updating Easily."" Page 231 24 Deadly Sins of Software Security: ""Sin 24: Trusting Network Name Resolution."" Page 361 -The Art of Software Security Assessment: Chapter 16, ""DNS Spoofing"", Page 1002." +The Art of Software Security Assessment: Chapter 16, ""DNS Spoofing"", Page 1002." CWE-351,EN-Insufficient Type Distinction (Type: Base),"The software does not properly distinguish between different types of elements in a way that leads to insecure behavior. When the software performs a reverse DNS resolution for an IP address, if an attacker controls the server for that IP address, then the attacker can cause the server to return an arbitrary hostname. As a result, the attacker may be able to bypass authentication, cause the wrong hostname to be recorded in log files to hide activities, or perform other attacks. Attackers can spoof DNS names by either (1) compromising a DNS server and modifying its records (sometimes called DNS cache poisoning), or (2) having legitimate control over a DNS server associated with their IP address. -Since DNS names can be easily spoofed or misreported, and it may be difficult for the software to detect if a trusted DNS server has not been compromised, they do not constitute a valid authentication mechanism.",,unclassified, +Since DNS names can be easily spoofed or misreported, and it may be difficult for the software to detect if a trusted DNS server has not been compromised, they do not constitute a valid authentication mechanism.",,unclassified, CWE-356,EN-Product UI does not Warn User of Unsafe Actions (Type: Base),"The software's user interface does not warn the user before undertaking an unsafe action on behalf of that user. This makes it easier for attackers to trick users into inflicting damage to their system. -Software systems should warn users that a potentially dangerous action may occur if the user proceeds. For example, if the user downloads a file from an unknown source and attempts to execute the file on their machine, then the application's GUI can indicate that the file is unsafe.",,unclassified, +Software systems should warn users that a potentially dangerous action may occur if the user proceeds. For example, if the user downloads a file from an unknown source and attempts to execute the file on their machine, then the application's GUI can indicate that the file is unsafe.",,unclassified, CWE-357,EN-Insufficient UI Warning of Dangerous Operations (Type: Base),"The user interface provides a warning to a user regarding dangerous or sensitive operations, but the warning is not noticeable enough to warrant attention. -Software systems should warn users that a potentially dangerous action may occur if the user proceeds. For example, if the user downloads a file from an unknown source and attempts to execute the file on their machine, then the application's GUI can indicate that the file is unsafe.",,unclassified, +Software systems should warn users that a potentially dangerous action may occur if the user proceeds. For example, if the user downloads a file from an unknown source and attempts to execute the file on their machine, then the application's GUI can indicate that the file is unsafe.",,unclassified, CWE-358,EN-Improperly Implemented Security Check for Standard (Type: Base),"The software does not implement or incorrectly implements one or more security-relevant checks as specified by the design of a standardized algorithm, protocol, or technique. -Software systems should warn users that a potentially dangerous action may occur if the user proceeds. For example, if the user downloads a file from an unknown source and attempts to execute the file on their machine, then the application's GUI can indicate that the file is unsafe.",,unclassified, +Software systems should warn users that a potentially dangerous action may occur if the user proceeds. For example, if the user downloads a file from an unknown source and attempts to execute the file on their machine, then the application's GUI can indicate that the file is unsafe.",,unclassified, CWE-359,EN-Privacy Violation (Type: Class),"Mishandling private information, such as customer passwords or social security numbers, can compromise user privacy and is often illegal. Software systems should warn users that a potentially dangerous action may occur if the user proceeds. For example, if the user downloads a file from an unknown source and attempts to execute the file on their machine, then the application's GUI can indicate that the file is unsafe.",,unclassified,"AOL man pleads guilty to selling 92m email addies: http://www.theregister.co.uk/2005/02/07/aol_email_theft/ Safe Harbor Privacy Framework: http://www.export.gov/safeharbor/ @@ -1202,106 +1202,106 @@ Financial Privacy: The Gramm-Leach Bliley Act (GLBA): http://www.ftc.gov/privacy Health Insurance Portability and Accountability Act (HIPAA): http://www.hhs.gov/ocr/hipaa/ California SB-1386: http://info.sen.ca.gov/pub/01-02/bill/sen/sb_1351-1400/sb_1386_bill_20020926_chaptered.html SECURITY REQUIREMENTS FOR CRYPTOGRAPHIC MODULES: http://csrc.nist.gov/publications/fips/fips140-2/fips1402.pdf -Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/" +Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/" CWE-36,EN-Absolute Path Traversal (Type: Base),"The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize absolute path sequences such as ""/abs/path"" that can resolve to a location that is outside of that directory. -This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified,"The Art of Software Security Assessment: Chapter 9, ""Filenames and Paths"", Page 503." +This allows attackers to traverse the file system to access files or directories that are outside of the restricted directory.",,unclassified,"The Art of Software Security Assessment: Chapter 9, ""Filenames and Paths"", Page 503." CWE-363,EN-Race Condition Enabling Link Following (Type: Base),"The software checks the status of a file or directory before accessing it, which produces a race condition in which the file can be replaced with a link before the access is performed, causing the software to access the wrong file. -While developers might expect that there is a very narrow time window between the time of check and time of use, there is still a race condition. An attacker could cause the software to slow down (e.g. with memory consumption), causing the time window to become larger. Alternately, in some situations, the attacker could win the race by performing a large number of attacks.",,unclassified,"The Art of Software Security Assessment: Chapter 9, ""Race Conditions"", Page 526." +While developers might expect that there is a very narrow time window between the time of check and time of use, there is still a race condition. An attacker could cause the software to slow down (e.g. with memory consumption), causing the time window to become larger. Alternately, in some situations, the attacker could win the race by performing a large number of attacks.",,unclassified,"The Art of Software Security Assessment: Chapter 9, ""Race Conditions"", Page 526." CWE-368,EN-Context Switching Race Condition (Type: Base),"A product performs a series of non-atomic actions to switch between contexts that cross privilege or other security boundaries, but a race condition allows an attacker to modify or misrepresent the product's behavior during the switch. -This is commonly seen in web browser vulnerabilities in which the attacker can perform certain actions while the browser is transitioning from a trusted to an untrusted domain, or vice versa, and the browser performs the actions on one domain using the trust level and resources of the other domain.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" +This is commonly seen in web browser vulnerabilities in which the attacker can perform certain actions while the browser is transitioning from a trusted to an untrusted domain, or vice versa, and the browser performs the actions on one domain using the trust level and resources of the other domain.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" CWE-37,EN-Path Traversal: /absolute/pathname/here (Type: Variant),"A software system that accepts input in the form of a slash absolute path ('/absolute/pathname/here') without appropriate validation can allow an attacker to traverse the file system to unintended locations or access arbitrary files. -This weakness typically occurs when an unexpected value is provided to the product, or if an error occurs that is not properly detected. It frequently occurs in calculations involving physical dimensions such as size, length, width, and height.",,unclassified, +This weakness typically occurs when an unexpected value is provided to the product, or if an error occurs that is not properly detected. It frequently occurs in calculations involving physical dimensions such as size, length, width, and height.",,unclassified, CWE-372,EN-Incomplete Internal State Distinction (Type: Base),"The software does not properly determine which state it is in, causing it to assume it is in state X when in fact it is in state Y, causing it to perform incorrect operations in a security-relevant manner. -If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,unclassified, +If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,unclassified, CWE-373,EN-DEPRECATED: State Synchronization Error (Type: Base),"This entry was deprecated because it overlapped the same concepts as race condition (CWE-362) and Improper Synchronization (CWE-662). -If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,unclassified, +If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,unclassified, CWE-377,EN-Insecure Temporary File (Type: Base),"Creating and using insecure temporary files can leave application and system data vulnerable to attack. If the revocation status of a certificate is not checked before each action that requires privileges, the system may be subject to a race condition. If a certificate is revoked after the initial check, all subsequent actions taken with the owner of the revoked certificate will lose all benefits guaranteed by the certificate. In fact, it is almost certain that the use of a revoked certificate indicates malicious activity.",,unclassified,"Writing Secure Code: Chapter 23, ""Creating Temporary Files Securely"" Page 682 The Art of Software Security Assessment: Chapter 9, ""Temporary Files"", Page 538. -The Art of Software Security Assessment: Chapter 11, ""File Squatting"", Page 662." +The Art of Software Security Assessment: Chapter 11, ""File Squatting"", Page 662." CWE-38,EN-Path Traversal: \absolute\pathname\here (Type: Variant),"A software system that accepts input in the form of a backslash absolute path ('\absolute\pathname\here') without appropriate validation can allow an attacker to traverse the file system to unintended locations or access arbitrary files. -On some operating systems, the fact that the temporary file exists may be apparent to any user with sufficient privileges to access that directory. Since the file is visible, the application that is using the temporary file could be known. If one has access to list the processes on the system, the attacker has gained information about what the user is doing at that time. By correlating this with the applications the user is running, an attacker could potentially discover what a user's actions are. From this, higher levels of security could be breached.",,unclassified, +On some operating systems, the fact that the temporary file exists may be apparent to any user with sufficient privileges to access that directory. Since the file is visible, the application that is using the temporary file could be known. If one has access to list the processes on the system, the attacker has gained information about what the user is doing at that time. By correlating this with the applications the user is running, an attacker could potentially discover what a user's actions are. From this, higher levels of security could be breached.",,unclassified, CWE-382,EN-J2EE Bad Practices: Use of System.exit() (Type: Variant),"A J2EE application uses System.exit(), which also shuts down its container. -On some operating systems, the fact that the temporary file exists may be apparent to any user with sufficient privileges to access that directory. Since the file is visible, the application that is using the temporary file could be known. If one has access to list the processes on the system, the attacker has gained information about what the user is doing at that time. By correlating this with the applications the user is running, an attacker could potentially discover what a user's actions are. From this, higher levels of security could be breached.",,unclassified, +On some operating systems, the fact that the temporary file exists may be apparent to any user with sufficient privileges to access that directory. Since the file is visible, the application that is using the temporary file could be known. If one has access to list the processes on the system, the attacker has gained information about what the user is doing at that time. By correlating this with the applications the user is running, an attacker could potentially discover what a user's actions are. From this, higher levels of security could be breached.",,unclassified, CWE-383,EN-J2EE Bad Practices: Direct Use of Threads (Type: Variant),"Thread management in a Web application is forbidden in some circumstances and is always highly error prone. -Thread management in a web application is forbidden by the J2EE standard in some circumstances and is always highly error prone. Managing threads is difficult and is likely to interfere in unpredictable ways with the behavior of the application container. Even without interfering with the container, thread management usually leads to bugs that are hard to detect and diagnose like deadlock, race conditions, and other synchronization errors.",,unclassified, +Thread management in a web application is forbidden by the J2EE standard in some circumstances and is always highly error prone. Managing threads is difficult and is likely to interfere in unpredictable ways with the behavior of the application container. Even without interfering with the container, thread management usually leads to bugs that are hard to detect and diagnose like deadlock, race conditions, and other synchronization errors.",,unclassified, CWE-386,EN-Symbolic Name not Mapping to Correct Object (Type: Base),"A constant symbolic reference to an object is used, even though the reference can resolve to a different object over time. In some instances, knowing when data is transmitted between parties can provide a malicious user with privileged information. Also, externally monitoring the timing of operations can potentially reveal sensitive data. For example, a cryptographic operation can expose its internal state if the time it takes to perform the operation varies, based on the state. -Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,unclassified, +Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,unclassified, CWE-39,EN-Path Traversal: C:dirname (Type: Variant),"An attacker can inject a drive letter or Windows volume letter ('C:dirname') into a software system to potentially redirect access to an unintended location or arbitrary file. In some instances, knowing when data is transmitted between parties can provide a malicious user with privileged information. Also, externally monitoring the timing of operations can potentially reveal sensitive data. For example, a cryptographic operation can expose its internal state if the time it takes to perform the operation varies, based on the state. -Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,unclassified, +Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,unclassified, CWE-392,EN-Missing Report of Error Condition (Type: Base),"The software encounters an error but does not provide a status code or return value to indicate that an error has occurred. In some instances, knowing when data is transmitted between parties can provide a malicious user with privileged information. Also, externally monitoring the timing of operations can potentially reveal sensitive data. For example, a cryptographic operation can expose its internal state if the time it takes to perform the operation varies, based on the state. -Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,unclassified, +Covert channels are frequently classified as either storage or timing channels. Some examples of covert timing channels are the system's paging rate, the time a certain transaction requires to execute, and the time it takes to gain access to a shared bus.",,unclassified, CWE-393,EN-Return of Wrong Status Code (Type: Base),"A function or operation returns an incorrect return value or status code that does not indicate an error, but causes the product to modify its behavior based on the incorrect result. -This can lead to unpredictable behavior. If the function is used to make security-critical decisions or provide security-critical information, then the wrong status code can cause the software to assume that an action is safe, even when it is not.",,unclassified, +This can lead to unpredictable behavior. If the function is used to make security-critical decisions or provide security-critical information, then the wrong status code can cause the software to assume that an action is safe, even when it is not.",,unclassified, CWE-394,EN-Unexpected Status Code or Return Value (Type: Base),"The software does not properly check when a function or operation returns a value that is legitimate for the function, but is not expected by the software. -This can lead to unpredictable behavior. If the function is used to make security-critical decisions or provide security-critical information, then the wrong status code can cause the software to assume that an action is safe, even when it is not.",,unclassified, +This can lead to unpredictable behavior. If the function is used to make security-critical decisions or provide security-critical information, then the wrong status code can cause the software to assume that an action is safe, even when it is not.",,unclassified, CWE-395,EN-Use of NullPointerException Catch to Detect NULL Pointer Dereference (Type: Base),"Catching NullPointerException should not be used as an alternative to programmatic checks to prevent dereferencing a null pointer. -This can lead to unpredictable behavior. If the function is used to make security-critical decisions or provide security-critical information, then the wrong status code can cause the software to assume that an action is safe, even when it is not.",,unclassified, +This can lead to unpredictable behavior. If the function is used to make security-critical decisions or provide security-critical information, then the wrong status code can cause the software to assume that an action is safe, even when it is not.",,unclassified, CWE-396,EN-Declaration of Catch for Generic Exception (Type: Base),"Catching overly broad exceptions promotes complex error handling code that is more likely to contain security vulnerabilities. -Multiple catch blocks can get ugly and repetitive, but ""condensing"" catch blocks by catching a high-level class like Exception can obscure exceptions that deserve special treatment or that should not be caught at this point in the program. Catching an overly broad exception essentially defeats the purpose of Java's typed exceptions, and can become particularly dangerous if the program grows and begins to throw new types of exceptions. The new exception types will not receive any attention.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 9: Catching Exceptions."" Page 157" +Multiple catch blocks can get ugly and repetitive, but ""condensing"" catch blocks by catching a high-level class like Exception can obscure exceptions that deserve special treatment or that should not be caught at this point in the program. Catching an overly broad exception essentially defeats the purpose of Java's typed exceptions, and can become particularly dangerous if the program grows and begins to throw new types of exceptions. The new exception types will not receive any attention.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 9: Catching Exceptions."" Page 157" CWE-397,EN-Declaration of Throws for Generic Exception (Type: Base),"Throwing overly broad exceptions promotes complex error handling code that is more likely to contain security vulnerabilities. -Declaring a method to throw Exception or Throwable makes it difficult for callers to perform proper error handling and error recovery. Java's exception mechanism, for example, is set up to make it easy for callers to anticipate what can go wrong and write code to handle each specific exceptional circumstance. Declaring that a method throws a generic form of exception defeats this system.",,unclassified, +Declaring a method to throw Exception or Throwable makes it difficult for callers to perform proper error handling and error recovery. Java's exception mechanism, for example, is set up to make it easy for callers to anticipate what can go wrong and write code to handle each specific exceptional circumstance. Declaring that a method throws a generic form of exception defeats this system.",,unclassified, CWE-398,EN-Indicator of Poor Code Quality (Type: Class),"The code has features that do not directly introduce a weakness or vulnerability, but indicate that the product has not been carefully developed or maintained. -Programs are more likely to be secure when good development practices are followed. If a program is complex, difficult to maintain, not portable, or shows evidence of neglect, then there is a higher likelihood that weaknesses are buried in the code.",,unclassified, +Programs are more likely to be secure when good development practices are followed. If a program is complex, difficult to maintain, not portable, or shows evidence of neglect, then there is a higher likelihood that weaknesses are buried in the code.",,unclassified, CWE-40,EN-Path Traversal: \\UNC\share\name\ (Windows UNC Share) (Type: Variant),"An attacker can inject a Windows UNC share ('\\UNC\share\name') into a software system to potentially redirect access to an unintended location or arbitrary file. -Programs are more likely to be secure when good development practices are followed. If a program is complex, difficult to maintain, not portable, or shows evidence of neglect, then there is a higher likelihood that weaknesses are buried in the code.",,unclassified,"The Art of Software Security Assessment: Chapter 11, ""Filelike Objects"", Page 664." +Programs are more likely to be secure when good development practices are followed. If a program is complex, difficult to maintain, not portable, or shows evidence of neglect, then there is a higher likelihood that weaknesses are buried in the code.",,unclassified,"The Art of Software Security Assessment: Chapter 11, ""Filelike Objects"", Page 664." CWE-402,EN-Transmission of Private Resources into a New Sphere (Resource Leak) (Type: Class),"The software makes resources available to untrusted parties when those resources are only intended to be accessed by the software. -This is often triggered by improper handling of malformed data or unexpectedly interrupted sessions.",,unclassified, +This is often triggered by improper handling of malformed data or unexpectedly interrupted sessions.",,unclassified, CWE-403,EN-Exposure of File Descriptor to Unintended Control Sphere (File Descriptor Leak) (Type: Base),"A process does not close sensitive file descriptors before invoking a child process, which allows the child to perform unauthorized I/O operations using those descriptors. When a new process is forked or executed, the child process inherits any open file descriptors. When the child process has fewer privileges than the parent process, this might introduce a vulnerability if the child process can access the file descriptor but does not have the privileges to access the associated file.",,unclassified,"File descriptors and setuid applications: https://blogs.oracle.com/paulr/entry/file_descriptors_and_setuid_applications -Introduction to Secure Coding Guide: https://developer.apple.com/library/mac/#documentation/security/conceptual/SecureCodingGuide/Articles/AccessControl.html" +Introduction to Secure Coding Guide: https://developer.apple.com/library/mac/#documentation/security/conceptual/SecureCodingGuide/Articles/AccessControl.html" CWE-405,EN-Asymmetric Resource Consumption (Amplification) (Type: Class),"Software that does not appropriately monitor or control resource consumption can lead to adverse system performance. -This situation is amplified if the software allows malicious users or attackers to consume more resources than their access level permits. Exploiting such a weakness can lead to asymmetric resource consumption, aiding in amplification attacks against the system or the network.",,unclassified, +This situation is amplified if the software allows malicious users or attackers to consume more resources than their access level permits. Exploiting such a weakness can lead to asymmetric resource consumption, aiding in amplification attacks against the system or the network.",,unclassified, CWE-406,EN-Insufficient Control of Network Message Volume (Network Amplification) (Type: Base),"The software does not sufficiently monitor or control transmitted network traffic volume, so that an actor can cause the software to transmit more traffic than should be allowed for that actor. -In the absence of a policy to restrict asymmetric resource consumption, the application or system cannot distinguish between legitimate transmissions and traffic intended to serve as an amplifying attack on target systems. Systems can often be configured to restrict the amount of traffic sent out on behalf of a client, based on the client's origin or access level. This is usually defined in a resource allocation policy. In the absence of a mechanism to keep track of transmissions, the system or application can be easily abused to transmit asymmetrically greater traffic than the request or client should be permitted to.",,unclassified, +In the absence of a policy to restrict asymmetric resource consumption, the application or system cannot distinguish between legitimate transmissions and traffic intended to serve as an amplifying attack on target systems. Systems can often be configured to restrict the amount of traffic sent out on behalf of a client, based on the client's origin or access level. This is usually defined in a resource allocation policy. In the absence of a mechanism to keep track of transmissions, the system or application can be easily abused to transmit asymmetrically greater traffic than the request or client should be permitted to.",,unclassified, CWE-408,EN-Incorrect Behavior Order: Early Amplification (Type: Base),"The software allows an entity to perform a legitimate but expensive operation before authentication or authorization has taken place. -In the absence of a policy to restrict asymmetric resource consumption, the application or system cannot distinguish between legitimate transmissions and traffic intended to serve as an amplifying attack on target systems. Systems can often be configured to restrict the amount of traffic sent out on behalf of a client, based on the client's origin or access level. This is usually defined in a resource allocation policy. In the absence of a mechanism to keep track of transmissions, the system or application can be easily abused to transmit asymmetrically greater traffic than the request or client should be permitted to.",,unclassified, +In the absence of a policy to restrict asymmetric resource consumption, the application or system cannot distinguish between legitimate transmissions and traffic intended to serve as an amplifying attack on target systems. Systems can often be configured to restrict the amount of traffic sent out on behalf of a client, based on the client's origin or access level. This is usually defined in a resource allocation policy. In the absence of a mechanism to keep track of transmissions, the system or application can be easily abused to transmit asymmetrically greater traffic than the request or client should be permitted to.",,unclassified, CWE-409,EN-Improper Handling of Highly Compressed Data (Data Amplification) (Type: Base),"The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output. -An example of data amplification is a ""decompression bomb,"" a small ZIP file that can produce a large amount of data when it is decompressed.",,unclassified, +An example of data amplification is a ""decompression bomb,"" a small ZIP file that can produce a large amount of data when it is decompressed.",,unclassified, CWE-41,EN-Improper Resolution of Path Equivalence (Type: Base),"The system or application is vulnerable to file system contents disclosure through path equivalence. Path equivalence involves the use of special characters in file and directory names. The associated manipulations are intended to generate multiple names for the same object. -Path equivalence is usually employed in order to circumvent access controls expressed using an incomplete set of file name or file path representations. This is different from path traversal, wherein the manipulations are performed to generate a name for a different object.",,unclassified, +Path equivalence is usually employed in order to circumvent access controls expressed using an incomplete set of file name or file path representations. This is different from path traversal, wherein the manipulations are performed to generate a name for a different object.",,unclassified, CWE-410,EN-Insufficient Resource Pool (Type: Base),"The software's resource pool is not large enough to handle peak demand, which allows an attacker to prevent others from accessing the resource by using a (relatively) large number of requests for resources. -Frequently the consequence is a ""flood"" of connection or sessions.",,unclassified,"Writing Secure Code: Chapter 17, ""Protecting Against Denial of Service Attacks"" Page 517" +Frequently the consequence is a ""flood"" of connection or sessions.",,unclassified,"Writing Secure Code: Chapter 17, ""Protecting Against Denial of Service Attacks"" Page 517" CWE-412,EN-Unrestricted Externally Accessible Lock (Type: Base),"The software properly checks for the existence of a lock, but the lock can be externally controlled or influenced by an actor that is outside of the intended sphere of control. -This prevents the software from acting on associated resources or performing other behaviors that are controlled by the presence of the lock. Relevant locks might include an exclusive lock or mutex, or modifying a shared resource that is treated as a lock. If the lock can be held for an indefinite period of time, then the denial of service could be permanent.",,unclassified, +This prevents the software from acting on associated resources or performing other behaviors that are controlled by the presence of the lock. Relevant locks might include an exclusive lock or mutex, or modifying a shared resource that is treated as a lock. If the lock can be held for an indefinite period of time, then the denial of service could be permanent.",,unclassified, CWE-413,EN-Improper Resource Locking (Type: Base),"The software does not lock or does not correctly lock a resource when the software must have exclusive access to the resource. -When a resource is not properly locked, an attacker could modify the resource while it is being operated on by the software. This might violate the software's assumption that the resource will not change, potentially leading to unexpected behaviors.",,unclassified, +When a resource is not properly locked, an attacker could modify the resource while it is being operated on by the software. This might violate the software's assumption that the resource will not change, potentially leading to unexpected behaviors.",,unclassified, CWE-414,EN-Missing Lock Check (Type: Base),"A product does not check to see if a lock is present before performing sensitive operations on a resource. -When a resource is not properly locked, an attacker could modify the resource while it is being operated on by the software. This might violate the software's assumption that the resource will not change, potentially leading to unexpected behaviors.",,unclassified, +When a resource is not properly locked, an attacker could modify the resource while it is being operated on by the software. This might violate the software's assumption that the resource will not change, potentially leading to unexpected behaviors.",,unclassified, CWE-419,EN-Unprotected Primary Channel (Type: Base),"The software uses a primary channel for administration or restricted functionality, but it does not properly protect the channel. The use of previously-freed memory can have any number of adverse consequences, ranging from the corruption of valid data to the execution of arbitrary code, depending on the instantiation and timing of the flaw. The simplest way data corruption may occur involves the system's reuse of the freed memory. Use-after-free errors have two common and sometimes overlapping causes: Error conditions and other exceptional circumstances. Confusion over which part of the program is responsible for freeing the memory. In this scenario, the memory in question is allocated to another pointer validly at some point after it has been freed. The original pointer to the freed memory is used again and points to somewhere within the new allocation. As the data is changed, it corrupts the validly used memory; this induces undefined behavior in the process. -If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,unclassified, +If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,unclassified, CWE-42,EN-Path Equivalence: filename. (Trailing Dot) (Type: Variant),"A software system that accepts path input in the form of trailing dot ('filedir.') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. The use of previously-freed memory can have any number of adverse consequences, ranging from the corruption of valid data to the execution of arbitrary code, depending on the instantiation and timing of the flaw. The simplest way data corruption may occur involves the system's reuse of the freed memory. Use-after-free errors have two common and sometimes overlapping causes: Error conditions and other exceptional circumstances. Confusion over which part of the program is responsible for freeing the memory. In this scenario, the memory in question is allocated to another pointer validly at some point after it has been freed. The original pointer to the freed memory is used again and points to somewhere within the new allocation. As the data is changed, it corrupts the validly used memory; this induces undefined behavior in the process. -If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,unclassified, +If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,unclassified, CWE-420,EN-Unprotected Alternate Channel (Type: Base),"The software protects a primary channel, but it does not use the same level of protection for an alternate channel. The use of previously-freed memory can have any number of adverse consequences, ranging from the corruption of valid data to the execution of arbitrary code, depending on the instantiation and timing of the flaw. The simplest way data corruption may occur involves the system's reuse of the freed memory. Use-after-free errors have two common and sometimes overlapping causes: Error conditions and other exceptional circumstances. Confusion over which part of the program is responsible for freeing the memory. In this scenario, the memory in question is allocated to another pointer validly at some point after it has been freed. The original pointer to the freed memory is used again and points to somewhere within the new allocation. As the data is changed, it corrupts the validly used memory; this induces undefined behavior in the process. -If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,unclassified, +If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved.",,unclassified, CWE-421,EN-Race Condition During Access to Alternate Channel (Type: Base),"The product opens an alternate channel to communicate with an authorized user, but the channel is accessible to other actors. This creates a race condition that allows an attacker to access the channel before the authorized user does.",,unclassified,"Discovering and Exploiting Named Pipe Security Flaws for Fun and Profit: http://www.blakewatts.com/namedpipepaper.html -24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" +24 Deadly Sins of Software Security: ""Sin 13: Race Conditions."" Page 205" CWE-422,EN-Unprotected Windows Messaging Channel (Shatter) (Type: Variant),"The software does not properly verify the source of a message in the Windows Messaging System while running at elevated privileges, creating an alternate channel through which an attacker can directly send a message to the product. This creates a race condition that allows an attacker to access the channel before the authorized user does.",,unclassified,"Exploiting design flaws in the Win32 API for privilege escalation. Or... Shatter Attacks - How to break Windows: http://web.archive.org/web/20060115174629/http://security.tombom.co.uk/shatter.html The Art of Software Security Assessment: Chapter 2, ""Design Review."" Page 34. -The Art of Software Security Assessment: Chapter 12, ""Shatter Attacks"", Page 694." +The Art of Software Security Assessment: Chapter 12, ""Shatter Attacks"", Page 694." CWE-423,EN-DEPRECATED (Duplicate): Proxied Trusted Channel (Type: Base),"This entry has been deprecated because it was a duplicate of CWE-441. All content has been transferred to CWE-441. -This creates a race condition that allows an attacker to access the channel before the authorized user does.",,unclassified, +This creates a race condition that allows an attacker to access the channel before the authorized user does.",,unclassified, CWE-424,EN-Improper Protection of Alternate Path (Type: Class),"The product does not sufficiently protect all possible paths that a user can take to access restricted functionality or resources. -This creates a race condition that allows an attacker to access the channel before the authorized user does.",,unclassified, +This creates a race condition that allows an attacker to access the channel before the authorized user does.",,unclassified, CWE-425,EN-Direct Request (Forced Browsing) (Type: Base),"The web application does not adequately enforce appropriate authorization on all restricted URLs, scripts, or files. -Web applications susceptible to direct request attacks often make the false assumption that such resources can only be reached through a given navigation path and so only apply authorization at certain points in the path.",,unclassified, +Web applications susceptible to direct request attacks often make the false assumption that such resources can only be reached through a given navigation path and so only apply authorization at certain points in the path.",,unclassified, CWE-427,EN-Uncontrolled Search Path Element (Type: Base),"The product uses a fixed or controlled search path to find resources, but one or more locations in that path can be under the control of unintended actors. Although this weakness can occur with any type of resource, it is frequently introduced when a product uses a directory search path to find executables or code libraries, but the path contains a directory that can be modified by an attacker, such as ""/tmp"" or the current working directory. In Windows-based systems, when the LoadLibrary or LoadLibraryEx function is called with a DLL name that does not contain a fully qualified path, the function follows a search order that includes two path elements that might be uncontrolled: @@ -1310,355 +1310,355 @@ the current working directory. In some cases, the attack can be conducted remotely, such as when SMB or WebDAV network shares are used. In some Unix-based systems, a PATH might be created that contains an empty element, e.g. by splicing an empty variable into the PATH. This empty element can be interpreted as equivalent to the current working directory, which might be an untrusted search element.",,unclassified,"Double clicking on MS Office documents from Windows Explorer may execute arbitrary programs in some cases ACROS Security: Remote Binary Planting in Apple iTunes for Windows (ASPR #2010-08-18-1) -Automatic Detection of Vulnerable Dynamic Component Loadings: http://www.cs.ucdavis.edu/research/tech-reports/2010/CSE-2010-2.pdf +Automatic Detection of Vulnerable Dynamic Component Loadings: http://www.cs.ucdavis.edu/research/tech-reports/2010/CSE-2010-2.pdf Dynamic-Link Library Search Order: http://msdn.microsoft.com/en-us/library/ms682586%28v=VS.85%29.aspx Dynamic-Link Library Security: http://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx An update on the DLL-preloading remote attack vector: http://blogs.technet.com/b/srd/archive/2010/08/23/an-update-on-the-dll-preloading-remote-attack-vector.aspx Insecure Library Loading Could Allow Remote Code Execution: http://www.microsoft.com/technet/security/advisory/2269637.mspx Application DLL Load Hijacking: http://blog.rapid7.com/?p=5325 -DLL Hijacking: Facts and Fiction: http://threatpost.com/en_us/blogs/dll-hijacking-facts-and-fiction-082610" +DLL Hijacking: Facts and Fiction: http://threatpost.com/en_us/blogs/dll-hijacking-facts-and-fiction-082610" CWE-428,EN-Unquoted Search Path or Element (Type: Base),"The product uses a search path that contains an unquoted element, in which the element contains whitespace or other separators. This can cause the product to access resources in a parent path. -If a malicious individual has access to the file system, it is possible to elevate privileges by inserting such a file as ""C:\Program.exe"" to be run by a privileged program making use of WinExec.",,unclassified,"The Art of Software Security Assessment: Chapter 11, ""Process Loading"", Page 654." +If a malicious individual has access to the file system, it is possible to elevate privileges by inserting such a file as ""C:\Program.exe"" to be run by a privileged program making use of WinExec.",,unclassified,"The Art of Software Security Assessment: Chapter 11, ""Process Loading"", Page 654." CWE-43,EN-Path Equivalence: filename.... (Multiple Trailing Dot) (Type: Variant),"A software system that accepts path input in the form of multiple trailing dot ('filedir....') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -If a malicious individual has access to the file system, it is possible to elevate privileges by inserting such a file as ""C:\Program.exe"" to be run by a privileged program making use of WinExec.",,unclassified, +If a malicious individual has access to the file system, it is possible to elevate privileges by inserting such a file as ""C:\Program.exe"" to be run by a privileged program making use of WinExec.",,unclassified, CWE-430,EN-Deployment of Wrong Handler (Type: Base),"The wrong ""handler"" is assigned to process an object. -An example of deploying the wrong handler would be calling a servlet to reveal source code of a .JSP file, or automatically ""determining"" type of the object even if it is contradictory to an explicitly specified type.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""File Handlers"", Page 74." +An example of deploying the wrong handler would be calling a servlet to reveal source code of a .JSP file, or automatically ""determining"" type of the object even if it is contradictory to an explicitly specified type.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""File Handlers"", Page 74." CWE-431,EN-Missing Handler (Type: Base),"A handler is not available or implemented. -When an exception is thrown and not caught, the process has given up an opportunity to decide if a given failure or event is worth a change in execution.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""File Handlers"", Page 74." +When an exception is thrown and not caught, the process has given up an opportunity to decide if a given failure or event is worth a change in execution.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""File Handlers"", Page 74." CWE-432,EN-Dangerous Signal Handler not Disabled During Sensitive Operations (Type: Base),"The application uses a signal handler that shares state with other signal handlers, but it does not properly mask or prevent those signal handlers from being invoked while the original signal handler is still running. -During the execution of a signal handler, it can be interrupted by another handler when a different signal is sent. If the two handlers share state - such as global variables - then an attacker can corrupt the state by sending another signal before the first handler has completed execution.",,unclassified, +During the execution of a signal handler, it can be interrupted by another handler when a different signal is sent. If the two handlers share state - such as global variables - then an attacker can corrupt the state by sending another signal before the first handler has completed execution.",,unclassified, CWE-433,EN-Unparsed Raw Web Content Delivery (Type: Variant),"The software stores raw content or supporting code under the web document root with an extension that is not specifically handled by the server. -If code is stored in a file with an extension such as "".inc"" or "".pl"", and the web server does not have a handler for that extension, then the server will likely send the contents of the file directly to the requester without the pre-processing that was expected. When that file contains sensitive information such as database credentials, this may allow the attacker to compromise the application or associated components.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""File Handlers"", Page 74." +If code is stored in a file with an extension such as "".inc"" or "".pl"", and the web server does not have a handler for that extension, then the server will likely send the contents of the file directly to the requester without the pre-processing that was expected. When that file contains sensitive information such as database credentials, this may allow the attacker to compromise the application or associated components.",,unclassified,"The Art of Software Security Assessment: Chapter 3, ""File Handlers"", Page 74." CWE-435,EN-Interaction Error (Type: Class),"An interaction error occurs when two entities work correctly when running independently, but they interact in unexpected ways when they are run together. -This could apply to products, systems, components, etc.",,unclassified, +This could apply to products, systems, components, etc.",,unclassified, CWE-436,EN-Interpretation Conflict (Type: Base),"Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state. This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified,"On Interpretation Conflict Vulnerabilities Insertion, Evasion, and Denial of Service: Eluding Network Intrusion Detection: http://www.insecure.org/stf/secnet_ids/secnet_ids.pdf 0x00 vs ASP file upload scripts: http://www.security-assessment.com/Whitepapers/0x00_vs_ASP_File_Uploads.pdf Poison NULL byte -Re: Corsaire Security Advisory - Multiple vendor MIME RFC2047 encoding: http://marc.theaimsgroup.com/?l=bugtraq&m=109525864717484&w=2" +Re: Corsaire Security Advisory - Multiple vendor MIME RFC2047 encoding: http://marc.theaimsgroup.com/?l=bugtraq&m=109525864717484&w=2" CWE-437,EN-Incomplete Model of Endpoint Features (Type: Base),"A product acts as an intermediary or monitor between two or more endpoints, but it does not have a complete model of an endpoint's features, behaviors, or state, potentially causing the product to perform incorrect actions based on this incomplete model. -This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, +This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, CWE-439,EN-Behavioral Change in New Version or Environment (Type: Base),"A's behavior or functionality changes with a new version of A, or a new environment, which is not known (or manageable) by B. -This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, +This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, CWE-44,EN-Path Equivalence: file.name (Internal Dot) (Type: Variant),"A software system that accepts path input in the form of internal dot ('file.ordir') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, +This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, CWE-440,EN-Expected Behavior Violation (Type: Base),"A feature, API, or function being used by a product behaves differently than the product expects. -This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, +This is generally found in proxies, firewalls, anti-virus software, and other intermediary devices that allow, deny, or modify traffic based on how the client or server is expected to behave.",,unclassified, CWE-441,EN-Unintended Proxy or Intermediary (Confused Deputy) (Type: Class),"The software receives a request, message, or directive from an upstream component, but the software does not sufficiently preserve the original source of the request before forwarding the request to an external actor that is outside of the software's control sphere. This causes the software to appear to be the source of the request, leading it to act as a proxy or other intermediary between the upstream component and the external actor. If an attacker cannot directly contact a target, but the software has access to the target, then the attacker can send a request to the software and have it be forwarded from the target. The request would appear to be coming from the software's system, not the attacker's system. As a result, the attacker can bypass access controls (such as firewalls) or hide the source of malicious requests, since the requests would not be coming directly from the attacker. Since proxy functionality and message-forwarding often serve a legitimate purpose, this issue only becomes a vulnerability when: The software runs with different privileges or on a different system, or otherwise has different levels of access than the upstream component; The attacker is prevented from making the request directly to the target; and -The attacker can create a request that the proxy does not explicitly intend to be forwarded on the behalf of the requester. Such a request might point to an unexpected hostname, port number, or service. Or, the request might be sent to an allowed service, but the request could contain disallowed directives, commands, or resources.",,unclassified,The Confused Deputy (or why capabilities might have been invented): http://www.cap-lore.com/CapTheory/ConfusedDeputy.html +The attacker can create a request that the proxy does not explicitly intend to be forwarded on the behalf of the requester. Such a request might point to an unexpected hostname, port number, or service. Or, the request might be sent to an allowed service, but the request could contain disallowed directives, commands, or resources.",,unclassified,The Confused Deputy (or why capabilities might have been invented): http://www.cap-lore.com/CapTheory/ConfusedDeputy.html CWE-443,EN-DEPRECATED (Duplicate): HTTP response splitting (Type: Base),"This weakness can be found at CWE-113. If an attacker cannot directly contact a target, but the software has access to the target, then the attacker can send a request to the software and have it be forwarded from the target. The request would appear to be coming from the software's system, not the attacker's system. As a result, the attacker can bypass access controls (such as firewalls) or hide the source of malicious requests, since the requests would not be coming directly from the attacker. Since proxy functionality and message-forwarding often serve a legitimate purpose, this issue only becomes a vulnerability when: The software runs with different privileges or on a different system, or otherwise has different levels of access than the upstream component; The attacker is prevented from making the request directly to the target; and -The attacker can create a request that the proxy does not explicitly intend to be forwarded on the behalf of the requester. Such a request might point to an unexpected hostname, port number, or service. Or, the request might be sent to an allowed service, but the request could contain disallowed directives, commands, or resources.",,unclassified, +The attacker can create a request that the proxy does not explicitly intend to be forwarded on the behalf of the requester. Such a request might point to an unexpected hostname, port number, or service. Or, the request might be sent to an allowed service, but the request could contain disallowed directives, commands, or resources.",,unclassified, CWE-444,EN-Inconsistent Interpretation of HTTP Requests (HTTP Request Smuggling) (Type: Base),"When malformed or abnormal HTTP requests are interpreted by one or more entities in the data flow between the user and the web server, such as a proxy or firewall, they can be interpreted inconsistently, allowing the attacker to ""smuggle"" a request to one device without the other device being aware of it. If an attacker cannot directly contact a target, but the software has access to the target, then the attacker can send a request to the software and have it be forwarded from the target. The request would appear to be coming from the software's system, not the attacker's system. As a result, the attacker can bypass access controls (such as firewalls) or hide the source of malicious requests, since the requests would not be coming directly from the attacker. Since proxy functionality and message-forwarding often serve a legitimate purpose, this issue only becomes a vulnerability when: The software runs with different privileges or on a different system, or otherwise has different levels of access than the upstream component; The attacker is prevented from making the request directly to the target; and -The attacker can create a request that the proxy does not explicitly intend to be forwarded on the behalf of the requester. Such a request might point to an unexpected hostname, port number, or service. Or, the request might be sent to an allowed service, but the request could contain disallowed directives, commands, or resources.",,unclassified,HTTP Request Smuggling: http://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf +The attacker can create a request that the proxy does not explicitly intend to be forwarded on the behalf of the requester. Such a request might point to an unexpected hostname, port number, or service. Or, the request might be sent to an allowed service, but the request could contain disallowed directives, commands, or resources.",,unclassified,HTTP Request Smuggling: http://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf CWE-446,EN-UI Discrepancy for Security Feature (Type: Base),"The user interface does not correctly enable or configure a security feature, but the interface provides feedback that causes the user to believe that the feature is in a secure state. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-447,EN-Unimplemented or Unsupported Feature in UI (Type: Base),"A UI function for a security feature appears to be supported and gives feedback to the user that suggests that it is supported, but the underlying functionality is not implemented. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-448,EN-Obsolete Feature in UI (Type: Base),"A UI function is obsolete and the product does not warn the user. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-449,EN-The UI Performs the Wrong Action (Type: Base),"The UI performs the wrong action with respect to the user's request. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-45,EN-Path Equivalence: file...name (Multiple Internal Dot) (Type: Variant),"A software system that accepts path input in the form of multiple internal dot ('file...dir') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-450,EN-Multiple Interpretations of UI Input (Type: Base),"The UI has multiple interpretations of user input but does not prompt the user when it selects the less secure interpretation. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-451,EN-UI Misrepresentation of Critical Information (Type: Base),"The UI does not properly represent critical information to the user, allowing the information - or its source - to be obscured or spoofed. This is often a component in phishing attacks. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-453,EN-Insecure Default Variable Initialization (Type: Base),"The software, by default, initializes an internal variable with an insecure or less secure value than is possible. -When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, +When the user interface does not properly reflect what the user asks of it, then it can lead the user into a false sense of security. For example, the user might check a box to enable a security option to enable encrypted communications, but the software does not actually enable the encryption. Alternately, the user might provide a ""restrict ALL'"" access control rule, but the software only implements ""restrict SOME"".",,unclassified, CWE-454,EN-External Initialization of Trusted Variables or Data Stores (Type: Base),"The software initializes critical internal variables or data stores using inputs that can be modified by untrusted actors. -A software system should be reluctant to trust variables that have been initialized outside of its trust boundary, especially if they are initialized by users. They may have been initialized incorrectly. If an attacker can initialize the variable, then he/she can influence what the vulnerable system will do.",,unclassified, +A software system should be reluctant to trust variables that have been initialized outside of its trust boundary, especially if they are initialized by users. They may have been initialized incorrectly. If an attacker can initialize the variable, then he/she can influence what the vulnerable system will do.",,unclassified, CWE-455,EN-Non-exit on Failed Initialization (Type: Base),"The software does not exit or otherwise modify its operation when security-relevant errors occur during initialization, such as when a configuration file has a format error, which can cause the software to execute in a less secure fashion than intended by the administrator. -A software system should be reluctant to trust variables that have been initialized outside of its trust boundary, especially if they are initialized by users. They may have been initialized incorrectly. If an attacker can initialize the variable, then he/she can influence what the vulnerable system will do.",,unclassified, +A software system should be reluctant to trust variables that have been initialized outside of its trust boundary, especially if they are initialized by users. They may have been initialized incorrectly. If an attacker can initialize the variable, then he/she can influence what the vulnerable system will do.",,unclassified, CWE-456,EN-Missing Initialization of a Variable (Type: Base),"The software does not initialize critical variables, which causes the execution environment to use unexpected values. -A software system should be reluctant to trust variables that have been initialized outside of its trust boundary, especially if they are initialized by users. They may have been initialized incorrectly. If an attacker can initialize the variable, then he/she can influence what the vulnerable system will do.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." +A software system should be reluctant to trust variables that have been initialized outside of its trust boundary, especially if they are initialized by users. They may have been initialized incorrectly. If an attacker can initialize the variable, then he/she can influence what the vulnerable system will do.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." CWE-458,EN-DEPRECATED: Incorrect Initialization (Type: Base),"This weakness has been deprecated because its name and description did not match. The description duplicated CWE-454, while the name suggested a more abstract initialization problem. Please refer to CWE-665 for the more abstract problem. -In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,unclassified, +In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,unclassified, CWE-459,EN-Incomplete Cleanup (Type: Base),"The software does not properly ""clean up"" and remove temporary or supporting resources after they have been used. -In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,unclassified, +In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,unclassified, CWE-46,EN-Path Equivalence: filename (Trailing Space) (Type: Variant),"A software system that accepts path input in the form of trailing space ('filedir ') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,unclassified, +In some languages such as C and C++, stack variables are not initialized by default. They generally contain junk data with the contents of stack memory before the function was invoked. An attacker can sometimes control or read these contents. In other languages or conditions, a variable that is not explicitly initialized can be given a default value that has security implications, depending on the logic of the program. The presence of an uninitialized variable can sometimes indicate a typographic error in the code.",,unclassified, CWE-463,EN-Deletion of Data Structure Sentinel (Type: Base),"The accidental deletion of a data-structure sentinel can cause serious programming logic problems. -Often times data-structure sentinels are used to mark structure of the data structure. A common example of this is the null character at the end of strings. Another common example is linked lists which may contain a sentinel to mark the end of the list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the deletion or modification outside of some wrapper interface which provides safety.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""NUL-Termination Problems"", Page 452." +Often times data-structure sentinels are used to mark structure of the data structure. A common example of this is the null character at the end of strings. Another common example is linked lists which may contain a sentinel to mark the end of the list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the deletion or modification outside of some wrapper interface which provides safety.",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""NUL-Termination Problems"", Page 452." CWE-466,EN-Return of Pointer Value Outside of Expected Range (Type: Base),"A function can return a pointer to memory that is outside of the buffer that the pointer is expected to reference. -Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" +Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 5: Buffer Overruns."" Page 89" CWE-47,EN-Path Equivalence: filename (Leading Space) (Type: Variant),"A software system that accepts path input in the form of leading space (' filedir') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,unclassified, +Data-structure sentinels are often used to mark the structure of data. A common example of this is the null character at the end of strings or a special sentinel to mark the end of a linked list. It is dangerous to allow this type of control data to be easily accessible. Therefore, it is important to protect from the addition or modification of sentinels.",,unclassified, CWE-470,EN-Use of Externally-Controlled Input to Select Classes or Code (Unsafe Reflection) (Type: Base),"The application uses external input with reflection to select which classes or code to use, but it does not sufficiently prevent the input from selecting improper classes or code. -If the application uses external inputs to determine which class to instantiate or which method to invoke, then an attacker could supply values to select unexpected classes or methods. If this occurs, then the attacker could create control flow paths that were not intended by the developer. These paths could bypass authentication or access control checks, or otherwise cause the application to behave in an unexpected manner. This situation becomes a doomsday scenario if the attacker can upload files into a location that appears on the application's classpath (CWE-427) or add new entries to the application's classpath (CWE-426). Under either of these conditions, the attacker can use reflection to introduce new, malicious behavior into the application.",,unclassified, +If the application uses external inputs to determine which class to instantiate or which method to invoke, then an attacker could supply values to select unexpected classes or methods. If this occurs, then the attacker could create control flow paths that were not intended by the developer. These paths could bypass authentication or access control checks, or otherwise cause the application to behave in an unexpected manner. This situation becomes a doomsday scenario if the attacker can upload files into a location that appears on the application's classpath (CWE-427) or add new entries to the application's classpath (CWE-426). Under either of these conditions, the attacker can use reflection to introduce new, malicious behavior into the application.",,unclassified, CWE-471,EN-Modification of Assumed-Immutable Data (MAID) (Type: Base),"The software does not properly protect an assumed-immutable element from being modified by an attacker. -If the application uses external inputs to determine which class to instantiate or which method to invoke, then an attacker could supply values to select unexpected classes or methods. If this occurs, then the attacker could create control flow paths that were not intended by the developer. These paths could bypass authentication or access control checks, or otherwise cause the application to behave in an unexpected manner. This situation becomes a doomsday scenario if the attacker can upload files into a location that appears on the application's classpath (CWE-427) or add new entries to the application's classpath (CWE-426). Under either of these conditions, the attacker can use reflection to introduce new, malicious behavior into the application.",,unclassified, +If the application uses external inputs to determine which class to instantiate or which method to invoke, then an attacker could supply values to select unexpected classes or methods. If this occurs, then the attacker could create control flow paths that were not intended by the developer. These paths could bypass authentication or access control checks, or otherwise cause the application to behave in an unexpected manner. This situation becomes a doomsday scenario if the attacker can upload files into a location that appears on the application's classpath (CWE-427) or add new entries to the application's classpath (CWE-426). Under either of these conditions, the attacker can use reflection to introduce new, malicious behavior into the application.",,unclassified, CWE-472,EN-External Control of Assumed-Immutable Web Parameter (Type: Base),"The web application does not sufficiently verify inputs that are assumed to be immutable but are actually externally controllable, such as hidden form fields. If a web product does not properly protect assumed-immutable values from modification in hidden form fields, parameters, cookies, or URLs, this can lead to modification of critical data. Web applications often mistakenly make the assumption that data passed to the client in hidden fields or cookies is not susceptible to tampering. Improper validation of data that are user-controllable can lead to the application processing incorrect, and often malicious, input. For example, custom cookies commonly store session data or persistent data across sessions. This kind of session data is normally involved in security related decisions on the server side, such as user authentication and access control. Thus, the cookies might contain sensitive data such as user credentials and privileges. This is a dangerous practice, as it can often lead to improper reliance on the value of the client-provided cookie by the server side application.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 4: Use of Magic URLs, Predictable Cookies, and Hidden Form Fields."" Page 75 -The Art of Software Security Assessment: Chapter 17, ""Embedding State in HTML and URLs"", Page 1032." +The Art of Software Security Assessment: Chapter 17, ""Embedding State in HTML and URLs"", Page 1032." CWE-473,EN-PHP External Variable Modification (Type: Variant),"A PHP application does not properly protect against the modification of variables from external sources, such as query parameters or cookies. This can expose the application to numerous weaknesses that would not exist otherwise. If a web product does not properly protect assumed-immutable values from modification in hidden form fields, parameters, cookies, or URLs, this can lead to modification of critical data. Web applications often mistakenly make the assumption that data passed to the client in hidden fields or cookies is not susceptible to tampering. Improper validation of data that are user-controllable can lead to the application processing incorrect, and often malicious, input. -For example, custom cookies commonly store session data or persistent data across sessions. This kind of session data is normally involved in security related decisions on the server side, such as user authentication and access control. Thus, the cookies might contain sensitive data such as user credentials and privileges. This is a dangerous practice, as it can often lead to improper reliance on the value of the client-provided cookie by the server side application.",,unclassified, +For example, custom cookies commonly store session data or persistent data across sessions. This kind of session data is normally involved in security related decisions on the server side, such as user authentication and access control. Thus, the cookies might contain sensitive data such as user credentials and privileges. This is a dangerous practice, as it can often lead to improper reliance on the value of the client-provided cookie by the server side application.",,unclassified, CWE-474,EN-Use of Function with Inconsistent Implementations (Type: Base),"The code uses a function that has inconsistent implementations across operating systems and versions, which might cause security-relevant portability problems. If a web product does not properly protect assumed-immutable values from modification in hidden form fields, parameters, cookies, or URLs, this can lead to modification of critical data. Web applications often mistakenly make the assumption that data passed to the client in hidden fields or cookies is not susceptible to tampering. Improper validation of data that are user-controllable can lead to the application processing incorrect, and often malicious, input. -For example, custom cookies commonly store session data or persistent data across sessions. This kind of session data is normally involved in security related decisions on the server side, such as user authentication and access control. Thus, the cookies might contain sensitive data such as user credentials and privileges. This is a dangerous practice, as it can often lead to improper reliance on the value of the client-provided cookie by the server side application.",,unclassified, +For example, custom cookies commonly store session data or persistent data across sessions. This kind of session data is normally involved in security related decisions on the server side, such as user authentication and access control. Thus, the cookies might contain sensitive data such as user credentials and privileges. This is a dangerous practice, as it can often lead to improper reliance on the value of the client-provided cookie by the server side application.",,unclassified, CWE-475,EN-Undefined Behavior for Input to API (Type: Base),"The behavior of this function is undefined unless its control parameter is set to a specific value. If a web product does not properly protect assumed-immutable values from modification in hidden form fields, parameters, cookies, or URLs, this can lead to modification of critical data. Web applications often mistakenly make the assumption that data passed to the client in hidden fields or cookies is not susceptible to tampering. Improper validation of data that are user-controllable can lead to the application processing incorrect, and often malicious, input. -For example, custom cookies commonly store session data or persistent data across sessions. This kind of session data is normally involved in security related decisions on the server side, such as user authentication and access control. Thus, the cookies might contain sensitive data such as user credentials and privileges. This is a dangerous practice, as it can often lead to improper reliance on the value of the client-provided cookie by the server side application.",,unclassified, +For example, custom cookies commonly store session data or persistent data across sessions. This kind of session data is normally involved in security related decisions on the server side, such as user authentication and access control. Thus, the cookies might contain sensitive data such as user credentials and privileges. This is a dangerous practice, as it can often lead to improper reliance on the value of the client-provided cookie by the server side application.",,unclassified, CWE-477,EN-Use of Obsolete Functions (Type: Base),"The code uses deprecated or obsolete functions, which suggests that the code has not been actively reviewed or maintained. -NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.",,unclassified, +NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.",,unclassified, CWE-478,EN-Missing Default Case in Switch Statement (Type: Variant),"The code does not have a default case in a switch statement, which might lead to complex logical errors and resultant weaknesses. -NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Switch Statements"", Page 337." +NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Switch Statements"", Page 337." CWE-48,EN-Path Equivalence: file name (Internal Whitespace) (Type: Variant),"A software system that accepts path input in the form of internal space ('file(SPACE)name') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. Non-reentrant functions are functions that cannot safely be called, interrupted, and then recalled before the first call has finished without resulting in memory corruption. This can lead to an unexpected system state an unpredictable results with a variety of potential consequences depending on context, including denial of service and code execution. -Many functions are not reentrant, but some of them can result in the corruption of memory if they are used in a signal handler. The function call syslog() is an example of this. In order to perform its functionality, it allocates a small amount of memory as ""scratch space."" If syslog() is suspended by a signal call and the signal handler calls syslog(), the memory used by both of these functions enters an undefined, and possibly, exploitable state. Implementations of malloc() and free() manage metadata in global structures in order to track which memory is allocated versus which memory is available, but they are non-reentrant. Simultaneous calls to these functions can cause corruption of the metadata.",,unclassified, +Many functions are not reentrant, but some of them can result in the corruption of memory if they are used in a signal handler. The function call syslog() is an example of this. In order to perform its functionality, it allocates a small amount of memory as ""scratch space."" If syslog() is suspended by a signal call and the signal handler calls syslog(), the memory used by both of these functions enters an undefined, and possibly, exploitable state. Implementations of malloc() and free() manage metadata in global structures in order to track which memory is allocated versus which memory is available, but they are non-reentrant. Simultaneous calls to these functions can cause corruption of the metadata.",,unclassified, CWE-485,EN-Insufficient Encapsulation (Type: Class),"The product does not sufficiently encapsulate critical data or functionality. -Encapsulation is about drawing strong boundaries. In a web browser that might mean ensuring that your mobile code cannot be abused by other mobile code. On the server it might mean differentiation between validated data and unvalidated data, between one user's data and another's, or between data users are allowed to see and data that they are not.",,unclassified, +Encapsulation is about drawing strong boundaries. In a web browser that might mean ensuring that your mobile code cannot be abused by other mobile code. On the server it might mean differentiation between validated data and unvalidated data, between one user's data and another's, or between data users are allowed to see and data that they are not.",,unclassified, CWE-488,EN-Exposure of Data Element to Wrong Session (Type: Variant),"The product does not sufficiently enforce boundaries between the states of different sessions, causing data to be provided to, or used by, the wrong session. Data can ""bleed"" from one session to another through member variables of singleton objects, such as Servlets, and objects from a shared pool. -In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified, +In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified, CWE-489,EN-Leftover Debug Code (Type: Base),"The application can be deployed with active debugging code that can create unintended entry points. Data can ""bleed"" from one session to another through member variables of singleton objects, such as Servlets, and objects from a shared pool. -In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified, +In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified, CWE-49,EN-Path Equivalence: filename/ (Trailing Slash) (Type: Variant),"A software system that accepts path input in the form of trailing slash ('filedir/') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. Data can ""bleed"" from one session to another through member variables of singleton objects, such as Servlets, and objects from a shared pool. -In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified, +In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified, CWE-491,EN-Public cloneable() Method Without Final (Object Hijack) (Type: Variant),"A class has a cloneable() method that is not declared final, which allows an object to be created without calling the constructor. This can cause the object to be in an unexpected state. Data can ""bleed"" from one session to another through member variables of singleton objects, such as Servlets, and objects from a shared pool. -In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified,"OWASP , Attack Category : Mobile code: object hijack: http://www.owasp.org/index.php/Mobile_code:_object_hijack" +In the case of Servlets, developers sometimes do not understand that, unless a Servlet implements the SingleThreadModel interface, the Servlet is a singleton; there is only one instance of the Servlet, and that single instance is used and re-used to handle multiple requests that are processed simultaneously by different threads. A common result is that developers use Servlet member fields in such a way that one user may inadvertently see another user's data. In other words, storing user data in Servlet member fields introduces a data access race condition.",,unclassified,"OWASP , Attack Category : Mobile code: object hijack: http://www.owasp.org/index.php/Mobile_code:_object_hijack" CWE-495,EN-Private Array-Typed Field Returned From A Public Method (Type: Variant),"The product has a method that is declared public, but returns a reference to a private array, which could then be modified in unexpected ways. -An attacker can execute malicious code by compromising the host server, performing DNS spoofing, or modifying the code in transit.",,unclassified, +An attacker can execute malicious code by compromising the host server, performing DNS spoofing, or modifying the code in transit.",,unclassified, CWE-496,EN-Public Data Assigned to Private Array-Typed Field (Type: Variant),"Assigning public data to a private array is equivalent to giving public access to the array. -An attacker can execute malicious code by compromising the host server, performing DNS spoofing, or modifying the code in transit.",,unclassified, +An attacker can execute malicious code by compromising the host server, performing DNS spoofing, or modifying the code in transit.",,unclassified, CWE-497,EN-Exposure of System Data to an Unauthorized Control Sphere (Type: Variant),"Exposing system data or debugging information helps an adversary learn about the system and form an attack plan. -An information exposure occurs when system data or debugging information leaves the program through an output stream or logging function that makes it accessible to unauthorized parties. An attacker can also cause errors to occur by submitting unusual requests to the web application. The response to these errors can reveal detailed system information, deny service, cause security mechanisms to fail, and crash the server. An attacker can use error messages that reveal technologies, operating systems, and product versions to tune the attack against known vulnerabilities in these technologies. An application may use diagnostic methods that provide significant implementation details such as stack traces as part of its error handling mechanism.",,unclassified, +An information exposure occurs when system data or debugging information leaves the program through an output stream or logging function that makes it accessible to unauthorized parties. An attacker can also cause errors to occur by submitting unusual requests to the web application. The response to these errors can reveal detailed system information, deny service, cause security mechanisms to fail, and crash the server. An attacker can use error messages that reveal technologies, operating systems, and product versions to tune the attack against known vulnerabilities in these technologies. An application may use diagnostic methods that provide significant implementation details such as stack traces as part of its error handling mechanism.",,unclassified, CWE-5,EN-J2EE Misconfiguration: Data Transmission Without Encryption (Type: Variant),"Information sent over a network can be compromised while in transit. An attacker may be able to read/modify the contents if the data are sent in plaintext or are weakly encrypted. -Serializable classes are effectively open classes since data cannot be hidden in them. Classes that do not explicitly deny serialization can be serialized by any other class, which can then in turn use the data stored inside it.",,unclassified, +Serializable classes are effectively open classes since data cannot be hidden in them. Classes that do not explicitly deny serialization can be serialized by any other class, which can then in turn use the data stored inside it.",,unclassified, CWE-50,EN-Path Equivalence: //multiple/leading/slash (Type: Variant),"A software system that accepts path input in the form of multiple leading slash ('//multiple/leading/slash') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -Serializable classes are effectively open classes since data cannot be hidden in them. Classes that do not explicitly deny serialization can be serialized by any other class, which can then in turn use the data stored inside it.",,unclassified, +Serializable classes are effectively open classes since data cannot be hidden in them. Classes that do not explicitly deny serialization can be serialized by any other class, which can then in turn use the data stored inside it.",,unclassified, CWE-501,EN-Trust Boundary Violation (Type: Base),"The product mixes trusted and untrusted data in the same data structure or structured message. -By combining trusted and untrusted data in the same data structure, it becomes easier for programmers to mistakenly trust unvalidated data.",,unclassified, +By combining trusted and untrusted data in the same data structure, it becomes easier for programmers to mistakenly trust unvalidated data.",,unclassified, CWE-506,EN-Embedded Malicious Code (Type: Class),"The application contains code that appears to be malicious in nature. -Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, +Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, CWE-507,EN-Trojan Horse (Type: Base),"The software appears to contain benign or useful functionality, but it also contains code that is hidden from normal operation that violates the intended security policy of the user or the system administrator. -Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified,"Writing Secure Code: Chapter 7, ""Viruses, Trojans, and Worms In a Nutshell"" Page 208" +Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified,"Writing Secure Code: Chapter 7, ""Viruses, Trojans, and Worms In a Nutshell"" Page 208" CWE-508,EN-Non-Replicating Malicious Code (Type: Base),"Non-replicating malicious code only resides on the target system or software that is attacked; it does not attempt to spread to other systems. -Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, +Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, CWE-509,EN-Replicating Malicious Code (Virus or Worm) (Type: Base),"Replicating malicious code, including viruses and worms, will attempt to attack other systems once it has successfully compromised the target system or software. -Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, +Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, CWE-51,EN-Path Equivalence: /multiple//internal/slash (Type: Variant),"A software system that accepts path input in the form of multiple internal slash ('/multiple//internal/slash/') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, +Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, CWE-510,EN-Trapdoor (Type: Base),"A trapdoor is a hidden piece of code that responds to a special input, allowing its user access to resources without passing through the normal security enforcement mechanism. -Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, +Malicious flaws have acquired colorful names, including Trojan horse, trapdoor, timebomb, and logic-bomb. A developer might insert malicious code with the intent to subvert the security of an application or its host system at some time in the future. It generally refers to a program that performs a useful service but exploits rights of the program's user in a way the user does not intend.",,unclassified, CWE-511,EN-Logic/Time Bomb (Type: Base),"The software contains code that is designed to disrupt the legitimate operation of the software (or its environment) when a certain time passes, or when a certain logical condition is met. -When the time bomb or logic bomb is detonated, it may perform a denial of service such as crashing the system, deleting critical data, or degrading system response time. This bomb might be placed within either a replicating or non-replicating Trojan horse.",,unclassified,Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/ +When the time bomb or logic bomb is detonated, it may perform a denial of service such as crashing the system, deleting critical data, or degrading system response time. This bomb might be placed within either a replicating or non-replicating Trojan horse.",,unclassified,Mobile App Top 10 List: http://www.veracode.com/blog/2010/12/mobile-app-top-10-list/ CWE-512,EN-Spyware (Type: Base),"The software collects personally identifiable information about a human user or the user's activities, but the software accesses this information using other resources besides itself, and it does not require that user's explicit approval or direct input into the software. -""Spyware"" is a commonly used term with many definitions and interpretations. In general, it is meant to software that collects information or installs functionality that human users might not allow if they were fully aware of the actions being taken by the software. For example, a user might expect that tax software would collect a social security number and include it when filing a tax return, but that same user would not expect gaming software to obtain the social security number from that tax software's data.",,unclassified, +""Spyware"" is a commonly used term with many definitions and interpretations. In general, it is meant to software that collects information or installs functionality that human users might not allow if they were fully aware of the actions being taken by the software. For example, a user might expect that tax software would collect a social security number and include it when filing a tax return, but that same user would not expect gaming software to obtain the social security number from that tax software's data.",,unclassified, CWE-514,EN-Covert Channel (Type: Class),"A covert channel is a path that can be used to transfer information in a way not intended by the system's designers. -Typically the system has not given authorization for the transmission and has no knowledge of its occurrence.",,unclassified, +Typically the system has not given authorization for the transmission and has no knowledge of its occurrence.",,unclassified, CWE-516,EN-DEPRECATED (Duplicate): Covert Timing Channel (Type: Base),"This weakness can be found at CWE-385. -Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,unclassified, +Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,unclassified, CWE-52,EN-Path Equivalence: /multiple/trailing/slash// (Type: Variant),"A software system that accepts path input in the form of multiple trailing slash ('/multiple/trailing/slash//') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,unclassified, +Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,unclassified, CWE-520,EN-.NET Misconfiguration: Use of Impersonation (Type: Variant),"Allowing a .NET application to run at potentially escalated levels of access to the underlying operating and file systems can be dangerous and result in various forms of attacks. -Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,unclassified, +Covert storage channels occur when out-of-band data is stored in messages for the purpose of memory reuse. Covert channels are frequently classified as either storage or timing channels. Examples would include using a file intended to hold only audit information to convey user passwords--using the name of a file or perhaps status bits associated with it that can be read by all users to signal the contents of the file. Steganography, concealing information in such a manner that no one but the intended recipient knows of the existence of the message, is a good example of a covert storage channel.",,unclassified, CWE-521,EN-Weak Password Requirements (Type: Base),"The product does not require that users should have strong passwords, which makes it easier for attackers to compromise user accounts. -An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" +An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" CWE-522,EN-Insufficiently Protected Credentials (Type: Base),"This weakness occurs when the application transmits or stores authentication credentials and uses an insecure method that is susceptible to unauthorized interception and/or retrieval. -An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" +An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" CWE-523,EN-Unprotected Transport of Credentials (Type: Variant),"Login pages not using adequate measures to protect the user name and password while they are in transit from the client to the server. -An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, +An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, CWE-524,EN-Information Exposure Through Caching (Type: Variant),"The application uses a cache to maintain a pool of objects, threads, connections, pages, or passwords to minimize the time it takes to access them or the resources to which they connect. If implemented improperly, these caches can allow access to unauthorized information or cause a denial of service vulnerability. -An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, +An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, CWE-525,EN-Information Exposure Through Browser Caching (Type: Variant),"For each web page, the application should have an appropriate caching policy specifying the extent to which the page and its form fields should be cached. -An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, +An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, CWE-526,EN-Information Exposure Through Environmental Variables (Type: Variant),"Environmental variables may contain sensitive information about a remote server. -An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, +An authentication mechanism is only as strong as its credentials. For this reason, it is important to require users to have strong passwords. Lack of password complexity significantly reduces the search space when trying to guess user's passwords, making brute-force attacks easier.",,unclassified, CWE-527,EN-Exposure of CVS Repository to an Unauthorized Control Sphere (Type: Variant),"The product stores a CVS repository in a directory or other container that is accessible to actors outside of the intended control sphere. -Information contained within a CVS subdirectory on a web server or other server could be recovered by an attacker and used for malicious purposes. This information may include usernames, filenames, path root, and IP addresses.",,unclassified, +Information contained within a CVS subdirectory on a web server or other server could be recovered by an attacker and used for malicious purposes. This information may include usernames, filenames, path root, and IP addresses.",,unclassified, CWE-528,EN-Exposure of Core Dump File to an Unauthorized Control Sphere (Type: Variant),"The product generates a core dump file in a directory that is accessible to actors outside of the intended control sphere. -Information contained within a CVS subdirectory on a web server or other server could be recovered by an attacker and used for malicious purposes. This information may include usernames, filenames, path root, and IP addresses.",,unclassified, +Information contained within a CVS subdirectory on a web server or other server could be recovered by an attacker and used for malicious purposes. This information may include usernames, filenames, path root, and IP addresses.",,unclassified, CWE-529,EN-Exposure of Access Control List Files to an Unauthorized Control Sphere (Type: Variant),"The product stores access control list files in a directory or other container that is accessible to actors outside of the intended control sphere. -Exposure of these access control list files may give the attacker information about the configuration of the site or system. This information may then be used to bypass the intended security policy or identify trusted systems from which an attack can be launched.",,unclassified, +Exposure of these access control list files may give the attacker information about the configuration of the site or system. This information may then be used to bypass the intended security policy or identify trusted systems from which an attack can be launched.",,unclassified, CWE-53,EN-Path Equivalence: \multiple\\internal\backslash (Type: Variant),"A software system that accepts path input in the form of multiple internal backslash ('\multiple\trailing\\slash') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -Exposure of these access control list files may give the attacker information about the configuration of the site or system. This information may then be used to bypass the intended security policy or identify trusted systems from which an attack can be launched.",,unclassified, +Exposure of these access control list files may give the attacker information about the configuration of the site or system. This information may then be used to bypass the intended security policy or identify trusted systems from which an attack can be launched.",,unclassified, CWE-530,EN-Exposure of Backup File to an Unauthorized Control Sphere (Type: Variant),"A backup file is stored in a directory that is accessible to actors outside of the intended control sphere. -Often, old files are renamed with an extension such as .~bk to distinguish them from production files. The source code for old files that have been renamed in this manner and left in the webroot can often be retrieved. This renaming may have been performed automatically by the web server, or manually by the administrator.",,unclassified, +Often, old files are renamed with an extension such as .~bk to distinguish them from production files. The source code for old files that have been renamed in this manner and left in the webroot can often be retrieved. This renaming may have been performed automatically by the web server, or manually by the administrator.",,unclassified, CWE-531,EN-Information Exposure Through Test Code (Type: Variant),"Accessible test applications can pose a variety of security risks. Since developers or administrators rarely consider that someone besides themselves would even know about the existence of these applications, it is common for them to contain sensitive information or functions. -Often, old files are renamed with an extension such as .~bk to distinguish them from production files. The source code for old files that have been renamed in this manner and left in the webroot can often be retrieved. This renaming may have been performed automatically by the web server, or manually by the administrator.",,unclassified, +Often, old files are renamed with an extension such as .~bk to distinguish them from production files. The source code for old files that have been renamed in this manner and left in the webroot can often be retrieved. This renaming may have been performed automatically by the web server, or manually by the administrator.",,unclassified, CWE-533,EN-Information Exposure Through Server Log Files (Type: Variant),"A server.log file was found. This can give information on whatever application left the file. Usually this can give full path names and system information, and sometimes usernames and passwords. -While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, +While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, CWE-534,EN-Information Exposure Through Debug Log Files (Type: Variant),"The application does not sufficiently restrict access to a log file that is used for debugging. -While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, +While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, CWE-535,EN-Information Exposure Through Shell Error Message (Type: Variant),"A command shell error message indicates that there exists an unhandled exception in the web application code. In many cases, an attacker can leverage the conditions that cause these errors in order to gain unauthorized access to the system. -While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, +While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, CWE-536,EN-Information Exposure Through Servlet Runtime Error Message (Type: Variant),"A servlet error message indicates that there exists an unhandled exception in your web application code and may provide useful information to an attacker. -While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, +While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, CWE-537,EN-Information Exposure Through Java Runtime Error Message (Type: Variant),"In many cases, an attacker can leverage the conditions that cause unhandled exception errors in order to gain unauthorized access to the system. -While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, +While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified, CWE-538,EN-File and Directory Information Exposure (Type: Base),"The product stores sensitive information in files or directories that are accessible to actors outside of the intended control sphere. -While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 12: Information Leakage."" Page 191" +While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 12: Information Leakage."" Page 191" CWE-539,EN-Information Exposure Through Persistent Cookies (Type: Variant),"Persistent cookies are cookies that are stored on the browser's hard drive. This can cause security and privacy issues depending on the information stored in the cookie and how it is accessed. -Cookies are small bits of data that are sent by the web application but stored locally in the browser. This lets the application use the cookie to pass information between pages and store variable information. The web application controls what information is stored in a cookie and how it is used. Typical types of information stored in cookies are session Identifiers, personalization and customization information, and in rare cases even usernames to enable automated logins. There are two different types of cookies: session cookies and persistent cookies. Session cookies just live in the browser's memory, and are not stored anywhere, but persistent cookies are stored on the browser's hard drive.",,unclassified, +Cookies are small bits of data that are sent by the web application but stored locally in the browser. This lets the application use the cookie to pass information between pages and store variable information. The web application controls what information is stored in a cookie and how it is used. Typical types of information stored in cookies are session Identifiers, personalization and customization information, and in rare cases even usernames to enable automated logins. There are two different types of cookies: session cookies and persistent cookies. Session cookies just live in the browser's memory, and are not stored anywhere, but persistent cookies are stored on the browser's hard drive.",,unclassified, CWE-54,EN-Path Equivalence: filedir\ (Trailing Backslash) (Type: Variant),"A software system that accepts path input in the form of trailing backslash ('filedir\') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -Cookies are small bits of data that are sent by the web application but stored locally in the browser. This lets the application use the cookie to pass information between pages and store variable information. The web application controls what information is stored in a cookie and how it is used. Typical types of information stored in cookies are session Identifiers, personalization and customization information, and in rare cases even usernames to enable automated logins. There are two different types of cookies: session cookies and persistent cookies. Session cookies just live in the browser's memory, and are not stored anywhere, but persistent cookies are stored on the browser's hard drive.",,unclassified, +Cookies are small bits of data that are sent by the web application but stored locally in the browser. This lets the application use the cookie to pass information between pages and store variable information. The web application controls what information is stored in a cookie and how it is used. Typical types of information stored in cookies are session Identifiers, personalization and customization information, and in rare cases even usernames to enable automated logins. There are two different types of cookies: session cookies and persistent cookies. Session cookies just live in the browser's memory, and are not stored anywhere, but persistent cookies are stored on the browser's hard drive.",,unclassified, CWE-540,EN-Information Exposure Through Source Code (Type: Variant),"Source code on a web server often contains sensitive information and should generally not be accessible to users. -There are situations where it is critical to remove source code from an area or server. For example, obtaining Perl source code on a system allows an attacker to understand the logic of the script and extract extremely useful information such as code bugs or logins and passwords.",,unclassified, +There are situations where it is critical to remove source code from an area or server. For example, obtaining Perl source code on a system allows an attacker to understand the logic of the script and extract extremely useful information such as code bugs or logins and passwords.",,unclassified, CWE-541,EN-Information Exposure Through Include Source Code (Type: Variant),"If an include file source is accessible, the file can contain usernames and passwords, as well as sensitive information pertaining to the application and system. -There are situations where it is critical to remove source code from an area or server. For example, obtaining Perl source code on a system allows an attacker to understand the logic of the script and extract extremely useful information such as code bugs or logins and passwords.",,unclassified, +There are situations where it is critical to remove source code from an area or server. For example, obtaining Perl source code on a system allows an attacker to understand the logic of the script and extract extremely useful information such as code bugs or logins and passwords.",,unclassified, CWE-542,EN-Information Exposure Through Cleanup Log Files (Type: Variant),"The application does not properly protect or delete a log file related to cleanup. -There are situations where it is critical to remove source code from an area or server. For example, obtaining Perl source code on a system allows an attacker to understand the logic of the script and extract extremely useful information such as code bugs or logins and passwords.",,unclassified, +There are situations where it is critical to remove source code from an area or server. For example, obtaining Perl source code on a system allows an attacker to understand the logic of the script and extract extremely useful information such as code bugs or logins and passwords.",,unclassified, CWE-543,EN-Use of Singleton Pattern Without Synchronization in a Multithreaded Context (Type: Variant),"The software uses the singleton pattern when creating a resource within a multithreaded environment. -The use of a singleton pattern may not be thread-safe.",,unclassified,Thread-Specifc Storage for C/C++: http://www.cs.wustl.edu/~schmidt/PDF/TSS-pattern.pdf +The use of a singleton pattern may not be thread-safe.",,unclassified,Thread-Specifc Storage for C/C++: http://www.cs.wustl.edu/~schmidt/PDF/TSS-pattern.pdf CWE-544,EN-Missing Standardized Error Handling Mechanism (Type: Base),"The software does not use a standardized method for handling errors throughout the code, which might introduce inconsistent error handling and resultant weaknesses. -If the application handles error messages individually, on a one-by-one basis, this is likely to result in inconsistent error handling. The causes of errors may be lost. Also, detailed information about the causes of an error may be unintentionally returned to the user.",,unclassified, +If the application handles error messages individually, on a one-by-one basis, this is likely to result in inconsistent error handling. The causes of errors may be lost. Also, detailed information about the causes of an error may be unintentionally returned to the user.",,unclassified, CWE-545,EN-Use of Dynamic Class Loading (Type: Variant),"Dynamically loaded code has the potential to be malicious. -If the application handles error messages individually, on a one-by-one basis, this is likely to result in inconsistent error handling. The causes of errors may be lost. Also, detailed information about the causes of an error may be unintentionally returned to the user.",,unclassified, +If the application handles error messages individually, on a one-by-one basis, this is likely to result in inconsistent error handling. The causes of errors may be lost. Also, detailed information about the causes of an error may be unintentionally returned to the user.",,unclassified, CWE-546,EN-Suspicious Comment (Type: Variant),"The code contains comments that suggest the presence of bugs, incomplete functionality, or weaknesses. -Many suspicious comments, such as BUG, HACK, FIXME, LATER, LATER2, TODO, in the code indicate missing security functionality and checking. Others indicate code problems that programmers should fix, such as hard-coded variables, error handling, not using stored procedures, and performance issues.",,unclassified, +Many suspicious comments, such as BUG, HACK, FIXME, LATER, LATER2, TODO, in the code indicate missing security functionality and checking. Others indicate code problems that programmers should fix, such as hard-coded variables, error handling, not using stored procedures, and performance issues.",,unclassified, CWE-547,"EN-Use of Hard-coded, Security-relevant Constants (Type: Variant)","The program uses hard-coded constants instead of symbolic names for security-critical values, which increases the likelihood of mistakes during code maintenance or security policy change. -If the developer does not find all occurrences of the hard-coded constants, an incorrect policy decision may be made if one of the constants is not changed. Making changes to these values will require code changes that may be difficult or impossible once the system is released to the field. In addition, these hard-coded values may become available to attackers if the code is ever disclosed.",,unclassified, +If the developer does not find all occurrences of the hard-coded constants, an incorrect policy decision may be made if one of the constants is not changed. Making changes to these values will require code changes that may be difficult or impossible once the system is released to the field. In addition, these hard-coded values may become available to attackers if the code is ever disclosed.",,unclassified, CWE-548,EN-Information Exposure Through Directory Listing (Type: Variant),"A directory listing is inappropriately exposed, yielding potentially sensitive information to attackers. -A directory listing provides an attacker with the complete index of all the resources located inside of the directory. The specific risks and consequences vary depending on which files are listed and accessible.",,unclassified, +A directory listing provides an attacker with the complete index of all the resources located inside of the directory. The specific risks and consequences vary depending on which files are listed and accessible.",,unclassified, CWE-549,EN-Missing Password Field Masking (Type: Variant),"The software does not mask passwords during entry, increasing the potential for attackers to observe and capture passwords. -A directory listing provides an attacker with the complete index of all the resources located inside of the directory. The specific risks and consequences vary depending on which files are listed and accessible.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" +A directory listing provides an attacker with the complete index of all the resources located inside of the directory. The specific risks and consequences vary depending on which files are listed and accessible.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" CWE-55,EN-Path Equivalence: /./ (Single Dot Directory) (Type: Variant),"A software system that accepts path input in the form of single dot directory exploit ('/./') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -A directory listing provides an attacker with the complete index of all the resources located inside of the directory. The specific risks and consequences vary depending on which files are listed and accessible.",,unclassified, +A directory listing provides an attacker with the complete index of all the resources located inside of the directory. The specific risks and consequences vary depending on which files are listed and accessible.",,unclassified, CWE-550,EN-Information Exposure Through Server Error Message (Type: Variant),"Certain conditions, such as network failure, will cause a server error message to be displayed. -While error messages in and of themselves are not dangerous, per se, it is what an attacker can glean from them that might cause eventual problems.",,unclassified, +While error messages in and of themselves are not dangerous, per se, it is what an attacker can glean from them that might cause eventual problems.",,unclassified, CWE-551,EN-Incorrect Behavior Order: Authorization Before Parsing and Canonicalization (Type: Base),"If a web server does not fully parse requested URLs before it examines them for authorization, it may be possible for an attacker to bypass authorization protection. -For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, +For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, CWE-552,EN-Files or Directories Accessible to External Parties (Type: Base),"Files or directories are accessible in the environment that should not be. -For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, +For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, CWE-553,EN-Command Shell in Externally Accessible Directory (Type: Variant),"A possible shell file exists in /cgi-bin/ or other accessible directories. This is extremely dangerous and can be used by an attacker to execute commands on the web server. -For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, +For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, CWE-554,EN-ASP.NET Misconfiguration: Not Using Input Validation Framework (Type: Variant),"The ASP.NET application does not use an input validation framework. -For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, +For instance, the character strings /./ and / both mean current directory. If /SomeDirectory is a protected directory and an attacker requests /./SomeDirectory, the attacker may be able to gain access to the resource if /./ is not converted to / before the authorization check is performed.",,unclassified, CWE-555,EN-J2EE Misconfiguration: Plaintext Password in Configuration File (Type: Variant),"The J2EE application stores a plaintext password in a configuration file. -Storing a plaintext password in a configuration file allows anyone who can read the file to access the password-protected resource, making it an easy target for attackers.",,unclassified, +Storing a plaintext password in a configuration file allows anyone who can read the file to access the password-protected resource, making it an easy target for attackers.",,unclassified, CWE-556,EN-ASP.NET Misconfiguration: Use of Identity Impersonation (Type: Variant),"Configuring an ASP.NET application to run with impersonated credentials may give the application unnecessary privileges. -The use of impersonated credentials allows an ASP.NET application to run with either the privileges of the client on whose behalf it is executing or with arbitrary privileges granted in its configuration.",,unclassified, +The use of impersonated credentials allows an ASP.NET application to run with either the privileges of the client on whose behalf it is executing or with arbitrary privileges granted in its configuration.",,unclassified, CWE-558,EN-Use of getlogin() in Multithreaded Application (Type: Variant),"The application uses the getlogin() function in a multithreaded context, potentially causing it to return incorrect values. -The getlogin() function returns a pointer to a string that contains the name of the user associated with the calling process. The function is not reentrant, meaning that if it is called from another process, the contents are not locked out and the value of the string can be changed by another process. This makes it very risky to use because the username can be changed by other processes, so the results of the function cannot be trusted.",,unclassified, +The getlogin() function returns a pointer to a string that contains the name of the user associated with the calling process. The function is not reentrant, meaning that if it is called from another process, the contents are not locked out and the value of the string can be changed by another process. This makes it very risky to use because the username can be changed by other processes, so the results of the function cannot be trusted.",,unclassified, CWE-56,EN-Path Equivalence: filedir* (Wildcard) (Type: Variant),"A software system that accepts path input in the form of asterisk wildcard ('filedir*') without appropriate validation can lead to ambiguous path resolution and allow an attacker to traverse the file system to unintended locations or access arbitrary files. -The getlogin() function returns a pointer to a string that contains the name of the user associated with the calling process. The function is not reentrant, meaning that if it is called from another process, the contents are not locked out and the value of the string can be changed by another process. This makes it very risky to use because the username can be changed by other processes, so the results of the function cannot be trusted.",,unclassified, +The getlogin() function returns a pointer to a string that contains the name of the user associated with the calling process. The function is not reentrant, meaning that if it is called from another process, the contents are not locked out and the value of the string can be changed by another process. This makes it very risky to use because the username can be changed by other processes, so the results of the function cannot be trusted.",,unclassified, CWE-560,EN-Use of umask() with chmod-style Argument (Type: Variant),"The product calls umask() with an incorrect argument that is specified as if it is an argument to chmod(). -The getlogin() function returns a pointer to a string that contains the name of the user associated with the calling process. The function is not reentrant, meaning that if it is called from another process, the contents are not locked out and the value of the string can be changed by another process. This makes it very risky to use because the username can be changed by other processes, so the results of the function cannot be trusted.",,unclassified, +The getlogin() function returns a pointer to a string that contains the name of the user associated with the calling process. The function is not reentrant, meaning that if it is called from another process, the contents are not locked out and the value of the string can be changed by another process. This makes it very risky to use because the username can be changed by other processes, so the results of the function cannot be trusted.",,unclassified, CWE-561,EN-Dead Code (Type: Variant),"The software contains dead code, which can never be executed. -Dead code is source code that can never be executed in a running program. The surrounding code makes it impossible for a section of code to ever be executed.",,unclassified, +Dead code is source code that can never be executed in a running program. The surrounding code makes it impossible for a section of code to ever be executed.",,unclassified, CWE-562,EN-Return of Stack Variable Address (Type: Base),"A function returns the address of a stack variable, which will cause unintended program behavior, typically in the form of a crash. -Dead code is source code that can never be executed in a running program. The surrounding code makes it impossible for a section of code to ever be executed.",,unclassified, +Dead code is source code that can never be executed in a running program. The surrounding code makes it impossible for a section of code to ever be executed.",,unclassified, CWE-563,EN-Unused Variable (Type: Variant),"The variable's value is assigned but never used, making it a dead store. -It is likely that the variable is simply vestigial, but it is also possible that the unused variable points out a bug.",,unclassified, +It is likely that the variable is simply vestigial, but it is also possible that the unused variable points out a bug.",,unclassified, CWE-564,EN-SQL Injection: Hibernate (Type: Variant),"Using Hibernate to execute a dynamic SQL statement built with user-controlled input can allow an attacker to modify the statement's meaning or to execute arbitrary SQL commands. -It is likely that the variable is simply vestigial, but it is also possible that the unused variable points out a bug.",,unclassified, +It is likely that the variable is simply vestigial, but it is also possible that the unused variable points out a bug.",,unclassified, CWE-565,EN-Reliance on Cookies without Validation and Integrity Checking (Type: Base),"The application relies on the existence or values of cookies when performing security-critical operations, but it does not properly ensure that the setting is valid for the associated user. -Attackers can easily modify cookies, within the browser or by implementing the client-side code outside of the browser. Reliance on cookies without detailed validation and integrity checking can allow attackers to bypass authentication, conduct injection attacks such as SQL injection and cross-site scripting, or otherwise modify inputs in unexpected ways.",,unclassified, +Attackers can easily modify cookies, within the browser or by implementing the client-side code outside of the browser. Reliance on cookies without detailed validation and integrity checking can allow attackers to bypass authentication, conduct injection attacks such as SQL injection and cross-site scripting, or otherwise modify inputs in unexpected ways.",,unclassified, CWE-566,EN-Authorization Bypass Through User-Controlled SQL Primary Key (Type: Variant),"The software uses a database table that includes records that should not be accessible to an actor, but it executes a SQL statement with a primary key that can be controlled by that actor. When a user can set a primary key to any value, then the user can modify the key to point to unauthorized records. Database access control errors occur when: Data enters a program from an untrusted source. The data is used to specify the value of a primary key in a SQL query. -The untrusted source does not have the permissions to be able to access all rows in the associated table.",,unclassified, +The untrusted source does not have the permissions to be able to access all rows in the associated table.",,unclassified, CWE-567,EN-Unsynchronized Access to Shared Data in a Multithreaded Context (Type: Base),"The product does not properly synchronize shared data, such as static variables across threads, which can lead to undefined behavior and unpredictable data changes. Within servlets, shared static variables are not protected from concurrent access, but servlets are multithreaded. This is a typical programming mistake in J2EE applications, since the multithreading is handled by the framework. When a shared variable can be influenced by an attacker, one thread could wind up modifying the variable to contain data that is not valid for a different thread that is also using the data within the variable. -Note that this weakness is not unique to servlets.",,unclassified, +Note that this weakness is not unique to servlets.",,unclassified, CWE-568,EN-finalize() Method Without super.finalize() (Type: Variant),"The software contains a finalize() method that does not call super.finalize(). -The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, +The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, CWE-57,EN-Path Equivalence: fakedir/../realdir/filename (Type: Variant),"The software contains protection mechanisms to restrict access to 'realdir/filename', but it constructs pathnames using external input in the form of 'fakedir/../realdir/filename' that are not handled by those mechanisms. This allows attackers to perform unauthorized actions against the targeted file. -The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, +The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, CWE-570,EN-Expression is Always False (Type: Variant),"The software contains an expression that will always evaluate to false. -The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, +The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, CWE-571,EN-Expression is Always True (Type: Variant),"The software contains an expression that will always evaluate to true. -The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, +The Java Language Specification states that it is a good practice for a finalize() method to call super.finalize().",,unclassified, CWE-572,EN-Call to Thread run() instead of start() (Type: Variant),"The program calls a thread's run() method instead of calling start(), which causes the code to run in the thread of the caller instead of the callee. -In most cases a direct call to a Thread object's run() method is a bug. The programmer intended to begin a new thread of control, but accidentally called run() instead of start(), so the run() method will execute in the caller's thread of control.",,unclassified, +In most cases a direct call to a Thread object's run() method is a bug. The programmer intended to begin a new thread of control, but accidentally called run() instead of start(), so the run() method will execute in the caller's thread of control.",,unclassified, CWE-573,EN-Improper Following of Specification by Caller (Type: Class),"The software does not follow or incorrectly follows the specifications as required by the implementation language, environment, framework, protocol, or platform. -When leveraging external functionality, such as an API, it is important that the caller does so in accordance with the requirements of the external functionality or else unintended behaviors may result, possibly leaving the system vulnerable to any number of exploits.",,unclassified, +When leveraging external functionality, such as an API, it is important that the caller does so in accordance with the requirements of the external functionality or else unintended behaviors may result, possibly leaving the system vulnerable to any number of exploits.",,unclassified, CWE-574,EN-EJB Bad Practices: Use of Synchronization Primitives (Type: Variant),"The program violates the Enterprise JavaBeans (EJB) specification by using thread synchronization primitives. -The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not use thread synchronization primitives to synchronize execution of multiple instances."" The specification justifies this requirement in the following way: ""This rule is required to ensure consistent runtime semantics because while some EJB containers may use a single JVM to execute all enterprise bean's instances, others may distribute the instances across multiple JVMs.""",,unclassified, +The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not use thread synchronization primitives to synchronize execution of multiple instances."" The specification justifies this requirement in the following way: ""This rule is required to ensure consistent runtime semantics because while some EJB containers may use a single JVM to execute all enterprise bean's instances, others may distribute the instances across multiple JVMs.""",,unclassified, CWE-575,EN-EJB Bad Practices: Use of AWT Swing (Type: Variant),"The program violates the Enterprise JavaBeans (EJB) specification by using AWT/Swing. -The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not use the AWT functionality to attempt to output information to a display, or to input information from a keyboard."" The specification justifies this requirement in the following way: ""Most servers do not allow direct interaction between an application program and a keyboard/display attached to the server system.""",,unclassified, +The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not use the AWT functionality to attempt to output information to a display, or to input information from a keyboard."" The specification justifies this requirement in the following way: ""Most servers do not allow direct interaction between an application program and a keyboard/display attached to the server system.""",,unclassified, CWE-576,EN-EJB Bad Practices: Use of Java I/O (Type: Variant),"The program violates the Enterprise JavaBeans (EJB) specification by using the java.io package. -The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not use the java.io package to attempt to access files and directories in the file system."" The specification justifies this requirement in the following way: ""The file system APIs are not well-suited for business components to access data. Business components should use a resource manager API, such as JDBC, to store data.""",,unclassified, +The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not use the java.io package to attempt to access files and directories in the file system."" The specification justifies this requirement in the following way: ""The file system APIs are not well-suited for business components to access data. Business components should use a resource manager API, such as JDBC, to store data.""",,unclassified, CWE-577,EN-EJB Bad Practices: Use of Sockets (Type: Variant),"The program violates the Enterprise JavaBeans (EJB) specification by using sockets. -The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not attempt to listen on a socket, accept connections on a socket, or use a socket for multicast."" The specification justifies this requirement in the following way: ""The EJB architecture allows an enterprise bean instance to be a network socket client, but it does not allow it to be a network server. Allowing the instance to become a network server would conflict with the basic function of the enterprise bean-- to serve the EJB clients.""",,unclassified, +The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""An enterprise bean must not attempt to listen on a socket, accept connections on a socket, or use a socket for multicast."" The specification justifies this requirement in the following way: ""The EJB architecture allows an enterprise bean instance to be a network socket client, but it does not allow it to be a network server. Allowing the instance to become a network server would conflict with the basic function of the enterprise bean-- to serve the EJB clients.""",,unclassified, CWE-578,EN-EJB Bad Practices: Use of Class Loader (Type: Variant),"The program violates the Enterprise JavaBeans (EJB) specification by using the class loader. -The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""The enterprise bean must not attempt to create a class loader; obtain the current class loader; set the context class loader; set security manager; create a new security manager; stop the JVM; or change the input, output, and error streams."" The specification justifies this requirement in the following way: ""These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions could compromise security and decrease the container's ability to properly manage the runtime environment.""",,unclassified, +The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""The enterprise bean must not attempt to create a class loader; obtain the current class loader; set the context class loader; set security manager; create a new security manager; stop the JVM; or change the input, output, and error streams."" The specification justifies this requirement in the following way: ""These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions could compromise security and decrease the container's ability to properly manage the runtime environment.""",,unclassified, CWE-579,EN-J2EE Bad Practices: Non-serializable Object Stored in Session (Type: Variant),"The application stores a non-serializable object as an HttpSession attribute, which can hurt reliability. -The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""The enterprise bean must not attempt to create a class loader; obtain the current class loader; set the context class loader; set security manager; create a new security manager; stop the JVM; or change the input, output, and error streams."" The specification justifies this requirement in the following way: ""These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions could compromise security and decrease the container's ability to properly manage the runtime environment.""",,unclassified, +The Enterprise JavaBeans specification requires that every bean provider follow a set of programming guidelines designed to ensure that the bean will be portable and behave consistently in any EJB container. In this case, the program violates the following EJB guideline: ""The enterprise bean must not attempt to create a class loader; obtain the current class loader; set the context class loader; set security manager; create a new security manager; stop the JVM; or change the input, output, and error streams."" The specification justifies this requirement in the following way: ""These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions could compromise security and decrease the container's ability to properly manage the runtime environment.""",,unclassified, CWE-58,EN-Path Equivalence: Windows 8.3 Filename (Type: Variant),"The software contains a protection mechanism that restricts access to a long filename on a Windows operating system, but the software does not properly restrict access to the equivalent short ""8.3"" filename. On later Windows operating systems, a file can have a ""long name"" and a short name that is compatible with older Windows file systems, with up to 8 characters in the filename and 3 characters for the extension. These ""8.3"" filenames, therefore, act as an alternate name for files with long names, so they are useful pathname equivalence manipulations.",,unclassified,"Writing Secure Code -The Art of Software Security Assessment: Chapter 11, ""DOS 8.3 Filenames"", Page 673." +The Art of Software Security Assessment: Chapter 11, ""DOS 8.3 Filenames"", Page 673." CWE-580,EN-clone() Method Without super.clone() (Type: Variant),"The software contains a clone() method that does not call super.clone() to obtain the new object. -All implementations of clone() should obtain the new object by calling super.clone(). If a class does not follow this convention, a subclass's clone() method will return an object of the wrong type.",,unclassified, +All implementations of clone() should obtain the new object by calling super.clone(). If a class does not follow this convention, a subclass's clone() method will return an object of the wrong type.",,unclassified, CWE-581,EN-Object Model Violation: Just One of Equals and Hashcode Defined (Type: Base),"The software does not maintain equal hashcodes for equal objects. -Java objects are expected to obey a number of invariants related to equality. One of these invariants is that equal objects must have equal hashcodes. In other words, if a.equals(b) == true then a.hashCode() == b.hashCode().",,unclassified, +Java objects are expected to obey a number of invariants related to equality. One of these invariants is that equal objects must have equal hashcodes. In other words, if a.equals(b) == true then a.hashCode() == b.hashCode().",,unclassified, CWE-582,"EN-Array Declared Public, Final, and Static (Type: Variant)","The program declares an array public, final, and static, which is not sufficient to prevent the array's contents from being modified. -Because arrays are mutable objects, the final constraint requires that the array object itself be assigned only once, but makes no guarantees about the values of the array elements. Since the array is public, a malicious program can change the values stored in the array. As such, in most cases an array declared public, final and static is a bug.",,unclassified, +Because arrays are mutable objects, the final constraint requires that the array object itself be assigned only once, but makes no guarantees about the values of the array elements. Since the array is public, a malicious program can change the values stored in the array. As such, in most cases an array declared public, final and static is a bug.",,unclassified, CWE-583,EN-finalize() Method Declared Public (Type: Variant),"The program violates secure coding principles for mobile code by declaring a finalize() method public. -A program should never call finalize explicitly, except to call super.finalize() inside an implementation of finalize(). In mobile code situations, the otherwise error prone practice of manual garbage collection can become a security threat if an attacker can maliciously invoke one of your finalize() methods because it is declared with public access.",,unclassified, +A program should never call finalize explicitly, except to call super.finalize() inside an implementation of finalize(). In mobile code situations, the otherwise error prone practice of manual garbage collection can become a security threat if an attacker can maliciously invoke one of your finalize() methods because it is declared with public access.",,unclassified, CWE-584,EN-Return Inside Finally Block (Type: Base),"The code has a return statement inside a finally block, which will cause any thrown exception in the try block to be discarded. -A program should never call finalize explicitly, except to call super.finalize() inside an implementation of finalize(). In mobile code situations, the otherwise error prone practice of manual garbage collection can become a security threat if an attacker can maliciously invoke one of your finalize() methods because it is declared with public access.",,unclassified, +A program should never call finalize explicitly, except to call super.finalize() inside an implementation of finalize(). In mobile code situations, the otherwise error prone practice of manual garbage collection can become a security threat if an attacker can maliciously invoke one of your finalize() methods because it is declared with public access.",,unclassified, CWE-585,EN-Empty Synchronized Block (Type: Variant),"The software contains an empty synchronized block. -An empty synchronized block does not actually accomplish any synchronization and may indicate a troubled section of code. An empty synchronized block can occur because code no longer needed within the synchronized block is commented out without removing the synchronized block.",,unclassified,Intrinsic Locks and Synchronization (in Java): http://java.sun.com/docs/books/tutorial/essential/concurrency/locksync.html +An empty synchronized block does not actually accomplish any synchronization and may indicate a troubled section of code. An empty synchronized block can occur because code no longer needed within the synchronized block is commented out without removing the synchronized block.",,unclassified,Intrinsic Locks and Synchronization (in Java): http://java.sun.com/docs/books/tutorial/essential/concurrency/locksync.html CWE-586,EN-Explicit Call to Finalize() (Type: Variant),"The software makes an explicit call to the finalize() method from outside the finalizer. -While the Java Language Specification allows an object's finalize() method to be called from outside the finalizer, doing so is usually a bad idea. For example, calling finalize() explicitly means that finalize() will be called more than once: the first time will be the explicit call and the last time will be the call that is made after the object is garbage collected.",,unclassified, +While the Java Language Specification allows an object's finalize() method to be called from outside the finalizer, doing so is usually a bad idea. For example, calling finalize() explicitly means that finalize() will be called more than once: the first time will be the explicit call and the last time will be the call that is made after the object is garbage collected.",,unclassified, CWE-587,EN-Assignment of a Fixed Address to a Pointer (Type: Base),"The software sets a pointer to a specific address other than NULL or 0. -Using a fixed address is not portable because that address will probably not be valid in all environments or platforms.",,unclassified, +Using a fixed address is not portable because that address will probably not be valid in all environments or platforms.",,unclassified, CWE-588,EN-Attempt to Access Child of a Non-structure Pointer (Type: Variant),"Casting a non-structure type to a structure type and accessing a field can lead to memory access errors or data corruption. -Using a fixed address is not portable because that address will probably not be valid in all environments or platforms.",,unclassified, +Using a fixed address is not portable because that address will probably not be valid in all environments or platforms.",,unclassified, CWE-589,EN-Call to Non-ubiquitous API (Type: Variant),"The software uses an API function that does not exist on all versions of the target platform. This could cause portability problems or inconsistencies that allow denial of service or other consequences. -Some functions that offer security features supported by the OS are not available on all versions of the OS in common use. Likewise, functions are often deprecated or made obsolete for security reasons and should not be used.",,unclassified, +Some functions that offer security features supported by the OS are not available on all versions of the OS in common use. Likewise, functions are often deprecated or made obsolete for security reasons and should not be used.",,unclassified, CWE-590,EN-Free of Memory not on the Heap (Type: Variant),"The application calls free() on a pointer to memory that was not allocated using associated heap allocation functions such as malloc(), calloc(), or realloc(). -When free() is called on an invalid pointer, the program's memory management data structures may become corrupted. This corruption can cause the program to crash or, in some circumstances, an attacker may be able to cause free() to operate on controllable memory locations to modify critical program variables or execute code.",,unclassified,Valgrind: http://valgrind.org/ +When free() is called on an invalid pointer, the program's memory management data structures may become corrupted. This corruption can cause the program to crash or, in some circumstances, an attacker may be able to cause free() to operate on controllable memory locations to modify critical program variables or execute code.",,unclassified,Valgrind: http://valgrind.org/ CWE-591,EN-Sensitive Data Storage in Improperly Locked Memory (Type: Variant),"The application stores sensitive data in memory that is not locked, or that has been incorrectly locked, which might cause the memory to be written to swap files on disk by the virtual memory manager. This can make the data more accessible to external actors. -On Windows systems the VirtualLock function can lock a page of memory to ensure that it will remain present in memory and not be swapped to disk. However, on older versions of Windows, such as 95, 98, or Me, the VirtualLock() function is only a stub and provides no protection. On POSIX systems the mlock() call ensures that a page will stay resident in memory but does not guarantee that the page will not appear in the swap. Therefore, it is unsuitable for use as a protection mechanism for sensitive data. Some platforms, in particular Linux, do make the guarantee that the page will not be swapped, but this is non-standard and is not portable. Calls to mlock() also require supervisor privilege. Return values for both of these calls must be checked to ensure that the lock operation was actually successful.",,unclassified, +On Windows systems the VirtualLock function can lock a page of memory to ensure that it will remain present in memory and not be swapped to disk. However, on older versions of Windows, such as 95, 98, or Me, the VirtualLock() function is only a stub and provides no protection. On POSIX systems the mlock() call ensures that a page will stay resident in memory but does not guarantee that the page will not appear in the swap. Therefore, it is unsuitable for use as a protection mechanism for sensitive data. Some platforms, in particular Linux, do make the guarantee that the page will not be swapped, but this is non-standard and is not portable. Calls to mlock() also require supervisor privilege. Return values for both of these calls must be checked to ensure that the lock operation was actually successful.",,unclassified, CWE-592,EN-Authentication Bypass Issues (Type: Class),"The software does not properly perform authentication, allowing it to be bypassed through various methods. -On Windows systems the VirtualLock function can lock a page of memory to ensure that it will remain present in memory and not be swapped to disk. However, on older versions of Windows, such as 95, 98, or Me, the VirtualLock() function is only a stub and provides no protection. On POSIX systems the mlock() call ensures that a page will stay resident in memory but does not guarantee that the page will not appear in the swap. Therefore, it is unsuitable for use as a protection mechanism for sensitive data. Some platforms, in particular Linux, do make the guarantee that the page will not be swapped, but this is non-standard and is not portable. Calls to mlock() also require supervisor privilege. Return values for both of these calls must be checked to ensure that the lock operation was actually successful.",,unclassified,"The Art of Software Security Assessment: Chapter 2, ""Untrustworthy Credentials"", Page 37." +On Windows systems the VirtualLock function can lock a page of memory to ensure that it will remain present in memory and not be swapped to disk. However, on older versions of Windows, such as 95, 98, or Me, the VirtualLock() function is only a stub and provides no protection. On POSIX systems the mlock() call ensures that a page will stay resident in memory but does not guarantee that the page will not appear in the swap. Therefore, it is unsuitable for use as a protection mechanism for sensitive data. Some platforms, in particular Linux, do make the guarantee that the page will not be swapped, but this is non-standard and is not portable. Calls to mlock() also require supervisor privilege. Return values for both of these calls must be checked to ensure that the lock operation was actually successful.",,unclassified,"The Art of Software Security Assessment: Chapter 2, ""Untrustworthy Credentials"", Page 37." CWE-593,EN-Authentication Bypass: OpenSSL CTX Object Modified after SSL Objects are Created (Type: Variant),"The software modifies the SSL context after connection creation has begun. -If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, +If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, CWE-594,EN-J2EE Framework: Saving Unserializable Objects to Disk (Type: Variant),"When the J2EE container attempts to write unserializable objects to disk there is no guarantee that the process will complete successfully. -If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, +If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, CWE-595,EN-Comparison of Object References Instead of Object Contents (Type: Base),"The program compares object references instead of the contents of the objects themselves, preventing it from detecting equivalent objects. -If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, +If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, CWE-596,EN-Incorrect Semantic Object Comparison (Type: Base),"The software does not correctly compare two objects based on their conceptual content. -If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, +If the program modifies the SSL_CTX object after creating SSL objects from it, there is the possibility that older SSL objects created from the original context could all be affected by that change.",,unclassified, CWE-597,EN-Use of Wrong Operator in String Comparison (Type: Variant),"The product uses the wrong operator when comparing a string, such as using ""=="" when the equals() method should be used instead. -In Java, using == or != to compare two strings for equality actually compares two objects for equality, not their values. Chances are good that the two references will never be equal. While this weakness often only affects program correctness, if the equality is used for a security decision, it could be leveraged to affect program security.",,unclassified,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." +In Java, using == or != to compare two strings for equality actually compares two objects for equality, not their values. Chances are good that the two references will never be equal. While this weakness often only affects program correctness, if the equality is used for a security decision, it could be leveraged to affect program security.",,unclassified,"The Art of Software Security Assessment: Chapter 6, ""Typos"", Page 289." CWE-598,EN-Information Exposure Through Query Strings in GET Request (Type: Variant),"The web application uses the GET method to process requests that contain sensitive information, which can expose that information through the browser's history, Referers, web logs, and other sources. -In Java, using == or != to compare two strings for equality actually compares two objects for equality, not their values. Chances are good that the two references will never be equal. While this weakness often only affects program correctness, if the equality is used for a security decision, it could be leveraged to affect program security.",,unclassified, +In Java, using == or != to compare two strings for equality actually compares two objects for equality, not their values. Chances are good that the two references will never be equal. While this weakness often only affects program correctness, if the equality is used for a security decision, it could be leveraged to affect program security.",,unclassified, CWE-599,EN-Missing Validation of OpenSSL Certificate (Type: Variant),"The software uses OpenSSL and trusts or uses a certificate without using the SSL_get_verify_result() function to ensure that the certificate satisfies all necessary security requirements. -This could allow an attacker to use an invalid certificate to claim to be a trusted host, use expired certificates, or conduct other attacks that could be detected if the certificate is properly validated.",,unclassified, +This could allow an attacker to use an invalid certificate to claim to be a trusted host, use expired certificates, or conduct other attacks that could be detected if the certificate is properly validated.",,unclassified, CWE-6,EN-J2EE Misconfiguration: Insufficient Session-ID Length (Type: Variant),"The J2EE application is configured to use an insufficient session ID length. -If an attacker can guess or steal a session ID, then he/she may be able to take over the user's session (called session hijacking). The number of possible session IDs increases with increased session ID length, making it more difficult to guess or steal a session ID.",,unclassified,No description: http://www.securiteam.com/securityreviews/5TP0F0UEVQ.html +If an attacker can guess or steal a session ID, then he/she may be able to take over the user's session (called session hijacking). The number of possible session IDs increases with increased session ID length, making it more difficult to guess or steal a session ID.",,unclassified,No description: http://www.securiteam.com/securityreviews/5TP0F0UEVQ.html CWE-600,EN-Uncaught Exception in Servlet (Type: Base),"The Servlet does not catch all exceptions, which may reveal sensitive debugging information. -When a Servlet throws an exception, the default error response the Servlet container sends back to the user typically includes debugging information. This information is of great value to an attacker. For example, a stack trace might show the attacker a malformed SQL query string, the type of database being used, and the version of the application container. This information enables the attacker to target known vulnerabilities in these components.",,unclassified, +When a Servlet throws an exception, the default error response the Servlet container sends back to the user typically includes debugging information. This information is of great value to an attacker. For example, a stack trace might show the attacker a malformed SQL query string, the type of database being used, and the version of the application container. This information enables the attacker to target known vulnerabilities in these components.",,unclassified, CWE-603,EN-Use of Client-Side Authentication (Type: Base),"A client/server product performs authentication within client code but not in server code, allowing server-side authentication to be bypassed via a modified client that omits the authentication check. -Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified,"The Art of Software Security Assessment: Chapter 2, ""Untrustworthy Credentials"", Page 37." +Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified,"The Art of Software Security Assessment: Chapter 2, ""Untrustworthy Credentials"", Page 37." CWE-605,EN-Multiple Binds to the Same Port (Type: Base),"When multiple sockets are allowed to bind to the same port, other services on that port may be stolen or spoofed. -Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified, +Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified, CWE-606,EN-Unchecked Input for Loop Condition (Type: Base),"The product does not properly check inputs that are used for loop conditions, potentially leading to a denial of service because of excessive looping. -Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Looping Constructs"", Page 327." +Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Looping Constructs"", Page 327." CWE-607,EN-Public Static Final Field References Mutable Object (Type: Variant),"A public or protected static final field references a mutable object, which allows the object to be changed by malicious code, or accidentally from another package. -Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified, +Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified, CWE-608,EN-Struts: Non-private Field in ActionForm Class (Type: Variant),"An ActionForm class contains a field that has not been declared private, which can be accessed without using a setter or getter. -Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified, +Client-side authentication is extremely weak and may be breached easily. Any attacker may read the source code and reverse-engineer the authentication mechanism to access parts of the application which would otherwise be protected.",,unclassified, CWE-609,EN-Double-Checked Locking (Type: Base),"The program uses double-checked locking to access a resource without the overhead of explicit synchronization, but the locking is insufficient. Double-checked locking refers to the situation where a programmer checks to see if a resource has been initialized, grabs a lock, checks again to see if the resource has been initialized, and then performs the initialization if it has not occurred yet. This should not be done, as is not guaranteed to work in all languages and on all architectures. In summary, other threads may not be operating inside the synchronous block and are not guaranteed to see the operations execute in the same order as they would appear inside the synchronous block.",,unclassified,"The ""Double-Checked Locking is Broken"" Declaration: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html JSR 133 (Java Memory Model) FAQ: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl -The Art of Software Security Assessment: Chapter 13, ""Threading Vulnerabilities"", Page 815." +The Art of Software Security Assessment: Chapter 13, ""Threading Vulnerabilities"", Page 815." CWE-610,EN-Externally Controlled Reference to a Resource in Another Sphere (Type: Class),"The product uses an externally controlled name or reference that resolves to a resource that is outside of the intended control sphere. -",,unclassified, +",,unclassified, CWE-611,EN-Improper Restriction of XML External Entity Reference (XXE) (Type: Variant),"The software processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output. XML documents optionally contain a Document Type Definition (DTD), which, among other features, enables the definition of XML entities. It is possible to define an entity by providing a substitution string in the form of a URI. The XML parser can access the contents of this URI and embed these contents back into the XML document for further processing. By submitting an XML file that defines an external entity with a file:// URI, an attacker can cause the processing application to read the contents of a local file. For example, a URI such as ""file:///c:/winnt/win.ini"" designates (in Windows) the file C:\Winnt\win.ini, or file:///etc/passwd designates the password file in Unix-based systems. Using URIs with other schemes such as http://, the attacker can force the application to make outgoing requests to servers that the attacker cannot reach directly, which can be used to bypass firewall restrictions or hide the source of attacks such as port scanning. @@ -1667,199 +1667,199 @@ XML External Entity Attacks (XXE): https://www.owasp.org/images/5/5d/XML_Exteral XXE (Xml eXternal Entity) Attack: http://www.securiteam.com/securitynews/6D0100A5PU.html XML External Entities (XXE) Attack: http://projects.webappsec.org/w/page/13247003/XML%20External%20Entities XML Denial of Service Attacks and Defenses: http://msdn.microsoft.com/en-us/magazine/ee335713.aspx -Preventing XXE in PHP: http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html" +Preventing XXE in PHP: http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html" CWE-612,EN-Information Exposure Through Indexing of Private Data (Type: Variant),"The product performs an indexing routine against private documents, but does not sufficiently verify that the actors who can access the index also have the privileges to access the private documents. -When an indexing routine is applied against a group of private documents, and that index's results are available to outsiders who do not have access to those documents, then outsiders might be able to obtain sensitive information by conducting targeted searches. The risk is especially dangerous if search results include surrounding text that was not part of the search query. This issue can appear in search engines that are not configured (or implemented) to ignore critical files that should remain hidden; even without permissions to download these files directly, the remote user could read them.",,unclassified, +When an indexing routine is applied against a group of private documents, and that index's results are available to outsiders who do not have access to those documents, then outsiders might be able to obtain sensitive information by conducting targeted searches. The risk is especially dangerous if search results include surrounding text that was not part of the search query. This issue can appear in search engines that are not configured (or implemented) to ignore critical files that should remain hidden; even without permissions to download these files directly, the remote user could read them.",,unclassified, CWE-613,EN-Insufficient Session Expiration (Type: Base),"According to WASC, ""Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization."" -When an indexing routine is applied against a group of private documents, and that index's results are available to outsiders who do not have access to those documents, then outsiders might be able to obtain sensitive information by conducting targeted searches. The risk is especially dangerous if search results include surrounding text that was not part of the search query. This issue can appear in search engines that are not configured (or implemented) to ignore critical files that should remain hidden; even without permissions to download these files directly, the remote user could read them.",,unclassified, +When an indexing routine is applied against a group of private documents, and that index's results are available to outsiders who do not have access to those documents, then outsiders might be able to obtain sensitive information by conducting targeted searches. The risk is especially dangerous if search results include surrounding text that was not part of the search query. This issue can appear in search engines that are not configured (or implemented) to ignore critical files that should remain hidden; even without permissions to download these files directly, the remote user could read them.",,unclassified, CWE-614,EN-Sensitive Cookie in HTTPS Session Without Secure Attribute (Type: Variant),"The Secure attribute for sensitive cookies in HTTPS sessions is not set, which could cause the user agent to send those cookies in plaintext over an HTTP session. -When an indexing routine is applied against a group of private documents, and that index's results are available to outsiders who do not have access to those documents, then outsiders might be able to obtain sensitive information by conducting targeted searches. The risk is especially dangerous if search results include surrounding text that was not part of the search query. This issue can appear in search engines that are not configured (or implemented) to ignore critical files that should remain hidden; even without permissions to download these files directly, the remote user could read them.",,unclassified, +When an indexing routine is applied against a group of private documents, and that index's results are available to outsiders who do not have access to those documents, then outsiders might be able to obtain sensitive information by conducting targeted searches. The risk is especially dangerous if search results include surrounding text that was not part of the search query. This issue can appear in search engines that are not configured (or implemented) to ignore critical files that should remain hidden; even without permissions to download these files directly, the remote user could read them.",,unclassified, CWE-615,EN-Information Exposure Through Comments (Type: Variant),"While adding general comments is very useful, some programmers tend to leave important data, such as: filenames related to the web application, old links or links which were not meant to be browsed by users, old code fragments, etc. -An attacker who finds these comments can map the application's structure and files, expose hidden parts of the site, and study the fragments of code to reverse engineer the application, which may help develop further attacks against the site.",,unclassified, +An attacker who finds these comments can map the application's structure and files, expose hidden parts of the site, and study the fragments of code to reverse engineer the application, which may help develop further attacks against the site.",,unclassified, CWE-616,EN-Incomplete Identification of Uploaded File Variables (PHP) (Type: Variant),"The PHP application uses an old method for processing uploaded files by referencing the four global variables that are set for each file (e.g. $varname, $varname_size, $varname_name, $varname_type). These variables could be overwritten by attackers, causing the application to process unauthorized files. -These global variables could be overwritten by POST requests, cookies, or other methods of populating or overwriting these variables. This could be used to read or process arbitrary files by providing values such as ""/etc/passwd"".",,unclassified,"A Study in Scarlet - section 5, ""File Upload""" +These global variables could be overwritten by POST requests, cookies, or other methods of populating or overwriting these variables. This could be used to read or process arbitrary files by providing values such as ""/etc/passwd"".",,unclassified,"A Study in Scarlet - section 5, ""File Upload""" CWE-617,EN-Reachable Assertion (Type: Variant),"The product contains an assert() or similar statement that can be triggered by an attacker, which leads to an application exit or other behavior that is more severe than necessary. -For example, if a server handles multiple simultaneous connections, and an assert() occurs in one single connection that causes all other connections to be dropped, this is a reachable assertion that leads to a denial of service.",,unclassified, +For example, if a server handles multiple simultaneous connections, and an assert() occurs in one single connection that causes all other connections to be dropped, this is a reachable assertion that leads to a denial of service.",,unclassified, CWE-618,EN-Exposed Unsafe ActiveX Method (Type: Base),"An ActiveX control is intended for use in a web browser, but it exposes dangerous methods that perform actions that are outside of the browser's security model (e.g. the zone or domain). ActiveX controls can exercise far greater control over the operating system than typical Java or javascript. Exposed methods can be subject to various vulnerabilities, depending on the implemented behaviors of those methods, and whether input validation is performed on the provided arguments. If there is no integrity checking or origin validation, this method could be invoked by attackers.",,unclassified,"No description: http://msdn.microsoft.com/workshop/components/activex/safety.asp No description: http://msdn.microsoft.com/workshop/components/activex/security.asp -The Art of Software Security Assessment: Chapter 12, ""ActiveX Security"", Page 749." +The Art of Software Security Assessment: Chapter 12, ""ActiveX Security"", Page 749." CWE-619,EN-Dangling Database Cursor (Cursor Injection) (Type: Base),"If a database cursor is not closed properly, then it could become accessible to other users while retaining the same privileges that were originally assigned, leaving the cursor ""dangling."" For example, an improper dangling cursor could arise from unhandled exceptions. The impact of the issue depends on the cursor's role, but SQL injection attacks are commonly possible.",,unclassified,"The Oracle Hacker's Handbook -Cursor Injection: http://www.databasesecurity.com/dbsec/cursor-injection.pdf" +Cursor Injection: http://www.databasesecurity.com/dbsec/cursor-injection.pdf" CWE-62,EN-UNIX Hard Link (Type: Variant),"The software, when opening a file or directory, does not sufficiently account for when the name is associated with a hard link to a target that is outside of the intended control sphere. This could allow an attacker to cause the software to operate on unauthorized files. -Failure for a system to check for hard links can result in vulnerability to different types of attacks. For example, an attacker can escalate their privileges if a file used by a privileged program is replaced with a hard link to a sensitive file (e.g. /etc/passwd). When the process opens the file, the attacker can assume the privileges of that process.",,unclassified,"The Art of Software Security Assessment: Chapter 9, ""Hard Links"", Page 518." +Failure for a system to check for hard links can result in vulnerability to different types of attacks. For example, an attacker can escalate their privileges if a file used by a privileged program is replaced with a hard link to a sensitive file (e.g. /etc/passwd). When the process opens the file, the attacker can assume the privileges of that process.",,unclassified,"The Art of Software Security Assessment: Chapter 9, ""Hard Links"", Page 518." CWE-620,EN-Unverified Password Change (Type: Variant),"When setting a new password for a user, the product does not require knowledge of the original password, or using another form of authentication. -This could be used by an attacker to change passwords for another user, thus gaining the privileges associated with that user.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" +This could be used by an attacker to change passwords for another user, thus gaining the privileges associated with that user.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 19: Use of Weak Password-Based Systems."" Page 279" CWE-621,EN-Variable Extraction Error (Type: Base),"The product uses external input to determine the names of variables into which information is extracted, without verifying that the names of the specified variables are valid. This could cause the program to overwrite unintended variables. -For example, in PHP, calling extract() or import_request_variables() without the proper arguments could allow arbitrary global variables to be overwritten, including superglobals. Similar functionality might be possible in other interpreted languages, including custom languages.",,unclassified, +For example, in PHP, calling extract() or import_request_variables() without the proper arguments could allow arbitrary global variables to be overwritten, including superglobals. Similar functionality might be possible in other interpreted languages, including custom languages.",,unclassified, CWE-622,EN-Improper Validation of Function Hook Arguments (Type: Variant),"A product adds hooks to user-accessible API functions, but does not properly validate the arguments. This could lead to resultant vulnerabilities. -Such hooks can be used in defensive software that runs with privileges, such as anti-virus or firewall, which hooks kernel calls. When the arguments are not validated, they could be used to bypass the protection scheme or attack the product itself.",,unclassified, +Such hooks can be used in defensive software that runs with privileges, such as anti-virus or firewall, which hooks kernel calls. When the arguments are not validated, they could be used to bypass the protection scheme or attack the product itself.",,unclassified, CWE-623,EN-Unsafe ActiveX Control Marked Safe For Scripting (Type: Variant),"An ActiveX control is intended for restricted use, but it has been marked as safe-for-scripting. This might allow attackers to use dangerous functionality via a web page that accesses the control, which can lead to different resultant vulnerabilities, depending on the control's behavior.",,unclassified,"No description: http://msdn.microsoft.com/workshop/components/activex/safety.asp No description: http://msdn.microsoft.com/workshop/components/activex/security.asp No description: http://support.microsoft.com/kb/240797 Writing Secure Code: Chapter 16, ""What ActiveX Components Are Safe for Initialization and Safe for Scripting?"" Page 510 -The Art of Software Security Assessment: Chapter 12, ""ActiveX Security"", Page 749." +The Art of Software Security Assessment: Chapter 12, ""ActiveX Security"", Page 749." CWE-624,EN-Executable Regular Expression Error (Type: Base),"The product uses a regular expression that either (1) contains an executable component with user-controlled inputs, or (2) allows a user to enable execution by inserting pattern modifiers. -Case (2) is possible in the PHP preg_replace() function, and possibly in other languages when a user-controlled input is inserted into a string that is later parsed as a regular expression.",,unclassified, +Case (2) is possible in the PHP preg_replace() function, and possibly in other languages when a user-controlled input is inserted into a string that is later parsed as a regular expression.",,unclassified, CWE-625,EN-Permissive Regular Expression (Type: Base),"The product uses a regular expression that does not sufficiently restrict the set of allowed values. This effectively causes the regexp to accept substrings that match the pattern, which produces a partial comparison to the target. In some cases, this can lead to other weaknesses. Common errors include: not identifying the beginning and end of the target string using wildcards instead of acceptable character ranges -others",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Character Stripping Vulnerabilities"", Page 437." +others",,unclassified,"The Art of Software Security Assessment: Chapter 8, ""Character Stripping Vulnerabilities"", Page 437." CWE-626,EN-Null Byte Interaction Error (Poison Null Byte) (Type: Variant),"The product does not properly handle null bytes or NUL characters when passing data between different representations or components. A null byte (NUL character) can have different meanings across representations or languages. For example, it is a string terminator in standard C libraries, but Perl and PHP strings do not treat it as a terminator. When two representations are crossed - such as when Perl or PHP invokes underlying C functionality - this can produce an interaction error with unexpected results. Similar issues have been reported for ASP. Other interpreters written in C might also be affected.",,unclassified,"Poison NULL byte: http://insecure.org/news/P55-07.txt 0x00 vs ASP file upload scripts: http://www.security-assessment.com/Whitepapers/0x00_vs_ASP_File_Uploads.pdf -ShAnKaR: multiple PHP application poison NULL byte vulnerability: http://seclists.org/fulldisclosure/2006/Sep/0185.html" +ShAnKaR: multiple PHP application poison NULL byte vulnerability: http://seclists.org/fulldisclosure/2006/Sep/0185.html" CWE-627,EN-Dynamic Variable Evaluation (Type: Base),"In a language where the user can influence the name of a variable at runtime, if the variable names are not controlled, an attacker can read or write to arbitrary variables, or access arbitrary functions. The resultant vulnerabilities depend on the behavior of the application, both at the crossover point and in any control/data flow that is reachable by the related variables or functions.",,unclassified,"Dynamic Evaluation Vulnerabilities in PHP applications: http://seclists.org/fulldisclosure/2006/May/0035.html -A Study In Scarlet: Exploiting Common Vulnerabilities in PHP Applications: http://www.securereality.com.au/studyinscarlet.txt" +A Study In Scarlet: Exploiting Common Vulnerabilities in PHP Applications: http://www.securereality.com.au/studyinscarlet.txt" CWE-628,EN-Function Call with Incorrectly Specified Arguments (Type: Base),"The product calls a function, procedure, or routine with arguments that are not correctly specified, leading to always-incorrect behavior and resultant weaknesses. There are multiple ways in which this weakness can be introduced, including: the wrong variable or reference; an incorrect number of arguments; incorrect order of arguments; wrong type of arguments; or -wrong value.",,unclassified, +wrong value.",,unclassified, CWE-636,EN-Not Failing Securely (Failing Open) (Type: Class),"When the product encounters an error condition or failure, its design requires it to fall back to a state that is less secure than other options that are available, such as selecting the weakest encryption algorithm or using the most permissive access control restrictions. By entering a less secure state, the product inherits the weaknesses associated with that state, making it easier to compromise. At the least, it causes administrators to have a false sense of security. This weakness typically occurs as a result of wanting to ""fail functional"" to minimize administration and support costs, instead of ""failing safe.""",,unclassified,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ -Failing Securely: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/349.html" +Failing Securely: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/349.html" CWE-637,EN-Unnecessary Complexity in Protection Mechanism (Not Using Economy of Mechanism) (Type: Class),"The software uses a more complex mechanism than necessary, which could lead to resultant weaknesses when the mechanism is not correctly understood, modeled, configured, implemented, or used. Security mechanisms should be as simple as possible. Complex security mechanisms may engender partial implementations and compatibility problems, with resulting mismatches in assumptions and implemented security. A corollary of this principle is that data specifications should be as simple as possible, because complex data specifications result in complex validation code. Complex tasks and systems may also need to be guarded by complex security checks, so simple systems should be preferred.",,unclassified,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ -Economy of Mechanism: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/348.html" +Economy of Mechanism: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/348.html" CWE-638,EN-Not Using Complete Mediation (Type: Class),"The software does not perform access checks on a resource every time the resource is accessed by an entity, which can create resultant weaknesses if that entity's rights or privileges change over time. ",,unclassified,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ -Complete Mediation: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/346.html" +Complete Mediation: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/346.html" CWE-65,EN-Windows Hard Link (Type: Variant),"The software, when opening a file or directory, does not sufficiently handle when the name is associated with a hard link to a target that is outside of the intended control sphere. This could allow an attacker to cause the software to operate on unauthorized files. -Failure for a system to check for hard links can result in vulnerability to different types of attacks. For example, an attacker can escalate their privileges if a file used by a privileged program is replaced with a hard link to a sensitive file (e.g. AUTOEXEC.BAT). When the process opens the file, the attacker can assume the privileges of that process, or prevent the program from accurately processing data.",,unclassified,"The Art of Software Security Assessment: Chapter 11, ""Links"", Page 676." +Failure for a system to check for hard links can result in vulnerability to different types of attacks. For example, an attacker can escalate their privileges if a file used by a privileged program is replaced with a hard link to a sensitive file (e.g. AUTOEXEC.BAT). When the process opens the file, the attacker can assume the privileges of that process, or prevent the program from accurately processing data.",,unclassified,"The Art of Software Security Assessment: Chapter 11, ""Links"", Page 676." CWE-651,EN-Information Exposure Through WSDL File (Type: Variant),"The Web services architecture may require exposing a WSDL file that contains information on the publicly accessible services and how callers of these services should interact with them (e.g. what parameters they expect and what types they return). An information exposure may occur if any of the following apply: The WSDL file is accessible to a wider audience than intended. The WSDL file contains information on the methods/services that should not be publicly accessible or information about deprecated methods. This problem is made more likely due to the WSDL often being automatically generated from the code. -Information in the WSDL file helps guess names/locations of methods/resources that should not be publicly accessible.",,unclassified, +Information in the WSDL file helps guess names/locations of methods/resources that should not be publicly accessible.",,unclassified, CWE-653,EN-Insufficient Compartmentalization (Type: Base),"The product does not sufficiently compartmentalize functionality or processes that require different privilege levels, rights, or permissions. When a weakness occurs in functionality that is accessible by lower-privileged users, then without strong boundaries, an attack might extend the scope of the damage to higher-privileged users.",,unclassified,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ -Separation of Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/357.html" +Separation of Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/357.html" CWE-654,EN-Reliance on a Single Factor in a Security Decision (Type: Base),"A protection mechanism relies exclusively, or to a large extent, on the evaluation of a single condition or the integrity of a single object or entity in order to make a decision about granting access to restricted resources or functionality. When a weakness occurs in functionality that is accessible by lower-privileged users, then without strong boundaries, an attack might extend the scope of the damage to higher-privileged users.",,unclassified,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ -Separation of Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/357.html" +Separation of Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/357.html" CWE-655,EN-Insufficient Psychological Acceptability (Type: Base),"The software has a protection mechanism that is too difficult or inconvenient to use, encouraging non-malicious users to disable or bypass the mechanism, whether by accident or on purpose. When a weakness occurs in functionality that is accessible by lower-privileged users, then without strong boundaries, an attack might extend the scope of the damage to higher-privileged users.",,unclassified,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ Psychological Acceptability: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/354.html Usability of Security: A Case Study: http://reports-archive.adm.cs.cmu.edu/anon/1998/CMU-CS-98-155.pdf -24 Deadly Sins of Software Security: ""Sin 14: Poor Usability."" Page 217" +24 Deadly Sins of Software Security: ""Sin 14: Poor Usability."" Page 217" CWE-656,EN-Reliance on Security Through Obscurity (Type: Base),"The software uses a protection mechanism whose strength depends heavily on its obscurity, such that knowledge of its algorithms or key data is sufficient to defeat the mechanism. This reliance on ""security through obscurity"" can produce resultant weaknesses if an attacker is able to reverse engineer the inner workings of the mechanism. Note that obscurity can be one small part of defense in depth, since it can create more work for an attacker; however, it is a significant risk if used as the primary means of protection.",,unclassified,"RFC: 793, TRANSMISSION CONTROL PROTOCOL: http://www.ietf.org/rfc/rfc0793.txt The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ -Never Assuming that Your Secrets Are Safe: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/352.html" +Never Assuming that Your Secrets Are Safe: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/352.html" CWE-657,EN-Violation of Secure Design Principles (Type: Class),"The product violates well-established principles for secure design. This can introduce resultant weaknesses or make it easier for developers to introduce related weaknesses during implementation. Because code is centered around design, it can be resource-intensive to fix design problems.",,unclassified,"The Protection of Information in Computer Systems: http://web.mit.edu/Saltzer/www/publications/protection/ -Design Principles: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/358.html" +Design Principles: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/358.html" CWE-66,EN-Improper Handling of File Names that Identify Virtual Resources (Type: Base),"The product does not handle or incorrectly handles a file name that identifies a ""virtual"" resource that is not directly specified within the directory that is associated with the file name, causing the product to perform file-based operations on a resource that is not a file. -Virtual file names are represented like normal file names, but they are effectively aliases for other resources that do not behave like normal files. Depending on their functionality, they could be alternate entities. They are not necessarily listed in directories.",,unclassified, +Virtual file names are represented like normal file names, but they are effectively aliases for other resources that do not behave like normal files. Depending on their functionality, they could be alternate entities. They are not necessarily listed in directories.",,unclassified, CWE-662,EN-Improper Synchronization (Type: Base),"The software attempts to use a shared resource in an exclusive manner, but does not prevent or incorrectly prevents use of the resource by another thread or process. -Virtual file names are represented like normal file names, but they are effectively aliases for other resources that do not behave like normal files. Depending on their functionality, they could be alternate entities. They are not necessarily listed in directories.",,unclassified, +Virtual file names are represented like normal file names, but they are effectively aliases for other resources that do not behave like normal files. Depending on their functionality, they could be alternate entities. They are not necessarily listed in directories.",,unclassified, CWE-663,EN-Use of a Non-reentrant Function in a Concurrent Context (Type: Base),"The software calls a non-reentrant function in a concurrent context in which a competing code sequence (e.g. thread or signal handler) may have an opportunity to call the same function or otherwise influence its state. Virtual file names are represented like normal file names, but they are effectively aliases for other resources that do not behave like normal files. Depending on their functionality, they could be alternate entities. They are not necessarily listed in directories.",,unclassified,"Java Concurrency API: http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/locks/ReentrantLock.html -Use reentrant functions for safer signal handling: http://www.ibm.com/developerworks/linux/library/l-reent.html" +Use reentrant functions for safer signal handling: http://www.ibm.com/developerworks/linux/library/l-reent.html" CWE-664,EN-Improper Control of a Resource Through its Lifetime (Type: Class),"The software does not maintain or incorrectly maintains control over a resource throughout its lifetime of creation, use, and release. Resources often have explicit instructions on how to be created, used and destroyed. When software does not follow these instructions, it can lead to unexpected behaviors and potentially exploitable states. -Even without explicit instructions, various principles are expected to be adhered to, such as ""Do not use an object until after its creation is complete,"" or ""do not use an object after it has been slated for destruction.""",,unclassified, +Even without explicit instructions, various principles are expected to be adhered to, such as ""Do not use an object until after its creation is complete,"" or ""do not use an object after it has been slated for destruction.""",,unclassified, CWE-666,EN-Operation on Resource in Wrong Phase of Lifetime (Type: Base),"The software performs an operation on a resource at the wrong phase of the resource's lifecycle, which can lead to unexpected behaviors. -When a developer wants to initialize, use or release a resource, it is important to follow the specifications outlined for how to operate on that resource and to ensure that the resource is in the expected state. In this case, the software wants to perform a normally valid operation, initialization, use or release, on a resource when it is in the incorrect phase of its lifetime.",,unclassified, +When a developer wants to initialize, use or release a resource, it is important to follow the specifications outlined for how to operate on that resource and to ensure that the resource is in the expected state. In this case, the software wants to perform a normally valid operation, initialization, use or release, on a resource when it is in the incorrect phase of its lifetime.",,unclassified, CWE-667,EN-Improper Locking (Type: Base),"The software does not properly acquire a lock on a resource, or it does not properly release a lock on a resource, leading to unexpected resource state changes and behaviors. -When a developer wants to initialize, use or release a resource, it is important to follow the specifications outlined for how to operate on that resource and to ensure that the resource is in the expected state. In this case, the software wants to perform a normally valid operation, initialization, use or release, on a resource when it is in the incorrect phase of its lifetime.",,unclassified, +When a developer wants to initialize, use or release a resource, it is important to follow the specifications outlined for how to operate on that resource and to ensure that the resource is in the expected state. In this case, the software wants to perform a normally valid operation, initialization, use or release, on a resource when it is in the incorrect phase of its lifetime.",,unclassified, CWE-668,EN-Exposure of Resource to Wrong Sphere (Type: Class),"The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource. Resources such as files and directories may be inadvertently exposed through mechanisms such as insecure permissions, or when a program accidentally operates on the wrong object. For example, a program may intend that private files can only be provided to a specific user. This effectively defines a control sphere that is intended to prevent attackers from accessing these private files. If the file permissions are insecure, then parties other than the user will be able to access those files. A separate control sphere might effectively require that the user can only access the private files, but not any other files on the system. If the program does not ensure that the user is only requesting private files, then the user might be able to access other files on the system. -In either case, the end result is that a resource has been exposed to the wrong party.",,unclassified, +In either case, the end result is that a resource has been exposed to the wrong party.",,unclassified, CWE-669,EN-Incorrect Resource Transfer Between Spheres (Type: Class),"The product does not properly transfer a resource/behavior to another sphere, or improperly imports a resource/behavior from another sphere, in a manner that provides unintended control over that resource. Resources such as files and directories may be inadvertently exposed through mechanisms such as insecure permissions, or when a program accidentally operates on the wrong object. For example, a program may intend that private files can only be provided to a specific user. This effectively defines a control sphere that is intended to prevent attackers from accessing these private files. If the file permissions are insecure, then parties other than the user will be able to access those files. A separate control sphere might effectively require that the user can only access the private files, but not any other files on the system. If the program does not ensure that the user is only requesting private files, then the user might be able to access other files on the system. -In either case, the end result is that a resource has been exposed to the wrong party.",,unclassified, +In either case, the end result is that a resource has been exposed to the wrong party.",,unclassified, CWE-670,EN-Always-Incorrect Control Flow Implementation (Type: Class),"The code contains a control flow path that does not reflect the algorithm that the path is intended to implement, leading to incorrect behavior any time this path is navigated. -This weakness captures cases in which a particular code segment is always incorrect with respect to the algorithm that it is implementing. For example, if a C programmer intends to include multiple statements in a single block but does not include the enclosing braces (CWE-483), then the logic is always incorrect. This issue is in contrast to most weaknesses in which the code usually behaves correctly, except when it is externally manipulated in malicious ways.",,unclassified, +This weakness captures cases in which a particular code segment is always incorrect with respect to the algorithm that it is implementing. For example, if a C programmer intends to include multiple statements in a single block but does not include the enclosing braces (CWE-483), then the logic is always incorrect. This issue is in contrast to most weaknesses in which the code usually behaves correctly, except when it is externally manipulated in malicious ways.",,unclassified, CWE-671,EN-Lack of Administrator Control over Security (Type: Class),"The product uses security features in a way that prevents the product's administrator from tailoring security settings to reflect the environment in which the product is being used. This introduces resultant weaknesses or prevents it from operating at a level of security that is desired by the administrator. -If the product's administrator does not have the ability to manage security-related decisions at all times, then protecting the product from outside threats - including the product's developer - can become impossible. For example, a hard-coded account name and password cannot be changed by the administrator, thus exposing that product to attacks that the administrator can not prevent.",,unclassified, +If the product's administrator does not have the ability to manage security-related decisions at all times, then protecting the product from outside threats - including the product's developer - can become impossible. For example, a hard-coded account name and password cannot be changed by the administrator, thus exposing that product to attacks that the administrator can not prevent.",,unclassified, CWE-672,EN-Operation on a Resource after Expiration or Release (Type: Base),"The software uses, accesses, or otherwise operates on a resource after that resource has been expired, released, or revoked. -If the product's administrator does not have the ability to manage security-related decisions at all times, then protecting the product from outside threats - including the product's developer - can become impossible. For example, a hard-coded account name and password cannot be changed by the administrator, thus exposing that product to attacks that the administrator can not prevent.",,unclassified, +If the product's administrator does not have the ability to manage security-related decisions at all times, then protecting the product from outside threats - including the product's developer - can become impossible. For example, a hard-coded account name and password cannot be changed by the administrator, thus exposing that product to attacks that the administrator can not prevent.",,unclassified, CWE-673,EN-External Influence of Sphere Definition (Type: Class),"The product does not prevent the definition of control spheres from external actors. -Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,unclassified, +Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,unclassified, CWE-674,EN-Uncontrolled Recursion (Type: Base),"The product does not properly control the amount of recursion that takes place, which consumes excessive resources, such as allocated memory or the program stack. -Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,unclassified, +Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,unclassified, CWE-675,EN-Duplicate Operations on Resource (Type: Class),"The product performs the same operation on a resource two or more times, when the operation should only be applied once. -Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,unclassified, +Typically, a product defines its control sphere within the code itself, or through configuration by the product's administrator. In some cases, an external party can change the definition of the control sphere. This is typically a resultant weakness.",,unclassified, CWE-683,EN-Function Call With Incorrect Order of Arguments (Type: Variant),"The software calls a function, procedure, or routine, but the caller specifies the arguments in an incorrect order, leading to resultant weaknesses. -While this weakness might be caught by the compiler in some languages, it can occur more frequently in cases in which the called function accepts variable numbers or types of arguments, such as format strings in C. It also can occur in languages or environments that do not enforce strong typing.",,unclassified, +While this weakness might be caught by the compiler in some languages, it can occur more frequently in cases in which the called function accepts variable numbers or types of arguments, such as format strings in C. It also can occur in languages or environments that do not enforce strong typing.",,unclassified, CWE-684,EN-Incorrect Provision of Specified Functionality (Type: Base),"The code does not function according to its published specifications, potentially leading to incorrect usage. -When providing functionality to an external party, it is important that the software behaves in accordance with the details specified. When requirements of nuances are not documented, the functionality may produce unintended behaviors for the caller, possibly leading to an exploitable state.",,unclassified, +When providing functionality to an external party, it is important that the software behaves in accordance with the details specified. When requirements of nuances are not documented, the functionality may produce unintended behaviors for the caller, possibly leading to an exploitable state.",,unclassified, CWE-685,EN-Function Call With Incorrect Number of Arguments (Type: Variant),"The software calls a function, procedure, or routine, but the caller specifies too many arguments, or too few arguments, which may lead to undefined behavior and resultant weaknesses. -When providing functionality to an external party, it is important that the software behaves in accordance with the details specified. When requirements of nuances are not documented, the functionality may produce unintended behaviors for the caller, possibly leading to an exploitable state.",,unclassified, +When providing functionality to an external party, it is important that the software behaves in accordance with the details specified. When requirements of nuances are not documented, the functionality may produce unintended behaviors for the caller, possibly leading to an exploitable state.",,unclassified, CWE-686,EN-Function Call With Incorrect Argument Type (Type: Variant),"The software calls a function, procedure, or routine, but the caller specifies an argument that is the wrong data type, which may lead to resultant weaknesses. -This weakness is most likely to occur in loosely typed languages, or in strongly typed languages in which the types of variable arguments cannot be enforced at compilation time, or where there is implicit casting.",,unclassified, +This weakness is most likely to occur in loosely typed languages, or in strongly typed languages in which the types of variable arguments cannot be enforced at compilation time, or where there is implicit casting.",,unclassified, CWE-687,EN-Function Call With Incorrectly Specified Argument Value (Type: Variant),"The software calls a function, procedure, or routine, but the caller specifies an argument that contains the wrong value, which may lead to resultant weaknesses. -This weakness is most likely to occur in loosely typed languages, or in strongly typed languages in which the types of variable arguments cannot be enforced at compilation time, or where there is implicit casting.",,unclassified, +This weakness is most likely to occur in loosely typed languages, or in strongly typed languages in which the types of variable arguments cannot be enforced at compilation time, or where there is implicit casting.",,unclassified, CWE-688,EN-Function Call With Incorrect Variable or Reference as Argument (Type: Variant),"The software calls a function, procedure, or routine, but the caller specifies the wrong variable or reference as one of the arguments, which may lead to undefined behavior and resultant weaknesses. -This weakness is most likely to occur in loosely typed languages, or in strongly typed languages in which the types of variable arguments cannot be enforced at compilation time, or where there is implicit casting.",,unclassified, +This weakness is most likely to occur in loosely typed languages, or in strongly typed languages in which the types of variable arguments cannot be enforced at compilation time, or where there is implicit casting.",,unclassified, CWE-69,EN-Improper Handling of Windows ::DATA Alternate Data Stream (Type: Variant),"The software does not properly prevent access to, or detect usage of, alternate data streams (ADS). An attacker can use an ADS to hide information about a file (e.g. size, the name of the process) from a system or file browser tools such as Windows Explorer and 'dir' at the command line utility. Alternately, the attacker might be able to bypass intended access restrictions for the associated data fork.",,unclassified,"Windows NTFS Alternate Data Streams: http://www.securityfocus.com/infocus/1822 -Writing Secure Code" +Writing Secure Code" CWE-691,EN-Insufficient Control Flow Management (Type: Class),"The code does not sufficiently manage its control flow during execution, creating conditions in which the control flow can be modified in unexpected ways. -An attacker can use an ADS to hide information about a file (e.g. size, the name of the process) from a system or file browser tools such as Windows Explorer and 'dir' at the command line utility. Alternately, the attacker might be able to bypass intended access restrictions for the associated data fork.",,unclassified, +An attacker can use an ADS to hide information about a file (e.g. size, the name of the process) from a system or file browser tools such as Windows Explorer and 'dir' at the command line utility. Alternately, the attacker might be able to bypass intended access restrictions for the associated data fork.",,unclassified, CWE-693,EN-Protection Mechanism Failure (Type: Class),"The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product. -This weakness covers three distinct situations. A ""missing"" protection mechanism occurs when the application does not define any mechanism against a certain class of attack. An ""insufficient"" protection mechanism might provide some defenses - for example, against the most common attacks - but it does not protect against everything that is intended. Finally, an ""ignored"" mechanism occurs when a mechanism is available and in active use within the product, but the developer has not applied it in some code path.",,unclassified, +This weakness covers three distinct situations. A ""missing"" protection mechanism occurs when the application does not define any mechanism against a certain class of attack. An ""insufficient"" protection mechanism might provide some defenses - for example, against the most common attacks - but it does not protect against everything that is intended. Finally, an ""ignored"" mechanism occurs when a mechanism is available and in active use within the product, but the developer has not applied it in some code path.",,unclassified, CWE-694,EN-Use of Multiple Resources with Duplicate Identifier (Type: Base),"The software uses multiple resources that can have the same identifier, in a context in which unique identifiers are required. -If the software assumes that each resource has a unique identifier, the software could operate on the wrong resource if attackers can cause multiple resources to be associated with the same identifier.",,unclassified, +If the software assumes that each resource has a unique identifier, the software could operate on the wrong resource if attackers can cause multiple resources to be associated with the same identifier.",,unclassified, CWE-695,EN-Use of Low-Level Functionality (Type: Base),"The software uses low-level functionality that is explicitly prohibited by the framework or specification under which the software is supposed to operate. -The use of low-level functionality can violate the specification in unexpected ways that effectively disable built-in protection mechanisms, introduce exploitable inconsistencies, or otherwise expose the functionality to attack.",,unclassified, +The use of low-level functionality can violate the specification in unexpected ways that effectively disable built-in protection mechanisms, introduce exploitable inconsistencies, or otherwise expose the functionality to attack.",,unclassified, CWE-696,EN-Incorrect Behavior Order (Type: Class),"The software performs multiple related behaviors, but the behaviors are performed in the wrong order in ways which may produce resultant weaknesses. -The use of low-level functionality can violate the specification in unexpected ways that effectively disable built-in protection mechanisms, introduce exploitable inconsistencies, or otherwise expose the functionality to attack.",,unclassified, +The use of low-level functionality can violate the specification in unexpected ways that effectively disable built-in protection mechanisms, introduce exploitable inconsistencies, or otherwise expose the functionality to attack.",,unclassified, CWE-697,EN-Insufficient Comparison (Type: Class),"The software compares two entities in a security-relevant context, but the comparison is insufficient, which may lead to resultant weaknesses. This weakness class covers several possibilities: the comparison checks one factor incorrectly; -the comparison should consider multiple factors, but it does not check some of those factors at all.",,unclassified, +the comparison should consider multiple factors, but it does not check some of those factors at all.",,unclassified, CWE-698,EN-Execution After Redirect (EAR) (Type: Base),"The web application sends a redirect to another location, but instead of exiting, it executes additional code. This weakness class covers several possibilities: the comparison checks one factor incorrectly; -the comparison should consider multiple factors, but it does not check some of those factors at all.",,unclassified,Fear the EAR: Discovering and Mitigating Execution After Redirect Vulnerabilities: http://cs.ucsb.edu/~bboe/public/pubs/fear-the-ear-ccs2011.pdf +the comparison should consider multiple factors, but it does not check some of those factors at all.",,unclassified,Fear the EAR: Discovering and Mitigating Execution After Redirect Vulnerabilities: http://cs.ucsb.edu/~bboe/public/pubs/fear-the-ear-ccs2011.pdf CWE-7,EN-J2EE Misconfiguration: Missing Custom Error Page (Type: Variant),"The default error page of a web application should not display sensitive information about the software system. -A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified,19 Deadly Sins of Software Security +A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified,19 Deadly Sins of Software Security CWE-703,EN-Improper Check or Handling of Exceptional Conditions (Type: Class),"The software does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the software. A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified,"A Taxonomy of Security Faults in the UNIX Operating System: http://ftp.cerias.purdue.edu/pub/papers/taimur-aslam/aslam-taxonomy-msthesis.pdf Use of A Taxonomy of Security Faults: http://csrc.nist.gov/nissc/1996/papers/NISSC96/paper057/PAPER.PDF -24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143" +24 Deadly Sins of Software Security: ""Sin 8: C++ Catastrophes."" Page 143" CWE-704,EN-Incorrect Type Conversion or Cast (Type: Class),"The software does not correctly convert an object, resource or structure from one type to a different type. -A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified, +A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified, CWE-705,EN-Incorrect Control Flow Scoping (Type: Class),"The software does not properly return control flow to the proper location after it has completed a task or detected an unusual condition. -A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified, +A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified, CWE-706,EN-Use of Incorrectly-Resolved Name or Reference (Type: Class),"The software uses a name or reference to access a resource, but the name/reference resolves to a resource that is outside of the intended control sphere. -A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified, +A Web application must define a default error page for 4xx errors (e.g. 404), 5xx (e.g. 500) errors and catch java.lang.Throwable exceptions to prevent attackers from mining information from the application container's built-in error response.",,unclassified, CWE-707,EN-Improper Enforcement of Message or Data Structure (Type: Class),"The software does not enforce or incorrectly enforces that structured messages or data are well-formed before being read from an upstream component or sent to a downstream component. If a message is malformed it may cause the message to be incorrectly interpreted. -This weakness typically applies in cases where the product prepares a control message that another process must act on, such as a command or query, and malicious input that was intended as data, can enter the control plane instead. However, this weakness also applies to more general cases where there are not always control implications.",,unclassified, +This weakness typically applies in cases where the product prepares a control message that another process must act on, such as a command or query, and malicious input that was intended as data, can enter the control plane instead. However, this weakness also applies to more general cases where there are not always control implications.",,unclassified, CWE-708,EN-Incorrect Ownership Assignment (Type: Base),"The software assigns an owner to a resource, but the owner is outside of the intended control sphere. -This may allow the resource to be manipulated by actors outside of the intended control sphere.",,unclassified, +This may allow the resource to be manipulated by actors outside of the intended control sphere.",,unclassified, CWE-71,EN-Apple .DS_Store (Type: Variant),"Software operating in a MAC OS environment, where .DS_Store is in effect, must carefully manage hard links, otherwise an attacker may be able to leverage a hard link from .DS_Store to overwrite arbitrary files and gain privileges. -This may allow the resource to be manipulated by actors outside of the intended control sphere.",,unclassified, +This may allow the resource to be manipulated by actors outside of the intended control sphere.",,unclassified, CWE-710,EN-Coding Standards Violation (Type: Class),"The software does not follow certain coding rules for development, which can lead to resultant weaknesses or increase the severity of the associated vulnerabilities. -This may allow the resource to be manipulated by actors outside of the intended control sphere.",,unclassified, +This may allow the resource to be manipulated by actors outside of the intended control sphere.",,unclassified, CWE-72,EN-Improper Handling of Apple HFS+ Alternate Data Stream Path (Type: Variant),"The software does not properly handle special paths that may identify the data or resource fork of a file on the HFS+ file system. -If the software chooses actions to take based on the file name, then if an attacker provides the data or resource fork, the software may take unexpected actions. Further, if the software intends to restrict access to a file, then an attacker might still be able to bypass intended access restrictions by requesting the data or resource fork for that file.",,unclassified,No description: http://docs.info.apple.com/article.html?artnum=300422 +If the software chooses actions to take based on the file name, then if an attacker provides the data or resource fork, the software may take unexpected actions. Further, if the software intends to restrict access to a file, then an attacker might still be able to bypass intended access restrictions by requesting the data or resource fork for that file.",,unclassified,No description: http://docs.info.apple.com/article.html?artnum=300422 CWE-733,EN-Compiler Optimization Removal or Modification of Security-critical Code (Type: Base),"The developer builds a security-critical protection mechanism into the software but the compiler optimizes the program such that the mechanism is removed or modified. -When a resource is given a permissions setting that provides access to a wider range of actors than required, it could lead to the exposure of sensitive information, or the modification of that resource by unintended parties. This is especially dangerous when the resource is related to program configuration, execution or sensitive user data.",,unclassified,"Writing Secure Code: Chapter 9, ""A Compiler Optimization Caveat"" Page 322" +When a resource is given a permissions setting that provides access to a wider range of actors than required, it could lead to the exposure of sensitive information, or the modification of that resource by unintended parties. This is especially dangerous when the resource is related to program configuration, execution or sensitive user data.",,unclassified,"Writing Secure Code: Chapter 9, ""A Compiler Optimization Caveat"" Page 322" CWE-75,EN-Failure to Sanitize Special Elements into a Different Plane (Special Element Injection) (Type: Class),"The software does not adequately filter user-controlled input for special elements with control implications. This weakness can lead to a wide variety of resultant weaknesses, depending on the behavior of the exposed method. It can apply to any number of technologies and approaches, such as ActiveX controls, Java functions, IOCTLs, and so on. The exposure can occur in a few different ways: 1) The function/method was never intended to be exposed to outside actors. -2) The function/method was only intended to be accessible to a limited set of actors, such as Internet-based access from a single web site.",,unclassified, +2) The function/method was only intended to be accessible to a limited set of actors, such as Internet-based access from a single web site.",,unclassified, CWE-756,EN-Missing Custom Error Page (Type: Class),"The software does not return custom error pages to the user, possibly exposing sensitive information. The programmer may assume that certain events or conditions will never occur or do not need to be worried about, such as low memory conditions, lack of access to resources due to restrictive permissions, or misbehaving clients or components. However, attackers may intentionally trigger these unusual conditions, thus violating the programmer's assumptions, possibly introducing instability, incorrect behavior, or a vulnerability. -Note that this entry is not exclusively about the use of exceptions and exception handling, which are mechanisms for both checking and handling unusual or unexpected conditions.",,unclassified, +Note that this entry is not exclusively about the use of exceptions and exception handling, which are mechanisms for both checking and handling unusual or unexpected conditions.",,unclassified, CWE-757,EN-Selection of Less-Secure Algorithm During Negotiation (Algorithm Downgrade) (Type: Class),"A protocol or its implementation supports interaction between multiple actors and allows those actors to negotiate which algorithm should be used as a protection mechanism such as encryption or authentication, but it does not select the strongest algorithm that is available to both parties. -When a security mechanism can be forced to downgrade to use a less secure algorithm, this can make it easier for attackers to compromise the software by exploiting weaker algorithm. The victim might not be aware that the less secure algorithm is being used. For example, if an attacker can force a communications channel to use cleartext instead of strongly-encrypted data, then the attacker could read the channel by sniffing, instead of going through extra effort of trying to decrypt the data using brute force techniques.",,unclassified, +When a security mechanism can be forced to downgrade to use a less secure algorithm, this can make it easier for attackers to compromise the software by exploiting weaker algorithm. The victim might not be aware that the less secure algorithm is being used. For example, if an attacker can force a communications channel to use cleartext instead of strongly-encrypted data, then the attacker could read the channel by sniffing, instead of going through extra effort of trying to decrypt the data using brute force techniques.",,unclassified, CWE-758,"EN-Reliance on Undefined, Unspecified, or Implementation-Defined Behavior (Type: Class)","The software uses an API function, data structure, or other entity in a way that relies on properties that are not always guaranteed to hold for that entity. -This can lead to resultant weaknesses when the required properties change, such as when the software is ported to a different platform or if an interaction error (CWE-435) occurs.",,unclassified, +This can lead to resultant weaknesses when the required properties change, such as when the software is ported to a different platform or if an interaction error (CWE-435) occurs.",,unclassified, CWE-759,EN-Use of a One-Way Hash without a Salt (Type: Base),"The software uses a one-way cryptographic hash against an input that should not be reversible, such as a password, but the software does not also use a salt as part of the input. This makes it easier for attackers to pre-compute the hash value using dictionary attack techniques such as rainbow tables. It should be noted that, despite common perceptions, the use of a good salt with a hash does not sufficiently increase the effort for an attacker who is targeting an individual password, or who has a large amount of computing resources available, such as with cloud-based services or specialized, inexpensive hardware. Offline password cracking can still be effective if the hash function is not expensive to compute; many cryptographic functions are designed to be efficient and can be vulnerable to attacks using massive computing resources, even if the hash is cryptographically strong. The use of a salt only slightly increases the computing requirements for an attacker compared to other strategies such as adaptive hash functions. See CWE-916 for more details.",,unclassified,"bcrypt: http://bcrypt.sourceforge.net/ @@ -1878,7 +1878,7 @@ Writing Secure Code: Chapter 9, ""Creating a Salted Hash"" Page 302 The Art of Software Security Assessment: Chapter 2, ""Salt Values"", Page 46. How To Safely Store A Password: http://codahale.com/how-to-safely-store-a-password/ Our password hashing has no clothes: http://www.troyhunt.com/2012/06/our-password-hashing-has-no-clothes.html -Should we really use bcrypt/scrypt?: http://www.analyticalengine.net/2012/06/should-we-really-use-bcryptscrypt/" +Should we really use bcrypt/scrypt?: http://www.analyticalengine.net/2012/06/should-we-really-use-bcryptscrypt/" CWE-760,EN-Use of a One-Way Hash with a Predictable Salt (Type: Base),"The software uses a one-way cryptographic hash against an input that should not be reversible, such as a password, but the software uses a predictable salt as part of the input. This makes it easier for attackers to pre-compute the hash value using dictionary attack techniques such as rainbow tables, effectively disabling the protection that an unpredictable salt would provide. It should be noted that, despite common perceptions, the use of a good salt with a hash does not sufficiently increase the effort for an attacker who is targeting an individual password, or who has a large amount of computing resources available, such as with cloud-based services or specialized, inexpensive hardware. Offline password cracking can still be effective if the hash function is not expensive to compute; many cryptographic functions are designed to be efficient and can be vulnerable to attacks using massive computing resources, even if the hash is cryptographically strong. The use of a salt only slightly increases the computing requirements for an attacker compared to other strategies such as adaptive hash functions. See CWE-916 for more details.",,unclassified,"bcrypt: http://bcrypt.sourceforge.net/ @@ -1897,28 +1897,28 @@ Writing Secure Code: Chapter 9, ""Creating a Salted Hash"" Page 302 The Art of Software Security Assessment: Chapter 2, ""Salt Values"", Page 46. How To Safely Store A Password: http://codahale.com/how-to-safely-store-a-password/ Our password hashing has no clothes: http://www.troyhunt.com/2012/06/our-password-hashing-has-no-clothes.html -Should we really use bcrypt/scrypt?: http://www.analyticalengine.net/2012/06/should-we-really-use-bcryptscrypt/" +Should we really use bcrypt/scrypt?: http://www.analyticalengine.net/2012/06/should-we-really-use-bcryptscrypt/" CWE-761,EN-Free of Pointer not at Start of Buffer (Type: Variant),"The application calls free() on a pointer to a memory resource that was allocated on the heap, but the pointer is not at the start of the buffer. This can cause the application to crash, or in some cases, modify critical program variables or execute code. This weakness often occurs when the memory is allocated explicitly on the heap with one of the malloc() family functions and free() is called, but pointer arithmetic has caused the pointer to be in the interior or end of the buffer.",,unclassified,"boost C++ Library Smart Pointers: http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/smart_ptr.htm -Valgrind: http://valgrind.org/" +Valgrind: http://valgrind.org/" CWE-763,EN-Release of Invalid Pointer or Reference (Type: Base),"The application attempts to return a memory resource to the system, but calls the wrong release function or calls the appropriate release function incorrectly. This weakness can take several forms, such as: The memory was allocated, explicitly or implicitly, via one memory management method and deallocated using a different, non-compatible function (CWE-762). The function calls or memory management routines chosen are appropriate, however they are used incorrectly, such as in CWE-761.",,unclassified,"boost C++ Library Smart Pointers: http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/smart_ptr.htm -Valgrind: http://valgrind.org/" +Valgrind: http://valgrind.org/" CWE-764,EN-Multiple Locks of a Critical Resource (Type: Variant),"The software locks a critical resource more times than intended, leading to an unexpected state in the system. -When software is operating in a concurrent environment and repeatedly locks a critical resource, the consequences will vary based on the type of lock, the lock's implementation, and the resource being protected. In some situations such as with semaphores, the resources are pooled and extra locking calls will reduce the size of the total available pool, possibly leading to degraded performance or a denial of service. If this can be triggered by an attacker, it will be similar to an unrestricted lock (CWE-412). In the context of a binary lock, it is likely that any duplicate locking attempts will never succeed since the lock is already held and progress may not be possible.",,unclassified, +When software is operating in a concurrent environment and repeatedly locks a critical resource, the consequences will vary based on the type of lock, the lock's implementation, and the resource being protected. In some situations such as with semaphores, the resources are pooled and extra locking calls will reduce the size of the total available pool, possibly leading to degraded performance or a denial of service. If this can be triggered by an attacker, it will be similar to an unrestricted lock (CWE-412). In the context of a binary lock, it is likely that any duplicate locking attempts will never succeed since the lock is already held and progress may not be possible.",,unclassified, CWE-765,EN-Multiple Unlocks of a Critical Resource (Type: Variant),"The software unlocks a critical resource more times than intended, leading to an unexpected state in the system. -When software is operating in a concurrent environment and repeatedly unlocks a critical resource, the consequences will vary based on the type of lock, the lock's implementation, and the resource being protected. In some situations such as with semaphores, the resources are pooled and extra calls to unlock will increase the count for the number of available resources, likely resulting in a crash or unpredictable behavior when the system nears capacity.",,unclassified, +When software is operating in a concurrent environment and repeatedly unlocks a critical resource, the consequences will vary based on the type of lock, the lock's implementation, and the resource being protected. In some situations such as with semaphores, the resources are pooled and extra calls to unlock will increase the count for the number of available resources, likely resulting in a crash or unpredictable behavior when the system nears capacity.",,unclassified, CWE-785,EN-Use of Path Manipulation Function without Maximum-sized Buffer (Type: Variant),"The software invokes a function for normalizing paths or file names, but it provides an output buffer that is smaller than the maximum possible size, such as PATH_MAX. -Passing an inadequately-sized output buffer to a path manipulation function can result in a buffer overflow. Such functions include realpath(), readlink(), PathAppend(), and others.",,unclassified, +Passing an inadequately-sized output buffer to a path manipulation function can result in a buffer overflow. Such functions include realpath(), readlink(), PathAppend(), and others.",,unclassified, CWE-786,EN-Access of Memory Location Before Start of Buffer (Type: Base),"The software reads or writes to a buffer using an index or pointer that references a memory location prior to the beginning of the buffer. -This typically occurs when a pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used.",,unclassified, +This typically occurs when a pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used.",,unclassified, CWE-787,EN-Out-of-bounds Write (Type: Base),"The software writes data past the end, or before the beginning, of the intended buffer. -This typically occurs when the pointer or its index is incremented or decremented to a position beyond the bounds of the buffer or when pointer arithmetic results in a position outside of the valid memory location to name a few. This may result in corruption of sensitive information, a crash, or code execution among other things.",,unclassified, +This typically occurs when the pointer or its index is incremented or decremented to a position beyond the bounds of the buffer or when pointer arithmetic results in a position outside of the valid memory location to name a few. This may result in corruption of sensitive information, a crash, or code execution among other things.",,unclassified, CWE-788,EN-Access of Memory Location After End of Buffer (Type: Base),"The software reads or writes to a buffer using an index or pointer that references a memory location after the end of the buffer. -This typically occurs when a pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. These problems may be resultant from missing sentinel values (CWE-463) or trusting a user-influenced input length variable.",,unclassified, +This typically occurs when a pointer or its index is decremented to a position before the buffer, when pointer arithmetic results in a position before the beginning of the valid memory location, or when a negative index is used. These problems may be resultant from missing sentinel values (CWE-463) or trusting a user-influenced input length variable.",,unclassified, CWE-790,EN-Improper Filtering of Special Elements (Type: Class),"The software receives data from an upstream component, but does not filter or incorrectly filters special elements before sending it to a downstream component. Cross-site scripting (XSS) vulnerabilities occur when: 1. Untrusted data enters a web application, typically from a web request. @@ -1932,7 +1932,7 @@ The server reads data directly from the HTTP request and reflects it back in the The application stores dangerous data in a database, message forum, visitor log, or other trusted data store. At a later time, the dangerous data is subsequently read back into the application and included in dynamic content. From an attacker's perspective, the optimal place to inject malicious content is in an area that is displayed to either many users or particularly interesting users. Interesting users typically have elevated privileges in the application or interact with sensitive data that is valuable to the attacker. If one of these users executes malicious content, the attacker may be able to perform privileged operations on behalf of the user or gain access to sensitive data belonging to the user. For example, the attacker might inject XSS into a log message, which might not be handled properly when an administrator views the logs. In DOM-based XSS, the client performs the injection of XSS into the page; in the other types, the server performs the injection. DOM-based XSS generally involves server-controlled, trusted script that is sent to the client, such as Javascript that performs sanity checks on a form before the user submits it. If the server-supplied script processes user-supplied data and then injects it back into the web page (such as with dynamic HTML), then DOM-based XSS is possible. Once the malicious script is injected, the attacker can perform a variety of malicious activities. The attacker could transfer private information, such as cookies that may include session information, from the victim's machine to the attacker. The attacker could send malicious requests to a web site on behalf of the victim, which could be especially dangerous to the site if the victim has administrator privileges to manage that site. Phishing attacks could be used to emulate trusted web sites and trick the victim into entering a password, allowing the attacker to compromise the victim's account on that web site. Finally, the script could exploit a vulnerability in the web browser itself possibly taking over the victim's machine, sometimes referred to as ""drive-by hacking."" -In many cases, the attack can be launched without the victim even being aware of it. Even with careful users, attackers frequently use a variety of methods to encode the malicious portion of the attack, such as URL encoding or Unicode, so the request looks less suspicious.",,unclassified, +In many cases, the attack can be launched without the victim even being aware of it. Even with careful users, attackers frequently use a variety of methods to encode the malicious portion of the attack, such as URL encoding or Unicode, so the request looks less suspicious.",,unclassified, CWE-791,EN-Incomplete Filtering of Special Elements (Type: Base),"The software receives data from an upstream component, but does not completely filter special elements before sending it to a downstream component. Cross-site scripting (XSS) vulnerabilities occur when: 1. Untrusted data enters a web application, typically from a web request. @@ -1946,66 +1946,66 @@ The server reads data directly from the HTTP request and reflects it back in the The application stores dangerous data in a database, message forum, visitor log, or other trusted data store. At a later time, the dangerous data is subsequently read back into the application and included in dynamic content. From an attacker's perspective, the optimal place to inject malicious content is in an area that is displayed to either many users or particularly interesting users. Interesting users typically have elevated privileges in the application or interact with sensitive data that is valuable to the attacker. If one of these users executes malicious content, the attacker may be able to perform privileged operations on behalf of the user or gain access to sensitive data belonging to the user. For example, the attacker might inject XSS into a log message, which might not be handled properly when an administrator views the logs. In DOM-based XSS, the client performs the injection of XSS into the page; in the other types, the server performs the injection. DOM-based XSS generally involves server-controlled, trusted script that is sent to the client, such as Javascript that performs sanity checks on a form before the user submits it. If the server-supplied script processes user-supplied data and then injects it back into the web page (such as with dynamic HTML), then DOM-based XSS is possible. Once the malicious script is injected, the attacker can perform a variety of malicious activities. The attacker could transfer private information, such as cookies that may include session information, from the victim's machine to the attacker. The attacker could send malicious requests to a web site on behalf of the victim, which could be especially dangerous to the site if the victim has administrator privileges to manage that site. Phishing attacks could be used to emulate trusted web sites and trick the victim into entering a password, allowing the attacker to compromise the victim's account on that web site. Finally, the script could exploit a vulnerability in the web browser itself possibly taking over the victim's machine, sometimes referred to as ""drive-by hacking."" -In many cases, the attack can be launched without the victim even being aware of it. Even with careful users, attackers frequently use a variety of methods to encode the malicious portion of the attack, such as URL encoding or Unicode, so the request looks less suspicious.",,unclassified, +In many cases, the attack can be launched without the victim even being aware of it. Even with careful users, attackers frequently use a variety of methods to encode the malicious portion of the attack, such as URL encoding or Unicode, so the request looks less suspicious.",,unclassified, CWE-792,EN-Incomplete Filtering of One or More Instances of Special Elements (Type: Variant),"The software receives data from an upstream component, but does not completely filter one or more instances of special elements before sending it to a downstream component. Incomplete filtering of this nature involves either only filtering a single instance of a special element when more exist, or -not filtering all instances or all elements where multiple special elements exist.",,unclassified, +not filtering all instances or all elements where multiple special elements exist.",,unclassified, CWE-793,EN-Only Filtering One Instance of a Special Element (Type: Variant),"The software receives data from an upstream component, but only filters a single instance of a special element before sending it to a downstream component. -Incomplete filtering of this nature may be location-dependent, as in only the first or last element is filtered.",,unclassified, +Incomplete filtering of this nature may be location-dependent, as in only the first or last element is filtered.",,unclassified, CWE-794,EN-Incomplete Filtering of Multiple Instances of Special Elements (Type: Variant),"The software receives data from an upstream component, but does not filter all instances of a special element before sending it to a downstream component. Incomplete filtering of this nature may be applied to sequential elements (special elements that appear next to each other) or -non-sequential elements (special elements that appear multiple times in different locations).",,unclassified, +non-sequential elements (special elements that appear multiple times in different locations).",,unclassified, CWE-795,EN-Only Filtering Special Elements at a Specified Location (Type: Base),"The software receives data from an upstream component, but only accounts for special elements at a specified location, thereby missing remaining special elements that may exist before sending it to a downstream component. A filter might only account for instances of special elements when they occur: relative to a marker (e.g. ""at the beginning/end of string; the second argument""), or at an absolute position (e.g. ""byte number 10""). -This may leave special elements in the data that did not match the filter position, but still may be dangerous.",,unclassified, +This may leave special elements in the data that did not match the filter position, but still may be dangerous.",,unclassified, CWE-796,EN-Only Filtering Special Elements Relative to a Marker (Type: Variant),"The software receives data from an upstream component, but only accounts for special elements positioned relative to a marker (e.g. ""at the beginning/end of a string; the second argument""), thereby missing remaining special elements that may exist before sending it to a downstream component. A filter might only account for instances of special elements when they occur: relative to a marker (e.g. ""at the beginning/end of string; the second argument""), or at an absolute position (e.g. ""byte number 10""). -This may leave special elements in the data that did not match the filter position, but still may be dangerous.",,unclassified, +This may leave special elements in the data that did not match the filter position, but still may be dangerous.",,unclassified, CWE-797,EN-Only Filtering Special Elements at an Absolute Position (Type: Variant),"The software receives data from an upstream component, but only accounts for special elements at an absolute position (e.g. ""byte number 10""), thereby missing remaining special elements that may exist before sending it to a downstream component. A filter might only account for instances of special elements when they occur: relative to a marker (e.g. ""at the beginning/end of string; the second argument""), or at an absolute position (e.g. ""byte number 10""). -This may leave special elements in the data that did not match the filter position, but still may be dangerous.",,unclassified, +This may leave special elements in the data that did not match the filter position, but still may be dangerous.",,unclassified, CWE-799,EN-Improper Control of Interaction Frequency (Type: Class),"The software does not properly limit the number or frequency of interactions that it has with an actor, such as the number of incoming requests. -This can allow the actor to perform actions more frequently than expected. The actor could be a human or an automated process such as a virus or bot. This could be used to cause a denial of service, compromise program logic (such as limiting humans to a single vote), or other consequences. For example, an authentication routine might not limit the number of times an attacker can guess a password. Or, a web site might conduct a poll but only expect humans to vote a maximum of once a day.",,unclassified,Insufficient Anti-automation: http://projects.webappsec.org/Insufficient+Anti-automation +This can allow the actor to perform actions more frequently than expected. The actor could be a human or an automated process such as a virus or bot. This could be used to cause a denial of service, compromise program logic (such as limiting humans to a single vote), or other consequences. For example, an authentication routine might not limit the number of times an attacker can guess a password. Or, a web site might conduct a poll but only expect humans to vote a maximum of once a day.",,unclassified,Insufficient Anti-automation: http://projects.webappsec.org/Insufficient+Anti-automation CWE-8,EN-J2EE Misconfiguration: Entity Bean Declared Remote (Type: Variant),"When an application exposes a remote interface for an entity bean, it might also expose methods that get or set the bean's data. These methods could be leveraged to read sensitive information, or to change data in ways that violate the application's expectations, potentially leading to other vulnerabilities. -This can allow the actor to perform actions more frequently than expected. The actor could be a human or an automated process such as a virus or bot. This could be used to cause a denial of service, compromise program logic (such as limiting humans to a single vote), or other consequences. For example, an authentication routine might not limit the number of times an attacker can guess a password. Or, a web site might conduct a poll but only expect humans to vote a maximum of once a day.",,unclassified, +This can allow the actor to perform actions more frequently than expected. The actor could be a human or an automated process such as a virus or bot. This could be used to cause a denial of service, compromise program logic (such as limiting humans to a single vote), or other consequences. For example, an authentication routine might not limit the number of times an attacker can guess a password. Or, a web site might conduct a poll but only expect humans to vote a maximum of once a day.",,unclassified, CWE-81,EN-Improper Neutralization of Script in an Error Message Web Page (Type: Variant),"The software receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special characters that could be interpreted as web-scripting elements when they are sent to an error page. Error pages may include customized 403 Forbidden or 404 Not Found pages. -When an attacker can trigger an error that contains unneutralized input, then cross-site scripting attacks may be possible.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 11: Failure to Handle Errors Correctly."" Page 183" +When an attacker can trigger an error that contains unneutralized input, then cross-site scripting attacks may be possible.",,unclassified,"24 Deadly Sins of Software Security: ""Sin 11: Failure to Handle Errors Correctly."" Page 183" CWE-82,EN-Improper Neutralization of Script in Attributes of IMG Tags in a Web Page (Type: Variant),"The web application does not neutralize or incorrectly neutralizes scripting elements within attributes of HTML IMG tags, such as the src attribute. -Attackers can embed XSS exploits into the values for IMG attributes (e.g. SRC) that is streamed and then executed in a victim's browser. Note that when the page is loaded into a user's browsers, the exploit will automatically execute.",,unclassified, +Attackers can embed XSS exploits into the values for IMG attributes (e.g. SRC) that is streamed and then executed in a victim's browser. Note that when the page is loaded into a user's browsers, the exploit will automatically execute.",,unclassified, CWE-820,EN-Missing Synchronization (Type: Base),"The software utilizes a shared resource in a concurrent manner but does not attempt to synchronize access to the resource. -If access to a shared resource is not synchronized, then the resource may not be in a state that is expected by the software. This might lead to unexpected or insecure behaviors, especially if an attacker can influence the shared resource.",,unclassified, +If access to a shared resource is not synchronized, then the resource may not be in a state that is expected by the software. This might lead to unexpected or insecure behaviors, especially if an attacker can influence the shared resource.",,unclassified, CWE-821,EN-Incorrect Synchronization (Type: Base),"The software utilizes a shared resource in a concurrent manner but it does not correctly synchronize access to the resource. -If access to a shared resource is not correctly synchronized, then the resource may not be in a state that is expected by the software. This might lead to unexpected or insecure behaviors, especially if an attacker can influence the shared resource.",,unclassified, +If access to a shared resource is not correctly synchronized, then the resource may not be in a state that is expected by the software. This might lead to unexpected or insecure behaviors, especially if an attacker can influence the shared resource.",,unclassified, CWE-822,EN-Untrusted Pointer Dereference (Type: Base),"The program obtains a value from an untrusted source, converts this value to a pointer, and dereferences the resulting pointer. An attacker can supply a pointer for memory locations that the program is not expecting. If the pointer is dereferenced for a write operation, the attack might allow modification of critical program state variables, cause a crash, or execute code. If the dereferencing operation is for a read, then the attack might allow reading of sensitive data, cause a crash, or set a program variable to an unexpected value (since the value will be read from an unexpected memory location). There are several variants of this weakness, including but not necessarily limited to: The untrusted value is directly invoked as a function call. In OS kernels or drivers where there is a boundary between ""userland"" and privileged memory spaces, an untrusted pointer might enter through an API or system call (see CWE-781 for one such example). -Inadvertently accepting the value from an untrusted control sphere when it did not have to be accepted as input at all. This might occur when the code was originally developed to be run by a single user in a non-networked environment, and the code is then ported to or otherwise exposed to a networked environment.",,unclassified, +Inadvertently accepting the value from an untrusted control sphere when it did not have to be accepted as input at all. This might occur when the code was originally developed to be run by a single user in a non-networked environment, and the code is then ported to or otherwise exposed to a networked environment.",,unclassified, CWE-823,EN-Use of Out-of-range Pointer Offset (Type: Base),"The program performs pointer arithmetic on a valid pointer, but it uses an offset that can point outside of the intended range of valid memory locations for the resulting pointer. While a pointer can contain a reference to any arbitrary memory location, a program typically only intends to use the pointer to access limited portions of memory, such as contiguous memory used to access an individual array. Programs may use offsets in order to access fields or sub-elements stored within structured data. The offset might be out-of-range if it comes from an untrusted source, is the result of an incorrect calculation, or occurs because of another error. -If an attacker can control or influence the offset so that it points outside of the intended boundaries of the structure, then the attacker may be able to read or write to memory locations that are used elsewhere in the program. As a result, the attack might change the state of the software as accessed through program variables, cause a crash or instable behavior, and possibly lead to code execution.",,unclassified,"The Art of Software Security Assessment: Chapter 6, ""Pointer Arithmetic"", Page 277." +If an attacker can control or influence the offset so that it points outside of the intended boundaries of the structure, then the attacker may be able to read or write to memory locations that are used elsewhere in the program. As a result, the attack might change the state of the software as accessed through program variables, cause a crash or instable behavior, and possibly lead to code execution.",,unclassified,"The Art of Software Security Assessment: Chapter 6, ""Pointer Arithmetic"", Page 277." CWE-824,EN-Access of Uninitialized Pointer (Type: Base),"The program accesses or uses a pointer that has not been initialized. If the pointer contains an uninitialized value, then the value might not point to a valid memory location. This could cause the program to read from or write to unexpected memory locations, leading to a denial of service. If the uninitialized pointer is used as a function call, then arbitrary functions could be invoked. If an attacker can influence the portion of uninitialized memory that is contained in the pointer, this weakness could be leveraged to execute code or perform other attacks. -Depending on memory layout, associated memory management behaviors, and program operation, the attacker might be able to influence the contents of the uninitialized pointer, thus gaining more fine-grained control of the memory location to be accessed.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." +Depending on memory layout, associated memory management behaviors, and program operation, the attacker might be able to influence the contents of the uninitialized pointer, thus gaining more fine-grained control of the memory location to be accessed.",,unclassified,"The Art of Software Security Assessment: Chapter 7, ""Variable Initialization"", Page 312." CWE-825,EN-Expired Pointer Dereference (Type: Base),"The program dereferences a pointer that contains a location for memory that was previously valid, but is no longer valid. -When a program releases memory, but it maintains a pointer to that memory, then the memory might be re-allocated at a later time. If the original pointer is accessed to read or write data, then this could cause the program to read or modify data that is in use by a different function or process. Depending on how the newly-allocated memory is used, this could lead to a denial of service, information exposure, or code execution.",,unclassified, +When a program releases memory, but it maintains a pointer to that memory, then the memory might be re-allocated at a later time. If the original pointer is accessed to read or write data, then this could cause the program to read or modify data that is in use by a different function or process. Depending on how the newly-allocated memory is used, this could lead to a denial of service, information exposure, or code execution.",,unclassified, CWE-826,EN-Premature Release of Resource During Expected Lifetime (Type: Base),"The program releases a resource that is still intended to be used by the program itself or another actor. This weakness focuses on errors in which the program should not release a resource, but performs the release anyway. This is different than a weakness in which the program releases a resource at the appropriate time, but it maintains a reference to the resource, which it later accesses. For this weaknesses, the resource should still be valid upon the subsequent access. -When a program releases a resource that is still being used, it is possible that operations will still be taken on this resource, which may have been repurposed in the meantime, leading to issues similar to CWE-825. Consequences may include denial of service, information exposure, or code execution.",,unclassified, +When a program releases a resource that is still being used, it is possible that operations will still be taken on this resource, which may have been repurposed in the meantime, leading to issues similar to CWE-825. Consequences may include denial of service, information exposure, or code execution.",,unclassified, CWE-827,EN-Improper Control of Document Type Definition (Type: Base),"The software does not restrict a reference to a Document Type Definition (DTD) to the intended control sphere. This might allow attackers to reference arbitrary DTDs, possibly causing the software to expose files, consume excessive system resources, or execute arbitrary http requests on behalf of the attacker. As DTDs are processed, they might try to read or include files on the machine performing the parsing. If an attacker is able to control the DTD, then the attacker might be able to specify sensitive resources or requests or provide malicious content. -For example, the SOAP specification prohibits SOAP messages from containing DTDs.",,unclassified,Apache CXF Security Advisory (CVE-2010-2076): http://svn.apache.org/repos/asf/cxf/trunk/security/CVE-2010-2076.pdf +For example, the SOAP specification prohibits SOAP messages from containing DTDs.",,unclassified,Apache CXF Security Advisory (CVE-2010-2076): http://svn.apache.org/repos/asf/cxf/trunk/security/CVE-2010-2076.pdf CWE-828,EN-Signal Handler with Functionality that is not Asynchronous-Safe (Type: Base),"The software defines a signal handler that contains code sequences that are not asynchronous-safe, i.e., the functionality is not reentrant, or it can be interrupted. This can lead to an unexpected system state with a variety of potential consequences depending on context, including denial of service and code execution. Signal handlers are typically intended to interrupt normal functionality of a program, or even other signals, in order to notify the process of an event. When a signal handler uses global or static variables, or invokes functions that ultimately depend on such state or its associated metadata, then it could corrupt system state that is being used by normal functionality. This could subject the program to race conditions or other weaknesses that allow an attacker to cause the program state to be corrupted. While denial of service is frequently the consequence, in some cases this weakness could be leveraged for code execution. @@ -2015,39 +2015,39 @@ Code sequences (not necessarily function calls) contain non-atomic use of global The signal handler function is intended to run at most one time, but instead it can be invoked multiple times. This could happen by repeated delivery of the same signal, or by delivery of different signals that have the same handler function (CWE-831). Note that in some environments or contexts, it might be possible for the signal handler to be interrupted itself. If both a signal handler and the normal behavior of the software have to operate on the same set of state variables, and a signal is received in the middle of the normal execution's modifications of those variables, the variables may be in an incorrect or corrupt state during signal handler execution, and possibly still incorrect or corrupt upon return.",,unclassified,"Delivering Signals for Fun and Profit: http://lcamtuf.coredump.cx/signals.txt -Race Condition: Signal Handling: http://www.fortify.com/vulncat/en/vulncat/cpp/race_condition_signal_handling.html" +Race Condition: Signal Handling: http://www.fortify.com/vulncat/en/vulncat/cpp/race_condition_signal_handling.html" CWE-829,EN-Inclusion of Functionality from Untrusted Control Sphere (Type: Class),"The software imports, requires, or includes executable functionality (such as a library) from a source that is outside of the intended control sphere. When including third-party functionality, such as a web widget, library, or other source of functionality, the software must effectively trust that functionality. Without sufficient protection mechanisms, the functionality could be malicious in nature (either by coming from an untrusted source, being spoofed, or being modified in transit from a trusted source). The functionality might also contain its own weaknesses, or grant access to additional functionality and state information that should be kept private to the base system, such as system state information, sensitive application data, or the DOM of a web application. This might lead to many different consequences depending on the included functionality, but some examples include injection of malware, information exposure by granting excessive privileges or permissions to the untrusted functionality, DOM-based XSS vulnerabilities, stealing user's cookies, or open redirect to malware (CWE-601).",,unclassified,"OWASP Enterprise Security API (ESAPI) Project: http://www.owasp.org/index.php/ESAPI -Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" +Least Privilege: https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html" CWE-83,EN-Improper Neutralization of Script in Attributes in a Web Page (Type: Variant),"The software does not neutralize or incorrectly neutralizes ""javascript:"" or other URIs from dangerous attributes within tags, such as onmouseover, onload, onerror, or style. When including third-party functionality, such as a web widget, library, or other source of functionality, the software must effectively trust that functionality. Without sufficient protection mechanisms, the functionality could be malicious in nature (either by coming from an untrusted source, being spoofed, or being modified in transit from a trusted source). The functionality might also contain its own weaknesses, or grant access to additional functionality and state information that should be kept private to the base system, such as system state information, sensitive application data, or the DOM of a web application. -This might lead to many different consequences depending on the included functionality, but some examples include injection of malware, information exposure by granting excessive privileges or permissions to the untrusted functionality, DOM-based XSS vulnerabilities, stealing user's cookies, or open redirect to malware (CWE-601).",,unclassified, +This might lead to many different consequences depending on the included functionality, but some examples include injection of malware, information exposure by granting excessive privileges or permissions to the untrusted functionality, DOM-based XSS vulnerabilities, stealing user's cookies, or open redirect to malware (CWE-601).",,unclassified, CWE-830,EN-Inclusion of Web Functionality from an Untrusted Source (Type: Base),"The software includes web functionality (such as a web widget) from another domain, which causes it to operate within the domain of the software, potentially granting total access and control of the software to the untrusted source. Including third party functionality in a web-based environment is risky, especially if the source of the functionality is untrusted. Even if the third party is a trusted source, the software may still be exposed to attacks and malicious behavior if that trusted source is compromised, or if the code is modified in transmission from the third party to the software. This weakness is common in ""mashup"" development on the web, which may include source functionality from other domains. For example, Javascript-based web widgets may be inserted by using ' + diff --git a/server/www/scripts/commons/directives/fileModel.js b/server/www/scripts/commons/directives/fileModel.js new file mode 100644 index 00000000000..51cb9f0ad27 --- /dev/null +++ b/server/www/scripts/commons/directives/fileModel.js @@ -0,0 +1,19 @@ +// Faraday Penetration Test IDE +// Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) +// See the file 'doc/LICENSE' for the license information + +angular.module('faradayApp').directive('fileModel', ['$parse', function ($parse) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + var model = $parse(attrs.fileModel); + var modelSetter = model.assign; + + element.bind('change', function(){ + scope.$apply(function(){ + modelSetter(scope, element[0].files[0]); + }); + }); + } + }; +}]); diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index 62cf0149b0a..f60b7b7a577 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -8,11 +8,11 @@ angular.module("faradayApp") "$location", "$uibModal", "$cookies", "$q", "$window", "BASEURL", "SEVERITIES", "EASEOFRESOLUTION", "STATUSES", "hostsManager", "commonsFact", "vulnsManager", "workspacesFact", "csvService", "uiGridConstants", "vulnModelsManager", - "referenceFact", "ServerAPI", + "referenceFact", "ServerAPI", '$http', function($scope, $filter, $routeParams, $location, $uibModal, $cookies, $q, $window, BASEURL, SEVERITIES, EASEOFRESOLUTION, STATUSES, hostsManager, commonsFact, - vulnsManager, workspacesFact, csvService, uiGridConstants, vulnModelsManager, referenceFact, ServerAPI) { + vulnsManager, workspacesFact, csvService, uiGridConstants, vulnModelsManager, referenceFact, ServerAPI, $http) { $scope.baseurl; $scope.columns; $scope.columnsWidths; @@ -1091,6 +1091,47 @@ angular.module("faradayApp") return $scope.encodeUrl(srvName); }; + function toggleFileUpload() { + if($scope.fileUploadEnabled === false) { + $scope.fileUploadEnabled = true; + } else { + $scope.fileUploadEnabled = false; + $scope.fileToUpload = undefined; + } + } + + $scope.enableFileUpload = function() { + if($scope.fileUploadEnabled === undefined) { + $http.get('/_api/session').then( + function(d) { + $scope.csrf_token = d.data.csrf_token; + $scope.fileUploadEnabled = true; + } + ); + } else { + toggleFileUpload(); + } + }; + + $scope.uploadFile = function() { + var fd = new FormData(); + fd.append('csrf_token', $scope.csrf_token); + fd.append('file', $scope.fileToUpload); + $http.post('http://localhost:5985/_api/v2/ws/' + $scope.workspace + '/upload_report', fd, { + transformRequest: angular.identity, + withCredentials: false, + headers: {'Content-Type': undefined}, + responseType: "arraybuffer", + params: { + fd + } + }).then( + function(d) { + $window.location.reload(); + } + ); + }; + $scope.concatForTooltip = function (items, isArray, useDoubleLinebreak) { var elements = []; for (var property in items) { diff --git a/server/www/scripts/statusReport/partials/statusReport.html b/server/www/scripts/statusReport/partials/statusReport.html index 46cf1b23dff..6f8b98f1f75 100644 --- a/server/www/scripts/statusReport/partials/statusReport.html +++ b/server/www/scripts/statusReport/partials/statusReport.html @@ -14,7 +14,7 @@
-
+
+ +
+
+ + + + + + +

{{fileToUpload.name}}

+
+
From d3ea9984b445ab96c8f7f75553793b5ed1560e6f Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Mon, 11 Jun 2018 12:41:01 -0300 Subject: [PATCH 1309/1506] [ADD] .gitignore to migrations/versions/ to add folder in pulls --- migrations/versions/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 migrations/versions/.gitignore diff --git a/migrations/versions/.gitignore b/migrations/versions/.gitignore new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/migrations/versions/.gitignore @@ -0,0 +1 @@ + From 57ecb1c8edd768bc0bb6beedfc7f73cb602650b8 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 11 Jun 2018 13:07:05 -0300 Subject: [PATCH 1310/1506] [FIX] Change jumbotron background color --- server/www/estilos-v3.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/www/estilos-v3.css b/server/www/estilos-v3.css index f69651edd42..c7eda195dd5 100644 --- a/server/www/estilos-v3.css +++ b/server/www/estilos-v3.css @@ -214,3 +214,6 @@ .file-selected { z-index: -999; } +.jumbotron { + background-color: #f4f3f4; +} From 79ba0c479d2db2c943797a51b039351c8c4c67e9 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 11 Jun 2018 15:43:14 -0300 Subject: [PATCH 1311/1506] [FIX] section width --- server/www/estilos.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/estilos.css b/server/www/estilos.css index 9e505591a53..a59957986c3 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -178,7 +178,7 @@ header.head { } .seccion { padding: 0px; - width: 101%; + width: 100%; float: left; } .seccion.treemap{ From 7b346bc2724d3fdfab8e7996a8c97babd87effde Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 11 Jun 2018 18:30:38 -0300 Subject: [PATCH 1312/1506] [MOD] Improvements on initdb workflow --- manage.py | 8 ++++++-- server/commands/initdb.py | 10 ++++++++-- server/importer.py | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/manage.py b/manage.py index c3373ecc2b0..3e4989235c1 100755 --- a/manage.py +++ b/manage.py @@ -69,7 +69,11 @@ def faraday_schema_display(): def initdb(): with app.app_context(): InitDB().run() - ImportCouchDB().run() + couchdb_config_present = server.config.couchdb + if couchdb_config_present and couchdb_config_present.user and couchdb_config_present.password: + print('Importing data from CouchDB, please wait...') + ImportCouchDB().run() + print('All users from CouchDB were imported. You can login with your old username/password to faraday now.') @click.command() def import_from_couchdb(): @@ -123,7 +127,7 @@ def createsuperuser(username, email, password): email=email, password=password, role='admin', - is_ldap=False) + is_ldap=False) db.session.commit() click.echo(click.style( 'User {} created successfully!'.format(username), diff --git a/server/commands/initdb.py b/server/commands/initdb.py index 5d9cadb6653..7b986a4c825 100644 --- a/server/commands/initdb.py +++ b/server/commands/initdb.py @@ -34,6 +34,7 @@ from colorama import Fore from sqlalchemy.exc import OperationalError +import server.config from config.globals import CONST_FARADAY_HOME_PATH from server.config import LOCAL_CONFIG_FILE init() @@ -95,7 +96,11 @@ def run(self): current_psql_output.close() conn_string = self._save_config(config, username, password, database_name, hostname) self._create_tables(conn_string) - self._create_admin_user(conn_string) + couchdb_config_present = server.config.couchdb + if not (couchdb_config_present and couchdb_config_present.user and couchdb_config_present.password): + self._create_admin_user(conn_string) + else: + print('Skipping new admin creation since couchdb configuration was found.') except KeyboardInterrupt: current_psql_output.close() print('User cancelled.') @@ -156,7 +161,7 @@ def _configure_new_postgres_user(self, psql_log_file): we return username and password and those values will be saved in the config file. """ print('This script will {blue} create a new postgres user {white} and {blue} save faraday-server settings {white}(server.ini). '.format(blue=Fore.BLUE, white=Fore.WHITE)) - username = 'faraday' + username = 'faraday_postgresql' postgres_command = ['sudo', '-u', 'postgres'] if sys.platform == 'darwin': postgres_command = [] @@ -172,6 +177,7 @@ def _configure_new_postgres_user(self, psql_log_file): print("{yellow}WARNING{white}: Role {username} already exists, skipping creation ".format(yellow=Fore.YELLOW, white=Fore.WHITE, username=username)) try: + password = server.config.database.connection_string.split(':')[2].split('@')[0] connection = psycopg2.connect(dbname='postgres', user=username, password=password) diff --git a/server/importer.py b/server/importer.py index 330dc82c76c..024a8193883 100644 --- a/server/importer.py +++ b/server/importer.py @@ -81,7 +81,7 @@ server.config.CONSTANTS.CONST_FARADAY_LOGS_PATH, 'couchdb-importer.log')) importer_file_handler = logging.FileHandler(importer_logfile) formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + '%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s') importer_file_handler.setFormatter(formatter) importer_file_handler.setLevel(logging.DEBUG) logger.addHandler(importer_file_handler) @@ -1327,6 +1327,7 @@ def _open_couchdb_conn(self): logger.error(u"CouchDB is not running at {}. Check faraday-server's"\ " configuration and make sure CouchDB is running".format( server.couchdb.get_couchdb_url())) + logger.error(u'Please start CouchDB and re-execute the importer with: \n\n --> python manage.py import_from_couchdb <--') sys.exit(1) except Unauthorized: From 92f022d5813f0ff5bf2628194b73ac669da85d0b Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 11 Jun 2018 18:47:44 -0300 Subject: [PATCH 1313/1506] [FIX] allow user.xml to be backwards compatible, avoid to delete couch config --- config/configuration.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/config/configuration.py b/config/configuration.py index 3e686fb391d..4ea6d4f5ff7 100644 --- a/config/configuration.py +++ b/config/configuration.py @@ -42,6 +42,9 @@ CONST_API_URL = "api_url" CONST_API_USERNAME = "api_username" CONST_API_PASSWORD = "api_password" +CONST_COUCH_URI = "couch_uri" +CONST_COUCH_REPLICS = "couch_replics" +CONST_COUCH_ISREPLICATED = "couch_is_replicated" CONST_REPO_URL = "repo_url" CONST_REPO_USER = "repo_user" CONST_REPORT_PATH = "report_path" @@ -149,6 +152,9 @@ def _getConfig(self): self._api_url = self._getValue(tree, CONST_API_URL) self._api_username = self._getValue(tree, CONST_API_USERNAME) self._api_password = self._getValue(tree, CONST_API_PASSWORD) + self._couch_uri = self._getValue(tree, CONST_COUCH_URI, default = "") + self._couch_replics = self._getValue(tree, CONST_COUCH_REPLICS, default = "") + self._couch_is_replicated = bool(self._getValue(tree, CONST_COUCH_ISREPLICATED, default = False)) self._repo_url = self._getValue(tree, CONST_REPO_URL) self._repo_user = self._getValue(tree, CONST_REPO_USER) self._report_path = self._getValue(tree, CONST_REPORT_PATH) @@ -305,6 +311,18 @@ def getAPIUsername(self): def getAPIPassword(self): return self._api_password + def getCouchURI(self): + if self._couch_uri and self._couch_uri.endswith('/'): + return self._couch_uri[:-1] + else: + return self._couch_uri + + def getCouchReplics(self): + return self._couch_replics + + def getCouchIsReplicated(self): + return self._couch_is_replicated + def setLastWorkspace(self, workspaceName): self._last_workspace = workspaceName @@ -414,6 +432,15 @@ def setAPIUsername(self, username): def setAPIPassword(self, password): self._api_password = password + def setCouchUri(self, uri): + self._couch_uri = uri + + def setCouchIsReplicated(self, is_it): + self._couch_is_replicated = is_it + + def setCouchReplics(self, urls): + self._couch_replics = urls + def setPluginSettings(self, settings): self._plugin_settings = settings @@ -570,6 +597,18 @@ def saveConfig(self, xml_file="~/.faraday/config/user.xml"): SERVER_PASSWORD.text = self.getAPIPassword() ROOT.append(SERVER_PASSWORD) + COUCH_URI = Element(CONST_COUCH_URI) + COUCH_URI.text = self.getCouchURI() + ROOT.append(COUCH_URI) + + COUCH_IS_REPLICATED = Element(CONST_COUCH_ISREPLICATED) + COUCH_IS_REPLICATED.text = str(self.getCouchIsReplicated()) + ROOT.append(COUCH_IS_REPLICATED) + + COUCH_REPLICS = Element(CONST_COUCH_REPLICS) + COUCH_REPLICS.text = self.getCouchReplics() + ROOT.append(COUCH_REPLICS) + VERSION = Element(CONST_VERSION) VERSION.text = self.getVersion() ROOT.append(VERSION) From 53d3c108965b0867463920f355f3b2d63f791ab0 Mon Sep 17 00:00:00 2001 From: WinnaZ Date: Mon, 11 Jun 2018 20:12:21 -0300 Subject: [PATCH 1314/1506] renamed old fplugins --- bin/{delAllHost.py => del_all_hosts.py} | 0 bin/{delAllServiceClosed.py => del_all_service_closed.py} | 0 bin/{delAllVulnsWith.py => del_all_vulns_with.py} | 0 bin/{getAllIps.py => get_all_ips.py} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename bin/{delAllHost.py => del_all_hosts.py} (100%) rename bin/{delAllServiceClosed.py => del_all_service_closed.py} (100%) rename bin/{delAllVulnsWith.py => del_all_vulns_with.py} (100%) rename bin/{getAllIps.py => get_all_ips.py} (100%) diff --git a/bin/delAllHost.py b/bin/del_all_hosts.py similarity index 100% rename from bin/delAllHost.py rename to bin/del_all_hosts.py diff --git a/bin/delAllServiceClosed.py b/bin/del_all_service_closed.py similarity index 100% rename from bin/delAllServiceClosed.py rename to bin/del_all_service_closed.py diff --git a/bin/delAllVulnsWith.py b/bin/del_all_vulns_with.py similarity index 100% rename from bin/delAllVulnsWith.py rename to bin/del_all_vulns_with.py diff --git a/bin/getAllIps.py b/bin/get_all_ips.py similarity index 100% rename from bin/getAllIps.py rename to bin/get_all_ips.py From fd20c2595b75a24cf25a5d1a623af18e7194ffef Mon Sep 17 00:00:00 2001 From: WinnaZ Date: Mon, 11 Jun 2018 20:23:45 -0300 Subject: [PATCH 1315/1506] one more rename --- bin/{del_all_service_closed.py => del_all_services_closed.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bin/{del_all_service_closed.py => del_all_services_closed.py} (100%) diff --git a/bin/del_all_service_closed.py b/bin/del_all_services_closed.py similarity index 100% rename from bin/del_all_service_closed.py rename to bin/del_all_services_closed.py From 5d18c161ed40553c3dd9956c43747cc87590bcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 13:17:25 -0300 Subject: [PATCH 1316/1506] Add more views to the list of views with no workspace viewer Some views don't depend on a workspace but they were showing the workspace switcher, and the edit button --- server/www/scripts/commons/controllers/headerCtrl.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 58efe365355..244e799b6cf 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -9,7 +9,11 @@ angular.module('faradayApp') $scope.confirmed = ($cookies.get('confirmed') == undefined) ? false : JSON.parse($cookies.get('confirmed')); $scope.showSwitcher = function() { - var noSwitcher = ["", "home", "login", "index", "workspaces", "users", "licenses"]; + var noSwitcher = [ + "", "home", "login", "index", "workspaces", "users", "licenses", + "taskgroup", "executive", // Only for white versions!! + "vulndb", "comparison", "webshell" + ]; return noSwitcher.indexOf($scope.component) < 0; }; From 083c0731fa63d6e01e703ca0c9325e611699cc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 13:19:00 -0300 Subject: [PATCH 1317/1506] Only show edit workspace button in workspaced views --- server/www/scripts/commons/partials/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index afe275f6c8b..8b18ab5e490 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -51,7 +51,7 @@ - From 8c34793ae2c520f66163bb02ac36442b1101771f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 13:24:25 -0300 Subject: [PATCH 1318/1506] Add header to commercial version views --- server/www/scripts/commons/partials/commercial.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/www/scripts/commons/partials/commercial.html b/server/www/scripts/commons/partials/commercial.html index 19510e7b0b0..9db98269e6b 100644 --- a/server/www/scripts/commons/partials/commercial.html +++ b/server/www/scripts/commons/partials/commercial.html @@ -4,7 +4,9 @@
-
+
+
+

Welcome to the {{header}} panel!

This feature belongs to our commercial versions

From fa5f5ee1830ebee74b91541a9db1f060d86cee18 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 12 Jun 2018 13:19:41 -0300 Subject: [PATCH 1319/1506] [FIX] remove all third party imports from begin of the file. If a dep is missing other commands will work. --- manage.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/manage.py b/manage.py index c3373ecc2b0..d2b634efee3 100755 --- a/manage.py +++ b/manage.py @@ -11,9 +11,6 @@ import click import requests import sys -from requests import ConnectionError -from sqlalchemy.exc import OperationalError -from pgcli.main import PGCli import server.config from persistence.server.server import _conf, FARADAY_UP, SERVER_URL @@ -43,6 +40,16 @@ def check_faraday_server(url): @click.option('--workspace', default=None) @click.option('--polling/--no-polling', default=True) def process_reports(debug, workspace, polling): + try: + from requests import ConnectionError + except ImportError: + print('Python requests was not found. Please install it with: pip install requests') + sys.exit(1) + try: + from sqlalchemy.exc import OperationalError + except ImportError: + print('SQLAlchemy was not found please install it with: pip install sqlalchemy') + sys.exit(1) setUpLogger(debug) configuration = _conf() url = '{0}/_api/v2/info'.format(configuration.getServerURI() if FARADAY_UP else SERVER_URL) @@ -82,6 +89,11 @@ def database_schema(): @click.command() def sql_shell(): + try: + from pgcli.main import PGCli + except ImportError: + print('PGCli was not found, please install it with: pip install pgcli') + sys.exit(1) conn_string = server.config.database.connection_string.strip("'") pgcli = PGCli() From 11633847f906d67573cd892112d84e595a4f535d Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 12 Jun 2018 13:29:16 -0300 Subject: [PATCH 1320/1506] [FIX] return log file to the beginning for check errros --- server/commands/initdb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/commands/initdb.py b/server/commands/initdb.py index 5d9cadb6653..efd7e1e5d15 100644 --- a/server/commands/initdb.py +++ b/server/commands/initdb.py @@ -133,6 +133,7 @@ def _configure_existing_postgres_user(self): return username, password def _check_psql_output(self, current_psql_output_file, process_status): + current_psql_output_file.seek(0) psql_output = current_psql_output_file.read() if 'unknown user: postgres' in psql_output: print('ERROR: Postgres user not found. Did you install package {blue}postgresql{white}?'.format(blue=Fore.BLUE, white=Fore.WHITE)) From 1c84700a9eb6d2b8df1f40d5160e8ee9481be853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 16:42:11 -0300 Subject: [PATCH 1321/1506] Add header to help page --- server/www/scripts/commons/controllers/headerCtrl.js | 2 +- server/www/scripts/help/partials/help.html | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 244e799b6cf..da73641c1ad 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -12,7 +12,7 @@ angular.module('faradayApp') var noSwitcher = [ "", "home", "login", "index", "workspaces", "users", "licenses", "taskgroup", "executive", // Only for white versions!! - "vulndb", "comparison", "webshell" + "vulndb", "comparison", "webshell", "help" ]; return noSwitcher.indexOf($scope.component) < 0; }; diff --git a/server/www/scripts/help/partials/help.html b/server/www/scripts/help/partials/help.html index 27b6f63cc84..2ac0b7a59be 100644 --- a/server/www/scripts/help/partials/help.html +++ b/server/www/scripts/help/partials/help.html @@ -5,7 +5,9 @@
-
+
+
+

Relax! Help is coming your way!

From f0a9841ef0c6ed5934d572acf8b915803802740d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 16:42:22 -0300 Subject: [PATCH 1322/1506] Fix typo in upload report button in status report --- server/www/scripts/statusReport/partials/statusReport.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/statusReport/partials/statusReport.html b/server/www/scripts/statusReport/partials/statusReport.html index 6f8b98f1f75..41b62657e77 100644 --- a/server/www/scripts/statusReport/partials/statusReport.html +++ b/server/www/scripts/statusReport/partials/statusReport.html @@ -84,7 +84,7 @@
-
From c07d377d9e53adeafee5e1a49a169b34e47d4a9b Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 12 Jun 2018 16:48:21 -0300 Subject: [PATCH 1323/1506] Add RELEASE.md changes --- RELEASE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index 02ed4de3ea3..fce8929411a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -36,6 +36,15 @@ TBA: * Add fix for net sparker regular and cloud fix on severity * Removed Chat feature (data is kept inside notes) * Plugin reports now can be imported in the server, from the Web UI +* Add CVSS score to reference field in Nessus plugin. +* Add HP Webinspect plugin. +* Fix unicode characters bug in Netsparker plugin. +* Fix qualys plugin. +* Fix bugs with MACOS and GTK. + +April 10, 2018: +--- +* Fix bug with tornado version 5.0 and GTK client. November 17, 2017: --- From 5469f56513829473895f9f41f8b74c6580e2ff47 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 12 Jun 2018 16:50:59 -0300 Subject: [PATCH 1324/1506] [FIX] use psql_output instead of the file --- server/commands/initdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/commands/initdb.py b/server/commands/initdb.py index 70be0233f05..6ecf35968e3 100644 --- a/server/commands/initdb.py +++ b/server/commands/initdb.py @@ -146,7 +146,7 @@ def _check_psql_output(self, current_psql_output_file, process_status): print('ERROR: {red}PostgreSQL service{white} is not running. Please verify that it is running in port 5432 before executing setup script.'.format(red=Fore.RED, white=Fore.WHITE)) elif process_status > 0: current_psql_output_file.seek(0) - print('ERROR: ' + current_psql_output_file.read()) + print('ERROR: ' + psql_output) if process_status is not 0: current_psql_output_file.close() # delete temp file From 35ff8833989c5042d06877e450013aefe20b9bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 16:54:17 -0300 Subject: [PATCH 1325/1506] Redirect to dashboard after importing a report --- server/www/scripts/statusReport/controllers/statusReport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index f60b7b7a577..afa3655d6e5 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -1127,7 +1127,7 @@ angular.module("faradayApp") } }).then( function(d) { - $window.location.reload(); + $location.path("/dashboard/ws/" + $routeParams.wsId); } ); }; From aec7acda0c4e30706581458d82a6482c9b7064f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 16:59:13 -0300 Subject: [PATCH 1326/1506] Show error when report upload failed --- server/www/scripts/statusReport/controllers/statusReport.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index afa3655d6e5..0d6c50891db 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -1128,6 +1128,9 @@ angular.module("faradayApp") }).then( function(d) { $location.path("/dashboard/ws/" + $routeParams.wsId); + }, + function(d){ + commonsFact.showMessage("Error uploading report"); } ); }; From a8210016309d3b317a20eaafc690a020e1715a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 17:55:11 -0300 Subject: [PATCH 1327/1506] Add header for host create, edit and detail views --- .../www/scripts/commons/partials/header.html | 1 + server/www/scripts/hosts/partials/new.html | 20 ++++------------- .../www/scripts/services/partials/list.html | 22 ++++--------------- 3 files changed, 9 insertions(+), 34 deletions(-) diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index 8b18ab5e490..67bd227ab0d 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -7,6 +7,7 @@ Dashboard Status Report Hosts + Hosts Credentials Executive Report Tasks diff --git a/server/www/scripts/hosts/partials/new.html b/server/www/scripts/hosts/partials/new.html index 4b2cd3468cf..421121c9d97 100644 --- a/server/www/scripts/hosts/partials/new.html +++ b/server/www/scripts/hosts/partials/new.html @@ -5,25 +5,13 @@
-
-

- Creating host -
- - - -
-

+
+
+

- Host details + Create a host diff --git a/server/www/scripts/services/partials/list.html b/server/www/scripts/services/partials/list.html index 906f02e7116..f03e09f3f23 100644 --- a/server/www/scripts/services/partials/list.html +++ b/server/www/scripts/services/partials/list.html @@ -4,25 +4,11 @@
-
-

- Editing {{hostName}} in {{workspace}} - Creating host - Viewing {{hostName}} in {{workspace}} - -

+
+
+
-

Host services

+

Services for host {{hostName}}

diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index da73641c1ad..3a93773e953 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -12,7 +12,7 @@ angular.module('faradayApp') var noSwitcher = [ "", "home", "login", "index", "workspaces", "users", "licenses", "taskgroup", "executive", // Only for white versions!! - "vulndb", "comparison", "webshell", "help" + "vulndb", "comparison", "webshell", "help", "forbidden" ]; return noSwitcher.indexOf($scope.component) < 0; }; From b37c4ffe64768b04201e515de217dc4508c72474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 19:20:57 -0300 Subject: [PATCH 1330/1506] Improve workspace progress widget when no dates set --- .../controllers/workspaceProgressCtrl.js | 4 ++-- .../partials/workspace-progress.html | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/server/www/scripts/dashboard/controllers/workspaceProgressCtrl.js b/server/www/scripts/dashboard/controllers/workspaceProgressCtrl.js index 3d2c65260cc..bec0a16973f 100644 --- a/server/www/scripts/dashboard/controllers/workspaceProgressCtrl.js +++ b/server/www/scripts/dashboard/controllers/workspaceProgressCtrl.js @@ -33,7 +33,7 @@ angular.module('faradayApp') today = new Date(), total = 0; - if(duration.start == "" || duration.end == "") { + if(!duration.start || !duration.end) { progress = null; } else { today = today.getTime(); @@ -53,4 +53,4 @@ angular.module('faradayApp') dashboardSrv.registerCallback(init); init(); - }]); \ No newline at end of file + }]); diff --git a/server/www/scripts/dashboard/partials/workspace-progress.html b/server/www/scripts/dashboard/partials/workspace-progress.html index 6bbc9501544..ae1b3577a84 100644 --- a/server/www/scripts/dashboard/partials/workspace-progress.html +++ b/server/www/scripts/dashboard/partials/workspace-progress.html @@ -6,23 +6,25 @@

+
+ +

Start date and end date are required

+
{{wsProgress}}%
- Start date: {{wsStart | amUtc | amDateFormat:'MM/DD/YYYY'}} + Start date: {{wsStart | amUtc | amDateFormat:'MM/DD/YYYY'}} + The workspace doesn't have a start date
- End date: {{wsEnd | amUtc | amDateFormat:'MM/DD/YYYY'}} + End date: {{wsEnd | amUtc | amDateFormat:'MM/DD/YYYY'}} + The workspace doesn't have an end date
-
- -

Start date and end date are required

-
From 79190f8184b94449d01fb6679b39a78c8b65c240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 12 Jun 2018 19:28:11 -0300 Subject: [PATCH 1331/1506] Make scope work when globally editing a workspace This is really ugly, but appearently works. Ticket #4864 --- server/www/scripts/commons/controllers/headerCtrl.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 3a93773e953..66a49b198a1 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -81,6 +81,13 @@ angular.module('faradayApp') } }); + // copy pasted from server/www/scripts/workspaces/controllers/workspaces.js + // it makes scope work properly (i think) + workspace.scope = workspace.scope.map(function(scope){ + return {key: scope} + }); + if (workspace.scope.length == 0) workspace.scope.push({key: ''}); + var oldName = $scope.workspace; var modal = $uibModal.open({ templateUrl: 'scripts/workspaces/partials/modalEdit.html', From 0b0998465ae741bcfcbbfc43327692ad555d318e Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 12 Jun 2018 20:16:48 -0300 Subject: [PATCH 1332/1506] Fix burp plugin --- plugins/repo/burp/faraday-burp.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/repo/burp/faraday-burp.rb b/plugins/repo/burp/faraday-burp.rb index 3b6ee4b6627..de62215ba5b 100644 --- a/plugins/repo/burp/faraday-burp.rb +++ b/plugins/repo/burp/faraday-burp.rb @@ -7,7 +7,7 @@ #__author__ = "Francisco Amato" #__copyright__ = "Copyright (c) 2014, Infobyte LLC" #__credits__ = ["Francisco Amato", "Micaela Ranea Sanchez"] -#__version__ = "1.3.0" +#__version__ = "1.4.0" #__maintainer__ = "Francisco Amato" #__email__ = "famato@infobytesec.com" #__status__ = "Development" @@ -17,7 +17,7 @@ require "pp" -PLUGINVERSION="Faraday v1.3 Ruby" +PLUGINVERSION="Faraday v1.4 Ruby" #Tested: Burp Professional v1.6.09 XMLRPC::Config.module_eval do @@ -250,10 +250,6 @@ def newScanIssue(issue, ctx=nil, import=nil) s_id = @server.call("createAndAddServiceToInterface",h_id, i_id, issue.getProtocol(),"tcp",[port],"open") - #Save website - n_id = @server.call("createAndAddNoteToService",h_id,s_id,"website","") - n2_id = @server.call("createAndAddNoteToNote",h_id,s_id,n_id,host,"") - path = "" response = "" request = "" From 33b24c09de848466470bd8d708450bd7c4d1abae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Jun 2018 12:28:27 -0300 Subject: [PATCH 1333/1506] Show at most 10 commands in the dashboard command history --- server/www/scripts/commons/providers/server.js | 4 ++-- server/www/scripts/dashboard/providers/dashboard.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/www/scripts/commons/providers/server.js b/server/www/scripts/commons/providers/server.js index afccaa17d12..b0f5df62c7a 100644 --- a/server/www/scripts/commons/providers/server.js +++ b/server/www/scripts/commons/providers/server.js @@ -258,8 +258,8 @@ angular.module("faradayApp") return get(getUrl, data); } - ServerAPI.getCommands = function(wsName, data) { - var getUrl = createGetUrl(wsName, 'commands'); + ServerAPI.getCommands = function(wsName, data, onlyLastCommands) { + var getUrl = createGetUrl(wsName, 'commands') + (onlyLastCommands ? '?page_size=10&page=1' : ''); return get(getUrl, data); } diff --git a/server/www/scripts/dashboard/providers/dashboard.js b/server/www/scripts/dashboard/providers/dashboard.js index 24615e15a59..d8e9c86d8a4 100644 --- a/server/www/scripts/dashboard/providers/dashboard.js +++ b/server/www/scripts/dashboard/providers/dashboard.js @@ -256,7 +256,7 @@ angular.module('faradayApp') dashboardSrv.getCommands = function(ws) { var deferred = $q.defer(); - ServerAPI.getCommands(ws) + ServerAPI.getCommands(ws, undefined, true) .then(function(res) { var tmp = []; res.data.commands.forEach(function(cmd) { From a909b277fc21509767af39b273706d7bd2c2eeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Jun 2018 12:29:34 -0300 Subject: [PATCH 1334/1506] Add pagination and default sorting for commands api --- server/api/modules/commandsrun.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/api/modules/commandsrun.py b/server/api/modules/commandsrun.py index e48861b130e..cd6f5bdfa38 100644 --- a/server/api/modules/commandsrun.py +++ b/server/api/modules/commandsrun.py @@ -9,7 +9,7 @@ from flask_classful import route from marshmallow import fields, post_load -from server.api.base import AutoSchema, ReadWriteWorkspacedView +from server.api.base import AutoSchema, ReadWriteWorkspacedView, PaginatedMixin from server.utils.logger import get_logger from server.utils.web import ( gzipped, @@ -55,11 +55,12 @@ class Meta: 'params', 'user', 'workspace', 'tool', 'import_source') -class CommandView(ReadWriteWorkspacedView): +class CommandView(PaginatedMixin, ReadWriteWorkspacedView): route_base = 'commands' model_class = Command schema_class = CommandSchema get_joinedloads = [Command.workspace] + order_field = Command.start_date.desc() def _envelope_list(self, objects, pagination_metadata=None): commands = [] From 914d8a94ac01fddb0d361e202e37eb1171346aac Mon Sep 17 00:00:00 2001 From: montive Date: Wed, 13 Jun 2018 12:45:02 -0300 Subject: [PATCH 1335/1506] Add Wfuzz Plugin --- RELEASE.md | 1 + plugins/repo/wfuzz/__init__.py | 6 ++ plugins/repo/wfuzz/plugin.py | 103 +++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 plugins/repo/wfuzz/__init__.py create mode 100644 plugins/repo/wfuzz/plugin.py diff --git a/RELEASE.md b/RELEASE.md index d1925242e10..c7984c60134 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -32,6 +32,7 @@ TBA: * Add new screenshot fplugin which takes a screenshot of the ip:ports of a given protocol * Add fix for net sparker regular and cloud fix on severity * Removed Chat feature (data is kept inside notes) +* Add new plugin wfuzz November 17, 2017: --- diff --git a/plugins/repo/wfuzz/__init__.py b/plugins/repo/wfuzz/__init__.py new file mode 100644 index 00000000000..004c49be6c6 --- /dev/null +++ b/plugins/repo/wfuzz/__init__.py @@ -0,0 +1,6 @@ +''' +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' diff --git a/plugins/repo/wfuzz/plugin.py b/plugins/repo/wfuzz/plugin.py new file mode 100644 index 00000000000..0d75c784dcd --- /dev/null +++ b/plugins/repo/wfuzz/plugin.py @@ -0,0 +1,103 @@ +from plugins import core +from urlparse import urljoin, urlparse + + +class WfuzzPlugin(core.PluginBase): + + def __init__(self): + core.PluginBase.__init__(self) + self.id = "Wfuzz" + self.name = "Wfuzz Plugin" + self.plugin_version = "0.0.1" + self.version = "2.2.11" + self.options = None + + self.host = None + self.port = None + self.protocol = None + self.fail = None + + def parseData(self, output): + + data = { + 'target' : '', + 'findings' : [ + + ] + } + for line in output: + + if(line.startswith('Target')): + data['target'] = line[8:].rstrip() + + if(line.startswith('0')): + aux = line.split(' ') + res = {} + for item in aux: + if 'C=' in item: + res['response'] = int(item.replace('C=', '')) + elif 'L' in item and ' ' in item: + res['lines'] = int(item.replace('L', '')) + elif 'W' in item and ' ' in item: + res['words'] = int(item.replace('W', '')) + elif 'Ch' in item and ' ' in item: + res['chars'] = int(item.replace('Ch', '')) + else: + res['request'] = item.rstrip().replace('"', '') + data['findings'].append(res) + + return data + + + def parseOutputString(self, output, debug=False): + output_list = output.split('\n') + + print output_list + info = self.parseData(output_list) + + target = info['target'] + target_url = urlparse(target) + port = 80 + if target_url.scheme == 'https': + port = 443 + custom_port = target_url.netloc.split(':') + if len(custom_port) > 1: + port = custom_port[1] + + host_id = self.createAndAddHost(target) + + service_id = self.createAndAddServiceToHost(host_id,name="http",protocol="tcp", ports=[port] ) + + for item in info['findings']: + path = item['request'] + status = item['response'] + url = urljoin(target, path) + lines = item['lines'] + chars = item['chars'] + words = item['words'] + name = "Wfuzz found: {path} with status {status} on url {url}".format(path=path, status=status, url=url) + desc = 'Wfuzz found a response with status {status}. Response contains: \n* {words} words \n* {lines} lines \n* {chars} chars'.format( + words=words, + url=url, + lines=lines, + chars=chars, + status=status + ) + vuln = self.createAndAddVulnWebToService(host_id, + service_id, + name, + desc, + severity="info", + website=target, + path=path + ) + + +def createPlugin(): + return WfuzzPlugin() + + +if __name__ == '__main__': + parser = WfuzzPlugin() + with open("/home/javier/salida", "r") as report: + parser.parseOutputString(report) From ef02e11711d3ff9513d7da71e2ed169404e6be2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Jun 2018 14:24:47 -0300 Subject: [PATCH 1336/1506] Add message when no credentials found in a workspace --- server/www/scripts/credentials/partials/list.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/www/scripts/credentials/partials/list.html b/server/www/scripts/credentials/partials/list.html index deb6611ac6b..2cd8b461af0 100644 --- a/server/www/scripts/credentials/partials/list.html +++ b/server/www/scripts/credentials/partials/list.html @@ -74,6 +74,9 @@

End Date Vulns HostsLast Modified Services
{{objects[ws.name]['total_vulns']}} {{objects[ws.name]['hosts']}}
+

+ No credentials found in this workspace +

From 41b5c9a1b9594705612a2000bcaefb0d6361fc16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 13 Jun 2018 14:47:29 -0300 Subject: [PATCH 1337/1506] Wrap text columns in vuln template list --- server/www/scripts/vulndb/partials/vulndb.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/www/scripts/vulndb/partials/vulndb.html b/server/www/scripts/vulndb/partials/vulndb.html index 9fb37ee198d..ecaad043614 100644 --- a/server/www/scripts/vulndb/partials/vulndb.html +++ b/server/www/scripts/vulndb/partials/vulndb.html @@ -71,11 +71,11 @@ selection-model-mode="multiple-additive" selection-model-selected-class="multi-selected" selection-model-on-change="selectedModels()"> -
{{model.name}}{{model.description}}{{model.resolution}}{{model.exploitation}}{{model.name}}{{model.description}}{{model.resolution}}{{model.exploitation}}
From d71c6b449e42152e769417e69eaa3ebdeebdb2c9 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Wed, 13 Jun 2018 15:00:45 -0300 Subject: [PATCH 1338/1506] Fix url in status report upload report --- server/www/scripts/statusReport/controllers/statusReport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index 0d6c50891db..e047e7a8114 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -46,6 +46,7 @@ angular.module("faradayApp") var init = function() { $scope.baseurl = BASEURL; + console.log($scope.baseurl); $scope.severities = SEVERITIES; $scope.easeofresolution = EASEOFRESOLUTION; $scope.propertyGroupBy = $routeParams.groupbyId; @@ -1112,12 +1113,11 @@ angular.module("faradayApp") toggleFileUpload(); } }; - $scope.uploadFile = function() { var fd = new FormData(); fd.append('csrf_token', $scope.csrf_token); fd.append('file', $scope.fileToUpload); - $http.post('http://localhost:5985/_api/v2/ws/' + $scope.workspace + '/upload_report', fd, { + $http.post($scope.baseurl + '_api/v2/ws/' + $scope.workspace + '/upload_report', fd, { transformRequest: angular.identity, withCredentials: false, headers: {'Content-Type': undefined}, From 8d44b06f44b205bc83f6c3e568c44a8557f67365 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Wed, 13 Jun 2018 15:10:02 -0300 Subject: [PATCH 1339/1506] Fix align status report --- server/www/scripts/dashboard/partials/services.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/dashboard/partials/services.html b/server/www/scripts/dashboard/partials/services.html index df27d16760b..1642ae72a15 100644 --- a/server/www/scripts/dashboard/partials/services.html +++ b/server/www/scripts/dashboard/partials/services.html @@ -15,7 +15,7 @@

Services report
-
+
{{srv.count}}
From 30089e3dacadd0625f0dd73c48e16511f1ab1e39 Mon Sep 17 00:00:00 2001 From: montive Date: Wed, 13 Jun 2018 15:38:19 -0300 Subject: [PATCH 1340/1506] Se agrego self._command_regex pero sigue sin funcionar en la terminal del GTK --- plugins/repo/wfuzz/plugin.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/repo/wfuzz/plugin.py b/plugins/repo/wfuzz/plugin.py index 0d75c784dcd..c9558eedd01 100644 --- a/plugins/repo/wfuzz/plugin.py +++ b/plugins/repo/wfuzz/plugin.py @@ -1,3 +1,5 @@ +import sys +import re from plugins import core from urlparse import urljoin, urlparse @@ -16,6 +18,8 @@ def __init__(self): self.port = None self.protocol = None self.fail = None + self._command_regex = re.compile( + r'^(wfuzz).*?') def parseData(self, output): @@ -50,14 +54,14 @@ def parseData(self, output): def parseOutputString(self, output, debug=False): - output_list = output.split('\n') - print output_list + output_list = output.split('\n') info = self.parseData(output_list) target = info['target'] target_url = urlparse(target) port = 80 + if target_url.scheme == 'https': port = 443 custom_port = target_url.netloc.split(':') @@ -92,6 +96,7 @@ def parseOutputString(self, output, debug=False): path=path ) + def createPlugin(): return WfuzzPlugin() @@ -100,4 +105,4 @@ def createPlugin(): if __name__ == '__main__': parser = WfuzzPlugin() with open("/home/javier/salida", "r") as report: - parser.parseOutputString(report) + parser.parseOutputString(report.read()) From b3a65056652a1b909479a2ee5a2c523d4c74ea18 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Wed, 13 Jun 2018 15:44:34 -0300 Subject: [PATCH 1341/1506] Fix workspace switcher in commertial header data_analysis --- server/www/scripts/commons/controllers/headerCtrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 66a49b198a1..9212d80c6b3 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -12,7 +12,7 @@ angular.module('faradayApp') var noSwitcher = [ "", "home", "login", "index", "workspaces", "users", "licenses", "taskgroup", "executive", // Only for white versions!! - "vulndb", "comparison", "webshell", "help", "forbidden" + "vulndb", "comparison", "webshell", "help", "forbidden", "data_analysis" ]; return noSwitcher.indexOf($scope.component) < 0; }; From 21f842dcae335bef4a7fd30cf6d79a8b7666c84b Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 13 Jun 2018 15:58:13 -0300 Subject: [PATCH 1342/1506] [FIX] check for pid file first --- faraday-server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/faraday-server.py b/faraday-server.py index 20d7e5e1c3b..470199d7278 100755 --- a/faraday-server.py +++ b/faraday-server.py @@ -131,11 +131,11 @@ def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = sock.connect_ex((args.bind_address or server.config.faraday_server.bind_address, int(args.port or server.config.faraday_server.port))) - if result == 0: - logger.error("Faraday server port in use. Check your processes and run the server again...") + if is_server_running(): sys.exit(1) - if is_server_running(): + if result == 0: + logger.error("Faraday server port in use. Check your processes and run the server again...") sys.exit(1) # Overwrites config option if SSL is set by argument From 36fb7d54c14c1b4a2c16da5e570d89d38701fd0f Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 13 Jun 2018 17:16:47 -0300 Subject: [PATCH 1343/1506] [FIX] Add one unique handler for each signal --- server/web.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server/web.py b/server/web.py index 8b96711d200..df409425dc9 100644 --- a/server/web.py +++ b/server/web.py @@ -133,12 +133,13 @@ def __build_websockets_resource(self): return factory def install_signal(self): - def signal_handler(*args): - logger.info("Stopping threads, please wait...") - # teardown() - self.raw_report_processor.stop() - reactor.stop() for sig in (SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM): + def signal_handler(*args): + logger.info("Stopping threads, please wait...") + # teardown() + self.raw_report_processor.stop() + reactor.stop() + signal(sig, signal_handler) def run(self): From 5c7d25bd24e251bb4defb880ef22ca3923967b72 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 13 Jun 2018 18:03:22 -0300 Subject: [PATCH 1344/1506] [FIX] Improve logging. Use proper logging for thread. Don't block upload report' --- server/api/modules/upload_reports.py | 14 ++++++-------- server/utils/daemonize.py | 2 +- server/utils/logger.py | 8 +++++++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/server/api/modules/upload_reports.py b/server/api/modules/upload_reports.py index 1a4c1509df0..edd75cc080f 100644 --- a/server/api/modules/upload_reports.py +++ b/server/api/modules/upload_reports.py @@ -81,7 +81,7 @@ def run(self): while not self._stop: try: workspace, file_path, cookie = UPLOAD_REPORTS_QUEUE.get(False, timeout=0.1) - logger.info('Processing raw report {0}'.format(file_path)) + get_logger().info('Processing raw report {0}'.format(file_path)) # Cookie of user, used to create objects in server with the right owner. server.FARADAY_UPLOAD_REPORTS_WEB_COOKIE = cookie @@ -91,13 +91,15 @@ def run(self): if not command_id: continue self.end_event.wait() + get_logger().info('Report processing of report {0} finished'.format(file_path)) self.end_event.clear() except Empty: time.sleep(0.1) except KeyboardInterrupt as ex: - break + get_logger().info('Keyboard interrupt, stopping report processing thread') + self.stop() except Exception as ex: - logger.exception(ex) + get_logger().exception(ex) continue @@ -136,8 +138,4 @@ def file_upload(workspace=None): output.write(report_file.read()) UPLOAD_REPORTS_QUEUE.put((workspace, file_path, request.cookies)) - command_id = UPLOAD_REPORTS_CMD_QUEUE.get() - if command_id: - # return jsonify({"status": "processing", "command_id": command_id}) - return redirect('/#/dashboard/ws/' + workspace) - abort(make_response(jsonify(message="Invalid file."), 400)) + return redirect('/#/dashboard/ws/' + workspace) diff --git a/server/utils/daemonize.py b/server/utils/daemonize.py index 021dc6e9690..a9029306930 100644 --- a/server/utils/daemonize.py +++ b/server/utils/daemonize.py @@ -140,13 +140,13 @@ def start_server(): def stop_server(): """Stops Faraday Server if it isn't running""" logger = get_logger(__name__) - pid = is_server_running() if pid is None: logger.error('Faraday Server is not running') return False try: + logger.info('Sending SIGTERM to pid {0}'.format(pid)) os.kill(pid, signal.SIGTERM) except OSError, err: if err.errno == errno.EPERM: diff --git a/server/utils/logger.py b/server/utils/logger.py index bf02a22e013..711dfae0a50 100644 --- a/server/utils/logger.py +++ b/server/utils/logger.py @@ -24,11 +24,12 @@ def setup_logging(): logger.setLevel(logging.DEBUG) formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + '%(asctime)s - %(name)s - %(levelname)s {%(threadName)s} [%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s') setup_console_logging(formatter) setup_file_logging(formatter) + def setup_console_logging(formatter): console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) @@ -36,6 +37,7 @@ def setup_console_logging(formatter): add_handler(console_handler) LVL_SETTABLE_HANDLERS.append(console_handler) + def setup_file_logging(formatter): create_logging_path() file_handler = logging.handlers.RotatingFileHandler( @@ -44,11 +46,13 @@ def setup_file_logging(formatter): file_handler.setLevel(logging.DEBUG) add_handler(file_handler) + def add_handler(handler): logger = logging.getLogger(ROOT_LOGGER) logger.addHandler(handler) LOGGING_HANDLERS.append(handler) + def get_logger(obj=None): """Creates a logger named by a string or an object's class name. Allowing logger to additionally accept strings as names @@ -64,11 +68,13 @@ def get_logger(obj=None): return logger + def set_logging_level(level): server.config.LOGGING_LEVEL = level for handler in LVL_SETTABLE_HANDLERS: handler.setLevel(level) + def create_logging_path(): try: os.makedirs(os.path.dirname(LOG_FILE)) From cf44305e90bd857290b1fb9d4dfc290b5b9a1d09 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 13 Jun 2018 18:07:48 -0300 Subject: [PATCH 1345/1506] Remove white spaces --- server/utils/logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/utils/logger.py b/server/utils/logger.py index 711dfae0a50..a60f05b1766 100644 --- a/server/utils/logger.py +++ b/server/utils/logger.py @@ -18,13 +18,14 @@ LOGGING_HANDLERS = [] LVL_SETTABLE_HANDLERS = [] + def setup_logging(): logger = logging.getLogger(ROOT_LOGGER) logger.propagate = False logger.setLevel(logging.DEBUG) formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s {%(threadName)s} [%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s') + '%(asctime)s - %(name)s - %(levelname)s {%(threadName)s} [%(filename)s:%(lineno)s - %(funcName)s() ] %(message)s') setup_console_logging(formatter) setup_file_logging(formatter) From 5060f7260c74d81915236eaed062676917006022 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 14 Jun 2018 13:11:03 -0300 Subject: [PATCH 1346/1506] [ADD] OS detection to install.sh --- install.sh | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index 6e44a1c343b..baead324478 100755 --- a/install.sh +++ b/install.sh @@ -10,14 +10,30 @@ if [ $EUID -ne 0 ]; then exit 1 fi -apt-get update +case `uname` in + Linux ) + LINUX=1 + type apt-get >/dev/null 2>&1 && { package_manager='apt-get'; } + type apt >/dev/null 2>&1 && { package_manager='apt'; } + ;; + Darwin ) + DARWIN=1 + type brew >/dev/null 2>&1 && { package_manager='brew'; } + ;; +esac -#Install community dependencies -for pkg in build-essential python-setuptools python-pip python-dev libpq-dev libffi-dev gir1.2-gtk-3.0 gir1.2-vte-2.91 python-gobject zsh curl python-psycopg2 ; do - apt-get install -y $pkg -done + +if [ "$package_manager" == "apt-get" ] +then + apt-get update + + #Install community dependencies + for pkg in build-essential python-setuptools python-pip python-dev libpq-dev libffi-dev gir1.2-gtk-3.0 gir1.2-vte-2.91 python-gobject zsh curl python-psycopg2 ; do + apt-get install -y $pkg + done +fi pip2 install -r requirements_server.txt pip2 install -r requirements.txt -echo "You can now run Faraday, enjoy!" \ No newline at end of file +echo "You can now run Faraday, enjoy!" From 8ee5b96312730f39002c731fa8c931b20bf93c99 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Thu, 14 Jun 2018 14:46:07 -0300 Subject: [PATCH 1347/1506] [FIX] w3af and zap expected xml parsing --- plugins/repo/w3af/plugin.py | 28 +++++++++++++++++++++------- plugins/repo/zap/plugin.py | 13 ++++++++++--- server/app.py | 2 +- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/plugins/repo/w3af/plugin.py b/plugins/repo/w3af/plugin.py index a45e537f2bf..30a4098a7bc 100644 --- a/plugins/repo/w3af/plugin.py +++ b/plugins/repo/w3af/plugin.py @@ -84,7 +84,11 @@ def get_items(self, tree): """ bugtype = "" - scaninfo = tree.findall('scan-info')[0] + if len(tree.findall('scan-info')) == 0: + scaninfo = tree.findall('scaninfo')[0] + else: + scaninfo = tree.findall('scan-info')[0] + self.target = scaninfo.get('target') host = re.search( "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", self.target) @@ -165,16 +169,26 @@ def __init__(self, item_node): self.req = self.resp = '' for tx in self.node.findall('http-transactions/http-transaction'): - self.req = tx.find('http-request/status').text - for h in tx.findall('http-request/headers/header'): + if tx.find('http-request'): + hreq = tx.find('http-request') + else: + hreq = tx.find('httprequest') + + if tx.find('http-response'): + hres = tx.find('http-response') + else: + hres = tx.find('httpresponse') + + self.req = hreq.find('status').text + for h in hreq.findall('headers/header'): self.req += "\n%s: %s" % (h.get('field'), h.get('content')) - self.resp = tx.find('http-response/status').text - for h in tx.findall('http-response/headers/header'): + self.resp = hres.find('status').text + for h in hres.findall('headers/header'): self.resp += "\n%s: %s" % (h.get('field'), h.get('content')) - if tx.find('http-response/body'): - self.resp += "\n%s" % tx.find('http-response/body').text + if hres.find('body'): + self.resp += "\n%s" % hres.find('body').text def do_clean(self, value): myreturn = "" diff --git a/plugins/repo/zap/plugin.py b/plugins/repo/zap/plugin.py index 4a243a34cdb..5b2cece0e60 100644 --- a/plugins/repo/zap/plugin.py +++ b/plugins/repo/zap/plugin.py @@ -199,10 +199,18 @@ def __init__(self, item_node): self.items = [] - for instance in item_node.find('instances'): + if item_node.find('instances'): + arr = item_node.find('instances') + else: + arr = [item_node] + + for elem in arr: + uri = elem.find('uri').text + self.parse_uri(uri) - uri = instance.find('uri').text + self.requests = "\n".join([i['uri'] for i in self.items]) + def parse_uri(self, uri): mregex = re.search( "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&" ";%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]" @@ -238,7 +246,6 @@ def __init__(self, item_node): } self.items.append(item) - self.requests = "\n".join([i['uri'] for i in self.items]) def get_text_from_subnode(self, subnode_xpath_expr): """ diff --git a/server/app.py b/server/app.py index 7f170ebee44..680995514d3 100644 --- a/server/app.py +++ b/server/app.py @@ -26,7 +26,7 @@ Security, SQLAlchemyUserDatastore, ) -from flask.ext.session import Session +from flask_session import Session from nplusone.ext.flask_sqlalchemy import NPlusOne from depot.manager import DepotManager From c05b726f53ca1ab994e10c1e0fecd66e830f2085 Mon Sep 17 00:00:00 2001 From: fedek Date: Thu, 14 Jun 2018 15:24:10 -0300 Subject: [PATCH 1348/1506] Change in the servicesbyhost modal from the dashboard to update the new UI elements Change in the workspace list to move the column DATE as the last item --- .../summarizedCtrlServicesModal.js | 1 + .../dashboard/partials/last-vulns.html | 7 +-- .../partials/modal-services-by-host.html | 59 ++++++++++++------- .../www/scripts/workspaces/partials/list.html | 5 +- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/server/www/scripts/dashboard/controllers/summarizedCtrlServicesModal.js b/server/www/scripts/dashboard/controllers/summarizedCtrlServicesModal.js index 455b0595467..ca2314b5929 100644 --- a/server/www/scripts/dashboard/controllers/summarizedCtrlServicesModal.js +++ b/server/www/scripts/dashboard/controllers/summarizedCtrlServicesModal.js @@ -11,6 +11,7 @@ angular.module('faradayApp') $scope.sortField = 'port'; $scope.sortReverse = false; $scope.osint = osint; + $scope.workspace = workspace // toggles sort field and order $scope.toggleSort = function(field) { diff --git a/server/www/scripts/dashboard/partials/last-vulns.html b/server/www/scripts/dashboard/partials/last-vulns.html index 4e1684a7fa1..eab8e8185c9 100644 --- a/server/www/scripts/dashboard/partials/last-vulns.html +++ b/server/www/scripts/dashboard/partials/last-vulns.html @@ -14,7 +14,7 @@

Last Vulnerabilities

Severity Target NameWebOwner Date
{{vuln.name}} - - - {{vuln.owner}}
+
- - - - - - + + + + + + - - + - - + - - + + -
-
Operating System: {{host.os}}
-
Created
+
+
+ OS: {{host.os | limitTo: 40}} +
+ {{host.metadata.owner}} was created + +
+
NameDescriptionPortProtocolStatus
+ Name + + Version + + Port + + Protocol + + Status +
+
{{srv.name}} - {{srv.description}} + + {{srv.version}} + {{srv.ports.toString().replace("[]", "")}} {{srv.protocol}}{{srv.status}} + {{srv.protocol}} + + {{srv.status}} +
- diff --git a/server/www/scripts/workspaces/partials/list.html b/server/www/scripts/workspaces/partials/list.html index f59769886f4..e92e002e2bc 100644 --- a/server/www/scripts/workspaces/partials/list.html +++ b/server/www/scripts/workspaces/partials/list.html @@ -49,9 +49,8 @@ End Date Vulns Hosts - - Last Modified Services + Last Modified @@ -66,8 +65,8 @@ {{objects[ws.name]['total_vulns']}} {{objects[ws.name]['hosts']}} - + From b287907544e5905133766f79ad6b2767f77046a5 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Thu, 14 Jun 2018 15:35:34 -0300 Subject: [PATCH 1349/1506] Fix bad phrase in services by host WebUi --- .../scripts/dashboard/partials/modal-services-by-host.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/www/scripts/dashboard/partials/modal-services-by-host.html b/server/www/scripts/dashboard/partials/modal-services-by-host.html index 1a6037903bb..02c164543eb 100644 --- a/server/www/scripts/dashboard/partials/modal-services-by-host.html +++ b/server/www/scripts/dashboard/partials/modal-services-by-host.html @@ -49,10 +49,10 @@

Services for
- OS: {{host.os | limitTo: 40}} + OS: {{host.os | limitTo: 40}}
- {{host.metadata.owner}} was created - + Created by {{host.metadata.owner}} +

From 1b3bd7c65fd752ba2655ebe6b65a0a6ed6efb30b Mon Sep 17 00:00:00 2001 From: fedek Date: Thu, 14 Jun 2018 15:56:06 -0300 Subject: [PATCH 1350/1506] Changed ServiceByHosts modal to new UI --- .../controllers/summarizedCtrlHostsModal.js | 1 + .../partials/modal-hosts-by-service.html | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js b/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js index f75d5214eca..b0bf10e6cb5 100644 --- a/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js +++ b/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js @@ -11,6 +11,7 @@ angular.module('faradayApp') $scope.sortField = 'name'; $scope.sortReverse = false; $scope.clipText = "Copy to Clipboard"; + $scope.workspace = workspace // toggles sort field and order $scope.toggleSort = function(field) { diff --git a/server/www/scripts/dashboard/partials/modal-hosts-by-service.html b/server/www/scripts/dashboard/partials/modal-hosts-by-service.html index 0845dfc2d7a..c1484809457 100644 --- a/server/www/scripts/dashboard/partials/modal-hosts-by-service.html +++ b/server/www/scripts/dashboard/partials/modal-hosts-by-service.html @@ -7,22 +7,32 @@

Services for {{name}} ({{hosts.length}} total)

@@ -54,7 +53,7 @@
Confirmed
- +
diff --git a/server/www/scripts/statusReport/partials/modalNew.html b/server/www/scripts/statusReport/partials/modalNew.html index 50fe7e617c2..5fff16af5e8 100644 --- a/server/www/scripts/statusReport/partials/modalNew.html +++ b/server/www/scripts/statusReport/partials/modalNew.html @@ -120,7 +120,7 @@
Ease of Resolution
- +
From 23a0e4eeec5fefc31af1f2b3fa6a051acaa94da9 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Thu, 14 Jun 2018 17:04:11 -0300 Subject: [PATCH 1352/1506] Fix impact position in WebUi status report --- server/www/estilos.css | 21 +++++++++++++++++++ .../statusReport/partials/modalEdit.html | 7 ++++--- .../statusReport/partials/modalNew.html | 8 +++---- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/server/www/estilos.css b/server/www/estilos.css index e5c1d780b0b..789185937ea 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -1288,3 +1288,24 @@ a.button-disable{cursor: not-allowed;pointer-events: none;opacity: 0.5} .tooltip-inner { white-space: pre-wrap; } + +/* deduplication of CSS - StatusResport */ +.label-impact { + display: inline; + padding: .2em .6em .3em; + font-size: 100%; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + -webkit-border-radius: .25em; + border-radius: .25em + } + .label-default-impact { + background-color: #777 + } + .label-success-impact { + background-color: #5cb85c + } \ No newline at end of file diff --git a/server/www/scripts/statusReport/partials/modalEdit.html b/server/www/scripts/statusReport/partials/modalEdit.html index b6724a36418..61cf8ea4ba7 100644 --- a/server/www/scripts/statusReport/partials/modalEdit.html +++ b/server/www/scripts/statusReport/partials/modalEdit.html @@ -145,9 +145,10 @@
Confirmed

Impact

-
-

{{key}}

-
+

+ {{key}} +

+

Evidence

diff --git a/server/www/scripts/statusReport/partials/modalNew.html b/server/www/scripts/statusReport/partials/modalNew.html index 5fff16af5e8..63a3d4bb9db 100644 --- a/server/www/scripts/statusReport/partials/modalNew.html +++ b/server/www/scripts/statusReport/partials/modalNew.html @@ -218,10 +218,10 @@
Ease of Resolution

Impact

-
-

{{key}}

-
- +

+ {{key}} +

+

Evidence

- +
-
From e0d08da2ff95d036529f8efcf18a2c8e1cbf6446 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 14 Jun 2018 20:47:36 +0000 Subject: [PATCH 1360/1506] [FIX] PEP8 and create host even if no vulns were found --- plugins/repo/xsssniper/plugin.py | 87 +++++++++++++++----------------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/plugins/repo/xsssniper/plugin.py b/plugins/repo/xsssniper/plugin.py index d2c09f0c829..fef691d896e 100644 --- a/plugins/repo/xsssniper/plugin.py +++ b/plugins/repo/xsssniper/plugin.py @@ -7,65 +7,62 @@ See the file 'doc/LICENSE' for the license information ''' -from plugins import core import re import socket +from plugins import core __author__ = "Roberto Focke" __copyright__ = "Copyright (c) 2017, Infobyte LLC" __license__ = "" __version__ = "1.0.0" + class xsssniper (core.PluginBase): def __init__(self): - - core.PluginBase.__init__(self) - self.id = "xsssniper" - self.name = "xsssniper" - self.plugin_version = "0.0.1" - self.version = "1.0.0" - self.protocol="tcp" - self._command_regex = re.compile(r'^(sudo xsssniper|xsssniper|sudo xsssniper\.py|xsssniper\.py|sudo python xsssniper\.py|.\/xsssniper\.py|python xsssniper\.py)') + core.PluginBase.__init__(self) + self.id = "xsssniper" + self.name = "xsssniper" + self.plugin_version = "0.0.1" + self.version = "1.0.0" + self.protocol="tcp" + self._command_regex = re.compile(r'^(sudo xsssniper|xsssniper|sudo xsssniper\.py|xsssniper\.py|sudo python xsssniper\.py|.\/xsssniper\.py|python xsssniper\.py)') def parseOutputString(self, output, debug=False): - j=0 - parametro=[] - lineas = output.split("\n") - for linea in lineas: - if ((linea.find("--[!] Target:")>0)): - url = re.findall('(?:[-\w.]|(?:%[\da-fA-F]{2}))+', linea) - print url - - - - if ((linea.find("Method")>0)): - list = re.findall("\w+", linea) - print list[1] - metodo= list[1] - if ((linea.find("Query String:")>0)): - lista_parametros=linea.split('=') - print lista_parametros - aux=len(lista_parametros) - print aux - if ((linea.find("--[!] Param:")>0)): - list2= re.findall("\w+",linea) - print list2[1] - parametro.append(list2[1]) - host_id = self.createAndAddHost(url[3]) - address=socket.gethostbyname(url[3]) - interface_id = self.createAndAddInterface(host_id,address,ipv4_address=address,hostname_resolution=url[3]) - service_id = self.createAndAddServiceToInterface(host_id,interface_id,self.protocol,'tcp',ports=['80'],status='Open',version="", description="") - if aux !=0: - print "entro al if" - print parametro - - self.createAndAddVulnWebToService(host_id,service_id,name="xss",desc="XSS",ref='',severity='med',website=url[0],path='',method=metodo,pname='',params=parametro,request='',response='') - - - def processCommandString(self, username, current_path, command_string): - return None + parametro=[] + lineas = output.split("\n") + aux = 0 + for linea in lineas: + if not linea: + continue + linea = linea.lower() + if ((linea.find("target:")>0)): + url = re.findall('(?:[-\w.]|(?:%[\da-fA-F]{2}))+', linea) + host_id = self.createAndAddHost(url[3]) + address=socket.gethostbyname(url[3]) + interface_id = self.createAndAddInterface(host_id,address,ipv4_address=address,hostname_resolution=url[3]) + if ((linea.find("method")>0)): + list_a = re.findall("\w+", linea) + metodo= list_a[1] + if ((linea.find("query string:")>0)): + lista_parametros=linea.split('=') + aux=len(lista_parametros) + if ((linea.find("param:")>0)): + list2= re.findall("\w+",linea) + parametro.append(list2[1]) + service_id = self.createAndAddServiceToInterface(host_id,interface_id,self.protocol,'tcp',ports=['80'],status='Open',version="", description="") + if aux !=0: + self.createAndAddVulnWebToService(host_id,service_id,name="xss",desc="XSS",ref='',severity='med',website=url[0],path='',method=metodo,pname='',params=''.join(parametro),request='',response='') + + def processCommandString(self, username, current_path, command_string): + return None def createPlugin(): return xsssniper() + + +if __name__ == '__main__': + plugin_xss = xsssniper() + with open('xsssniper_out', 'r') as xsssniper_file: + plugin_xss.parseOutputString(xsssniper_file.read()) From fc7b736adcf3fcecd2b7a66d82f600ea6e617ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Jun 2018 18:21:07 -0300 Subject: [PATCH 1361/1506] Disgusting fix to the vulns counter of the header It wasn't working well when filter vulns, due to the innecesary use of global variables and function calls from angular partials. It is horrible. --- server/www/scripts/commons/controllers/headerCtrl.js | 9 ++++++--- server/www/scripts/commons/partials/header.html | 2 +- server/www/scripts/vulns/providers/vulns.js | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index 9212d80c6b3..c9a4e5e7db7 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -17,9 +17,12 @@ angular.module('faradayApp') return noSwitcher.indexOf($scope.component) < 0; }; - $scope.getVulnsNum = function() { - return vulnsManager.getVulnsNum($routeParams.wsId); - }; + // Ugly, ugly, ugly hack + $scope.vulnsNum = vulnsManager.getVulnsNum($routeParams.wsId); + setInterval(function(){ + $scope.vulnsNum = vulnsManager.getVulnsNum($routeParams.wsId); + $scope.$apply(); + }, 500) $scope.toggleConfirmed = function() { $scope.confirmed = !$scope.confirmed; diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index 67bd227ab0d..c33f0a8615d 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -25,7 +25,7 @@
- {{getVulnsNum()}} vulns total + {{vulnsNum}} vulns total
diff --git a/server/www/scripts/vulns/providers/vulns.js b/server/www/scripts/vulns/providers/vulns.js index a28195e2089..284970b59f1 100644 --- a/server/www/scripts/vulns/providers/vulns.js +++ b/server/www/scripts/vulns/providers/vulns.js @@ -75,7 +75,8 @@ angular.module('faradayApp') vulnsManager.loadVulnsCounter = function(ws){ // Ugly hack to populate the vulnsCounter global variable workspacesFact.get(ws).then(function(data){ - vulnsCounter = data.stats.total_vulns; + // Commenting this line worked. I'm not sure why + // vulnsCounter = data.stats.total_vulns; totalVulns = data.stats.total_vulns; }) }; From b3bbbc2d42b88b90d3de6162db9ea21328c9b1c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Jun 2018 18:29:51 -0300 Subject: [PATCH 1362/1506] Add header to common workspace selector --- server/www/scripts/commons/partials/header.html | 2 +- server/www/scripts/commons/partials/workspaces.html | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index c33f0a8615d..a44e2853ce7 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -1,5 +1,5 @@
-
+
@@ -25,7 +25,8 @@

Services for host {{hostName}}

-
+
+
From f50cc90484c96f2d36f2d458ffb9e1b0608bf773 Mon Sep 17 00:00:00 2001 From: fedek Date: Thu, 14 Jun 2018 18:56:32 -0300 Subject: [PATCH 1365/1506] Added header icon for missing sections, in case no icon is available section name is set --- server/www/scripts/commons/partials/header.html | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index a44e2853ce7..ffc9ee0b188 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -11,9 +11,15 @@ Credentials Executive Report Tasks + Workspaces Comparison + Webshell Vulns -

Licenses

+ Data Analysis

Workspaces

+

Users

+

Licenses

+

Help

+
@@ -43,9 +49,15 @@ Credentials Executive Report Tasks + Workspaces Comparison + Webshell Vulns -

Licenses

+ Data Analysis

Workspaces

+

Users

+

Licenses

+

Help

+
From aef1f4f9780250f6941fb42ac6e4b5a9c9040114 Mon Sep 17 00:00:00 2001 From: fedek Date: Thu, 14 Jun 2018 19:00:24 -0300 Subject: [PATCH 1366/1506] Oops, header had a bad definition from previous commit --- server/www/scripts/commons/partials/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index ffc9ee0b188..4346db648a2 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -56,7 +56,7 @@

Workspaces

Users

Licenses

-

Help

+

Help

From 8a265039611e74d78715d77f10c035513e2b5ffb Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Thu, 14 Jun 2018 19:20:05 -0300 Subject: [PATCH 1367/1506] Change ngClip to angular-clipboard, now "Copy to Clipboard" works! --- server/www/index.html | 3 +- server/www/script/ZeroClipboard.min.js | 9 -- server/www/script/ZeroClipboard.swf | Bin 4036 -> 0 bytes server/www/script/angular-clipboard.js | 101 ++++++++++++++++++ server/www/script/ngClip.js | 84 --------------- server/www/scripts/app.js | 7 +- .../controllers/summarizedCtrlHostsModal.js | 4 +- .../partials/modal-hosts-by-service.html | 2 +- 8 files changed, 108 insertions(+), 102 deletions(-) delete mode 100644 server/www/script/ZeroClipboard.min.js delete mode 100644 server/www/script/ZeroClipboard.swf create mode 100644 server/www/script/angular-clipboard.js delete mode 100644 server/www/script/ngClip.js diff --git a/server/www/index.html b/server/www/index.html index fb98c77883a..7d5f5fd27ec 100644 --- a/server/www/index.html +++ b/server/www/index.html @@ -69,12 +69,11 @@ - - + diff --git a/server/www/script/ZeroClipboard.min.js b/server/www/script/ZeroClipboard.min.js deleted file mode 100644 index 365737f559a..00000000000 --- a/server/www/script/ZeroClipboard.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * ZeroClipboard - * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. - * Copyright (c) 2014 Jon Rohan, James M. Greene - * Licensed MIT - * http://zeroclipboard.org/ - * v2.1.6 - */ -!function(a,b){"use strict";var c,d,e=a,f=e.document,g=e.navigator,h=e.setTimeout,i=e.encodeURIComponent,j=e.ActiveXObject,k=e.Error,l=e.Number.parseInt||e.parseInt,m=e.Number.parseFloat||e.parseFloat,n=e.Number.isNaN||e.isNaN,o=e.Math.round,p=e.Date.now,q=e.Object.keys,r=e.Object.defineProperty,s=e.Object.prototype.hasOwnProperty,t=e.Array.prototype.slice,u=function(){var a=function(a){return a};if("function"==typeof e.wrap&&"function"==typeof e.unwrap)try{var b=f.createElement("div"),c=e.unwrap(b);1===b.nodeType&&c&&1===c.nodeType&&(a=e.unwrap)}catch(d){}return a}(),v=function(a){return t.call(a,0)},w=function(){var a,c,d,e,f,g,h=v(arguments),i=h[0]||{};for(a=1,c=h.length;c>a;a++)if(null!=(d=h[a]))for(e in d)s.call(d,e)&&(f=i[e],g=d[e],i!==g&&g!==b&&(i[e]=g));return i},x=function(a){var b,c,d,e;if("object"!=typeof a||null==a)b=a;else if("number"==typeof a.length)for(b=[],c=0,d=a.length;d>c;c++)s.call(a,c)&&(b[c]=x(a[c]));else{b={};for(e in a)s.call(a,e)&&(b[e]=x(a[e]))}return b},y=function(a,b){for(var c={},d=0,e=b.length;e>d;d++)b[d]in a&&(c[b[d]]=a[b[d]]);return c},z=function(a,b){var c={};for(var d in a)-1===b.indexOf(d)&&(c[d]=a[d]);return c},A=function(a){if(a)for(var b in a)s.call(a,b)&&delete a[b];return a},B=function(a,b){if(a&&1===a.nodeType&&a.ownerDocument&&b&&(1===b.nodeType&&b.ownerDocument&&b.ownerDocument===a.ownerDocument||9===b.nodeType&&!b.ownerDocument&&b===a.ownerDocument))do{if(a===b)return!0;a=a.parentNode}while(a);return!1},C=function(a){var b;return"string"==typeof a&&a&&(b=a.split("#")[0].split("?")[0],b=a.slice(0,a.lastIndexOf("/")+1)),b},D=function(a){var b,c;return"string"==typeof a&&a&&(c=a.match(/^(?:|[^:@]*@|.+\)@(?=http[s]?|file)|.+?\s+(?: at |@)(?:[^:\(]+ )*[\(]?)((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]?b=c[1]:(c=a.match(/\)@((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]&&(b=c[1]))),b},E=function(){var a,b;try{throw new k}catch(c){b=c}return b&&(a=b.sourceURL||b.fileName||D(b.stack)),a},F=function(){var a,c,d;if(f.currentScript&&(a=f.currentScript.src))return a;if(c=f.getElementsByTagName("script"),1===c.length)return c[0].src||b;if("readyState"in c[0])for(d=c.length;d--;)if("interactive"===c[d].readyState&&(a=c[d].src))return a;return"loading"===f.readyState&&(a=c[c.length-1].src)?a:(a=E())?a:b},G=function(){var a,c,d,e=f.getElementsByTagName("script");for(a=e.length;a--;){if(!(d=e[a].src)){c=null;break}if(d=C(d),null==c)c=d;else if(c!==d){c=null;break}}return c||b},H=function(){var a=C(F())||G()||"";return a+"ZeroClipboard.swf"},I={bridge:null,version:"0.0.0",pluginType:"unknown",disabled:null,outdated:null,unavailable:null,deactivated:null,overdue:null,ready:null},J="11.0.0",K={},L={},M=null,N={ready:"Flash communication is established",error:{"flash-disabled":"Flash is disabled or not installed","flash-outdated":"Flash is too outdated to support ZeroClipboard","flash-unavailable":"Flash is unable to communicate bidirectionally with JavaScript","flash-deactivated":"Flash is too outdated for your browser and/or is configured as click-to-activate","flash-overdue":"Flash communication was established but NOT within the acceptable time limit"}},O={swfPath:H(),trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceEnhancedClipboard:!1,flashLoadTimeout:3e4,autoActivate:!0,bubbleEvents:!0,containerId:"global-zeroclipboard-html-bridge",containerClass:"global-zeroclipboard-container",swfObjectId:"global-zeroclipboard-flash-bridge",hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",forceHandCursor:!1,title:null,zIndex:999999999},P=function(a){if("object"==typeof a&&null!==a)for(var b in a)if(s.call(a,b))if(/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(b))O[b]=a[b];else if(null==I.bridge)if("containerId"===b||"swfObjectId"===b){if(!cb(a[b]))throw new Error("The specified `"+b+"` value is not valid as an HTML4 Element ID");O[b]=a[b]}else O[b]=a[b];{if("string"!=typeof a||!a)return x(O);if(s.call(O,a))return O[a]}},Q=function(){return{browser:y(g,["userAgent","platform","appName"]),flash:z(I,["bridge"]),zeroclipboard:{version:Fb.version,config:Fb.config()}}},R=function(){return!!(I.disabled||I.outdated||I.unavailable||I.deactivated)},S=function(a,b){var c,d,e,f={};if("string"==typeof a&&a)e=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.on(c,a[c]);if(e&&e.length){for(c=0,d=e.length;d>c;c++)a=e[c].replace(/^on/,""),f[a]=!0,K[a]||(K[a]=[]),K[a].push(b);if(f.ready&&I.ready&&Fb.emit({type:"ready"}),f.error){var g=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=g.length;d>c;c++)if(I[g[c]]===!0){Fb.emit({type:"error",name:"flash-"+g[c]});break}}}return Fb},T=function(a,b){var c,d,e,f,g;if(0===arguments.length)f=q(K);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=K[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return Fb},U=function(a){var b;return b="string"==typeof a&&a?x(K[a])||null:x(K)},V=function(a){var b,c,d;return a=db(a),a&&!jb(a)?"ready"===a.type&&I.overdue===!0?Fb.emit({type:"error",name:"flash-overdue"}):(b=w({},a),ib.call(this,b),"copy"===a.type&&(d=pb(L),c=d.data,M=d.formatMap),c):void 0},W=function(){if("boolean"!=typeof I.ready&&(I.ready=!1),!Fb.isFlashUnusable()&&null===I.bridge){var a=O.flashLoadTimeout;"number"==typeof a&&a>=0&&h(function(){"boolean"!=typeof I.deactivated&&(I.deactivated=!0),I.deactivated===!0&&Fb.emit({type:"error",name:"flash-deactivated"})},a),I.overdue=!1,nb()}},X=function(){Fb.clearData(),Fb.blur(),Fb.emit("destroy"),ob(),Fb.off()},Y=function(a,b){var c;if("object"==typeof a&&a&&"undefined"==typeof b)c=a,Fb.clearData();else{if("string"!=typeof a||!a)return;c={},c[a]=b}for(var d in c)"string"==typeof d&&d&&s.call(c,d)&&"string"==typeof c[d]&&c[d]&&(L[d]=c[d])},Z=function(a){"undefined"==typeof a?(A(L),M=null):"string"==typeof a&&s.call(L,a)&&delete L[a]},$=function(a){return"undefined"==typeof a?x(L):"string"==typeof a&&s.call(L,a)?L[a]:void 0},_=function(a){if(a&&1===a.nodeType){c&&(xb(c,O.activeClass),c!==a&&xb(c,O.hoverClass)),c=a,wb(a,O.hoverClass);var b=a.getAttribute("title")||O.title;if("string"==typeof b&&b){var d=mb(I.bridge);d&&d.setAttribute("title",b)}var e=O.forceHandCursor===!0||"pointer"===yb(a,"cursor");Cb(e),Bb()}},ab=function(){var a=mb(I.bridge);a&&(a.removeAttribute("title"),a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.top="1px"),c&&(xb(c,O.hoverClass),xb(c,O.activeClass),c=null)},bb=function(){return c||null},cb=function(a){return"string"==typeof a&&a&&/^[A-Za-z][A-Za-z0-9_:\-\.]*$/.test(a)},db=function(a){var b;if("string"==typeof a&&a?(b=a,a={}):"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(b=a.type),b){!a.target&&/^(copy|aftercopy|_click)$/.test(b.toLowerCase())&&(a.target=d),w(a,{type:b.toLowerCase(),target:a.target||c||null,relatedTarget:a.relatedTarget||null,currentTarget:I&&I.bridge||null,timeStamp:a.timeStamp||p()||null});var e=N[a.type];return"error"===a.type&&a.name&&e&&(e=e[a.name]),e&&(a.message=e),"ready"===a.type&&w(a,{target:null,version:I.version}),"error"===a.type&&(/^flash-(disabled|outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{target:null,minimumVersion:J}),/^flash-(outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{version:I.version})),"copy"===a.type&&(a.clipboardData={setData:Fb.setData,clearData:Fb.clearData}),"aftercopy"===a.type&&(a=qb(a,M)),a.target&&!a.relatedTarget&&(a.relatedTarget=eb(a.target)),a=fb(a)}},eb=function(a){var b=a&&a.getAttribute&&a.getAttribute("data-clipboard-target");return b?f.getElementById(b):null},fb=function(a){if(a&&/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)){var c=a.target,d="_mouseover"===a.type&&a.relatedTarget?a.relatedTarget:b,g="_mouseout"===a.type&&a.relatedTarget?a.relatedTarget:b,h=Ab(c),i=e.screenLeft||e.screenX||0,j=e.screenTop||e.screenY||0,k=f.body.scrollLeft+f.documentElement.scrollLeft,l=f.body.scrollTop+f.documentElement.scrollTop,m=h.left+("number"==typeof a._stageX?a._stageX:0),n=h.top+("number"==typeof a._stageY?a._stageY:0),o=m-k,p=n-l,q=i+o,r=j+p,s="number"==typeof a.movementX?a.movementX:0,t="number"==typeof a.movementY?a.movementY:0;delete a._stageX,delete a._stageY,w(a,{srcElement:c,fromElement:d,toElement:g,screenX:q,screenY:r,pageX:m,pageY:n,clientX:o,clientY:p,x:o,y:p,movementX:s,movementY:t,offsetX:0,offsetY:0,layerX:0,layerY:0})}return a},gb=function(a){var b=a&&"string"==typeof a.type&&a.type||"";return!/^(?:(?:before)?copy|destroy)$/.test(b)},hb=function(a,b,c,d){d?h(function(){a.apply(b,c)},0):a.apply(b,c)},ib=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=K["*"]||[],d=K[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},jb=function(a){var b=a.target||c||null,e="swf"===a._source;delete a._source;var f=["flash-disabled","flash-outdated","flash-unavailable","flash-deactivated","flash-overdue"];switch(a.type){case"error":-1!==f.indexOf(a.name)&&w(I,{disabled:"flash-disabled"===a.name,outdated:"flash-outdated"===a.name,unavailable:"flash-unavailable"===a.name,deactivated:"flash-deactivated"===a.name,overdue:"flash-overdue"===a.name,ready:!1});break;case"ready":var g=I.deactivated===!0;w(I,{disabled:!1,outdated:!1,unavailable:!1,deactivated:!1,overdue:g,ready:!g});break;case"beforecopy":d=b;break;case"copy":var h,i,j=a.relatedTarget;!L["text/html"]&&!L["text/plain"]&&j&&(i=j.value||j.outerHTML||j.innerHTML)&&(h=j.value||j.textContent||j.innerText)?(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h),i!==h&&a.clipboardData.setData("text/html",i)):!L["text/plain"]&&a.target&&(h=a.target.getAttribute("data-clipboard-text"))&&(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h));break;case"aftercopy":Fb.clearData(),b&&b!==vb()&&b.focus&&b.focus();break;case"_mouseover":Fb.focus(b),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseenter",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseover"})));break;case"_mouseout":Fb.blur(),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseleave",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseout"})));break;case"_mousedown":wb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mouseup":xb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_click":d=null,O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mousemove":O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}))}return/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)?!0:void 0},kb=function(a){if(a&&"string"==typeof a.type&&a){var b,c=a.target||null,d=c&&c.ownerDocument||f,g={view:d.defaultView||e,canBubble:!0,cancelable:!0,detail:"click"===a.type?1:0,button:"number"==typeof a.which?a.which-1:"number"==typeof a.button?a.button:d.createEvent?0:1},h=w(g,a);c&&d.createEvent&&c.dispatchEvent&&(h=[h.type,h.canBubble,h.cancelable,h.view,h.detail,h.screenX,h.screenY,h.clientX,h.clientY,h.ctrlKey,h.altKey,h.shiftKey,h.metaKey,h.button,h.relatedTarget],b=d.createEvent("MouseEvents"),b.initMouseEvent&&(b.initMouseEvent.apply(b,h),b._source="js",c.dispatchEvent(b)))}},lb=function(){var a=f.createElement("div");return a.id=O.containerId,a.className=O.containerClass,a.style.position="absolute",a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.height="1px",a.style.zIndex=""+Db(O.zIndex),a},mb=function(a){for(var b=a&&a.parentNode;b&&"OBJECT"===b.nodeName&&b.parentNode;)b=b.parentNode;return b||null},nb=function(){var a,b=I.bridge,c=mb(b);if(!b){var d=ub(e.location.host,O),g="never"===d?"none":"all",h=sb(O),i=O.swfPath+rb(O.swfPath,O);c=lb();var j=f.createElement("div");c.appendChild(j),f.body.appendChild(c);var k=f.createElement("div"),l="activex"===I.pluginType;k.innerHTML='"+(l?'':"")+'',b=k.firstChild,k=null,u(b).ZeroClipboard=Fb,c.replaceChild(b,j)}return b||(b=f[O.swfObjectId],b&&(a=b.length)&&(b=b[a-1]),!b&&c&&(b=c.firstChild)),I.bridge=b||null,b},ob=function(){var a=I.bridge;if(a){var b=mb(a);b&&("activex"===I.pluginType&&"readyState"in a?(a.style.display="none",function c(){if(4===a.readyState){for(var d in a)"function"==typeof a[d]&&(a[d]=null);a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b)}else h(c,10)}()):(a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b))),I.ready=null,I.bridge=null,I.deactivated=null}},pb=function(a){var b={},c={};if("object"==typeof a&&a){for(var d in a)if(d&&s.call(a,d)&&"string"==typeof a[d]&&a[d])switch(d.toLowerCase()){case"text/plain":case"text":case"air:text":case"flash:text":b.text=a[d],c.text=d;break;case"text/html":case"html":case"air:html":case"flash:html":b.html=a[d],c.html=d;break;case"application/rtf":case"text/rtf":case"rtf":case"richtext":case"air:rtf":case"flash:rtf":b.rtf=a[d],c.rtf=d}return{data:b,formatMap:c}}},qb=function(a,b){if("object"!=typeof a||!a||"object"!=typeof b||!b)return a;var c={};for(var d in a)if(s.call(a,d)){if("success"!==d&&"data"!==d){c[d]=a[d];continue}c[d]={};var e=a[d];for(var f in e)f&&s.call(e,f)&&s.call(b,f)&&(c[d][b[f]]=e[f])}return c},rb=function(a,b){var c=null==b||b&&b.cacheBust===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+p():""},sb=function(a){var b,c,d,f,g="",h=[];if(a.trustedDomains&&("string"==typeof a.trustedDomains?f=[a.trustedDomains]:"object"==typeof a.trustedDomains&&"length"in a.trustedDomains&&(f=a.trustedDomains)),f&&f.length)for(b=0,c=f.length;c>b;b++)if(s.call(f,b)&&f[b]&&"string"==typeof f[b]){if(d=tb(f[b]),!d)continue;if("*"===d){h.length=0,h.push(d);break}h.push.apply(h,[d,"//"+d,e.location.protocol+"//"+d])}return h.length&&(g+="trustedOrigins="+i(h.join(","))),a.forceEnhancedClipboard===!0&&(g+=(g?"&":"")+"forceEnhancedClipboard=true"),"string"==typeof a.swfObjectId&&a.swfObjectId&&(g+=(g?"&":"")+"swfObjectId="+i(a.swfObjectId)),g},tb=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},ub=function(){var a=function(a){var b,c,d,e=[];if("string"==typeof a&&(a=[a]),"object"!=typeof a||!a||"number"!=typeof a.length)return e;for(b=0,c=a.length;c>b;b++)if(s.call(a,b)&&(d=tb(a[b]))){if("*"===d){e.length=0,e.push("*");break}-1===e.indexOf(d)&&e.push(d)}return e};return function(b,c){var d=tb(c.swfPath);null===d&&(d=b);var e=a(c.trustedDomains),f=e.length;if(f>0){if(1===f&&"*"===e[0])return"always";if(-1!==e.indexOf(b))return 1===f&&b===d?"sameDomain":"always"}return"never"}}(),vb=function(){try{return f.activeElement}catch(a){return null}},wb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)||a.classList.add(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},xb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)&&a.classList.remove(b),a;if("string"==typeof b&&b){var c=b.split(/\s+/);if(1===a.nodeType&&a.className){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}}return a},yb=function(a,b){var c=e.getComputedStyle(a,null).getPropertyValue(b);return"cursor"!==b||c&&"auto"!==c||"A"!==a.nodeName?c:"pointer"},zb=function(){var a,b,c,d=1;return"function"==typeof f.body.getBoundingClientRect&&(a=f.body.getBoundingClientRect(),b=a.right-a.left,c=f.body.offsetWidth,d=o(b/c*100)/100),d},Ab=function(a){var b={left:0,top:0,width:0,height:0};if(a.getBoundingClientRect){var c,d,g,h=a.getBoundingClientRect();"pageXOffset"in e&&"pageYOffset"in e?(c=e.pageXOffset,d=e.pageYOffset):(g=zb(),c=o(f.documentElement.scrollLeft/g),d=o(f.documentElement.scrollTop/g));var i=f.documentElement.clientLeft||0,j=f.documentElement.clientTop||0;b.left=h.left+c-i,b.top=h.top+d-j,b.width="width"in h?h.width:h.right-h.left,b.height="height"in h?h.height:h.bottom-h.top}return b},Bb=function(){var a;if(c&&(a=mb(I.bridge))){var b=Ab(c);w(a.style,{width:b.width+"px",height:b.height+"px",top:b.top+"px",left:b.left+"px",zIndex:""+Db(O.zIndex)})}},Cb=function(a){I.ready===!0&&(I.bridge&&"function"==typeof I.bridge.setHandCursor?I.bridge.setHandCursor(a):I.ready=!1)},Db=function(a){if(/^(?:auto|inherit)$/.test(a))return a;var b;return"number"!=typeof a||n(a)?"string"==typeof a&&(b=Db(l(a,10))):b=a,"number"==typeof b?b:"auto"},Eb=function(a){function b(a){var b=a.match(/[\d]+/g);return b.length=3,b.join(".")}function c(a){return!!a&&(a=a.toLowerCase())&&(/^(pepflashplayer\.dll|libpepflashplayer\.so|pepperflashplayer\.plugin)$/.test(a)||"chrome.plugin"===a.slice(-13))}function d(a){a&&(i=!0,a.version&&(l=b(a.version)),!l&&a.description&&(l=b(a.description)),a.filename&&(k=c(a.filename)))}var e,f,h,i=!1,j=!1,k=!1,l="";if(g.plugins&&g.plugins.length)e=g.plugins["Shockwave Flash"],d(e),g.plugins["Shockwave Flash 2.0"]&&(i=!0,l="2.0.0.11");else if(g.mimeTypes&&g.mimeTypes.length)h=g.mimeTypes["application/x-shockwave-flash"],e=h&&h.enabledPlugin,d(e);else if("undefined"!=typeof a){j=!0;try{f=new a("ShockwaveFlash.ShockwaveFlash.7"),i=!0,l=b(f.GetVariable("$version"))}catch(n){try{f=new a("ShockwaveFlash.ShockwaveFlash.6"),i=!0,l="6.0.21"}catch(o){try{f=new a("ShockwaveFlash.ShockwaveFlash"),i=!0,l=b(f.GetVariable("$version"))}catch(p){j=!1}}}}I.disabled=i!==!0,I.outdated=l&&m(l)c;c++)a=e[c].replace(/^on/,""),f[a]=!0,g[a]||(g[a]=[]),g[a].push(b);if(f.ready&&I.ready&&this.emit({type:"ready",client:this}),f.error){var h=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=h.length;d>c;c++)if(I[h[c]]){this.emit({type:"error",name:"flash-"+h[c],client:this});break}}}return this},Nb=function(a,b){var c,d,e,f,g,h=Hb[this.id]&&Hb[this.id].handlers;if(0===arguments.length)f=q(h);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=h[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return this},Ob=function(a){var b=null,c=Hb[this.id]&&Hb[this.id].handlers;return c&&(b="string"==typeof a&&a?c[a]?c[a].slice(0):[]:x(c)),b},Pb=function(a){if(Ub.call(this,a)){"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(a=w({},a));var b=w({},db(a),{client:this});Vb.call(this,b)}return this},Qb=function(a){a=Wb(a);for(var b=0;b0,d=!a.target||c&&-1!==b.indexOf(a.target),e=a.relatedTarget&&c&&-1!==b.indexOf(a.relatedTarget),f=a.client&&a.client===this;return d||e||f?!0:!1},Vb=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=Hb[this.id]&&Hb[this.id].handlers["*"]||[],d=Hb[this.id]&&Hb[this.id].handlers[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},Wb=function(a){return"string"==typeof a&&(a=[]),"number"!=typeof a.length?[a]:a},Xb=function(a){if(a&&1===a.nodeType){var b=function(a){(a||(a=e.event))&&("js"!==a._source&&(a.stopImmediatePropagation(),a.preventDefault()),delete a._source)},c=function(c){(c||(c=e.event))&&(b(c),Fb.focus(a))};a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",b,!1),a.addEventListener("mouseenter",b,!1),a.addEventListener("mouseleave",b,!1),a.addEventListener("mousemove",b,!1),Kb[a.zcClippingId]={mouseover:c,mouseout:b,mouseenter:b,mouseleave:b,mousemove:b}}},Yb=function(a){if(a&&1===a.nodeType){var b=Kb[a.zcClippingId];if("object"==typeof b&&b){for(var c,d,e=["move","leave","enter","out","over"],f=0,g=e.length;g>f;f++)c="mouse"+e[f],d=b[c],"function"==typeof d&&a.removeEventListener(c,d,!1);delete Kb[a.zcClippingId]}}};Fb._createClient=function(){Lb.apply(this,v(arguments))},Fb.prototype.on=function(){return Mb.apply(this,v(arguments))},Fb.prototype.off=function(){return Nb.apply(this,v(arguments))},Fb.prototype.handlers=function(){return Ob.apply(this,v(arguments))},Fb.prototype.emit=function(){return Pb.apply(this,v(arguments))},Fb.prototype.clip=function(){return Qb.apply(this,v(arguments))},Fb.prototype.unclip=function(){return Rb.apply(this,v(arguments))},Fb.prototype.elements=function(){return Sb.apply(this,v(arguments))},Fb.prototype.destroy=function(){return Tb.apply(this,v(arguments))},Fb.prototype.setText=function(a){return Fb.setData("text/plain",a),this},Fb.prototype.setHtml=function(a){return Fb.setData("text/html",a),this},Fb.prototype.setRichText=function(a){return Fb.setData("application/rtf",a),this},Fb.prototype.setData=function(){return Fb.setData.apply(this,v(arguments)),this},Fb.prototype.clearData=function(){return Fb.clearData.apply(this,v(arguments)),this},Fb.prototype.getData=function(){return Fb.getData.apply(this,v(arguments))},"function"==typeof define&&define.amd?define(function(){return Fb}):"object"==typeof module&&module&&"object"==typeof module.exports&&module.exports?module.exports=Fb:a.ZeroClipboard=Fb}(function(){return this||window}()); diff --git a/server/www/script/ZeroClipboard.swf b/server/www/script/ZeroClipboard.swf deleted file mode 100644 index d4e2561b366e131d3bae303acce90a137a4956e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4036 zcmV;#4?FNfS5ppe82|uy+Ko8*dmG1fGkXno36KCtQ4~c|ON%BYdPzcuWs8(#QzRjg zmIR51E{i4>z$~%SVi($7@UW~{Q6eW!(z{mg7ET*CZPcds-K0&AAKLHh2a=8WwLkW! z{uk<-UGS3Ae1SMSZ{EE3=H0Wa6(sx*LXDpx)P~V`;s8SE!{&d-2%T{Y#_;rbT3snw zwl@r`vwcP1FAon5EiW$*E}s~5+{K||r%#_AN*y0MetZBZ2E0|<*H;E??{MF_K)^Wl z@~&C-O~+Q*TF*HZ|7>4hU1}k}Ewo&5tw3ZUKSV8BqPFi19UD9bf(rRz!*NTxe@-u# zEi;8)-=gP63r>!zX^XC?C)Lp%{(w3pVZ?}njDq!Bt!bFrSIm`5l)IC?R@7d0Bc*G8t(d1pK`_5j9 zEgajobbGO)FVb@pSAxCmb)ci}({oc5OFjOErXEinI~l6MuxEx|@^&2q0X&Ds#gs$6y|9-UyY zl*!$td0(3GUDIBSq$_rwsV+Vw-x> zW^x@@2p*n~D+JzP-*e>dJo))%MlgvQ3=5V@d z>83qPSNx1^ttLa<7R$Kq>yewDH|Lv{M~b?~5OJ6p?KWehr-OOs`!sZ7!Z0mx(yWta zF2An3p6L0IKVo2Er!A_gHQm*3rjvcG}V65VLj@#_g zlCwnj5Gt0V7pOaF8;$~Qh3tSsz3xTJ$?4X>`w+S9sD!mSkaNw#B5m-NjWFnwh3Egsnx#PWeM+a`}1Mkn*=hVRIh2c8`cLwMCdjy|) zexvVJSPo@ebJ4Uttch?Eeh`s5YDBrE zs-~eH@mI^#G1NT;sGdCwlD1ClQ+s>WA4N9Es_G?LrquyG+RzbBLU$q>#y68(JQ#18gci*vlASVH$1+{*Is*~fxYM=U=y30Eqs_Ig|;@Y5f zVAf9hj40g0qcM;AJJm13X09$=nn=%zI$JgdJ2TKJ>1C%t<%yf4W3vnC$ywRk!bJm;YZajPSB3Kv zGqdD=xQHyedbw!kJvm3=n6znGO3>qOm<7Ms5P%n{xmffg1y^652D{%wRzb;8C^4FM%Buo<$hrZofiVmAp}}`9Im^@?(>=r9q)=r73H6BkIGWVo)iV#%Vpvw{8*%oDP*COwDAj$YFyvjaA8__M%_p zG&M}nL7Vz9$Xas|ifGoEo4%yLn5OqD(5D;jf;QpH(=a~59UT!DJZc$Is2Q~K6<;X& zC5v}`BRZy+^_&U0Xi|@JAnPHnRJ2UHvLej{EjQiAC zyL^7tr{N1v{Z69ZJ>MzI+wAd0cH`>SHbTt9o0c2jUZ;asUaw(x;wBVPX8Q8zEP?ac zi47NLFJG$TV$1CMoSRLv6TSHK_bmI+n}ZXh^H#2679iPS{IIFS-WOcJq~ zh%H3iN5obl?k8d!i5wu2b`m*AB1wWf2<{}fi{L{rzArC>@D&pK3O)$H zGKqbR<*#D^zX9W?VEitjK2l=?_mCW7~g~OeHi}$;~#@?%}()J_Oiz z0uH_l*b_c7FcuKvLq`=I0IYE4c{p+F0aOvJ zC>YmgQDp4!XIt<*fKjX+qsD_6HFaPV@5Ct4D9SQw6}y-*hcMc& zV${}+(ScVmYCkN>4d@W)(SVw{o&?|WYC_o8!SyEi&c1}uB`8=HFiPSm=y(t}3W!tj zVF7`wF^r87t4xLOVuV(Ez^Vm!|{OWsHL(;k`V;RIEsU1wd_%&zh=ZUuW>-b z^Pp}dtF84u(+6NJ2%Hjq3=(hj|1d?d`baEcHt{8a;_k;Kku4%6Xl3AToPgJ98e`EHQloCuK&!7EkIz zPVN?RG7xfjmynYnM0+d!I%aBRPZ@7yPXS)ayb0WWB|e}zt)-v!C*&<5r+c3zMJA%R z#MiV7+QllMxvDl<)uyW2J5}vcRl8i(u2i*5Rl8c%u2r?^sx}i~W~%pr zs;*F6OD7O>QEk2vB=J*?SdZ1_qfF{l6zdH&qaodnk{#fEzzLas7%4xMjD13MI(rl&>6&YNJ9dT&LYtJ94iJ<k#eHA=7 zo1Q~e{pz~*HLPE+ts7U@L(63zVsWjOTFVN?jhfK{rNzLTJK(Jy@b(V)?hbgTW+ZpO z01KW_IH>Q#wG>5JF_oQ9W#3C>7gE`5D$B#)P1G6uiZbZXfH5zGogm6_g4hstg0;*6 z45%0{h>FXq`Z-kfS_#*(x{>RB)}Mq{cog6~IAedkKA0oe%+SxE_n}_B73j5Jg_vRJ z6Z_*W5VX{K>Ne`%>`%Q0()#p5XN0fSj8>2X(LY)<_CM9zF5r=im#Bdkq31@UjT{!l z1n9M!Y6;A2;z7M6Y_J@GZlBfH)}b;$@3;dcyKRSW7O0;_MLU^EEi5gNsv^e{G5P_K4GP zKp%%bBX4BQb;yUi>3g{PWHTr9;Oe?zMM0Ad?VGr|`2!&*0Ak;NzIZWTnzyyj@LE;- zU0mI$YTw4yDI06QhchK+OcJ8WH`P+}4vVIE6L%J)fNtK2TnoqX!gWZbPqg315FDPR zCz$;`kwb~?mZ!S7NO6{UwZzN z%3j*scp8dYXXmsEkbm6}&a7wnJ*Y=aS2(B~?T_)Zr^T)?D-W@(+|P4i+-pV`$PRVU zyI`k{mjrPo2wYJ7LG&Ky#rPps#bQqAda}6zjva&*1?1WyeKMfv3l7I>xJU=5|>>L zu8(qNg*LQeYc>dbu+qT)^>Hda1#!vOfMky!W7X0i=G9+Y+lj zdcf|dZRsBv4>Mmt;Sr&OBU?9Cc`5mNP?P~x_Cw%DcZM&wWXryOb<2dGimZ9-z=HE4+H$%$G3Z<{?^2NX{Ep5Be@^H@g)0 zazGRu?`|K@?LFRQUgb|uU#G^$_?!4MOpX7-pK{;gsKM`e`3Xk+19XrTTO5k*BOF#6 z*I8_Y%<9;wwoU=6ZJ?IYh67{jr2uNJ!5hgxVeOysc@F0ES1|V^4sVYdhRo=~z$8G4 q0(=NwVMQruv>;C1I`OOZ6Zy{KVOI9d?ElO6{y#btH2OaX)bM*lY1Xp< diff --git a/server/www/script/angular-clipboard.js b/server/www/script/angular-clipboard.js new file mode 100644 index 00000000000..ab976c0d7c4 --- /dev/null +++ b/server/www/script/angular-clipboard.js @@ -0,0 +1,101 @@ +(function (root, factory) { + /* istanbul ignore next */ + if (typeof define === 'function' && define.amd) { + define(['angular'], factory); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(require('angular')); + } else { + root.angularClipboard = factory(root.angular); + } +}(this, function (angular) { + +return angular.module('angular-clipboard', []) + .factory('clipboard', ['$document', '$window', function ($document, $window) { + function createNode(text, context) { + var node = $document[0].createElement('textarea'); + node.style.position = 'absolute'; + node.style.fontSize = '12pt'; + node.style.border = '0'; + node.style.padding = '0'; + node.style.margin = '0'; + node.style.left = '-10000px'; + node.style.top = ($window.pageYOffset || $document[0].documentElement.scrollTop) + 'px'; + node.textContent = text; + return node; + } + + function copyNode(node) { + try { + // Set inline style to override css styles + $document[0].body.style.webkitUserSelect = 'initial'; + + var selection = $document[0].getSelection(); + selection.removeAllRanges(); + + var range = document.createRange(); + range.selectNodeContents(node); + selection.addRange(range); + // This makes it work in all desktop browsers (Chrome) + node.select(); + // This makes it work on Mobile Safari + node.setSelectionRange(0, 999999); + + try { + if(!$document[0].execCommand('copy')) { + throw('failure copy'); + } + } finally { + selection.removeAllRanges(); + } + } finally { + // Reset inline style + $document[0].body.style.webkitUserSelect = ''; + } + } + + function copyText(text, context) { + var left = $window.pageXOffset || $document[0].documentElement.scrollLeft; + var top = $window.pageYOffset || $document[0].documentElement.scrollTop; + + var node = createNode(text, context); + $document[0].body.appendChild(node); + copyNode(node); + + $window.scrollTo(left, top); + $document[0].body.removeChild(node); + } + + return { + copyText: copyText, + supported: 'queryCommandSupported' in $document[0] && $document[0].queryCommandSupported('copy') + }; + }]) + .directive('clipboard', ['clipboard', function (clipboard) { + return { + restrict: 'A', + scope: { + onCopied: '&', + onError: '&', + text: '=', + supported: '=?' + }, + link: function (scope, element) { + scope.supported = clipboard.supported; + + element.on('click', function (event) { + try { + clipboard.copyText(scope.text, element[0]); + if (angular.isFunction(scope.onCopied)) { + scope.$evalAsync(scope.onCopied()); + } + } catch (err) { + if (angular.isFunction(scope.onError)) { + scope.$evalAsync(scope.onError({err: err})); + } + } + }); + } + }; + }]); + +})); diff --git a/server/www/script/ngClip.js b/server/www/script/ngClip.js deleted file mode 100644 index 280e394ff3f..00000000000 --- a/server/www/script/ngClip.js +++ /dev/null @@ -1,84 +0,0 @@ -/*jslint node: true */ -/*global ZeroClipboard */ - -(function(window, angular, undefined) { - 'use strict'; - - angular.module('ngClipboard', []). - provider('ngClip', function() { - var self = this; - this.path = '//cdnjs.cloudflare.com/ajax/libs/zeroclipboard/2.1.6/ZeroClipboard.swf'; - return { - setPath: function(newPath) { - self.path = newPath; - }, - setConfig: function(config) { - self.config = config; - }, - $get: function() { - return { - path: self.path, - config: self.config - }; - } - }; - }). - run(['ngClip', function(ngClip) { - var config = { - swfPath: ngClip.path, - trustedDomains: ["*"], - allowScriptAccess: "always", - forceHandCursor: true, - }; - ZeroClipboard.config(angular.extend(config,ngClip.config || {})); - }]). - directive('clipCopy', ['ngClip', function (ngClip) { - return { - scope: { - clipCopy: '&', - clipClick: '&', - clipClickFallback: '&' - }, - restrict: 'A', - link: function (scope, element, attrs) { - // Bind a fallback function if flash is unavailable - if (ZeroClipboard.isFlashUnusable()) { - element.bind('click', function($event) { - // Execute the expression with local variables `$event` and `copy` - scope.$apply(scope.clipClickFallback({ - $event: $event, - copy: scope.$eval(scope.clipCopy) - })); - }); - - return; - } - - // Create the client object - var client = new ZeroClipboard(element); - if (attrs.clipCopy === "") { - scope.clipCopy = function(scope) { - return element[0].previousElementSibling.innerText; - }; - } - client.on( 'ready', function(readyEvent) { - - client.on('copy', function (event) { - var clipboard = event.clipboardData; - clipboard.setData(attrs.clipCopyMimeType || 'text/plain', scope.$eval(scope.clipCopy)); - }); - - client.on( 'aftercopy', function(event) { - if (angular.isDefined(attrs.clipClick)) { - scope.$apply(scope.clipClick); - } - }); - - scope.$on('$destroy', function() { - client.destroy(); - }); - }); - } - }; - }]); -})(window, window.angular); \ No newline at end of file diff --git a/server/www/scripts/app.js b/server/www/scripts/app.js index 88be6e820e2..dfe93ec6e98 100644 --- a/server/www/scripts/app.js +++ b/server/www/scripts/app.js @@ -9,7 +9,7 @@ $.ajaxSetup({ }); var faradayApp = angular.module('faradayApp', ['ngRoute', 'selectionModel', 'ui.bootstrap', 'angularFileUpload', - 'filter', 'ngClipboard', 'ngCookies', 'cfp.hotkeys', 'chart.js', + 'filter', 'angular-clipboard', 'ngCookies', 'cfp.hotkeys', 'chart.js', 'ui.grid', 'ui.grid.selection', 'ui.grid.grouping', 'ngSanitize', 'ui.grid.pagination', 'ui.grid.pinning', 'angularMoment', 'ui-notification', 'tandibar/ng-rollbar', 'ui.grid.resizeColumns']) @@ -71,12 +71,11 @@ var faradayApp = angular.module('faradayApp', ['ngRoute', 'selectionModel', 'ui. return statuses; })()); -faradayApp.config(['$routeProvider', 'ngClipProvider', '$uibTooltipProvider', 'RollbarProvider', - function($routeProvider, ngClipProvider, $uibTooltipProvider, RollbarProvider) { +faradayApp.config(['$routeProvider', '$uibTooltipProvider', 'RollbarProvider', + function($routeProvider, $uibTooltipProvider, RollbarProvider) { $uibTooltipProvider.options({ appendToBody: true }); - ngClipProvider.setPath("script/ZeroClipboard.swf"); $routeProvider. when('/dashboard/ws/:wsId', { templateUrl: 'scripts/dashboard/partials/dashboard.html', diff --git a/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js b/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js index b0bf10e6cb5..6e5b0d4bf10 100644 --- a/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js +++ b/server/www/scripts/dashboard/controllers/summarizedCtrlHostsModal.js @@ -4,9 +4,9 @@ angular.module('faradayApp') .controller('summarizedCtrlHostsModal', - ['$scope', '$modalInstance', 'dashboardSrv', 'workspace', 'srv_name', 'osint', + ['$scope', '$modalInstance', 'dashboardSrv', 'workspace', 'srv_name', 'osint', function($scope, $modalInstance, dashboardSrv, workspace, srv_name, osint) { - + $scope.osint = osint; $scope.sortField = 'name'; $scope.sortReverse = false; diff --git a/server/www/scripts/dashboard/partials/modal-hosts-by-service.html b/server/www/scripts/dashboard/partials/modal-hosts-by-service.html index c1484809457..7474300ebbd 100644 --- a/server/www/scripts/dashboard/partials/modal-hosts-by-service.html +++ b/server/www/scripts/dashboard/partials/modal-hosts-by-service.html @@ -37,7 +37,7 @@

Services for {{name}} ({{hosts.length}} total)

- +
From a52d52e8917c7e7b9c46dfd7aa04177f0e290fc4 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 15 Jun 2018 12:24:19 -0300 Subject: [PATCH 1368/1506] Update version on default.xml --- config/default.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.xml b/config/default.xml index a557e32e7f3..efe8d0a3802 100644 --- a/config/default.xml +++ b/config/default.xml @@ -2,7 +2,7 @@ Faraday - Penetration Test IDE - 2.7.2 + 3.0 0 -Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1 ~/ From 1ef2fdaae1c27562311d738d0eeef152cf8456a4 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 15 Jun 2018 13:05:51 -0300 Subject: [PATCH 1369/1506] [FIX] if faraday can't access the plugins allow the server to run. --- plugins/manager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/manager.py b/plugins/manager.py index 13160a9c01d..1e01a6a96b9 100644 --- a/plugins/manager.py +++ b/plugins/manager.py @@ -16,7 +16,7 @@ from plugins.controller import PluginController from config.configuration import getInstanceConfiguration -from utils.logs import getLogger +import server.utils.logger CONF = getInstanceConfiguration() @@ -96,6 +96,9 @@ def _loadPlugins(self, plugin_repo_path): sys.path.append(plugin_repo_path) dir_name_regexp = re.compile(r"^[\d\w\-\_]+$") + if not os.path.exists(plugin_repo_path): + server.utils.logger.get_logger(self).error('Plugins path could not be opened, no pluging will be available!') + return for name in os.listdir(plugin_repo_path): if dir_name_regexp.match(name): try: From 03bfc8c722936657746efb6c3a3708efb20f05fc Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 15 Jun 2018 13:20:51 -0300 Subject: [PATCH 1370/1506] [FIX] show only workspace controls if the workspace is already selected --- server/www/scripts/commons/partials/header.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/www/scripts/commons/partials/header.html b/server/www/scripts/commons/partials/header.html index 4346db648a2..992fb31c9b6 100644 --- a/server/www/scripts/commons/partials/header.html +++ b/server/www/scripts/commons/partials/header.html @@ -57,17 +57,17 @@

Users

Licenses

Help

- +
- - - + From 2cae1bc942302f4f28f51152f69b94d732fb64bb Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 15 Jun 2018 15:30:58 -0300 Subject: [PATCH 1371/1506] Fix leo bug :p --- plugins/manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/manager.py b/plugins/manager.py index 1e01a6a96b9..9e1bad0be5d 100644 --- a/plugins/manager.py +++ b/plugins/manager.py @@ -117,12 +117,12 @@ def _loadPlugins(self, plugin_repo_path): elif file_ext.lower() == '.pyc': self._plugin_modules[name] = imp.load_compiled(name, module_filename) - getLogger(self).debug('Loading plugin {0}'.format(name)) + server.utils.logger.get_logger(self).debug('Loading plugin {0}'.format(name)) except Exception as e: msg = "An error ocurred while loading plugin %s.\n%s" % ( module_filename, traceback.format_exc()) - getLogger(self).debug(msg) - getLogger(self).warn(e) + server.utils.logger.get_logger(self).debug(msg) + server.utils.logger.get_logger(self).warn(e) def getPlugins(self): plugins = self._instancePlugins() From c52a5394f873f866094af97730d5701a1aea6849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 15 Jun 2018 16:15:53 -0300 Subject: [PATCH 1372/1506] Change severity of importer log message It wasn't so important so I moved it from warning to debug --- server/importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index 024a8193883..310937223dd 100644 --- a/server/importer.py +++ b/server/importer.py @@ -346,8 +346,8 @@ def update_command_tools(workspace, command_tool_map, id_map): missing_tool_count = Command.query.filter_by( workspace=workspace, tool="unknown").count() if missing_tool_count: - logger.warn("Couldn't find the tool name of {} commands in " - "workspace {}".format( + logger.debug("Couldn't find the tool name of {} commands in " + "workspace {}".format( missing_tool_count, workspace.name)) From cfe720becd4c7a0dca0f541fae13c2358e1305b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 15 Jun 2018 16:35:13 -0300 Subject: [PATCH 1373/1506] Add plugins to RELEASE.md --- RELEASE.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index fce8929411a..5d29fbe4fe9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,7 +12,7 @@ TBA: --- * Allow to upload report file from external tools from the web * Fix sshcheck import file from GTK -* Add reconng plugin +* Add reconng, sublist3r, HP Webinspect, dirsearch and ip360 plugins * CouchDB was replaced by PostgreSQL :) * Host object changed, now the name property is called ip * Interface object was removed @@ -28,7 +28,6 @@ TBA: * Exploitation and severity fields only allow certain values. CWE CVEs were fixed to be valid. A script to convert custom CSVs was added. * Web UI path changed from /_ui/ to / (_ui has now a redirection to / for keeping backwards compatibility) * dirb plugin should creates a vulnerability type information instead of a note. -* Add new plugin ip360 * Add confirmed column to exported csv from webui * Fixes in Arachni plugin * Add new parameters --keep-old and --keep-new for faraday CLI @@ -37,7 +36,6 @@ TBA: * Removed Chat feature (data is kept inside notes) * Plugin reports now can be imported in the server, from the Web UI * Add CVSS score to reference field in Nessus plugin. -* Add HP Webinspect plugin. * Fix unicode characters bug in Netsparker plugin. * Fix qualys plugin. * Fix bugs with MACOS and GTK. From 21508ba9748047061c7cf05e643579e5d5d18627 Mon Sep 17 00:00:00 2001 From: montive Date: Fri, 15 Jun 2018 17:37:50 -0300 Subject: [PATCH 1374/1506] Fix path calculation on vulndbToCsv.py --- helpers/vulndbToCsv.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/helpers/vulndbToCsv.py b/helpers/vulndbToCsv.py index e8fa5e10392..ca3c30ab802 100755 --- a/helpers/vulndbToCsv.py +++ b/helpers/vulndbToCsv.py @@ -3,14 +3,13 @@ ''' Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) Author: Ezequiel Tavella -See the file 'doc/LICENSE' for the license information This script generate a CSV file with information about the vulndb database. CSV Format: cwe,name,desc_summary,description,resolution,exploitation,references ''' from subprocess import call -from os import walk +from os import walk, path import json import csv @@ -100,18 +99,17 @@ def main(): for file_db in files: print '[*]Parsing ' + file_db - with open(root + file_db, 'r') as file_object: + with open(path.join(root, file_db), 'r') as file_object: csv_content = JsonToCsv(file_object) - result = ( - csv_content.cwe, - csv_content.name, - csv_content.description, - csv_content.resolution, - '', - ' '.join(csv_content.references), - csv_content.severity + csv_content.cwe, + csv_content.name, + csv_content.description, + csv_content.resolution, + '', + ' '.join(csv_content.references or []), + csv_content.severity ) writer.writerow(result) From b65576b014ce1ca55a1eec28fa48db5e7492e3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 15 Jun 2018 18:17:10 -0300 Subject: [PATCH 1375/1506] Fix requests error catching on the importer With the previous way it wasn't working properly with some requests versions --- server/importer.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/importer.py b/server/importer.py index 310937223dd..e6188d247ce 100644 --- a/server/importer.py +++ b/server/importer.py @@ -17,6 +17,7 @@ import requests +from requests.exceptions import HTTPError, RequestException from tempfile import NamedTemporaryFile from collections import ( @@ -213,7 +214,7 @@ def get_children_from_couch(workspace, parent_couchdb_id, child_type): try: r = requests.put(view_url, json=view_data) - except requests.exceptions.RequestException as e: + except RequestException as e: logger.exception(e) return [] @@ -227,7 +228,7 @@ def get_children_from_couch(workspace, parent_couchdb_id, child_type): try: r = requests.get(couch_url) - except requests.exceptions.RequestException as e: + except RequestException as e: logger.error('Network error in CouchDB request {}'.format( couch_url, r.status_code, @@ -237,7 +238,7 @@ def get_children_from_couch(workspace, parent_couchdb_id, child_type): try: r.raise_for_status() - except requests.exceptions.RequestException as e: + except RequestException as e: logger.error('Error in CouchDB request {}. ' 'Status code: {}. ' 'Body: {}'.format(couch_url, @@ -1215,10 +1216,10 @@ def run(self): try: cwes = requests.get(cwe_url) cwes.raise_for_status() - except requests.exceptions.HTTPError: + except HTTPError: logger.warn('Unable to retrieve Vulnerability Templates Database. Moving on.') return - except requests.exceptions.RequestException as e: + except RequestException as e: logger.exception(e) return @@ -1298,10 +1299,10 @@ def run(self): try: licenses = requests.get(licenses_url) licenses.raise_for_status() - except requests.exceptions.HTTPError: + except HTTPError: logger.warn('Unable to retrieve Licenses Database. Moving on.') return - except requests.exceptions.RequestException as e: + except RequestException as e: logger.exception(e) return From 5a30353d609ad691e830b6824b4e35c04cf10ae1 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 15 Jun 2018 19:15:13 -0300 Subject: [PATCH 1376/1506] Fix bug with hostnames, put hostnames in Host object --- model/api.py | 8 ++++---- model/controller.py | 4 ++-- persistence/server/models.py | 7 +++++++ persistence/server/server.py | 6 ++++-- persistence/server/utils.py | 1 + plugins/plugin.py | 5 +++-- test_cases/test_api_hosts.py | 24 +++++++++++++++++++++++- 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/model/api.py b/model/api.py index 6402ed01a7b..5ecee3cceb0 100644 --- a/model/api.py +++ b/model/api.py @@ -138,8 +138,8 @@ def _setUpAPIServer(hostname=None, port=None): # plugin created the object -def createAndAddHost(ip, os="Unknown"): - host = newHost(ip, os) +def createAndAddHost(ip, os="Unknown", hostnames=None): + host = newHost(ip, os, hostnames=hostnames) if addHost(host): return host.getID() return None @@ -328,12 +328,12 @@ def delCredFromService(cred, hostname, srvname): # CREATION APIS #------------------------------------------------------------------------------- -def newHost(ip, os = "Unknown"): +def newHost(ip, os = "Unknown", hostnames=None): """ It creates and returns a Host object. The object created is not added to the model. """ - return __model_controller.newHost(ip, os) + return __model_controller.newHost(ip, os, hostnames=hostnames) def newService(name, protocol = "tcp?", ports = [], status = "running", diff --git a/model/controller.py b/model/controller.py index 2c9895b0863..e288f14fc37 100644 --- a/model/controller.py +++ b/model/controller.py @@ -447,10 +447,10 @@ def _log(self, msg, *args, **kwargs): api.log(msg, *args[:-1]) return True - def newHost(self, ip, os="Unknown"): + def newHost(self, ip, os="Unknown", hostnames=None): return model.common.factory.createModelObject( models.Host.class_signature, ip, - workspace_name=self.mappers_manager.workspace_name, os=os, parent_id=None) + workspace_name=self.mappers_manager.workspace_name, os=os, parent_id=None, hostnames=hostnames) def newService(self, name, protocol="tcp?", ports=[], status="running", version="unknown", description="", parent_id=None): diff --git a/persistence/server/models.py b/persistence/server/models.py index da4f1fdb6ee..f45b330ba85 100644 --- a/persistence/server/models.py +++ b/persistence/server/models.py @@ -857,6 +857,7 @@ def __init__(self, host, workspace_name): self.os = host.get('os') if host.get('os') else 'unknown' self.vuln_amount = int(host.get('vulns', 0)) self.ip = host.get('ip', self.name) + self.hostnames = host.get('hostnames', []) if host.get('hostnames') else [] def getName(self): return self.ip @@ -890,6 +891,12 @@ def getVulnsAmount(self): def getDefaultGateway(self): return self.default_gateway + def getHostnames(self): + return self.hostnames + + def setHostnames(self, hostnames): + self.hostnames = hostnames + def getVulns(self): """ Get all vulns of this host. diff --git a/persistence/server/server.py b/persistence/server/server.py index cbf12168665..8059314ab13 100644 --- a/persistence/server/server.py +++ b/persistence/server/server.py @@ -889,7 +889,7 @@ def get_commands_number(workspace_name, **params): def create_host(workspace_name, command_id, ip, os, default_gateway=None, description="", metadata=None, owned=False, owner="", - parent=None): + parent=None, hostnames=None): """Create a host. Args: @@ -918,11 +918,12 @@ def create_host(workspace_name, command_id, ip, os, default_gateway=None, owner=owner, parent=parent, description=description, + hostnames=hostnames, type="Host") def update_host(workspace_name, command_id, id, ip, os, default_gateway="", description="", metadata=None, owned=False, owner="", - parent=None): + parent=None, hostnames=None): """Updates a host. Args: @@ -952,6 +953,7 @@ def update_host(workspace_name, command_id, id, ip, os, default_gateway="", owner=owner, parent=parent, description=description, + hostnames=hostnames, type="Host") diff --git a/persistence/server/utils.py b/persistence/server/utils.py index 36197f98d73..7a9c5e8893d 100644 --- a/persistence/server/utils.py +++ b/persistence/server/utils.py @@ -47,6 +47,7 @@ def get_object_properties(obj): def get_host_properties(host): host_dict = { 'os': host.getOS(), + 'hostnames': host.getHostnames() } if host.getDefaultGateway(): host_dict['default_gateway'] = host.getDefaultGateway() diff --git a/plugins/plugin.py b/plugins/plugin.py index b45b4804933..2f8936e9008 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -171,14 +171,15 @@ def __addPendingAction(self, *args): logger.debug('AddPendingAction', args) self._pending_actions.put(args) - def createAndAddHost(self, name, os="unknown"): + def createAndAddHost(self, name, os="unknown", hostnames=None): host_obj = factory.createModelObject( Host.class_signature, name, os=os, parent_id=None, - workspace_name=self.workspace) + workspace_name=self.workspace, + hostnames=hostnames) host_obj._metadata.creatoserverr = self.id self.__addPendingAction(Modelactions.ADDHOST, host_obj) diff --git a/test_cases/test_api_hosts.py b/test_cases/test_api_hosts.py index c4e2ee63977..514ad209a5a 100644 --- a/test_cases/test_api_hosts.py +++ b/test_cases/test_api_hosts.py @@ -26,7 +26,7 @@ ReadWriteAPITests, PaginationTestsMixin, ) -from server.models import db, Host +from server.models import db, Host, Hostname from server.api.modules.hosts import HostsView from test_cases.factories import HostFactory, CommandFactory, \ EmptyCommandFactory, WorkspaceFactory @@ -651,6 +651,28 @@ def test_delete_host_with_blank_ip(self, session, test_client): res = test_client.delete(self.url(host, workspace=host.workspace)) assert res.status_code == 204 + def test_update_hostname(self, session, test_client): + host = HostFactory.create() + session.add(host) + session.commit() + data = { + "description":"", + "default_gateway":"", + "ip":"127.0.0.1", + "owned":False, + "name":"127.0.0.1", + "mac":"", + "hostnames":["dasdas"], + "owner":"faraday", + "os":"Unknown", + } + + res = test_client.put('v2/ws/{0}/hosts/{1}/'.format(host.workspace.name, host.id), data=data) + assert res.status_code == 200 + + assert session.query(Hostname).filter_by(host=host).count() == 1 + assert session.query(Hostname).all()[0].name == 'dasdas' + def host_json(): return st.fixed_dictionaries( From 2dc666283df72b0a91993d5139e8871aefee3f1d Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 15 Jun 2018 19:53:19 -0300 Subject: [PATCH 1377/1506] Move import to avoid importerror (we need faraday as a package) --- server/api/modules/upload_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/modules/upload_reports.py b/server/api/modules/upload_reports.py index edd75cc080f..8017d0bef5c 100644 --- a/server/api/modules/upload_reports.py +++ b/server/api/modules/upload_reports.py @@ -26,7 +26,6 @@ from werkzeug.utils import secure_filename from wtforms import ValidationError -from faraday import setupPlugins from server.utils.logger import get_logger from server.utils.web import gzipped @@ -55,6 +54,7 @@ class RawReportProcessor(Thread): def __init__(self): super(RawReportProcessor, self).__init__() + from faraday import setupPlugins setupPlugins() self.pending_actions = Queue() From eae08a5360d47a57848b45c460726be4f7e7d6fa Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 15 Jun 2018 20:09:33 -0300 Subject: [PATCH 1378/1506] Fix bug in test hostnames.... --- test_cases/test_managers_mapper_manager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test_cases/test_managers_mapper_manager.py b/test_cases/test_managers_mapper_manager.py index f47ca70ba4e..93eee811067 100644 --- a/test_cases/test_managers_mapper_manager.py +++ b/test_cases/test_managers_mapper_manager.py @@ -55,6 +55,7 @@ 'name': '192.168.0.20', 'description': 'My computer', 'default_gateway': '192.168.0.1', + 'hostnames': [], 'os': 'Debian', 'owned': False, 'owner': 'leo' @@ -65,6 +66,7 @@ 'default_gateway': '192.168.0.1', 'description': 'My computer', 'ip': '192.168.0.20', + 'hostnames': [], 'os': 'Debian', 'owned': False, 'owner': 'leo', @@ -491,6 +493,7 @@ 'description': 'Test description', 'ip': '192.168.1.1', 'os': 'Linux 2.6.9', + 'hostnames': [], 'owned': False, 'owner': 'leonardo'} @@ -874,4 +877,4 @@ def mock_unsafe_io_with_server(host, test_data, server_io_function, server_expec serialized_obj = test_data['get_properties_function'](found_obj) if obj_class not in [Command]: metadata = serialized_obj.pop('metadata') - assert serialized_obj == test_data['serialized_expected_results'] + assert serialized_obj == test_data['serialized_expected_results'] \ No newline at end of file From 298d2eff02b3295fcf7a451a3a3ef1b398529ef4 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Mon, 18 Jun 2018 12:27:26 -0300 Subject: [PATCH 1379/1506] [COM] New bug partial fix warning [MOD] README with changes --- RELEASE.md | 1 + plugins/repo/wapiti/plugin.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index a3d286c53cd..f4cc462cea1 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,6 +10,7 @@ New features in the latest update TBA: --- +* Fix W3af, Zap * Add brutexss plugin * Allow to upload report file from external tools from the web * Fix sshcheck import file from GTK diff --git a/plugins/repo/wapiti/plugin.py b/plugins/repo/wapiti/plugin.py index 40d07d065d1..36295d5738f 100644 --- a/plugins/repo/wapiti/plugin.py +++ b/plugins/repo/wapiti/plugin.py @@ -244,6 +244,9 @@ def parseOutputString(self, output, debug=False): """ self._output_file_path = "/root/dev/faraday/trunk/src/report/wapiti2.3.0_abaco.xml" + #TODO FOR mig_white_4896... if NOT debug + # OR replace all if/else with + # parser = WapitiXmlParser(output) if debug: parser = WapitiXmlParser(output) else: From 9e9a111de30dded9b7814c02bceec09602335411 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 18 Jun 2018 15:25:38 -0300 Subject: [PATCH 1380/1506] Update release.md --- RELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.md b/RELEASE.md index f4cc462cea1..ec0108588fc 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,6 +10,7 @@ New features in the latest update TBA: --- +* Add xsssniper plugin * Fix W3af, Zap * Add brutexss plugin * Allow to upload report file from external tools from the web From b9b384017e7bbb9fe0eef937782da3e18d4ac295 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 18 Jun 2018 17:29:50 -0300 Subject: [PATCH 1381/1506] PEP8 fixes --- plugins/repo/wfuzz/plugin.py | 71 +++++++++++++++++------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/plugins/repo/wfuzz/plugin.py b/plugins/repo/wfuzz/plugin.py index c9558eedd01..778822f84e7 100644 --- a/plugins/repo/wfuzz/plugin.py +++ b/plugins/repo/wfuzz/plugin.py @@ -1,8 +1,9 @@ -import sys import re -from plugins import core +import sys from urlparse import urljoin, urlparse +from plugins import core + class WfuzzPlugin(core.PluginBase): @@ -23,51 +24,48 @@ def __init__(self): def parseData(self, output): - data = { - 'target' : '', - 'findings' : [ - - ] - } - for line in output: - - if(line.startswith('Target')): + data = { + 'target' : '', + 'findings' : [] + } + for line in output: + + if line.startswith('Target'): data['target'] = line[8:].rstrip() - if(line.startswith('0')): - aux = line.split(' ') - res = {} - for item in aux: - if 'C=' in item: - res['response'] = int(item.replace('C=', '')) - elif 'L' in item and ' ' in item: - res['lines'] = int(item.replace('L', '')) - elif 'W' in item and ' ' in item: - res['words'] = int(item.replace('W', '')) - elif 'Ch' in item and ' ' in item: - res['chars'] = int(item.replace('Ch', '')) - else: - res['request'] = item.rstrip().replace('"', '') - data['findings'].append(res) + if line.startswith('0'): + aux = line.split(' ') + res = {} + for item in aux: + if 'C=' in item: + res['response'] = int(item.replace('C=', '')) + elif 'L' in item and ' ' in item: + res['lines'] = int(item.replace('L', '')) + elif 'W' in item and ' ' in item: + res['words'] = int(item.replace('W', '')) + elif 'Ch' in item and ' ' in item: + res['chars'] = int(item.replace('Ch', '')) + else: + res['request'] = item.rstrip().replace('"', '') + data['findings'].append(res) return data - def parseOutputString(self, output, debug=False): output_list = output.split('\n') - info = self.parseData(output_list) + info = self.parseData(output_list) target = info['target'] target_url = urlparse(target) port = 80 - + if target_url.scheme == 'https': port = 443 custom_port = target_url.netloc.split(':') if len(custom_port) > 1: port = custom_port[1] - + host_id = self.createAndAddHost(target) service_id = self.createAndAddServiceToHost(host_id,name="http",protocol="tcp", ports=[port] ) @@ -87,17 +85,16 @@ def parseOutputString(self, output, debug=False): chars=chars, status=status ) - vuln = self.createAndAddVulnWebToService(host_id, + self.createAndAddVulnWebToService(host_id, service_id, - name, - desc, - severity="info", - website=target, + name, + desc, + severity="info", + website=target, path=path - ) + ) - def createPlugin(): return WfuzzPlugin() From 7f51baaf20036642098838b5f1fa07ef4e1dba21 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 18 Jun 2018 18:59:01 -0300 Subject: [PATCH 1382/1506] [FIX] remove hidden chars to make the plugin work from terminal --- plugins/repo/wfuzz/plugin.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/repo/wfuzz/plugin.py b/plugins/repo/wfuzz/plugin.py index 778822f84e7..86673b8c32b 100644 --- a/plugins/repo/wfuzz/plugin.py +++ b/plugins/repo/wfuzz/plugin.py @@ -1,5 +1,6 @@ import re import sys +import string from urlparse import urljoin, urlparse from plugins import core @@ -29,10 +30,12 @@ def parseData(self, output): 'findings' : [] } for line in output: - + # remove stdout hidden chars + line = ''.join([char for char in line if char in string.printable]) + line = line.strip('\r').replace('[0K', '').replace('[0m', '') if line.startswith('Target'): data['target'] = line[8:].rstrip() - + continue if line.startswith('0'): aux = line.split(' ') res = {} @@ -52,7 +55,6 @@ def parseData(self, output): return data def parseOutputString(self, output, debug=False): - output_list = output.split('\n') info = self.parseData(output_list) From 5d91c18d3dd086046d4bcf3078bcd43e730d2fc7 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Mon, 18 Jun 2018 19:07:51 -0300 Subject: [PATCH 1383/1506] Fix hostnames bug #4895 --- managers/reports_managers.py | 2 +- plugins/controller.py | 1 + plugins/plugin.py | 9 +++++++++ plugins/repo/burp/faraday-burp.rb | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/managers/reports_managers.py b/managers/reports_managers.py index 45a9bf155fe..7a6625c4901 100755 --- a/managers/reports_managers.py +++ b/managers/reports_managers.py @@ -51,7 +51,7 @@ def sendReport(self, plugin_id, filename): """Sends a report to the appropiate plugin specified by plugin_id""" getLogger(self).info( 'The file is %s, %s' % (filename, plugin_id)) - command_id = self.plugin_controller.processReport(plugin_id, filename, self.ws_name) + command_id = self.plugin_controller.processReport(plugin_id, filename, ws_name=self.ws_name) if not command_id: getLogger(self).error( "Faraday doesn't have a plugin for this tool..." diff --git a/plugins/controller.py b/plugins/controller.py index 7543a7f23ef..32e596ab422 100644 --- a/plugins/controller.py +++ b/plugins/controller.py @@ -287,6 +287,7 @@ def processReport(self, plugin, filepath, ws_name=None): cmd_info.setID(command_id) if plugin in self._plugins: logger.info('Processing report with plugin {0}'.format(plugin)) + self._plugins[plugin].workspace = ws_name with open(filepath, 'rb') as output: self.processOutput(self._plugins[plugin], output.read(), cmd_info, True) return command_id diff --git a/plugins/plugin.py b/plugins/plugin.py index 2f8936e9008..7d9cd7ffe9c 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -20,6 +20,7 @@ import model.api import model.common from model.common import factory +from persistence.server.models import get_host , update_host from persistence.server.models import ( Host, Service, @@ -198,6 +199,14 @@ def createAndAddInterface( # We don't use interface anymore, so return a host id to maintain # backwards compatibility + # Little hack because we dont want change all the plugins for add hostnames in Host object. + # SHRUG + try: + host = get_host(self.workspace, host_id=host_id) + host.hostnames = hostname_resolution + update_host(self.workspace, host, command_id=self.command_id) + except: + logger.info("Error updating Host with right hostname resolution...") return host_id @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", diff --git a/plugins/repo/burp/faraday-burp.rb b/plugins/repo/burp/faraday-burp.rb index de62215ba5b..9b8d80593a4 100644 --- a/plugins/repo/burp/faraday-burp.rb +++ b/plugins/repo/burp/faraday-burp.rb @@ -243,7 +243,7 @@ def newScanIssue(issue, ctx=nil, import=nil) begin rt = @server.call("devlog", "[BURP] New issue generation") - h_id = @server.call("createAndAddHost",ip, "unknown") + h_id = @server.call("createAndAddHost",ip, "unknown", host) i_id = @server.call("createAndAddInterface",h_id, ip,"00:00:00:00:00:00", ip, "0.0.0.0", "0.0.0.0",[], "0000:0000:0000:0000:0000:0000:0000:0000","00","0000:0000:0000:0000:0000:0000:0000:0000", [],"",host) From 2e8936e05638b847cac8c77c31a5f13176bc5be2 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Mon, 18 Jun 2018 19:55:12 -0300 Subject: [PATCH 1384/1506] Fix bug in Burp plugin --- plugins/repo/burp/faraday-burp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/repo/burp/faraday-burp.rb b/plugins/repo/burp/faraday-burp.rb index 9b8d80593a4..b71407ce63c 100644 --- a/plugins/repo/burp/faraday-burp.rb +++ b/plugins/repo/burp/faraday-burp.rb @@ -243,7 +243,7 @@ def newScanIssue(issue, ctx=nil, import=nil) begin rt = @server.call("devlog", "[BURP] New issue generation") - h_id = @server.call("createAndAddHost",ip, "unknown", host) + h_id = @server.call("createAndAddHost",ip, "unknown", [host]) i_id = @server.call("createAndAddInterface",h_id, ip,"00:00:00:00:00:00", ip, "0.0.0.0", "0.0.0.0",[], "0000:0000:0000:0000:0000:0000:0000:0000","00","0000:0000:0000:0000:0000:0000:0000:0000", [],"",host) From 18180fab1c9bf48cefdc3310f61aed0e7f25ed14 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 19 Jun 2018 12:55:01 -0300 Subject: [PATCH 1385/1506] [FIX] Remove lazy joined from Scope to avoid performance issues on workspace summary. we need to review scope query for other use cases --- server/models.py | 3 ++- test_cases/test_api_workspace.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index f3adf890ad3..d5e3856bc37 100644 --- a/server/models.py +++ b/server/models.py @@ -1240,9 +1240,10 @@ class Scope(Metadata): index=True, nullable=False ) + workspace = relationship( 'Workspace', - backref=backref('scope', lazy="joined", cascade="all, delete-orphan"), + backref=backref('scope', cascade="all, delete-orphan"), foreign_keys=[workspace_id], ) diff --git a/test_cases/test_api_workspace.py b/test_cases/test_api_workspace.py index 0b8eb1b18ce..8b26639152a 100644 --- a/test_cases/test_api_workspace.py +++ b/test_cases/test_api_workspace.py @@ -9,6 +9,7 @@ from server.models import Workspace, Scope from server.api.modules.workspaces import WorkspaceView +from test_cases.conftest import ignore_nplusone from test_cases.test_api_non_workspaced_base import ReadWriteAPITests from test_cases import factories @@ -163,3 +164,8 @@ def test_update_with_scope(self, session, test_client, workspace): assert res.status_code == 200 assert set(res.json['scope']) == set(desired_scope) assert set(s.name for s in workspace.scope) == set(desired_scope) + + @ignore_nplusone + def test_list_retrieves_all_items_from(self, test_client): + super(TestWorkspaceAPI, self).test_list_retrieves_all_items_from(test_client) + From 1e5f14da1a77070871b402d9d245726df8e66c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Jun 2018 12:56:20 -0300 Subject: [PATCH 1386/1506] Fix a potential cause for an association proxy bug The session can hold hosts inside it, which don't have a name. The bug was probably related to a race condition when doing many requests at the same time --- server/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models.py b/server/models.py index f3adf890ad3..3ae830270c9 100644 --- a/server/models.py +++ b/server/models.py @@ -479,7 +479,7 @@ def _create(self, value): # we need to fetch already created objs. session.rollback() for conflict_obj in conflict_objs: - if conflict_obj.name == value: + if hasattr(conflict_obj, 'name') and conflict_obj.name == value: continue persisted_conclict_obj = session.query(conflict_obj.__class__).filter_by(name=conflict_obj.name).first() if persisted_conclict_obj: From 988e71fdf3601187204da3316e77d9264c7406db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Jun 2018 14:50:41 -0300 Subject: [PATCH 1387/1506] Fix importing services without parent from couch The case where parent is null wasn't handled --- server/importer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/importer.py b/server/importer.py index e6188d247ce..da36277650e 100644 --- a/server/importer.py +++ b/server/importer.py @@ -533,11 +533,11 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation command = session.query(Command).get(couchdb_relational_map[document['metadata']['command_id']][0]) except (KeyError, IndexError): command = None - try: - parent_id = document['parent'].split('.')[0] - except KeyError: - # some services are missing the parent key - parent_id = document['_id'].split('.')[0] + + # This should be safe because _id is always present and split never + # returns an empty list + parent_id = (document.get('parent') or document.get('_id')).split('.')[0] + for relational_parent_id in couchdb_relational_map[parent_id]: host, created = get_or_create(session, Host, id=relational_parent_id) if command and created: From e9f9de389a31a4be8f8665535f0839bbca0b85de Mon Sep 17 00:00:00 2001 From: montive Date: Tue, 19 Jun 2018 16:04:28 -0300 Subject: [PATCH 1388/1506] Plugin created: Sslyze --- plugins/repo/sslyze/__init__.py | 0 plugins/repo/sslyze/plugin.py | 192 ++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 plugins/repo/sslyze/__init__.py create mode 100644 plugins/repo/sslyze/plugin.py diff --git a/plugins/repo/sslyze/__init__.py b/plugins/repo/sslyze/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/repo/sslyze/plugin.py b/plugins/repo/sslyze/plugin.py new file mode 100644 index 00000000000..1c88eab94a6 --- /dev/null +++ b/plugins/repo/sslyze/plugin.py @@ -0,0 +1,192 @@ +#coding=utf-8 +import re +import os +import random +from plugins import core + +try: + from lxml import etree as ET +except ImportError: + import xml.etree.ElementTree as ET + + +WEAK_CIPHER_LIST = [ + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" +] + + +class SslyzeXmlParser(object): + + def __init__(self, xml_output): + self.parser = self.parse_xml(xml_output) + self.target = self.get_target(self.parser) + self.certificate = self.get_hostname_validation(self.parser) + self.cipher_suite = self.get_weak_cipher_suite(self.parser) + self.heart_bleed = self.get_heartbleed(self.parser) + self.open_ssl_ccs = self.get_openssl_ccs(self.parser) + + def parse_xml(self, xml_output): + try: + tree = ET.fromstring(xml_output) + return tree + except IndexError: + print "Syntax error" + return None + + def get_target(self, tree): + return tree.xpath('//target') + + def get_hostname_validation(self, tree): + return tree.xpath('//hostnameValidation') + + def get_protocol_name(self, tree): + protocol_supported = [] + protocols = [] + protocols.append(tree.xpath('//sslv2')) + protocols.append(tree.xpath('//sslv3')) + protocols.append(tree.xpath('//tlsv1')) + protocols.append(tree.xpath('//tlsv1_1')) + protocols.append(tree.xpath('//tlsv1_2')) + protocols.append(tree.xpath('//tlsv1_3')) + + for protocol in protocols: + if protocol[0].attrib['isProtocolSupported'] == "True": + protocol_supported.append(protocol[0]) + + return protocol_supported + + def get_weak_cipher_suite(self, tree): + protocols = self.get_protocol_name(tree) + weak_cipher = {} + + for protocol in protocols: + weak_cipher[protocol.tag] = [] + for ciphers in protocol: + if ciphers.tag == 'preferredCipherSuite' or ciphers.tag == 'acceptedCipherSuites': + for cipher in ciphers: + if cipher.attrib['name'] in WEAK_CIPHER_LIST: + if not cipher.attrib['name'] in weak_cipher[protocol.tag]: + weak_cipher[protocol.tag].append(cipher.attrib['name']) + + return weak_cipher + + def get_heartbleed(self, tree): + return tree.xpath('//heartbleed') + + def get_openssl_ccs(self, tree): + return tree.xpath('//openssl_ccs') + + +class SslyzePlugin(core.PluginBase): + + def __init__(self): + core.PluginBase.__init__(self) + self.id = "Sslyze" + self.name = "Sslyze Plugin" + self.plugin_version = "0.0.1" + self.version = "1.4.2" + self.framework_version = "1.0.0" + self.options = None + self._current_output = None + self._command_regex = re.compile(r'^(sudo sslyze|sslyze|\.\/sslyze).*?') + self.xml_arg_re = re.compile(r"^.*(--xml_output\s*[^\s]+).*$") + + def parseOutputString(self, output, debug=False): + parser = SslyzeXmlParser(output) + host = parser.target[0].attrib['host'] + ip = parser.target[0].attrib['ip'] + port = parser.target[0].attrib['port'] + protocol = parser.target[0].attrib['tlsWrappedProtocol'] + cipher = parser.cipher_suite + + # Creating host + host_id = self.createAndAddHost(ip) + # Creating service CHANGE NAME + service_id = self.createAndAddServiceToHost( + host_id, + name=protocol, + protocol=protocol, + ports=[port], + ) + + # Checking if certificate matches + certificate = parser.certificate[0].attrib['certificateMatchesServerHostname'] + server_hostname = parser.certificate[0].attrib['serverHostname'] + if certificate.lower() == 'false': + self.createAndAddVulnToService( + host_id, + service_id, + name="Certificate mismatch", + desc="Certificate does not match server hostname {}".format(server_hostname), + severity="info") + #Ciphers + cipher = parser.cipher_suite + + for key in cipher: + for value in cipher[key]: + self.createAndAddVulnToService( + host_id, + service_id, + name=value, + desc="In protocol [{}], weak cipher suite: {}".format(key, value), + severity="low") + + #Heartbleed + heartbleed = parser.heart_bleed + + if heartbleed[0][0].attrib['isVulnerable'].lower() == 'true': + self.createAndAddVulnToService( + host_id, + service_id, + name="OpenSSL Heartbleed", + desc="OpenSSL Heartbleed is vulnerable", + severity="critical") + + #OpenSsl CCS Injection + openssl_ccs = parser.open_ssl_ccs + + if openssl_ccs[0][0].attrib['isVulnerable'].lower() == 'true': + self.createAndAddVulnToService( + host_id, + service_id, + name="OpenSSL CCS Injection", + desc="OpenSSL CCS Injection is vulnerable", + severity="medium") + + def processCommandString(self, username, current_path, command_string): + self._output_file_path = os.path.join( + self.data_path, + "%s_%s_output-%s.xml" % ( + self.get_ws(), + self.id, + random.uniform(1, 10)) + ) + + arg_match = self.xml_arg_re.match(command_string) + + if arg_match is None: + return re.sub(r"(^.*?sslyze)", + r"\1 --xml_out %s" % self._output_file_path, + command_string) + else: + return re.sub(arg_match.group(1), + r"--xml_out %s" % self._output_file_path, + command_string) + + +def createPlugin(): + return SslyzePlugin() + +if __name__ == '__main__': + parser = SslyzePlugin() + with open("/home/javier/expired.xml","r") as report: + parser.parseOutputString(report.read()) \ No newline at end of file From eba1a8a51930c96f33721e24210a535f879105a3 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 19 Jun 2018 16:09:46 -0300 Subject: [PATCH 1389/1506] Fix bug #4903 --- faraday.py | 3 +-- persistence/server/server.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/faraday.py b/faraday.py index 1d457ae21d7..ba30d562b4a 100755 --- a/faraday.py +++ b/faraday.py @@ -490,8 +490,7 @@ def doLoginLoop(): CONF.saveConfig() user_info = get_user_info() - - if user_info is None or 'username' not in user_info: + if (user_info is None) or (not user_info) or ('username' not in user_info): print("You can't login as a client. You have %s attempt(s) left." % (3 - attempt)) continue diff --git a/persistence/server/server.py b/persistence/server/server.py index 8059314ab13..ed1217be770 100644 --- a/persistence/server/server.py +++ b/persistence/server/server.py @@ -1577,7 +1577,7 @@ def check_server_url(url_to_test): def get_user_info(): try: resp = requests.get(urlparse.urljoin(_get_base_server_url(), "/_api/session"), cookies=_conf().getDBSessionCookies(), timeout=1) - if resp.status_code != 403: + if (resp.status_code != 401) and (resp.status_code != 403): return resp.json() else: return False From a67da8ff2c83a08693a43e335a9cf8c4226c88f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Jun 2018 16:24:03 -0300 Subject: [PATCH 1390/1506] Fix potential race condition when importing users I wasn't able to reproduce the bug properly but I think this makes it more resistant to race conditions --- server/importer.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/importer.py b/server/importer.py index da36277650e..976232545e7 100644 --- a/server/importer.py +++ b/server/importer.py @@ -597,18 +597,20 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation yield service +user_lock = threading.Lock() def get_or_create_user(session, username): - rng = SystemRandom() - password = "".join( - [rng.choice(string.ascii_letters + string.digits) for _ in - xrange(12)]) - creator, created = get_or_create(session, User, username=username) - if created: - creator.active = False - creator.password = password - session.add(creator) # remove me - session.commit() # remove me - return creator + with user_lock: + rng = SystemRandom() + password = "".join( + [rng.choice(string.ascii_letters + string.digits) for _ in + xrange(12)]) + creator, created = get_or_create(session, User, username=username) + if created: + creator.active = False + creator.password = password + session.add(creator) # remove me + session.commit() # remove me + return creator class VulnerabilityImporter(object): From 629a54b15919d9494325bdbaea1f0095932a6c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Jun 2018 16:35:08 -0300 Subject: [PATCH 1391/1506] Fix importer error when the workspace has duplicate scope elements --- server/importer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/importer.py b/server/importer.py index 976232545e7..cd60352cb22 100644 --- a/server/importer.py +++ b/server/importer.py @@ -898,6 +898,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation workspace.end_date = datetime.datetime.fromtimestamp(float(document.get('duration')['end'])/1000) for scope in [x.strip() for x in document.get('scope', '').split('\n') if x.strip()]: scope_obj, created = get_or_create(session, Scope, name=scope, workspace=workspace) + session.flush() # This fixes integrity errors for duplicate scope elements users = document.get('users', []) if not users: workspace.public = True From b7aea3aee9065b505f32d4492c8442b360fa6c56 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 19 Jun 2018 16:59:44 -0300 Subject: [PATCH 1392/1506] Update release.md --- RELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.md b/RELEASE.md index fb1b1304b30..6fb9ca095c4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,6 +10,7 @@ New features in the latest update TBA: --- +* Add new plugin sslyze * Add new plugin wfuzz * Add xsssniper plugin * Fix W3af, Zap From 4054b47e389eefafee7c7842c49d4fcdf4222539 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 19 Jun 2018 17:20:26 -0300 Subject: [PATCH 1393/1506] Fix bug #4906 --- manage.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/manage.py b/manage.py index eeee42afed9..c1b1172d16e 100755 --- a/manage.py +++ b/manage.py @@ -25,8 +25,9 @@ from server.web import app from utils.logs import setUpLogger +CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) -@click.group() +@click.group(context_settings=CONTEXT_SETTINGS) def cli(): pass @@ -35,7 +36,7 @@ def check_faraday_server(url): return requests.get(url) -@click.command() +@click.command(help="Enable importation of plugins reports in ~/.faraday folder") @click.option('--debug/--no-debug', default=False) @click.option('--workspace', default=None) @click.option('--polling/--no-polling', default=True) @@ -64,15 +65,11 @@ def process_reports(debug, workspace, polling): print('Can\'t connect to {0}. Please check if the server is running.'.format(url)) -@click.command() +@click.command(help="Show all URLs in Faraday Server API") def show_urls(): show_all_urls() -@click.command() -def faraday_schema_display(): - DatabaseSchema().run() - -@click.command() +@click.command(help="Create Faraday DB in Postgresql, also tables and indexes") def initdb(): with app.app_context(): InitDB().run() @@ -82,16 +79,16 @@ def initdb(): ImportCouchDB().run() print('All users from CouchDB were imported. You can login with your old username/password to faraday now.') -@click.command() +@click.command(help="Import all your data from Couchdb Faraday databases") def import_from_couchdb(): with app.app_context(): ImportCouchDB().run() -@click.command() +@click.command(help="Create a PNG image with Faraday model object") def database_schema(): DatabaseSchema().run() -@click.command() +@click.command(help="Open a SQL Shell connected to postgresql 'Faraday DB'") def sql_shell(): try: from pgcli.main import PGCli @@ -105,7 +102,7 @@ def sql_shell(): pgcli.run_cli() -@click.command() +@click.command(help="Check critical modules in Faraday server application") def status_check(): full_status_check() @@ -125,7 +122,7 @@ def validate_email(ctx, param, value): return validate_user_unique_field(ctx, param, value) -@click.command() +@click.command(help="Create ADMIN user for Faraday application") @click.option('--username', prompt=True, callback=validate_user_unique_field) @click.option('--email', prompt=True, callback=validate_email) @click.option('--password', prompt=True, hide_input=True, @@ -148,7 +145,6 @@ def createsuperuser(username, email, password): cli.add_command(process_reports) cli.add_command(show_urls) -cli.add_command(faraday_schema_display) cli.add_command(initdb) cli.add_command(import_from_couchdb) cli.add_command(database_schema) @@ -158,5 +154,4 @@ def createsuperuser(username, email, password): if __name__ == '__main__': - cli() - + cli() \ No newline at end of file From 24e4af3a2c4da1490efa76fc4ecedd113503819b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 19 Jun 2018 17:23:23 -0300 Subject: [PATCH 1394/1506] Fix importer bug when hosts has to interfaces with same ip --- server/importer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/importer.py b/server/importer.py index cd60352cb22..c1905c21bed 100644 --- a/server/importer.py +++ b/server/importer.py @@ -452,12 +452,14 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation if check_ip_address(interface['ipv4']['address']): interface_ip = interface['ipv4']['address'] host, created = get_or_create(session, Host, ip=interface_ip, workspace=workspace) + session.flush() host.default_gateway_ip = interface['ipv4']['gateway'] self.merge_with_host(host, interface, workspace) hosts.append((host, created)) if check_ip_address(interface['ipv6']['address']): interface_ip = interface['ipv6']['address'] host, created = get_or_create(session, Host, ip=interface_ip, workspace=workspace) + session.flush() host.default_gateway_ip = interface['ipv6']['gateway'] self.merge_with_host(host, interface, workspace) hosts.append((host, created)) From ec458a99b5de5cd9eb932a79deb7766c87c70ca4 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 19 Jun 2018 18:35:29 -0300 Subject: [PATCH 1395/1506] Fix bug #4905 --- persistence/server/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/persistence/server/models.py b/persistence/server/models.py index f45b330ba85..15f39f68468 100644 --- a/persistence/server/models.py +++ b/persistence/server/models.py @@ -1035,6 +1035,8 @@ def tieBreakable(self, key): return True if key == "status": return True + if key == "refs": + return True return False def tieBreak(self, key, prop1, prop2): @@ -1043,6 +1045,10 @@ def tieBreak(self, key, prop1, prop2): Return a tuple with prop1, prop2 if we cant resolve conflict. """ + if key == "refs": + prop1.extend([x for x in prop2 if x not in prop1]) + return prop1 + if key == "confirmed": return True @@ -1268,6 +1274,9 @@ def tieBreakable(self, key): return True if key == "status": return True + if key == "refs": + return True + return False def tieBreak(self, key, prop1, prop2): @@ -1276,6 +1285,10 @@ def tieBreak(self, key, prop1, prop2): Return a tuple with prop1, prop2 if we cant resolve conflict. """ + if key == "refs": + prop1.extend([x for x in prop2 if x not in prop1]) + return prop1 + if key == "response": return self._resolve_response(prop1, prop2) From 36fd645600ef15786d3abc0a17825734b8608e80 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 19 Jun 2018 19:17:42 -0300 Subject: [PATCH 1396/1506] Fix bug #4913 --- gui/gtk/dialogs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gui/gtk/dialogs.py b/gui/gtk/dialogs.py index a4979099898..3f62c789956 100644 --- a/gui/gtk/dialogs.py +++ b/gui/gtk/dialogs.py @@ -6,12 +6,13 @@ See the file 'doc/LICENSE' for the license information ''' -import gi import webbrowser +import gi import os gi.require_version('Gtk', '3.0') +from persistence.server.server import ResourceDoesNotExist from gi.repository import Gtk, GdkPixbuf, Gdk from config.configuration import getInstanceConfiguration from persistence.server.server import is_authenticated, login_user, get_user_info, check_server_url @@ -1262,7 +1263,7 @@ def save(self, button, keeper): dialog.run() dialog.destroy() - except KeyError: # TODO: revert this hack to prevent exception when + except ResourceDoesNotExist: # TODO: revert this hack to prevent exception when # fixing conflict of non existent object dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, From 4cab3d068b709160e970d11498b3a1e00dabe94f Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 19 Jun 2018 19:49:34 -0300 Subject: [PATCH 1397/1506] Fix bug #4914 --- gui/gtk/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/gtk/server.py b/gui/gtk/server.py index 6fc35e50bf6..1d516fed523 100644 --- a/gui/gtk/server.py +++ b/gui/gtk/server.py @@ -113,10 +113,11 @@ def get_changes(): obj_id = obj_information.get('id') obj_type = obj_information.get('type') obj_name = obj_information.get('name') - obj = self.get_object(obj_type, obj_id) if action == 'CREATE': + obj = self.get_object(obj_type, obj_id) notification_center.addObject(obj) elif action == 'UPDATE': + obj = self.get_object(obj_type, obj_id) notification_center.editObject(obj) elif action == 'DELETE': notification_center.deleteObject(obj_id, obj_type) From 7c4a05200f8a739420f5f4176afccb205617fd25 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Tue, 19 Jun 2018 19:55:30 -0300 Subject: [PATCH 1398/1506] Fix bug with get a deleted service in GTK #4914 --- gui/gtk/mainwidgets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gui/gtk/mainwidgets.py b/gui/gtk/mainwidgets.py index 8aeadeae2f2..47b92552ed2 100644 --- a/gui/gtk/mainwidgets.py +++ b/gui/gtk/mainwidgets.py @@ -402,8 +402,10 @@ def remove_object(self, obj_id, obj_type): """Remove an obj of id obj_id from the model, if found there""" if obj_type == 'Host': self.remove_host(host_id=obj_id) - # elif not is_host and self._is_vuln_of_host(vuln_id=obj_id, host_id=potential_host_id): - # self.remove_vuln(vuln_id=obj_id) + elif obj_type == 'Service': + # Yeah, we query to server about services + # We are not using a cached version of model + pass else: # Since we don't know the type of the delete object, # we have to assume it's a vulnerability so the host's From f34baf42735d2509e1ae394fec8f27126a2890b4 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 19 Jun 2018 19:56:53 -0300 Subject: [PATCH 1399/1506] [ADD] test for search by id --- test_cases/test_api_vulnerability.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 75a59b75a67..5013c25a502 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -1439,6 +1439,16 @@ def test_after_deleting_vuln_ref_and_policies_remains(self, session, test_client assert PolicyViolation.query.count() == 1 assert Vulnerability.query.count() == 5 + def test_search_by_id(self, session, test_client): + vuln = VulnerabilityFactory.create() + vuln2 = VulnerabilityFactory.create(workspace=vuln.workspace) + session.add(vuln) + session.add(vuln2) + session.commit() + res = test_client.get(self.url(workspace=vuln.workspace) + '?id={0}'.format(vuln.id)) + assert res.json['count'] == 1 + res.json['vulnerabilities'][0]['value']['name'] == vuln.name + def test_type_filter(workspace, session, vulnerability_factory, From 69b28ce6bd9a726b78d9db0f610c544862f15d79 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 19 Jun 2018 20:00:01 -0300 Subject: [PATCH 1400/1506] [FIX] Search by vuln id --- server/api/modules/vulns.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 2817e40c256..c34a187bf87 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -264,6 +264,11 @@ class Meta: _strict_filtering = {'default_operator': operators.Equal} +class IDFilter(Filter): + def filter(self, query, model, attr, value): + return query.filter(model.id == value) + + class TargetFilter(Filter): def filter(self, query, model, attr, value): return query.filter(model.target_host_ip == value) @@ -311,7 +316,7 @@ class Meta(FilterSetMeta): # command, impact, issuetracker, tags, date, host # evidence, policy violations, hostnames fields = ( - "status", "website", "pname", "query", "path", "service", + "id", "status", "website", "pname", "query", "path", "service", "data", "severity", "confirmed", "name", "request", "response", "parameters", "params", "resolution", "ease_of_resolution", "description", "command_id", "target", "creator", "method", @@ -325,10 +330,12 @@ class Meta(FilterSetMeta): ) default_operator = CustomILike + # next line uses dict comprehensions! column_overrides = { field: _strict_filtering for field in strict_fields } operators = (CustomILike, operators.Equal) + id = IDFilter(fields.Int()) target = TargetFilter(fields.Str()) type = TypeFilter(fields.Str(validate=[OneOf(['Vulnerability', 'VulnerabilityWeb'])])) From 31867f2a141db093cbb24b0932a51c299bbc75d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Jun 2018 11:18:54 -0300 Subject: [PATCH 1401/1506] Add create_tables command for users not using the initdb Advanced PostgreSQL configurations are not contemplated by the initdb script, so allow the user to configure it manually and then create the tables with this command --- manage.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/manage.py b/manage.py index c1b1172d16e..72a4836904e 100755 --- a/manage.py +++ b/manage.py @@ -143,6 +143,20 @@ def createsuperuser(username, email, password): fg='green', bold=True)) +@click.command(help="Create database tables. Requires a functional " + "PostgreSQL database configured in the server.ini") +def create_tables(): + with app.app_context(): + # Ugly hack to create tables and also setting alembic revision + import server.config + conn_string = server.config.database.connection_string + from server.commands.initdb import InitDB + InitDB()._create_tables(conn_string) + click.echo(click.style( + 'Tables created successfully!', + fg='green', bold=True)) + + cli.add_command(process_reports) cli.add_command(show_urls) cli.add_command(initdb) @@ -151,7 +165,8 @@ def createsuperuser(username, email, password): cli.add_command(createsuperuser) cli.add_command(sql_shell) cli.add_command(status_check) +cli.add_command(create_tables) if __name__ == '__main__': - cli() \ No newline at end of file + cli() From f3b2d1050e5d1400eb716aed112953ca5b801e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Jun 2018 11:49:59 -0300 Subject: [PATCH 1402/1506] Use ilike filtering for target field in vulns api Instead of exact matching --- server/api/modules/vulns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index c34a187bf87..154006fb198 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -271,7 +271,7 @@ def filter(self, query, model, attr, value): class TargetFilter(Filter): def filter(self, query, model, attr, value): - return query.filter(model.target_host_ip == value) + return query.filter(model.target_host_ip.ilike("%" + value + "%")) class TypeFilter(Filter): From 6796c2f397a5b45a005804187383901df983c84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 21 Jun 2018 12:21:27 -0300 Subject: [PATCH 1403/1506] Fix filtering by status Allow using "open" and "opened" as values --- server/api/modules/vulns.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 154006fb198..918c64b1430 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -349,6 +349,10 @@ class Meta(FilterSetMeta): pname = Filter(fields.String(attribute='parameter_name')) query = Filter(fields.String(attribute='query_string')) params = Filter(fields.String(attribute='parameters')) + status = Filter(fields.Function( + deserialize=lambda val: 'open' if val == 'opened' else val, + validate=OneOf(Vulnerability.STATUSES + ['opened']) + )) def filter(self): """Generate a filtered query from request parameters. From aea752ebac389acabd0e2182838b52512abbab73 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 21 Jun 2018 12:32:16 -0300 Subject: [PATCH 1404/1506] [FIX] Update code to work with pip 10 --- utils/dependencies.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utils/dependencies.py b/utils/dependencies.py index 8d80fd42ad9..2ff854b7306 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -5,7 +5,11 @@ ''' import sys -import pip +try: + from pip import main +except ImportError: + # pip 10 compat + from pip._internal import main import pkg_resources @@ -37,4 +41,4 @@ def install_packages(packages): pip_cmd = ['install', package, '-U'] if not hasattr(sys, 'real_prefix'): pip_cmd.append('--user') - pip.main(pip_cmd) + main(pip_cmd) From 541e9d5860d42808d7c85fe61f336f0297f0cae6 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 21 Jun 2018 14:25:16 -0300 Subject: [PATCH 1405/1506] [FIX] Fix daemon start and stop --- server/web.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/server/web.py b/server/web.py index df409425dc9..3ac73cad144 100644 --- a/server/web.py +++ b/server/web.py @@ -5,7 +5,7 @@ import os import sys import functools -from signal import SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM, signal +from signal import SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIG_DFL, signal import twisted.web from twisted.web.resource import Resource, ForbiddenResource @@ -134,15 +134,17 @@ def __build_websockets_resource(self): def install_signal(self): for sig in (SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM): - def signal_handler(*args): - logger.info("Stopping threads, please wait...") - # teardown() - self.raw_report_processor.stop() - reactor.stop() + signal(sig, SIG_DFL) + - signal(sig, signal_handler) def run(self): + def signal_handler(*args): + logger.info("Stopping threads, please wait...") + # teardown() + self.raw_report_processor.stop() + reactor.stop() + site = twisted.web.server.Site(self.__root_resource) if self.__ssl_enabled: ssl_context = self.__load_ssl_certs() @@ -167,6 +169,7 @@ def run(self): except : logger.warn('Could not start websockets, address already open. This is ok is you wan to run multiple instances.') logger.info('Faraday Server is ready') + reactor.addSystemEventTrigger('before', 'shutdown', signal_handler) reactor.run() except error.CannotListenError as e: From 4f8d3b1963b1ee564d7a7fd6c0b066fa9b60a316 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 21 Jun 2018 17:32:48 -0300 Subject: [PATCH 1406/1506] Fix to allow faraday-server on multiple ports. Daemon will only work in one port for now --- faraday-server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/faraday-server.py b/faraday-server.py index 470199d7278..2ede16436c8 100755 --- a/faraday-server.py +++ b/faraday-server.py @@ -131,7 +131,7 @@ def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = sock.connect_ex((args.bind_address or server.config.faraday_server.bind_address, int(args.port or server.config.faraday_server.port))) - if is_server_running(): + if is_server_running() and result == 0: sys.exit(1) if result == 0: From 1b329a4ae655f2dc98f7eace99a788e805e3a328 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Thu, 21 Jun 2018 18:32:01 -0300 Subject: [PATCH 1407/1506] update release --- RELEASE.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 6fb9ca095c4..554bb84082b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,14 +10,25 @@ New features in the latest update TBA: --- +* Add hostname to host +* Interface removed from model and from persistence server lib (fplugin) +* Performance iprovements on the backend +* Add quick change workspace name (from all views) +* Allow user to change workspace +* New faraday styles in all webui views +* Add search by id for vulnerabilities * Add new plugin sslyze * Add new plugin wfuzz * Add xsssniper plugin -* Fix W3af, Zap +* Fix W3af, Zap plugins * Add brutexss plugin * Allow to upload report file from external tools from the web * Fix sshcheck import file from GTK -* Add reconng, sublist3r, HP Webinspect, dirsearch and ip360 plugins +* Add reconng plugin +* Add sublist3r plugin +* Add HP Webinspect plugin +* Add dirsearch plugin +* Add ip360 plugin * CouchDB was replaced by PostgreSQL :) * Host object changed, now the name property is called ip * Interface object was removed From 324c4630b201a062a339095d0933e68494136dad Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Thu, 21 Jun 2018 18:43:51 -0300 Subject: [PATCH 1408/1506] Fix bug #4904 --- server/www/scripts/statusReport/controllers/statusReport.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index e047e7a8114..3cf5a8e87c4 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -211,7 +211,7 @@ angular.module("faradayApp") "service": "110", "hostnames": "130", "target": "100", - "desc": "200", + "desc": "600", "resolution": "170", "data": "170", "easeofresolution": "140", @@ -332,7 +332,9 @@ angular.module("faradayApp") cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/desccolumn.html', headerCellTemplate: header, sort: getColumnSort('desc'), - visible: $scope.columns["desc"] + visible: $scope.columns["desc"], + minWidth: '300', + maxWidth: '600', }); $scope.gridOptions.columnDefs.push({ name : 'resolution', cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/resolutioncolumn.html', From 2a1ad520b28c253aa64983151d9f027e470ff21a Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Thu, 21 Jun 2018 20:19:25 -0300 Subject: [PATCH 1409/1506] Fix bug #4907 status href with \ --- .../statusReport/partials/ui-grid/columns/statuscolumn.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html index 9b4ba5fee07..1abaf9841fd 100644 --- a/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html +++ b/server/www/scripts/statusReport/partials/ui-grid/columns/statuscolumn.html @@ -1,12 +1,12 @@ \ No newline at end of file From 65ddb4401001a5fe8f9b58e15c585fea93ea94dd Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 22 Jun 2018 14:21:05 -0300 Subject: [PATCH 1410/1506] Disable flask session due to a bug with gtk client --- server/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app.py b/server/app.py index 680995514d3..801e49af1cf 100644 --- a/server/app.py +++ b/server/app.py @@ -207,7 +207,7 @@ def create_app(db_connection_string=None, testing=None): from server.models import db db.init_app(app) - Session(app) + #Session(app) # Setup Flask-Security app.user_datastore = SQLAlchemyUserDatastore( From 80c04e8b5cbe544483af5bb44aa7d1df9e2b8e75 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 22 Jun 2018 16:55:49 -0300 Subject: [PATCH 1411/1506] Fix bug in hostnames API #4907 --- server/api/modules/vulns.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 918c64b1430..4a646a2f009 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -33,7 +33,8 @@ Vulnerability, VulnerabilityWeb, VulnerabilityGeneric, - Workspace + Workspace, + Hostname ) from server.utils.database import get_or_create @@ -298,6 +299,21 @@ def filter(self, query, model, attr, value): alias.name == value ) +class HostnamesFilter(Filter): + def filter(self, query, model, attr, value): + alias = aliased(Hostname, name='hostname_filter') + + service_hostnames_query = query.join(Service, Service.id == Vulnerability.service_id).\ + join(Host).\ + join(alias).\ + filter(alias.name == value) + + host_hostnames_query = query.join(Host, Host.id == Vulnerability.host_id).\ + join(alias).\ + filter(alias.name == value) + + query = service_hostnames_query.union(host_hostnames_query) + return query class CustomILike(operators.Operator): """A filter operator that puts a % in the beggining and in the @@ -353,6 +369,7 @@ class Meta(FilterSetMeta): deserialize=lambda val: 'open' if val == 'opened' else val, validate=OneOf(Vulnerability.STATUSES + ['opened']) )) + hostnames = HostnamesFilter(fields.Str()) def filter(self): """Generate a filtered query from request parameters. From c220dec957ffedd23b908d37b580f057e006ac3f Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 22 Jun 2018 16:57:14 -0300 Subject: [PATCH 1412/1506] Commit test for hostnames --- test_cases/test_api_vulnerability.py | 43 ++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 5013c25a502..f38e1b5e7e8 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -45,7 +45,7 @@ from test_cases.factories import ServiceFactory, CommandFactory, \ CommandObjectFactory, HostFactory, EmptyCommandFactory, \ UserFactory, VulnerabilityWebFactory, VulnerabilityFactory, \ - ReferenceFactory, PolicyViolationFactory + ReferenceFactory, PolicyViolationFactory, HostnameFactory, WorkspaceFactory CURRENT_PATH = os.path.dirname(os.path.abspath(__file__)) @@ -1447,7 +1447,46 @@ def test_search_by_id(self, session, test_client): session.commit() res = test_client.get(self.url(workspace=vuln.workspace) + '?id={0}'.format(vuln.id)) assert res.json['count'] == 1 - res.json['vulnerabilities'][0]['value']['name'] == vuln.name + assert res.json['vulnerabilities'][0]['value']['name'] == vuln.name + + def test_search_by_hostnames_service_case(self, session, test_client): + workspace = WorkspaceFactory.create() + vuln2 = VulnerabilityFactory.create(workspace=workspace) + hostname = HostnameFactory.create(workspace=workspace, name='test.com') + host = HostFactory.create(workspace=workspace) + host.hostnames.append(hostname) + service = ServiceFactory.create(workspace=workspace, host=host) + vuln = VulnerabilityFactory.create(service=service, workspace=workspace) + session.add(vuln) + session.add(vuln2) + session.add(hostname) + session.commit() + url = self.url(workspace=workspace) + '?hostnames={0}'.format(hostname.name) + res = test_client.get(url) + import ipdb; ipdb.set_trace() + + assert res.status_code == 200 + assert res.json['count'] == 1 + assert res.json['vulnerabilities'][0]['value']['name'] == vuln.name + + def test_search_by_hostnames_host_case(self, session, test_client): + workspace = WorkspaceFactory.create() + vuln2 = VulnerabilityFactory.create(workspace=workspace) + hostname = HostnameFactory.create(workspace=workspace, name='test.com') + host = HostFactory.create(workspace=workspace) + host.hostnames.append(hostname) + vuln = VulnerabilityFactory.create(host=host, workspace=workspace) + session.add(vuln) + session.add(vuln2) + session.add(hostname) + session.commit() + import ipdb; ipdb.set_trace() + url = self.url(workspace=workspace) + '?hostnames={0}'.format(hostname.name) + res = test_client.get(url) + + assert res.status_code == 200 + assert res.json['count'] == 1 + assert res.json['vulnerabilities'][0]['value']['name'] == vuln.name def test_type_filter(workspace, session, From c4bd5a7433bb2354a5c01a28af4e1489fb29eb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 22 Jun 2018 17:56:13 -0300 Subject: [PATCH 1413/1506] Add extra flushs to couchdb importer This probably doesn't make any difference --- server/importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index c1905c21bed..437b2b0910a 100644 --- a/server/importer.py +++ b/server/importer.py @@ -592,8 +592,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation service.status = status_mapper.get(couchdb_status, 'open') service.version = document.get('version') service.workspace = workspace + session.flush() if command and created: - session.flush() CommandObject.create(service, command) yield service @@ -690,8 +690,8 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation vulnerability.impact_availability = document.get('impact', {}).get('availability') or False vulnerability.impact_confidentiality = document.get('impact', {}).get('confidentiality') or False vulnerability.impact_integrity = document.get('impact', {}).get('integrity') or False + session.flush() if command and created: - session.flush() CommandObject.create(vulnerability, command) if document['type'] == 'VulnerabilityWeb': vulnerability.query_string = document.get('query') From 2590008752383b3a40d6481a6c42cdfd1f71044b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 22 Jun 2018 17:56:53 -0300 Subject: [PATCH 1414/1506] Add vulnerability.type to unique index Otherwise, the importer would break with a vuln web with similar data to a service vulnerability --- server/models.py | 4 ++-- server/utils/database.py | 1 + test_cases/test_api_vulnerability.py | 6 +++--- test_cases/test_utils_database.py | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/server/models.py b/server/models.py index 821d0317bcc..3168cad4b42 100644 --- a/server/models.py +++ b/server/models.py @@ -1610,14 +1610,14 @@ def attachments(self): vulnerability_uniqueness = DDL( "CREATE UNIQUE INDEX uix_vulnerability ON %(fullname)s " - "(md5(name), md5(description), COALESCE(host_id, -1), COALESCE(service_id, -1), " + "(md5(name), md5(description), type, COALESCE(host_id, -1), COALESCE(service_id, -1), " "COALESCE(md5(method), ''), COALESCE(md5(parameter_name), ''), COALESCE(md5(path), ''), " "COALESCE(md5(website), ''), workspace_id, COALESCE(source_code_id, -1));" ) vulnerability_uniqueness_sqlite = DDL( "CREATE UNIQUE INDEX uix_vulnerability ON %(fullname)s " - "(name, description, COALESCE(host_id, -1), COALESCE(service_id, -1), " + "(name, description, type, COALESCE(host_id, -1), COALESCE(service_id, -1), " "COALESCE(method, ''), COALESCE(parameter_name, ''), COALESCE(path, ''), " "COALESCE(website, ''), workspace_id, COALESCE(source_code_id, -1));" ) diff --git a/server/utils/database.py b/server/utils/database.py index ed9f0a08646..2552165e004 100644 --- a/server/utils/database.py +++ b/server/utils/database.py @@ -235,6 +235,7 @@ def get_unique_fields(session, instance): 'column_names': [ 'name', 'description', + 'type', 'host_id', 'service_id', 'method', diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 5013c25a502..4c050582061 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -1242,7 +1242,7 @@ def test_create_webvuln_multiple_times_returns_conflict(self, host_with_hostname data=raw_data) assert res.status_code == 409 - def test_create_vuln_service_vuln_web_conflict( + def test_create_similar_vuln_service_and_vuln_web_conflict_succeed( self, service, vulnerability_factory, vulnerability_web_factory, session, test_client, workspace): service_vuln = vulnerability_factory.create( @@ -1263,8 +1263,8 @@ def test_create_vuln_service_vuln_web_conflict( ) raw_data['type'] = 'VulnerabilityWeb' res = test_client.post(self.url(), data=raw_data) - assert res.status_code == 409 - assert VulnerabilityGeneric.query.count() == old_count + assert res.status_code == 201 + assert VulnerabilityGeneric.query.count() == old_count + 1 def test_update_conflict(self, host, vulnerability_factory, session, test_client): diff --git a/test_cases/test_utils_database.py b/test_cases/test_utils_database.py index 4fa88dba492..26ed99c497b 100644 --- a/test_cases/test_utils_database.py +++ b/test_cases/test_utils_database.py @@ -23,6 +23,7 @@ Vulnerability: [ 'name', 'description', + 'type', 'host_id', 'service_id', 'method', @@ -53,7 +54,7 @@ def test_vulnerability_ddl_invariant(session): for statement_clean in statements_clean: statement_clean = statement_clean if statement_clean not in unique_constraint: - raise Exception('Please check server.data.utils.get_unique_fields. Vulnerability DDL changed?') + raise Exception('Please check server.utils.database.get_unique_fields. Vulnerability DDL changed?') @pytest.mark.parametrize("obj_class, expected_unique_fields", UNIQUE_FIELDS.items()) From 04f1c7d7c9351a45c2e1972e5a531602a8f8ff79 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 22 Jun 2018 18:36:48 -0300 Subject: [PATCH 1415/1506] [ADD] if beta in version, skip version check --- faraday.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/faraday.py b/faraday.py index ba30d562b4a..202b74290ba 100755 --- a/faraday.py +++ b/faraday.py @@ -420,6 +420,9 @@ def checkUpdates(): resp = resp.text.strip() except Exception as e: logger.error(e) + version = getInstanceConfiguration().getVersion() + if 'b' in version.split("+")[0]: + return if not resp == u'OK': logger.info("You have available updates. Run ./faraday.py --update to catchup!") else: From f50ad9c3a74607a7da764d7086072a8256d158f5 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Fri, 22 Jun 2018 18:42:07 -0300 Subject: [PATCH 1416/1506] Update version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2e2c8b81e03..391435a30dc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0a1+white +3.0b1+white From 2a16dbcf39ea3e52c4375750cfa7799bd7ceb6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Fri, 22 Jun 2018 19:06:21 -0300 Subject: [PATCH 1417/1506] Fix command importing with duplicated couch databases It was mixing data from both workspaces. This isn't a security vulnerability, since it will only happen on duplicated databases --- server/importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/importer.py b/server/importer.py index 437b2b0910a..9bdd26a5111 100644 --- a/server/importer.py +++ b/server/importer.py @@ -336,7 +336,7 @@ def update_command_tools(workspace, command_tool_map, id_map): command_id )) continue - assert workspace.id == command.workspace_id + assert workspace.id == command.workspace_id, (workspace.id, command.workspace_id) if command.tool and command.tool != 'unknown': logger.warn("Command {} (Couch ID {}) has already a tool. " "Overriding it".format(command_id, @@ -812,6 +812,7 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation Command, command=document.get('command', None), start_date=start_date, + workspace=workspace, ) if document.get('duration'): From 9c6de0c4d42e692de0b7dde0561233611de430de Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 25 Jun 2018 15:02:20 +0000 Subject: [PATCH 1418/1506] Show a message to the user when xdot is missing --- server/commands/faraday_schema_display.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/commands/faraday_schema_display.py b/server/commands/faraday_schema_display.py index e62181e75d3..68ae99aca3f 100644 --- a/server/commands/faraday_schema_display.py +++ b/server/commands/faraday_schema_display.py @@ -38,7 +38,14 @@ def _draw_entity_diagrama(self): rankdir='LR', # From left to right (instead of top to bottom) concentrate=False # Don't try to join the relation lines together ) - graph.write_png('entity_dbschema.png') # write out the file + try: + graph.write_png('entity_dbschema.png') # write out the file + except OSError as ex: + if 'dot' in ex.strerror: + print('Rendering entity scheam requires dot. Please install it with: sudo apt install xdot') + sys.exit(1) + raise + def _draw_uml_class_diagram(self): # lets find all the mappers in our model From c738443230e43a36e7a7647044f9bbe6ab9c063f Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Mon, 25 Jun 2018 13:16:07 -0300 Subject: [PATCH 1419/1506] [MOD] Take the hostnames search parameter to a list comma separated --- server/api/modules/vulns.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/api/modules/vulns.py b/server/api/modules/vulns.py index 4a646a2f009..f13a58f2ea1 100644 --- a/server/api/modules/vulns.py +++ b/server/api/modules/vulns.py @@ -303,14 +303,16 @@ class HostnamesFilter(Filter): def filter(self, query, model, attr, value): alias = aliased(Hostname, name='hostname_filter') + value_list = value.split(",") + service_hostnames_query = query.join(Service, Service.id == Vulnerability.service_id).\ join(Host).\ join(alias).\ - filter(alias.name == value) + filter(alias.name.in_(value_list)) host_hostnames_query = query.join(Host, Host.id == Vulnerability.host_id).\ join(alias).\ - filter(alias.name == value) + filter(alias.name.in_(value_list)) query = service_hostnames_query.union(host_hostnames_query) return query From f1cebfb5ee8a29add525850dd0ec459f7d6671c2 Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Mon, 25 Jun 2018 14:18:43 -0300 Subject: [PATCH 1420/1506] [ADD] Comma-separated list hostname test --- test_cases/test_api_vulnerability.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test_cases/test_api_vulnerability.py b/test_cases/test_api_vulnerability.py index 4c050582061..7eab88f7e52 100644 --- a/test_cases/test_api_vulnerability.py +++ b/test_cases/test_api_vulnerability.py @@ -45,7 +45,7 @@ from test_cases.factories import ServiceFactory, CommandFactory, \ CommandObjectFactory, HostFactory, EmptyCommandFactory, \ UserFactory, VulnerabilityWebFactory, VulnerabilityFactory, \ - ReferenceFactory, PolicyViolationFactory + ReferenceFactory, PolicyViolationFactory, HostnameFactory CURRENT_PATH = os.path.dirname(os.path.abspath(__file__)) @@ -1449,6 +1449,27 @@ def test_search_by_id(self, session, test_client): assert res.json['count'] == 1 res.json['vulnerabilities'][0]['value']['name'] == vuln.name + def test_hostnames_comma_separated(self, test_client, session): + #Create Host A with hostname HA + hostnameA = HostnameFactory.create() + hostnameA.host.workspace = hostnameA.workspace + #Create Host B with hostname HB + hostnameB = HostnameFactory.create(workspace=hostnameA.workspace) + hostnameB.host.workspace = hostnameA.workspace + #Create Vuln with Host A + vuln = VulnerabilityFactory.create(host=hostnameA.host, workspace=hostnameA.workspace) + #Create Vuln with Host B + vuln2 = VulnerabilityFactory.create(host=hostnameB.host, workspace=hostnameA.workspace) + session.add(hostnameA) + session.add(hostnameB) + session.add(vuln) + session.add(vuln2) + session.commit() + + #Search with hosnames=HA,HB + res = test_client.get(self.url(workspace=vuln.workspace) + '?hostname={0},{1}'.format(hostnameA,hostnameB)) + assert res.status_code == 200 + assert res.json['count'] == 2 def test_type_filter(workspace, session, vulnerability_factory, From a31d453657c4913aa3fb0f093acdf2865ba37ba8 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 25 Jun 2018 17:39:27 +0000 Subject: [PATCH 1421/1506] [FIX] remove duplicate parameter --- server/www/scripts/services/controllers/serviceModalEdit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/services/controllers/serviceModalEdit.js b/server/www/scripts/services/controllers/serviceModalEdit.js index 16e95deddaf..3e1c8cdda67 100644 --- a/server/www/scripts/services/controllers/serviceModalEdit.js +++ b/server/www/scripts/services/controllers/serviceModalEdit.js @@ -4,8 +4,8 @@ angular.module('faradayApp') .controller('serviceModalEdit', - ['$q', '$scope', '$modalInstance', '$routeParams', 'SERVICE_STATUSES', 'service', 'servicesManager', 'commonsFact', 'servicesManager', - function($q, $scope, $modalInstance, $routeParams, SERVICE_STATUSES, service, servicesManager, commonsFact, servicesManager) { + ['$q', '$scope', '$modalInstance', '$routeParams', 'SERVICE_STATUSES', 'service', 'servicesManager', 'commonsFact', + function($q, $scope, $modalInstance, $routeParams, SERVICE_STATUSES, service, servicesManager, commonsFact) { init = function() { // current Workspace From f994c41c2d04ff1d6cefbbdb839e019d2a997367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 26 Jun 2018 19:56:40 -0300 Subject: [PATCH 1422/1506] Update RELEASE.md --- RELEASE.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 554bb84082b..30b9245c8a5 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,11 +10,10 @@ New features in the latest update TBA: --- -* Add hostname to host * Interface removed from model and from persistence server lib (fplugin) * Performance iprovements on the backend * Add quick change workspace name (from all views) -* Allow user to change workspace +* Changed the scope field of a workspace from a free text input to a list of targets * New faraday styles in all webui views * Add search by id for vulnerabilities * Add new plugin sslyze @@ -40,17 +39,15 @@ TBA: * Allow the user to specify the desired fields of the host list table * Add optional hostnames, services, MAC and description fields to the host list * Workspace names can be changed from the Web UI -* Changed the scope field of a workspace from a free text input to a list of targets * Exploitation and severity fields only allow certain values. CWE CVEs were fixed to be valid. A script to convert custom CSVs was added. * Web UI path changed from /_ui/ to / (_ui has now a redirection to / for keeping backwards compatibility) -* dirb plugin should creates a vulnerability type information instead of a note. +* dirb plugin creates an informational vulnerability instead of a note. * Add confirmed column to exported csv from webui * Fixes in Arachni plugin * Add new parameters --keep-old and --keep-new for faraday CLI * Add new screenshot fplugin which takes a screenshot of the ip:ports of a given protocol * Add fix for net sparker regular and cloud fix on severity * Removed Chat feature (data is kept inside notes) -* Plugin reports now can be imported in the server, from the Web UI * Add CVSS score to reference field in Nessus plugin. * Fix unicode characters bug in Netsparker plugin. * Fix qualys plugin. From ab316b82a74c6874fe9769aa36f8019457eb6ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 26 Jun 2018 19:57:00 -0300 Subject: [PATCH 1423/1506] Fix typo in vuln create error message --- server/www/scripts/statusReport/controllers/modalNew.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/www/scripts/statusReport/controllers/modalNew.js b/server/www/scripts/statusReport/controllers/modalNew.js index 41d9f4567c4..faed51fcea9 100644 --- a/server/www/scripts/statusReport/controllers/modalNew.js +++ b/server/www/scripts/statusReport/controllers/modalNew.js @@ -139,7 +139,7 @@ angular.module('faradayApp') $modalInstance.close(vm.data); }, function(response){ if (response.status == 409) { - commonsFact.showMessage("Error while creating a new Vulnerability " + vm.data.name + " Conflicting Vulnarability with id: " + response.data.object._id + ". " + response.data.message); + commonsFact.showMessage("Error while creating a new Vulnerability " + vm.data.name + " Conflicting Vulnerability with id: " + response.data.object._id + ". " + response.data.message); } else { commonsFact.showMessage("Error from backend: " + response.status); } From fd1939026eb9e8b6e32724ea9b60ccef2f5b1a50 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 27 Jun 2018 15:41:11 -0300 Subject: [PATCH 1424/1506] [FIX] version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 391435a30dc..566a271a6f5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0b1+white +3.0b2 From 374e24d8cb1196f8e4f8993f45af0a5ae4ccb0a7 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Wed, 27 Jun 2018 15:48:26 -0300 Subject: [PATCH 1425/1506] Update version to beta3 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 566a271a6f5..e536b8a2f28 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0b2 +3.0b3 From e47c68dfee80bec6e72880d3c1016b412fc76430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 27 Jun 2018 15:55:36 -0300 Subject: [PATCH 1426/1506] Revert "Add hardcoded rollbar credentials" This reverts commit 108c2a17832e91b461001095765dc2fd06772412. --- server/www/index.html | 1 - server/www/script/ng-rollbar.min.js | 1 - server/www/scripts/app.js | 12 +++--------- 3 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 server/www/script/ng-rollbar.min.js diff --git a/server/www/index.html b/server/www/index.html index 7d5f5fd27ec..b34bf0826f5 100644 --- a/server/www/index.html +++ b/server/www/index.html @@ -83,7 +83,6 @@ - diff --git a/server/www/script/ng-rollbar.min.js b/server/www/script/ng-rollbar.min.js deleted file mode 100644 index 535ad1bee45..00000000000 --- a/server/www/script/ng-rollbar.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(angular){angular.module("tandibar/ng-rollbar",[]);angular.module("tandibar/ng-rollbar").config(["$provide",function($provide){$provide.decorator("$exceptionHandler",["$delegate","$injector","$window",function($delegate,$injector,$window){return function(exception,cause){if($window.Rollbar){$window.Rollbar.error(exception,{cause:cause},function(err,data){var $rootScope=$injector.get("$rootScope");$rootScope.$emit("rollbar:exception",{exception:exception,err:err,data:data.result})})}$delegate(exception,cause)}}])}]);angular.module("tandibar/ng-rollbar").provider("Rollbar",function RollbarProvider(){var rollbarProvider=this;var rollbarActivated=true;this.init=function(config){var _rollbarConfig=config;if(rollbarActivated){!function(r){function o(n){if(e[n])return e[n].exports;var t=e[n]={exports:{},id:n,loaded:!1};return r[n].call(t.exports,t,t.exports,o),t.loaded=!0,t.exports}var e={};return o.m=r,o.c=e,o.p="",o(0)}([function(r,o,e){"use strict";var n=e(1),t=e(4);_rollbarConfig=_rollbarConfig||{},_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/2.3.1/rollbar.min.js",_rollbarConfig.async=void 0===_rollbarConfig.async||_rollbarConfig.async;var a=n.setupShim(window,_rollbarConfig),l=t(_rollbarConfig);window.rollbar=n.Rollbar,a.loadFull(window,document,!_rollbarConfig.async,_rollbarConfig,l)},function(r,o,e){"use strict";function n(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}function t(r,o){this.options=r,this._rollbarOldOnError=null;var e=s++;this.shimId=function(){return e},window&&window._rollbarShims&&(window._rollbarShims[e]={handler:o,messages:[]})}function a(r,o){var e=o.globalAlias||"Rollbar";if("object"==typeof r[e])return r[e];r._rollbarShims={},r._rollbarWrappedError=null;var t=new p(o);return n(function(){o.captureUncaught&&(t._rollbarOldOnError=r.onerror,i.captureUncaughtExceptions(r,t,!0),i.wrapGlobals(r,t,!0)),o.captureUnhandledRejections&&i.captureUnhandledRejections(r,t,!0);var n=o.autoInstrument;return o.enabled!==!1&&(void 0===n||n===!0||"object"==typeof n&&n.network)&&r.addEventListener&&(r.addEventListener("load",t.captureLoad.bind(t)),r.addEventListener("DOMContentLoaded",t.captureDomContentLoaded.bind(t))),r[e]=t,t})()}function l(r){return n(function(){var o=this,e=Array.prototype.slice.call(arguments,0),n={shim:o,method:r,args:e,ts:new Date};window._rollbarShims[this.shimId()].messages.push(n)})}var i=e(2),s=0,d=e(3),c=function(r,o){return new t(r,o)},p=d.bind(null,c);t.prototype.loadFull=function(r,o,e,t,a){var l=function(){var o;if(void 0===r._rollbarDidLoad){o=new Error("rollbar.js did not load");for(var e,n,t,l,i=0;e=r._rollbarShims[i++];)for(e=e.messages||[];n=e.shift();)for(t=n.args||[],i=0;i Date: Wed, 27 Jun 2018 15:59:55 -0300 Subject: [PATCH 1427/1506] Update version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index e536b8a2f28..70a5c59c74c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0b3 +3.0b4 From b0998c327f1cfc1442615a6c0bd5e1f1f09a89e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 27 Jun 2018 16:54:42 -0300 Subject: [PATCH 1428/1506] Adapt service important to actual unique constraints --- server/importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/importer.py b/server/importer.py index 9bdd26a5111..001e3a892b1 100644 --- a/server/importer.py +++ b/server/importer.py @@ -564,13 +564,13 @@ def update_from_document(self, document, workspace, level=None, couchdb_relation port = 65535 service, created = get_or_create(session, Service, - name=document.get('name'), + protocol=document.get('protocol'), port=port, host=host) service.description = document.get('description') service.owned = document.get('owned', False) service.banner = document.get('banner') - service.protocol = document.get('protocol') + service.name = document.get('name') if not document.get('status'): logger.warning('Service {0} with empty status. Using \'open\' as status'.format(document['_id'])) document['status'] = 'open' From 27aa1e74d95ec1e6386bf59ad8e5cc49a305a05b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Wed, 27 Jun 2018 16:56:31 -0300 Subject: [PATCH 1429/1506] Fix string encoding bug on importer I don't know what caused it, just skipped the conflicting characters :P --- server/utils/invalid_chars.py | 44 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/server/utils/invalid_chars.py b/server/utils/invalid_chars.py index 97238a5cfa5..db0278c2f9c 100644 --- a/server/utils/invalid_chars.py +++ b/server/utils/invalid_chars.py @@ -46,26 +46,30 @@ def clean_string(s): def clean_char(char): - #Get rid of the ctrl characters first. - #http://stackoverflow.com/questions/1833873/python-regex-escape-characters - char = re.sub('\x1b[^m]*m', '', char) - #Clean up invalid xml - char = remove_invalid_chars(char) - replacements = [ - (u'\u201c', '\"'), - (u'\u201d', '\"'), - (u"\u001B", ' '), #http://www.fileformat.info/info/unicode/char/1b/index.htm - (u"\u0019", ' '), #http://www.fileformat.info/info/unicode/char/19/index.htm - (u"\u0016", ' '), #http://www.fileformat.info/info/unicode/char/16/index.htm - (u"\u001C", ' '), #http://www.fileformat.info/info/unicode/char/1c/index.htm - (u"\u0003", ' '), #http://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=0x - (u"\u000C", ' ') - ] - for rep, new_char in replacements: - if char == rep: - #print ord(char), char.encode('ascii', 'ignore') - return new_char - return char + try: + #Get rid of the ctrl characters first. + #http://stackoverflow.com/questions/1833873/python-regex-escape-characters + char = re.sub('\x1b[^m]*m', '', char) + #Clean up invalid xml + char = remove_invalid_chars(char) + replacements = [ + (u'\u201c', '\"'), + (u'\u201d', '\"'), + (u"\u001B", ' '), #http://www.fileformat.info/info/unicode/char/1b/index.htm + (u"\u0019", ' '), #http://www.fileformat.info/info/unicode/char/19/index.htm + (u"\u0016", ' '), #http://www.fileformat.info/info/unicode/char/16/index.htm + (u"\u001C", ' '), #http://www.fileformat.info/info/unicode/char/1c/index.htm + (u"\u0003", ' '), #http://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=0x + (u"\u000C", ' ') + ] + for rep, new_char in replacements: + if char == rep: + #print ord(char), char.encode('ascii', 'ignore') + return new_char + return char + except UnicodeEncodeError: + # Ugly hack triggered when importing some strange objects + return '' def remove_invalid_chars(c): From c57fe314ea01160a66d8fb4fa44d948ad51b19b5 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Wed, 27 Jun 2018 18:04:44 -0300 Subject: [PATCH 1430/1506] Fix bug Object Object in Workspace edit Webui #4911 --- server/www/scripts/commons/controllers/headerCtrl.js | 4 +++- server/www/scripts/statusReport/controllers/statusReport.js | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/www/scripts/commons/controllers/headerCtrl.js b/server/www/scripts/commons/controllers/headerCtrl.js index c9a4e5e7db7..e4e733d4a11 100644 --- a/server/www/scripts/commons/controllers/headerCtrl.js +++ b/server/www/scripts/commons/controllers/headerCtrl.js @@ -87,7 +87,9 @@ angular.module('faradayApp') // copy pasted from server/www/scripts/workspaces/controllers/workspaces.js // it makes scope work properly (i think) workspace.scope = workspace.scope.map(function(scope){ - return {key: scope} + if(scope.key === undefined) + return {key: scope}; + return scope; }); if (workspace.scope.length == 0) workspace.scope.push({key: ''}); diff --git a/server/www/scripts/statusReport/controllers/statusReport.js b/server/www/scripts/statusReport/controllers/statusReport.js index 3cf5a8e87c4..a1b3cf90b45 100644 --- a/server/www/scripts/statusReport/controllers/statusReport.js +++ b/server/www/scripts/statusReport/controllers/statusReport.js @@ -46,7 +46,6 @@ angular.module("faradayApp") var init = function() { $scope.baseurl = BASEURL; - console.log($scope.baseurl); $scope.severities = SEVERITIES; $scope.easeofresolution = EASEOFRESOLUTION; $scope.propertyGroupBy = $routeParams.groupbyId; From 47517018d40e31c9ec47c6628d5f4dff20d356bb Mon Sep 17 00:00:00 2001 From: Eric Horvat Date: Thu, 28 Jun 2018 17:28:09 -0300 Subject: [PATCH 1431/1506] [FIX] If server is not available, print an message and exit --- faraday.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/faraday.py b/faraday.py index 202b74290ba..afd8ec49a2b 100755 --- a/faraday.py +++ b/faraday.py @@ -449,6 +449,12 @@ def checkServerUrl(): sys.exit(-1) except requests.exceptions.MissingSchema as ex: print("Check ~/.faraday/config/user.xml server url, the following error was found: {0} ".format(ex)) + except requests.exceptions.ConnectionError: + print("\n\n" + "Can't connect to server. \nCheck if server is running or the file ~/.faraday/config/user.xml server url." + "\n\n" + ) + sys.exit(-1) def check_faraday_version(): From 0dfe2ab6b1c8ab776bd5838ce80eca3c5c93581d Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 29 Jun 2018 17:42:07 -0300 Subject: [PATCH 1432/1506] Support for MAC OS in initdb --- server/commands/initdb.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/commands/initdb.py b/server/commands/initdb.py index ef8fb96a85f..a5dbcf74cb1 100644 --- a/server/commands/initdb.py +++ b/server/commands/initdb.py @@ -163,11 +163,12 @@ def _configure_new_postgres_user(self, psql_log_file): """ print('This script will {blue} create a new postgres user {white} and {blue} save faraday-server settings {white}(server.ini). '.format(blue=Fore.BLUE, white=Fore.WHITE)) username = 'faraday_postgresql' - postgres_command = ['sudo', '-u', 'postgres'] + postgres_command = ['sudo', '-u', 'postgres', 'psql'] if sys.platform == 'darwin': - postgres_command = [] + print('{blue}MAC OS detected{white}'.format(blue=Fore.BLUE, white=Fore.WHITE)) + postgres_command = ['psql', 'postgres'] password = self.generate_random_pw(25) - command = postgres_command + ['psql', '-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)] + command = postgres_command + [ '-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)] p = Popen(command, stderr=psql_log_file, stdout=psql_log_file) p.wait() psql_log_file.seek(0) From 5b834c0000484feb1805ca590183094bf3b436db Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 29 Jun 2018 19:55:18 -0300 Subject: [PATCH 1433/1506] Fix various login issues in faraday client --- faraday.py | 184 +++++++++++++++++++++++++---------------------------- 1 file changed, 88 insertions(+), 96 deletions(-) diff --git a/faraday.py b/faraday.py index afd8ec49a2b..5fab2278d3e 100755 --- a/faraday.py +++ b/faraday.py @@ -1,15 +1,16 @@ #!/usr/bin/env python2.7 ''' Faraday Penetration Test IDE -Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) +Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/) See the file 'doc/LICENSE' for the license information - ''' import os import sys import shutil +import getpass import argparse +import requests.exceptions from config.configuration import getInstanceConfiguration from config.globals import ( @@ -70,8 +71,8 @@ def getParserArgs(): - """Parser setup for faraday launcher arguments. - + """ + Parser setup for faraday launcher arguments. """ parser = argparse.ArgumentParser( @@ -176,15 +177,13 @@ def getParserArgs(): parser.add_argument('-v', '--version', action='version', version='Faraday v{version}'.format(version=f_version)) - # args = parser.parse_args(['@parser_args.cfg']) return parser.parse_args() def check_dependencies_or_exit(): - """Dependency resolver based on a previously specified CONST_REQUIREMENTS_FILE. - + """ + Dependency resolver based on a previously specified CONST_REQUIREMENTS_FILE. Currently checks a list of dependencies from a file and exits if they are not met. - """ installed_deps, missing_deps, conflict_deps = dependencies.check_dependencies(requirements_file=FARADAY_REQUIREMENTS_FILE) @@ -210,11 +209,10 @@ def check_dependencies_or_exit(): logger.info("Dependencies met") def setConf(): - """User configuration management and instantiation. - + """ + User configuration management and instantiation. Setting framework configuration based either on previously user saved settings or default ones. - """ logger.info("Setting configuration.") @@ -260,7 +258,6 @@ def startFaraday(): start = main_app.start from colorama import Fore, Back, Style - import string serverURL = getInstanceConfiguration().getServerURI() if serverURL: url = "%s/_ui" % serverURL @@ -276,7 +273,8 @@ def startFaraday(): def setupPlugins(dev_mode=False): - """Checks and handles Faraday's plugin status. + """ + Checks and handles Faraday's plugin status. When dev_mode is True, the user enters in development mode and the plugins will be replaced with the latest ones. @@ -286,7 +284,6 @@ def setupPlugins(dev_mode=False): TODO: When dependencies are not satisfied ask user if he wants to try and run faraday with a inestability warning. - """ if dev_mode: @@ -303,11 +300,11 @@ def setupPlugins(dev_mode=False): def setupZSH(): - """Cheks and handles Faraday's integration with ZSH. + """ + Checks and handles Faraday's integration with ZSH. If the user has a .zshrc file, it gets copied and integrated with faraday's zsh plugin. - """ if os.path.isfile(USER_ZSHRC): @@ -325,7 +322,8 @@ def setupZSH(): def setupXMLConfig(): - """Checks user configuration file status. + """ + Checks user configuration file status. If there is no custom config the default one will be copied as a default. """ @@ -338,7 +336,8 @@ def setupXMLConfig(): def setupImages(): - """ Copy png icons + """ + Copy png icons """ if os.path.exists(FARADAY_USER_IMAGES): shutil.rmtree(FARADAY_USER_IMAGES) @@ -346,7 +345,8 @@ def setupImages(): def checkConfiguration(gui_type): - """Checks if the environment is ready to run Faraday. + """ + Checks if the environment is ready to run Faraday. Checks different environment requirements and sets them before starting Faraday. This includes checking for plugin folders, libraries, @@ -364,8 +364,8 @@ def checkConfiguration(gui_type): def setupFolders(folderlist): - """Checks if a list of folders exists and creates them otherwise. - + """ + Checks if a list of folders exists and creates them otherwise. """ for folder in folderlist: @@ -374,8 +374,8 @@ def setupFolders(folderlist): def checkFolder(folder): - """Checks whether a folder exists and creates it if it doesn't. - + """ + Checks whether a folder exists and creates it if it doesn't. """ if not os.path.isdir(folder): @@ -385,8 +385,8 @@ def checkFolder(folder): def printBanner(): - """Prints Faraday's ascii banner. - + """ + Prints Faraday's ascii banner. """ from colorama import Fore, Back, Style print (Fore.RED + """ @@ -429,72 +429,61 @@ def checkUpdates(): logger.info("No updates available, enjoy Faraday.") -def checkServerUrl(): - import requests - CONF = getInstanceConfiguration() - server_url = CONF.getServerURI() - - if server_url is None or CONF.getAPIUsername() is None or CONF.getAPIUsername() is None: - doLoginLoop() - server_url = CONF.getServerURI() - - try: - requests.get(server_url, timeout=5) - except requests.exceptions.SSLError: - print(""" - SSL certificate validation failed. - You can use the --cert option in Faraday - to set the path of the cert - """) - sys.exit(-1) - except requests.exceptions.MissingSchema as ex: - print("Check ~/.faraday/config/user.xml server url, the following error was found: {0} ".format(ex)) - except requests.exceptions.ConnectionError: - print("\n\n" - "Can't connect to server. \nCheck if server is running or the file ~/.faraday/config/user.xml server url." - "\n\n" - ) - sys.exit(-1) - - def check_faraday_version(): try: server.check_faraday_version() except RuntimeError: - getLogger("launcher").error("The server is running a different Faraday version than the client " - "you are running. Version numbers must match!") - + getLogger("launcher").error( + "The server is running a different Faraday version than the client you are running. Version numbers must match!") sys.exit(2) -def doLoginLoop(): - """ Sets the username and passwords from the command line. - If --login flag is set then username and password is set """ +def try_login_user(server_uri, api_username, api_password): + + try: + session_cookie = login_user(server_uri, api_username, api_password) + return session_cookie + except requests.exceptions.SSLError: + print("SSL certificate validation failed.\nYou can use the --cert option in Faraday to set the path of the cert") + sys.exit(-1) + except requests.exceptions.MissingSchema: + print("The Faraday server URL is incorrect, please try again.") + sys.exit(-2) - import getpass - print("""\nTo login please provide your valid DB Credentials.\n -You have 3 attempts.""") +def doLoginLoop(): + """ + Sets the username and passwords from the command line. + If --login flag is set then username and password is set + """ try: CONF = getInstanceConfiguration() - server_url = CONF.getAPIUrl() - if server_url is None: - server_url = raw_input( - "Please enter the faraday server url (press enter for http://localhost:5985): ") or "http://localhost:5985" - CONF.setAPIUrl(server_url) + old_server_url = CONF.getAPIUrl() + + if old_server_url is None: + new_server_url = raw_input( + "\nPlease enter the Faraday server URL (Press enter for http://localhost:5985): ") or "http://localhost:5985" + else: + new_server_url = raw_input( + "\nPlease enter the Faraday server URL (Press enter for last used: {}): ".format(old_server_url)) or old_server_url + + CONF.setAPIUrl(new_server_url) + + print("""\nTo login please provide your valid Faraday credentials.\nYou have 3 attempts.""") for attempt in range(1, 4): - username = raw_input("Username (press enter for faraday): ") or "faraday" - password = getpass.getpass('Password: ') + api_username = raw_input("Username (press enter for faraday): ") or "faraday" + api_password = getpass.getpass('Password: ') + + session_cookie = try_login_user(new_server_url, api_username, api_password) - session_cookie = login_user(server_url, username, password) if session_cookie: - CONF.setAPIUsername(username) - CONF.setAPIPassword(password) + CONF.setAPIUsername(api_username) + CONF.setAPIPassword(api_password) CONF.setDBSessionCookies(session_cookie) CONF.saveConfig() @@ -503,18 +492,42 @@ def doLoginLoop(): print("You can't login as a client. You have %s attempt(s) left." % (3 - attempt)) continue - logger.info('Login successful') - + logger.info('Login successful: {0}'.format(api_username)) break + print('Login failed, please try again. You have %d more attempts' % (3 - attempt)) else: logger.fatal('Invalid credentials, 3 attempts failed. Quitting Faraday...') sys.exit(-1) + except KeyboardInterrupt: sys.exit(0) +def login(forced_login): + + CONF = getInstanceConfiguration() + server_uri = CONF.getServerURI() + api_username = CONF.getAPIUsername() + api_password = CONF.getAPIPassword() + + if forced_login: + doLoginLoop() + return + + if server_uri and api_username and api_password: + + session_cookie = try_login_user(server_uri, api_username, api_password) + + if session_cookie: + CONF.setDBSessionCookies(session_cookie) + logger.info('Login successful: {0}'.format(api_username)) + return + + doLoginLoop() + + def main(): """ Main function for launcher. @@ -524,38 +537,17 @@ def main(): global logger, args logger = getLogger("launcher") - args = getParserArgs() setupFolders(CONST_FARADAY_FOLDER_LIST) setUpLogger(args.debug) - if not args.nodeps: check_dependencies_or_exit() - printBanner() if args.cert_path: os.environ[REQUESTS_CA_BUNDLE_VAR] = args.cert_path checkConfiguration(args.gui) setConf() - checkServerUrl() - CONF = getInstanceConfiguration() - if args.login: - if not CONF.getServerURI(): - couchURI = raw_input("Enter the Faraday server [http://127.0.0.1:5985]: ") or "http://127.0.0.1:5985" - - if couchURI: - CONF.setAPIUrl(couchURI) - checkServerUrl() - else: - logger.fatal('Please configure Faraday server to authenticate (--login)') - sys.exit(-1) - - doLoginLoop() - else: - session_cookie = login_user(CONF.getServerURI(), CONF.getAPIUsername(), CONF.getAPIPassword()) - if session_cookie: - CONF.setDBSessionCookies(session_cookie) - + login(args.login) check_faraday_version() checkUpdates() startFaraday() From 5869f93773cc17d5516443b7327819e956293443 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 29 Jun 2018 20:03:33 -0300 Subject: [PATCH 1434/1506] Remove bad print in faraday client, related to client role --- faraday.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/faraday.py b/faraday.py index 5fab2278d3e..ed80e1165ed 100755 --- a/faraday.py +++ b/faraday.py @@ -489,7 +489,7 @@ def doLoginLoop(): user_info = get_user_info() if (user_info is None) or (not user_info) or ('username' not in user_info): - print("You can't login as a client. You have %s attempt(s) left." % (3 - attempt)) + print('Login failed, please try again. You have %d more attempts' % (3 - attempt)) continue logger.info('Login successful: {0}'.format(api_username)) From 6de6d5af59d0229fb23d2134cf56783cb6007e66 Mon Sep 17 00:00:00 2001 From: Ezequiel Tavella Date: Fri, 29 Jun 2018 20:27:49 -0300 Subject: [PATCH 1435/1506] Remove duplicated item in requirements_extras --- requirements_extras.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements_extras.txt b/requirements_extras.txt index 14967431c7f..1fa335710dc 100644 --- a/requirements_extras.txt +++ b/requirements_extras.txt @@ -1,4 +1,3 @@ beautifulsoup4>=4.6.0 -psycopg2>=2.7.3 w3af_api_client>=1.1.7 selenium>=3.9.0 From 9d25e866b664c754ed7e71e57056f07dd0c0f7e5 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Mon, 2 Jul 2018 12:27:49 -0300 Subject: [PATCH 1436/1506] [FIX] design changes. All remove rounded borders from all button. add some padding in vuln by severity --- server/www/estilos-v3.css | 4 ++++ server/www/estilos.css | 22 +++++++++---------- .../partials/modal-hosts-by-service.html | 2 +- .../dashboard/partials/vulns-by-severity.html | 2 +- .../statusReport/partials/modalNew.html | 4 ++-- .../scripts/vulndb/partials/modalEdit.html | 4 ++-- .../www/scripts/vulndb/partials/modalNew.html | 4 ++-- .../workspaces/partials/modalEdit.html | 4 ++-- .../scripts/workspaces/partials/modalNew.html | 4 ++-- 9 files changed, 27 insertions(+), 23 deletions(-) diff --git a/server/www/estilos-v3.css b/server/www/estilos-v3.css index c7eda195dd5..79d88a2c6e7 100644 --- a/server/www/estilos-v3.css +++ b/server/www/estilos-v3.css @@ -217,3 +217,7 @@ .jumbotron { background-color: #f4f3f4; } + +.btn { + border-radius: 0px; +} diff --git a/server/www/estilos.css b/server/www/estilos.css index 789185937ea..726f6328a80 100644 --- a/server/www/estilos.css +++ b/server/www/estilos.css @@ -196,16 +196,16 @@ article.untercio{ .untercio article{ height: 20%; } - .seccion article { - min-height: 85px; - font-size: 11px; - text-align: left; - } - .seccion article header { - background: #FFFFFF; - min-height: 40px; - text-align: left; - } +.seccion article { + min-height: 85px; + font-size: 11px; + text-align: left; +} +.seccion article header { + background: #FFFFFF; + min-height: 40px; + text-align: left; +} .seccion article header h2 { color: #101010; font-size: 14px; @@ -1308,4 +1308,4 @@ a.button-disable{cursor: not-allowed;pointer-events: none;opacity: 0.5} } .label-success-impact { background-color: #5cb85c - } \ No newline at end of file + } diff --git a/server/www/scripts/dashboard/partials/modal-hosts-by-service.html b/server/www/scripts/dashboard/partials/modal-hosts-by-service.html index 7474300ebbd..6792129482c 100644 --- a/server/www/scripts/dashboard/partials/modal-hosts-by-service.html +++ b/server/www/scripts/dashboard/partials/modal-hosts-by-service.html @@ -41,6 +41,6 @@

Services for {{name}} ({{hosts.length}} total)

-