diff --git a/.gitignore b/.gitignore index ded60678804..16c9bdfb11d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,9 @@ nosetests.xml .mr.developer.cfg .project .pydevproject + +# ui_mranea +lib-ubuntu13-10-i686.tgz +lib-ubuntu13-10-i686/ +views/reports/_attachments/reports/push.sh +views/upload diff --git a/apis/__init__.py b/apis/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apis/rest/__init__.py b/apis/rest/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/api.py b/apis/rest/api.py similarity index 52% rename from plugins/api.py rename to apis/rest/api.py index 3dd190c61e9..06d05baab02 100644 --- a/plugins/api.py +++ b/apis/rest/api.py @@ -19,6 +19,9 @@ from plugins.core import PluginControllerForApi from managers.all import CommandManager +from model.visitor import VulnsLookupVisitor + +import utils.logs as logger _plugin_controller_api = None @@ -37,59 +40,38 @@ def stopServer(): IOLoop.instance().stop() -def startPluginControllerAPI(plugin_manager): - global _plugin_controller_api +def startAPIs(plugin_manager, model_controller): + global _rest_controllers global _http_server - if _plugin_controller_api is None: - #TODO: load API configuration from config file - hostname = "localhost" - port = 9977 - _plugin_controller_api = PluginControllerAPI(plugin_manager, - hostname, - port) - if _http_server is None: - _http_server = HTTPServer(WSGIContainer(_plugin_controller_api.getApp())) - _http_server.listen(port) - logging.getLogger("tornado.access").addHandler(logging.NullHandler()) - logging.getLogger("tornado.access").propagate = False - threading.Thread(target=startServer).start() - - -def stopPluginControllerAPI(): - stopServer() + _rest_controllers = [PluginControllerAPI(plugin_manager), ModelControllerAPI(model_controller)] + #TODO: load API configuration from config file + hostname = "localhost" + port = 9977 + app = Flask('APISController') + _http_server = HTTPServer(WSGIContainer(app)) + _http_server.listen(port) -class PluginControllerAPI(object): - def __init__(self, plugin_manager, hostname, port): - self.plugin_controller = PluginControllerForApi("PluginController", plugin_manager.getPlugins(), CommandManager()) - self.app = Flask(__name__.split('.')[0]) - self.addRoutes() - self.hostname = hostname - self.port = port - #self.api = Api(self.app) + routes = [r for c in _rest_controllers for r in c.getRoutes()] - def getApp(self): - return self.app + for route in routes: + app.add_url_rule(route.path, view_func=route.view_func, methods=route.methods) - #def run(self): - # self.app.run(host=self.hostname, port=self.port) + logging.getLogger("tornado.access").addHandler(logger.getLogger(app)) + logging.getLogger("tornado.access").propagate = False + threading.Thread(target=startServer).start() + +def stopAPIs(): + stopServer() - def addRoutes(self): - self.app.add_url_rule('/cmd/input', - view_func=self.postCmdInput, - methods=['POST']) - self.app.add_url_rule('/cmd/output', - view_func=self.postCmdOutput, - methods=['POST']) - self.app.add_url_rule('/cmd/active-plugins', - view_func=self.clearActivePlugins, - methods=['DELETE']) - def startAPI(self): - pass +class RESTApi(object): + """ Abstract class for REST Controllers + All REST Controllers should extend this class + in order to get published""" - def stopAPI(self): - pass + def getRoutes(self): + raise NotImplementedError('Abstract Class') def badRequest(self, message): error = 400 @@ -101,16 +83,112 @@ def noContent(self, message): return jsonify(code=code, message=message), code - def pluginAvailable(self, new_cmd, output_file): + def ok(self, message): code = 200 return jsonify(code=code, - cmd=new_cmd, - custom_output_file=output_file) + message=message) - def ok(self, message): +class ModelControllerAPI(RESTApi): + def __init__(self, model_controller): + self.controller = model_controller + + def getRoutes(self): + routes = [] + routes.append(Route(path='/model/edit/vulns', + view_func=self.postEditVulns, + methods=['POST'])) + + routes.append(Route(path='/model/del/vulns', + view_func=self.deleteVuln, + methods=['DELETE'])) + return routes + + + return host + + def deleteVuln(self): + json_data = request.get_json() + # validate mandatory: + if not 'vulnid' in json_data: + return self.badRequest("vulid is mandatory") + if not 'hostid' in json_data: + return self.badRequest("hostid is mandatory") + + vulnid = json_data['vulnid'] + hostid = json_data['hostid'] + + host = self.controller.getHost(hostid) + if not host: + return self.badRequest("no plugin available for cmd") + + visitor = VulnsLookupVisitor(vulnid) + host.accept(visitor) + + if not visitor.vulns: + return self.noContent('No vuls matched criteria') + + # forward to controller + for vuln, parents in zip(visitor.vulns, visitor.parents): + last_parent = parents[0] + self.controller.delVulnSYNC(last_parent, vuln.getID()) + + return self.ok("output successfully sent to plugin") + + + def postEditVulns(self): + json_data = request.get_json() + # validate mandatory: + if not 'vulnid' in json_data: + return self.badRequest("vulid is mandatory") + if not 'hostid' in json_data: + return self.badRequest("hostid is mandatory") + + vulnid = json_data['vulnid'] + hostid = json_data['hostid'] + + host = self.controller.getHost(hostid) + if not host: + return self.badRequest("no plugin available for cmd") + + visitor = VulnsLookupVisitor(vulnid) + host.accept(visitor) + + if not visitor.vulns: + return self.noContent('No vuls matched criteria') + + name = json_data.get('name', None) + desc = json_data.get('desc', None) + severity = json_data.get('severity', None) + refs = json_data.get('refs', None) + + # forward to controller + for vuln in visitor.vulns: + self.controller.editVulnSYNC(vuln, name, desc, severity, refs) + + return self.ok("output successfully sent to plugin") + +class PluginControllerAPI(RESTApi): + def __init__(self, plugin_manager): + self.plugin_controller = PluginControllerForApi("PluginController", plugin_manager.getPlugins(), CommandManager()) + + def getRoutes(self): + routes = [] + routes.append(Route(path='/cmd/input', + view_func=self.postCmdInput, + methods=['POST'])) + routes.append(Route(path='/cmd/output', + view_func=self.postCmdOutput, + methods=['POST'])) + routes.append(Route(path='/cmd/active-plugins', + view_func=self.clearActivePlugins, + methods=['DELETE'])) + return routes + + def pluginAvailable(self, new_cmd, output_file): code = 200 return jsonify(code=code, - message=message) + cmd=new_cmd, + custom_output_file=output_file) def postCmdInput(self): json_data = request.get_json() @@ -179,4 +257,12 @@ def send_output(self, cmd, output_file): headers=self.headers) if response.status_code != 200: return False - return True \ No newline at end of file + return True + + +class Route(object): + """ Route class, abstracts information about: + path, handler and methods """ + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) diff --git a/bin/getAllIpsInterfaces.py b/bin/getAllIpsInterfaces.py new file mode 100644 index 00000000000..15609cba23f --- /dev/null +++ b/bin/getAllIpsInterfaces.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +for host in api.__model_controller.getAllHosts(): + if len(host.getAllInterfaces()) > 1: + print host.name diff --git a/bin/getAllTelnet.py b/bin/getAllTelnet.py new file mode 100644 index 00000000000..7d71d785a6f --- /dev/null +++ b/bin/getAllTelnet.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +webs={} +for host in api.__model_controller.getAllHosts(): + + for i in host.getAllInterfaces(): + for s in i.getAllServices(): + for p in s.getPorts(): + if str(p) == '23': + webs[host.name]=1 + +for k,v in webs.iteritems(): + print "hydra -l '' -p 'telecom' -w 10 telnet://"+k+":23" + + + + + +# 200.61.47.65 +# 200.45.69.29 +# 200.61.47.217 +# 200.61.47.121 +# 200.45.69.17 +# 200.61.47.129 +# 200.61.47.113 +# 200.61.47.9 +# 190.221.164.65 +# 200.61.47.146 +# 186.153.146.227 +# 200.61.47.177 +# 200.61.47.17 +# 200.61.47.33 +# 200.45.69.30 +# 200.61.47.179 +# 200.61.47.233 +# 200.61.47.41 +# 200.61.47.221 +# 200.61.47.220 \ No newline at end of file diff --git a/bin/getAllVnc.py b/bin/getAllVnc.py new file mode 100644 index 00000000000..87fe955ea9c --- /dev/null +++ b/bin/getAllVnc.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +webs={} +for host in api.__model_controller.getAllHosts(): + + for i in host.getAllInterfaces(): + for s in i.getAllServices(): + for p in s.getPorts(): + if str(p) == '5900': + webs[host.name]=1 + +for k,v in webs.iteritems(): + print k +# print "hydra -l '' -p 'telecom' -w 10 telnet://"+k+":23" + + + + + +# 200.61.47.65 +# 200.45.69.29 +# 200.61.47.217 +# 200.61.47.121 +# 200.45.69.17 +# 200.61.47.129 +# 200.61.47.113 +# 200.61.47.9 +# 190.221.164.65 +# 200.61.47.146 +# 186.153.146.227 +# 200.61.47.177 +# 200.61.47.17 +# 200.61.47.33 +# 200.45.69.30 +# 200.61.47.179 +# 200.61.47.233 +# 200.61.47.41 +# 200.61.47.221 +# 200.61.47.220 diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 00000000000..be7f2e84ab6 --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1,7 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + diff --git a/controllers/change.py b/controllers/change.py new file mode 100644 index 00000000000..38d0d3769b7 --- /dev/null +++ b/controllers/change.py @@ -0,0 +1,23 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import model.guiapi + + +class ChangeController(object): + def __init__(self): + self.workspace = None + + def setWorkspace(self, workspace): + self.workspace = workspace + + def loadChanges(self, changes): + # first, we notify the changes + for change in changes: + model.guiapi.notification_center.changeFromInstance(change) + # then we reload the workspace + self.workspace.load() diff --git a/faraday b/faraday index d01936c4ed3..a174e1e3cf1 100755 --- a/faraday +++ b/faraday @@ -86,10 +86,11 @@ if [ ! -d $faraday_user/zsh ]; then mkdir -p "$faraday_user/zsh" fi if [ -e $HOME/.zshrc ]; then - cp -a $HOME/.zshrc $faraday_user/zsh/ + cp -a -L $HOME/.zshrc $faraday_user/zsh/ else touch $HOME/.zshrc $faraday_user/zsh/.zshrc fi +sed -i '1iZDOTDIR=$OLDZDOTDIR' $faraday_user/zsh/.zshrc echo "source $faraday_user/zsh/faraday.zsh" >> $faraday_user/zsh/.zshrc cp $faraday_base/zsh/faraday.zsh $faraday_user/zsh/ cp $faraday_base/zsh/plugin_controller_client.py $faraday_user/zsh/ diff --git a/faraday-terminal.zsh b/faraday-terminal.zsh index 98379418b75..5ebe332a741 100755 --- a/faraday-terminal.zsh +++ b/faraday-terminal.zsh @@ -7,5 +7,7 @@ ### #ZDOTDIR="~/.faraday/zsh/" /bin/zsh -ZDOTDIR="$HOME/.faraday/zsh/" /bin/zsh +FARADAYZDOTDIR="$HOME/.faraday/zsh/" +OLDZDOTDIR=$ZDOTDIR +ZDOTDIR=$FARADAYZDOTDIR /bin/zsh #source ~/.faraday/zsh/.zshrc diff --git a/gui/customevents.py b/gui/customevents.py index 2e66f06a6b0..2b7dd0efe5c 100644 --- a/gui/customevents.py +++ b/gui/customevents.py @@ -28,6 +28,7 @@ ADDHOST = 4100 DELHOST = 4101 EDITHOST = 4102 +CHANGEFROMINSTANCE = 5100 UPDATEMODEL_ID = 54321 @@ -128,3 +129,9 @@ class DeleteHostCustomEvent(CustomEvent): def __init__(self, host_id): CustomEvent.__init__(self, DELHOST) self.host_id = host_id + + +class ChangeFromInstanceCustomEvent(CustomEvent): + def __init__(self, change): + CustomEvent.__init__(self, CHANGEFROMINSTANCE) + self.change = change diff --git a/gui/nogui/application.py b/gui/nogui/application.py index 2f101ed948c..cb0c6859d12 100644 --- a/gui/nogui/application.py +++ b/gui/nogui/application.py @@ -9,6 +9,8 @@ import time from gui.gui_app import FaradayUi +from gui.nogui.eventwatcher import EventWatcher +import model.guiapi class GuiApp(FaradayUi): @@ -18,6 +20,9 @@ def __init__(self, model_controller, plugin_manager, workspace_manager): plugin_manager, workspace_manager) self._stop = False + model.guiapi.setMainApp(self) + self.event_watcher = EventWatcher() + model.guiapi.notification_center.registerWidget(self.event_watcher) def run(self, args): @@ -28,3 +33,6 @@ def run(self, args): def quit(self): self._stop = True + + def postEvent(self, receiver, event): + receiver.update(event) diff --git a/gui/nogui/eventwatcher.py b/gui/nogui/eventwatcher.py new file mode 100644 index 00000000000..add0a55b28c --- /dev/null +++ b/gui/nogui/eventwatcher.py @@ -0,0 +1,19 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +from utils.logs import getLogger +from gui.customevents import CHANGEFROMINSTANCE + + +class EventWatcher(object): + def __init__(self): + self.logger = getLogger(self) + + def update(self, event): + if event.type() == CHANGEFROMINSTANCE: + getLogger(self).debug( + "[Update Received] " + event.change.getMessage()) diff --git a/gui/notifier.py b/gui/notifier.py index 158016b1555..20f49b336c3 100644 --- a/gui/notifier.py +++ b/gui/notifier.py @@ -68,3 +68,6 @@ def conflictUpdate(self, vulns_changed): def conflictResolution(self, conflicts): self._notifyWidgets(events.ResolveConflictsCustomEvent(conflicts)) + + def changeFromInstance(self, change): + self._notifyWidgets(events.ChangeFromInstanceCustomEvent(change)) diff --git a/gui/qt3/application.py b/gui/qt3/application.py index 22e69c72bf4..90fc0538f1a 100644 --- a/gui/qt3/application.py +++ b/gui/qt3/application.py @@ -49,6 +49,7 @@ def __init__(self, model_controller, plugin_manager, workspace_manager): notifier = model.log.getNotifier() notifier.widget = self._main_window + model.guiapi.notification_center.registerWidget(self._main_window) self._splash_screen = qt.QSplashScreen( qt.QPixmap(os.path.join(CONF.getImagePath(), "splash2.png")), @@ -164,10 +165,14 @@ def createWorkspace(self, name, description="", w_type=""): else: model.api.log("Creating workspace '%s'" % name) model.api.devlog("Looking for the delegation class") - workingClass = globals()[w_type] + manager = self.getWorkspaceManager() - w = self.getWorkspaceManager().\ - createWorkspace(name, description, workspaceClass=workingClass) + workingClass = None + if w_type and w_type in globals(): + # If set as argument, otherwise let creation delegate behaviour + workingClass = globals()[w_type] + + w = manager.createWorkspace(name, description, workspaceClass=workingClass) self.getWorkspaceManager().setActiveWorkspace(w) self.getModelController().setWorkspace(w) diff --git a/gui/qt3/customevents.py b/gui/qt3/customevents.py index 8178160d2f8..b8ed98abb0e 100644 --- a/gui/qt3/customevents.py +++ b/gui/qt3/customevents.py @@ -16,7 +16,7 @@ CLEARHOSTS_ID, DIFFHOSTS_ID, SYNCFAILED_ID, CONFLICTS_ID, WORKSPACE_CHANGED, CONFLICT_UPDATE, RESOLVECONFLICTS_ID, UPDATEMODEL_ID, ADDHOST, - EDITHOST, DELHOST) + EDITHOST, DELHOST, CHANGEFROMINSTANCE) class LogCustomEvent(qt.QCustomEvent): @@ -106,6 +106,12 @@ def __init__(self, e): self.host_id = e.host_id +class ChangeFromInstanceCustomEvent(qt.QCustomEvent): + def __init__(self, e): + qt.QCustomEvent.__init__(self, e.type()) + self.change = e.change + + class QtCustomEvent(qt.QCustomEvent): events = { LOGEVENT_ID: LogCustomEvent, @@ -123,7 +129,8 @@ class QtCustomEvent(qt.QCustomEvent): UPDATEMODEL_ID: ModelObjectUpdateEvent, ADDHOST: AddHostCustomEvent, DELHOST: DeleteHostCustomEvent, - EDITHOST: EditHostCustomEvent + EDITHOST: EditHostCustomEvent, + CHANGEFROMINSTANCE: ChangeFromInstanceCustomEvent } @staticmethod diff --git a/gui/qt3/hostsbrowser.py b/gui/qt3/hostsbrowser.py index bc910e87838..bce599dfc8b 100644 --- a/gui/qt3/hostsbrowser.py +++ b/gui/qt3/hostsbrowser.py @@ -5,6 +5,7 @@ ''' import qt +from threading import Lock from gui.qt3.modelobjectitems import * import model.api as api import model.guiapi as guiapi @@ -17,7 +18,6 @@ from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() - from whoosh.index import create_in from whoosh.fields import * @@ -324,13 +324,18 @@ def customEvent(self, event): class HostsBrowser(qt.QVBox): """Tree view to display Hosts""" - def __init__(self, parent, caption): + def __init__(self, parent, model_controller, caption): qt.QVBox.__init__(self, parent) + self._model_controller = model_controller + self.modelUpdateTimer = qt.QTimer(self) self.__pendingModelObjectRedraws = [] + self.reindex_flag_lock = Lock() + self.reindex_flag = False + self.connect( self.modelUpdateTimer, qt.SIGNAL("timeout()"), self._modelObjectViewUpdater) self.modelUpdateTimer.start( 1000 , False) @@ -457,10 +462,24 @@ def redrawTree(self, hosts): for ci in self._category_items.values(): ci.setOpen(True) - self.createIndex(hosts) + self.createIndex() self.filterTree(self._filter) + def setReindex(self): + self.reindex_flag_lock.acquire() + if not self.reindex_flag: + self.reindex_flag = True + self.reindex_flag_lock.release() + + def reindex(self): + self.reindex_flag_lock.acquire() + if self.reindex_flag: + self.createIndex() + self.reindex_flag = False + self.reindex_flag_lock.release() + def filterTree(self, mfilter=""): + self.reindex() hosts=[] viewall=False self._filter=mfilter @@ -501,7 +520,8 @@ def _filterHost(self,hosts): return hostv - def createIndex(self,hosts): + def createIndex(self): + hosts = self._model_controller.getAllHosts() schema = Schema(ip=TEXT(stored=True), hostname=TEXT(stored=True), mac=TEXT(stored=True), @@ -521,46 +541,46 @@ def createIndex(self,hosts): os.mkdir(indexdir) self.ix = create_in(indexdir, schema) - writer = self.ix.writer() - revulns={} for host in hosts: + self.indexHost(host) - - writer.add_document(ip=unicode(host.name), os=unicode(host.getOS()),owned=host.isOwned(), - vuln=True if host.vulnsCount() >0 else False, - note=True if len(host.getNotes()) >0 else False) - - - - for i in host.getAllInterfaces(): - for h in i._hostnames: - writer.add_document(ip=unicode(host.name), hostname=unicode(h),mac=unicode(i.getMAC())) - - - - for v in host.getVulns(): - - - - - - writer.add_document(ip=unicode(host.name), vulnn=unicode(v.name)) - - - for i in host.getAllInterfaces(): - for s in i.getAllServices(): - - for v in s.getVulns(): - writer.add_document(ip=unicode(host.name), vulnn=unicode(v.name),srvname=unicode(s.getName())) - - for p in s.getPorts(): - writer.add_document(ip=unicode(host.name), port=unicode(str(p)),owned=s.isOwned(), - vuln=True if s.vulnsCount() >0 else False, - note=True if len(s.getNotes()) >0 else False, - cred=True if s.credsCount() > 0 else False, - srvname=unicode(s.getName()), - srvstatus=unicode(s.getStatus())) + def indexHost(self, host): + writer = self.ix.writer() + writer.add_document(ip=unicode(host.name), os=unicode(host.getOS()), + owned=host.isOwned(), + vuln=True if host.vulnsCount() > 0 else False, + note=True if len(host.getNotes()) > 0 else False) + + for i in host.getAllInterfaces(): + for h in i._hostnames: + writer.add_document(ip=unicode(host.name), + hostname=unicode(h), + mac=unicode(i.getMAC())) + + for v in host.getVulns(): + writer.add_document(ip=unicode(host.name), vulnn=unicode(v.name)) + + for i in host.getAllInterfaces(): + for s in i.getAllServices(): + for v in s.getVulns(): + writer.add_document(ip=unicode(host.name), + vulnn=unicode(v.name), + srvname=unicode(s.getName())) + for p in s.getPorts(): + writer.add_document( + ip=unicode(host.name), + port=unicode(str(p)), + owned=s.isOwned(), + vuln=True if s.vulnsCount() > 0 else False, + note=True if len(s.getNotes()) > 0 else False, + cred=True if s.credsCount() > 0 else False, + srvname=unicode(s.getName()), + srvstatus=unicode(s.getStatus())) + writer.commit() + def removeIndexHost(self, host): + writer = self.ix.writer() + writer.delete_by_term('ip', host.name) writer.commit() def selectWord(self, word): @@ -661,6 +681,8 @@ def _addHost(self, host): category = host.getCurrentCategory() self._addCategory(category) self._addHostToCategory(host, category) + #self.removeIndexHost(host) + #self.indexHost(host) def _removeHost(self, host_id): item = self._host_items.get(host_id, None) @@ -1087,12 +1109,15 @@ def customEvent(self, event): elif event.type() == ADDHOST: self._addHost(event.host) + self.setReindex() elif event.type() == DELHOST: self._removeHost(event.host_id) + self.setReindex() elif event.type() == EDITHOST: self._editHost(event.host) + self.setReindex() def _setupContextPopups(self): diff --git a/gui/qt3/mainwindow.py b/gui/qt3/mainwindow.py index 9c45d941dd3..1dda9559a69 100644 --- a/gui/qt3/mainwindow.py +++ b/gui/qt3/mainwindow.py @@ -16,6 +16,7 @@ from gui.qt3.configdialog import ConfigDialog from gui.qt3.toolbars import * from gui.qt3.customevents import * +from gui.qt3.notification import NotificationsDialog from model.guiapi import notification_center as notifier from managers.all import PersistenceManagerFactory, CouchdbManager @@ -31,10 +32,6 @@ from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() - -test_count = 0 - - class MainWindow(qt.QMainWindow): @@ -43,11 +40,9 @@ def __init__(self, title, main_app, model_controller, plugin_manager): self.setWindowState(qt.Qt.WindowMaximized) self.setCaption(title) + self.setIcon(qt.QPixmap(os.path.join(CONF.getIconsPath(), + 'faraday_icon.png'))) - - self.setIcon(qt.QPixmap(os.path.join(CONF.getIconsPath(),"faraday_icon.png"))) - - self._main_app = main_app self._model_controller = model_controller @@ -55,64 +50,53 @@ def __init__(self, title, main_app, model_controller, plugin_manager): self.setCentralWidget(self._mainArea) self._vb_splitter = qt.QSplitter(self._mainArea) self._vb_splitter.setOrientation(qt.QSplitter.Vertical) - self._hb_splitter = qt.QSplitter(self._vb_splitter) self._hb_splitter.setOrientation(qt.QSplitter.Horizontal) - - - self.statusBar().setSizeGripEnabled(False) self._shell_widgets = [] - - - - + self._notifications = [] self._tab_manager = TabManager(self._hb_splitter) - self._perspective_manager = PerspectiveManager(self._hb_splitter, self._main_app) + self._perspective_manager = PerspectiveManager(self._hb_splitter, + self._main_app) - - self._hosts_treeview = HostsBrowser(self._perspective_manager,"Hosts") + self._hosts_treeview = HostsBrowser(self._perspective_manager, + self._model_controller, + 'Hosts') notifier.registerWidget(self._hosts_treeview) - self._perspective_manager.registerPerspective(self._hosts_treeview, default=True) - - - wtw = WorkspaceTreeWindow(self._perspective_manager, "Workspaces", + self._perspective_manager.registerPerspective(self._hosts_treeview, + default=True) + + wtw = WorkspaceTreeWindow(self._perspective_manager, 'Workspaces', self._main_app.getWorkspaceManager()) self._perspective_manager.registerPerspective(wtw) self._workspaces_treeview = wtw - self._log_console = LogConsole(self._vb_splitter,"Console") + self._log_console = LogConsole(self._vb_splitter, 'Console') - self._actions = dict() self._setupActions() - self._menues = {} self._setupMenues() - - self.main_toolbar = qt.QToolBar(self,'main toolbar') + self.main_toolbar = qt.QToolBar(self, 'main toolbar') self._setupMainToolbar() - self.location_toolbar = LocationToolbar(self,'location toolbar') + self.location_toolbar = LocationToolbar(self, 'location toolbar') self.location_toolbar.setOffset(1500) - - self._status_bar_widgets = dict() self._setupStatusBar() self._is_shell_maximized = False - - self.shell_font=qt.QFont() + self.shell_font = qt.QFont() self.shell_font.setRawName(CONF.getFont()) self.setSizeFont() - + def setSizeFont(self): if re.search("fixed",str(self.shell_font.family()),re.IGNORECASE) is None: self.shell_font=qt.QFont() @@ -237,7 +221,6 @@ def _setupActions(self): a = self._actions["debug"] = qt.QAction( qt.QIconSet(qt.QPixmap(os.path.join(CONF.getIconsPath(),"debug.png"))), "Debug", 0, self, "Debug" ) self.connect(a, qt.SIGNAL('activated()'), self.doDebug) - def _setupStatusBar(self): label_order = ["username", "userLevel", "space", "status"] for lname in label_order: @@ -245,7 +228,15 @@ def _setupStatusBar(self): l.setFrameStyle(qt.QFrame.MenuBarPanel | qt.QFrame.Plain) self._status_bar_widgets[lname] = l self.statusBar().addWidget(l, 0, True) - + + notification_button = qt.QPushButton("0", self) + notification_button.setSizePolicy(qt.QSizePolicy( + qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum)) + self.connect(notification_button, qt.SIGNAL('clicked()'), + self.showNotifications) + self._status_bar_widgets["notifications"] = notification_button + self.statusBar().addWidget(notification_button, 0, True) + w = qt.QWidget(self) self.statusBar().addWidget(w, 1, True) @@ -571,8 +562,9 @@ def customEvent(self, event): elif event.type() == SHOWPOPUP_ID: self.showPopup(event.text, event.level) elif event.type() == CONFLICTS_ID: - self.showConflictsDialog(event.local) + elif event.type() == CHANGEFROMINSTANCE: + self.newNotification(event.change) def update(self, event): if event.type() == EXCEPTION_ID: @@ -584,6 +576,20 @@ def update(self, event): elif event.type() == CONFLICTS_ID: self.showConflictsDialog(event.local) + def newNotification(self, change): + button = self._status_bar_widgets["notifications"] + button.setText(str(int(button.text()) + 1)) + button.setPaletteBackgroundColor(qt.QColor(180, 0, 0)) + self._notifications.append(change) + + def showNotifications(self): + button = self._status_bar_widgets["notifications"] + button.setText("0") + button.setPaletteBackgroundColor(self.paletteBackgroundColor()) + dialog = NotificationsDialog(self, self._notifications) + dialog.exec_loop() + self._notifications[:] = [] + def toggleLogConsole(self): if self._log_console.isVisible(): self._log_console.hide() diff --git a/gui/qt3/modelobjectitems.py b/gui/qt3/modelobjectitems.py index f37269958f7..7e1dbb0bc87 100644 --- a/gui/qt3/modelobjectitems.py +++ b/gui/qt3/modelobjectitems.py @@ -237,9 +237,10 @@ def __init__(self, qtparent, name = "", model_object=None): class WorkspaceListViewItem(ModelObjectListViewItem): type = "Workspace" def __init__(self, qtparent, model_object=None): - ModelObjectListViewItem.__init__(self, qtparent, model_object.name, model_object) + ModelObjectListViewItem.__init__(self, qtparent, model_object.name) self.nconflicts = 0 self.setOpen(True) + self.workspace_name = "%s" % self.name def _checkVulns(self): pass @@ -257,9 +258,10 @@ def _setIcon(self): def updateName(self, nconflicts): self.nconflicts += nconflicts if self.nconflicts: - self.name = "%s (%s)" % (self.getModelObject().name, self.nconflicts) + text = "%s (%s)" % (self.workspace_name, self.nconflicts) else: - self.name = "%s" % (self.getModelObject().name) + text = "%s" % (self.workspace_name) + self.setText(0, text) class CategoryListViewItem(ModelObjectListViewItem): @@ -485,3 +487,4 @@ def clearVulns(self): self._childs = [] + diff --git a/gui/qt3/notification.py b/gui/qt3/notification.py new file mode 100644 index 00000000000..561db4e9c66 --- /dev/null +++ b/gui/qt3/notification.py @@ -0,0 +1,28 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import qt + + +class NotificationsDialog(qt.QDialog): + def __init__(self, parent, notifications, modal=True): + qt.QDialog.__init__(self, parent, "Notifications", modal) + self.layout = qt.QVBoxLayout(self, 0, 1, "layout") + self.layout.addWidget(NotificationsList(self, notifications)) + + def sizeHint(self): + return qt.QSize(300, 500) + + +class NotificationsList(qt.QListView): + def __init__(self, parent, notifications): + qt.QListView.__init__(self, parent) + self.addColumn("New notifications") + self.setColumnWidthMode(0, qt.QListView.Maximum) + for n in notifications: + notif_item = qt.QListViewItem(self) + notif_item.setText(0, qt.QString(n.getMessage())) diff --git a/gui/qt3/workspacebrowser.py b/gui/qt3/workspacebrowser.py index 23409e3cad7..b1114ac203e 100644 --- a/gui/qt3/workspacebrowser.py +++ b/gui/qt3/workspacebrowser.py @@ -21,22 +21,22 @@ class WorkspaceListViewItem(qt.QListViewItem): """Item for displaying in the WorkspaceTreeWindow.""" - def __init__(self, qtparent, name = "", workspace_object=None): + def __init__(self, qtparent, name, is_active, workspace_type): qt.QListViewItem.__init__(self, qtparent) self.setRenameEnabled(0, False) self.index = 0 - self.object = workspace_object - self.name = self.object.name if self.object is not None else name + self.is_active = is_active + self.workspace_type = workspace_type + self.objname = name + self.name = "%s (%s)" % (self.objname , self.workspace_type.replace("WorkspaceOn","")) self.setDragEnabled(False) self.setDropEnabled(False) - self._setIcon() - - self.name = "%s (%s)" % (self.name , self.object.__class__.__name__.replace("WorkspaceOn","")) + self._setIcon() def _setIcon(self): - active = self.object.isActive() if self.object is not None else False + active = self.is_active icon_name = "FolderBlue-20.png" if active else "FolderSteel-20.png" icon_path = os.path.join(CONF.getIconsPath(), icon_name) pm = qt.QPixmap(icon_path) @@ -44,10 +44,7 @@ def _setIcon(self): def setText(self, col, text): - """Update name of widget if rename is called.""" - - - + """Update name of widget if rename is called.""" if col == 0: try: self.widget.rename( unicode(text) ) @@ -211,8 +208,10 @@ def loadAllWorkspaces(self): Clear the tree and loads all workspaces defined in the workspace manager """ self.clearTree() - for w in self.manager.getWorkspaces(): - witem = WorkspaceListViewItem(self.listview, w.name, w) + for name in self.manager.getWorkspacesNames(): + witem = WorkspaceListViewItem(self.listview, name=name, + is_active=self.manager.isActive(name), + workspace_type=self.manager.getWorkspaceType(name)) self._workspace_items.append(witem) def setDefaultWorkspace(self): @@ -220,19 +219,12 @@ def setDefaultWorkspace(self): if first_child: self._openWorkspace(first_child) - def _itemDoubleClick(self, item, pos, val): - - - - if not item.object.isActive(): + def _itemDoubleClick(self, item, pos, val): + if not self.manager.isActive(item.name): self._openWorkspace(item) def _showContextMenu(self, item, pos, val): - """Pop up a context menu when an item is right-clicked on the list view.""" - - - - + """Pop up a context menu when an item is right-clicked on the list view.""" popup = qt.QPopupMenu(self) @@ -243,7 +235,7 @@ def _showContextMenu(self, item, pos, val): popup.insertItem('Create Workspace', 100) else: if len(selected_items) == 1: - if item.object.isActive(): + if item.is_active: popup.insertItem('Save', self._saveWorkspace) popup.insertItem('Synchronize', self._syncWorkspace) popup.insertItem('Close', 300) @@ -275,19 +267,16 @@ def _getSelectedItems(self): def _deleteWorkspaces(self, items): for item in items: - self._getMainApp().removeWorkspace(item.object.name) + self._getMainApp().removeWorkspace(item.objname) self.loadAllWorkspaces() def _deleteWorkspace(self, item): - self._getMainApp().removeWorkspace(item.object.name) + self._getMainApp().removeWorkspace(item.objname) self.loadAllWorkspaces() - def _openWorkspace(self, item): - - - api.devlog("Opening workspace %s selected on the Workspace Perspective" % item.name) - self._getMainApp().openWorkspace(item.object.name) - + def _openWorkspace(self, item): + api.devlog("Opening workspace %s selected on the Workspace Perspective" % item.objname) + self._getMainApp().openWorkspace(item.objname) self.loadAllWorkspaces() def _saveWorkspace(self): @@ -300,6 +289,5 @@ def _getMainApp(self): return self.parent().parent().getMainApp() def _showWorkspaceProperties(self, item): - if item.object is not None: - d = WorkspacePropertiesDialog(self, "Workspace Properties", workspace=item.object) - d.exec_loop() + d = WorkspacePropertiesDialog(self, "Workspace Properties", workspace=item.objname) + d.exec_loop() diff --git a/managers/all.py b/managers/all.py index ca5c4336741..f638dac01f2 100644 --- a/managers/all.py +++ b/managers/all.py @@ -25,6 +25,7 @@ import shutil import json from model.common import ModelObject +from persistence.change import change_factory CONF = getInstanceConfiguration() @@ -71,11 +72,23 @@ def saveDocument(self, aWorkspaceName, aDocument): class FSManager(PersistenceManager): """ This is a file system manager for the workspace, it will load from the provided FS""" - def __init__(self, path): + def __init__(self, path=CONF.getPersistencePath()): self._path = path if not os.path.exists(self._path): os.mkdir(self._path) + def getWorkspacesNames(self): + workspaces = [] + for name in os.listdir(CONF.getPersistencePath()): + if os.path.isdir(os.path.join(CONF.getPersistencePath(), name)): + workspaces.append(name) + return workspaces + + + def addWorkspace(self, wname): + wpath = os.path.expanduser("~/.faraday/persistence/%s" % wname) + os.mkdir(wpath) + def removeWorkspace(self, name): shutil.rmtree(os.path.join(self._path)) @@ -127,13 +140,14 @@ def get_types(subclasses): return [] self._model_object_types = get_types([ModelObject]) try: - self.testCouchUrl(uri) - url=urlparse(uri) - getLogger(self).debug("Setting user,pass %s %s" % (url.username, url.password)) - self.__serv = Server(uri = uri) - #print dir(self.__serv) - self.__serv.resource_class.credentials = (url.username, url.password) - self._available = True + if uri is not None: + self.testCouchUrl(uri) + url = urlparse(uri) + getLogger(self).debug("Setting user,pass %s %s" % (url.username, url.password)) + self.__serv = Server(uri=uri) + #print dir(self.__serv) + self.__serv.resource_class.credentials = (url.username, url.password) + self._available = True except: getLogger(self).warn("No route to couchdb server on: %s" % uri) getLogger(self).debug(traceback.format_exc()) @@ -157,35 +171,33 @@ def reconnect(self): return ret_val - - @staticmethod def testCouch(uri): - host, port = None, None - try: - import socket - url=urlparse(uri) - proto = url.scheme - host=url.hostname - port=url.port - - port = port if port else socket.getservbyname(proto) - s = socket.socket() - s.settimeout(1) - s.connect((host, int(port))) - except: - return False - getLogger(CouchdbManager).info("Connecting Couch to: %s:%s" % (host, port)) - return True - - + if uri is not None: + host, port = None, None + try: + import socket + url = urlparse(uri) + proto = url.scheme + host = url.hostname + port = url.port + + port = port if port else socket.getservbyname(proto) + s = socket.socket() + s.settimeout(1) + s.connect((host, int(port))) + except: + return False + getLogger(CouchdbManager).info("Connecting Couch to: %s:%s" % (host, port)) + return True def testCouchUrl(self, uri): - url=urlparse(uri) - proto = url.scheme - host=url.hostname - port=url.port - self.test(host, int(port)) + if uri is not None: + url = urlparse(uri) + proto = url.scheme + host = url.hostname + port = url.port + self.test(host, int(port)) def test(self, address, port): import socket @@ -242,6 +254,10 @@ def getDocument(self, aWorkspaceName, documentId): getLogger(self).debug("Getting document for workspace [%s]" % aWorkspaceName) return self._getDb(aWorkspaceName).get(documentId) + @trap_timeout + def getDeletedDocument(self, aWorkspaceName, documentId, documentRev): + return self._getDb(aWorkspaceName).get(documentId, rev=documentRev) + @trap_timeout def checkDocument(self, aWorkspaceName, documentName): return self._getDb(aWorkspaceName).doc_exist(documentName) @@ -292,14 +308,16 @@ def waitForDBChange(self, db_name, since = 0, timeout = 15000): changes = [] last_seq = max(self.getLastChangeSeq(db_name), since) db = self._getDb(db_name) - with ChangesStream(db, feed="longpoll", since = last_seq, timeout = timeout) as stream: + with ChangesStream(db, feed="longpoll", since=last_seq, timeout=timeout) as stream: for change in stream: if change['seq'] > self.getLastChangeSeq(db_name): self.setLastChangeSeq(db_name, change['seq']) if not change['id'].startswith('_design'): - changes.append(change) - #last_seq = reduce(lambda x,y: max(y['seq'], x) , changes, self.getLastChangeSeq(db_name)) - #self.setLastChangeSeq(db_name, last_seq) + #fake doc type for deleted objects + doc = {'type': 'unknown', '_deleted': 'False', '_rev':[0]} + if not change.get('deleted'): + doc = self.getDocument(db_name, change['id']) + changes.append(change_factory.create(doc)) if len(changes): getLogger(self).debug("Changes from another instance") return changes diff --git a/model/application.py b/model/application.py index e0828437ab2..79f10feab19 100644 --- a/model/application.py +++ b/model/application.py @@ -19,7 +19,7 @@ import model.controller import model.api import model.guiapi -import plugins.api +import apis.rest.api as restapi import model.log from utils.logs import getLogger import traceback @@ -98,7 +98,7 @@ def start(self): model.api.devlog("Starting model controller daemon...") self._model_controller.start() model.api.startAPIServer() - plugins.api.startPluginControllerAPI(self._plugin_manager) + restapi.startAPIs(self._plugin_manager, self._model_controller) #self._writeSplashMessage("Setting up main GUI...") @@ -173,7 +173,7 @@ def __exit(self, exit_code=0): model.api.devlog("Waiting for controller threads to end...") self._model_controller.join() model.api.stopAPIServer() - plugins.api.stopServer() + restapi.stopServer() return exit_code diff --git a/model/common.py b/model/common.py index e714fd174ae..cf47f360d59 100644 --- a/model/common.py +++ b/model/common.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python ''' Faraday Penetration Test IDE - Community Version Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) @@ -301,11 +300,8 @@ def __init__(self): self.updates = [] - #def __getattr__( self, name): - # l_strace = traceback.extract_stack(limit = 5) - # print l_strace - # print ("BBBBBBBBBBBBBBBB ModelObject attribute to refactor: %s" % name) - + def accept(self, visitor): + visitor.visit(self) def defaultValues(self): return [-1, 0, '', 'unknown', None, [], {}] @@ -678,6 +674,8 @@ def _fromDict(self, dict): if dict.get("vulnerability"): for vuln in dict["vulnerability"].values(): v = ModelObjectVuln("") + if vuln.get("type") == ModelObjectVulnWeb.class_signature: + v = ModelObjectVulnWeb("") v._parent = self v._fromDict(vuln) self.addVuln(v, setparent=False) diff --git a/model/hosts.py b/model/hosts.py index 4a0d9589b6a..7d8bf1a2618 100644 --- a/model/hosts.py +++ b/model/hosts.py @@ -20,25 +20,6 @@ print "[-] ex: sudo pip install IPy" CONF = getInstanceConfiguration() - - - - - - - - - - - - - - - - - - - class Host(ModelObject): """ Represents a host found in the network. @@ -65,9 +46,10 @@ def __init(self, name, os = "Unknown", default_gateway=None): self._name = None if name is not None: self.setName(name) - self._name = name - self._operating_system = os if os else "Unknown" - self._default_gateway = api.getLocalDefaultGateway() if default_gateway is None else default_gateway + self._name = name + self._operating_system = os if os else "Unknown" + self._default_gateway = api.getLocalDefaultGateway() \ + if default_gateway is None else default_gateway self.getMetadataHistory().pushMetadataForId(self.getID(), self.getMetadata()) def _updatePublicAttributes(self): @@ -75,6 +57,12 @@ def _updatePublicAttributes(self): self.publicattrs['Operating System'] = 'getOS' self.publicattrsrefs['Operating System'] = '_operating_system' + def accept(self, visitor): + """ Accept method for visitor in the host leaf""" + for ints in self.getAllInterfaces(): + ints.accept(visitor) + visitor.visit(self) + def getCategories(self): return self.categories @@ -440,6 +428,11 @@ def defaultValues(self): }, {'prefix': '00', 'gateway': '0000:0000:0000:0000:0000:0000:0000:0000', 'DNS': [], 'address': '0000:0000:0000:0000:0000:0000:0000:0000'}]) return defVals + def accept(self, visitor): + for servs in self.getAllServices(): + servs.accept(visitor) + visitor.visit(self) + def tieBreakable(self, property_key): if property_key in ["_hostnames"]: diff --git a/model/report.py b/model/report.py index 4918180a8f1..bd44aab5666 100644 --- a/model/report.py +++ b/model/report.py @@ -14,7 +14,7 @@ import re import requests from persistence.utils import ET -from plugins.api import PluginControllerAPIClient +from apis.rest.api import PluginControllerAPIClient from config.configuration import getInstanceConfiguration CONF = getInstanceConfiguration() diff --git a/model/visitor.py b/model/visitor.py new file mode 100644 index 00000000000..d71ab916e8d --- /dev/null +++ b/model/visitor.py @@ -0,0 +1,45 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +""" +Contains base classes used to represent the application model +and some other common objects and functions used in the model +""" +import sys +import os +import traceback +import threading +import SimpleXMLRPCServer +import xmlrpclib +from utils.decorators import updateLocalMetadata, save, delete +import json +import model +from conflict import ConflictUpdate +from model.diff import ModelObjectDiff + + +class ModelObjectVisitor(object): + def visit(self, modelObjectInstance): + raise NotImplemented('Abstract method') + + +class VulnsLookupVisitor(ModelObjectVisitor): + def __init__(self, vulnId): + self.vulnId = vulnId + self.parents = [] + self.vulns = [] + + def visit(self, modelObject): + vuln = modelObject.getVuln(self.vulnId) + parents = [] + if vuln: + self.vulns.append(vuln) + parent = vuln.getParent() + while parent: + parents.append(parent) + parent = parent.getParent() + + self.parents.append(parents) diff --git a/model/workspace.py b/model/workspace.py index 1a2ab18044b..e9c6dc6a821 100644 --- a/model/workspace.py +++ b/model/workspace.py @@ -38,12 +38,7 @@ class Workspace(object): history for all users working on the same workspace. It has a list with all existing workspaces just in case user wants to open a new one. - """ - - - - - + """ def __init__(self, name, manager, shared=CONF.getAutoShareWorkspace()): self.name = name @@ -219,6 +214,7 @@ def load(self): for filename in files: newHost = self.__loadHostFromFile(filename) modelobjectcontainer[newHost.getID()] = newHost + notifier.workspaceLoad(self.getAllHosts()) def __loadHostFromFile(self, filename): if os.path.basename(filename) in self._persistence_excluded_filenames: @@ -332,7 +328,13 @@ def find_leaf(path, sub_graph = hosts): hosts['subs'] = subs continue - leaf = find_leaf(id_path) + leaf = {} + try: + leaf = find_leaf(id_path) + except Exception as e: + model.api.devlog('Object parent not found, skipping: %s' % '.'.join(id_path)) + continue + subs = leaf.get('subs', {}) subs[d['obj_id']] = d leaf['subs'] = subs @@ -374,8 +376,10 @@ def __init__(self, model_controller, plugin_controller): self.report_manager = ReportManager(10, plugin_controller) self.couchdbmanager = PersistenceManagerFactory().getInstance() + self.fsmanager = FSManager() self._workspaces = {} + self._workspaces_types = {} self._model_controller = model_controller self._excluded_directories = [".svn"] self.workspace_persister = WorkspacePersister() @@ -383,6 +387,9 @@ def __init__(self, model_controller, plugin_controller): def couchAvailable(self, isit): self._couchAvailable = isit + def _notifyWorkspaceNoConnection(self): + notifier.showPopup("Couchdb Connection lost. Defaulting to memory. Fix network and try again in 5 minutes.") + def reconnect(self): if not self.reconnectCouchManager(): self._notifyWorkspaceNoConnection() @@ -443,23 +450,33 @@ def createVisualizations(self): def _notifyNoVisualizationAvailable(self): notifier.showPopup("No visualizations available, please install and configure CouchDB") - def createWorkspace(self, name, description="", workspaceClass = WorkspaceOnFS, shared=CONF.getAutoShareWorkspace(), + def createWorkspace(self, name, description="", workspaceClass = None, shared=CONF.getAutoShareWorkspace(), customer="", sdate=None, fdate=None): - if name not in self._workspaces: - w = workspaceClass(name, self, shared) - w.description = description - w.customer = customer - if sdate is not None: - w.start_date = sdate - if fdate is not None: - w.finish_date = fdate - self.addWorkspace(w) - else: - w = self._workspaces[name] + + model.api.devlog("Creating Workspace") + if self.getWorkspaceType(name) in globals(): + workspaceClass = globals()[self.getWorkspaceType(name)] + elif not workspaceClass: + # Defaulting =( + model.api.devlog("Defaulting to WorkspaceOnFS") + workspaceClass = WorkspaceOnFS + + w = workspaceClass(name, self, shared) + # Register the created workspace type: + self._workspaces_types[name] = workspaceClass.__name__ + w.description = description + w.customer = customer + if sdate is not None: + w.start_date = sdate + if fdate is not None: + w.finish_date = fdate + self.addWorkspace(w) return w def removeWorkspace(self, name): - dm = self.getWorkspace(name).getDataManager() + work = self.getWorkspace(name) + if not work: return + dm = work.getDataManager() dm.removeWorkspace(name) datapath = CONF.getDataPath() @@ -473,14 +490,26 @@ def removeWorkspace(self, name): self.setActiveWorkspace(self.getWorkspace(self._workspaces.keys()[0])) def getWorkspace(self, name): - return self._workspaces.get(name) - + ''' May return None ''' + if not self._workspaces.get(name): + # Retrieve the workspace + self.loadWorkspace(name) + return self._workspaces.get(name) + + def loadWorkspace(self, name): + workspaceClass = None + workspace = None + if name in self.fsmanager.getWorkspacesNames(): + workspace = self.createWorkspace(name, workspaceClass = WorkspaceOnFS) + elif name in self.couchdbmanager.getWorkspacesNames(): + workspace = self.createWorkspace(name, workspaceClass = WorkspaceOnCouch) + + return workspace + def openWorkspace(self, name): - if name in self._workspaces: - w = self._workspaces[name] - self.setActiveWorkspace(w) - return w - raise Exception("Error on OpenWorkspace for %s " % name) + w = self.getWorkspace(name) + self.setActiveWorkspace(w) + return w def getWorkspaces(self): """ @@ -496,16 +525,20 @@ def getWorkspacesNames(self): return self._workspaces.keys() def loadWorkspaces(self): - self._workspaces.clear() - for name in os.listdir(CONF.getPersistencePath()): - if name not in self._workspaces: - if os.path.isdir(os.path.join(CONF.getPersistencePath(),name)) and name not in self._excluded_directories: - w = self.createWorkspace(name, workspaceClass = WorkspaceOnFS) - - for name in self.couchdbmanager.getWorkspacesNames(): - if name not in self._workspaces and not name == "reports": - self.createWorkspace(name, workspaceClass = WorkspaceOnCouch) - + + self._workspaces_types = {} + fsworkspaces = {name: None for name in self.fsmanager.getWorkspacesNames()} + self._workspaces.update(fsworkspaces) + couchworkspaces = {name: None for name in self.couchdbmanager .getWorkspacesNames() + if not name == 'reports'} + self._workspaces.update(couchworkspaces) + + self._workspaces_types.update({name: WorkspaceOnFS.__name__ for name in fsworkspaces}) + self._workspaces_types.update({name: WorkspaceOnCouch.__name__ for name in couchworkspaces}) + + def getWorkspaceType(self, name): + return self._workspaces_types.get(name, 'undefined') + def setActiveWorkspace(self, workspace): try: self.stopAutoLoader() @@ -524,6 +557,9 @@ def setActiveWorkspace(self, workspace): if isinstance(self.active_workspace, WorkspaceOnCouch): self.startAutoLoader() + + def isActive(self, name): + return self.active_workspace.name == name def syncWorkspaces(self): """ diff --git a/persistence/change.py b/persistence/change.py new file mode 100644 index 00000000000..d4986256fbc --- /dev/null +++ b/persistence/change.py @@ -0,0 +1,90 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +from model.common import (ModelObjectNote, ModelObjectCred, ModelObjectVuln, + ModelObjectVulnWeb) +from model.hosts import Host, Interface, Service + + +class ChangeFactory(object): + def __init__(self): + pass + + def create(self, dic): + _type = dic.get("type") + if _type in [Host.class_signature, + Interface.class_signature, + Service.class_signature, + ModelObjectNote.class_signature, + ModelObjectVuln.class_signature, + ModelObjectVulnWeb.class_signature, + ModelObjectCred.class_signature, + 'unknown']: + return ChangeModelObject(dic) + else: + return ChangeCmd(dic) + + +class Change(object): + MODEL_OBJECT_ADDED = 1, + MODEL_OBJECT_MODIFIED = 2 + MODEL_OBJECT_DELETED = 3 + CMD_EXECUTED = 4 + CMD_FINISHED = 5 + UNKNOWN = 999 + + def __init__(self, doc): + self.type = doc.get("type") + self.action = self.UNKNOWN + self.msg = "Change: Action: %s - Type: %s" % (self.type, self.action) + + def getAction(self): + return self.action + + def getType(self): + return self.type + + def getMessage(self): + return self.msg + + +class ChangeModelObject(Change): + def __init__(self, doc): + Change.__init__(self, doc) + num_of_rev = int(doc.get("_rev")[0]) + self.object_name = doc.get("name") + if doc.get("_deleted"): + self.action = self.MODEL_OBJECT_DELETED + self.msg = "Object deleted" + elif num_of_rev > 1: + self.action = self.MODEL_OBJECT_MODIFIED + self.msg = "%s %s modified" % (self.getType(), + self.getObjectName()) + else: + self.action = self.MODEL_OBJECT_ADDED + self.msg = "%s %s added" % (self.getType(), + self.getObjectName()) + + def getObjectName(self): + return self.object_name + + +class ChangeCmd(Change): + def __init__(self, doc): + Change.__init__(self, doc) + self.cmd_info = doc.get('command') + " " + doc.get('params') + if doc.get("duration"): + self.action = self.CMD_FINISHED + self.msg = "Command finished: %s" % self.getCmdInfo() + else: + self.action = self.CMD_EXECUTED + self.msg = "Command executed: %s" % self.getCmdInfo() + + def getCmdInfo(self): + return self.cmd_info + +change_factory = ChangeFactory() diff --git a/persistence/orm.py b/persistence/orm.py index 8d75a0fb03e..381c8473d3f 100644 --- a/persistence/orm.py +++ b/persistence/orm.py @@ -6,8 +6,10 @@ ''' import model -import threading +import threading import traceback +from controllers.change import ChangeController + class WorkspacePersister(object): _instance = None @@ -15,29 +17,30 @@ class WorkspacePersister(object): _workspace = None _workspace_autoloader = None _pending_actions = None + _change_controller = ChangeController() - - def __new__(cls, *args, **kargs): + def __new__(cls, *args, **kargs): if cls._instance is None: cls._instance = object.__new__(cls, *args, **kargs) return cls._instance - - - def setPersister(self, workspace, persister): WorkspacePersister._persister = persister WorkspacePersister._workspace = workspace - WorkspacePersister._workspace_autoloader = WorkspaceAutoSync(self.reloadWorkspace, self.backendChangeListener) + WorkspacePersister._change_controller.setWorkspace(workspace) + WorkspacePersister._workspace_autoloader = WorkspaceAutoSync(self.loadChanges, self.backendChangeListener) WorkspacePersister._workspace_autoloader.start() WorkspacePersister._pending_actions = PendingTransactionsQueue() @staticmethod def stopThreads(): - WorkspacePersister._workspace_autoloader.stop() + WorkspacePersister._workspace_autoloader.stop() + + def loadChanges(self, changes): + self._change_controller.loadChanges(changes) def reloadWorkspace(self): - WorkspacePersister._workspace.load() + WorkspacePersister._workspace.load() @staticmethod def addPendingAction(obj, func, args, kwargs): @@ -78,13 +81,12 @@ def __init__(self, action_callback, listener): self._action = action_callback def run(self): - while not self._stop: try: result = self._listener() if result: model.api.devlog("Changes found: %s" % result) - self._action() + self._action(result) except Exception, e: model.api.devlog("An exception was captured while saving workspaces\n%s" % traceback.format_exc()) diff --git a/plugins/core.py b/plugins/core.py index 803f8f59bf4..1ab6c8536f5 100644 --- a/plugins/core.py +++ b/plugins/core.py @@ -135,7 +135,6 @@ def __init__(self, id, available_plugins, command_manager): self._setupActionDispatcher() self._command_manager = command_manager - self.last_command_information = None def _find_plugin(self, new_plugin_id): try: @@ -143,12 +142,6 @@ def _find_plugin(self, new_plugin_id): except KeyError: return None - def setLastCommandInformation(self, command_string): - self.last_command_information = CommandRunInformation( **{'workspace': model.api.getActiveWorkspace().name, - 'itime': time(), - 'command': command_string.split()[0], - 'params': ' '.join(command_string.split()[1:])}) - def _is_command_malformed(self, original_command, modified_command): """ Checks if the command to be executed is safe and it's not in the block list @@ -239,9 +232,9 @@ def processOutput(self, plugin, output): break # Finally we register the recently executed command information - self.last_command_information.duration = time() - self.last_command_information.itime - workspace = model.api.getActiveWorkspace() - self._command_manager.saveCommand(self.last_command_information, workspace) + # self.last_command_information.duration = time() - self.last_command_information.itime + # workspace = model.api.getActiveWorkspace() + # self._command_manager.saveCommand(self.last_command_information, workspace) def _processAction(self, action, parameters): """ @@ -309,6 +302,7 @@ class PluginController(PluginControllerBase): def __init__(self, id, available_plugins, command_manager): PluginControllerBase.__init__(self, id, available_plugins, command_manager) self._active_plugin = None + self.last_command_information = None self._buffer = StringIO() def setActivePlugin(self, plugin): @@ -344,7 +338,16 @@ def processCommandInput(self, prompt, username, current_path, command_string, in if self._is_command_malformed(command_string, modified_cmd_string): return None else: - self.setLastCommandInformation(command_string) + cmd_info = CommandRunInformation( + **{'workspace': model.api.getActiveWorkspace().name, + 'itime': time(), + 'command': command_string.split()[0], + 'params': ' '.join(command_string.split()[1:])}) + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + + self.last_command_information = cmd_info + return modified_cmd_string if isinstance(modified_cmd_string, basestring) else None def storeCommandOutput(self, output): @@ -403,6 +406,11 @@ def onCommandFinished(self): This method is called when the last executed command has finished. It's in charge of giving the plugin the output to be parsed. """ + cmd_info = self.last_command_information + cmd_info.duration = time() - cmd_info.itime + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + if self._active_plugin.has_custom_output(): output_file = open(self._active_plugin.get_custom_file_path(), 'r') output = output_file.read() @@ -426,9 +434,6 @@ def __init__(self, id, available_plugins, command_manager): PluginControllerBase.__init__(self, id, available_plugins, command_manager) self._active_plugins = {} - def setActivePlugin(self, plugin): - self._active_plugin = plugin - def processCommandInput(self, command_string): plugin = self._get_plugins_by_input(command_string) @@ -436,11 +441,20 @@ def processCommandInput(self, command_string): if plugin: modified_cmd_string = plugin.processCommandString("", "", command_string) if not self._is_command_malformed(command_string, modified_cmd_string): - self._active_plugins[command_string] = plugin + + cmd_info = CommandRunInformation( + **{'workspace': model.api.getActiveWorkspace().name, + 'itime': time(), + 'command': command_string.split()[0], + 'params': ' '.join(command_string.split()[1:])}) + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + + self._active_plugins[command_string] = plugin, cmd_info + output_file_path = None if plugin.has_custom_output(): output_file_path = plugin.get_custom_file_path() - self.setLastCommandInformation(command_string) return True, modified_cmd_string, output_file_path return False, None, None @@ -462,15 +476,16 @@ def getPluginAutocompleteOptions(self, command_string): # return new_options pass - def _disable_active_plugin(self): - model.api.devlog("Disabling active plugin") - self._active_plugin = None - def onCommandFinished(self, cmd, output): if cmd not in self._active_plugins.keys(): return False - self.processOutput(self._active_plugins.get(cmd), output) + plugin, cmd_info = self._active_plugins.get(cmd) + cmd_info.duration = time() - cmd_info.itime + workspace = model.api.getActiveWorkspace() + self._command_manager.saveCommand(cmd_info, workspace) + + self.processOutput(plugin, output) del self._active_plugins[cmd] return True diff --git a/plugins/repo/hydra/plugin.py b/plugins/repo/hydra/plugin.py index 67dce53fe81..bfc447d38fc 100644 --- a/plugins/repo/hydra/plugin.py +++ b/plugins/repo/hydra/plugin.py @@ -41,7 +41,7 @@ def __init__(self, xml_output): lines = xml_output.splitlines() self.items = [] for l in lines: - reg = re.search("\[([^$]+)\]\[([^$]+)\] host: ([^$]+) login: ([^$]+) password: ([^$]+)\n",l) + reg = re.search("\[([^$]+)\]\[([^$]+)\] host: ([^$]+) login: ([^$]+) password: ([^$]+)",l) if reg: item = {'port' : reg.group(1), 'plugin' : reg.group(2), 'ip' : reg.group(3), 'login' : reg.group(4), 'password' : reg.group(5) } diff --git a/plugins/repo/medusa/plugin.py b/plugins/repo/medusa/plugin.py index e513cdd21cb..a504ce73218 100644 --- a/plugins/repo/medusa/plugin.py +++ b/plugins/repo/medusa/plugin.py @@ -49,7 +49,8 @@ def __init__(self, xml_output): self.items = [] for l in lines: - reg = re.search("ACCOUNT FOUND: \[([^$]+)\] Host: ([^$]+) User: ([^$]+) Password: ([^$]+) \[SUCCESS\]\n",l) + reg = re.search("ACCOUNT FOUND: \[([^$]+)\] Host: ([^$]+) User: ([^$]+) Password: ([^$]+) \[SUCCESS\]",l) + print "REG" + str(reg) if reg: item = {'service' : reg.group(1), 'host' : reg.group(2), 'user' : reg.group(3), diff --git a/test_cases/__init__.py b/test_cases/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test_cases/changes_from_another_instance.py b/test_cases/changes_from_another_instance.py new file mode 100644 index 00000000000..965ddf4bf59 --- /dev/null +++ b/test_cases/changes_from_another_instance.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +import unittest +import sys +import os +sys.path.append(os.path.abspath(os.getcwd())) +import random + +from mockito import mock, when +import model.guiapi +import time +from model import api +from gui.notifier import NotificationCenter +from model.workspace import WorkspaceOnCouch, WorkspaceManager +import plugins.core as plcore +import model.controller as controller +from managers.all import CouchdbManager +from persistence.change import ChangeModelObject, ChangeCmd, Change +from persistence.orm import WorkspacePersister + +from config.configuration import getInstanceConfiguration +CONF = getInstanceConfiguration() + + +def new_random_workspace_name(): + return ("aworkspace" + "".join(random.sample( + [chr(i) for i in range(65, 90)], 10))).lower() + + +class ChangesTestSuite(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.model_controller = mock(controller.ModelController) + api.setUpAPIs(cls.model_controller) + cls.couch_uri = CONF.getCouchURI() + cls.cdm = CouchdbManager(uri=cls.couch_uri) + + class NotificationTest(NotificationCenter): + def __init__(self, ui): + self.changes = [] + + def changeFromInstance(self, change): + self.changes.append(change) + + cls.notifier = NotificationTest(None) + model.guiapi.notification_center = cls.notifier + cls._couchdb_workspaces = [] + cls.wm = WorkspaceManager(cls.model_controller, + mock(plcore.PluginController)) + cls.workspace = cls.wm.createWorkspace(new_random_workspace_name(), + workspaceClass=WorkspaceOnCouch) + when(cls.workspace).load().thenReturn(True) + cls._couchdb_workspaces.append(cls.workspace.name) + cls.wm.setActiveWorkspace(cls.workspace) + + def setUp(self): + self.notifier.changes = [] + + @classmethod + def tearDownClass(cls): + WorkspacePersister.stopThreads() + cls.cleanCouchDatabases() + + @classmethod + def cleanCouchDatabases(cls): + try: + for wname in cls._couchdb_workspaces: + cls.cdm.removeWorkspace(wname) + except Exception as e: + print(e) + + def test_model_objects_added(self): + d1 = { + 'type': 'Service' + } + d2 = { + 'type': 'Host' + } + d3 = { + 'type': 'Interface' + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + self.cdm._getDb(self.workspace.name).save_doc(d2, use_uuids=True, + force_update=True) + self.cdm._getDb(self.workspace.name).save_doc(d3, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 3, + "Some changes weren't added") + for change in self.notifier.changes: + self.assertIsInstance(change, ChangeModelObject, + "It should be a ChangeModelObject") + self.assertNotIsInstance(change, ChangeCmd, + "It shouldn't be a ChangeCmd") + self.assertEquals(change.getAction(), Change.MODEL_OBJECT_ADDED, + "Change should be an addition") + + def test_model_objects_delete(self): + d1 = { + '_id': '1', + 'type': 'Host', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 1, + "Some changes weren't added") + + self.assertEquals(self.notifier.changes[0].getAction(), + Change.MODEL_OBJECT_ADDED, + "First change should be an addition") + + self.cdm._getDb(self.workspace.name).delete_doc(d1['_id']) + time.sleep(1) + + self.assertEquals(self.notifier.changes[1].getAction(), + Change.MODEL_OBJECT_DELETED, + "Second change should be a Removal") + + def test_model_objects_modified(self): + d1 = { + '_id': '1', + 'type': 'Host', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + d1 = { + '_id': '1', + 'type': 'Host', + 'foo': 'bar' + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 2, + "Some changes weren't added") + self.assertEquals(self.notifier.changes[0].getAction(), + Change.MODEL_OBJECT_ADDED, + "First change should be an addition") + self.assertEquals(self.notifier.changes[1].getAction(), + Change.MODEL_OBJECT_MODIFIED, + "Second change should be a modification") + + def test_cmd_executed(self): + d1 = { + 'command': 'nmap', + 'params': '-A -T4 127.0.0.1', + 'type': 'CommandRunInformation', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 1, + "The change wasn't added") + change = self.notifier.changes[0] + self.assertNotIsInstance(change, ChangeModelObject, + "It shouldn't be a ChangeModelObject") + self.assertIsInstance(change, ChangeCmd, + "It should be a ChangeCmd") + self.assertEquals(change.getAction(), Change.CMD_EXECUTED, + "Change should be an executed command") + + def test_cmd_finished(self): + d1 = { + 'command': 'nmap', + 'params': '-A -T4 127.0.0.1', + 'type': 'CommandRunInformation', + } + self.cdm._getDb(self.workspace.name).save_doc(d1, use_uuids=True, + force_update=True) + d2 = { + 'command': 'nmap', + 'params': '-A -T4 127.0.0.1', + 'type': 'CommandRunInformation', + 'duration': '5' + } + self.cdm._getDb(self.workspace.name).save_doc(d2, use_uuids=True, + force_update=True) + + time.sleep(1) + + self.assertEquals(len(self.notifier.changes), 2, + "Some changes weren't added") + change = self.notifier.changes[1] + self.assertNotIsInstance(change, ChangeModelObject, + "It shouldn't be a ChangeModelObject") + self.assertIsInstance(change, ChangeCmd, + "It should be a ChangeCmd") + self.assertEquals(change.getAction(), Change.CMD_FINISHED, + "Change should be a finished command") + +if __name__ == '__main__': + unittest.main() diff --git a/test_cases/common.py b/test_cases/common.py new file mode 100644 index 00000000000..834bea3a0cb --- /dev/null +++ b/test_cases/common.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' +from model.hosts import Host, Interface, Service, ModelObjectVuln +import random +def new_random_workspace_name(): + return ("aworkspace" + "".join(random.sample([chr(i) for i in range(65, 90) + ], 10 ))).lower() + +def create_host(self, host_name="pepito", os="linux"): + host = Host(host_name, os) + self.model_controller.addHostSYNC(host) + 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 + +def create_service(self, host, interface, service_name = "coquito"): + service = Service(service_name) + self.model_controller.addServiceToInterfaceSYNC(host.getID(), + interface.getID(), service) + return service + +def create_host_vuln(self, host, name, desc, severity): + vuln = ModelObjectVuln(name, desc, severity) + self.model_controller.addVulnToHostSYNC(host.getID(), vuln) + + return vuln + +def create_int_vuln(self, host, interface, name, desc, severity): + vuln = ModelObjectVuln(name, desc, severity) + self.model_controller.addVulnToInterfaceSYNC( host.getID(), interface.getID(), vuln) + + return vuln + +def create_serv_vuln(self, host, service, name, desc, severity): + vuln = ModelObjectVuln(name, desc, severity) + self.model_controller.addVulnToServiceSYNC( host.getID(), service.getID(), vuln) + + return vuln diff --git a/test_cases/model_object.py b/test_cases/model_object.py index 054a28be27d..065b16aec1f 100644 --- a/test_cases/model_object.py +++ b/test_cases/model_object.py @@ -19,28 +19,11 @@ from persistence.orm import WorkspacePersister import random +from model.visitor import VulnsLookupVisitor +import test_cases.common as test_utils -from managers.all import CommandManager, CouchdbManager, PersistenceManagerFactory - -def new_random_workspace_name(): - return ("aworkspace" + "".join(random.sample([chr(i) for i in range(65, 90) - ], 10 ))).lower() - -def create_host(self, host_name="pepito", os="linux"): - host = Host(host_name, os) - self.model_controller.addHostSYNC(host) - 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 - -def create_service(self, host, interface, service_name = "coquito"): - service = Service(service_name) - self.model_controller.addServiceToInterfaceSYNC(host.getID(), - interface.getID(), service) - return service +from managers.all import CommandManager, CouchdbManager, PersistenceManagerFactory class ModelObjectCRUD(unittest.TestCase): @@ -53,7 +36,7 @@ def setUp(self): self.wm = WorkspaceManager(self.model_controller, mock(plcore.PluginController)) self.temp_workspace = self.wm.createWorkspace( - new_random_workspace_name(), + test_utils.new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) self.wm.setActiveWorkspace(self.temp_workspace) @@ -67,7 +50,7 @@ def testAddHost(self): then checks it's vality""" # When hostname = 'host' - _ = create_host(self, host_name=hostname, os='windows') + _ = test_utils.create_host(self, host_name=hostname, os='windows') # #Then added_host = self.model_controller.getHost(hostname) @@ -81,7 +64,7 @@ def testAddVulnToHost(self): then adds a VULN""" # When - h = create_host(self) + h = test_utils.create_host(self) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') self.model_controller.addVulnToHostSYNC(h.getID(), vuln) @@ -98,8 +81,8 @@ def testAddVulnToInterface(self): adds an interface to it then adds a VULN""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -118,9 +101,9 @@ def testAddVulnToService(self): adds an interface to it then adds service then a VULN""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -141,7 +124,7 @@ def testAddVulnWebToHost(self): then adds a VulnWeb""" # When - h = create_host(self) + h = test_utils.create_host(self) vuln = ModelObjectVulnWeb(name='VulnTest', desc='TestDescription', severity='high') self.model_controller.addVulnToHostSYNC(h.getID(), vuln) @@ -157,8 +140,8 @@ def testAddVulnWebToInterface(self): adds an interface to it then adds a VulnWeb""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) vuln = ModelObjectVulnWeb(name='VulnTest', desc='TestDescription', severity='high') @@ -186,9 +169,9 @@ def testAddVulnWebToService(self): adds an interface to it then adds service then a VulnWeb""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) vuln = ModelObjectVulnWeb(name='VulnTest', desc='TestDescription', severity='high') @@ -209,7 +192,7 @@ def testAddNoteToHost(self): then adds a Note""" # When - h = create_host(self) + h = test_utils.create_host(self) note = ModelObjectNote(name='NoteTest', text='TestDescription') self.model_controller.addNoteToHostSYNC(h.getID(), note) @@ -224,8 +207,8 @@ def testAddNoteToInterface(self): adds an interface to it then adds a Note""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -244,9 +227,9 @@ def testAddNoteToService(self): adds an interface to it then adds service then a Note""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -263,7 +246,7 @@ def testAddNoteToService(self): def testDeleteHost(self): """ Creates a Host to test it's removal from the controllers list """ - host1 = create_host(self, "coquito") + host1 = test_utils.create_host(self, "coquito") hosts_ids = [h.getID() for h in self.model_controller.getAllHosts()] self.assertIn(host1.getID(), hosts_ids, @@ -279,8 +262,8 @@ def testDeleteInterface(self): """ Creates a Host and an Interface, then deletes the interface to test it's removal from the controllers list """ - host1 = create_host(self, "coquito") - interface1 = create_interface(self, host1, iname="pepito") + host1 = test_utils.create_host(self, "coquito") + interface1 = test_utils.create_interface(self, host1, iname="pepito") hosts_ids = [h.getID() for h in self.model_controller.getAllHosts()] self.assertIn(host1.getID(), hosts_ids, @@ -306,9 +289,9 @@ def testDeleteService(self): """ Creates a Host an Interface and a Service, then deletes the Service to test it's removal from the controllers list """ - host1 = create_host(self, "coquito") - interface1 = create_interface(self, host1, iname="pepito") - service1 = create_service(self, host1, interface1) + host1 = test_utils.create_host(self, "coquito") + interface1 = test_utils.create_interface(self, host1, iname="pepito") + service1 = test_utils.create_service(self, host1, interface1) hosts_ids = [h.getID() for h in self.model_controller.getAllHosts()] self.assertIn(host1.getID(), hosts_ids, @@ -339,7 +322,7 @@ def testDeleteService(self): def testDeleteVulnFromHost(self): """ Creates a Host adds a Vuln then removes """ - host1 = create_host(self, "coquito") + host1 = test_utils.create_host(self, "coquito") vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -363,8 +346,8 @@ def testDelVulnFromInterface(self): adds an interface to it then adds a VULN""" # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -395,9 +378,9 @@ def testDelVulnFromService(self): Vuln""" # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', severity='high') @@ -425,7 +408,7 @@ def testDelVulnFromService(self): def testDeleteNoteFromHost(self): """ Creates a Host adds a Note then removes """ - host1 = create_host(self, "coquito") + host1 = test_utils.create_host(self, "coquito") note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -448,8 +431,8 @@ def testDelNoteFromInterface(self): note """ # When - host = create_host(self) - interface = create_interface(self, host) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -478,9 +461,9 @@ def testDelNoteFromService(self): note """ # When - host = create_host(self) - interface = create_interface(self, host) - service = create_service(self, host, interface) + host = test_utils.create_host(self) + interface = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, interface) note = ModelObjectNote(name='NoteTest', text='TestDescription') @@ -504,6 +487,64 @@ def testDelNoteFromService(self): notes = added_service.getNotes() self.assertNotIn(note, notes, 'Note not removed') + def testVulnHostLookup(self): + host = test_utils.create_host(self) + vuln = test_utils.create_host_vuln(self, host, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + + self.assertEquals(len(visitor.parents[0]), 1, + "object hierarchy should be only host") + self.assertIn(vuln, visitor.vulns) + + def testVulnInterfaceLookup(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + vuln = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + self.assertEquals(len(visitor.parents[0]), 2, + "object hierarchy should be host and interface") + self.assertIn(vuln, visitor.vulns) + + def testVulnServiceLookup(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, service, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + self.assertEquals(len(visitor.parents[0]), 3, + "object hierarchy should be host, interface and service") + self.assertIn(vuln, visitor.vulns) + + def testMultipleVulnLookup(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + service = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, service, 'vuln', 'desc', 'high') + vuln2 = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + visitor = VulnsLookupVisitor(vuln.getID()) + host.accept(visitor) + + parents1 = visitor.parents[0] + parents2 = visitor.parents[1] + + self.assertIn(host, parents1, + "Host should be in parents") + + self.assertIn(host, parents2, + "Host should be in parents") + + self.assertIn(inter, parents2, + "Interface should be in parents") + + self.assertIn(inter, parents2, + "Interface should be in parents") + if __name__ == '__main__': unittest.main() diff --git a/test_cases/plugin_controller_api.py b/test_cases/plugin_controller_api.py deleted file mode 100644 index d62adb06fa0..00000000000 --- a/test_cases/plugin_controller_api.py +++ /dev/null @@ -1,137 +0,0 @@ -''' -Faraday Penetration Test IDE - Community Version -Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) -See the file 'doc/LICENSE' for the license information - -''' - -import unittest -import os -import requests -import json -import sys -import base64 -from mockito import mock, when - -sys.path.append('.') - -from managers.all import PluginManager -import plugins.api -import model.api -import model.controller -from model.workspace import Workspace -from model.container import ModelObjectContainer -from managers.all import PersistenceManager - - -class TestPluginControllerApi(unittest.TestCase): - - @classmethod - def setUpClass(cls): - plugin_repo_path = os.path.join(os.getcwd(), "plugins", "repo") - plugin_manager = PluginManager(plugin_repo_path) - plugins.api.startPluginControllerAPI(plugin_manager) - - @classmethod - def tearDownClass(cls): - plugins.api.stopPluginControllerAPI() - - def setUp(self): - self.model_controller = model.controller.ModelController(mock()) - self.workspace = mock(Workspace) - self.workspace.name = "default" - self.workspace._dmanager = mock(PersistenceManager()) - when(self.workspace._dmanager).saveDocument().thenReturn(True) - when(self.workspace).getContainee().thenReturn(ModelObjectContainer()) - self.model_controller.setWorkspace(self.workspace) - - model.api.setUpAPIs(self.model_controller) - self.url_input = "http://127.0.0.1:9977/cmd/input" - self.url_output = "http://127.0.0.1:9977/cmd/output" - self.url_active_plugins = "http://127.0.0.1:9977/cmd/active-plugins" - self.headers = {'Content-type': 'application/json', 'Accept': 'application/json'} - - def tearDown(self): - requests.delete(self.url_active_plugins) - - def test_cmd_input_ls(self): - cmd = "ls" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - - self.assertEquals(response.status_code, 204, "Status Code should be 204: No Content, but received: %d" % response.status_code) - - - def test_cmd_input_ping(self): - cmd = "ping 127.0.0.1" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - json_response = response.json() - - self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) - self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") - self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") - self.assertIsNone(json_response.get("cmd"), "cmd should be None") - self.assertIsNone(json_response.get("custom_output_file"), "custom_output_file should be None") - - def test_cmd_input_nmap(self): - cmd = "nmap 127.0.0.1" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - json_response = response.json() - - self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) - self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") - self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") - self.assertIsNotNone(json_response.get("cmd"), "cmd shouldn't be None") - self.assertIsNotNone(json_response.get("custom_output_file"), "custom_output_file shouldn't be None") - - def test_cmd_input_get_instead_post(self): - cmd = "ls" - data = {"cmd": cmd} - response = requests.get(self.url_input, - data=json.dumps(data), - headers=self.headers) - - self.assertEquals(response.status_code, 405, "Status code should be 405, but received: %d" % response.status_code) - - def test_cmd_output_nmap(self): - # send input to register the active plugin - cmd = "nmap 127.0.0.1" - data = {"cmd": cmd} - response = requests.post(self.url_input, - data=json.dumps(data), - headers=self.headers) - - #send output, using a fake nmap xml ouput - output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) - output = base64.b64encode(output_file.read()) - data = {"cmd": cmd, "output": output} - response = requests.post(self.url_output, - data=json.dumps(data), - headers=self.headers) - self.model_controller.processAllPendingActions() - - self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) - self.assertEquals(len(self.model_controller.getAllHosts()), 1, "Controller should have 1 host") - - def test_cmd_output_plugin_not_active(self): - #send output, using a fake nmap xml ouput - cmd = "nmap 127.0.0.1" - output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) - output = base64.b64encode(output_file.read()) - data = {"cmd": cmd, "output": output} - response = requests.post(self.url_output, - data=json.dumps(data), - headers=self.headers) - - self.assertEquals(response.status_code, 400, "Status Code should be 400: Bad Request, but received: %d" % response.status_code) - -if __name__ == '__main__': - unittest.main() diff --git a/test_cases/pluginbase_api.py b/test_cases/pluginbase_api.py index 360fe9b3ab4..7b0c954e85c 100644 --- a/test_cases/pluginbase_api.py +++ b/test_cases/pluginbase_api.py @@ -18,6 +18,8 @@ from model.workspace import Workspace from model.container import ModelObjectContainer from managers.all import CommandManager +from time import time +from model.commands_history import CommandRunInformation class TestPluginCreateModelObject(TestCase): @@ -45,13 +47,18 @@ def parseOutputString(self, output, debug=False): api.setUpAPIs(self._model_controller) self._plugin_controller.setActivePlugin(self.plugin) + self.cmdinfo = CommandRunInformation( + **{'workspace': 'test', + 'itime': time(), + 'command': 'test', + 'params': 'test'}) def test_create_host(self): """ Testing the creation of one host """ h = self.plugin.createAndAddHost("pepito", "linux") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -67,7 +74,7 @@ def test_create_same_host_two_times(self): """ h1 = self.plugin.createAndAddHost("pepito", "linux") h2 = self.plugin.createAndAddHost("pepito", "linux") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -80,7 +87,7 @@ def test_create_host_with_interface(self): """ h = self.plugin.createAndAddHost("pepito", "linux") i = self.plugin.createAndAddInterface(h, "1.2.3.4") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -101,7 +108,7 @@ def test_create_interface_two_times(self): h2 = self.plugin.createAndAddHost("pepito", "linux") i2 = self.plugin.createAndAddInterface(h2, "1.2.3.4") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -115,7 +122,7 @@ def test_create_host_with_interface_with_service(self): h = self.plugin.createAndAddHost("pepito", "linux") i = self.plugin.createAndAddInterface(h, "1.2.3.4") s = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -133,7 +140,7 @@ def test_create_two_services_different_names_equal_port(self): i = self.plugin.createAndAddInterface(h, "1.2.3.4") s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "test", protocol="tcp", ports=['80']) - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -151,7 +158,7 @@ def test_create_two_services_same_names_different_port(self): i = self.plugin.createAndAddInterface(h, "1.2.3.4") s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -169,7 +176,7 @@ def test_create_vuln_to_service(self): s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) v = self.plugin.createAndAddVulnToService(h, s1, "vuln1", "descripcion") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -190,7 +197,7 @@ def test_create_note_to_service(self): s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) n = self.plugin.createAndAddNoteToService(h, s1, "note1", "desc1") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -212,7 +219,7 @@ def test_create_note_to_note_service(self): s2 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['443']) n = self.plugin.createAndAddNoteToService(h, s1, "note1", "desc1") n2 = self.plugin.createAndAddNoteToNote(h, s1, n, "note2", "desc2") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() @@ -232,7 +239,7 @@ def test_create_cred_to_service(self): i = self.plugin.createAndAddInterface(h, "1.2.3.4") s1 = self.plugin.createAndAddServiceToInterface(h, i, "unknown", protocol="tcp", ports=['80']) c = self.plugin.createAndAddCredToService(h, s1, "user", "pass") - self._plugin_controller.setLastCommandInformation("mock") + self._plugin_controller.last_command_information = self.cmdinfo self._plugin_controller.onCommandFinished() self._model_controller.processAllPendingActions() diff --git a/test_cases/rest_controller_apis.py b/test_cases/rest_controller_apis.py new file mode 100644 index 00000000000..fb51d58f3eb --- /dev/null +++ b/test_cases/rest_controller_apis.py @@ -0,0 +1,267 @@ +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import unittest +import os +import requests +import json +import sys +import base64 +from mockito import mock, when + +sys.path.append('.') + +from managers.all import PluginManager +import apis.rest.api as api +import model.api +import model.controller +from model.workspace import Workspace +from model.container import ModelObjectContainer +from managers.all import PersistenceManager +import test_cases.common as test_utils + + +class TestPluginControllerApi(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.model_controller = model.controller.ModelController(mock()) + plugin_repo_path = os.path.join(os.getcwd(), "plugins", "repo") + plugin_manager = PluginManager(plugin_repo_path) + api.startAPIs(plugin_manager, cls.model_controller) + + @classmethod + def tearDownClass(cls): + api.stopAPIs() + + def setUp(self): + self.workspace = mock(Workspace) + self.workspace.name = "default" + self.workspace._dmanager = mock(PersistenceManager()) + when(self.workspace._dmanager).saveDocument().thenReturn(True) + when(self.workspace).getContainee().thenReturn(ModelObjectContainer()) + self.model_controller.setWorkspace(self.workspace) + + model.api.setUpAPIs(self.model_controller) + self.url_input = "http://127.0.0.1:9977/cmd/input" + self.url_output = "http://127.0.0.1:9977/cmd/output" + self.url_active_plugins = "http://127.0.0.1:9977/cmd/active-plugins" + self.headers = {'Content-type': 'application/json', 'Accept': 'application/json'} + + self.url_model_edit_vulns = "http://127.0.0.1:9977/model/edit/vulns" + self.url_model_del_vulns = "http://127.0.0.1:9977/model/del/vulns" + + def tearDown(self): + requests.delete(self.url_active_plugins) + + def test_cmd_input_ls(self): + cmd = "ls" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 204, "Status Code should be 204: No Content, but received: %d" % response.status_code) + + + def test_cmd_input_ping(self): + cmd = "ping 127.0.0.1" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + json_response = response.json() + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) + self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") + self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") + self.assertIsNone(json_response.get("cmd"), "cmd should be None") + self.assertIsNone(json_response.get("custom_output_file"), "custom_output_file should be None") + + def test_cmd_input_nmap(self): + cmd = "nmap 127.0.0.1" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + json_response = response.json() + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) + self.assertIn("cmd", json_response.keys(), "Json response should have a cmd key") + self.assertIn("custom_output_file", json_response.keys(), "Json response should have a custom_output_file key") + self.assertIsNotNone(json_response.get("cmd"), "cmd shouldn't be None") + self.assertIsNotNone(json_response.get("custom_output_file"), "custom_output_file shouldn't be None") + + def test_cmd_input_get_instead_post(self): + cmd = "ls" + data = {"cmd": cmd} + response = requests.get(self.url_input, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 405, "Status code should be 405, but received: %d" % response.status_code) + + def test_cmd_output_nmap(self): + # send input to register the active plugin + cmd = "nmap 127.0.0.1" + data = {"cmd": cmd} + response = requests.post(self.url_input, + data=json.dumps(data), + headers=self.headers) + + + #send output, using a fake nmap xml ouput + output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) + output = base64.b64encode(output_file.read()) + data = {"cmd": cmd, "output": output} + response = requests.post(self.url_output, + data=json.dumps(data), + headers=self.headers) + self.model_controller.processAllPendingActions() + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK, but received: %d" % response.status_code) + self.assertEquals(len(self.model_controller.getAllHosts()), 1, "Controller should have 1 host") + + def test_cmd_output_plugin_not_active(self): + #send output, using a fake nmap xml ouput + cmd = "nmap 127.0.0.1" + output_file = open(os.path.join(os.getcwd(), 'test_cases/data/nmap_plugin_with_api.xml')) + output = base64.b64encode(output_file.read()) + data = {"cmd": cmd, "output": output} + response = requests.post(self.url_output, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 400, "Status Code should be 400: Bad Request, but received: %d" % response.status_code) + + def test_model_edit_host_vuln(self): + host = test_utils.create_host(self) + vuln = test_utils.create_host_vuln(self, host, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.post(self.url_model_edit_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedvuln = addedhost.getVuln(vuln.getID()) + + self.assertEquals(addedvuln.name, 'coco', 'Name not updated') + self.assertEquals(addedvuln.desc, 'newdesc', 'Desc not updated') + self.assertEquals(addedvuln.severity, 'low', 'Severity not updated') + + + def test_model_edit_int_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + vuln = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.post(self.url_model_edit_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + addedvuln = addedInterface.getVuln(vuln.getID()) + + self.assertEquals(addedvuln.name, 'coco', 'Name not updated') + self.assertEquals(addedvuln.desc, 'newdesc', 'Desc not updated') + self.assertEquals(addedvuln.severity, 'low', 'Severity not updated') + + + def test_model_edit_serv_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + serv = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, serv, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.post(self.url_model_edit_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + addedService = addedInterface.getService(serv.getID()) + addedvuln = addedService.getVuln(vuln.getID()) + + self.assertEquals(addedvuln.name, 'coco', 'Name not updated') + self.assertEquals(addedvuln.desc, 'newdesc', 'Desc not updated') + self.assertEquals(addedvuln.severity, 'low', 'Severity not updated') + + + def test_model_remove_host_vuln(self): + host = test_utils.create_host(self) + vuln = test_utils.create_host_vuln(self, host, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID(), 'name': 'coco', + 'desc': 'newdesc', 'severity': 'low'} + + response = requests.delete(self.url_model_del_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedvuln = addedhost.getVulns() + + self.assertEquals(len(addedvuln), 0, 'Vuln not removed from Host') + + def test_model_del_int_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + vuln = test_utils.create_int_vuln(self, host, inter, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID()} + + response = requests.delete(self.url_model_del_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + self.assertEquals(len(addedInterface.getVulns()), 0, 'Interface vulns not deleted') + + def test_model_remove_serv_vuln(self): + host = test_utils.create_host(self) + inter = test_utils.create_interface(self, host) + serv = test_utils.create_service(self, host, inter) + vuln = test_utils.create_serv_vuln(self, host, serv, 'vuln', 'desc', 'high') + + data = {"vulnid": vuln.getID(), "hostid": host.getID()} + + response = requests.delete(self.url_model_del_vulns, + data=json.dumps(data), + headers=self.headers) + + self.assertEquals(response.status_code, 200, "Status Code should be 200: OK") + + addedhost = self.model_controller.getHost(host.getID()) + addedInterface = addedhost.getInterface(inter.getID()) + addedService = addedInterface.getService(serv.getID()) + + self.assertEquals(len(addedService.getVulns()), 0, 'Service vulns not removed') + + +if __name__ == '__main__': + unittest.main() diff --git a/test_cases/updates.py b/test_cases/updates.py new file mode 100644 index 00000000000..7f75f0a42ad --- /dev/null +++ b/test_cases/updates.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +''' +Faraday Penetration Test IDE - Community Version +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information + +''' + +import unittest +import sys +sys.path.append('.') +import model.controller as controller +import plugins.core as plcore +from mockito import mock +from model import api +from model.workspace import WorkspaceOnCouch, WorkspaceManager +from persistence.orm import WorkspacePersister + +import test_cases.common as test_utils + + +class UpdatesTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.model_controller = controller.ModelController(mock()) + api.setUpAPIs(cls.model_controller) + cls.wm = WorkspaceManager(cls.model_controller, + mock(plcore.PluginController)) + cls.temp_workspace = cls.wm.createWorkspace( + test_utils.new_random_workspace_name(), + workspaceClass=WorkspaceOnCouch) + + cls.wm.setActiveWorkspace(cls.temp_workspace) + WorkspacePersister.stopThreads() + + def setUp(self): + pass + + @classmethod + def tearDownClass(cls): + WorkspacePersister.stopThreads() + cls.wm.removeWorkspace(cls.temp_workspace.name) + + def tearDown(self): + pass + + def testAddHost(self): + """ This test case creates a host within the Model Controller context + then checks it's vality""" + # When + hostname = 'host' + test_utils.create_host(self, host_name=hostname, os='windows') + # Then, we generate an update + test_utils.create_host(self, host_name=hostname, os='linux') + + self.assertEquals(len(self.model_controller.getConflicts()), 1, + 'Update not generated') + + conflict = self.model_controller.getConflicts()[0] + + +if __name__ == '__main__': + unittest.main() diff --git a/test_cases/workspace.py b/test_cases/workspace.py index 1751b721e53..b1e1f18255b 100644 --- a/test_cases/workspace.py +++ b/test_cases/workspace.py @@ -10,7 +10,7 @@ import os import sys sys.path.append('.') -from model.workspace import (CouchdbManager, WorkspaceManager, +from model.workspace import (FSManager, CouchdbManager, WorkspaceManager, WorkspaceOnCouch, WorkspaceOnFS) from model.controller import ModelController @@ -28,6 +28,8 @@ class TestWorkspacesManagement(unittest.TestCase): def setUp(self): self.couch_uri = CONF.getCouchURI() self.cdm = CouchdbManager(uri=self.couch_uri) + wpath = os.path.expanduser("~/.faraday/persistence/" ) + self.fsm = FSManager(wpath) self.wm = WorkspaceManager(mock(ModelController), mock(PluginController)) self._fs_workspaces = [] @@ -70,6 +72,7 @@ def test_create_fs_workspace(self): wpath = os.path.expanduser("~/.faraday/persistence/%s" % wname) self.assertTrue(os.path.exists(wpath)) + self.assertEquals(WorkspaceOnFS.__name__, self.wm.getWorkspaceType(wname)) def test_create_couch_workspace(self): """ @@ -84,6 +87,8 @@ def test_create_couch_workspace(self): wpath = os.path.expanduser("~/.faraday/persistence/%s" % wname) self.assertFalse(os.path.exists(wpath)) + self.assertEquals(WorkspaceOnCouch.__name__, self.wm.getWorkspaceType(wname)) + def test_delete_couch_workspace(self): """ Verifies the deletion of a couch workspace @@ -111,6 +116,120 @@ def test_delete_fs_workspace(self): self.wm.removeWorkspace(wname) self.assertFalse(os.path.exists(wpath)) + def test_list_workspaces(self): + """ Lists FS workspaces and Couch workspaces """ + # First create workspaces manually + wnamefs = self.new_random_workspace_name() + wnamecouch = self.new_random_workspace_name() + # FS + self.fsm.addWorkspace(wnamefs) + # Couch + self.cdm.addWorkspace(wnamecouch) + + # When loading workspaces + self.wm.loadWorkspaces() + + self.assertIn(wnamefs, self.wm.getWorkspacesNames(), 'FS Workspace not loaded') + self.assertIn(wnamecouch, self.wm.getWorkspacesNames(), 'Couch Workspace not loaded') + + self.assertEquals(self.wm.getWorkspaceType(wnamefs), WorkspaceOnFS.__name__, 'Workspace type bad defined' ) + self.assertEquals(self.wm.getWorkspaceType(wnamecouch), WorkspaceOnCouch.__name__, 'Workspace type bad defined') + + + def test_get_workspace(self): + """ Create a workspace, now ask for it """ + + # When + wname = self.new_random_workspace_name() + workspace = self.wm.createWorkspace(wname, workspaceClass=WorkspaceOnFS) + + added_workspace = self.wm.getWorkspace(wname) + + # Then + self.assertIsNotNone(workspace, 'Workspace added should not be none') + self.assertEquals(workspace, added_workspace, 'Workspace created and added diffier') + + def test_get_existent_couch_workspace(self): + """ Create a workspace in the backend, now ask for it """ + + # When + wname = self.new_random_workspace_name() + workspace = self.cdm.addWorkspace(wname) + self.wm.loadWorkspaces() + + added_workspace = self.wm.getWorkspace(wname) + + # Then + self.assertIsNotNone(added_workspace, 'Workspace added should not be none') + + def test_get_existent_fs_workspace(self): + """ Create a workspace in the backend, now ask for it """ + + # When + wname = self.new_random_workspace_name() + workspace = self.fsm.addWorkspace(wname) + self.wm.loadWorkspaces() + + added_workspace = self.wm.getWorkspace(wname) + + # Then + self.assertIsNotNone(added_workspace, 'Workspace added should not be none') + + def test_get_non_existent_workspace(self): + """ Retrieve a non existent workspace """ + + added_workspace = self.wm.getWorkspace('inventado') + + # Then + self.assertIsNone(added_workspace, 'Workspace added should not be none') + + def test_set_active_workspace(self): + ''' create a workspace through the backend, then set it as active ''' + + wname = self.new_random_workspace_name() + workspace = self.fsm.addWorkspace(wname) + self.wm.loadWorkspaces() + + added_workspace = self.wm.getWorkspace(wname) + + # when + self.wm.setActiveWorkspace(added_workspace) + + self.assertEquals(added_workspace, self.wm.getActiveWorkspace(), + 'Active workspace diffiers with expected workspace') + + self.assertTrue(self.wm.isActive(added_workspace.name), + 'Workspace is active flag not set') + + def test_remove_fs_workspace(self): + # First + wname = self.new_random_workspace_name() + added_workspace = self.wm.createWorkspace(wname, workspaceClass=WorkspaceOnFS) + + # When + self.wm.removeWorkspace(wname) + + # Then + self.assertNotIn(wname, self.fsm.getWorkspacesNames()) + + def test_remove_couch_workspace(self): + # First + wname = self.new_random_workspace_name() + added_workspace = self.wm.createWorkspace(wname, workspaceClass=WorkspaceOnCouch) + + # When + self.wm.removeWorkspace(wname) + + # Then + self.assertNotIn(wname, self.cdm.getWorkspacesNames()) + + def test_remove_non_existent_workspace(self): + # When + self.wm.removeWorkspace('invented') + + # Then + self.assertNotIn('invented', self.cdm.getWorkspacesNames()) + if __name__ == '__main__': unittest.main() diff --git a/test_cases/workspace_manager.py b/test_cases/workspace_manager.py index b21d704e902..817b508a819 100644 --- a/test_cases/workspace_manager.py +++ b/test_cases/workspace_manager.py @@ -74,7 +74,7 @@ def cleanCouchDatabases(self): except Exception as e: print e - def test_switch_workspace_with_objects(self): + def _test_switch_workspace_with_objects(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) self._couchdb_workspaces.append(workspace.name) @@ -111,7 +111,7 @@ def test_switch_workspace_with_objects(self): self.assertIn(service1, interface1.getAllServices(), "Service not in Interface!") - def test_remove_active_workspace(self): + def _test_remove_active_workspace(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) @@ -125,7 +125,7 @@ def test_remove_active_workspace(self): self.assertNotIn(host1.getID(), hosts_ids, 'Host not removed while removing active workspace') - def test_remove_active_workspace_fs(self): + def _test_remove_active_workspace_fs(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnFS) self.wm.setActiveWorkspace(workspace) @@ -137,7 +137,7 @@ def test_remove_active_workspace_fs(self): self.assertNotIn(host1, self.model_controller.getAllHosts(), 'Host not removed while removing active workspace') - def test_remove_another_workspace(self): + def _test_remove_another_workspace(self): workspace = self.wm.createWorkspace(new_random_workspace_name(), workspaceClass=WorkspaceOnCouch) @@ -157,7 +157,7 @@ def test_remove_another_workspace(self): self.assertIn(workspace2.name, self.wm.getWorkspacesNames(), "Workspace removed while removing another workspace") - def test_load_workspace_on_couch(self): + def _test_load_workspace_on_couch(self): """ This test case creates a host within the Model Controller context adds an interface to it then adds a VulnWeb""" @@ -363,7 +363,215 @@ def test_load_workspace_on_couch(self): "Note not created") self.assertEquals(len(added_service3.getCreds()), 1, "Cred not created") - + + def test_load_workspace_on_fs(self): + """ This test case creates a host within the Model Controller context + adds an interface to it then adds a VulnWeb""" + + """ + We are going to test this structure: + host -> interface1 -> service1 -> vuln_web + -> vuln + -> note + -> service2 -> vuln + -> vuln + -> vuln + -> note + -> note + + -> interface2 -> service3 -> note + -> credential + -> vuln + -> vuln + """ + + workspace = self.wm.createWorkspace(new_random_workspace_name(), + workspaceClass=WorkspaceOnFS) + #self._couchdb_workspaces.append(workspace.name) + self.wm.setActiveWorkspace(workspace) + WorkspacePersister.stopThreads() + + host = create_host(self) + interface = create_interface(self, host, ip="127.0.0.1") + interface2 = create_interface(self, host, ip="127.0.0.2") + service = create_service(self, host, interface, ports=1) + service2 = create_service(self, host, interface, ports=2) + service3 = create_service(self, host, interface2, ports=3) + + vulnweb = ModelObjectVulnWeb(name='VulnWebTest', + desc='TestDescription', + severity='high') + + self.model_controller.addVulnToServiceSYNC(host.getID(), + service.getID(), + vulnweb) + + vuln = ModelObjectVuln(name='VulnTest', desc='TestDescription', + severity='high') + vuln2 = ModelObjectVuln(name='VulnTest2', desc='TestDescription', + severity='high') + vuln3 = ModelObjectVuln(name='VulnTest3', desc='TestDescription', + severity='high') + vuln4 = ModelObjectVuln(name='VulnTest4', desc='TestDescription', + severity='high') + vuln5 = ModelObjectVuln(name='VulnTest5', desc='TestDescription', + severity='high') + vuln6 = ModelObjectVuln(name='VulnTest6', desc='TestDescription', + severity='high') + + self.model_controller.addVulnToServiceSYNC(host.getID(), + service.getID(), + vuln) + self.model_controller.addVulnToServiceSYNC(host.getID(), + service2.getID(), + vuln2) + self.model_controller.addVulnToServiceSYNC(host.getID(), + service2.getID(), + vuln3) + self.model_controller.addVulnToHostSYNC(host.getID(), + vuln4) + self.model_controller.addVulnToServiceSYNC(host.getID(), + service3.getID(), + vuln5) + self.model_controller.addVulnToInterfaceSYNC(host.getID(), + interface2.getID(), + vuln6) + + note = ModelObjectNote(name='NoteTest', text='TestDescription') + note2 = ModelObjectNote(name='NoteTest2', text='TestDescription') + note3 = ModelObjectNote(name='NoteTest3', text='TestDescription') + note4 = ModelObjectNote(name='NoteTest4', text='TestDescription') + + self.model_controller.addNoteToServiceSYNC(host.getID(), + service.getID(), + note) + self.model_controller.addNoteToHostSYNC(host.getID(), + note2) + self.model_controller.addNoteToHostSYNC(host.getID(), + note3) + self.model_controller.addNoteToServiceSYNC(host.getID(), + service3.getID(), + note4) + + cred = ModelObjectCred(username='user', password='pass') + + self.model_controller.addCredToServiceSYNC(host.getID(), + service3.getID(), + cred) + + # First, we test if the structure was correctly created + + # one host with two interfaces, one vuln and two notes + + self.assertEquals(len(self.model_controller.getAllHosts()), 1, + "Host not created") + added_host = self.model_controller.getHost(host.getID()) + + self.assertEquals(len(added_host.getAllInterfaces()), 2, + "Interfaces not added to Host") + self.assertEquals(len(added_host.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_host.getNotes()), 2, + "Notes not created") + + # one interface with two services, and another one + # with a service and a vuln + + added_interface1 = added_host.getInterface(interface.getID()) + added_interface2 = added_host.getInterface(interface2.getID()) + + self.assertEquals(len(added_interface1.getAllServices()), 2, + "Services not created") + + self.assertEquals(len(added_interface2.getAllServices()), 1, + "Service not created") + + self.assertEquals(len(added_interface2.getVulns()), 1, + "Vulns not created") + + # one service with a note, a vuln and a vuln web + added_service1 = added_interface1.getService(service.getID()) + self.assertEquals(len(added_service1.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service1.getVulns()), 2, + "Vulns not created") + added_vuln_web = added_service1.getVuln(vulnweb.getID()) + self.assertEquals(added_vuln_web.class_signature, "VulnerabilityWeb", + "Not a vuln web") + + # one service with two vulns + added_service2 = added_interface1.getService(service2.getID()) + self.assertEquals(len(added_service2.getVulns()), 2, + "Services not created") + + # one service with a note, a vuln and a credential + + added_service3 = added_interface2.getService(service3.getID()) + self.assertEquals(len(added_service3.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_service3.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service3.getCreds()), 1, + "Cred not created") + + # So, now we reload the worskpace and check everything again + print workspace.name + + workspace.load() + + # one host with two interfaces, one vuln and two notes + + self.assertEquals(len(self.model_controller.getAllHosts()), 1, + "Host not created") + added_host = self.model_controller.getHost(host.getID()) + + self.assertEquals(len(added_host.getAllInterfaces()), 2, + "Interfaces not added to Host") + self.assertEquals(len(added_host.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_host.getNotes()), 2, + "Notes not created") + + # one interface with two services, and another one + # with a service and a vuln + + added_interface1 = added_host.getInterface(interface.getID()) + added_interface2 = added_host.getInterface(interface2.getID()) + + self.assertEquals(len(added_interface1.getAllServices()), 2, + "Services not created") + + self.assertEquals(len(added_interface2.getAllServices()), 1, + "Service not created") + + self.assertEquals(len(added_interface2.getVulns()), 1, + "Vulns not created") + + # one service with a note, a vuln and a vuln web + added_service1 = added_interface1.getService(service.getID()) + self.assertEquals(len(added_service1.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service1.getVulns()), 2, + "Vulns not created") + added_vuln_web = added_service1.getVuln(vulnweb.getID()) + self.assertEquals(added_vuln_web.class_signature, "VulnerabilityWeb", + "Not a vuln web") + + # one service with two vulns + added_service2 = added_interface1.getService(service2.getID()) + self.assertEquals(len(added_service2.getVulns()), 2, + "Services not created") + + # one service with a note, a vuln and a credential + + added_service3 = added_interface2.getService(service3.getID()) + self.assertEquals(len(added_service3.getVulns()), 1, + "Vuln not created") + self.assertEquals(len(added_service3.getNotes()), 1, + "Note not created") + self.assertEquals(len(added_service3.getCreds()), 1, + "Cred not created") + if __name__ == '__main__': unittest.main() diff --git a/update b/update new file mode 100755 index 00000000000..11a057fd85b --- /dev/null +++ b/update @@ -0,0 +1,10 @@ +#!/bin/bash +### +## Faraday Penetration Test IDE - Community Version +## Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +## See the file 'doc/LICENSE' for the license information +### + +#faraday update v0.1 +find -name "*pyc" -delete +git pull diff --git a/views/_design/hosts/views/services/map.js b/views/_design/hosts/views/services/map.js deleted file mode 100644 index 09c520bb58e..00000000000 --- a/views/_design/hosts/views/services/map.js +++ /dev/null @@ -1,17 +0,0 @@ -// Faraday Penetration Test IDE - Community Version -// Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) -// See the file 'doc/LICENSE' for the license information -function(doc) { - if(doc.type=="Service"){ - if(doc.parent != 'null') { - var hid = doc._id.substring(0, doc._id.indexOf('.')); - emit(hid, {"name": doc.name, - "description": doc.description, - "protocol": doc.protocol, - "ports": doc.ports, - "status": doc.status, - "owned": doc.owned, - "hid": hid}); - } - } -} diff --git a/views/_design/hosts/views/vulns/map.js b/views/_design/hosts/views/vulns/map.js index 06e8faf7af6..7115c9e12c1 100644 --- a/views/_design/hosts/views/vulns/map.js +++ b/views/_design/hosts/views/vulns/map.js @@ -4,23 +4,6 @@ function(doc) { if(doc.type=="Vulnerability" || doc.type=="VulnerabilityWeb") { - var label = "5"; - var sev = doc.severity; - if(sev == "Information") { - label = "0"; - } else if(sev == "Low") { - label = "1"; - } else if(sev == "Medium") { - label = "2"; - } else if(sev == "High") { - label = "3"; - } else if(sev == "Critical") { - label = "4"; - } else if(sev=="") { - label = "5"; - } else if(sev >= 0 && sev <= 4) { - label = sev; - } - emit(label, 1); + emit(doc.severity, 1); } } diff --git a/views/reports/_attachments/estilos.css b/views/reports/_attachments/estilos.css index 9d6378cefeb..ec7a5d8c202 100644 --- a/views/reports/_attachments/estilos.css +++ b/views/reports/_attachments/estilos.css @@ -248,9 +248,7 @@ aside { -moz-transition: all .3s ease; -webkit-transition: all .3s ease; } - aside nav a.activo, aside nav a:hover { - background: #DF3936; - } + .seccion { background: #E8EFF0; @@ -475,12 +473,10 @@ table.tablesorter tbody tr td:first-child { margin-bottom: .5em !important; } .formu .input, .formu textarea, .formu select, .input { - /* -border-radius:5px; + border-radius:5px; -ms-border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px; -*/ } .formu .input:focus, .formu textarea:focus, .formu select:focus, .input:focus { border-color: #aaa; @@ -524,3 +520,12 @@ label { color: #2D333D; font-size: 13px; } +.left { + float: left; +} +#sec { + float: right; +} +* { /* compatibilidad entre nuestros estilos y los de bootstrap */ + -moz-box-sizing: content-box !important; +} diff --git a/views/reports/_attachments/index.html b/views/reports/_attachments/index.html index efdf590615a..60f4cb77db3 100644 --- a/views/reports/_attachments/index.html +++ b/views/reports/_attachments/index.html @@ -18,6 +18,8 @@ + + @@ -30,6 +32,7 @@ +
@@ -37,23 +40,6 @@ -