Merge tag 'upstream/1.0.19' into kali/master
Upstream version 1.0.19
Sophie Brun
8 years ago
6 | 6 | Designed for simplicity, users should notice no difference between their own terminal application and the one included in Faraday. Developed with a specialized set of functionalities that help users improve their own work. Do you remember yourself programming without an IDE? Well, Faraday does the same as an IDE does for you when programming, but from the perspective of a penetration test. |
7 | 7 | |
8 | 8 | Please read the [RELEASE notes](https://github.com/infobyte/faraday/blob/master/RELEASE.md)! |
9 | ||
10 | ![GUI - Web](https://raw.github.com/wiki/infobyte/faraday/images/GUI_Dashboard_new.png) | |
9 | 11 | |
10 | 12 | Plugins |
11 | 13 | --- |
30 | 32 | |
31 | 33 | This applies only to Debian, Ubuntu, Kali and Backtrack. For the full installation guide [visit our wiki](https://github.com/infobyte/faraday/wiki/Installation). |
32 | 34 | |
33 | Download the [latest tarball](https://github.com/infobyte/faraday/tarball/master) or clone the [Faraday Git Project](https://github.com/infobyte/faraday repository): | |
35 | Download the [latest tarball](https://github.com/infobyte/faraday/tarball/master) or clone our repo: | |
34 | 36 | |
35 | 37 | ``` |
36 | 38 | $ git clone https://github.com/infobyte/faraday.git faraday-dev |
43 | 45 | --- |
44 | 46 | Want to read more about the project? Try our [wiki](https://github.com/infobyte/faraday/wiki). |
45 | 47 | |
46 | Already a user and have a question or bug report? Please check out our [FAQ](https://github.com/infobyte/faraday/wiki/FAQ). If you're still having troubles you can [open a ticket](https://github.com/infobyte/faraday/issues/new). | |
48 | Already a user and have a question or bug report? Check out our [FAQ](https://github.com/infobyte/faraday/wiki/FAQ) and [troubleshooting](https://github.com/infobyte/faraday/wiki/troubleshooting) pages. If you're still having troubles you can [open a ticket](https://github.com/infobyte/faraday/issues/new). | |
47 | 49 | |
48 | 50 | Join our community! Subscribe to our [mailing list](https://groups.google.com/forum/#!forum/faradaysec) or find us on Twitter [@faradaysec] (https://twitter.com/faradaysec) |
49 | 51 | |
50 | Do you have a question? Troubleshooting? Joing our IRC channel #faraday-dev in [freenode](ircs://irc.freenode.net/faraday-dev) or access directrly from this link: [![Visit our IRC channel](https://kiwiirc.com/buttons/irc.freenode.org/faraday-dev.png)](https://kiwiirc.com/client/irc.freenode.org/?nick=faraday_gi|?#faraday-dev) | |
52 | Do you have a question? Troubleshooting? Joing our IRC channel #faraday-dev in [freenode](ircs://irc.freenode.net/faraday-dev) or access directly from this link: [![Visit our IRC channel](https://kiwiirc.com/buttons/irc.freenode.org/faraday-dev.png)](https://kiwiirc.com/client/irc.freenode.org/?nick=faraday_gi|?#faraday-dev) | |
51 | 53 |
8 | 8 | |
9 | 9 | New features in the latest update |
10 | 10 | ===================================== |
11 | ||
12 | Apr 29, 2016: | |
13 | --- | |
14 | * Added Open services count to Hosts list in WEB UI | |
15 | * Improved zsh integration | |
16 | * Added GTK3 interface prototype | |
17 | * Added plugin detection through report name | |
18 | * Fixed an error in wcscan script | |
19 | * Fixed nikto plugin | |
20 | * Fixed openvas plugin | |
11 | 21 | |
12 | 22 | Apr 04, 2016 |
13 | 23 | --- |
15 | 15 | from tornado.httpserver import HTTPServer |
16 | 16 | from tornado.ioloop import IOLoop |
17 | 17 | |
18 | from plugins.core import PluginControllerForApi | |
19 | 18 | from model.visitor import VulnsLookupVisitor |
20 | 19 | |
21 | 20 | import utils.logs as logger |
40 | 39 | _http_server.stop() |
41 | 40 | |
42 | 41 | |
43 | def startAPIs(plugin_manager, model_controller, mapper_manager, hostname, port): | |
42 | def startAPIs(plugin_controller, model_controller, hostname, port): | |
44 | 43 | global _rest_controllers |
45 | 44 | global _http_server |
46 | _rest_controllers = [PluginControllerAPI(plugin_manager, mapper_manager), ModelControllerAPI(model_controller)] | |
45 | _rest_controllers = [PluginControllerAPI(plugin_controller), ModelControllerAPI(model_controller)] | |
47 | 46 | |
48 | 47 | app = Flask('APISController') |
49 | 48 | |
91 | 90 | def badRequest(self, message): |
92 | 91 | error = 400 |
93 | 92 | return jsonify(error=error, |
94 | message=message), error | |
93 | message=message) | |
95 | 94 | |
96 | 95 | def noContent(self, message): |
97 | 96 | code = 204 |
98 | 97 | return jsonify(code=code, |
99 | message=message), code | |
98 | message=message) | |
100 | 99 | |
101 | 100 | def ok(self, message): |
102 | 101 | code = 200 |
286 | 285 | |
287 | 286 | |
288 | 287 | class PluginControllerAPI(RESTApi): |
289 | def __init__(self, plugin_manager, mapper_manager): | |
290 | self.plugin_controller = PluginControllerForApi( | |
291 | "PluginController", | |
292 | plugin_manager.getPlugins(), | |
293 | mapper_manager) | |
288 | def __init__(self, plugin_controller): | |
289 | self.plugin_controller = plugin_controller | |
294 | 290 | |
295 | 291 | def getRoutes(self): |
296 | 292 | routes = [] |
297 | 293 | routes.append(Route(path='/cmd/input', |
298 | view_func=self.postCmdInput, | |
299 | methods=['POST'])) | |
294 | view_func=self.postCmdInput, | |
295 | methods=['POST'])) | |
300 | 296 | routes.append(Route(path='/cmd/output', |
301 | view_func=self.postCmdOutput, | |
302 | methods=['POST'])) | |
297 | view_func=self.postCmdOutput, | |
298 | methods=['POST'])) | |
303 | 299 | routes.append(Route(path='/cmd/active-plugins', |
304 | view_func=self.clearActivePlugins, | |
305 | methods=['DELETE'])) | |
300 | view_func=self.clearActivePlugins, | |
301 | methods=['DELETE'])) | |
306 | 302 | return routes |
307 | 303 | |
308 | def pluginAvailable(self, new_cmd, output_file): | |
304 | def pluginAvailable(self, plugin, cmd): | |
309 | 305 | code = 200 |
310 | 306 | return jsonify(code=code, |
311 | cmd=new_cmd, | |
312 | custom_output_file=output_file) | |
307 | cmd=cmd, | |
308 | plugin=plugin) | |
313 | 309 | |
314 | 310 | def postCmdInput(self): |
315 | 311 | json_data = request.get_json() |
316 | 312 | if 'cmd' in json_data.keys(): |
317 | cmd = json_data.get('cmd') | |
318 | has_plugin, new_cmd, output_file = self.plugin_controller.\ | |
319 | processCommandInput(cmd) | |
320 | if has_plugin: | |
321 | return self.pluginAvailable(new_cmd, output_file) | |
322 | return self.noContent("no plugin available for cmd") | |
323 | #cmd not sent, bad request | |
324 | return self.badRequest("cmd parameter not sent") | |
313 | if 'pid' in json_data.keys(): | |
314 | if 'pwd' in json_data.keys(): | |
315 | try: | |
316 | cmd = base64.b64decode(json_data.get('cmd')) | |
317 | pwd = base64.b64decode(json_data.get('pwd')) | |
318 | except: | |
319 | cmd = '' | |
320 | pwd = '' | |
321 | pid = json_data.get('pid') | |
322 | plugin, new_cmd = self.plugin_controller.\ | |
323 | processCommandInput(pid, cmd, pwd) | |
324 | if plugin: | |
325 | return self.pluginAvailable(plugin, new_cmd) | |
326 | else: | |
327 | return self.noContent("no plugin available for cmd") | |
328 | else: | |
329 | return self.badRequest("pwd parameter not sent") | |
330 | else: | |
331 | return self.badRequest("pid parameter not sent") | |
332 | else: | |
333 | return self.badRequest("cmd parameter not sent") | |
334 | ||
335 | ||
325 | 336 | |
326 | 337 | def postCmdOutput(self): |
327 | 338 | json_data = request.get_json() |
328 | if 'cmd' in json_data.keys(): | |
339 | if 'pid' in json_data.keys(): | |
329 | 340 | if 'output' in json_data.keys(): |
330 | cmd = json_data.get('cmd') | |
331 | output = base64.b64decode(json_data.get('output')) | |
332 | if self.plugin_controller.onCommandFinished(cmd, output): | |
333 | return self.ok("output successfully sent to plugin") | |
334 | return self.badRequest("output received but no active plugin") | |
341 | if 'exit_code' in json_data.keys(): | |
342 | pid = json_data.get('pid') | |
343 | output = base64.b64decode(json_data.get('output')) | |
344 | exit_code = json_data.get('exit_code') | |
345 | if self.plugin_controller.onCommandFinished( | |
346 | pid, exit_code, output): | |
347 | return self.ok("output successfully sent to plugin") | |
348 | return self.badRequest( | |
349 | "output received but no active plugin") | |
350 | return self.badRequest("exit_code parameter not sent") | |
335 | 351 | return self.badRequest("output parameter not sent") |
336 | return self.badRequest("cmd parameter not sent") | |
352 | return self.badRequest("pid parameter not sent") | |
337 | 353 | |
338 | 354 | def clearActivePlugins(self): |
339 | 355 | self.plugin_controller.clearActivePlugins() |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>1.0.18</version> | |
4 | <version>1.0.19</version> | |
5 | 5 | <debug_status>0</debug_status> |
6 | 6 | <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font> |
7 | 7 | <home_path>~/</home_path> |
12 | 12 | CONST_FARADAY_QTRC_PATH = 'deps/qtrc' |
13 | 13 | CONST_FARADAY_IMAGES = 'images/' |
14 | 14 | CONST_FARADAY_LOGS_PATH = 'logs/' |
15 | CONST_FARADAY_FOLDER_LIST = [ "config", "data", "images", | |
15 | CONST_FARADAY_FOLDER_LIST = [ "config", "data", "images", | |
16 | 16 | "persistence", "plugins", |
17 | 17 | "report", "temp", "zsh", "logs" ] |
18 | 18 | |
23 | 23 | CONST_FARADAY_QTRC_BACKUP = '~/.qt/.qtrc_faraday.bak' |
24 | 24 | CONST_FARADAY_ZSHRC = "zsh/.zshrc" |
25 | 25 | CONST_FARADAY_ZSH_FARADAY = "zsh/faraday.zsh" |
26 | CONST_FARADAY_ZSH_PLUGIN = "zsh/plugin_controller_client.py" | |
26 | CONST_FARADAY_ZSH_OUTPUT_PATH = "zsh/output" | |
27 | 27 | CONST_FARADAY_BASE_CFG = "config/default.xml" |
28 | 28 | CONST_FARADAY_USER_CFG = "config/config.xml" |
29 | 29 | CONST_FARADAY_LIB_HELPERS = "shell/core/_helpers.so" |
53 | 53 | FARADAY_USER_ZSHRC = os.path.join(FARADAY_USER_HOME, CONST_FARADAY_ZSHRC) |
54 | 54 | FARADAY_USER_ZSH_PATH = os.path.join(FARADAY_USER_HOME, CONST_ZSH_PATH) |
55 | 55 | FARADAY_BASE_ZSH = os.path.join(FARADAY_BASE, CONST_FARADAY_ZSH_FARADAY) |
56 | FARADAY_BASE_ZSH_PLUGIN = os.path.join(FARADAY_BASE, | |
57 | CONST_FARADAY_ZSH_PLUGIN) | |
58 | 56 | |
59 | 57 | USER_QT = os.path.expanduser(CONST_USER_QT_PATH) |
60 | 58 | USER_QTRC = os.path.expanduser(CONST_USER_QTRC_PATH) |
140 | 138 | |
141 | 139 | parser.add_argument('--gui', action="store", dest="gui", |
142 | 140 | default="qt3", |
143 | help="Select interface to start faraday. Default = qt3") | |
141 | help="Select interface to start faraday. Supported values are " | |
142 | "qt3 (deprecated), gtk and 'no' (no GUI at all). Defaults to qt3") | |
144 | 143 | |
145 | 144 | parser.add_argument('--cli', action="store_true", |
146 | 145 | dest="cli", |
299 | 298 | |
300 | 299 | logger.info("All done. Opening environment.") |
301 | 300 | #TODO: Handle args in CONF and send only necessary ones. |
302 | # Force OSX to run no gui | |
303 | if sys.platform == "darwin": | |
304 | args.gui = "no-gui" | |
305 | 301 | |
306 | 302 | main_app = MainApplication(args) |
307 | 303 | |
399 | 395 | f.seek(0, 0) |
400 | 396 | f.write('ZDOTDIR=$OLDZDOTDIR' + '\n' + content) |
401 | 397 | with open(FARADAY_USER_ZSHRC, "a") as f: |
402 | f.write("source %s" % FARADAY_BASE_ZSH) | |
398 | f.write("source \"%s\"" % FARADAY_BASE_ZSH) | |
403 | 399 | shutil.copy(FARADAY_BASE_ZSH, FARADAY_USER_ZSH_PATH) |
404 | shutil.copy(FARADAY_BASE_ZSH_PLUGIN, FARADAY_USER_ZSH_PATH) | |
405 | ||
406 | 400 | |
407 | 401 | def setupXMLConfig(): |
408 | 402 | """Checks user configuration file status. |
0 | #!/usr/bin/python2.7 | |
1 | # -*- coding: utf-8 -*- | |
2 | ''' | |
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | ||
7 | ''' | |
8 | ||
9 | import os | |
10 | import sys | |
11 | ||
12 | try: | |
13 | import gi | |
14 | except ImportError as e: | |
15 | print ("You are missing Gobject Instrospection. Please install " | |
16 | "version 3.14 or above") | |
17 | sys.exit(1) | |
18 | ||
19 | try: | |
20 | gi.require_version('Gtk', '3.0') | |
21 | gi.require_version('Vte', '2.91') | |
22 | except ValueError: | |
23 | print ("WARNING: You don't seem to have installed the recommended versions" | |
24 | " of GTK and VTE. Check install of VTE 2.91 and GTK+3") | |
25 | ||
26 | try: | |
27 | from gi.repository import Gio, Gtk, GdkPixbuf, Vte, GLib, GObject, Gdk | |
28 | except ImportError as e: | |
29 | print ("You are missing some of the required dependencies. " | |
30 | "Check that you have GTK+3 and Vte installed.") | |
31 | sys.exit(1) | |
32 | ||
33 | ||
34 | import model.guiapi | |
35 | import model.api | |
36 | import model.log | |
37 | ||
38 | from gui.gui_app import FaradayUi | |
39 | from config.configuration import getInstanceConfiguration | |
40 | from utils.logs import getLogger | |
41 | from appwindow import AppWindow | |
42 | ||
43 | from dialogs import PreferenceWindowDialog | |
44 | from dialogs import NewWorkspaceDialog | |
45 | from dialogs import PluginOptionsDialog | |
46 | from dialogs import NotificationsDialog | |
47 | from dialogs import aboutDialog | |
48 | from dialogs import helpDialog | |
49 | ||
50 | from mainwidgets import Sidebar | |
51 | from mainwidgets import ConsoleLog | |
52 | from mainwidgets import Terminal | |
53 | from mainwidgets import Statusbar | |
54 | ||
55 | from gui.loghandler import GUIHandler | |
56 | from utils.logs import addHandler | |
57 | ||
58 | CONF = getInstanceConfiguration() | |
59 | ||
60 | class GuiApp(Gtk.Application, FaradayUi): | |
61 | """ | |
62 | Creates the application and has the necesary callbacks to FaradayUi | |
63 | Right now handles by itself only the menu, everything is else is | |
64 | appWindow's resposibility as far as the initial UI goes. | |
65 | The dialogs are found inside the dialogs module | |
66 | """ | |
67 | ||
68 | def __init__(self, model_controller, plugin_manager, workspace_manager, | |
69 | plugin_controller): | |
70 | ||
71 | FaradayUi.__init__(self, | |
72 | model_controller, | |
73 | plugin_manager, | |
74 | workspace_manager, | |
75 | plugin_controller) | |
76 | ||
77 | Gtk.Application.__init__(self, application_id="org.infobyte.faraday", | |
78 | flags=Gio.ApplicationFlags.FLAGS_NONE) | |
79 | ||
80 | icons = CONF.getImagePath() + "icons/" | |
81 | self.icon = GdkPixbuf.Pixbuf.new_from_file(icons + "faraday_icon.png") | |
82 | self.window = None | |
83 | ||
84 | def getMainWindow(self): | |
85 | """Returns the main window. This is none only at the | |
86 | the startup, the GUI will create one as soon as do_activate() is called | |
87 | """ | |
88 | return self.window | |
89 | ||
90 | def createWorkspace(self, name, description="", w_type=""): | |
91 | """Pretty much copy/pasted from the QT3 GUI. | |
92 | Uses the instance of workspace manager passed into __init__ to | |
93 | get all the workspaces names and see if they don't clash with | |
94 | the one the user wrote. If everything's fine, it saves the new | |
95 | workspace and returns True. If something went wrong, return False""" | |
96 | ||
97 | if name in self.getWorkspaceManager().getWorkspacesNames(): | |
98 | ||
99 | model.api.log("A workspace with name %s already exists" | |
100 | % name, "ERROR") | |
101 | status = True | |
102 | else: | |
103 | model.api.log("Creating workspace '%s'" % name) | |
104 | model.api.devlog("Looking for the delegation class") | |
105 | manager = self.getWorkspaceManager() | |
106 | try: | |
107 | w = manager.createWorkspace(name, description, | |
108 | manager.namedTypeToDbType(w_type)) | |
109 | CONF.setLastWorkspace(w.name) | |
110 | CONF.saveConfig() | |
111 | status = True | |
112 | except Exception as e: | |
113 | status = False | |
114 | model.guiapi.notification_center.showDialog(str(e)) | |
115 | ||
116 | return status | |
117 | ||
118 | def removeWorkspace(self, button, ws_name): | |
119 | """Removes a workspace. If the workspace to be deleted is the one | |
120 | selected, it moves you first to the default. The clears and refreshes | |
121 | sidebar""" | |
122 | ||
123 | model.api.log("Removing Workspace: %s" % ws_name) | |
124 | if CONF.getLastWorkspace() == ws_name: | |
125 | self.openDefaultWorkspace() | |
126 | self.getWorkspaceManager().removeWorkspace(ws_name) | |
127 | self.sidebar.clearSidebar() | |
128 | self.sidebar.refreshSidebar() | |
129 | ||
130 | def do_startup(self): | |
131 | """ | |
132 | GTK calls this method after Gtk.Application.run() | |
133 | Creates instances of the sidebar, terminal, console log and | |
134 | statusbar to be added to the app window. | |
135 | Sets up necesary acttions on menu and toolbar buttons | |
136 | Also reads the .xml file from menubar.xml | |
137 | """ | |
138 | Gtk.Application.do_startup(self) # deep GTK magic | |
139 | ||
140 | self.sidebar = Sidebar(self.workspace_manager, | |
141 | self.changeWorkspace, | |
142 | self.removeWorkspace, | |
143 | CONF.getLastWorkspace()) | |
144 | ||
145 | self.terminal = Terminal(CONF) | |
146 | self.console_log = ConsoleLog() | |
147 | self.statusbar = Statusbar(self.on_click_notifications) | |
148 | self.notificationsModel = Gtk.ListStore(str) | |
149 | ||
150 | action = Gio.SimpleAction.new("about", None) | |
151 | action.connect("activate", self.on_about) | |
152 | self.add_action(action) | |
153 | ||
154 | action = Gio.SimpleAction.new("help", None) | |
155 | action.connect("activate", self.on_help) | |
156 | self.add_action(action) | |
157 | ||
158 | action = Gio.SimpleAction.new("quit", None) | |
159 | action.connect("activate", self.on_quit) | |
160 | self.add_action(action) | |
161 | ||
162 | action = Gio.SimpleAction.new("preferences", None) | |
163 | action.connect("activate", self.on_preferences) | |
164 | self.add_action(action) | |
165 | ||
166 | action = Gio.SimpleAction.new("pluginOptions", None) | |
167 | action.connect("activate", self.on_pluginOptions) | |
168 | self.add_action(action) | |
169 | ||
170 | action = Gio.SimpleAction.new("new", None) | |
171 | action.connect("activate", self.on_new_button) | |
172 | self.add_action(action) | |
173 | ||
174 | action = Gio.SimpleAction.new("new_terminal") # new terminal = new tab | |
175 | action.connect("activate", self.on_new_terminal_button) | |
176 | self.add_action(action) | |
177 | ||
178 | dirname = os.path.dirname(os.path.abspath(__file__)) | |
179 | builder = Gtk.Builder.new_from_file(dirname + '/menubar.xml') | |
180 | builder.connect_signals(self) | |
181 | appmenu = builder.get_object('appmenu') | |
182 | self.set_app_menu(appmenu) | |
183 | helpMenu = builder.get_object('Help') | |
184 | self.set_menubar(helpMenu) | |
185 | ||
186 | def do_activate(self): | |
187 | """If there's no window, create one and present it (show it to user). | |
188 | If there's a window, just present it""" | |
189 | ||
190 | # We only allow a single window and raise any existing ones | |
191 | if not self.window: | |
192 | # Windows are associated with the application | |
193 | # when the last one is closed the application shuts down | |
194 | self.window = AppWindow(self.sidebar, | |
195 | self.terminal, | |
196 | self.console_log, | |
197 | self.statusbar, | |
198 | application=self, | |
199 | title="Faraday") | |
200 | ||
201 | self.window.set_icon(self.icon) | |
202 | self.window.present() | |
203 | ||
204 | self.loghandler = GUIHandler() | |
205 | model.guiapi.setMainApp(self) | |
206 | addHandler(self.loghandler) | |
207 | self.loghandler.registerGUIOutput(self.window) | |
208 | ||
209 | notifier = model.log.getNotifier() | |
210 | notifier.widget = self.window | |
211 | model.guiapi.notification_center.registerWidget(self.window) | |
212 | ||
213 | def postEvent(self, receiver, event): | |
214 | if receiver is None: | |
215 | receiver = self.getMainWindow() | |
216 | if event.type() == 3131: | |
217 | receiver.emit("new_log", event.text) | |
218 | if event.type() == 5100: | |
219 | self.notificationsModel.prepend([event.change.getMessage()]) | |
220 | receiver.emit("new_notif") | |
221 | if event.type() == 3132: | |
222 | dialog_text = event.text | |
223 | dialog = Gtk.MessageDialog(self.window, 0, | |
224 | Gtk.MessageType.INFO, | |
225 | Gtk.ButtonsType.OK, | |
226 | dialog_text) | |
227 | dialog.run() | |
228 | dialog.destroy() | |
229 | ||
230 | def on_about(self, action, param): | |
231 | """ Defines what happens when you press 'about' on the menu""" | |
232 | ||
233 | about_dialog = aboutDialog(self.window) | |
234 | about_dialog.run() | |
235 | about_dialog.destroy() | |
236 | ||
237 | def on_help(self, action, param): | |
238 | """Defines what happens when user press 'help' on the menu""" | |
239 | ||
240 | help_dialog = helpDialog(self.window) | |
241 | help_dialog.run() | |
242 | help_dialog.destroy() | |
243 | ||
244 | def on_preferences(self, action, param): | |
245 | """Defines what happens when you press 'preferences' on the menu. | |
246 | Sends as a callback reloadWsManager, so if the user actually | |
247 | changes her Couch URL, the sidebar will reload reflecting the | |
248 | new workspaces available""" | |
249 | ||
250 | preference_window = PreferenceWindowDialog(self.reloadWorkspaces, | |
251 | self.window) | |
252 | preference_window.show_all() | |
253 | ||
254 | def reloadWorkspaces(self): | |
255 | """Used in conjunction with on_preferences: close workspace, | |
256 | resources the workspaces available, clears the sidebar of the old | |
257 | workspaces and injects all the new ones in there too""" | |
258 | self.workspace_manager.closeWorkspace() | |
259 | self.workspace_manager.resource() | |
260 | self.sidebar.clearSidebar() | |
261 | self.sidebar.refreshSidebar() | |
262 | ||
263 | def on_pluginOptions(self, action, param): | |
264 | """Defines what happens when you press "Plugins" on the menu""" | |
265 | pluginsOption_window = PluginOptionsDialog(self.plugin_manager, | |
266 | self.window) | |
267 | pluginsOption_window.show_all() | |
268 | ||
269 | def on_new_button(self, action, params): | |
270 | "Defines what happens when you press the 'new' button on the toolbar" | |
271 | new_workspace_dialog = NewWorkspaceDialog(self.createWorkspace, | |
272 | self.workspace_manager, | |
273 | self.sidebar, self.window) | |
274 | new_workspace_dialog.show_all() | |
275 | ||
276 | def on_new_terminal_button(self, action, params): | |
277 | """When the user clicks on the new_terminal button, creates a new | |
278 | instance of the Terminal and tells the window to add it as a new tab | |
279 | for the notebook""" | |
280 | new_terminal = Terminal(CONF) | |
281 | the_new_terminal = new_terminal.getTerminal() | |
282 | AppWindow.new_tab(self.window, the_new_terminal) | |
283 | ||
284 | def on_click_notifications(self, button): | |
285 | """Defines what happens when the user clicks on the notifications | |
286 | button.""" | |
287 | ||
288 | notifications_view = Gtk.TreeView(self.notificationsModel) | |
289 | renderer = Gtk.CellRendererText() | |
290 | column = Gtk.TreeViewColumn("Notifications", renderer, text=0) | |
291 | notifications_view.append_column(column) | |
292 | notifications_dialog = NotificationsDialog(notifications_view, | |
293 | self.delete_notifications, | |
294 | self.window) | |
295 | notifications_dialog.show_all() | |
296 | ||
297 | def delete_notifications(self): | |
298 | self.notificationsModel.clear() | |
299 | self.window.emit("clear_notifications") | |
300 | ||
301 | def changeWorkspace(self, selection=None): | |
302 | """Pretty much copy/pasted from QT3 GUI. | |
303 | Selection is actually used nowhere, but the connect function is | |
304 | Sidebar passes it as an argument so well there it is""" | |
305 | ||
306 | tree_model, treeiter = selection.get_selected() | |
307 | workspaceName = tree_model[treeiter][0] | |
308 | ||
309 | try: | |
310 | ws = super(GuiApp, self).openWorkspace(workspaceName) | |
311 | except Exception as e: | |
312 | model.guiapi.notification_center.showDialog(str(e)) | |
313 | ws = self.openDefaultWorkspace() | |
314 | workspace = ws.name | |
315 | CONF.setLastWorkspace(workspace) | |
316 | CONF.saveConfig() | |
317 | return ws | |
318 | ||
319 | def run(self, args): | |
320 | """First method to run, as defined by FaradayUi. This method is | |
321 | mandatory""" | |
322 | ||
323 | workspace = args.workspace | |
324 | try: | |
325 | ws = super(GuiApp, self).openWorkspace(workspace) | |
326 | except Exception as e: | |
327 | getLogger(self).error( | |
328 | ("Your last workspace %s is not accessible, " | |
329 | "check configuration") % workspace) | |
330 | getLogger(self).error(str(e)) | |
331 | ws = self.openDefaultWorkspace() | |
332 | workspace = ws.name | |
333 | ||
334 | CONF.setLastWorkspace(workspace) | |
335 | CONF.saveConfig() | |
336 | Gtk.Application.run(self) | |
337 | ||
338 | def on_quit(self, action, param): | |
339 | self.quit() |
0 | #!/usr/bin/python2.7 | |
1 | # -*- coding: utf-8 -*- | |
2 | ''' | |
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | ||
7 | ''' | |
8 | import gi | |
9 | ||
10 | from config.configuration import getInstanceConfiguration | |
11 | ||
12 | gi.require_version('Gtk', '3.0') | |
13 | gi.require_version('Vte', '2.91') | |
14 | ||
15 | from gi.repository import GLib, Gio, Gtk, GObject, Gdk | |
16 | ||
17 | CONF = getInstanceConfiguration() | |
18 | ||
19 | ||
20 | class _IdleObject(GObject.GObject): | |
21 | """ | |
22 | Override GObject.GObject to always emit signals in the main thread | |
23 | by emmitting on an idle handler. Deep magic, do not touch unless | |
24 | you know what you are doing. | |
25 | """ | |
26 | def __init__(self): | |
27 | GObject.GObject.__init__(self) | |
28 | ||
29 | def emit(self, *args): | |
30 | GObject.idle_add(GObject.GObject.emit, self, *args) | |
31 | ||
32 | ||
33 | class AppWindow(Gtk.ApplicationWindow, _IdleObject): | |
34 | """The main window of the GUI. Draws the toolbar. | |
35 | Positions the terminal, sidebar, consolelog and statusbar received from | |
36 | the app and defined in the mainwidgets module""" | |
37 | ||
38 | __gsignals__ = { | |
39 | "new_log": (GObject.SIGNAL_RUN_FIRST, None, (str, )), | |
40 | "new_notif": (GObject.SIGNAL_RUN_FIRST, None, ()), | |
41 | "clear_notifications": (GObject.SIGNAL_RUN_FIRST, None, ()) | |
42 | } | |
43 | ||
44 | def __init__(self, sidebar, terminal, console_log, statusbar, | |
45 | *args, **kwargs): | |
46 | super(Gtk.ApplicationWindow, self).__init__(*args, **kwargs) | |
47 | ||
48 | # This will be in the windows group and have the "win" prefix | |
49 | glib_variant = GLib.Variant.new_boolean(False) | |
50 | max_action = Gio.SimpleAction.new_stateful("maximize", None, | |
51 | glib_variant) | |
52 | max_action.connect("change-state", self.on_maximize_toggle) | |
53 | self.add_action(max_action) | |
54 | ||
55 | self.sidebar = sidebar | |
56 | self.terminal = terminal | |
57 | self.log = console_log | |
58 | self.statusbar = statusbar | |
59 | ||
60 | self.icons = CONF.getImagePath() + "icons/" | |
61 | ||
62 | # sets up the clipboard | |
63 | self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) | |
64 | self.selection_clipboard = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY) | |
65 | ||
66 | # Keep it in sync with the actual state. Deep dark GTK magic | |
67 | self.connect("notify::is-maximized", | |
68 | lambda obj: | |
69 | max_action.set_state( | |
70 | GLib.Variant.new_boolean(obj.props.is_maximized))) | |
71 | ||
72 | # TOP BOX: TOOLBAR AND FILTER | |
73 | self.topBox = Gtk.Box() | |
74 | self.topBox.pack_start(self.create_toolbar(), True, True, 0) | |
75 | ||
76 | # SIDEBAR BOX | |
77 | self.sidebarBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
78 | self.sidebarBox.pack_start(self.sidebar.scrollableView, True, True, 0) | |
79 | self.sidebarBox.pack_start(self.sidebar.getButton(), False, False, 0) | |
80 | ||
81 | # TERMINAL BOX | |
82 | self.firstTerminalBox = self.terminalBox(self.terminal.getTerminal()) | |
83 | ||
84 | # MIDDLE BOX: NOTEBOOK AND SIDEBAR | |
85 | self.notebook = Gtk.Notebook() | |
86 | self.notebook.set_scrollable(True) | |
87 | self.notebook.append_page(self.firstTerminalBox, Gtk.Label("1")) | |
88 | ||
89 | self.middleBox = Gtk.Box() | |
90 | self.middleBox.pack_start(self.notebook, True, True, 0) | |
91 | self.middleBox.pack_start(self.sidebarBox, False, False, 0) | |
92 | ||
93 | # LOGGER BOX: THE LOGGER, DUH | |
94 | self.loggerBox = Gtk.Box() | |
95 | self.loggerBox.pack_start(self.log.getLogger(), True, True, 0) | |
96 | ||
97 | # NOTIFACTION BOX: THE BUTTON TO ACCESS NOTIFICATION DIALOG | |
98 | self.notificationBox = Gtk.Box() | |
99 | self.notificationBox.pack_start(self.statusbar.button, True, True, 0) | |
100 | ||
101 | # MAINBOX: THE BIGGER BOX FOR ALL THE LITTLE BOXES | |
102 | self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
103 | self.mainBox.pack_start(self.topBox, False, False, 0) | |
104 | self.mainBox.pack_start(self.middleBox, True, True, 0) | |
105 | self.mainBox.pack_start(self.loggerBox, False, False, 0) | |
106 | self.mainBox.pack_end(self.notificationBox, False, False, 0) | |
107 | ||
108 | remove_terminal_icon = Gtk.Image.new_from_file(self.icons + "exit.png") | |
109 | remove_terminal_button = Gtk.Button() | |
110 | remove_terminal_button.set_tooltip_text("Delete current tab") | |
111 | remove_terminal_button.connect("clicked", self.delete_tab) | |
112 | remove_terminal_button.set_image(remove_terminal_icon) | |
113 | remove_terminal_button.set_relief(Gtk.ReliefStyle.NONE) | |
114 | remove_terminal_button.show() | |
115 | at_end = Gtk.PackType.END | |
116 | ||
117 | self.notebook.set_action_widget(remove_terminal_button, at_end) | |
118 | ||
119 | self.add(self.mainBox) | |
120 | self.tab_number = 0 # 0 indexed, even when it shows 1 to the user | |
121 | ||
122 | self.show_all() | |
123 | ||
124 | def terminalBox(self, terminal): | |
125 | """Given a terminal, creates an EventBox for the Box that has as a | |
126 | children said terminal""" | |
127 | eventTerminalBox = Gtk.EventBox() | |
128 | terminalBox = Gtk.Box() | |
129 | terminalBox.pack_start(terminal, True, True, 0) | |
130 | eventTerminalBox.connect("button_press_event", self.right_click) | |
131 | eventTerminalBox.add(terminalBox) | |
132 | return eventTerminalBox | |
133 | ||
134 | def right_click(self, eventbox, event): | |
135 | """Defines the menu created when a user rightclicks on the | |
136 | terminal eventbox""" | |
137 | menu = Gtk.Menu() | |
138 | copy = Gtk.MenuItem("Copy") | |
139 | paste = Gtk.MenuItem("Paste") | |
140 | menu.append(paste) | |
141 | menu.append(copy) | |
142 | ||
143 | # TODO: make accelerators for copy paste work. add accel for paste | |
144 | # accelgroup = Gtk.AccelGroup() | |
145 | # self.add_accel_group(accelgroup) | |
146 | # accellabel = Gtk.AccelLabel("Copy/Paste") | |
147 | # accellabel.set_hexpand(True) | |
148 | # copy.add_accelerator("activate", | |
149 | # accelgroup, | |
150 | # Gdk.keyval_from_name("c"), | |
151 | # Gdk.ModifierType.SHIFT_MASK | | |
152 | # Gdk.ModifierType.CONTROL_MASK, | |
153 | # Gtk.AccelFlags.VISIBLE) | |
154 | ||
155 | copy.connect("activate", self.copy_text) | |
156 | paste.connect("activate", self.paste_text) | |
157 | ||
158 | copy.show() | |
159 | paste.show() | |
160 | menu.popup(None, None, None, None, event.button, event.time) | |
161 | ||
162 | def copy_text(self, button): | |
163 | """What happens when the user copies text""" | |
164 | content = self.selection_clipboard.wait_for_text() | |
165 | self.clipboard.set_text(content, -1) | |
166 | ||
167 | def paste_text(self, button): | |
168 | """What happens when the user pastes text""" | |
169 | currentTerminal = self.getCurrentFocusedTerminal() | |
170 | currentTerminal.paste_clipboard() | |
171 | ||
172 | def getFocusedTab(self): | |
173 | """Return the focused tab""" | |
174 | return self.notebook.get_current_page() | |
175 | ||
176 | def getCurrentFocusedTerminal(self): | |
177 | """Returns the current focused terminal""" | |
178 | ||
179 | # the focused terminal is the only children of the notebook | |
180 | # thas has only children an event box that has as only children | |
181 | # the terminal. Yeah, I know. | |
182 | ||
183 | currentTab = self.getFocusedTab() | |
184 | currentEventBox = self.notebook.get_children()[currentTab] | |
185 | currentBox = currentEventBox.get_children()[0] | |
186 | currentTerminal = currentBox.get_children()[0] | |
187 | return currentTerminal | |
188 | ||
189 | def do_new_log(self, text): | |
190 | """To be used on a new_log signal. Calls a method on log to append | |
191 | to it""" | |
192 | self.log.customEvent(text) | |
193 | ||
194 | def do_clear_notifications(self): | |
195 | "On clear_notifications signal, it will return the button label to 0" | |
196 | self.statusbar.button.set_label("0") | |
197 | ||
198 | def do_new_notif(self): | |
199 | """On a new notification, increment the button label by one""" | |
200 | self.statusbar.inc_button_label() | |
201 | ||
202 | def getLogConsole(self): | |
203 | """Returns the LogConsole. Needed by the GUIHandler logger""" | |
204 | return self.log | |
205 | ||
206 | def on_maximize_toggle(self, action, value): | |
207 | """Defines what happens when the window gets the signal to maximize""" | |
208 | action.set_state(value) | |
209 | if value.get_boolean(): | |
210 | self.maximize() | |
211 | else: | |
212 | self.unmaximize() | |
213 | ||
214 | def refreshSidebar(self): | |
215 | """Call the refresh method on sidebar. It will append new workspaces, | |
216 | but it will *NOT* delete workspaces not found anymore in the current | |
217 | ws anymore""" | |
218 | self.sidebar.refresh() | |
219 | ||
220 | def create_toolbar(self): | |
221 | """ Creates toolbar with an open and new button, getting the icons | |
222 | from the stock. The method by which it does this is deprecated, | |
223 | this could be improved""" | |
224 | ||
225 | toolbar = Gtk.Toolbar() | |
226 | toolbar.set_hexpand(True) | |
227 | toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) | |
228 | icons = self.icons | |
229 | ||
230 | # new_from_stock is deprecated, but should work fine for now | |
231 | new_button_icon = Gtk.Image.new_from_file(icons + "sync.png") | |
232 | new_terminal_icon = Gtk.Image.new_from_file(icons + "newshell.png") | |
233 | toggle_log_icon = Gtk.Image.new_from_file(icons + "debug.png") | |
234 | ||
235 | new_terminal_button = Gtk.ToolButton.new(new_terminal_icon, None) | |
236 | new_terminal_button.set_tooltip_text("Create a new tab") | |
237 | new_terminal_button.set_action_name('app.new_terminal') | |
238 | toolbar.insert(new_terminal_button, 0) | |
239 | ||
240 | new_button = Gtk.ToolButton.new(new_button_icon, None) | |
241 | new_button.set_tooltip_text("Create a new workspace") | |
242 | toolbar.insert(new_button, 1) | |
243 | new_button.set_action_name('app.new') | |
244 | ||
245 | toggle_log_button = Gtk.ToggleToolButton.new() | |
246 | toggle_log_button.set_icon_widget(toggle_log_icon) | |
247 | toggle_log_button.set_active(True) # log enabled by default | |
248 | toggle_log_button.set_tooltip_text("Toggle log console") | |
249 | toggle_log_button.connect("clicked", self.toggle_log) | |
250 | toolbar.insert(toggle_log_button, 2) | |
251 | ||
252 | return toolbar | |
253 | ||
254 | def new_tab(self, new_terminal): | |
255 | """The on_new_terminal_button redirects here. Tells the window | |
256 | to create pretty much a clone of itself when the user wants a new | |
257 | tab""" | |
258 | ||
259 | self.tab_number += 1 | |
260 | tab_number = self.tab_number | |
261 | pageN = self.terminalBox(new_terminal) | |
262 | self.notebook.append_page(pageN, Gtk.Label(str(tab_number+1))) | |
263 | self.show_all() | |
264 | ||
265 | def delete_tab(self, button): | |
266 | """Deletes the current tab""" | |
267 | current_page = self.notebook.get_current_page() | |
268 | self.notebook.remove_page(current_page) | |
269 | self.reorder_tab_names() | |
270 | ||
271 | def reorder_tab_names(self): | |
272 | """When a tab is deleted, all other tabs must be renamed to reacomodate | |
273 | the numbers""" | |
274 | # Tabs are zero indexed, but their labels start at one | |
275 | ||
276 | number_of_tabs = self.notebook.get_n_pages() | |
277 | for n in range(number_of_tabs): | |
278 | tab = self.notebook.get_nth_page(n) | |
279 | self.notebook.set_tab_label_text(tab, str(n+1)) | |
280 | self.tab_number = number_of_tabs-1 | |
281 | ||
282 | def toggle_log(self, button): | |
283 | """Reverses the visibility status of the loggerbox""" | |
284 | current_state = self.loggerBox.is_visible() | |
285 | self.loggerBox.set_visible(not current_state) |
0 | #!/usr/bin/python2.7 | |
1 | # -*- coding: utf-8 -*- | |
2 | ''' | |
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | ||
7 | ''' | |
8 | import gi | |
9 | import re | |
10 | ||
11 | gi.require_version('Gtk', '3.0') | |
12 | ||
13 | from gi.repository import Gtk, GdkPixbuf, Gdk | |
14 | from persistence.persistence_managers import CouchDbManager | |
15 | from utils.common import checkSSL | |
16 | from config.configuration import getInstanceConfiguration | |
17 | ||
18 | ||
19 | CONF = getInstanceConfiguration() | |
20 | ||
21 | """This could probably be made much better with just a little effort. | |
22 | It'd be probably a good idea to make a super class Dialog from which | |
23 | all the dialogs inherit from with the common methods used (particularly the | |
24 | OK and Cancel buttons). Good starting point if we continue on with the idea | |
25 | of using GTK. | |
26 | ||
27 | Update: so it seems like Gtk actually already provides a Gtk.Dialog class | |
28 | which would seem practical. All dialogs are already made and it is a | |
29 | convenience class only, but if there's need to add more, it's a good | |
30 | thing to know""" | |
31 | ||
32 | ||
33 | class PreferenceWindowDialog(Gtk.Window): | |
34 | """Sets up a preference dialog with basically nothing more than a | |
35 | label, a text entry to input your CouchDB IP and a couple of buttons. | |
36 | Takes a callback function to the mainapp so that it can refresh the | |
37 | workspace list and information""" | |
38 | ||
39 | def __init__(self, callback, parent): | |
40 | Gtk.Window.__init__(self, title="Preferences") | |
41 | self.parent = parent | |
42 | self.set_size_request(400, 100) | |
43 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) | |
44 | self.set_transient_for(parent) | |
45 | self.timeout_id = None | |
46 | self.reloadWorkspaces = callback | |
47 | ||
48 | vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) | |
49 | self.add(vbox) | |
50 | ||
51 | self.label = Gtk.Label() | |
52 | self.label.set_text("Your Couch IP") | |
53 | vbox.pack_start(self.label, True, False, 0) | |
54 | ||
55 | couch_uri = CONF.getCouchURI() | |
56 | self.entry = Gtk.Entry() | |
57 | text = couch_uri if couch_uri else "http://127.0.0.1:5050" | |
58 | self.entry.set_text(text) | |
59 | vbox.pack_start(self.entry, True, False, 0) | |
60 | ||
61 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) | |
62 | vbox.pack_end(hbox, False, True, 0) | |
63 | ||
64 | self.OK_button = Gtk.Button.new_with_label("OK") | |
65 | self.OK_button.connect("clicked", self.on_click_OK) | |
66 | ||
67 | hbox.pack_start(self.OK_button, False, True, 0) | |
68 | ||
69 | self.cancel_button = Gtk.Button.new_with_label("Cancel") | |
70 | self.cancel_button.connect("clicked", self.on_click_cancel) | |
71 | hbox.pack_end(self.cancel_button, False, True, 0) | |
72 | ||
73 | def on_click_OK(self, button): | |
74 | """Defines what happens when user clicks OK button""" | |
75 | repourl = self.entry.get_text() | |
76 | if not CouchDbManager.testCouch(repourl): | |
77 | errorDialog(self, "The provided URL is not valid", | |
78 | "Are you sure CouchDB is running?") | |
79 | elif repourl.startswith("https://"): | |
80 | if not checkSSL(repourl): | |
81 | errorDialog("The SSL certificate validation has failed") | |
82 | else: | |
83 | CONF.setCouchUri(repourl) | |
84 | CONF.saveConfig() | |
85 | self.reloadWorkspaces() | |
86 | self.destroy() | |
87 | ||
88 | def on_click_cancel(self, button): | |
89 | self.destroy() | |
90 | ||
91 | ||
92 | class NewWorkspaceDialog(Gtk.Window): | |
93 | """Sets up the New Workspace Dialog, where the user can set a name, | |
94 | a description and a type for a new workspace. Also checks that the | |
95 | those attributes don't correspond to an existing workspace""" | |
96 | ||
97 | def __init__(self, callback, workspace_manager, sidebar, parent): | |
98 | ||
99 | Gtk.Window.__init__(self, title="Create New Workspace") | |
100 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) | |
101 | self.set_transient_for(parent) | |
102 | self.set_size_request(200, 200) | |
103 | self.timeout_id = None | |
104 | self.callback = callback | |
105 | self.sidebar = sidebar | |
106 | ||
107 | self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) | |
108 | ||
109 | self.nameBox = Gtk.Box(spacing=6) | |
110 | self.name_label = Gtk.Label() | |
111 | self.name_label.set_text("Name: ") | |
112 | self.name_entry = Gtk.Entry() | |
113 | self.nameBox.pack_start(self.name_label, False, False, 10) | |
114 | self.nameBox.pack_end(self.name_entry, True, True, 10) | |
115 | self.nameBox.show() | |
116 | ||
117 | self.descrBox = Gtk.Box(spacing=6) | |
118 | self.descr_label = Gtk.Label() | |
119 | self.descr_label.set_text("Description: ") | |
120 | self.descr_entry = Gtk.Entry() | |
121 | self.descrBox.pack_start(self.descr_label, False, False, 10) | |
122 | self.descrBox.pack_end(self.descr_entry, True, True, 10) | |
123 | self.descrBox.show() | |
124 | ||
125 | self.typeBox = Gtk.Box(spacing=6) | |
126 | self.type_label = Gtk.Label() | |
127 | self.type_label.set_text("Type: ") | |
128 | self.comboBox = Gtk.ComboBoxText() | |
129 | for w in workspace_manager.getAvailableWorkspaceTypes(): | |
130 | self.comboBox.append_text(w) | |
131 | self.typeBox.pack_start(self.type_label, False, False, 10) | |
132 | self.typeBox.pack_end(self.comboBox, True, True, 10) | |
133 | self.typeBox.show() | |
134 | ||
135 | self.buttonBox = Gtk.Box(spacing=6) | |
136 | self.OK_button = Gtk.Button.new_with_label("OK") | |
137 | self.OK_button.connect("clicked", self.on_click_OK) | |
138 | self.cancel_button = Gtk.Button.new_with_label("Cancel") | |
139 | self.cancel_button.connect("clicked", self.on_click_cancel) | |
140 | self.buttonBox.pack_start(self.OK_button, False, False, 10) | |
141 | self.buttonBox.pack_end(self.cancel_button, False, False, 10) | |
142 | self.buttonBox.show() | |
143 | ||
144 | self.mainBox.pack_start(self.nameBox, False, False, 0) | |
145 | self.mainBox.pack_start(self.descrBox, False, False, 0) | |
146 | self.mainBox.pack_start(self.typeBox, False, False, 0) | |
147 | self.mainBox.pack_end(self.buttonBox, False, False, 0) | |
148 | ||
149 | self.mainBox.show() | |
150 | self.add(self.mainBox) | |
151 | ||
152 | def on_click_OK(self, button): | |
153 | letters_or_numbers = r"^[a-z][a-z0-9\_\$()\+\-\/]*$" | |
154 | res = re.match(letters_or_numbers, str(self.name_entry.get_text())) | |
155 | if res: | |
156 | if self.callback is not None: | |
157 | self.__name_txt = str(self.name_entry.get_text()) | |
158 | self.__desc_txt = str(self.descr_entry.get_text()) | |
159 | self.__type_txt = str(self.comboBox.get_active_text()) | |
160 | creation_ok = self.callback(self.__name_txt, | |
161 | self.__desc_txt, | |
162 | self.__type_txt) | |
163 | if creation_ok: | |
164 | self.sidebar.addWorkspace(self.__name_txt) | |
165 | self.destroy() | |
166 | else: | |
167 | errorDialog(self, "Invalid workspace name", | |
168 | "A workspace must be named with " | |
169 | "all lowercase letters (a-z), digi" | |
170 | "ts(0-9) or any of the _$()+-/ " | |
171 | "characters. The name has to start" | |
172 | " with a lowercase letter") | |
173 | ||
174 | def on_click_cancel(self, button): | |
175 | self.destroy() | |
176 | ||
177 | ||
178 | class PluginOptionsDialog(Gtk.Window): | |
179 | """The dialog where the user can see details about installed plugins. | |
180 | It is not the prettiest thing in the world but it works. | |
181 | Creating and displaying the models of each plugin settings is specially | |
182 | messy, there's more info in the appropiate methods""" | |
183 | # TODO: probably stop hardcoding the first plugin, right? | |
184 | ||
185 | def __init__(self, plugin_manager, parent): | |
186 | ||
187 | Gtk.Window.__init__(self, title="Plugins Options") | |
188 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) | |
189 | self.set_transient_for(parent) | |
190 | self.set_size_request(800, 300) | |
191 | ||
192 | if plugin_manager is not None: | |
193 | self.plugin_settings = plugin_manager.getSettings() | |
194 | else: | |
195 | self.plugin_settings = {} | |
196 | ||
197 | self.settings_view = None | |
198 | self.id_of_selected = "Acunetix XML Output Plugin" # first one by name | |
199 | self.models = self.createPluginsSettingsModel() | |
200 | self.setSettingsView() | |
201 | ||
202 | plugin_info = self.createPluginInfo(plugin_manager) | |
203 | pluginList = self.createPluginListView(plugin_info) | |
204 | scroll_pluginList = Gtk.ScrolledWindow(None, None) | |
205 | scroll_pluginList.add(pluginList) | |
206 | # scroll_pluginList.set_min_content_width(300) | |
207 | pluginListBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
208 | pluginListBox.pack_start(scroll_pluginList, True, True, 0) | |
209 | ||
210 | buttonBox = Gtk.Box() | |
211 | OK_button = Gtk.Button.new_with_label("OK") | |
212 | cancel_button = Gtk.Button.new_with_label("Cancel") | |
213 | OK_button.connect("clicked", self.on_click_OK, plugin_manager) | |
214 | cancel_button.connect("clicked", self.on_click_cancel) | |
215 | buttonBox.pack_start(OK_button, True, True, 0) | |
216 | buttonBox.pack_start(cancel_button, True, True, 0) | |
217 | pluginListBox.pack_start(buttonBox, False, False, 0) | |
218 | ||
219 | infoBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
220 | nameBox, versionBox, pluginVersionBox = [Gtk.Box() for i in range(3)] | |
221 | ||
222 | nameLabel, versionLabel, pluginVersionLabel = [Gtk.Label() | |
223 | for i in range(3)] | |
224 | ||
225 | self.nameEntry, self.versionEntry, self.pluginVersionEntry = [ | |
226 | Gtk.Label() for i in range(3)] | |
227 | ||
228 | nameLabel.set_text("Name: ") | |
229 | versionLabel.set_text("Version: ") | |
230 | pluginVersionLabel.set_text("Plugin version: ") | |
231 | ||
232 | nameBox.pack_start(nameLabel, False, False, 5) | |
233 | nameBox.pack_start(self.nameEntry, False, True, 5) | |
234 | versionBox.pack_start(versionLabel, False, False, 5) | |
235 | versionBox.pack_start(self.versionEntry, False, True, 5) | |
236 | pluginVersionBox.pack_start(pluginVersionLabel, False, False, 5) | |
237 | pluginVersionBox.pack_start(self.pluginVersionEntry, False, True, 5) | |
238 | ||
239 | infoBox.pack_start(nameBox, False, False, 5) | |
240 | infoBox.pack_start(versionBox, False, False, 5) | |
241 | infoBox.pack_start(pluginVersionBox, False, False, 5) | |
242 | ||
243 | self.pluginSpecsBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
244 | self.pluginSpecsBox.pack_start(infoBox, False, False, 5) | |
245 | self.pluginSpecsBox.pack_start(self.settings_view, True, True, 0) | |
246 | ||
247 | self.mainBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) | |
248 | self.mainBox.pack_start(pluginListBox, True, True, 5) | |
249 | self.mainBox.pack_end(self.pluginSpecsBox, True, True, 5) | |
250 | ||
251 | self.add(self.mainBox) | |
252 | ||
253 | def on_click_OK(self, button, plugin_manager): | |
254 | """On click OK button update the plugins settings and then destroy""" | |
255 | if plugin_manager is not None: | |
256 | plugin_manager.updateSettings(self.plugin_settings) | |
257 | self.destroy() | |
258 | ||
259 | def on_click_cancel(self, button): | |
260 | """On click cancel button, destroy brutally. No mercy""" | |
261 | self.destroy() | |
262 | ||
263 | def createPluginInfo(self, plugin_manager): | |
264 | """Creates and return a TreeStore where the basic information about | |
265 | the plugins: the plugin ID, name, intended version of the tool | |
266 | and plugin version""" | |
267 | plugin_info = Gtk.TreeStore(str, str, str, str) | |
268 | ||
269 | for plugin_id, params in self.plugin_settings.iteritems(): | |
270 | plugin_info.append(None, [plugin_id, | |
271 | params["name"], | |
272 | params["version"], # tool version | |
273 | params["plugin_version"]]) | |
274 | ||
275 | # Sort it! | |
276 | sorted_plugin_info = Gtk.TreeModelSort(model=plugin_info) | |
277 | sorted_plugin_info.set_sort_column_id(1, Gtk.SortType.ASCENDING) | |
278 | return sorted_plugin_info | |
279 | ||
280 | def createPluginListView(self, plugin_info): | |
281 | """Creates the view for the left-hand side list of the dialog. | |
282 | It uses an instance of the plugin manager to get a list | |
283 | of all available plugins""" | |
284 | ||
285 | plugin_list_view = Gtk.TreeView(plugin_info) | |
286 | renderer = Gtk.CellRendererText() | |
287 | column = Gtk.TreeViewColumn("Title", renderer, text=1) | |
288 | column.set_sort_column_id(1) | |
289 | plugin_list_view.append_column(column) | |
290 | ||
291 | selection = plugin_list_view.get_selection() | |
292 | selection.connect("changed", self.on_plugin_selection) | |
293 | ||
294 | return plugin_list_view | |
295 | ||
296 | def createPluginsSettingsModel(self): | |
297 | """Creates a dictionary with | |
298 | {plugin-name : [(setting-name, setting-value)]} structure. This is used | |
299 | to hold all the plugins settings models""" | |
300 | ||
301 | models = {} | |
302 | ||
303 | for plugin_id in self.plugin_settings.iteritems(): | |
304 | # iter through the plugins | |
305 | plugin_info = plugin_id[1] # get dictionary associated to plugin | |
306 | store = Gtk.ListStore(str, str) # create the store for that plugin | |
307 | ||
308 | # iter through settings dictionary | |
309 | for setting in plugin_info["settings"].items(): | |
310 | setting_name = setting[0] | |
311 | setting_value = setting[1] | |
312 | store.append([setting_name, setting_value]) | |
313 | ||
314 | models[plugin_id[1]["name"]] = store # populate dict with store | |
315 | return models | |
316 | ||
317 | def createAdecuatePluginSettingView(self, store): | |
318 | """Create the adecuate plugin settings view. The first time this is | |
319 | executed, it will be none and it will tell the view which columns | |
320 | to display. After that, it will just change the model displayed""" | |
321 | self.active_store = store | |
322 | ||
323 | if self.settings_view is None: | |
324 | self.settings_view = Gtk.TreeView(store) | |
325 | renderer_text = Gtk.CellRendererText() | |
326 | column_text = Gtk.TreeViewColumn("Settings", renderer_text, text=0) | |
327 | self.settings_view.append_column(column_text) | |
328 | ||
329 | renderer_editable_text = Gtk.CellRendererText() | |
330 | renderer_editable_text.set_property("editable", True) | |
331 | renderer_editable_text.connect("edited", self.value_changed) | |
332 | column_editabletext = Gtk.TreeViewColumn("Value", | |
333 | renderer_editable_text, | |
334 | text=1) | |
335 | ||
336 | self.settings_view.append_column(column_editabletext) | |
337 | ||
338 | else: | |
339 | self.settings_view.set_model(store) | |
340 | ||
341 | def value_changed(self, widget, path, text): | |
342 | """Save new settings""" | |
343 | self.active_store[path][1] = text | |
344 | setting = self.active_store[path][0] | |
345 | settings = self.plugin_settings[self.name_of_selected]["settings"] | |
346 | settings[setting.strip()] = text.strip() | |
347 | ||
348 | def on_plugin_selection(self, selection): | |
349 | """When the user selects a plugin, it will change the text | |
350 | displeyed on the entries to their corresponding values""" | |
351 | ||
352 | model, treeiter = selection.get_selected() | |
353 | self.id_of_selected = model[treeiter][1] | |
354 | self.name_of_selected = model[treeiter][0] | |
355 | ||
356 | self.setSettingsView() | |
357 | ||
358 | self.nameEntry.set_label(model[treeiter][1]) | |
359 | self.versionEntry.set_label(model[treeiter][2]) | |
360 | self.pluginVersionEntry.set_label(model[treeiter][3]) | |
361 | ||
362 | def setSettingsView(self): | |
363 | """Makes the window match the selected plugin with the settings | |
364 | displayed""" | |
365 | ||
366 | adecuateModel = self.models[self.id_of_selected] | |
367 | self.createAdecuatePluginSettingView(adecuateModel) | |
368 | ||
369 | ||
370 | class NotificationsDialog(Gtk.Window): | |
371 | """Defines a simple notification dialog. It isn't much, really""" | |
372 | ||
373 | def __init__(self, view, callback, parent): | |
374 | Gtk.Window.__init__(self, title="Notifications") | |
375 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) | |
376 | self.set_transient_for(parent) | |
377 | self.set_size_request(400, 200) | |
378 | self.view = view | |
379 | self.destroy_notifications = callback | |
380 | ||
381 | self.button = Gtk.Button() | |
382 | self.button.set_label("OK") | |
383 | self.button.connect("clicked", self.on_click_OK) | |
384 | ||
385 | self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
386 | self.mainBox.pack_start(self.view, True, True, 0) | |
387 | self.mainBox.pack_start(self.button, False, False, 0) | |
388 | ||
389 | self.add(self.mainBox) | |
390 | ||
391 | def on_click_OK(self, button): | |
392 | self.destroy_notifications() | |
393 | self.destroy() | |
394 | ||
395 | ||
396 | class aboutDialog(Gtk.AboutDialog): | |
397 | """The simple about dialog displayed when the user clicks on "about" | |
398 | ont the menu. Could be in application.py, but for consistency reasons | |
399 | its here""" | |
400 | def __init__(self, main_window): | |
401 | ||
402 | Gtk.AboutDialog.__init__(self, transient_for=main_window, modal=True) | |
403 | icons = CONF.getImagePath() + "icons/" | |
404 | faraday_icon = GdkPixbuf.Pixbuf.new_from_file(icons+"about.png") | |
405 | self.set_logo(faraday_icon) | |
406 | self.set_program_name("Faraday") | |
407 | self.set_comments("Penetration Test IDE -" | |
408 | " Infobyte LLC. - All rights reserved") | |
409 | faraday_website = "http://www.infobytesec.com/faraday.html" | |
410 | self.set_website(faraday_website) | |
411 | self.set_website_label("Learn more about Faraday") | |
412 | ||
413 | ||
414 | class helpDialog(Gtk.AboutDialog): | |
415 | """Using about dialog 'cause they are very similar, but this will | |
416 | display github page, Wiki, and such""" | |
417 | def __init__(self, main_window): | |
418 | Gtk.AboutDialog.__init__(self, transient_for=main_window, modal=True) | |
419 | icons = CONF.getImagePath() + "icons/" | |
420 | faraday_icon = GdkPixbuf.Pixbuf.new_from_file(icons+"faraday_icon.png") | |
421 | self.set_logo(faraday_icon) | |
422 | self.set_program_name("Faraday") | |
423 | self.set_comments("Farday is a Penetration Test IDE. " | |
424 | "Just use one of the supported tools on Faraday's " | |
425 | " terminal and a plugin will capture the output and " | |
426 | "extract useful information for you.") | |
427 | faraday_website = "https://github.com/infobyte/faraday/wiki" | |
428 | self.set_website(faraday_website) | |
429 | self.set_website_label("Learn more about how to use Faraday") | |
430 | ||
431 | ||
432 | class errorDialog(Gtk.MessageDialog): | |
433 | """A simple error dialog to show the user where things went wrong. | |
434 | Takes the parent window, (Gtk.Window or Gtk.Dialog, most probably) | |
435 | the error and explanation (strings, nothing fancy) as arguments""" | |
436 | ||
437 | def __init__(self, parent_window, error, explanation=None): | |
438 | Gtk.MessageDialog.__init__(self, parent_window, 0, | |
439 | Gtk.MessageType.ERROR, | |
440 | Gtk.ButtonsType.OK, | |
441 | error) | |
442 | if explanation is not None: | |
443 | self.format_secondary_text(explanation) | |
444 | self.run() | |
445 | self.destroy() |
0 | #!/usr/bin/python2.7 | |
1 | # -*- coding: utf-8 -*- | |
2 | ''' | |
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | ||
7 | ''' | |
8 | import gi | |
9 | import os | |
10 | import sys | |
11 | ||
12 | gi.require_version('Gtk', '3.0') | |
13 | gi.require_version('Vte', '2.91') | |
14 | ||
15 | from gi.repository import Gtk, Vte, GLib | |
16 | ||
17 | ||
18 | class Terminal(Gtk.Widget): | |
19 | """Defines a simple terminal that will execute faraday-terminal with the | |
20 | corresponding host and port as specified by the CONF""" | |
21 | def __init__(self, CONF): | |
22 | super(Gtk.Widget, self).__init__() | |
23 | ||
24 | self.terminal = Vte.Terminal() | |
25 | faraday_directory = os.path.dirname(os.path.realpath(sys.argv[0])) | |
26 | self.terminal.spawn_sync(Vte.PtyFlags.DEFAULT, | |
27 | faraday_directory, ['/bin/zsh'], | |
28 | [], | |
29 | GLib.SpawnFlags.DO_NOT_REAP_CHILD, | |
30 | None, | |
31 | None) | |
32 | ||
33 | host, port = CONF.getApiRestfulConInfo() | |
34 | faraday_exec = './faraday-terminal.zsh' | |
35 | self.command = (faraday_exec + " " + host + " " + str(port)) | |
36 | self.terminal.feed_child(self.command + '\n', len(self.command)+1) | |
37 | ||
38 | def getTerminal(self): | |
39 | return self.terminal | |
40 | ||
41 | ||
42 | class Sidebar(Gtk.Widget): | |
43 | """Defines the sidebar widget to be used by the AppWindow, passed as an | |
44 | instance by the application. It only handles the view, all the backend | |
45 | word is handled by the application via the callback""" | |
46 | ||
47 | def __init__(self, workspace_manager, callback_to_change_workspace, | |
48 | callback_to_remove_workspace, conf): | |
49 | super(Gtk.Widget, self).__init__() | |
50 | self.callback = callback_to_change_workspace | |
51 | self.removeWsCallback = callback_to_remove_workspace | |
52 | self.ws_manager = workspace_manager | |
53 | self.lastWorkspace = conf | |
54 | self.workspaces = self.ws_manager.getWorkspacesNames() | |
55 | self.workspace_list_info = Gtk.ListStore(str) | |
56 | ||
57 | self.workspaceModel() | |
58 | self.workspaceView() | |
59 | ||
60 | self.sidebar_button = Gtk.Button.new_with_label("Refresh") | |
61 | self.sidebar_button.connect("clicked", self.refreshSidebar) | |
62 | ||
63 | self.scrollableView = Gtk.ScrolledWindow.new(None, None) | |
64 | self.scrollableView.set_min_content_width(160) | |
65 | self.scrollableView.add(self.lst) | |
66 | ||
67 | def refreshSidebar(self, button=None): | |
68 | """Function called when the user press the refresh button. | |
69 | Gets an updated copy of the workspaces and checks against | |
70 | the model to see which are already there and which arent""" | |
71 | model = self.workspace_list_info | |
72 | self.workspaces = self.ws_manager.getWorkspacesNames() | |
73 | added_workspaces = [added_ws[0] for added_ws in model] | |
74 | for ws in self.workspaces: | |
75 | if ws not in added_workspaces: | |
76 | self.addWorkspace(ws) | |
77 | ||
78 | def clearSidebar(self): | |
79 | """Brutaly clear all the information from the model. | |
80 | No one survives""" | |
81 | self.workspace_list_info.clear() | |
82 | ||
83 | def createTitle(self): | |
84 | """Return a label with the text "Workspaces""" | |
85 | title = Gtk.Label() | |
86 | title.set_text("Workspaces") | |
87 | return title | |
88 | ||
89 | def workspaceModel(self): | |
90 | """Populates the workspace model. Also assigns self.defaultSelection | |
91 | to the treeIter which represents the last active workspace""" | |
92 | for ws in self.workspaces: | |
93 | treeIter = self.workspace_list_info.append([ws]) | |
94 | if ws == self.lastWorkspace: | |
95 | self.defaultSelection = treeIter | |
96 | ||
97 | def workspaceView(self): | |
98 | """Populate the workspace view. Also selected by default | |
99 | self.defaultSelection (see workspaceModel method). Also connect | |
100 | a selection with the change workspace callback""" | |
101 | self.lst = Gtk.TreeView(self.workspace_list_info) | |
102 | renderer = Gtk.CellRendererText() | |
103 | column = Gtk.TreeViewColumn("Workspaces", renderer, text=0) | |
104 | self.lst.append_column(column) | |
105 | ||
106 | # select by default the last active workspace | |
107 | if self.defaultSelection is not None: | |
108 | self.selectDefault = self.lst.get_selection() | |
109 | self.selectDefault.select_iter(self.defaultSelection) | |
110 | ||
111 | self.lst.connect("button-press-event", self.on_right_click) | |
112 | selection = self.lst.get_selection() | |
113 | selection.connect("changed", self.callback) | |
114 | ||
115 | def on_right_click(self, view, event): | |
116 | """On click, check if it was a right click. If it was, | |
117 | create a menu with the delete option. On click on that option, | |
118 | delete the workspace that occupied the position where the user | |
119 | clicked. Returns True if it was a right click""" | |
120 | ||
121 | if event.button == 3: # 3 represents right click | |
122 | menu = Gtk.Menu() | |
123 | delete_item = Gtk.MenuItem("Delete") | |
124 | menu.append(delete_item) | |
125 | ||
126 | # underscores mean "i don't 'care 'cause i won't use them | |
127 | # thank haskell for that | |
128 | # get the path of the item where the user clicked | |
129 | # then get its tree_iter. then get its name. then delete | |
130 | # that workspace | |
131 | ||
132 | path, _, _, _ = view.get_path_at_pos(int(event.x), int(event.y)) | |
133 | tree_iter = self.workspace_list_info.get_iter(path) | |
134 | ws_name = self.workspace_list_info[tree_iter][0] | |
135 | ||
136 | delete_item.connect("activate", self.removeWsCallback, ws_name) | |
137 | ||
138 | delete_item.show() | |
139 | menu.popup(None, None, None, None, event.button, event.time) | |
140 | return True # prevents the click from selecting a workspace | |
141 | ||
142 | def addWorkspace(self, ws): | |
143 | """Append ws workspace to the model""" | |
144 | self.workspace_list_info.append([ws]) | |
145 | ||
146 | def getSelectedWs(self): | |
147 | """Returns the current selected workspace""" | |
148 | return self.lst.get_selection() | |
149 | ||
150 | def selectWs(self, ws): | |
151 | """Selects workspace ws in the list""" | |
152 | self.select = self.lst.get_selection() | |
153 | self.select.select_iter(ws) | |
154 | ||
155 | def getButton(self): | |
156 | """Returns the refresh sidebar button""" | |
157 | return self.sidebar_button | |
158 | ||
159 | ||
160 | class ConsoleLog(Gtk.Widget): | |
161 | """Defines a textView and a textBuffer to be used for displaying | |
162 | and updating logging information in the appwindow.""" | |
163 | ||
164 | def __init__(self): | |
165 | super(Gtk.Widget, self).__init__() | |
166 | ||
167 | self.textBuffer = Gtk.TextBuffer() | |
168 | self.textBuffer.new() | |
169 | self.textBuffer.set_text("LOG. Please run Faraday with the --debug " | |
170 | "flag for more verbose output \n \0", -1) | |
171 | ||
172 | self.textView = Gtk.TextView() | |
173 | self.textView.set_editable(False) | |
174 | # TODO: only execute monospace if Gi >= 3.16 | |
175 | # self.textView.set_monospace(True) | |
176 | self.textView.set_justification(Gtk.Justification.LEFT) | |
177 | ||
178 | self.textView.set_buffer(self.textBuffer) | |
179 | ||
180 | self.logger = Gtk.ScrolledWindow.new(None, None) | |
181 | self.logger.set_min_content_height(100) | |
182 | self.logger.set_min_content_width(100) | |
183 | self.logger.add(self.textView) | |
184 | ||
185 | def getLogger(self): | |
186 | """Returns the ScrolledWindow used to contain the view""" | |
187 | return self.logger | |
188 | ||
189 | def getView(self): | |
190 | """Returns the text view""" | |
191 | return self.textView | |
192 | ||
193 | def getBuffer(self): | |
194 | """Returns the buffer""" | |
195 | return self.textBuffer | |
196 | ||
197 | def customEvent(self, text): | |
198 | """Filters event so that only those with type 3131 get to the log""" | |
199 | self.update(text) | |
200 | ||
201 | def update(self, event): | |
202 | """Updates the textBuffer with the event sent. Also scrolls to last | |
203 | posted automatically""" | |
204 | last_position = self.textBuffer.get_end_iter() | |
205 | self.textBuffer.insert(last_position, event+"\n", len(event + "\n")) | |
206 | insert = self.textBuffer.get_insert() | |
207 | self.textView.scroll_to_mark(insert, 0, False, 1, 1) | |
208 | ||
209 | ||
210 | class Statusbar(Gtk.Widget): | |
211 | """Defines a statusbar, which is actually more quite like a button. | |
212 | The button has a label that tells how many notifications are there. | |
213 | Takes an on_button_do callback, so it can tell the application what | |
214 | to do when the user presses the button""" | |
215 | ||
216 | def __init__(self, on_button_do): | |
217 | super(Gtk.Widget, self).__init__() | |
218 | """Creates a button with zero ("0") as label""" | |
219 | self.callback = on_button_do | |
220 | self.button_label_int = 0 | |
221 | self.button = Gtk.Button.new_with_label(str(self.button_label_int)) | |
222 | self.button.connect("clicked", self.callback) | |
223 | ||
224 | def inc_button_label(self): | |
225 | """increments the label by one""" | |
226 | self.button_label_int += 1 | |
227 | self.button.set_label(str(self.button_label_int)) |
0 | <!-- | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | --> | |
5 | ||
6 | <?xml version="1.0"?> | |
7 | <interface> | |
8 | <menu id="appmenu"> | |
9 | <section> | |
10 | <item> | |
11 | <attribute name="label" translatable="yes">Preferences</attribute> | |
12 | <attribute name="action">app.preferences</attribute> | |
13 | </item> | |
14 | <item> | |
15 | <attribute name="label" translatable="yes">Plugins</attribute> | |
16 | <attribute name="action">app.pluginOptions</attribute> | |
17 | </item> | |
18 | </section> | |
19 | <section> | |
20 | <item> | |
21 | <attribute name="label" translatable="yes">About</attribute> | |
22 | <attribute name="action">app.about</attribute> | |
23 | </item> | |
24 | <item> | |
25 | <attribute name="label" translatable="yes">Help</attribute> | |
26 | <attribute name="action">app.help</attribute> | |
27 | </item> | |
28 | <item> | |
29 | <attribute name="label" translatable="yes">Quit</attribute> | |
30 | <attribute name="action">app.quit</attribute> | |
31 | <attribute name="accel"><Primary>q</attribute> | |
32 | </item> | |
33 | </section> | |
34 | </menu> | |
35 | </interface> | |
36 |
13 | 13 | |
14 | 14 | class UiFactory(object): |
15 | 15 | @staticmethod |
16 | def create(model_controller, plugin_manager, workspace_manager, gui="gtk"): | |
16 | def create(model_controller, plugin_manager, workspace_manager, plugin_controller, gui="gtk"): | |
17 | 17 | if gui == "gtk": |
18 | 18 | from gui.gtk.application import GuiApp |
19 | 19 | elif gui == "qt3": |
21 | 21 | else: |
22 | 22 | from gui.nogui.application import GuiApp |
23 | 23 | |
24 | return GuiApp(model_controller, plugin_manager, workspace_manager) | |
24 | return GuiApp(model_controller, plugin_manager, workspace_manager, plugin_controller) | |
25 | 25 | |
26 | 26 | |
27 | 27 | class FaradayUi(object): |
28 | def __init__(self, model_controller=None, plugin_manager=None, | |
29 | workspace_manager=None, gui="qt3"): | |
28 | def __init__(self, model_controller, plugin_manager, | |
29 | workspace_manager, plugin_controller, gui="qt3"): | |
30 | 30 | self.model_controller = model_controller |
31 | 31 | self.plugin_manager = plugin_manager |
32 | 32 | self.workspace_manager = workspace_manager |
33 | self.plugin_controller = plugin_controller | |
33 | 34 | self.report_manager = None |
34 | 35 | |
35 | 36 | def getModelController(self): |
77 | 78 | try: |
78 | 79 | ws = self.getWorkspaceManager().openWorkspace(name) |
79 | 80 | self.report_manager = ReportManager( |
80 | 10, name) | |
81 | 10, | |
82 | name, | |
83 | self.plugin_controller | |
84 | ) | |
81 | 85 | self.report_manager.start() |
82 | 86 | except Exception as e: |
83 | 87 | raise e |
96 | 100 | self.report_manager.join() |
97 | 101 | ws = self.getWorkspaceManager().openDefaultWorkspace() |
98 | 102 | self.report_manager = ReportManager( |
99 | 10, ws.name) | |
103 | 10, | |
104 | ws.name, | |
105 | self.plugin_controller | |
106 | ) | |
100 | 107 | self.report_manager.start() |
101 | 108 | return ws |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' | |
6 | ||
7 | import logging | |
8 | import threading | |
9 | import model.guiapi | |
10 | from gui.customevents import LogCustomEvent | |
11 | ||
12 | class GUIHandler(logging.Handler): | |
13 | def __init__(self): | |
14 | logging.Handler.__init__(self) | |
15 | self._widgets = [] | |
16 | self._widgets_lock = threading.RLock() | |
17 | formatter = logging.Formatter( | |
18 | '%(levelname)s - %(asctime)s - %(name)s - %(message)s') | |
19 | self.setFormatter(formatter) | |
20 | ||
21 | def registerGUIOutput(self, widget): | |
22 | self._widgets_lock.acquire() | |
23 | self._widgets.append(widget) | |
24 | self._widgets_lock.release() | |
25 | ||
26 | def clearWidgets(self): | |
27 | self._widgets_lock.acquire() | |
28 | self._widgets = [] | |
29 | self._widgets_lock.release() | |
30 | ||
31 | def emit(self, record): | |
32 | try: | |
33 | msg = self.format(record) | |
34 | self._widgets_lock.acquire() | |
35 | for widget in self._widgets: | |
36 | event = LogCustomEvent(msg) | |
37 | model.guiapi.postCustomEvent(event, widget) | |
38 | self._widgets_lock.release() | |
39 | except: | |
40 | self.handleError(record) |
17 | 17 | |
18 | 18 | |
19 | 19 | class GuiApp(FaradayUi): |
20 | def __init__(self, model_controller, plugin_manager, workspace_manager): | |
20 | def __init__(self, model_controller, plugin_manager, workspace_manager, plugin_controller): | |
21 | 21 | FaradayUi.__init__(self, |
22 | 22 | model_controller, |
23 | 23 | plugin_manager, |
24 | workspace_manager) | |
24 | workspace_manager, | |
25 | plugin_controller) | |
25 | 26 | self._stop = False |
26 | 27 | model.guiapi.setMainApp(self) |
27 | 28 | self.event_watcher = EventWatcher() |
10 | 10 | |
11 | 11 | |
12 | 12 | class NotificationCenter(): |
13 | def __init__(self, uiapp=FaradayUi()): | |
13 | def __init__(self, uiapp=FaradayUi(None, None, None, None, None)): | |
14 | 14 | self.uiapp = uiapp |
15 | 15 | self._consumers = [] |
16 | 16 | self._consumers_lock = threading.RLock() |
17 | 17 | from gui.gui_app import FaradayUi |
18 | 18 | from gui.qt3.mainwindow import MainWindow |
19 | 19 | from gui.qt3.customevents import QtCustomEvent |
20 | from gui.qt3.logconsole import GUIHandler | |
20 | from gui.loghandler import GUIHandler | |
21 | 21 | from shell.controller.env import ShellEnvironment |
22 | 22 | |
23 | 23 | import model.guiapi |
31 | 31 | |
32 | 32 | |
33 | 33 | class GuiApp(qt.QApplication, FaradayUi): |
34 | def __init__(self, model_controller, plugin_manager, workspace_manager): | |
34 | def __init__(self, model_controller, plugin_manager, workspace_manager, plugin_controller): | |
35 | 35 | FaradayUi.__init__(self, |
36 | 36 | model_controller, |
37 | 37 | plugin_manager, |
38 | workspace_manager) | |
38 | workspace_manager, | |
39 | plugin_controller) | |
39 | 40 | qt.QApplication.__init__(self, []) |
40 | 41 | |
41 | 42 | self._shell_envs = dict() |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | ''' |
6 | import logging | |
7 | import threading | |
6 | import qt | |
8 | 7 | import re |
9 | ||
10 | from gui.customevents import LogCustomEvent | |
11 | import model.guiapi | |
12 | import qt | |
13 | ||
14 | ||
15 | class GUIHandler(logging.Handler): | |
16 | def __init__(self): | |
17 | logging.Handler.__init__(self) | |
18 | self._widgets = [] | |
19 | self._widgets_lock = threading.RLock() | |
20 | formatter = logging.Formatter( | |
21 | '%(levelname)s - %(asctime)s - %(name)s - %(message)s') | |
22 | self.setFormatter(formatter) | |
23 | ||
24 | def registerGUIOutput(self, widget): | |
25 | self._widgets_lock.acquire() | |
26 | self._widgets.append(widget) | |
27 | self._widgets_lock.release() | |
28 | ||
29 | def clearWidgets(self): | |
30 | self._widgets_lock.acquire() | |
31 | self._widgets = [] | |
32 | self._widgets_lock.release() | |
33 | ||
34 | def emit(self, record): | |
35 | try: | |
36 | msg = self.format(record) | |
37 | self._widgets_lock.acquire() | |
38 | for widget in self._widgets: | |
39 | event = LogCustomEvent(msg) | |
40 | model.guiapi.postCustomEvent(event, widget) | |
41 | self._widgets_lock.release() | |
42 | except: | |
43 | self.handleError(record) | |
44 | ||
45 | 8 | |
46 | 9 | class LogConsole(qt.QVBox): |
47 | 10 | """ |
27 | 27 | self.connect(self, qt.PYSIGNAL('contextMenu'), self._showContextMenu ) |
28 | 28 | self.contextPopupMenu = qt.QPopupMenu(self) |
29 | 29 | self._setupContextPopupMenu() |
30 | ||
30 | ||
31 | 31 | def addAction(self, name, func): |
32 | 32 | self._actions[name] = func |
33 | 33 | |
35 | 35 | """ |
36 | 36 | setups all items in the context menu with all its actions |
37 | 37 | """ |
38 | #insertItem ( const QString & text, | |
39 | # const QObject * receiver, | |
40 | # const char * member, | |
41 | # const QKeySequence & accel = 0, | |
42 | # int id = -1, | |
43 | # int index = -1 ) | |
44 | 38 | self.contextPopupMenu.insertItem("Allow plugins on this shell", self._allowPlugins) |
45 | 39 | self._actions["new_shell"].addTo(self.contextPopupMenu); |
46 | 40 | self._actions["close_shell"].addTo(self.contextPopupMenu); |
47 | #self.contextPopupMenu.insertItem("Close tab", self._actions["close_shell"]) | |
48 | 41 | |
49 | 42 | def contextMenuEvent(self, event): |
50 | 43 | #XXX: emits the signal to show the parent context menu |
62 | 55 | # e is a qt.QMouseEvent |
63 | 56 | if "maximize" in self._actions: |
64 | 57 | self._actions["maximize"]() |
65 | ||
58 | ||
66 | 59 | def _setupActions(self): |
67 | 60 | """ |
68 | 61 | creates some actions needed on some menues and toolbars |
69 | 62 | """ |
70 | 63 | a = self._actions["close_shell"] = qt.QAction( qt.QIconSet(qt.QPixmap(os.path.join(CONF.getIconsPath(),"newshell.png"))), "&Close Shell", qt.Qt.CTRL + qt.Qt.Key_W, self, "New Shell" ) |
71 | 64 | self.connect(a, qt.SIGNAL('activated()'), self.destroyShellTab) |
72 | ||
65 | ||
73 | 66 | a = self._actions["new_shell"] = qt.QAction( qt.QIconSet(qt.QPixmap(os.path.join(CONF.getIconsPath(),"newshell.png"))), "&New Shell", qt.Qt.CTRL + qt.Qt.Key_T, self, "New Shell" ) |
74 | 67 | self.connect(a, qt.SIGNAL('activated()'), self.createShellTab) |
75 | 68 | |
76 | 69 | def destroyShellTab(self): |
77 | 70 | getMainWindow().destroyShellTab() |
78 | ||
71 | ||
79 | 72 | def createShellTab(self): |
80 | 73 | getMainWindow().createShellTab() |
81 | 74 | |
86 | 79 | self.views = [] |
87 | 80 | self.setMargin(10) |
88 | 81 | self.connect(self, qt.SIGNAL('currentChanged(QWidget*)'), self._setFocus) |
89 | ||
82 | ||
90 | 83 | # we replace the tab bar with our own wich handles contextMenu |
91 | 84 | tabbar = ContextMenuTabBar(self) |
92 | 85 | self.setTabBar(tabbar) |
93 | 86 | self._next_id = 0 |
94 | ||
87 | ||
95 | 88 | def getNextId(self): |
96 | 89 | self._next_id += 1 |
97 | 90 | return self._next_id |
98 | ||
91 | ||
99 | 92 | def addView(self, view): |
100 | 93 | if view not in self.views: |
101 | 94 | self.views.append(view) |
128 | 121 | |
129 | 122 | def _setFocus(self, widget): |
130 | 123 | # just set focus is set on the widget that is contained in the new selected page. |
131 | widget.setFocus()⏎ | |
124 | widget.setFocus() |
4 | 4 | |
5 | 5 | ''' |
6 | 6 | # -*- coding: utf-8 -*- |
7 | ||
8 | ||
9 | ||
10 | ||
11 | ||
12 | ||
13 | ||
14 | ||
15 | 7 | |
16 | 8 | from qt import * |
17 | 9 | from qttable import QTable |
109 | 101 | label_version_font.setBold(1) |
110 | 102 | self.label_version.setFont(label_version_font) |
111 | 103 | layout6.addWidget(self.label_version) |
112 | ||
104 | ||
113 | 105 | self.le_version = QLineEdit(self.frame3,"le_version") |
114 | 106 | self.le_version.setMinimumSize(QSize(250,0)) |
115 | 107 | self.le_version.setReadOnly(1) |
116 | 108 | layout6.addWidget(self.le_version) |
117 | 109 | |
118 | 110 | frame3Layout.addLayout(layout6,1,0) |
119 | ||
111 | ||
120 | 112 | layout7 = QHBoxLayout(None,0,6,"layout7") |
121 | ||
113 | ||
122 | 114 | self.label_pversion = QLabel(self.frame3,"label_pversion") |
123 | 115 | self.label_pversion.setMinimumSize(QSize(67,0)) |
124 | 116 | self.label_pversion.setMaximumSize(QSize(67,32767)) |
126 | 118 | label_pversion_font.setBold(1) |
127 | 119 | self.label_pversion.setFont(label_pversion_font) |
128 | 120 | layout7.addWidget(self.label_pversion) |
129 | ||
121 | ||
130 | 122 | self.le_pversion = QLineEdit(self.frame3,"le_pversion") |
131 | 123 | self.le_pversion.setMinimumSize(QSize(250,0)) |
132 | 124 | self.le_pversion.setReadOnly(1) |
133 | 125 | layout7.addWidget(self.le_pversion) |
134 | ||
135 | frame3Layout.addLayout(layout7,2,0) | |
126 | ||
127 | frame3Layout.addLayout(layout7,2,0) | |
136 | 128 | |
137 | 129 | PluginSettingsUiLayout.addWidget(self.frame3,0,1) |
138 | 130 |
3 | 3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) |
4 | 4 | See the file 'doc/LICENSE' for the license information |
5 | 5 | |
6 | ''' | |
7 | ''' | |
8 | 6 | This script fixes invalid XMLs. |
9 | 7 | ''' |
10 | 8 |
3 | 3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) |
4 | 4 | See the file 'doc/LICENSE' for the license information |
5 | 5 | |
6 | ''' | |
7 | ''' | |
8 | 6 | This script either updates or removes Interfaces, Services and Vulnerabilities in case their parent property is null. |
9 | 7 | If the property is null but a parent is found in Couch, the document is updated. |
10 | 8 | If the parent is not found in Couch the document is deleted, since it is an invalid one. |
3 | 3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) |
4 | 4 | See the file 'doc/LICENSE' for the license information |
5 | 5 | |
6 | ''' | |
7 | ''' | |
8 | This script upload a Vulnerability database to Couch. | |
6 | This script uploads a Vulnerability database to Couch. | |
9 | 7 | It takes the content of the DB from data/cwe.csv |
10 | 8 | ''' |
11 | 9 | import argparse |
18 | 16 | def main(): |
19 | 17 | |
20 | 18 | #arguments parser |
21 | parser = argparse.ArgumentParser(prog='pushExecutiveReports', epilog="Example: ./%(prog)s.py") | |
19 | parser = argparse.ArgumentParser(prog='pushCwe', epilog="Example: ./%(prog)s.py") | |
22 | 20 | parser.add_argument('-c', '--couchdburi', action='store', type=str, |
23 | 21 | dest='couchdb',default="http://127.0.0.1:5984", |
24 | 22 | help='Couchdb URL (default http://127.0.0.1:5984)') |
3 | 3 | Copyright (C) 2014 Infobyte LLC (http://www.infobytesec.com/) |
4 | 4 | See the file 'doc/LICENSE' for the license information |
5 | 5 | |
6 | ''' | |
7 | ''' | |
8 | This script either updates or removes Interfaces, Services and Vulnerabilities in case their parent property is null. | |
9 | If the property is null but a parent is found in Couch, the document is updated. | |
10 | If the parent is not found in Couch the document is deleted, since it is an invalid one. | |
6 | This script removes vulnerabilities from Couch depending on thei severity. | |
11 | 7 | ''' |
12 | 8 | |
13 | 9 | import argparse |
2 | 2 | ''' |
3 | 3 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) |
4 | 4 | Author: Ezequiel Tavella |
5 | ''' | |
6 | 5 | |
7 | ''' | |
8 | 6 | This script generate a CSV file with information about the vulndb database. |
9 | 7 | CSV Format: |
10 | 8 | cwe,name,desc_summary,description,resolution,exploitation,references |
11 | ||
12 | 9 | ''' |
13 | 10 | from subprocess import call |
14 | 11 | from os import walk |
6 | 6 | ''' |
7 | 7 | |
8 | 8 | import os |
9 | import model.api | |
9 | #import model.api | |
10 | 10 | import threading |
11 | 11 | import time |
12 | 12 | import traceback |
13 | 13 | import re |
14 | 14 | |
15 | from utils.logs import getLogger | |
16 | ||
15 | 17 | try: |
16 | 18 | import xml.etree.cElementTree as ET |
17 | 19 | |
18 | 20 | except ImportError: |
19 | 21 | print "cElementTree could not be imported. Using ElementTree instead" |
20 | 22 | import xml.etree.ElementTree as ET |
21 | from apis.rest.client import PluginControllerAPIClient | |
22 | 23 | |
23 | 24 | from config.configuration import getInstanceConfiguration |
24 | 25 | CONF = getInstanceConfiguration() |
25 | 26 | |
26 | 27 | |
27 | 28 | class ReportProcessor(): |
28 | def __init__(self): | |
29 | host = CONF.getApiConInfoHost() | |
30 | port_rest = int(CONF.getApiRestfulConInfoPort()) | |
31 | ||
32 | self.client = PluginControllerAPIClient(host, port_rest) | |
29 | def __init__(self, plugin_controller): | |
30 | self.plugin_controller = plugin_controller | |
33 | 31 | |
34 | 32 | def processReport(self, filename): |
35 | 33 | """ |
36 | 34 | Process one Report |
37 | 35 | """ |
38 | model.api.log("Report file is %s" % filename) | |
36 | getLogger(self).debug("Report file is %s" % filename) | |
39 | 37 | |
40 | 38 | parser = ReportParser(filename) |
41 | if (parser.report_type is not None): | |
42 | model.api.log( | |
43 | "The file is %s, %s" % (filename, parser.report_type)) | |
44 | ||
45 | command_string = "./%s %s" % (parser.report_type.lower(), | |
46 | filename) | |
47 | model.api.log("Executing %s" % (command_string)) | |
48 | ||
49 | new_cmd, output_file = self.client.send_cmd(command_string) | |
50 | self.client.send_output(command_string, filename) | |
51 | return True | |
52 | return False | |
39 | ||
40 | if parser.report_type is None: | |
41 | getLogger(self).error( | |
42 | 'Plugin not found: automatic and manual try!' | |
43 | ) | |
44 | return False | |
45 | ||
46 | return self._sendReport(parser.report_type, filename) | |
47 | ||
48 | def _sendReport(self, plugin_id, filename): | |
49 | getLogger(self).debug( | |
50 | 'The file is %s, %s' % (filename, plugin_id)) | |
51 | if not self.plugin_controller.processReport(plugin_id, filename): | |
52 | getLogger(self).error( | |
53 | "Faraday doesn't have a plugin for this tool..." | |
54 | " Processing: ABORT") | |
55 | return False | |
56 | return True | |
53 | 57 | |
54 | 58 | def onlinePlugin(self, cmd): |
55 | new_cmd, output_file = self.client.send_cmd(cmd) | |
56 | self.client.send_output(cmd) | |
59 | ||
60 | _, new_cmd, output_file = self.plugin_controller.processCommandInput( | |
61 | cmd) | |
62 | self.plugin_controller.onCommandFinished(cmd, '') | |
57 | 63 | |
58 | 64 | |
59 | 65 | class ReportManager(threading.Thread): |
60 | def __init__(self, timer, ws_name): | |
66 | def __init__(self, timer, ws_name, plugin_controller): | |
61 | 67 | threading.Thread.__init__(self) |
62 | 68 | self.setDaemon(True) |
63 | 69 | self.timer = timer |
64 | 70 | self._stop = False |
65 | 71 | self._report_path = os.path.join(CONF.getReportPath(), ws_name) |
66 | 72 | self._report_ppath = os.path.join(self._report_path, "process") |
67 | self.processor = ReportProcessor() | |
73 | self._report_upath = os.path.join(self._report_path, "unprocessed") | |
74 | self.processor = ReportProcessor(plugin_controller) | |
68 | 75 | |
69 | 76 | if not os.path.exists(self._report_path): |
70 | 77 | os.mkdir(self._report_path) |
71 | 78 | |
72 | 79 | if not os.path.exists(self._report_ppath): |
73 | 80 | os.mkdir(self._report_ppath) |
81 | ||
82 | if not os.path.exists(self._report_upath): | |
83 | os.mkdir(self._report_upath) | |
74 | 84 | |
75 | 85 | def run(self): |
76 | 86 | tmp_timer = 0 |
82 | 92 | try: |
83 | 93 | self.syncReports() |
84 | 94 | except Exception: |
85 | model.api.log("An exception was captured while saving reports\n%s" % traceback.format_exc()) | |
95 | getLogger(self).error( | |
96 | "An exception was captured while saving reports\n%s" | |
97 | % traceback.format_exc() | |
98 | ) | |
86 | 99 | finally: |
87 | 100 | tmp_timer = 0 |
88 | 101 | |
101 | 114 | for name in files: |
102 | 115 | filename = os.path.join(root, name) |
103 | 116 | |
104 | self.processor.processReport(filename) | |
105 | ||
106 | os.rename(filename, os.path.join(self._report_ppath, name)) | |
117 | # If plugin not is detected... move to unprocessed | |
118 | if self.processor.processReport(filename) is False: | |
119 | ||
120 | os.rename( | |
121 | filename, | |
122 | os.path.join(self._report_upath, name) | |
123 | ) | |
124 | else: | |
125 | os.rename( | |
126 | filename, | |
127 | os.path.join(self._report_ppath, name) | |
128 | ) | |
107 | 129 | |
108 | 130 | self.onlinePlugins() |
109 | 131 | |
132 | 154 | """ |
133 | 155 | |
134 | 156 | def __init__(self, report_path): |
135 | self.report_type = "" | |
157 | self.report_type = None | |
136 | 158 | root_tag, output = self.getRootTag(report_path) |
137 | 159 | |
138 | 160 | if root_tag: |
139 | 161 | self.report_type = self.rType(root_tag, output) |
162 | ||
163 | if self.report_type is None: | |
164 | ||
165 | getLogger(self).debug( | |
166 | 'Automatical detection FAILED... Trying manual...') | |
167 | ||
168 | self.report_type = self.getUserPluginName(report_path) | |
169 | ||
170 | def getUserPluginName(self, pathFile): | |
171 | rname = pathFile[pathFile.rfind('/') + 1:] | |
172 | ext = rname.rfind('.') | |
173 | if ext < 0: | |
174 | ext = len(rname) + 1 | |
175 | rname = rname[0:ext] | |
176 | faraday_index = rname.rfind('_faraday_') | |
177 | if faraday_index > -1: | |
178 | plugin = rname[faraday_index + 9:] | |
179 | return plugin | |
180 | return None | |
140 | 181 | |
141 | 182 | def open_file(self, file_path): |
142 | 183 | """ |
154 | 195 | f = result = None |
155 | 196 | |
156 | 197 | signatures = { |
157 | "\x50\x4B" : "zip" , | |
158 | "\x3C\x3F\x78\x6D\x6C" : "xml" | |
198 | "\x50\x4B": "zip", | |
199 | "\x3C\x3F\x78\x6D\x6C": "xml" | |
159 | 200 | } |
160 | 201 | |
161 | 202 | try: |
167 | 208 | if file_signature.find(key) == 0: |
168 | 209 | |
169 | 210 | result = signatures[key] |
170 | model.api.log("Report type detected: %s" %result) | |
211 | getLogger(self).debug("Report type detected: %s" % result) | |
171 | 212 | break |
172 | 213 | |
173 | 214 | except IOError, err: |
174 | 215 | self.report_type = None |
175 | model.api.log("Error while opening file.\n%s. %s" % (err, file_path)) | |
216 | getLogger(self).error( | |
217 | "Error while opening file.\n%s. %s" % (err, file_path)) | |
176 | 218 | |
177 | 219 | return f, result |
178 | 220 | |
182 | 224 | |
183 | 225 | f, report_type = self.open_file(file_path) |
184 | 226 | |
185 | #Check error in open_file() | |
186 | if f == None and report_type == None: | |
227 | # Check error in open_file() | |
228 | if f is None and report_type is None: | |
187 | 229 | self.report_type = None |
188 | 230 | return None, None |
189 | 231 | |
190 | #Find root tag based in report_type | |
232 | # Find root tag based in report_type | |
191 | 233 | if report_type == "zip": |
192 | 234 | result = "maltego" |
193 | 235 | |
200 | 242 | |
201 | 243 | except SyntaxError, err: |
202 | 244 | self.report_type = None |
203 | model.api.log("Not an xml file.\n %s" % (err)) | |
245 | getLogger(self).error("Not an xml file.\n %s" % (err)) | |
204 | 246 | |
205 | 247 | f.seek(0) |
206 | 248 | output = f.read() |
207 | if f: f.close() | |
249 | if f: | |
250 | f.close() | |
208 | 251 | |
209 | 252 | return result, output |
210 | 253 | |
215 | 258 | :rtype |
216 | 259 | """ |
217 | 260 | if "nmaprun" == tag: |
218 | return "nmap" | |
261 | return "Nmap" | |
219 | 262 | elif "w3af-run" == tag: |
220 | return "w3af" | |
263 | return "W3af" | |
221 | 264 | elif "NessusClientData_v2" == tag: |
222 | return "nessus" | |
265 | return "Nessus" | |
223 | 266 | elif "report" == tag: |
224 | if re.search("https://raw.githubusercontent.com/Arachni/arachni/", output) != None: | |
225 | return "arachni_faraday" | |
226 | elif re.search("OpenVAS", output) != None or re.search('<omp><version>', output) != None: | |
227 | return "openvas" | |
267 | ||
268 | if re.search( | |
269 | "https://raw.githubusercontent.com/Arachni/arachni/", | |
270 | output | |
271 | ) is not None: | |
272 | return "Arachni" | |
273 | ||
274 | elif re.search("OpenVAS", output) is not None or re.search( | |
275 | '<omp><version>', | |
276 | output | |
277 | ) is not None: | |
278 | return "Openvas" | |
279 | ||
228 | 280 | else: |
229 | return "zap" | |
281 | return "Zap" | |
282 | ||
230 | 283 | elif "niktoscan" == tag: |
231 | return "nikto" | |
284 | return "Nikto" | |
232 | 285 | elif "MetasploitV4" == tag: |
233 | return "metasploit" | |
286 | return "Metasploit" | |
234 | 287 | elif "MetasploitV5" == tag: |
235 | return "metasploit" | |
288 | return "Metasploit" | |
236 | 289 | elif "issues" == tag: |
237 | return "burp" | |
290 | return "Burp" | |
238 | 291 | elif "OWASPZAPReport" == tag: |
239 | return "zap" | |
292 | return "Zap" | |
240 | 293 | elif "ScanGroup" == tag: |
241 | return "acunetix" | |
294 | return "Acunetix" | |
242 | 295 | elif "session" == tag: |
243 | return "x1" | |
296 | return "X1" | |
244 | 297 | elif "landscapePolicy" == tag: |
245 | return "x1" | |
298 | return "X1" | |
246 | 299 | elif "entities" == tag: |
247 | return "impact" | |
300 | return "Core Impact" | |
248 | 301 | elif "NeXposeSimpleXML" == tag: |
249 | return "nexpose" | |
302 | return "Nexpose" | |
250 | 303 | elif "NexposeReport" == tag: |
251 | return "nexpose-full" | |
304 | return "NexposeFull" | |
252 | 305 | elif "ASSET_DATA_REPORT" == tag: |
253 | return "qualysguard" | |
306 | return "Qualysguard" | |
254 | 307 | elif "scanJob" == tag: |
255 | return "retina" | |
308 | return "Retina" | |
256 | 309 | elif "netsparker" == tag: |
257 | return "netsparker" | |
310 | return "Netsparker" | |
258 | 311 | elif "maltego" == tag: |
259 | return "maltego_faraday" | |
312 | return "Maltego" | |
260 | 313 | else: |
261 | 314 | return None |
14 | 14 | from persistence.persistence_managers import DbManager |
15 | 15 | from controllers.change import ChangeController |
16 | 16 | from managers.workspace_manager import WorkspaceManager |
17 | from plugins.controller import PluginControllerForApi | |
18 | ||
17 | 19 | import model.api |
18 | 20 | import model.guiapi |
19 | 21 | import apis.rest.api as restapi |
78 | 80 | self._mappers_manager, |
79 | 81 | self._changes_controller) |
80 | 82 | |
83 | # Create a PluginController and send this to UI selected. | |
84 | self._plugin_controller = PluginControllerForApi( | |
85 | 'PluginController', | |
86 | self._plugin_manager, | |
87 | self._mappers_manager | |
88 | ) | |
89 | ||
81 | 90 | if self.args.cli: |
82 | self.app = CliApp(self._workspace_manager) | |
91 | self.app = CliApp(self._workspace_manager, self._plugin_controller) | |
83 | 92 | CONF.setMergeStrategy("new") |
84 | 93 | else: |
85 | 94 | self.app = UiFactory.create(self._model_controller, |
86 | 95 | self._plugin_manager, |
87 | 96 | self._workspace_manager, |
97 | self._plugin_controller, | |
88 | 98 | self.args.gui) |
89 | 99 | |
90 | 100 | self.timer = TimerClass() |
117 | 127 | self._model_controller.start() |
118 | 128 | model.api.startAPIServer() |
119 | 129 | restapi.startAPIs( |
120 | self._plugin_manager, | |
130 | self._plugin_controller, | |
121 | 131 | self._model_controller, |
122 | self._mappers_manager, | |
123 | 132 | CONF.getApiConInfoHost(), |
124 | CONF.getApiRestfulConInfoPort()) | |
133 | CONF.getApiRestfulConInfoPort() | |
134 | ) | |
125 | 135 | |
126 | 136 | model.api.devlog("Faraday ready...") |
127 | 137 |
9 | 9 | |
10 | 10 | |
11 | 11 | class CliApp(): |
12 | def __init__(self, workspace_manager): | |
12 | def __init__(self, workspace_manager, plugin_controller): | |
13 | 13 | self.workspace_manager = workspace_manager |
14 | self.plugin_controller = plugin_controller | |
14 | 15 | |
15 | 16 | def run(self, args): |
16 | 17 | workspace = args.workspace |
23 | 24 | getLogger(self).error(str(e)) |
24 | 25 | return -1 |
25 | 26 | |
26 | rp = ReportProcessor() | |
27 | rp = ReportProcessor(self.plugin_controller) | |
27 | 28 | rp.processReport(args.filename) |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | ||
10 | import errno | |
11 | from cStringIO import StringIO | |
12 | import multiprocessing | |
13 | import os | |
14 | import Queue | |
15 | import shlex | |
16 | import time | |
17 | ||
18 | from plugins.plugin import PluginProcess | |
19 | import model.api | |
20 | from model.commands_history import CommandRunInformation | |
21 | from plugins.modelactions import modelactions | |
22 | from utils.logs import getLogger | |
23 | ||
24 | from config.globals import ( | |
25 | CONST_FARADAY_HOME_PATH, | |
26 | CONST_FARADAY_ZSH_OUTPUT_PATH) | |
27 | ||
28 | ||
29 | class PluginControllerBase(object): | |
30 | """ | |
31 | TODO: Doc string. | |
32 | """ | |
33 | def __init__(self, id, plugin_manager, mapper_manager): | |
34 | self.plugin_manager = plugin_manager | |
35 | self._plugins = plugin_manager.getPlugins() | |
36 | self.id = id | |
37 | self._actionDispatcher = None | |
38 | self._setupActionDispatcher() | |
39 | self._mapper_manager = mapper_manager | |
40 | self.output_path = os.path.join( | |
41 | os.path.expanduser(CONST_FARADAY_HOME_PATH), | |
42 | CONST_FARADAY_ZSH_OUTPUT_PATH) | |
43 | ||
44 | def _find_plugin(self, plugin_id): | |
45 | return self._plugins.get(plugin_id, None) | |
46 | ||
47 | def _is_command_malformed(self, original_command, modified_command): | |
48 | """ | |
49 | Checks if the command to be executed is safe and it's not in the | |
50 | block list defined by the user. Returns False if the modified | |
51 | command is ok, True if otherwise. | |
52 | """ | |
53 | block_chars = set(["|", "$", "#"]) | |
54 | ||
55 | if original_command == modified_command: | |
56 | return False | |
57 | ||
58 | orig_cmd_args = shlex.split(original_command) | |
59 | ||
60 | if not isinstance(modified_command, basestring): | |
61 | modified_command = "" | |
62 | mod_cmd_args = shlex.split(modified_command) | |
63 | ||
64 | block_flag = False | |
65 | orig_args_len = len(orig_cmd_args) | |
66 | for index in xrange(0, len(mod_cmd_args)): | |
67 | if (index < orig_args_len and | |
68 | orig_cmd_args[index] == mod_cmd_args[index]): | |
69 | continue | |
70 | ||
71 | for char in block_chars: | |
72 | if char in mod_cmd_args[index]: | |
73 | block_flag = True | |
74 | break | |
75 | ||
76 | return block_flag | |
77 | ||
78 | def _get_plugins_by_input(self, current_input): | |
79 | """ | |
80 | Finds a plugin that can parse the current input and returns | |
81 | the plugin object. Otherwise returns None. | |
82 | """ | |
83 | for plugin in self._plugins.itervalues(): | |
84 | if plugin.canParseCommandString(current_input): | |
85 | return plugin | |
86 | return None | |
87 | ||
88 | def getAvailablePlugins(self): | |
89 | """ | |
90 | Return a dictionary with the available plugins. | |
91 | Plugin ID's as keys and plugin instences as values | |
92 | """ | |
93 | return self._plugins | |
94 | ||
95 | def processOutput(self, plugin, output, isReport=False): | |
96 | output_queue = multiprocessing.JoinableQueue() | |
97 | new_elem_queue = multiprocessing.Queue() | |
98 | ||
99 | plugin_process = PluginProcess( | |
100 | plugin, output_queue, new_elem_queue, isReport) | |
101 | ||
102 | getLogger(self).debug( | |
103 | "Created plugin_process (%d) for plugin instance (%d)" % | |
104 | (id(plugin_process), id(plugin))) | |
105 | ||
106 | plugin_process.start() | |
107 | ||
108 | output_queue.put(output) | |
109 | output_queue.put(None) | |
110 | output_queue.join() | |
111 | ||
112 | self._processAction(modelactions.PLUGINSTART, []) | |
113 | ||
114 | while True: | |
115 | try: | |
116 | current_action = new_elem_queue.get(block=False) | |
117 | if current_action is None: | |
118 | break | |
119 | action = current_action[0] | |
120 | parameters = current_action[1:] | |
121 | getLogger(self).debug( | |
122 | "Core: Processing a new '%s', parameters (%s)\n" % | |
123 | (action, str(parameters))) | |
124 | self._processAction(action, parameters) | |
125 | ||
126 | except Queue.Empty: | |
127 | continue | |
128 | except IOError, e: | |
129 | if e.errno == errno.EINTR: | |
130 | continue | |
131 | else: | |
132 | getLogger(self).debug( | |
133 | "new_elem_queue Exception - " | |
134 | "something strange happened... " | |
135 | "unhandled exception?") | |
136 | break | |
137 | except Exception: | |
138 | getLogger(self).debug( | |
139 | "new_elem_queue Exception - " | |
140 | "something strange happened... " | |
141 | "unhandled exception?") | |
142 | break | |
143 | self._processAction(modelactions.PLUGINEND, []) | |
144 | ||
145 | def _processAction(self, action, parameters): | |
146 | """ | |
147 | decodes and performs the action given | |
148 | It works kind of a dispatcher | |
149 | """ | |
150 | getLogger(self).debug( | |
151 | "_processAction - %s - parameters = %s" % | |
152 | (action, str(parameters))) | |
153 | self._actionDispatcher[action](*parameters) | |
154 | ||
155 | def _setupActionDispatcher(self): | |
156 | self._actionDispatcher = { | |
157 | modelactions.ADDHOST: model.api.addHost, | |
158 | modelactions.CADDHOST: model.api.createAndAddHost, | |
159 | modelactions.ADDINTERFACE: model.api.addInterface, | |
160 | modelactions.CADDINTERFACE: model.api.createAndAddInterface, | |
161 | modelactions.ADDSERVICEINT: model.api.addServiceToInterface, | |
162 | modelactions.ADDSERVICEAPP: model.api.addServiceToApplication, | |
163 | modelactions.CADDSERVICEINT: model.api.createAndAddServiceToInterface, | |
164 | modelactions.CADDSERVICEAPP: model.api.createAndAddServiceToApplication, | |
165 | modelactions.ADDAPPLICATION: model.api.addApplication, | |
166 | modelactions.CADDAPPLICATION: model.api.createAndAddApplication, | |
167 | modelactions.DELSERVICEINT: model.api.delServiceFromInterface, | |
168 | #Vulnerability | |
169 | modelactions.ADDVULNINT: model.api.addVulnToInterface, | |
170 | modelactions.CADDVULNINT: model.api.createAndAddVulnToInterface, | |
171 | modelactions.ADDVULNAPP: model.api.addVulnToApplication, | |
172 | modelactions.CADDVULNAPP: model.api.createAndAddVulnToApplication, | |
173 | modelactions.ADDVULNHOST: model.api.addVulnToHost, | |
174 | modelactions.CADDVULNHOST: model.api.createAndAddVulnToHost, | |
175 | modelactions.ADDVULNSRV: model.api.addVulnToService, | |
176 | modelactions.CADDVULNSRV: model.api.createAndAddVulnToService, | |
177 | #VulnWeb | |
178 | modelactions.ADDVULNWEBSRV: model.api.addVulnWebToService, | |
179 | modelactions.CADDVULNWEBSRV: model.api.createAndAddVulnWebToService, | |
180 | #Note | |
181 | modelactions.ADDNOTEINT: model.api.addNoteToInterface, | |
182 | modelactions.CADDNOTEINT: model.api.createAndAddNoteToInterface, | |
183 | modelactions.ADDNOTEAPP: model.api.addNoteToApplication, | |
184 | modelactions.CADDNOTEAPP: model.api.createAndAddNoteToApplication, | |
185 | modelactions.ADDNOTEHOST: model.api.addNoteToHost, | |
186 | modelactions.CADDNOTEHOST: model.api.createAndAddNoteToHost, | |
187 | modelactions.ADDNOTESRV: model.api.addNoteToService, | |
188 | modelactions.CADDNOTESRV: model.api.createAndAddNoteToService, | |
189 | modelactions.ADDNOTENOTE: model.api.addNoteToNote, | |
190 | modelactions.CADDNOTENOTE: model.api.createAndAddNoteToNote, | |
191 | #Creds | |
192 | modelactions.CADDCREDSRV: model.api.createAndAddCredToService, | |
193 | modelactions.ADDCREDSRV: model.api.addCredToService, | |
194 | #LOG | |
195 | modelactions.LOG: model.api.log, | |
196 | modelactions.DEVLOG: model.api.devlog, | |
197 | # Plugin state | |
198 | modelactions.PLUGINSTART: model.api.pluginStart, | |
199 | modelactions.PLUGINEND: model.api.pluginEnd | |
200 | } | |
201 | ||
202 | def updatePluginSettings(self, plugin_id, new_settings): | |
203 | if plugin_id in self._plugins: | |
204 | self._plugins[plugin_id].updateSettings(new_settings) | |
205 | ||
206 | ||
207 | class PluginController(PluginControllerBase): | |
208 | """ | |
209 | This class is going to be deprecated once we dump qt3 | |
210 | """ | |
211 | def __init__(self, id, plugin_manager, mapper_manager): | |
212 | PluginControllerBase.__init__(self, id, plugin_manager, mapper_manager) | |
213 | self._active_plugin = None | |
214 | self.last_command_information = None | |
215 | self._buffer = StringIO() | |
216 | ||
217 | def setActivePlugin(self, plugin): | |
218 | self._active_plugin = plugin | |
219 | ||
220 | def processCommandInput(self, prompt, username, current_path, command_string, interactive): | |
221 | """ | |
222 | Receives the prompt that the current session has, the actual command_string that | |
223 | the user typed and if the command is interactive. If it is interactive the | |
224 | plugin controller does not choose a new active plugin but use the one the | |
225 | is already set (if none is set it raises an exeception). | |
226 | ||
227 | If always returns an string. It could be modified by the active plugin or, if | |
228 | there is none available, it will return the original command_string. | |
229 | """ | |
230 | ||
231 | if interactive: | |
232 | return None | |
233 | else: | |
234 | self._disable_active_plugin() | |
235 | ||
236 | choosen_plugin = self._get_plugins_by_input(command_string) | |
237 | if choosen_plugin is None: | |
238 | model.api.devlog("There is no active plugin to handle current command/user input") | |
239 | return None | |
240 | self._active_plugin = choosen_plugin | |
241 | ||
242 | modified_cmd_string = self._active_plugin.processCommandString( | |
243 | username, | |
244 | current_path, | |
245 | command_string) | |
246 | ||
247 | if self._is_command_malformed(command_string, modified_cmd_string): | |
248 | return None | |
249 | else: | |
250 | cmd_info = CommandRunInformation( | |
251 | **{'workspace': model.api.getActiveWorkspace().name, | |
252 | 'itime': time.time(), | |
253 | 'command': command_string.split()[0], | |
254 | 'params': ' '.join(command_string.split()[1:])}) | |
255 | self._mapper_manager.save(cmd_info) | |
256 | ||
257 | self.last_command_information = cmd_info | |
258 | ||
259 | return modified_cmd_string if isinstance(modified_cmd_string, basestring) else None | |
260 | ||
261 | def storeCommandOutput(self, output): | |
262 | """ | |
263 | Receives and output string and stores it in the buffer. Returns False | |
264 | if the output was not added to the plugin controllers buffer. Returns | |
265 | True otherwise. | |
266 | """ | |
267 | if not self.getActivePluginStatus(): | |
268 | return False | |
269 | else: | |
270 | self._buffer.write(output) | |
271 | return True | |
272 | ||
273 | def getPluginAutocompleteOptions(self, prompt, username, current_path, command_string, interactive): | |
274 | """ | |
275 | This method should return a list of possible completitions based on the | |
276 | current output. | |
277 | TODO: We should think how to actually implement this... | |
278 | May be checking which plugin should handle the command in the current input | |
279 | and then passing it to the plugin to return a list of possible values. | |
280 | Each plugin implementation should return possible option according to | |
281 | what was received since it's the plugin the one it knows the command line | |
282 | parameters, etc. | |
283 | """ | |
284 | if interactive: | |
285 | return None | |
286 | else: | |
287 | self._disable_active_plugin() | |
288 | ||
289 | choosen_plugin = self._get_plugins_by_input(command_string) | |
290 | if choosen_plugin is None: | |
291 | model.api.devlog("There is no active plugin to handle current command/user input") | |
292 | return None | |
293 | ||
294 | self._active_plugin = choosen_plugin | |
295 | ||
296 | new_options = self._active_plugin.getCompletitionSuggestionsList(command_string) | |
297 | return new_options | |
298 | ||
299 | def getActivePluginStatus(self): | |
300 | """ | |
301 | Returns true if an active plugin is set, otherwise return False. | |
302 | """ | |
303 | return (self._active_plugin is not None) | |
304 | ||
305 | def _disable_active_plugin(self): | |
306 | """ | |
307 | This method is suppose to disable the active plugin. | |
308 | """ | |
309 | model.api.devlog("Disabling active plugin") | |
310 | self._active_plugin = None | |
311 | ||
312 | def onCommandFinished(self): | |
313 | """ | |
314 | This method is called when the last executed command has finished. It's | |
315 | in charge of giving the plugin the output to be parsed. | |
316 | """ | |
317 | cmd_info = self.last_command_information | |
318 | cmd_info.duration = time.time() - cmd_info.itime | |
319 | self._mapper_manager.save(cmd_info) | |
320 | ||
321 | if self._active_plugin.has_custom_output(): | |
322 | if not os.path.isfile(self._active_plugin.get_custom_file_path()): | |
323 | model.api.devlog("Report file PluginController output file (%s) not created" % self._active_plugin.get_custom_file_path()) | |
324 | return False | |
325 | output_file = open(self._active_plugin.get_custom_file_path(), 'r') | |
326 | output = output_file.read() | |
327 | self._buffer.seek(0) | |
328 | self._buffer.truncate() | |
329 | self._buffer.write(output) | |
330 | ||
331 | self.processOutput(self._active_plugin, self._buffer.getvalue()) | |
332 | ||
333 | self._buffer.seek(0) | |
334 | self._buffer.truncate() | |
335 | model.api.devlog("PluginController buffer cleared") | |
336 | ||
337 | self._disable_active_plugin() | |
338 | ||
339 | return True | |
340 | ||
341 | ||
342 | class PluginControllerForApi(PluginControllerBase): | |
343 | def __init__(self, id, plugin_manager, mapper_manager): | |
344 | PluginControllerBase.__init__( | |
345 | self, id, plugin_manager, mapper_manager) | |
346 | self._active_plugins = {} | |
347 | self.plugin_sets = {} | |
348 | self.plugin_manager.addController(self, self.id) | |
349 | ||
350 | def _get_plugins_by_input(self, cmd, plugin_set): | |
351 | for plugin in plugin_set.itervalues(): | |
352 | if plugin.canParseCommandString(cmd): | |
353 | return plugin | |
354 | return None | |
355 | ||
356 | def createPluginSet(self, id): | |
357 | self.plugin_sets[id] = self.plugin_manager.getPlugins() | |
358 | ||
359 | def processCommandInput(self, pid, cmd, pwd): | |
360 | """ | |
361 | This method tries to find a plugin to parse the command sent | |
362 | by the terminal (identiefied by the process id). | |
363 | """ | |
364 | if pid not in self.plugin_sets: | |
365 | self.createPluginSet(pid) | |
366 | ||
367 | plugin = self._get_plugins_by_input(cmd, self.plugin_sets[pid]) | |
368 | ||
369 | if plugin: | |
370 | modified_cmd_string = plugin.processCommandString("", pwd, cmd) | |
371 | if not self._is_command_malformed(cmd, modified_cmd_string): | |
372 | ||
373 | cmd_info = CommandRunInformation( | |
374 | **{'workspace': model.api.getActiveWorkspace().name, | |
375 | 'itime': time.time(), | |
376 | 'command': cmd.split()[0], | |
377 | 'params': ' '.join(cmd.split()[1:])}) | |
378 | self._mapper_manager.save(cmd_info) | |
379 | self._active_plugins[pid] = plugin, cmd_info | |
380 | ||
381 | return plugin.id, modified_cmd_string | |
382 | ||
383 | return None, None | |
384 | ||
385 | def getPluginAutocompleteOptions(self, command_string): | |
386 | """ | |
387 | Not implementend right now. Maybe it's better to use | |
388 | zsh autocomplete features | |
389 | """ | |
390 | pass | |
391 | ||
392 | def onCommandFinished(self, pid, exit_code, term_output): | |
393 | ||
394 | if pid not in self._active_plugins.keys(): | |
395 | return False | |
396 | if exit_code != 0: | |
397 | del self._active_plugins[pid] | |
398 | return False | |
399 | ||
400 | plugin, cmd_info = self._active_plugins.get(pid) | |
401 | ||
402 | cmd_info.duration = time.time() - cmd_info.itime | |
403 | self._mapper_manager.save(cmd_info) | |
404 | ||
405 | self.processOutput(plugin, term_output) | |
406 | del self._active_plugins[pid] | |
407 | return True | |
408 | ||
409 | def processReport(self, plugin, filepath): | |
410 | if plugin in self._plugins: | |
411 | self.processOutput(self._plugins[plugin], filepath, True) | |
412 | return True | |
413 | return False | |
414 | ||
415 | def clearActivePlugins(self): | |
416 | self._active_plugins = {} | |
417 | ||
418 | def updatePluginSettings(self, plugin_id, new_settings): | |
419 | for plugin_set in self.plugin_sets.values(): | |
420 | if plugin_id in plugin_set: | |
421 | plugin_set[plugin_id].updateSettings(new_settings) | |
422 | if plugin_id in self._plugins: | |
423 | self._plugins[plugin_id].updateSettings(new_settings) |
7 | 7 | |
8 | 8 | ''' |
9 | 9 | |
10 | import multiprocessing | |
11 | import shlex | |
12 | import copy_reg | |
13 | import types | |
14 | import model.api | |
15 | from cStringIO import StringIO | |
16 | import os | |
17 | import re | |
18 | import Queue | |
19 | import traceback | |
20 | import model.common | |
21 | import errno | |
22 | from model.common import ( | |
23 | factory, ModelObjectVuln, ModelObjectVulnWeb, | |
24 | ModelObjectNote, ModelObjectCred) | |
25 | from model.hosts import Host, Interface, Service | |
10 | from plugins.plugin import PluginBase as PluginBaseExt | |
26 | 11 | |
27 | from model.commands_history import CommandRunInformation | |
28 | from utils.common import sha1OfStr | |
29 | ||
30 | from time import time | |
31 | ||
32 | from config.configuration import getInstanceConfiguration | |
33 | CONF = getInstanceConfiguration() | |
34 | ||
35 | ||
36 | def _pickle_method(method): | |
37 | func_name = method.im_func.__name__ | |
38 | obj = method.im_self | |
39 | cls = method.im_class | |
40 | return _unpickle_method, (func_name, obj, cls) | |
41 | ||
42 | ||
43 | def _unpickle_method(func_name, obj, cls): | |
44 | for cls in cls.mro(): | |
45 | try: | |
46 | func = cls.__dict__[func_name] | |
47 | except KeyError: | |
48 | pass | |
49 | else: | |
50 | break | |
51 | return func.__get__(obj, cls) | |
52 | ||
53 | copy_reg.pickle(types.MethodType, _pickle_method, _unpickle_method) | |
54 | ||
55 | ||
56 | class modelactions: | |
57 | ADDHOST = 2000 | |
58 | CADDHOST = 2001 | |
59 | ADDINTERFACE = 2002 | |
60 | CADDINTERFACE = 2003 | |
61 | ADDSERVICEINT = 2004 | |
62 | ADDSERVICEAPP = 2005 | |
63 | CADDSERVICEINT = 2006 | |
64 | CADDSERVICEAPP = 2007 | |
65 | CADDSERVICEHOST = 2008 | |
66 | ADDAPPLICATION = 2009 | |
67 | CADDAPPLICATION = 2010 | |
68 | ADDVULNINT = 2013 | |
69 | CADDVULNINT = 2014 | |
70 | ADDVULNAPP = 2015 | |
71 | CADDVULNAPP = 2016 | |
72 | ADDVULNHOST = 2017 | |
73 | CADDVULNHOST = 2018 | |
74 | ADDVULNSRV = 2019 | |
75 | CADDVULNSRV = 2020 | |
76 | ADDNOTEINT = 2021 | |
77 | CADDNOTEINT = 2022 | |
78 | ADDNOTEAPP = 2023 | |
79 | CADDNOTEAPP = 2024 | |
80 | ADDNOTEHOST = 2025 | |
81 | CADDNOTEHOST = 2026 | |
82 | ADDNOTESRV = 2027 | |
83 | CADDNOTESRV = 2028 | |
84 | CADDNOTEVULN = 2030 | |
85 | CADDNOTEVULN = 2031 | |
86 | LOG = 2032 | |
87 | DEVLOG = 2033 | |
88 | DELSERVICEINT = 2034 | |
89 | ADDCREDSRV = 2035 | |
90 | CADDCREDSRV = 2036 | |
91 | ADDVULNWEBSRV = 2037 | |
92 | CADDVULNWEBSRV = 2038 | |
93 | ADDNOTENOTE = 2039 | |
94 | CADDNOTENOTE = 2039 | |
95 | PLUGINSTART = 3000 | |
96 | PLUGINEND = 3001 | |
97 | ||
98 | __descriptions = { | |
99 | ADDHOST: "ADDHOST", | |
100 | CADDHOST: "CADDHOST", | |
101 | ADDINTERFACE: "ADDINTERFACE", | |
102 | CADDINTERFACE: "CADDINTERFACE", | |
103 | ADDSERVICEINT: "ADDSERVICEINT", | |
104 | ADDSERVICEAPP: "ADDSERVICEAPP", | |
105 | CADDSERVICEINT: "CADDSERVICEINT", | |
106 | CADDSERVICEAPP: "CADDSERVICEAPP", | |
107 | CADDSERVICEHOST: "CADDSERVICEHOST", | |
108 | ADDAPPLICATION: "ADDAPPLICATION", | |
109 | CADDAPPLICATION: "CADDAPPLICATION", | |
110 | ADDVULNINT: "ADDVULNINT", | |
111 | CADDVULNINT: "CADDVULNINT", | |
112 | ADDVULNAPP: "ADDVULNAPP", | |
113 | CADDVULNAPP: "CADDVULNAPP", | |
114 | ADDVULNHOST: "ADDVULNHOST", | |
115 | CADDVULNHOST: "CADDVULNHOST", | |
116 | ADDVULNSRV: "ADDVULNSRV", | |
117 | CADDVULNSRV: "CADDVULNSRV", | |
118 | LOG: "LOG", | |
119 | DEVLOG: "DEVLOG", | |
120 | DELSERVICEINT: "DELSERVICEINT", | |
121 | ADDCREDSRV: "ADDCREDINT", | |
122 | ADDVULNWEBSRV: "ADDVULNWEBSRV", | |
123 | CADDVULNWEBSRV: "CADDVULNWEBSRV", | |
124 | ADDNOTENOTE: "ADDNOTENOTE", | |
125 | CADDNOTENOTE: "CADDNOTENOTE", | |
126 | PLUGINSTART: "PLUGINSTART", | |
127 | PLUGINEND: "PLUGINEND" | |
128 | } | |
129 | ||
130 | @staticmethod | |
131 | def getDescription(action): | |
132 | return modelactions.__descriptions.get(action, "") | |
133 | ||
134 | ||
135 | class PluginControllerBase(object): | |
136 | """ | |
137 | TODO: Doc string. | |
138 | """ | |
139 | def __init__(self, id, available_plugins, mapper_manager): | |
140 | self._plugins = available_plugins | |
141 | self.id = id | |
142 | self._actionDispatcher = None | |
143 | self._setupActionDispatcher() | |
144 | ||
145 | self._mapper_manager = mapper_manager | |
146 | ||
147 | def _find_plugin(self, new_plugin_id): | |
148 | try: | |
149 | return self._plugins[new_plugin_id] | |
150 | except KeyError: | |
151 | return None | |
152 | ||
153 | def _is_command_malformed(self, original_command, modified_command): | |
154 | """ | |
155 | Checks if the command to be executed is safe and it's not in the block list | |
156 | defined by the user. Returns False if the modified command is ok, True if | |
157 | otherwise. | |
158 | ||
159 | TODO: Use global command block list. | |
160 | TODO: complete block idioms | |
161 | """ | |
162 | block_chars = set(["|", "$", "#"]) | |
163 | ||
164 | if original_command == modified_command: | |
165 | return False | |
166 | ||
167 | orig_cmd_args = shlex.split(original_command) | |
168 | ||
169 | if not isinstance(modified_command, basestring): | |
170 | modified_command = "" | |
171 | mod_cmd_args = shlex.split(modified_command) | |
172 | ||
173 | block_flag = False | |
174 | orig_args_len = len(orig_cmd_args) | |
175 | for index in xrange(0, len(mod_cmd_args)): | |
176 | if index < orig_args_len and orig_cmd_args[index] == mod_cmd_args[index]: | |
177 | continue | |
178 | ||
179 | for char in block_chars: | |
180 | if char in mod_cmd_args[index]: | |
181 | block_flag = True | |
182 | break | |
183 | ||
184 | return block_flag | |
185 | ||
186 | def _get_plugins_by_input(self, current_input): | |
187 | """ | |
188 | Finds a plugin that can parse the current input and returns the plugin | |
189 | object. Otherwise returns None. | |
190 | """ | |
191 | for plugin in self._plugins.itervalues(): | |
192 | if plugin.canParseCommandString(current_input): | |
193 | return plugin | |
194 | return None | |
195 | ||
196 | def getAvailablePlugins(self): | |
197 | """ | |
198 | Return a dictionary with the available plugins. | |
199 | Plugin ID's as keys and plugin instences as values | |
200 | """ | |
201 | return self._plugins | |
202 | ||
203 | def processOutput(self, plugin, output): | |
204 | output_queue = multiprocessing.JoinableQueue() | |
205 | new_elem_queue = multiprocessing.Queue() | |
206 | ||
207 | plugin_process = PluginProcess(plugin, output_queue, new_elem_queue) | |
208 | model.api.devlog("PluginController (%d) - Created plugin_process (%d) for plugin instance (%d)" % | |
209 | (id(self), id(plugin_process), id(plugin))) | |
210 | ||
211 | plugin_process.start() | |
212 | ||
213 | output_queue.put(output) | |
214 | output_queue.put(None) | |
215 | output_queue.join() | |
216 | ||
217 | self._processAction(modelactions.PLUGINSTART, []) | |
218 | ||
219 | #model.api.devlog("Core: queue size '%s'" % new_elem_queue.qsize()) | |
220 | while True: | |
221 | try: | |
222 | current_action = new_elem_queue.get(block=False) | |
223 | if current_action is None: | |
224 | break | |
225 | action = current_action[0] | |
226 | parameters = current_action[1:] | |
227 | model.api.devlog("Core: Processing a new '%s' , parameters (%s) \n" % (action,str(parameters))) | |
228 | self._processAction(action, parameters) | |
229 | ||
230 | except Queue.Empty: | |
231 | continue | |
232 | except IOError, e: | |
233 | if e.errno == errno.EINTR: | |
234 | continue | |
235 | else: | |
236 | model.api.devlog("PluginController.onCommandFinished - new_elem_queue Exception- something strange happened... unhandled exception?") | |
237 | model.api.devlog(traceback.format_exc()) | |
238 | break | |
239 | except Exception: | |
240 | model.api.devlog("PluginController.onCommandFinished - new_elem_queue Exception- something strange happened... unhandled exception?") | |
241 | model.api.devlog(traceback.format_exc()) | |
242 | break | |
243 | self._processAction(modelactions.PLUGINEND, []) | |
244 | ||
245 | def _processAction(self, action, parameters): | |
246 | """ | |
247 | decodes and performs the action given | |
248 | It works kind of a dispatcher | |
249 | """ | |
250 | model.api.devlog("(PluginController) _processAction - %s - parameters = %s" % (action, str(parameters))) | |
251 | res = self._actionDispatcher[action](*parameters) | |
252 | ||
253 | def _setupActionDispatcher(self): | |
254 | self._actionDispatcher = { | |
255 | modelactions.ADDHOST : model.api.addHost, | |
256 | modelactions.CADDHOST : model.api.createAndAddHost, | |
257 | modelactions.ADDINTERFACE : model.api.addInterface, | |
258 | modelactions.CADDINTERFACE : model.api.createAndAddInterface, | |
259 | modelactions.ADDSERVICEINT : model.api.addServiceToInterface, | |
260 | modelactions.ADDSERVICEAPP : model.api.addServiceToApplication, | |
261 | modelactions.CADDSERVICEINT : model.api.createAndAddServiceToInterface, | |
262 | modelactions.CADDSERVICEAPP : model.api.createAndAddServiceToApplication, | |
263 | modelactions.ADDAPPLICATION : model.api.addApplication, | |
264 | modelactions.CADDAPPLICATION : model.api.createAndAddApplication, | |
265 | modelactions.DELSERVICEINT : model.api.delServiceFromInterface, | |
266 | #Vulnerability | |
267 | modelactions.ADDVULNINT : model.api.addVulnToInterface, | |
268 | modelactions.CADDVULNINT : model.api.createAndAddVulnToInterface, | |
269 | modelactions.ADDVULNAPP : model.api.addVulnToApplication, | |
270 | modelactions.CADDVULNAPP : model.api.createAndAddVulnToApplication, | |
271 | modelactions.ADDVULNHOST : model.api.addVulnToHost, | |
272 | modelactions.CADDVULNHOST : model.api.createAndAddVulnToHost, | |
273 | modelactions.ADDVULNSRV : model.api.addVulnToService, | |
274 | modelactions.CADDVULNSRV : model.api.createAndAddVulnToService, | |
275 | #VulnWeb | |
276 | modelactions.ADDVULNWEBSRV : model.api.addVulnWebToService, | |
277 | modelactions.CADDVULNWEBSRV : model.api.createAndAddVulnWebToService, | |
278 | #Note | |
279 | modelactions.ADDNOTEINT : model.api.addNoteToInterface, | |
280 | modelactions.CADDNOTEINT : model.api.createAndAddNoteToInterface, | |
281 | modelactions.ADDNOTEAPP : model.api.addNoteToApplication, | |
282 | modelactions.CADDNOTEAPP : model.api.createAndAddNoteToApplication, | |
283 | modelactions.ADDNOTEHOST : model.api.addNoteToHost, | |
284 | modelactions.CADDNOTEHOST : model.api.createAndAddNoteToHost, | |
285 | modelactions.ADDNOTESRV : model.api.addNoteToService, | |
286 | modelactions.CADDNOTESRV : model.api.createAndAddNoteToService, | |
287 | modelactions.ADDNOTENOTE : model.api.addNoteToNote, | |
288 | modelactions.CADDNOTENOTE : model.api.createAndAddNoteToNote, | |
289 | #Creds | |
290 | modelactions.CADDCREDSRV : model.api.createAndAddCredToService, | |
291 | modelactions.ADDCREDSRV : model.api.addCredToService, | |
292 | #modelactions.ADDNOTEVULN : model.api.createAndAddNoteToApplication, | |
293 | #modelactions.CADDNOTEVULN : model.api.createAndAddNoteToApplication, | |
294 | #LOG | |
295 | modelactions.LOG : model.api.log, | |
296 | modelactions.DEVLOG : model.api.devlog, | |
297 | # Plugin state | |
298 | modelactions.PLUGINSTART: model.api.pluginStart, | |
299 | modelactions.PLUGINEND: model.api.pluginEnd | |
300 | } | |
301 | ||
302 | def updatePluginSettings(self, plugin_id, new_settings): | |
303 | if plugin_id in self._plugins: | |
304 | self._plugins[plugin_id].updateSettings(new_settings) | |
305 | ||
306 | ||
307 | class PluginController(PluginControllerBase): | |
308 | """ | |
309 | TODO: Doc string. | |
310 | """ | |
311 | def __init__(self, id, available_plugins, mapper_manager): | |
312 | PluginControllerBase.__init__(self, id, available_plugins, mapper_manager) | |
313 | self._active_plugin = None | |
314 | self.last_command_information = None | |
315 | self._buffer = StringIO() | |
316 | ||
317 | def setActivePlugin(self, plugin): | |
318 | self._active_plugin = plugin | |
319 | ||
320 | def processCommandInput(self, prompt, username, current_path, command_string, interactive): | |
321 | """ | |
322 | Receives the prompt that the current session has, the actual command_string that | |
323 | the user typed and if the command is interactive. If it is interactive the | |
324 | plugin controller does not choose a new active plugin but use the one the | |
325 | is already set (if none is set it raises an exeception). | |
326 | ||
327 | If always returns an string. It could be modified by the active plugin or, if | |
328 | there is none available, it will return the original command_string. | |
329 | """ | |
330 | ||
331 | if interactive: | |
332 | return None | |
333 | else: | |
334 | self._disable_active_plugin() | |
335 | ||
336 | choosen_plugin = self._get_plugins_by_input(command_string) | |
337 | if choosen_plugin is None: | |
338 | model.api.devlog("There is no active plugin to handle current command/user input") | |
339 | return None | |
340 | self._active_plugin = choosen_plugin | |
341 | ||
342 | modified_cmd_string = self._active_plugin.processCommandString( | |
343 | username, | |
344 | current_path, | |
345 | command_string) | |
346 | ||
347 | if self._is_command_malformed(command_string, modified_cmd_string): | |
348 | return None | |
349 | else: | |
350 | cmd_info = CommandRunInformation( | |
351 | **{'workspace': model.api.getActiveWorkspace().name, | |
352 | 'itime': time(), | |
353 | 'command': command_string.split()[0], | |
354 | 'params': ' '.join(command_string.split()[1:])}) | |
355 | self._mapper_manager.save(cmd_info) | |
356 | ||
357 | self.last_command_information = cmd_info | |
358 | ||
359 | return modified_cmd_string if isinstance(modified_cmd_string, basestring) else None | |
360 | ||
361 | def storeCommandOutput(self, output): | |
362 | """ | |
363 | Receives and output string and stores it in the buffer. Returns False | |
364 | if the output was not added to the plugin controllers buffer. Returns | |
365 | True otherwise. | |
366 | """ | |
367 | if not self.getActivePluginStatus(): | |
368 | return False | |
369 | else: | |
370 | self._buffer.write(output) | |
371 | return True | |
372 | ||
373 | def getPluginAutocompleteOptions(self, prompt, username, current_path, command_string, interactive): | |
374 | """ | |
375 | This method should return a list of possible completitions based on the | |
376 | current output. | |
377 | TODO: We should think how to actually implement this... | |
378 | May be checking which plugin should handle the command in the current input | |
379 | and then passing it to the plugin to return a list of possible values. | |
380 | Each plugin implementation should return possible option according to | |
381 | what was received since it's the plugin the one it knows the command line | |
382 | parameters, etc. | |
383 | """ | |
384 | if interactive: | |
385 | return None | |
386 | else: | |
387 | self._disable_active_plugin() | |
388 | ||
389 | choosen_plugin = self._get_plugins_by_input(command_string) | |
390 | if choosen_plugin is None: | |
391 | model.api.devlog("There is no active plugin to handle current command/user input") | |
392 | return None | |
393 | ||
394 | self._active_plugin = choosen_plugin | |
395 | ||
396 | new_options = self._active_plugin.getCompletitionSuggestionsList(command_string) | |
397 | return new_options | |
398 | ||
399 | def getActivePluginStatus(self): | |
400 | """ | |
401 | Returns true if an active plugin is set, otherwise return False. | |
402 | """ | |
403 | return (self._active_plugin is not None) | |
404 | ||
405 | def _disable_active_plugin(self): | |
406 | """ | |
407 | This method is suppose to disable the active plugin. | |
408 | """ | |
409 | model.api.devlog("Disabling active plugin") | |
410 | self._active_plugin = None | |
411 | ||
412 | def onCommandFinished(self): | |
413 | """ | |
414 | This method is called when the last executed command has finished. It's | |
415 | in charge of giving the plugin the output to be parsed. | |
416 | """ | |
417 | cmd_info = self.last_command_information | |
418 | cmd_info.duration = time() - cmd_info.itime | |
419 | self._mapper_manager.save(cmd_info) | |
420 | ||
421 | if self._active_plugin.has_custom_output(): | |
422 | if not os.path.isfile(self._active_plugin.get_custom_file_path()): | |
423 | model.api.devlog("Report file PluginController output file (%s) not created" % self._active_plugin.get_custom_file_path()) | |
424 | return False | |
425 | output_file = open(self._active_plugin.get_custom_file_path(), 'r') | |
426 | output = output_file.read() | |
427 | self._buffer.seek(0) | |
428 | self._buffer.truncate() | |
429 | self._buffer.write(output) | |
430 | ||
431 | self.processOutput(self._active_plugin, self._buffer.getvalue()) | |
432 | ||
433 | self._buffer.seek(0) | |
434 | self._buffer.truncate() | |
435 | model.api.devlog("PluginController buffer cleared") | |
436 | ||
437 | self._disable_active_plugin() | |
438 | ||
439 | return True | |
440 | ||
441 | ||
442 | class PluginControllerForApi(PluginControllerBase): | |
443 | def __init__(self, id, available_plugins, mapper_manager): | |
444 | PluginControllerBase.__init__(self, id, available_plugins, mapper_manager) | |
445 | self._active_plugins = {} | |
446 | ||
447 | def processCommandInput(self, command_string): | |
448 | ||
449 | plugin = self._get_plugins_by_input(command_string) | |
450 | ||
451 | if plugin: | |
452 | modified_cmd_string = plugin.processCommandString("", "", command_string) | |
453 | if not self._is_command_malformed(command_string, modified_cmd_string): | |
454 | ||
455 | cmd_info = CommandRunInformation( | |
456 | **{'workspace': model.api.getActiveWorkspace().name, | |
457 | 'itime': time(), | |
458 | 'command': command_string.split()[0], | |
459 | 'params': ' '.join(command_string.split()[1:])}) | |
460 | self._mapper_manager.save(cmd_info) | |
461 | ||
462 | self._active_plugins[command_string] = plugin, cmd_info | |
463 | ||
464 | output_file_path = None | |
465 | if plugin.has_custom_output(): | |
466 | output_file_path = plugin.get_custom_file_path() | |
467 | return True, modified_cmd_string, output_file_path | |
468 | ||
469 | return False, None, None | |
470 | ||
471 | def getPluginAutocompleteOptions(self, command_string): | |
472 | # if interactive: | |
473 | # return None | |
474 | # else: | |
475 | # self._disable_active_plugin() | |
476 | ||
477 | # choosen_plugin = self._get_plugins_by_input(command_string) | |
478 | # if choosen_plugin is None: | |
479 | # model.api.devlog("There is no active plugin to handle current command/user input") | |
480 | # return None | |
481 | ||
482 | # self._active_plugin = choosen_plugin | |
483 | ||
484 | # new_options = self._active_plugin.getCompletitionSuggestionsList(command_string) | |
485 | # return new_options | |
486 | pass | |
487 | ||
488 | def onCommandFinished(self, cmd, output): | |
489 | if cmd not in self._active_plugins.keys(): | |
490 | return False | |
491 | ||
492 | plugin, cmd_info = self._active_plugins.get(cmd) | |
493 | cmd_info.duration = time() - cmd_info.itime | |
494 | self._mapper_manager.save(cmd_info) | |
495 | ||
496 | self.processOutput(plugin, output) | |
497 | ||
498 | del self._active_plugins[cmd] | |
499 | return True | |
500 | ||
501 | def clearActivePlugins(self): | |
502 | self._active_plugins = {} | |
503 | ||
504 | ||
505 | class PluginBase(object): | |
506 | # TODO: Add class generic identifier | |
507 | class_signature = "PluginBase" | |
508 | ||
509 | def __init__(self): | |
510 | ||
511 | self.data_path = CONF.getDataPath() | |
512 | self.persistence_path = CONF.getPersistencePath() | |
513 | # Must be unique. Check that there is not | |
514 | # an existant plugin with the same id. | |
515 | # TODO: Make script that list current ids. | |
516 | self.id = None | |
517 | self._rid = id(self) | |
518 | self.version = None | |
519 | self.name = None | |
520 | self.description = "" | |
521 | self._command_regex = None | |
522 | self._output_file_path = None | |
523 | self.framework_version = None | |
524 | self._completition = {} | |
525 | self._new_elems = [] | |
526 | self._pending_actions = Queue.Queue() | |
527 | self._settings = {} | |
528 | ||
529 | def has_custom_output(self): | |
530 | return bool(self._output_file_path) | |
531 | ||
532 | def get_custom_file_path(self): | |
533 | return self._output_file_path | |
534 | ||
535 | def get_ws(self): | |
536 | return CONF.getLastWorkspace() | |
537 | ||
538 | def getSettings(self): | |
539 | for param, (param_type, value) in self._settings.iteritems(): | |
540 | yield param, value | |
541 | ||
542 | def getSetting(self, name): | |
543 | setting_type, value = self._settings[name] | |
544 | return value | |
545 | ||
546 | def addSetting(self, param, param_type, value): | |
547 | self._settings[param] = param_type, value | |
548 | ||
549 | def updateSettings(self, new_settings): | |
550 | for name, value in new_settings.iteritems(): | |
551 | setting_type, curr_value = self._settings[name] | |
552 | self._settings[name] = setting_type, setting_type(value) | |
553 | ||
554 | def canParseCommandString(self, current_input): | |
555 | """ | |
556 | This method can be overriden in the plugin implementation | |
557 | if a different kind of check is needed | |
558 | """ | |
559 | return self._command_regex is not None and\ | |
560 | self._command_regex.match(current_input.strip()) is not None | |
561 | ||
562 | ||
563 | def getCompletitionSuggestionsList(self, current_input): | |
564 | """ | |
565 | This method can be overriden in the plugin implementation | |
566 | if a different kind of check is needed | |
567 | """ | |
568 | ||
569 | words=current_input.split(" ") | |
570 | ||
571 | cword=words[len(words)-1] | |
572 | ||
573 | ||
574 | ||
575 | options={} | |
576 | for k,v in self._completition.iteritems(): | |
577 | if re.search(str("^"+cword),k,flags=re.IGNORECASE): | |
578 | ||
579 | options[k]=v | |
580 | ||
581 | return options | |
582 | ||
583 | def parseOutputString(self, output): | |
584 | """ | |
585 | This method must be implemented. | |
586 | This method will be called when the command finished executing and | |
587 | the complete output will be received to work with it | |
588 | Using the output the plugin can create and add hosts, interfaces, services, etc. | |
589 | """ | |
590 | pass | |
591 | ||
592 | def processCommandString(self, username, current_path, command_string): | |
593 | """ | |
594 | With this method a plugin can add aditional arguments to the command that | |
595 | it's going to be executed. | |
596 | """ | |
597 | return None | |
598 | ||
599 | def getParsedElementsDict(self): | |
600 | """ | |
601 | This method must be implemented and must return | |
602 | a dictionary with the following form. | |
603 | ||
604 | { 'FrameworkVersion': self.framework_version, | |
605 | 'HostList': list_of_host_dictionaries, | |
606 | 'PortList': list_of_port_dictionaries } | |
607 | ||
608 | list_of_host_dictionaries: must be 'None' or a list of | |
609 | dictionaries that have the following form | |
610 | ||
611 | { 'HostId': string, | |
612 | 'HostAddress': string, | |
613 | ... } | |
614 | ||
615 | list_of_port_dictionaries: must be 'None' or a list of | |
616 | dictionaries that have the following form | |
617 | ||
618 | { 'PortNumber': integer, | |
619 | 'Status': 'OPEN' or 'CLOSED', | |
620 | 'Service': string, | |
621 | ... } | |
622 | """ | |
623 | pass | |
624 | ||
625 | def _set_host(self): | |
626 | ||
627 | pass | |
628 | ||
629 | def __addPendingAction(self, *args): | |
630 | """ | |
631 | Adds a new pending action to the queue | |
632 | Action is build with generic args tuple. | |
633 | The caller of this function has to build the action in the right | |
634 | way since no checks are preformed over args | |
635 | """ | |
636 | self._pending_actions.put(args) | |
637 | ||
638 | def createAndAddHost(self, name, os = "unknown", category = None, update = False, old_hostname = None): | |
639 | self.__addPendingAction(modelactions.CADDHOST, name, os, category, update, old_hostname) | |
640 | return factory.generateID(Host.class_signature, name=name, os=os) | |
641 | ||
642 | def createAndAddInterface(self, host_id, name = "", mac = "00:00:00:00:00:00", | |
643 | ipv4_address = "0.0.0.0", ipv4_mask = "0.0.0.0", | |
644 | ipv4_gateway = "0.0.0.0", ipv4_dns = [], | |
645 | ipv6_address = "0000:0000:0000:0000:0000:0000:0000:0000", ipv6_prefix = "00", | |
646 | ipv6_gateway = "0000:0000:0000:0000:0000:0000:0000:0000", ipv6_dns = [], | |
647 | network_segment = "", hostname_resolution = []): | |
648 | self.__addPendingAction(modelactions.CADDINTERFACE, host_id, name, mac, ipv4_address, | |
649 | ipv4_mask, ipv4_gateway, ipv4_dns, ipv6_address, ipv6_prefix, ipv6_gateway, ipv6_dns, | |
650 | network_segment, hostname_resolution) | |
651 | return factory.generateID( | |
652 | Interface.class_signature, parent_id=host_id, name=name, mac=mac, | |
653 | ipv4_address=ipv4_address, ipv4_mask=ipv4_mask, | |
654 | ipv4_gateway=ipv4_gateway, ipv4_dns=ipv4_dns, | |
655 | ipv6_address=ipv6_address, ipv6_prefix=ipv6_prefix, | |
656 | ipv6_gateway=ipv6_gateway, ipv6_dns=ipv6_dns, | |
657 | network_segment=network_segment, | |
658 | hostname_resolution=hostname_resolution) | |
659 | ||
660 | def createAndAddServiceToInterface(self, host_id, interface_id, name, protocol = "tcp?", | |
661 | ports = [], status = "running", version = "unknown", description = ""): | |
662 | self.__addPendingAction(modelactions.CADDSERVICEINT, host_id, interface_id, name, protocol, | |
663 | ports, status, version, description) | |
664 | return factory.generateID( | |
665 | Service.class_signature, | |
666 | name=name, protocol=protocol, ports=ports, | |
667 | status=status, version=version, description=description, parent_id=interface_id) | |
668 | ||
669 | def createAndAddVulnToHost(self, host_id, name, desc="", ref=[], severity="", resolution=""): | |
670 | self.__addPendingAction(modelactions.CADDVULNHOST, host_id, name, desc, ref, severity, resolution) | |
671 | return factory.generateID( | |
672 | ModelObjectVuln.class_signature, | |
673 | name=name, desc=desc, ref=ref, severity=severity, | |
674 | resolution=resolution, parent_id=host_id) | |
675 | ||
676 | def createAndAddVulnToInterface(self, host_id, interface_id, name, desc="", ref=[], severity="", resolution=""): | |
677 | self.__addPendingAction(modelactions.CADDVULNINT, host_id, interface_id, name, desc, ref, severity, resolution) | |
678 | return factory.generateID( | |
679 | ModelObjectVuln.class_signature, | |
680 | name=name, desc=desc, ref=ref, severity=severity, | |
681 | resolution=resolution, parent_id=interface_id) | |
682 | ||
683 | def createAndAddVulnToService(self, host_id, service_id, name, desc="", ref=[], severity="", resolution=""): | |
684 | self.__addPendingAction(modelactions.CADDVULNSRV, host_id, service_id, name, desc, ref, severity, resolution) | |
685 | return factory.generateID( | |
686 | ModelObjectVuln.class_signature, | |
687 | name=name, desc=desc, ref=ref, severity=severity, | |
688 | resolution=resolution, parent_id=service_id) | |
689 | ||
690 | def createAndAddVulnWebToService(self, host_id, service_id, name, desc="", ref=[], | |
691 | severity="", resolution="", website="", path="", request="", | |
692 | response="",method="",pname="", params="",query="",category=""): | |
693 | self.__addPendingAction(modelactions.CADDVULNWEBSRV, host_id, service_id, name, desc, ref, | |
694 | severity, resolution, website, path, request, response, | |
695 | method, pname, params, query,category) | |
696 | return factory.generateID( | |
697 | ModelObjectVulnWeb.class_signature, | |
698 | name=name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
699 | website=website, path=path, request=request, response=response, | |
700 | method=method, pname=pname, params=params, query=query, | |
701 | category=category, parent_id=service_id) | |
702 | ||
703 | def createAndAddNoteToHost(self, host_id, name, text): | |
704 | self.__addPendingAction(modelactions.CADDNOTEHOST, host_id, name, text) | |
705 | return factory.generateID( | |
706 | ModelObjectNote.class_signature, | |
707 | name=name, text=text, parent_id=host_id) | |
708 | ||
709 | def createAndAddNoteToInterface(self, host_id, interface_id, name, text): | |
710 | self.__addPendingAction(modelactions.CADDNOTEINT, host_id, interface_id, name, text) | |
711 | return factory.generateID( | |
712 | ModelObjectNote.class_signature, | |
713 | name=name, text=text, parent_id=interface_id) | |
714 | ||
715 | def createAndAddNoteToService(self, host_id, service_id, name, text): | |
716 | self.__addPendingAction(modelactions.CADDNOTESRV, host_id, service_id, name, text) | |
717 | return factory.generateID( | |
718 | ModelObjectNote.class_signature, | |
719 | name=name, text=text, parent_id=service_id) | |
720 | ||
721 | def createAndAddNoteToNote(self, host_id, service_id, note_id, name, text): | |
722 | self.__addPendingAction(modelactions.CADDNOTENOTE, host_id, service_id, note_id, name, text) | |
723 | return factory.generateID( | |
724 | ModelObjectNote.class_signature, | |
725 | name=name, text=text, parent_id=note_id) | |
726 | ||
727 | def createAndAddCredToService(self, host_id, service_id, username, password): | |
728 | self.__addPendingAction(modelactions.CADDCREDSRV, host_id, service_id, username, password) | |
729 | return factory.generateID( | |
730 | ModelObjectCred.class_signature, | |
731 | username=username, password=password, parent_id=service_id) | |
732 | ||
733 | def addHost(self, host, category=None,update=False, old_hostname=None): | |
734 | self.__addPendingAction(modelactions.ADDHOST, host, category, update, old_hostname) | |
735 | ||
736 | def addInterface(self, host_id, interface): | |
737 | self.__addPendingAction(modelactions.ADDINTERFACE, host_id, interface) | |
738 | ||
739 | def addApplication(self, host_id, application): | |
740 | self.__addPendingAction(modelactions.ADDAPPLICATION, host_id, application) | |
741 | ||
742 | def addServiceToApplication(self, host_id, application_id, service): | |
743 | self.__addPendingAction(modelactions.ADDSERVICEAPP, host_id, application_id, service) | |
744 | ||
745 | def addServiceToInterface(self, host_id, interface_id, service): | |
746 | self.__addPendingAction(modelactions.ADDSERVICEINT, host_id, interface_id, service) | |
747 | ||
748 | def addVulnToHost(self, host_id, vuln): | |
749 | self.__addPendingAction(modelactions.ADDVULNHOST, host_id, vuln) | |
750 | ||
751 | def addVulnToInterface(self, host_id, interface_id, vuln): | |
752 | self.__addPendingAction(modelactions.ADDVULNINT, host_id, interface_id, vuln) | |
753 | ||
754 | def addVulnToApplication(self, host_id, application_id, vuln): | |
755 | self.__addPendingAction(modelactions.ADDVULNAPP, host_id, application_id, vuln) | |
756 | ||
757 | def addVulnToService(self, host_id, service_id, vuln): | |
758 | self.__addPendingAction(modelactions.ADDVULNSRV, host_id, service_id, vuln) | |
759 | ||
760 | def addVulnWebToService(self, host_id, service_id, vuln): | |
761 | self.__addPendingAction(modelactions.ADDVULNWEBSRV, host_id, service_id, vuln) | |
762 | ||
763 | def addNoteToHost(self, host_id, note): | |
764 | self.__addPendingAction(modelactions.ADDNOTEHOST, host_id, note) | |
765 | ||
766 | def addNoteToInterface(self, host_id, interface_id, note): | |
767 | self.__addPendingAction(modelactions.ADDNOTEINT, host_id, interface_id, note) | |
768 | ||
769 | def addNoteToApplication(self, host_id, application_id, note): | |
770 | self.__addPendingAction(modelactions.ADDNOTEAPP, host_id, application_id, note) | |
771 | ||
772 | def addNoteToService(self, host_id, service_id, note): | |
773 | self.__addPendingAction(modelactions.ADDNOTESRV, host_id, service_id, note) | |
774 | ||
775 | def addNoteToNote(self, host_id, service_id,note_id, note): | |
776 | self.__addPendingAction(modelactions.ADDNOTENOTE, host_id, service_id, note_id, note) | |
777 | ||
778 | def addCredToService(self, host_id, service_id, cred): | |
779 | self.__addPendingAction(modelactions.ADDCREDSRV, host_id, service_id, cred) | |
780 | ||
781 | def delServiceFromInterface(self, service, hostname, | |
782 | intname, remote = True): | |
783 | self.__addPendingAction(modelactions.DELSERVICEINT,hostname,intname,service,remote) | |
784 | ||
785 | def log(self, msg, level='INFO'): | |
786 | self.__addPendingAction(modelactions.LOG,msg,level) | |
787 | ||
788 | def devlog(self, msg): | |
789 | self.__addPendingAction(modelactions.DEVLOG,msg) | |
790 | ||
791 | ||
792 | class PluginProcess(multiprocessing.Process): | |
793 | def __init__(self, plugin_instance, output_queue, new_elem_queue): | |
794 | multiprocessing.Process.__init__(self) | |
795 | self.output_queue = output_queue | |
796 | self.new_elem_queue = new_elem_queue | |
797 | self.plugin = plugin_instance | |
798 | ||
799 | ||
800 | def run(self): | |
801 | proc_name = self.name | |
802 | plugin = self.plugin | |
803 | model.api.devlog("-" * 40) | |
804 | model.api.devlog("proc_name = %s" % proc_name) | |
805 | model.api.devlog("Starting run method on PluginProcess") | |
806 | model.api.devlog('parent process: %s' % os.getppid()) | |
807 | model.api.devlog('process id: %s' % os.getpid()) | |
808 | model.api.devlog("-" * 40) | |
809 | done = False | |
810 | while not done: | |
811 | output = self.output_queue.get() | |
812 | if output is not None: | |
813 | model.api.devlog('%s: %s' % (proc_name, "New Output")) | |
814 | try: | |
815 | self.output = output | |
816 | self.plugin.parseOutputString(output) | |
817 | except Exception: | |
818 | model.api.log('Plugin Error: %s, (%s)' % (plugin.id, sha1OfStr(output)),"DEBUG") | |
819 | model.api.devlog("Plugin raised an exception:") | |
820 | model.api.devlog(traceback.format_exc()) | |
821 | else: | |
822 | while True: | |
823 | try: | |
824 | self.new_elem_queue.put(self.plugin._pending_actions.get(block=False)) | |
825 | except Queue.Empty: | |
826 | model.api.log('Plugin Error: %s, (%s)' % (plugin.id, sha1OfStr(output)),"DEBUG") | |
827 | model.api.devlog("PluginProcess run _pending_actions queue Empty. Breaking loop") | |
828 | break | |
829 | except Exception: | |
830 | model.api.log('Plugin Error: %s, (%s)' % (plugin.id, sha1OfStr(output)),"DEBUG") | |
831 | model.api.devlog("PluginProcess run getting from _pending_action queue - something strange happened... unhandled exception?") | |
832 | model.api.devlog(traceback.format_exc()) | |
833 | break | |
834 | ||
835 | else: | |
836 | ||
837 | done = True | |
838 | model.api.devlog('%s: Exiting' % proc_name) | |
839 | model.api.log('Plugin finished: %s, (%s)' % (plugin.id, sha1OfStr(self.output))) | |
840 | ||
841 | self.output_queue.task_done() | |
842 | self.new_elem_queue.put(None) | |
843 | return | |
844 | ||
12 | # This class was moved to plugins.plugin so we need a way to | |
13 | # support plugins that are still inheriting from core | |
14 | PluginBase = PluginBaseExt |
13 | 13 | import sys |
14 | 14 | import traceback |
15 | 15 | |
16 | import plugins.core | |
16 | from plugins.controller import PluginController | |
17 | 17 | from config.configuration import getInstanceConfiguration |
18 | 18 | from utils.logs import getLogger |
19 | 19 | |
34 | 34 | """ |
35 | 35 | Creates a new plugin controller and adds it into the controllers list. |
36 | 36 | """ |
37 | plugs = self._instancePlugins() | |
38 | new_controller = plugins.core.PluginController( | |
39 | id, plugs, self._mapper_manager) | |
37 | new_controller = PluginController( | |
38 | id, self, self._mapper_manager) | |
40 | 39 | self._controllers[new_controller.id] = new_controller |
41 | 40 | self.updateSettings(self._plugin_settings) |
42 | 41 | return new_controller |
42 | ||
43 | def addController(self, controller, id): | |
44 | self._controllers[id] = controller | |
43 | 45 | |
44 | 46 | def _loadSettings(self): |
45 | 47 | _plugin_settings = CONF.getPluginSettings() |
98 | 100 | try: |
99 | 101 | os.stat(plugin_repo_path) |
100 | 102 | except OSError: |
101 | ||
102 | 103 | pass |
103 | 104 | |
104 | 105 | sys.path.append(plugin_repo_path) |
121 | 122 | pass |
122 | 123 | |
123 | 124 | def getPlugins(self): |
124 | return self._instancePlugins() | |
125 | plugins = self._instancePlugins() | |
126 | for id, plugin in plugins.items(): | |
127 | if id in self._plugin_settings: | |
128 | plugin.updateSettings(self._plugin_settings[id]["settings"]) | |
129 | return plugins | |
125 | 130 | |
126 | 131 | def _updatePluginSettings(self, new_plugin_id): |
127 | 132 | pass |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | ||
10 | ||
11 | class modelactions: | |
12 | ADDHOST = 2000 | |
13 | CADDHOST = 2001 | |
14 | ADDINTERFACE = 2002 | |
15 | CADDINTERFACE = 2003 | |
16 | ADDSERVICEINT = 2004 | |
17 | ADDSERVICEAPP = 2005 | |
18 | CADDSERVICEINT = 2006 | |
19 | CADDSERVICEAPP = 2007 | |
20 | CADDSERVICEHOST = 2008 | |
21 | ADDAPPLICATION = 2009 | |
22 | CADDAPPLICATION = 2010 | |
23 | ADDVULNINT = 2013 | |
24 | CADDVULNINT = 2014 | |
25 | ADDVULNAPP = 2015 | |
26 | CADDVULNAPP = 2016 | |
27 | ADDVULNHOST = 2017 | |
28 | CADDVULNHOST = 2018 | |
29 | ADDVULNSRV = 2019 | |
30 | CADDVULNSRV = 2020 | |
31 | ADDNOTEINT = 2021 | |
32 | CADDNOTEINT = 2022 | |
33 | ADDNOTEAPP = 2023 | |
34 | CADDNOTEAPP = 2024 | |
35 | ADDNOTEHOST = 2025 | |
36 | CADDNOTEHOST = 2026 | |
37 | ADDNOTESRV = 2027 | |
38 | CADDNOTESRV = 2028 | |
39 | CADDNOTEVULN = 2030 | |
40 | CADDNOTEVULN = 2031 | |
41 | LOG = 2032 | |
42 | DEVLOG = 2033 | |
43 | DELSERVICEINT = 2034 | |
44 | ADDCREDSRV = 2035 | |
45 | CADDCREDSRV = 2036 | |
46 | ADDVULNWEBSRV = 2037 | |
47 | CADDVULNWEBSRV = 2038 | |
48 | ADDNOTENOTE = 2039 | |
49 | CADDNOTENOTE = 2039 | |
50 | PLUGINSTART = 3000 | |
51 | PLUGINEND = 3001 |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | ||
10 | import multiprocessing | |
11 | import os | |
12 | import re | |
13 | import Queue | |
14 | import traceback | |
15 | ||
16 | import model.api | |
17 | import model.common | |
18 | from model.common import ( | |
19 | factory, | |
20 | ModelObjectVuln, | |
21 | ModelObjectVulnWeb, | |
22 | ModelObjectNote, | |
23 | ModelObjectCred | |
24 | ) | |
25 | from model.hosts import Host, Interface, Service | |
26 | from plugins.modelactions import modelactions | |
27 | ||
28 | from config.configuration import getInstanceConfiguration | |
29 | CONF = getInstanceConfiguration() | |
30 | ||
31 | ||
32 | class PluginBase(object): | |
33 | # TODO: Add class generic identifier | |
34 | class_signature = "PluginBase" | |
35 | ||
36 | def __init__(self): | |
37 | ||
38 | self.data_path = CONF.getDataPath() | |
39 | self.persistence_path = CONF.getPersistencePath() | |
40 | # Must be unique. Check that there is not | |
41 | # an existant plugin with the same id. | |
42 | # TODO: Make script that list current ids. | |
43 | self.id = None | |
44 | self._rid = id(self) | |
45 | self.version = None | |
46 | self.name = None | |
47 | self.description = "" | |
48 | self._command_regex = None | |
49 | self._output_file_path = None | |
50 | self.framework_version = None | |
51 | self._completition = {} | |
52 | self._new_elems = [] | |
53 | self._pending_actions = Queue.Queue() | |
54 | self._settings = {} | |
55 | ||
56 | def has_custom_output(self): | |
57 | return bool(self._output_file_path) | |
58 | ||
59 | def get_custom_file_path(self): | |
60 | return self._output_file_path | |
61 | ||
62 | def getSettings(self): | |
63 | for param, (param_type, value) in self._settings.iteritems(): | |
64 | yield param, value | |
65 | ||
66 | def get_ws(self): | |
67 | return CONF.getLastWorkspace() | |
68 | ||
69 | def getSetting(self, name): | |
70 | setting_type, value = self._settings[name] | |
71 | return value | |
72 | ||
73 | def addSetting(self, param, param_type, value): | |
74 | self._settings[param] = param_type, value | |
75 | ||
76 | def updateSettings(self, new_settings): | |
77 | for name, value in new_settings.iteritems(): | |
78 | setting_type, curr_value = self._settings[name] | |
79 | self._settings[name] = setting_type, setting_type(value) | |
80 | ||
81 | def canParseCommandString(self, current_input): | |
82 | """ | |
83 | This method can be overriden in the plugin implementation | |
84 | if a different kind of check is needed | |
85 | """ | |
86 | return (self._command_regex is not None and | |
87 | self._command_regex.match(current_input.strip()) is not None) | |
88 | ||
89 | def getCompletitionSuggestionsList(self, current_input): | |
90 | """ | |
91 | This method can be overriden in the plugin implementation | |
92 | if a different kind of check is needed | |
93 | """ | |
94 | words = current_input.split(" ") | |
95 | cword = words[len(words) - 1] | |
96 | ||
97 | options = {} | |
98 | for k, v in self._completition.iteritems(): | |
99 | if re.search(str("^" + cword), k, flags=re.IGNORECASE): | |
100 | options[k] = v | |
101 | ||
102 | return options | |
103 | ||
104 | def processOutput(self, term_output): | |
105 | output = term_output | |
106 | if self.has_custom_output(): | |
107 | output = open(self.get_custom_file_path(), 'r').read() | |
108 | self.parseOutputString(output) | |
109 | ||
110 | def processReport(self, filepath): | |
111 | output = open(filepath, 'r').read() | |
112 | self.parseOutputString(output) | |
113 | ||
114 | def parseOutputString(self, output): | |
115 | """ | |
116 | This method must be implemented. | |
117 | This method will be called when the command finished executing and | |
118 | the complete output will be received to work with it | |
119 | Using the output the plugin can create and add hosts, interfaces, | |
120 | services, etc. | |
121 | """ | |
122 | pass | |
123 | ||
124 | def processCommandString(self, username, current_path, command_string): | |
125 | """ | |
126 | With this method a plugin can add aditional arguments to the | |
127 | command that it's going to be executed. | |
128 | """ | |
129 | return None | |
130 | ||
131 | def __addPendingAction(self, *args): | |
132 | """ | |
133 | Adds a new pending action to the queue | |
134 | Action is build with generic args tuple. | |
135 | The caller of this function has to build the action in the right | |
136 | way since no checks are preformed over args | |
137 | """ | |
138 | self._pending_actions.put(args) | |
139 | ||
140 | def createAndAddHost(self, name, os="unknown", category=None, | |
141 | update=False, old_hostname=None): | |
142 | self.__addPendingAction(modelactions.CADDHOST, name, os, category, | |
143 | update, old_hostname) | |
144 | return factory.generateID(Host.class_signature, name=name, os=os) | |
145 | ||
146 | def createAndAddInterface( | |
147 | self, host_id, name="", mac="00:00:00:00:00:00", | |
148 | ipv4_address="0.0.0.0", ipv4_mask="0.0.0.0", ipv4_gateway="0.0.0.0", | |
149 | ipv4_dns=[], ipv6_address="0000:0000:0000:0000:0000:0000:0000:0000", | |
150 | ipv6_prefix="00", | |
151 | ipv6_gateway="0000:0000:0000:0000:0000:0000:0000:0000", ipv6_dns=[], | |
152 | network_segment="", hostname_resolution=[] | |
153 | ): | |
154 | self.__addPendingAction(modelactions.CADDINTERFACE, host_id, name, | |
155 | mac, ipv4_address, ipv4_mask, ipv4_gateway, | |
156 | ipv4_dns, ipv6_address, ipv6_prefix, | |
157 | ipv6_gateway, ipv6_dns, | |
158 | network_segment, hostname_resolution) | |
159 | return factory.generateID( | |
160 | Interface.class_signature, parent_id=host_id, name=name, mac=mac, | |
161 | ipv4_address=ipv4_address, ipv4_mask=ipv4_mask, | |
162 | ipv4_gateway=ipv4_gateway, ipv4_dns=ipv4_dns, | |
163 | ipv6_address=ipv6_address, ipv6_prefix=ipv6_prefix, | |
164 | ipv6_gateway=ipv6_gateway, ipv6_dns=ipv6_dns, | |
165 | network_segment=network_segment, | |
166 | hostname_resolution=hostname_resolution) | |
167 | ||
168 | def createAndAddServiceToInterface(self, host_id, interface_id, name, | |
169 | protocol="tcp?", ports=[], | |
170 | status="running", version="unknown", | |
171 | description=""): | |
172 | self.__addPendingAction(modelactions.CADDSERVICEINT, host_id, | |
173 | interface_id, name, protocol, ports, status, | |
174 | version, description) | |
175 | return factory.generateID( | |
176 | Service.class_signature, | |
177 | name=name, protocol=protocol, ports=ports, | |
178 | status=status, version=version, description=description, | |
179 | parent_id=interface_id) | |
180 | ||
181 | def createAndAddVulnToHost(self, host_id, name, desc="", ref=[], | |
182 | severity="", resolution=""): | |
183 | self.__addPendingAction(modelactions.CADDVULNHOST, host_id, name, | |
184 | desc, ref, severity, resolution) | |
185 | return factory.generateID( | |
186 | ModelObjectVuln.class_signature, | |
187 | name=name, desc=desc, ref=ref, severity=severity, | |
188 | resolution=resolution, parent_id=host_id) | |
189 | ||
190 | def createAndAddVulnToInterface(self, host_id, interface_id, name, | |
191 | desc="", ref=[], severity="", | |
192 | resolution=""): | |
193 | self.__addPendingAction(modelactions.CADDVULNINT, host_id, | |
194 | interface_id, name, desc, ref, severity, | |
195 | resolution) | |
196 | return factory.generateID( | |
197 | ModelObjectVuln.class_signature, | |
198 | name=name, desc=desc, ref=ref, severity=severity, | |
199 | resolution=resolution, parent_id=interface_id) | |
200 | ||
201 | def createAndAddVulnToService(self, host_id, service_id, name, desc="", | |
202 | ref=[], severity="", resolution=""): | |
203 | self.__addPendingAction(modelactions.CADDVULNSRV, host_id, | |
204 | service_id, name, desc, ref, severity, | |
205 | resolution) | |
206 | return factory.generateID( | |
207 | ModelObjectVuln.class_signature, | |
208 | name=name, desc=desc, ref=ref, severity=severity, | |
209 | resolution=resolution, parent_id=service_id) | |
210 | ||
211 | def createAndAddVulnWebToService(self, host_id, service_id, name, desc="", | |
212 | ref=[], severity="", resolution="", | |
213 | website="", path="", request="", | |
214 | response="", method="", pname="", | |
215 | params="", query="", category=""): | |
216 | self.__addPendingAction(modelactions.CADDVULNWEBSRV, host_id, | |
217 | service_id, name, desc, ref, severity, | |
218 | resolution, website, path, request, response, | |
219 | method, pname, params, query, category) | |
220 | return factory.generateID( | |
221 | ModelObjectVulnWeb.class_signature, | |
222 | name=name, desc=desc, ref=ref, severity=severity, | |
223 | resolution=resolution, website=website, path=path, request=request, | |
224 | response=response, method=method, pname=pname, params=params, | |
225 | query=query, category=category, parent_id=service_id) | |
226 | ||
227 | def createAndAddNoteToHost(self, host_id, name, text): | |
228 | self.__addPendingAction(modelactions.CADDNOTEHOST, host_id, name, text) | |
229 | return factory.generateID( | |
230 | ModelObjectNote.class_signature, | |
231 | name=name, text=text, parent_id=host_id) | |
232 | ||
233 | def createAndAddNoteToInterface(self, host_id, interface_id, name, text): | |
234 | self.__addPendingAction(modelactions.CADDNOTEINT, host_id, interface_id, | |
235 | name, text) | |
236 | return factory.generateID( | |
237 | ModelObjectNote.class_signature, | |
238 | name=name, text=text, parent_id=interface_id) | |
239 | ||
240 | def createAndAddNoteToService(self, host_id, service_id, name, text): | |
241 | self.__addPendingAction(modelactions.CADDNOTESRV, host_id, service_id, | |
242 | name, text) | |
243 | return factory.generateID( | |
244 | ModelObjectNote.class_signature, | |
245 | name=name, text=text, parent_id=service_id) | |
246 | ||
247 | def createAndAddNoteToNote(self, host_id, service_id, note_id, name, text): | |
248 | self.__addPendingAction(modelactions.CADDNOTENOTE, host_id, service_id, | |
249 | note_id, name, text) | |
250 | return factory.generateID( | |
251 | ModelObjectNote.class_signature, | |
252 | name=name, text=text, parent_id=note_id) | |
253 | ||
254 | def createAndAddCredToService(self, host_id, service_id, username, | |
255 | password): | |
256 | self.__addPendingAction(modelactions.CADDCREDSRV, host_id, service_id, | |
257 | username, password) | |
258 | return factory.generateID( | |
259 | ModelObjectCred.class_signature, | |
260 | username=username, password=password, parent_id=service_id) | |
261 | ||
262 | def addHost(self, host, category=None, update=False, old_hostname=None): | |
263 | self.__addPendingAction(modelactions.ADDHOST, host, category, update, | |
264 | old_hostname) | |
265 | ||
266 | def addInterface(self, host_id, interface): | |
267 | self.__addPendingAction(modelactions.ADDINTERFACE, host_id, interface) | |
268 | ||
269 | def addApplication(self, host_id, application): | |
270 | self.__addPendingAction(modelactions.ADDAPPLICATION, host_id, | |
271 | application) | |
272 | ||
273 | def addServiceToApplication(self, host_id, application_id, service): | |
274 | self.__addPendingAction(modelactions.ADDSERVICEAPP, host_id, | |
275 | application_id, service) | |
276 | ||
277 | def addServiceToInterface(self, host_id, interface_id, service): | |
278 | self.__addPendingAction(modelactions.ADDSERVICEINT, host_id, | |
279 | interface_id, service) | |
280 | ||
281 | def addVulnToHost(self, host_id, vuln): | |
282 | self.__addPendingAction(modelactions.ADDVULNHOST, host_id, vuln) | |
283 | ||
284 | def addVulnToInterface(self, host_id, interface_id, vuln): | |
285 | self.__addPendingAction(modelactions.ADDVULNINT, host_id, interface_id, | |
286 | vuln) | |
287 | ||
288 | def addVulnToApplication(self, host_id, application_id, vuln): | |
289 | self.__addPendingAction(modelactions.ADDVULNAPP, host_id, | |
290 | application_id, vuln) | |
291 | ||
292 | def addVulnToService(self, host_id, service_id, vuln): | |
293 | self.__addPendingAction(modelactions.ADDVULNSRV, host_id, service_id, | |
294 | vuln) | |
295 | ||
296 | def addVulnWebToService(self, host_id, service_id, vuln): | |
297 | self.__addPendingAction(modelactions.ADDVULNWEBSRV, host_id, service_id, | |
298 | vuln) | |
299 | ||
300 | def addNoteToHost(self, host_id, note): | |
301 | self.__addPendingAction(modelactions.ADDNOTEHOST, host_id, note) | |
302 | ||
303 | def addNoteToInterface(self, host_id, interface_id, note): | |
304 | self.__addPendingAction(modelactions.ADDNOTEINT, host_id, interface_id, | |
305 | note) | |
306 | ||
307 | def addNoteToApplication(self, host_id, application_id, note): | |
308 | self.__addPendingAction(modelactions.ADDNOTEAPP, host_id, | |
309 | application_id, note) | |
310 | ||
311 | def addNoteToService(self, host_id, service_id, note): | |
312 | self.__addPendingAction(modelactions.ADDNOTESRV, host_id, service_id, | |
313 | note) | |
314 | ||
315 | def addNoteToNote(self, host_id, service_id, note_id, note): | |
316 | self.__addPendingAction(modelactions.ADDNOTENOTE, host_id, service_id, | |
317 | note_id, note) | |
318 | ||
319 | def addCredToService(self, host_id, service_id, cred): | |
320 | self.__addPendingAction(modelactions.ADDCREDSRV, host_id, service_id, | |
321 | cred) | |
322 | ||
323 | def delServiceFromInterface(self, service, hostname, | |
324 | intname, remote=True): | |
325 | self.__addPendingAction(modelactions.DELSERVICEINT, hostname, intname, | |
326 | service, remote) | |
327 | ||
328 | def log(self, msg, level='INFO'): | |
329 | self.__addPendingAction(modelactions.LOG, msg, level) | |
330 | ||
331 | def devlog(self, msg): | |
332 | self.__addPendingAction(modelactions.DEVLOG, msg) | |
333 | ||
334 | ||
335 | class PluginTerminalOutput(PluginBase): | |
336 | def __init__(self): | |
337 | super(PluginTerminalOutput, self).__init__() | |
338 | ||
339 | def processOutput(self, term_output): | |
340 | self.parseOutputString(term_output) | |
341 | ||
342 | ||
343 | class PluginCustomOutput(PluginBase): | |
344 | def __init__(self): | |
345 | super(PluginCustomOutput, self).__init__() | |
346 | ||
347 | def processOutput(self, term_output): | |
348 | # we discard the term_output since it's not necessary | |
349 | # for this type of plugins | |
350 | self.processReport(self._output_file_path) | |
351 | ||
352 | ||
353 | class PluginProcess(multiprocessing.Process): | |
354 | def __init__(self, plugin_instance, output_queue, new_elem_queue, isReport=False): | |
355 | multiprocessing.Process.__init__(self) | |
356 | self.output_queue = output_queue | |
357 | self.new_elem_queue = new_elem_queue | |
358 | self.plugin = plugin_instance | |
359 | self.isReport = isReport | |
360 | ||
361 | def run(self): | |
362 | proc_name = self.name | |
363 | model.api.devlog("-" * 40) | |
364 | model.api.devlog("proc_name = %s" % proc_name) | |
365 | model.api.devlog("Starting run method on PluginProcess") | |
366 | model.api.devlog('parent process: %s' % os.getppid()) | |
367 | model.api.devlog('process id: %s' % os.getpid()) | |
368 | model.api.devlog("-" * 40) | |
369 | done = False | |
370 | while not done: | |
371 | output = self.output_queue.get() | |
372 | if output is not None: | |
373 | model.api.devlog('%s: %s' % (proc_name, "New Output")) | |
374 | try: | |
375 | if self.isReport: | |
376 | self.plugin.processReport(output) | |
377 | else: | |
378 | self.plugin.processOutput(output) | |
379 | except Exception: | |
380 | model.api.devlog("Plugin raised an exception:") | |
381 | model.api.devlog(traceback.format_exc()) | |
382 | else: | |
383 | while True: | |
384 | try: | |
385 | self.new_elem_queue.put( | |
386 | self.plugin._pending_actions.get(block=False)) | |
387 | except Queue.Empty: | |
388 | model.api.devlog( | |
389 | ("PluginProcess run _pending_actions" | |
390 | " queue Empty. Breaking loop")) | |
391 | break | |
392 | except Exception: | |
393 | model.api.devlog( | |
394 | ("PluginProcess run getting from " | |
395 | "_pending_action queue - something strange " | |
396 | "happened... unhandled exception?")) | |
397 | model.api.devlog(traceback.format_exc()) | |
398 | break | |
399 | ||
400 | else: | |
401 | ||
402 | done = True | |
403 | model.api.devlog('%s: Exiting' % proc_name) | |
404 | ||
405 | self.output_queue.task_done() | |
406 | self.new_elem_queue.put(None) | |
407 | return |
8 | 8 | ''' |
9 | 9 | from __future__ import with_statement |
10 | 10 | from plugins import core |
11 | from model import api | |
11 | import socket | |
12 | import sys | |
12 | 13 | import re |
13 | import os, socket | |
14 | import pprint | |
15 | import sys | |
14 | import os | |
16 | 15 | |
17 | 16 | try: |
18 | 17 | import xml.etree.cElementTree as ET |
21 | 20 | except ImportError: |
22 | 21 | import xml.etree.ElementTree as ET |
23 | 22 | ETREE_VERSION = ET.VERSION |
24 | ||
23 | ||
25 | 24 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
26 | 25 | |
27 | 26 | current_path = os.path.abspath(os.getcwd()) |
28 | 27 | |
29 | __author__ = "Francisco Amato" | |
30 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
31 | __credits__ = ["Francisco Amato"] | |
32 | __version__ = "1.0.0" | |
28 | __author__ = "Francisco Amato" | |
29 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
30 | __credits__ = ["Francisco Amato"] | |
31 | __version__ = "1.0.0" | |
33 | 32 | __maintainer__ = "Francisco Amato" |
34 | __email__ = "[email protected]" | |
35 | __status__ = "Development" | |
36 | ||
37 | ||
38 | ||
39 | ||
33 | __email__ = "[email protected]" | |
34 | __status__ = "Development" | |
35 | ||
40 | 36 | |
41 | 37 | class AcunetixXmlParser(object): |
42 | 38 | """ |
43 | The objective of this class is to parse an xml file generated by the acunetix tool. | |
39 | The objective of this class is to parse an xml file generated by | |
40 | the acunetix tool. | |
44 | 41 | |
45 | 42 | TODO: Handle errors. |
46 | TODO: Test acunetix output version. Handle what happens if the parser doesn't support it. | |
43 | TODO: Test acunetix output version. Handle what happens if | |
44 | the parser doesn't support it. | |
47 | 45 | TODO: Test cases. |
48 | 46 | |
49 | 47 | @param acunetix_xml_filepath A proper xml generated by acunetix |
50 | 48 | """ |
51 | 49 | def __init__(self, xml_output): |
52 | #self.filepath = acunetix_xml_filepath | |
53 | 50 | |
54 | 51 | tree = self.parse_xml(xml_output) |
55 | ||
52 | ||
56 | 53 | if tree: |
57 | 54 | self.sites = [data for data in self.get_items(tree)] |
58 | 55 | else: |
59 | 56 | self.sites = [] |
60 | ||
61 | 57 | |
62 | 58 | def parse_xml(self, xml_output): |
63 | 59 | """ |
64 | 60 | Open and parse an xml file. |
65 | 61 | |
66 | TODO: Write custom parser to just read the nodes that we need instead of | |
67 | reading the whole file. | |
62 | TODO: Write custom parser to just read the nodes that we need instead | |
63 | of reading the whole file. | |
68 | 64 | |
69 | 65 | @return xml_tree An xml tree instance. None if error. |
70 | 66 | """ |
80 | 76 | """ |
81 | 77 | @return items A list of Host instances |
82 | 78 | """ |
83 | ||
79 | ||
84 | 80 | for node in tree.findall('Scan'): |
85 | 81 | yield Site(node) |
86 | 82 | |
87 | 83 | |
88 | ||
89 | 84 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): |
90 | 85 | """ |
91 | 86 | Finds a subnode in the item node and the retrieves a value from it |
94 | 89 | """ |
95 | 90 | global ETREE_VERSION |
96 | 91 | node = None |
97 | ||
92 | ||
98 | 93 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: |
99 | ||
100 | match_obj = re.search("([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'",subnode_xpath_expr) | |
94 | ||
95 | match_obj = re.search( | |
96 | "([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'", | |
97 | subnode_xpath_expr) | |
98 | ||
101 | 99 | if match_obj is not None: |
102 | 100 | node_to_find = match_obj.group(1) |
103 | 101 | xpath_attrib = match_obj.group(2) |
118 | 116 | return None |
119 | 117 | |
120 | 118 | |
121 | ||
122 | ||
123 | 119 | class Site(object): |
124 | 120 | def __init__(self, item_node): |
125 | 121 | self.node = item_node |
126 | ||
122 | ||
127 | 123 | self.url = self.get_text_from_subnode('StartURL') |
128 | mregex = re.search("(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", self.url) | |
129 | ||
124 | mregex = re.search( | |
125 | "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)" | |
126 | "*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]" | |
127 | ")\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0" | |
128 | ")\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0" | |
129 | ")\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|" | |
130 | "localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil" | |
131 | "|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:" | |
132 | "]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", | |
133 | self.url) | |
134 | ||
130 | 135 | self.protocol = mregex.group(1) |
131 | 136 | self.host = mregex.group(4) |
132 | self.port=80 | |
137 | self.port = 80 | |
133 | 138 | if self.protocol == 'https': |
134 | self.port=443 | |
139 | self.port = 443 | |
135 | 140 | if mregex.group(11) is not None: |
136 | 141 | self.port = mregex.group(11) |
137 | ||
138 | ||
142 | ||
139 | 143 | self.ip = self.resolve(self.host) |
140 | 144 | self.os = self.get_text_from_subnode('Os') |
141 | 145 | self.banner = self.get_text_from_subnode('Banner') |
161 | 165 | except: |
162 | 166 | pass |
163 | 167 | return host |
168 | ||
164 | 169 | |
165 | 170 | class Item(object): |
166 | 171 | """ |
179 | 184 | self.parameter = self.get_text_from_subnode('Parameter') |
180 | 185 | self.uri = self.get_text_from_subnode('Affects') |
181 | 186 | self.desc = self.get_text_from_subnode('Description') |
182 | self.desc += "\nSolution: " + self.get_text_from_subnode('Recommendation') if self.get_text_from_subnode('Recommendation') else "" | |
183 | self.desc += "\nDetails: " + self.get_text_from_subnode('Details') if self.get_text_from_subnode('reference') else "" | |
184 | ||
185 | self.ref=[] | |
187 | ||
188 | if self.get_text_from_subnode('Recommendation'): | |
189 | self.desc += "\nSolution: " + self.get_text_from_subnode( | |
190 | 'Recommendation') | |
191 | else: | |
192 | self.desc += "" | |
193 | ||
194 | if self.get_text_from_subnode('reference'): | |
195 | self.desc += "\nDetails: " + self.get_text_from_subnode('Details') | |
196 | else: | |
197 | self.desc += "" | |
198 | ||
199 | self.ref = [] | |
186 | 200 | for n in item_node.findall('References/Reference'): |
187 | 201 | n2 = n.find('URL') |
188 | 202 | self.ref.append(n2.text) |
189 | ||
203 | ||
190 | 204 | def get_text_from_subnode(self, subnode_xpath_expr): |
191 | 205 | """ |
192 | 206 | Finds a subnode in the host node and the retrieves a value from it. |
200 | 214 | return None |
201 | 215 | |
202 | 216 | |
203 | ||
204 | 217 | class AcunetixPlugin(core.PluginBase): |
205 | 218 | """ |
206 | 219 | Example plugin to parse acunetix output. |
207 | 220 | """ |
208 | 221 | def __init__(self): |
209 | 222 | core.PluginBase.__init__(self) |
210 | self.id = "Acunetix" | |
211 | self.name = "Acunetix XML Output Plugin" | |
212 | self.plugin_version = "0.0.1" | |
213 | self.version = "9" | |
214 | self.framework_version = "1.0.0" | |
215 | self.options = None | |
223 | self.id = "Acunetix" | |
224 | self.name = "Acunetix XML Output Plugin" | |
225 | self.plugin_version = "0.0.1" | |
226 | self.version = "9" | |
227 | self.framework_version = "1.0.0" | |
228 | self.options = None | |
216 | 229 | self._current_output = None |
217 | 230 | self.target = None |
218 | self._command_regex = re.compile(r'^(acunetix|sudo acunetix|\.\/acunetix).*?') | |
231 | self._command_regex = re.compile( | |
232 | r'^(acunetix|sudo acunetix|\.\/acunetix).*?') | |
219 | 233 | |
220 | 234 | global current_path |
221 | self._output_file_path = os.path.join(self.data_path, | |
222 | "acunetix_output-%s.xml" % self._rid) | |
223 | ||
224 | ||
225 | def parseOutputString(self, output, debug = False): | |
226 | """ | |
227 | This method will discard the output the shell sends, it will read it from | |
228 | the xml where it expects it to be present. | |
235 | self._output_file_path = os.path.join( | |
236 | self.data_path, | |
237 | "acunetix_output-%s.xml" % self._rid) | |
238 | ||
239 | def parseOutputString(self, output, debug=False): | |
240 | """ | |
241 | This method will discard the output the shell sends, it will read it | |
242 | from the xml where it expects it to be present. | |
229 | 243 | |
230 | 244 | NOTE: if 'debug' is true then it is being run from a test case and the |
231 | 245 | output being sent is valid. |
237 | 251 | host = [] |
238 | 252 | if site.host != site.ip: |
239 | 253 | host = [site.host] |
240 | h_id = self.createAndAddHost(site.ip,site.os) | |
241 | i_id = self.createAndAddInterface(h_id, site.ip, ipv4_address=site.ip, hostname_resolution=host) | |
242 | s_id = self.createAndAddServiceToInterface(h_id, i_id, "http", "tcp", | |
243 | ports = [site.port], | |
244 | version= site.banner, | |
245 | status = 'open') | |
246 | n_id = self.createAndAddNoteToService(h_id,s_id,"website","") | |
247 | n2_id = self.createAndAddNoteToNote(h_id,s_id,n_id,site.host,"") | |
254 | h_id = self.createAndAddHost(site.ip, site.os) | |
255 | i_id = self.createAndAddInterface( | |
256 | h_id, | |
257 | site.ip, | |
258 | ipv4_address=site.ip, | |
259 | hostname_resolution=host) | |
260 | ||
261 | s_id = self.createAndAddServiceToInterface( | |
262 | h_id, | |
263 | i_id, | |
264 | "http", | |
265 | "tcp", | |
266 | ports=[site.port], | |
267 | version=site.banner, | |
268 | status='open') | |
269 | ||
270 | n_id = self.createAndAddNoteToService(h_id, s_id, "website", "") | |
271 | self.createAndAddNoteToNote(h_id, s_id, n_id, site.host, "") | |
248 | 272 | |
249 | 273 | for item in site.items: |
250 | v_id = self.createAndAddVulnWebToService(h_id, s_id, item.name, | |
251 | item.desc, website=site.host, severity=item.severity, | |
252 | path=item.uri,params=item.parameter, | |
253 | request=item.request,response=item.response,ref=item.ref) | |
274 | self.createAndAddVulnWebToService( | |
275 | h_id, | |
276 | s_id, | |
277 | item.name, | |
278 | item.desc, | |
279 | website=site.host, | |
280 | severity=item.severity, | |
281 | path=item.uri, | |
282 | params=item.parameter, | |
283 | request=item.request, | |
284 | response=item.response, | |
285 | ref=item.ref) | |
254 | 286 | |
255 | 287 | del parser |
256 | ||
257 | ||
258 | ||
259 | ||
260 | ||
261 | ||
262 | ||
288 | ||
263 | 289 | def processCommandString(self, username, current_path, command_string): |
264 | 290 | return None |
265 | ||
266 | 291 | |
267 | 292 | def setHost(self): |
268 | 293 | pass |
4 | 4 | Faraday Penetration Test IDE |
5 | 5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | 9 | from __future__ import with_statement |
10 | 10 | from plugins import core |
11 | from model import api | |
12 | import re | |
13 | import os | |
14 | 11 | import argparse |
15 | 12 | import shlex |
16 | 13 | import socket |
17 | 14 | import random |
15 | import re | |
16 | import os | |
17 | ||
18 | 18 | current_path = os.path.abspath(os.getcwd()) |
19 | 19 | |
20 | 20 | |
21 | ||
22 | ||
23 | ||
24 | ||
25 | ||
26 | 21 | class AmapPlugin(core.PluginBase): |
27 | """ | |
28 | Example plugin to parse amap output. | |
29 | """ | |
22 | """ Example plugin to parse amap output.""" | |
30 | 23 | def __init__(self): |
31 | 24 | core.PluginBase.__init__(self) |
32 | self.id = "Amap" | |
33 | self.name = "Amap Output Plugin" | |
34 | self.plugin_version = "0.0.3" | |
35 | self.version = "5.4" | |
36 | self.options = None | |
25 | self.id = "Amap" | |
26 | self.name = "Amap Output Plugin" | |
27 | self.plugin_version = "0.0.3" | |
28 | self.version = "5.4" | |
29 | self.options = None | |
37 | 30 | self._current_output = None |
38 | self._command_regex = re.compile(r'^(amap|sudo amap).*?') | |
39 | self._hosts = [] | |
40 | self._completition = { | |
41 | "":"amap [-A|-B|-P|-W] [-1buSRHUdqv] [[-m] -o <file>] [-D <file>] [-t/-T sec] [-c cons] [-C retries] [-p proto] [-i <file>] [target port [port] ...]", | |
42 | "-A":"Map applications: send triggers and analyse responses (default)", | |
43 | "-B":"Just grab banners, do not send triggers", | |
44 | "-P":"No banner or application stuff - be a (full connect) port scanner", | |
45 | "-1":"Only send triggers to a port until 1st identification. Speeeeed!", | |
46 | "-6":"Use IPv6 instead of IPv4", | |
47 | "-b":"Print ascii banner of responses", | |
48 | "-i":"Nmap machine readable outputfile to read ports from", | |
49 | "-u":"Ports specified on commandline are UDP (default is TCP)", | |
50 | "-R":"Do NOT identify RPC / SSL services", | |
51 | "-S":"Do NOT identify RPC / SSL services", | |
52 | "-H":"Do NOT send application triggers marked as potentially harmful", | |
53 | "-U":"Do NOT dump unrecognised responses (better for scripting)", | |
54 | "-d":"Dump all responses", | |
55 | "-v":"Verbose mode, use twice (or more!) for debug (not recommended :-)", | |
56 | "-q":"Do not report closed ports, and do not print them as unidentified", | |
57 | "-o":"FILE [-m] Write output to file FILE, -m creates machine readable output", | |
58 | "-c":"CONS Amount of parallel connections to make (default 32, max 256)", | |
59 | "-C":"RETRIES Number of reconnects on connect timeouts (see -T) (default 3)", | |
60 | "-T":"SEC Connect timeout on connection attempts in seconds (default 5)", | |
61 | "-t":"SEC Response wait timeout in seconds (default 5)", | |
62 | "-p":"PROTO Only send triggers for this protocol (e.g. ftp)", | |
63 | } | |
31 | self._command_regex = re.compile(r'^(amap|sudo amap).*?') | |
32 | self._hosts = [] | |
64 | 33 | |
65 | 34 | global current_path |
66 | self._file_output_path=os.path.join(self.data_path,"amap_output-%s.txt" % random.uniform(1,10)) | |
35 | self._file_output_path = os.path.join( | |
36 | self.data_path, | |
37 | "amap_output-%s.txt" % random.uniform(1, 10)) | |
67 | 38 | |
68 | ||
69 | def parseOutputString(self, output, debug = False): | |
70 | ||
71 | ||
39 | def parseOutputString(self, output, debug=False): | |
72 | 40 | if not os.path.exists(self._file_output_path): |
73 | 41 | return False |
74 | 42 | |
75 | 43 | if not debug: |
76 | 44 | with open(self._file_output_path) as f: |
77 | 45 | output = f.read() |
78 | ||
46 | ||
79 | 47 | services = {} |
80 | 48 | for line in output.split('\n'): |
81 | 49 | if line.startswith('#'): |
82 | ||
83 | continue | |
84 | ||
85 | ||
86 | fields = self.get_info(line) | |
87 | ||
88 | ||
89 | if len(fields) < 6: | |
90 | ||
91 | 50 | continue |
92 | 51 | |
93 | address = fields[0] | |
52 | fields = self.get_info(line) | |
53 | ||
54 | if len(fields) < 6: | |
55 | continue | |
56 | ||
57 | address = fields[0] | |
94 | 58 | h_id = self.createAndAddHost(address) |
95 | 59 | |
96 | port = fields[1] | |
97 | protocol = fields[2] | |
98 | port_status = fields[3] | |
99 | ||
100 | identification = fields[5] | |
60 | port = fields[1] | |
61 | protocol = fields[2] | |
62 | port_status = fields[3] | |
63 | ||
64 | identification = fields[5] | |
101 | 65 | printable_banner = fields[6] |
102 | ||
66 | ||
103 | 67 | if port in services.keys(): |
104 | ||
105 | 68 | if identification != 'unidentified': |
106 | ||
107 | 69 | services[port][5] += ', ' + identification |
108 | 70 | else: |
109 | services[port] = [address, port, protocol, port_status, None, identification, printable_banner, None] | |
110 | ||
111 | ||
112 | args={} | |
71 | services[port] = [ | |
72 | address, | |
73 | port, | |
74 | protocol, | |
75 | port_status, | |
76 | None, | |
77 | identification, | |
78 | printable_banner, | |
79 | None] | |
80 | ||
81 | args = {} | |
82 | ||
113 | 83 | if self.args.__getattribute__("6"): |
114 | 84 | self.ip = self.get_ip_6(self.args.m) |
115 | args['ipv6_address']=address | |
85 | args['ipv6_address'] = address | |
116 | 86 | else: |
117 | self.ip=self.getAddress(self.args.m) | |
118 | args['ipv4_address']=address | |
119 | ||
87 | self.ip = self.getAddress(self.args.m) | |
88 | args['ipv4_address'] = address | |
89 | ||
120 | 90 | if address != self.args.m: |
121 | args['hostname_resolution']=self.args.m | |
122 | ||
123 | i_id = self.createAndAddInterface(h_id, name=address, **args) | |
91 | args['hostname_resolution'] = self.args.m | |
92 | ||
93 | i_id = self.createAndAddInterface(h_id, name=address, **args) | |
124 | 94 | |
125 | 95 | for key in services: |
126 | 96 | service = services.get(key) |
127 | s_id = self.createAndAddServiceToInterface(h_id, i_id, service[5], service[2], ports = [service[1]], status = service[3],description = service[6]) | |
128 | ||
97 | self.createAndAddServiceToInterface( | |
98 | h_id, | |
99 | i_id, | |
100 | service[5], | |
101 | service[2], | |
102 | ports=[service[1]], | |
103 | status=service[3], | |
104 | description=service[6]) | |
105 | ||
129 | 106 | return True |
130 | 107 | |
131 | 108 | file_arg_re = re.compile(r"^.*(-o \s*[^\s]+\s+(?:-m|)).*$") |
132 | 109 | |
133 | def get_info(self,data): | |
110 | def get_info(self, data): | |
134 | 111 | if self.args.__getattribute__("6"): |
135 | f = re.search(r"^\[(.*)\]:(.*):(.*):(.*):(.*):(.*):(.*):(.*)",data) | |
136 | return [f.group(1),f.group(2),f.group(3), | |
137 | f.group(4),f.group(5),f.group(6),f.group(7),f.group(8)] if f else [] | |
138 | ||
112 | f = re.search( | |
113 | r"^\[(.*)\]:(.*):(.*):(.*):(.*):(.*):(.*):(.*)", | |
114 | data) | |
115 | ||
116 | return [ | |
117 | f.group(1), | |
118 | f.group(2), | |
119 | f.group(3), | |
120 | f.group(4), | |
121 | f.group(5), | |
122 | f.group(6), | |
123 | f.group(7), | |
124 | f.group(8)] if f else [] | |
125 | ||
139 | 126 | else: |
140 | 127 | return data.split(':') |
141 | ||
142 | ||
143 | def get_ip_6(self,host, port=0): | |
144 | ||
145 | alladdr = socket.getaddrinfo(host,port) | |
146 | ip6 = filter( | |
147 | lambda x: x[0] == socket.AF_INET6, | |
148 | alladdr | |
149 | ) | |
150 | ||
151 | ||
152 | return list(ip6)[0][4][0] | |
153 | ||
154 | ||
155 | ||
128 | ||
129 | def get_ip_6(self, host, port=0): | |
130 | alladdr = socket.getaddrinfo(host, port) | |
131 | ip6 = filter( | |
132 | lambda x: x[0] == socket.AF_INET6, | |
133 | alladdr) | |
134 | ||
135 | return list(ip6)[0][4][0] | |
136 | ||
156 | 137 | def getAddress(self, hostname): |
157 | 138 | """ |
158 | 139 | Returns remote IP address from hostname. |
160 | 141 | try: |
161 | 142 | return socket.gethostbyname(hostname) |
162 | 143 | except socket.error, msg: |
163 | ||
164 | 144 | return hostname |
165 | 145 | |
166 | 146 | def processCommandString(self, username, current_path, command_string): |
168 | 148 | Adds the -m parameter to get machine readable output. |
169 | 149 | """ |
170 | 150 | arg_match = self.file_arg_re.match(command_string) |
171 | ||
151 | ||
172 | 152 | parser = argparse.ArgumentParser() |
173 | ||
174 | parser.add_argument('-6',action='store_true') | |
153 | ||
154 | parser.add_argument('-6', action='store_true') | |
175 | 155 | parser.add_argument('-o') |
176 | 156 | parser.add_argument('-m') |
177 | ||
178 | ||
179 | self._output_file_path = os.path.join(self.data_path,"%s_%s_output-%s.xml" % (self.get_ws(), | |
180 | self.id, | |
181 | random.uniform(1,10))) | |
182 | ||
183 | if arg_match is None: | |
184 | final= re.sub(r"(^.*?amap)", | |
185 | r"\1 -o %s -m " % self._file_output_path, | |
186 | command_string) | |
157 | ||
158 | self._output_file_path = os.path.join( | |
159 | self.data_path, "%s_%s_output-%s.xml" % ( | |
160 | self.get_ws(), | |
161 | self.id, | |
162 | random.uniform(1, 10))) | |
163 | ||
164 | if arg_match is None: | |
165 | final = re.sub( | |
166 | r"(^.*?amap)", | |
167 | r"\1 -o %s -m " % self._file_output_path, | |
168 | command_string) | |
187 | 169 | else: |
188 | final= re.sub(arg_match.group(1), | |
189 | r"-o %s -m " % self._file_output_path, | |
190 | command_string) | |
191 | ||
192 | ||
193 | cmd=shlex.split(re.sub(r'\-h|\-\-help', r'', final)) | |
170 | final = re.sub( | |
171 | arg_match.group(1), | |
172 | r"-o %s -m " % self._file_output_path, | |
173 | command_string) | |
174 | ||
175 | cmd = shlex.split(re.sub(r'\-h|\-\-help', r'', final)) | |
194 | 176 | if "-6" in cmd: |
195 | 177 | cmd.remove("-6") |
196 | cmd.insert(1,"-6") | |
197 | ||
198 | ||
199 | ||
200 | args=None | |
178 | cmd.insert(1, "-6") | |
179 | ||
180 | args = None | |
201 | 181 | if len(cmd) > 4: |
202 | 182 | try: |
203 | 183 | args, unknown = parser.parse_known_args(cmd) |
204 | 184 | except SystemExit: |
205 | 185 | pass |
206 | ||
207 | self.args=args | |
186 | ||
187 | self.args = args | |
208 | 188 | return final |
209 | 189 | |
210 | 190 | def setHost(self): |
211 | 191 | pass |
212 | 192 | |
193 | ||
213 | 194 | def createPlugin(): |
214 | 195 | return AmapPlugin() |
215 |
366 | 366 | self.options = None |
367 | 367 | |
368 | 368 | self._command_regex = re.compile( |
369 | r'^(arachni_faraday |\.\/arachni_faraday).*?' | |
369 | r'^(arachni |\.\/arachni).*?' | |
370 | 370 | ) |
371 | 371 | |
372 | 372 | self.protocol = None |
4 | 4 | Faraday Penetration Test IDE |
5 | 5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | 9 | from __future__ import with_statement |
10 | 10 | from plugins import core |
11 | 11 | import re |
13 | 13 | import sys |
14 | 14 | import random |
15 | 15 | |
16 | ||
17 | 16 | current_path = os.path.abspath(os.getcwd()) |
18 | 17 | |
19 | __author__ = "Francisco Amato" | |
20 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
21 | __credits__ = ["Francisco Amato"] | |
22 | __license__ = "" | |
23 | __version__ = "1.0.0" | |
18 | __author__ = "Francisco Amato" | |
19 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
20 | __credits__ = ["Francisco Amato"] | |
21 | __license__ = "" | |
22 | __version__ = "1.0.0" | |
24 | 23 | __maintainer__ = "Francisco Amato" |
25 | __email__ = "[email protected]" | |
26 | __status__ = "Development" | |
27 | ||
28 | ||
29 | ||
30 | ||
24 | __email__ = "[email protected]" | |
25 | __status__ = "Development" | |
31 | 26 | |
32 | 27 | class DnsmapParser(object): |
33 | 28 | """ |
34 | The objective of this class is to parse an xml file generated by the dnsmap tool. | |
29 | The objective of this class is to parse an xml file generated by the | |
30 | dnsmap tool. | |
35 | 31 | |
36 | 32 | TODO: Handle errors. |
37 | TODO: Test dnsmap output version. Handle what happens if the parser doesn't support it. | |
33 | TODO: Test dnsmap output version. Handle what happens if the parser | |
34 | doesn't support it. | |
38 | 35 | TODO: Test cases. |
39 | 36 | |
40 | 37 | @param dnsmap_filepath A proper simple report generated by dnsmap |
46 | 43 | |
47 | 44 | for line in lists: |
48 | 45 | mitem = line.split(',') |
49 | if mitem.__len__() > 1: | |
46 | if len(mitem) > 1: | |
50 | 47 | item = {'host': mitem[0], 'ip': mitem[1]} |
51 | 48 | self.items.append(item) |
52 | 49 | |
53 | 50 | |
54 | 51 | class DnsmapPlugin(core.PluginBase): |
55 | """ | |
56 | Example plugin to parse dnsmap output. | |
57 | """ | |
52 | """Example plugin to parse dnsmap output.""" | |
53 | ||
58 | 54 | def __init__(self): |
59 | 55 | core.PluginBase.__init__(self) |
60 | 56 | self.id = "Dnsmap" |
61 | 57 | self.name = "Dnsmap XML Output Plugin" |
62 | 58 | self.plugin_version = "0.0.2" |
63 | 59 | self.version = "0.30" |
64 | self._completition = { | |
65 | "":"dnsmap <target-domain> [options]", | |
66 | "-w":"-w <wordlist-file>", | |
67 | "-r":"-r <regular-results-file>", | |
68 | "-c":"-c <csv-results-file>", | |
69 | "-d":"-d <delay-millisecs>", | |
70 | "-i":"-i <ips-to-ignore> (useful if you're obtaining false positives)", | |
71 | } | |
72 | ||
73 | 60 | self.options = None |
74 | 61 | self._current_output = None |
75 | 62 | self.current_path = None |
76 | self._command_regex = re.compile(r'^(sudo dnsmap|dnsmap|\.\/dnsmap).*?') | |
63 | self._command_regex = re.compile( | |
64 | r'^(sudo dnsmap|dnsmap|\.\/dnsmap).*?') | |
65 | ||
66 | self.xml_arg_re = re.compile(r"^.*(-c\s*[^\s]+).*$") | |
77 | 67 | |
78 | 68 | global current_path |
79 | self._output_file_path = os.path.join(self.data_path,"%s_%s_output-%s.xml" % (self.get_ws(), | |
80 | self.id, | |
81 | random.uniform(1,10))) | |
69 | ||
70 | self._output_file_path = os.path.join( | |
71 | self.data_path, | |
72 | "%s_%s_output-%s.xml" % ( | |
73 | self.get_ws(), | |
74 | self.id, | |
75 | random.uniform(1, 10) | |
76 | ) | |
77 | ) | |
82 | 78 | |
83 | 79 | def canParseCommandString(self, current_input): |
84 | 80 | if self._command_regex.match(current_input.strip()): |
88 | 84 | |
89 | 85 | def parseOutputString(self, output, debug=False): |
90 | 86 | """ |
91 | This method will discard the output the shell sends, it will read it from | |
92 | the xml where it expects it to be present. | |
93 | ||
94 | NOTE: if 'debug' is true then it is being run from a test case and the | |
95 | output being sent is valid. | |
87 | This method will discard the output the shell sends, it will read it | |
88 | from the xml where it expects it to be present. | |
96 | 89 | """ |
97 | 90 | |
98 | if debug: | |
99 | parser = DnsmapParser(self._output_file_path) | |
100 | else: | |
91 | parser = DnsmapParser(output) | |
101 | 92 | |
102 | if not os.path.exists(self._output_file_path): | |
103 | return False | |
104 | ||
105 | parser = DnsmapParser(self._output_file_path) | |
106 | ||
107 | for item in parser.items: | |
108 | h_id = self.createAndAddHost(item['ip']) | |
109 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address=item['ip'],hostname_resolution=item['host']) | |
110 | ||
111 | ||
112 | del parser | |
93 | for item in parser.items: | |
94 | h_id = self.createAndAddHost(item['ip']) | |
95 | self.createAndAddInterface( | |
96 | h_id, | |
97 | item['ip'], | |
98 | ipv4_address=item['ip'], | |
99 | hostname_resolution=item['host']) | |
113 | 100 | |
114 | 101 | return True |
115 | ||
116 | xml_arg_re = re.compile(r"^.*(-c\s*[^\s]+).*$") | |
117 | 102 | |
118 | 103 | def processCommandString(self, username, current_path, command_string): |
119 | 104 | """ |
120 | 105 | Adds the parameter to get output to the command string that the |
121 | 106 | user has set. |
122 | 107 | """ |
123 | ||
124 | 108 | arg_match = self.xml_arg_re.match(command_string) |
125 | 109 | |
126 | 110 | if arg_match is None: |
7 | 7 | ''' |
8 | 8 | from __future__ import with_statement |
9 | 9 | from plugins import core |
10 | from model import api | |
11 | 10 | import re |
12 | 11 | import os |
13 | import pprint | |
14 | 12 | import sys |
15 | 13 | import socket |
16 | 14 | |
17 | ||
18 | 15 | current_path = os.path.abspath(os.getcwd()) |
19 | 16 | |
20 | __author__ = "Francisco Amato" | |
21 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
22 | __credits__ = ["Francisco Amato"] | |
23 | __license__ = "" | |
24 | __version__ = "1.0.0" | |
17 | __author__ = "Francisco Amato" | |
18 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
19 | __credits__ = ["Francisco Amato"] | |
20 | __license__ = "" | |
21 | __version__ = "1.0.0" | |
25 | 22 | __maintainer__ = "Francisco Amato" |
26 | __email__ = "[email protected]" | |
27 | __status__ = "Development" | |
23 | __email__ = "[email protected]" | |
24 | __status__ = "Development" | |
28 | 25 | |
29 | ||
30 | ||
31 | ||
32 | 26 | |
33 | 27 | class DnswalkParser(object): |
34 | 28 | """ |
35 | The objective of this class is to parse an xml file generated by the dnswalk tool. | |
29 | The objective of this class is to parse an xml file generated | |
30 | by the dnswalk tool. | |
36 | 31 | |
37 | 32 | TODO: Handle errors. |
38 | TODO: Test dnswalk output version. Handle what happens if the parser doesn't support it. | |
33 | TODO: Test dnswalk output version. Handle what happens if the parser | |
34 | doesn't support it. | |
39 | 35 | TODO: Test cases. |
40 | 36 | |
41 | 37 | @param dnswalk_filepath A proper simple report generated by dnswalk |
42 | 38 | """ |
43 | 39 | def __init__(self, output): |
44 | ||
40 | ||
45 | 41 | lists = output.split("\n") |
46 | 42 | self.items = [] |
47 | ||
48 | ||
43 | ||
49 | 44 | for line in lists: |
50 | mregex = re.search("WARN: ([\w\.]+) ([\w]+) ([\w\.]+):",line) | |
45 | mregex = re.search("WARN: ([\w\.]+) ([\w]+) ([\w\.]+):", line) | |
51 | 46 | if mregex is not None: |
52 | print "host %s, ip %s" % (mregex.group(1),mregex.group(3)) | |
53 | item = {'host' : mregex.group(1), 'ip' : mregex.group(3), 'type' : mregex.group(2)} | |
47 | ||
48 | item = { | |
49 | 'host': mregex.group(1), | |
50 | 'ip': mregex.group(3), | |
51 | 'type': mregex.group(2)} | |
52 | ||
54 | 53 | self.items.append(item) |
55 | ||
56 | ||
57 | mregex = re.search("Getting zone transfer of ([\w\.]+) from ([\w\.]+)\.\.\.done\.",line) | |
54 | ||
55 | mregex = re.search( | |
56 | "Getting zone transfer of ([\w\.]+) from ([\w\.]+)\.\.\.done\.", | |
57 | line) | |
58 | ||
58 | 59 | if mregex is not None: |
59 | ip=self.getAddress(mregex.group(2)) | |
60 | item = {'host' : mregex.group(1), 'ip' : ip, 'type' : 'info'} | |
60 | ip = self.getAddress(mregex.group(2)) | |
61 | item = { | |
62 | 'host': mregex.group(1), | |
63 | 'ip': ip, | |
64 | 'type': 'info'} | |
61 | 65 | self.items.append(item) |
62 | ||
66 | ||
63 | 67 | def getAddress(self, hostname): |
64 | """ | |
65 | Returns remote IP address from hostname. | |
66 | """ | |
68 | """Returns remote IP address from hostname.""" | |
67 | 69 | try: |
68 | 70 | return socket.gethostbyname(hostname) |
69 | except socket.error, msg: | |
70 | ||
71 | return hostname | |
72 | ||
73 | ||
71 | except socket.error: | |
72 | return hostname | |
74 | 73 | |
75 | 74 | |
76 | 75 | class DnswalkPlugin(core.PluginBase): |
79 | 78 | """ |
80 | 79 | def __init__(self): |
81 | 80 | core.PluginBase.__init__(self) |
82 | self.id = "Dnswalk" | |
83 | self.name = "Dnswalk XML Output Plugin" | |
84 | self.plugin_version = "0.0.1" | |
85 | self.version = "2.0.2" | |
86 | ||
87 | self.options = None | |
81 | self.id = "Dnswalk" | |
82 | self.name = "Dnswalk XML Output Plugin" | |
83 | self.plugin_version = "0.0.1" | |
84 | self.version = "2.0.2" | |
85 | self.options = None | |
88 | 86 | self._current_output = None |
89 | 87 | self._current_path = None |
90 | self._command_regex = re.compile(r'^(sudo dnswalk|dnswalk|\.\/dnswalk).*?') | |
91 | self._completition = { | |
92 | "":"dnswalk domain", | |
93 | "-r":"Recursively descend subdomains of domain", | |
94 | "-i":"Suppress check for invalid characters in a domain name.", | |
95 | "-a":"turn on warning of duplicate A records.", | |
96 | "-d":"Debugging", | |
97 | "-m":"Check only if the domain has been modified. (Useful only if dnswalk has been run previously.)", | |
98 | "-F":"Enable \"facist\" checking. (See man page)", | |
99 | "-l":"Check lame delegations", | |
100 | } | |
88 | self._command_regex = re.compile( | |
89 | r'^(sudo dnswalk|dnswalk|\.\/dnswalk).*?') | |
101 | 90 | |
102 | 91 | global current_path |
103 | ||
104 | ||
105 | 92 | |
106 | 93 | def canParseCommandString(self, current_input): |
107 | 94 | if self._command_regex.match(current_input.strip()): |
109 | 96 | else: |
110 | 97 | return False |
111 | 98 | |
99 | def parseOutputString(self, output, debug=False): | |
100 | """ | |
101 | output is the shell output of command Dnswalk. | |
102 | """ | |
103 | parser = DnswalkParser(output) | |
112 | 104 | |
113 | def parseOutputString(self, output, debug = False): | |
114 | """ | |
115 | This method will discard the output the shell sends, it will read it from | |
116 | the xml where it expects it to be present. | |
105 | for item in parser.items: | |
117 | 106 | |
118 | NOTE: if 'debug' is true then it is being run from a test case and the | |
119 | output being sent is valid. | |
120 | """ | |
121 | ||
122 | ||
123 | if debug: | |
124 | parser = DnswalkParser(output) | |
125 | else: | |
126 | ||
127 | parser = DnswalkParser(output) | |
107 | if item['type'] == "A": | |
128 | 108 | |
129 | print parser.items.__len__() | |
130 | for item in parser.items: | |
131 | if item['type'] == "A": | |
132 | 109 | h_id = self.createAndAddHost(item['ip']) |
133 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address=item['ip'],hostname_resolution=item['host']) | |
110 | i_id = self.createAndAddInterface( | |
111 | h_id, | |
112 | item['ip'], | |
113 | ipv4_address=item['ip'], | |
114 | hostname_resolution=item['host']) | |
115 | ||
134 | 116 | elif item['type'] == "info": |
117 | ||
135 | 118 | h_id = self.createAndAddHost(item['ip']) |
136 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address=item['ip'],hostname_resolution=item['host']) | |
137 | s_id = self.createAndAddServiceToInterface(h_id, i_id, "domain", "tcp", ports=['53']) | |
138 | self.createAndAddVulnToService(h_id, s_id, "Zone transfer", desc="A Dns server allows unrestricted zone transfers", | |
139 | ref=["CVE-1999-0532"]) | |
140 | 119 | |
141 | del parser | |
120 | i_id = self.createAndAddInterface( | |
121 | h_id, | |
122 | item['ip'], | |
123 | ipv4_address=item['ip'], | |
124 | hostname_resolution=item['host']) | |
125 | ||
126 | s_id = self.createAndAddServiceToInterface( | |
127 | h_id, | |
128 | i_id, | |
129 | "domain", | |
130 | "tcp", | |
131 | ports=['53']) | |
132 | ||
133 | self.createAndAddVulnToService( | |
134 | h_id, | |
135 | s_id, | |
136 | "Zone transfer", | |
137 | desc="A Dns server allows unrestricted zone transfers", | |
138 | ref=["CVE-1999-0532"]) | |
139 | ||
142 | 140 | return True |
143 | 141 | |
144 | 142 | def processCommandString(self, username, current_path, command_string): |
145 | """ | |
146 | """ | |
147 | 143 | return None |
144 | ||
148 | 145 | |
149 | 146 | def createPlugin(): |
150 | 147 | return DnswalkPlugin() |
7 | 7 | ''' |
8 | 8 | from __future__ import with_statement |
9 | 9 | from plugins import core |
10 | from model import api | |
11 | 10 | import socket |
12 | 11 | import re |
13 | 12 | import os |
14 | import pprint | |
15 | 13 | import sys |
16 | ||
17 | 14 | |
18 | 15 | current_path = os.path.abspath(os.getcwd()) |
19 | 16 | |
20 | __author__ = "Francisco Amato" | |
21 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
22 | __credits__ = ["Francisco Amato"] | |
23 | __license__ = "" | |
24 | __version__ = "1.0.0" | |
17 | __author__ = "Francisco Amato" | |
18 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
19 | __credits__ = ["Francisco Amato"] | |
20 | __license__ = "" | |
21 | __version__ = "1.0.0" | |
25 | 22 | __maintainer__ = "Francisco Amato" |
26 | __email__ = "[email protected]" | |
27 | __status__ = "Development" | |
28 | ||
29 | ||
30 | ||
31 | ||
23 | __email__ = "[email protected]" | |
24 | __status__ = "Development" | |
32 | 25 | |
33 | 26 | valid_records = ["NS", "CNAME", "A"] |
34 | 27 | |
35 | 28 | class FierceParser(object): |
36 | 29 | """ |
37 | The objective of this class is to parse an xml file generated by the fierce tool. | |
30 | The objective of this class is to parse an shell output generated by | |
31 | the fierce tool. | |
38 | 32 | |
39 | 33 | TODO: Handle errors. |
40 | TODO: Test fierce output version. Handle what happens if the parser doesn't support it. | |
34 | TODO: Test fierce output version. Handle what happens if the parser | |
35 | doesn't support it. | |
41 | 36 | TODO: Test cases. |
42 | 37 | |
43 | 38 | @param fierce_filepath A proper simple report generated by fierce |
44 | 39 | """ |
45 | 40 | def __init__(self, output): |
46 | ||
47 | ||
41 | ||
48 | 42 | self.target = None |
49 | 43 | self.items = [] |
50 | ||
51 | ||
52 | ||
53 | ||
54 | ||
55 | ||
56 | ||
57 | r = re.search("DNS Servers for ([\w\.-]+):\r\n([^$]+)Trying zone transfer first...",output) | |
44 | ||
45 | r = re.search( | |
46 | "DNS Servers for ([\w\.-]+):\r\n([^$]+)Trying zone transfer first...", | |
47 | output) | |
48 | ||
58 | 49 | if r is not None: |
59 | 50 | self.target = r.group(1) |
60 | mstr = re.sub("\t","",r.group(2)) | |
51 | mstr = re.sub("\t", "", r.group(2)) | |
61 | 52 | self.dns = mstr.split() |
62 | ||
63 | ||
64 | ||
65 | ||
66 | r = re.search("Now performing [\d]+ test\(s\)...\r\n([^$]+)\x0D\nSubnets found ",output) | |
53 | ||
54 | r = re.search( | |
55 | "Now performing [\d]+ test\(s\)...\r\n([^$]+)\x0D\nSubnets found ", | |
56 | output) | |
57 | ||
67 | 58 | if r is not None: |
68 | 59 | list = r.group(1).split("\r\n") |
69 | 60 | for i in list: |
70 | 61 | if i != "": |
71 | 62 | mstr = i.split("\t") |
72 | item = {'host' : mstr[1], 'type' : "A", 'ip' : mstr[0]} | |
63 | item = {'host': mstr[1], 'type': "A", 'ip': mstr[0]} | |
73 | 64 | self.items.append(item) |
74 | ||
75 | ||
76 | ||
77 | ||
65 | ||
78 | 66 | self.isZoneVuln = False |
79 | r = re.search("Whoah, it worked - misconfigured DNS server found:\r\n([^$]+)There isn't much point continuing, you have everything.",output) | |
67 | r = re.search( | |
68 | "Whoah, it worked - misconfigured DNS server found:\r\n([^$]+)There isn't much point continuing, you have everything.", | |
69 | output) | |
70 | ||
80 | 71 | if r is not None: |
81 | ||
72 | ||
82 | 73 | self.isZoneVuln = True |
83 | 74 | list = r.group(1).split("\n") |
84 | 75 | for i in list: |
76 | ||
85 | 77 | if i != "": |
86 | 78 | mstr = i.split() |
87 | 79 | if (mstr and mstr[0] != "" and len(mstr) > 3 and mstr[3] in valid_records): |
88 | item = {'host' : mstr[0], 'type' : mstr[3], 'ip' : mstr[4]} | |
89 | ||
80 | item = {'host': mstr[0], 'type': mstr[3], 'ip': mstr[4]} | |
90 | 81 | self.items.append(item) |
82 | ||
91 | 83 | |
92 | 84 | class FiercePlugin(core.PluginBase): |
93 | 85 | """ |
95 | 87 | """ |
96 | 88 | def __init__(self): |
97 | 89 | core.PluginBase.__init__(self) |
98 | self.id = "Fierce" | |
99 | self.name = "Fierce Output Plugin" | |
100 | self.plugin_version = "0.0.1" | |
101 | self.version = "0.9.9" | |
102 | ||
103 | self.options = None | |
90 | self.id = "Fierce" | |
91 | self.name = "Fierce Output Plugin" | |
92 | self.plugin_version = "0.0.1" | |
93 | self.version = "0.9.9" | |
94 | self.options = None | |
104 | 95 | self._current_output = None |
105 | 96 | self._current_path = None |
106 | self._command_regex = re.compile(r'^(sudo fierce|fierce|sudo fierce\.pl|fierce\.pl|perl fierce\.pl|\.\/fierce\.pl).*?') | |
107 | self._completition = { | |
108 | "":"perl fierce.pl [-dns example.com] [OPTIONS]", | |
109 | "-connect":"Attempt to make http connections to any non RFC1918 lot of free time on your hands (could take hours-days). ", | |
110 | "-delay":"The number of seconds to wait between lookups.", | |
111 | "-dns":"The domain you would like scanned.", | |
112 | "-dnsfile":"Use DNS servers provided by a file (one per line) for", | |
113 | "-dnsserver":"Use a particular DNS server for reverse lookups ", | |
114 | "-file":"A file you would like to output to be logged to.", | |
115 | "-fulloutput":"When combined with -connect this will output everything", | |
116 | "-help":"This screen.", | |
117 | "-nopattern":"Don't use a search pattern when looking for nearby", | |
118 | "-range":"Scan an internal IP range (must be combined with dnsserver). Note, that this does not support a pattern. perl fierce.pl -range 111.222.333.0-255 -dnsserver ns1.example.co", | |
119 | "-search":" -search Search list. When fierce attempts to traverse up and. perl fierce.pl -dns examplecompany.com -search corpcompany,blahcompany", | |
120 | "-suppress":"Suppress all TTY output (when combined with -file).", | |
121 | "-tcptimeout":"Specify a different timeout (default 10 seconds). You", | |
122 | "-threads":"Specify how many threads to use while scanning (default", | |
123 | "-traverse":"Specify a number of IPs above and below whatever IP you", | |
124 | "-version":"Output the version number.", | |
125 | "-wide":"Scan the entire class C after finding any matching", | |
126 | "-wordlist":"Use a seperate wordlist (one word per line). Usage:", | |
127 | } | |
128 | ||
97 | self._command_regex = re.compile( | |
98 | r'^(sudo fierce|fierce|sudo fierce\.pl|fierce\.pl|perl fierce\.pl|\.\/fierce\.pl).*?') | |
129 | 99 | global current_path |
130 | ||
131 | ||
132 | 100 | |
133 | 101 | def canParseCommandString(self, current_input): |
134 | 102 | if self._command_regex.match(current_input.strip()): |
155 | 123 | pass |
156 | 124 | return item |
157 | 125 | |
158 | def parseOutputString(self, output, debug = False): | |
159 | """ | |
160 | This method will discard the output the shell sends, it will read it from | |
161 | the xml where it expects it to be present. | |
126 | def parseOutputString(self, output, debug=False): | |
162 | 127 | |
163 | NOTE: if 'debug' is true then it is being run from a test case and the | |
164 | output being sent is valid. | |
165 | """ | |
166 | ||
167 | 128 | parser = FierceParser(output) |
168 | 129 | for item in parser.items: |
130 | ||
169 | 131 | item['isResolver'] = False |
170 | 132 | item['isZoneVuln'] = False |
171 | 133 | if (item['type'] == "CNAME"): |
175 | 137 | item['isResolver'] = True |
176 | 138 | item['isZoneVuln'] = parser.isZoneVuln |
177 | 139 | for item2 in parser.items: |
140 | ||
178 | 141 | if item['ip'] == item2['ip'] and item != item2: |
179 | 142 | item2['isResolver'] = item['isResolver'] |
180 | 143 | item2['isZoneVuln'] = item['isZoneVuln'] |
181 | ||
182 | 144 | item['ip'] = '' |
183 | 145 | |
184 | ||
185 | ||
186 | ||
187 | ||
188 | ||
146 | for item in parser.items: | |
189 | 147 | |
190 | ||
191 | ||
192 | ||
193 | ||
194 | for item in parser.items: | |
195 | 148 | if item['ip'] == "127.0.0.1" or item['ip'] == '': |
196 | 149 | continue |
197 | 150 | h_id = self.createAndAddHost(item['ip']) |
198 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address= item['ip'], hostname_resolution = [item['host']]) | |
151 | i_id = self.createAndAddInterface( | |
152 | h_id, | |
153 | item['ip'], | |
154 | ipv4_address=item['ip'], | |
155 | hostname_resolution=[item['host']]) | |
156 | ||
199 | 157 | if item['isResolver']: |
200 | s_id = self.createAndAddServiceToInterface(h_id, i_id, "domain", "tcp", ports=['53']) | |
158 | s_id = self.createAndAddServiceToInterface( | |
159 | h_id, | |
160 | i_id, | |
161 | "domain", | |
162 | "tcp", | |
163 | ports=['53']) | |
164 | ||
201 | 165 | if item['isZoneVuln']: |
202 | self.createAndAddVulnToService(h_id, s_id, "Zone transfer", desc="A Dns server allows unrestricted zone transfers", | |
203 | ref=["CVE-1999-0532"]) | |
204 | del parser | |
166 | self.createAndAddVulnToService( | |
167 | h_id, | |
168 | s_id, | |
169 | "Zone transfer", | |
170 | desc="A Dns server allows unrestricted zone transfers", | |
171 | ref=["CVE-1999-0532"]) | |
205 | 172 | |
206 | 173 | def processCommandString(self, username, current_path, command_string): |
207 | """ | |
208 | """ | |
209 | 174 | return None |
175 | ||
210 | 176 | |
211 | 177 | def createPlugin(): |
212 | 178 | return FiercePlugin() |
4 | 4 | Faraday Penetration Test IDE |
5 | 5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
6 | 6 | See the file 'doc/LICENSE' for the license information |
7 | ''' | |
7 | 8 | |
8 | ''' | |
9 | 9 | from __future__ import with_statement |
10 | 10 | from plugins import core |
11 | from model import api | |
11 | import socket | |
12 | import sys | |
12 | 13 | import re |
13 | import os, socket | |
14 | import pprint | |
15 | import sys | |
16 | ||
14 | import os | |
17 | 15 | |
18 | 16 | current_path = os.path.abspath(os.getcwd()) |
19 | 17 | |
20 | __author__ = "Francisco Amato" | |
21 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
22 | __credits__ = ["Francisco Amato"] | |
23 | __license__ = "" | |
24 | __version__ = "1.0.0" | |
18 | __author__ = "Francisco Amato" | |
19 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
20 | __credits__ = ["Francisco Amato"] | |
21 | __license__ = "" | |
22 | __version__ = "1.0.0" | |
25 | 23 | __maintainer__ = "Francisco Amato" |
26 | __email__ = "[email protected]" | |
27 | __status__ = "Development" | |
28 | ||
29 | ||
30 | ||
31 | ||
24 | __email__ = "[email protected]" | |
25 | __status__ = "Development" | |
32 | 26 | |
33 | 27 | class GoohostParser(object): |
34 | 28 | """ |
44 | 38 | def __init__(self, goohost_filepath, goohost_scantype): |
45 | 39 | self.filepath = goohost_filepath |
46 | 40 | self.scantype = goohost_scantype |
47 | ||
48 | ||
49 | ||
50 | with open(self.filepath,"r") as f: | |
51 | ||
52 | print "estoy por leer el archivo (%s)" % self.filepath | |
41 | ||
42 | with open(self.filepath, "r") as f: | |
43 | ||
53 | 44 | line = f.readline() |
54 | 45 | self.items = [] |
55 | print "afuera del while %s" % line | |
56 | 46 | while line: |
57 | print "estoy adentro del while %s" % line | |
58 | 47 | if self.scantype == 'ip': |
59 | 48 | minfo = line.split() |
60 | item = {'host' : minfo[0], 'ip' : minfo[1]} | |
61 | print "El item ip (%s) (%s)" % (minfo[0],minfo[1]) | |
49 | item = {'host': minfo[0], 'ip': minfo[1]} | |
62 | 50 | elif self.scantype == 'host': |
63 | 51 | line = line.strip() |
64 | item = {'host' : line, 'ip' : self.resolve(line)} | |
65 | print "El item ip (%s) (%s)" % (item['host'],item['ip']) | |
52 | item = {'host': line, 'ip': self.resolve(line)} | |
66 | 53 | else: |
67 | item = {'data' : line} | |
68 | print "el item host email %s" % line | |
69 | ||
54 | item = {'data': line} | |
55 | ||
70 | 56 | self.items.append(item) |
71 | 57 | line = f.readline() |
72 | 58 | |
84 | 70 | """ |
85 | 71 | def __init__(self): |
86 | 72 | core.PluginBase.__init__(self) |
87 | self.id = "Goohost" | |
88 | self.name = "Goohost XML Output Plugin" | |
89 | self.plugin_version = "0.0.1" | |
90 | self.version = "v.0.0.1" | |
91 | self.options = None | |
73 | self.id = "Goohost" | |
74 | self.name = "Goohost XML Output Plugin" | |
75 | self.plugin_version = "0.0.1" | |
76 | self.version = "v.0.0.1" | |
77 | self.options = None | |
92 | 78 | self._current_output = None |
93 | 79 | self._current_path = None |
94 | self._command_regex = re.compile(r'^(sudo goohost\.sh|goohost\.sh|sh goohost\.sh|\.\/goohost\.sh).*?') | |
80 | self._command_regex = re.compile( | |
81 | r'^(sudo goohost\.sh|goohost\.sh|sh goohost\.sh|\.\/goohost\.sh).*?') | |
95 | 82 | self.scantype = "host" |
96 | 83 | self.host = None |
97 | self._completition = { | |
98 | "":"./goohost.sh -t domain.tld [-m <host|ip|mail> -p <1-20> -v] ", | |
99 | "-t":"target domain. Ex: backtrack.linux.org ", | |
100 | "-m":"method: <ip|host|mail>. Default value is set to host ", | |
101 | "-p":"pages [1-20]. Max number of pages to download from Google. Default 5 ", | |
102 | "-v":"verbosity. Default is set to off ", | |
103 | } | |
104 | 84 | |
105 | 85 | global current_path |
106 | 86 | self.output_path = None |
107 | ||
108 | ||
109 | 87 | |
110 | ||
111 | def parseOutputString(self, output, debug = False): | |
88 | def parseOutputString(self, output, debug=False): | |
112 | 89 | """ |
113 | 90 | This method will discard the output the shell sends, it will read it from |
114 | 91 | the xml where it expects it to be present. |
116 | 93 | NOTE: if 'debug' is true then it is being run from a test case and the |
117 | 94 | output being sent is valid. |
118 | 95 | """ |
119 | ||
120 | print "este es el output (%s)" % output | |
121 | ||
96 | ||
122 | 97 | if self.output_path is None: |
123 | mypath = re.search("Results saved in file (\S+)",output); | |
124 | print "este es el output %s" % output | |
98 | mypath = re.search("Results saved in file (\S+)", output) | |
125 | 99 | if mypath is not None: |
126 | print "encontre el archivo '%s' '%s'" % (mypath.group(1), self.scantype) | |
127 | ||
128 | 100 | self.output_path = self._current_path + "/" + mypath.group(1) |
129 | ||
130 | ||
131 | 101 | else: |
132 | 102 | return False |
133 | ||
103 | ||
134 | 104 | if debug: |
135 | parser = GoohostParser(output,self.scantype) | |
105 | parser = GoohostParser(output, self.scantype) | |
136 | 106 | else: |
137 | 107 | if not os.path.exists(self.output_path): |
138 | print "el archivo no existe '%s'" % self.output_path | |
139 | 108 | return False |
140 | ||
141 | print "estoy a punto de entrar a goohost" | |
142 | parser = GoohostParser(self.output_path,self.scantype) | |
143 | 109 | |
110 | parser = GoohostParser(self.output_path, self.scantype) | |
144 | 111 | |
145 | ||
146 | ||
147 | ||
148 | ||
149 | if self.scantype == 'host' or self.scantype == 'ip' : | |
150 | ||
151 | ||
112 | if self.scantype == 'host' or self.scantype == 'ip': | |
152 | 113 | for item in parser.items: |
153 | 114 | h_id = self.createAndAddHost(item['ip']) |
154 | i_id = self.createAndAddInterface(h_id, item['ip'],ipv4_address=item['ip'],hostname_resolution=item['host']) | |
115 | i_id = self.createAndAddInterface( | |
116 | h_id, | |
117 | item['ip'], | |
118 | ipv4_address=item['ip'], | |
119 | hostname_resolution=item['host']) | |
155 | 120 | |
156 | 121 | del parser |
157 | ||
158 | ||
159 | ||
160 | ||
161 | 122 | |
162 | 123 | def processCommandString(self, username, current_path, command_string): |
163 | 124 | """ |
164 | Adds the -oX parameter to get xml output to the command string that the | |
165 | user has set. | |
125 | Set output path for parser... | |
166 | 126 | """ |
167 | ||
168 | ||
169 | ||
170 | ||
171 | ||
172 | ||
173 | ||
174 | ||
175 | ||
176 | ||
127 | self._current_path = current_path | |
177 | 128 | |
178 | 129 | def setHost(self): |
179 | 130 | pass |
24 | 24 | __email__ = "[email protected]" |
25 | 25 | __status__ = "Development" |
26 | 26 | |
27 | ||
28 | ||
29 | ||
27 | ||
28 | ||
29 | ||
30 | 30 | |
31 | 31 | class ListurlsParser(object): |
32 | 32 | """ |
45 | 45 | if re.search("Could not reach",output) is not None: |
46 | 46 | self.fail = True |
47 | 47 | return |
48 | ||
48 | ||
49 | 49 | for line in lists: |
50 | 50 | if i > 8: |
51 | 51 | print line |
78 | 78 | } |
79 | 79 | |
80 | 80 | global current_path |
81 | self.output_path = os.path.join(self.data_path, | |
81 | self.output_file_path = os.path.join(self.data_path, | |
82 | 82 | "listurls_output-%s.txt" % self._rid) |
83 | ||
84 | ||
83 | ||
84 | ||
85 | 85 | |
86 | 86 | def canParseCommandString(self, current_input): |
87 | 87 | if self._command_regex.match(current_input.strip()): |
98 | 98 | NOTE: if 'debug' is true then it is being run from a test case and the |
99 | 99 | output being sent is valid. |
100 | 100 | """ |
101 | ||
102 | ||
103 | ||
104 | ||
105 | ||
106 | ||
107 | ||
108 | ||
109 | ||
110 | ||
111 | ||
112 | ||
113 | ||
114 | ||
115 | ||
116 | ||
117 | ||
118 | ||
119 | ||
120 | ||
121 | ||
122 | ||
123 | ||
124 | ||
125 | ||
126 | ||
127 | ||
128 | ||
129 | ||
130 | ||
131 | ||
132 | ||
133 | ||
134 | ||
135 | ||
136 | ||
137 | ||
138 | ||
139 | ||
140 | ||
141 | 101 | |
142 | ||
143 | ||
144 | ||
145 | 102 | def processCommandString(self, username, current_path, command_string): |
146 | ||
103 | ||
147 | 104 | host = re.search("(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", command_string) |
148 | ||
105 | ||
149 | 106 | self.protocol = host.group(1) |
150 | 107 | self.host = host.group(4) |
151 | 108 | if self.protocol == 'https': |
153 | 110 | if host.group(11) is not None: |
154 | 111 | self.port = host.group(11) |
155 | 112 | return None |
156 | ||
113 | ||
157 | 114 | def setHost(self): |
158 | 115 | pass |
159 | 116 |
8 | 8 | |
9 | 9 | from __future__ import with_statement |
10 | 10 | from plugins import core |
11 | from model import api | |
12 | 11 | |
13 | 12 | import zipfile |
14 | 13 | import sys |
27 | 26 | |
28 | 27 | current_path = os.path.abspath(os.getcwd()) |
29 | 28 | |
30 | __author__ = "Ezequiel Tavella" | |
31 | __copyright__ = "Copyright (c) 2015, Infobyte LLC" | |
32 | __credits__ = ["Ezequiel Tavella"] | |
33 | __license__ = "" | |
34 | __version__ = "1.0.1" | |
29 | __author__ = "Ezequiel Tavella" | |
30 | __copyright__ = "Copyright (c) 2015, Infobyte LLC" | |
31 | __credits__ = ["Ezequiel Tavella"] | |
32 | __license__ = "" | |
33 | __version__ = "1.0.1" | |
35 | 34 | __maintainer__ = "Ezequiel Tavella" |
36 | __status__ = "Development" | |
35 | __status__ = "Development" | |
36 | ||
37 | 37 | |
38 | 38 | def openMtgx(mtgx_file): |
39 | 39 | |
47 | 47 | |
48 | 48 | file.close() |
49 | 49 | return xml |
50 | ||
50 | 51 | |
51 | 52 | class Host(): |
52 | 53 | |
60 | 61 | self.mx_record = "" |
61 | 62 | self.ns_record = "" |
62 | 63 | |
64 | ||
63 | 65 | class MaltegoMtgxParser(): |
64 | 66 | |
65 | 67 | def __init__(self, xml_file): |
67 | 69 | self.xml = openMtgx(xml_file) |
68 | 70 | |
69 | 71 | self.nodes = self.xml.findall( |
70 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
71 | "{http://graphml.graphdrawing.org/xmlns}node" | |
72 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
73 | "{http://graphml.graphdrawing.org/xmlns}node" | |
72 | 74 | ) |
73 | 75 | |
74 | 76 | self.edges = self.xml.findall( |
75 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
76 | "{http://graphml.graphdrawing.org/xmlns}edge" | |
77 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
78 | "{http://graphml.graphdrawing.org/xmlns}edge" | |
77 | 79 | ) |
78 | 80 | |
79 | 81 | self.list_hosts = [] |
92 | 94 | target = edge.get("target") |
93 | 95 | |
94 | 96 | if source not in self.relations: |
95 | self.relations.update({source : [target]}) | |
97 | self.relations.update({source: [target]}) | |
96 | 98 | |
97 | 99 | if target not in self.relations: |
98 | self.relations.update({target : [source]}) | |
100 | self.relations.update({target: [source]}) | |
99 | 101 | |
100 | 102 | values = self.relations[source] |
101 | 103 | values.append(target) |
102 | self.relations.update({source : values}) | |
104 | self.relations.update({source: values}) | |
103 | 105 | |
104 | 106 | values = self.relations[target] |
105 | 107 | values.append(source) |
106 | self.relations.update({target : values}) | |
108 | self.relations.update({target: values}) | |
107 | 109 | |
108 | 110 | def getIpAndId(self, node): |
109 | 111 | |
110 | #Find node ID and maltego entity | |
112 | # Find node ID and maltego entity | |
111 | 113 | node_id = node.get("id") |
112 | 114 | entity = node.find( |
113 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
114 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity" | |
115 | ) | |
116 | ||
117 | #Check if is IPv4Address | |
115 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
116 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity" | |
117 | ) | |
118 | ||
119 | # Check if is IPv4Address | |
118 | 120 | if entity.get("type") != "maltego.IPv4Address": |
119 | 121 | return None |
120 | 122 | |
121 | #Get IP value | |
123 | # Get IP value | |
122 | 124 | value = entity.find( |
123 | "{http://maltego.paterva.com/xml/mtgx}Properties/" | |
124 | "{http://maltego.paterva.com/xml/mtgx}Property/" | |
125 | "{http://maltego.paterva.com/xml/mtgx}Value" | |
126 | ) | |
127 | ||
128 | return {"node_id" : node_id ,"ip" : value.text} | |
125 | "{http://maltego.paterva.com/xml/mtgx}Properties/" | |
126 | "{http://maltego.paterva.com/xml/mtgx}Property/" | |
127 | "{http://maltego.paterva.com/xml/mtgx}Value" | |
128 | ) | |
129 | ||
130 | return {"node_id": node_id, "ip": value.text} | |
129 | 131 | |
130 | 132 | def getNode(self, node_id): |
131 | 133 | |
132 | #Get node, filter by id | |
134 | # Get node, filter by id | |
133 | 135 | for node in self.nodes: |
134 | 136 | |
135 | 137 | if node.get("id") == node_id: |
137 | 139 | |
138 | 140 | def getType(self, node): |
139 | 141 | |
140 | #Get type of this node | |
142 | # Get type of this node | |
141 | 143 | entity = node.find( |
142 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
143 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity" | |
144 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
145 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity" | |
144 | 146 | ) |
145 | 147 | |
146 | 148 | return entity.get("type") |
147 | 149 | |
148 | 150 | def getWebsite(self, target_node): |
149 | 151 | |
150 | #Parse Website Entity | |
151 | result = {"name" : "", "ssl_enabled": "" , "urls": ""} | |
152 | # Parse Website Entity | |
153 | result = {"name": "", "ssl_enabled": "", "urls": ""} | |
152 | 154 | |
153 | 155 | props = target_node.find( |
154 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
155 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
156 | "{http://maltego.paterva.com/xml/mtgx}Properties" | |
156 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
157 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
158 | "{http://maltego.paterva.com/xml/mtgx}Properties" | |
157 | 159 | ) |
158 | 160 | |
159 | 161 | for prop in props: |
160 | 162 | |
161 | 163 | name_property = prop.get("name") |
162 | value = prop.find("{http://maltego.paterva.com/xml/mtgx}Value").text | |
164 | value = prop.find( | |
165 | "{http://maltego.paterva.com/xml/mtgx}Value").text | |
163 | 166 | |
164 | 167 | if name_property == "fqdn": |
165 | 168 | result["name"] = value |
172 | 175 | |
173 | 176 | def getNetBlock(self, target_node): |
174 | 177 | |
175 | #Parse Netblock Entity | |
176 | result = {"ipv4_range" : "", "network_owner": "" , "country": ""} | |
178 | # Parse Netblock Entity | |
179 | result = {"ipv4_range": "", "network_owner": "", "country": ""} | |
177 | 180 | |
178 | 181 | props = target_node.find( |
179 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
180 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
181 | "{http://maltego.paterva.com/xml/mtgx}Properties" | |
182 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
183 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
184 | "{http://maltego.paterva.com/xml/mtgx}Properties" | |
182 | 185 | ) |
183 | 186 | |
184 | 187 | for prop in props: |
185 | 188 | |
186 | 189 | name_property = prop.get("name") |
187 | value = prop.find("{http://maltego.paterva.com/xml/mtgx}Value").text | |
190 | value = prop.find( | |
191 | "{http://maltego.paterva.com/xml/mtgx}Value").text | |
188 | 192 | |
189 | 193 | if name_property == "ipv4-range": |
190 | 194 | result["ipv4_range"] = value |
197 | 201 | |
198 | 202 | def getLocation(self, target_node): |
199 | 203 | |
200 | #Parse Location Entity | |
201 | result = {"name" : "", "area": "" , "country_code": "", | |
202 | "longitude" : "", "latitude" : "", "area_2" : "" | |
203 | } | |
204 | ||
205 | #Get relations with other nodes | |
204 | # Parse Location Entity | |
205 | result = { | |
206 | "name": "", | |
207 | "area": "", | |
208 | "country_code": "", | |
209 | "longitude": "", | |
210 | "latitude": "", | |
211 | "area_2": ""} | |
212 | ||
213 | # Get relations with other nodes | |
206 | 214 | node_relations = self.relations[target_node.get("id")] |
207 | 215 | |
208 | #Find location node based in relation with netblock node. | |
216 | # Find location node based in relation with netblock node. | |
209 | 217 | located = False |
210 | 218 | for node_id in node_relations: |
211 | 219 | |
217 | 225 | if not located: |
218 | 226 | return None |
219 | 227 | |
220 | #Get properties and update data | |
228 | # Get properties and update data | |
221 | 229 | props = target_node.find( |
222 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
223 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
224 | "{http://maltego.paterva.com/xml/mtgx}Properties" | |
230 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
231 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
232 | "{http://maltego.paterva.com/xml/mtgx}Properties" | |
225 | 233 | ) |
226 | 234 | |
227 | 235 | for prop in props: |
228 | 236 | |
229 | 237 | name_property = prop.get("name") |
230 | value = prop.find("{http://maltego.paterva.com/xml/mtgx}Value").text | |
238 | value = prop.find( | |
239 | "{http://maltego.paterva.com/xml/mtgx}Value").text | |
231 | 240 | |
232 | 241 | if name_property == "location.name": |
233 | 242 | result["name"] = value |
246 | 255 | |
247 | 256 | def getValue(self, target_node): |
248 | 257 | |
249 | #Parse Entity | |
250 | result = {"value" : ""} | |
258 | # Parse Entity | |
259 | result = {"value": ""} | |
251 | 260 | |
252 | 261 | value = target_node.find( |
253 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
254 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
255 | "{http://maltego.paterva.com/xml/mtgx}Properties/" | |
256 | "{http://maltego.paterva.com/xml/mtgx}Property/" | |
257 | "{http://maltego.paterva.com/xml/mtgx}Value" | |
262 | "{http://graphml.graphdrawing.org/xmlns}data/" | |
263 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity/" | |
264 | "{http://maltego.paterva.com/xml/mtgx}Properties/" | |
265 | "{http://maltego.paterva.com/xml/mtgx}Property/" | |
266 | "{http://maltego.paterva.com/xml/mtgx}Value" | |
258 | 267 | ) |
259 | 268 | |
260 | 269 | result["value"] = value.text |
266 | 275 | |
267 | 276 | for node in self.nodes: |
268 | 277 | |
269 | #Get IP Address if not continue with other node... | |
278 | # Get IP Address if not continue with other node... | |
270 | 279 | result = self.getIpAndId(node) |
271 | 280 | if not result: |
272 | 281 | continue |
273 | 282 | |
274 | #Create host with values by default | |
283 | # Create host with values by default | |
275 | 284 | host = Host() |
276 | 285 | host.ip = result["ip"] |
277 | 286 | host.node_id = result["node_id"] |
278 | 287 | |
279 | #Get relations with other nodes | |
288 | # Get relations with other nodes | |
280 | 289 | node_relations = self.relations[host.node_id] |
281 | 290 | |
282 | 291 | for node_id in node_relations: |
283 | 292 | |
284 | #Get target node and type of node. | |
293 | # Get target node and type of node. | |
285 | 294 | target_node = self.getNode(node_id) |
286 | 295 | target_type = self.getType(target_node) |
287 | 296 | |
288 | #Check type of node y add data to host... | |
297 | # Check type of node y add data to host... | |
289 | 298 | if target_type == "maltego.DNSName": |
290 | 299 | host.dns_name = self.getValue(target_node) |
291 | 300 | elif target_type == "maltego.Website": |
292 | 301 | host.website = self.getWebsite(target_node) |
293 | 302 | elif target_type == "maltego.Netblock": |
294 | 303 | host.netblock = self.getNetBlock(target_node) |
295 | #Get location based in relation: netblock -> location | |
304 | # Get location based in relation: netblock -> location | |
296 | 305 | host.location = self.getLocation(target_node) |
297 | 306 | elif target_type == "maltego.MXRecord": |
298 | 307 | host.mx_record = self.getValue(target_node) |
303 | 312 | |
304 | 313 | return self.list_hosts |
305 | 314 | |
315 | ||
306 | 316 | class MaltegoPlugin(core.PluginBase): |
307 | ||
308 | def __init__(self): | |
309 | ||
310 | core.PluginBase.__init__(self) | |
311 | self.id = "Maltego" | |
312 | self.name = "Maltego MTGX Output Plugin" | |
313 | self.plugin_version = "1.0.1" | |
314 | self.version = "Maltego 3.6" | |
315 | self.framework_version = "1.0.0" | |
316 | self.current_path = None | |
317 | self.options = None | |
318 | self._current_output = None | |
319 | ||
320 | self._command_regex = re.compile(r'^(sudo maltego_faraday|maltego_faraday|\.\/maltego_faraday).*?') | |
321 | ||
322 | self.report = None | |
323 | global current_path | |
324 | ||
325 | def parseOutputString(self, output, debug = False): | |
326 | ||
327 | maltego_parser = MaltegoMtgxParser(self.report) | |
328 | for host in maltego_parser.parse(): | |
329 | ||
330 | #Create host | |
331 | try: | |
332 | old_hostname = host.dns_name["value"] | |
333 | except: | |
334 | old_hostname = "unknown" | |
335 | ||
336 | host_id = self.createAndAddHost( | |
337 | name = host.ip, | |
338 | old_hostname = old_hostname | |
339 | ) | |
340 | ||
341 | #Create interface | |
342 | try: | |
343 | network_segment = host.netblock["ipv4_range"] | |
344 | hostname_resolution = [host.dns_name["value"]] | |
345 | except: | |
346 | network_segment = "unknown" | |
347 | hostname_resolution = "unknown" | |
348 | ||
349 | interface_id = self.createAndAddInterface( | |
350 | host_id = host_id, | |
351 | name = host.ip, | |
352 | ipv4_address = host.ip, | |
353 | network_segment = network_segment , | |
354 | hostname_resolution = hostname_resolution | |
355 | ) | |
356 | ||
357 | #Create note with NetBlock information | |
358 | if host.netblock: | |
359 | ||
360 | try: | |
361 | ||
362 | text = ( | |
363 | "Network owner:\n" + | |
364 | host.netblock["network_owner"] or "unknown" + | |
365 | "Country:\n" + host.netblock["country"] or "unknown" | |
366 | ) | |
367 | except: | |
368 | text = "unknown" | |
369 | ||
370 | self.createAndAddNoteToHost( | |
371 | host_id = host_id, | |
372 | name = "Netblock Information", | |
373 | text = text.encode('ascii', 'ignore') | |
374 | ) | |
375 | ||
376 | #Create note with host location | |
377 | if host.location: | |
378 | ||
379 | try: | |
380 | text = ( | |
381 | "Location:\n" + | |
382 | host.location["name"] + | |
383 | "\nArea:\n" + | |
384 | host.location["area"] + | |
385 | "\nArea 2:\n" + | |
386 | host.location["area_2"] + | |
387 | "\nCountry_code:\n" + | |
388 | host.location["country_code"] + | |
389 | "\nLatitude:\n" + | |
390 | host.location["latitude"] + | |
391 | "\nLongitude:\n" + | |
392 | host.location["longitude"] | |
393 | ) | |
394 | except: | |
395 | text = "unknown" | |
396 | ||
397 | self.createAndAddNoteToHost( | |
398 | host_id = host_id, | |
399 | name = "Location Information", | |
400 | text = text.encode('ascii', 'ignore') | |
401 | ) | |
402 | ||
403 | #Create service web server | |
404 | if host.website: | |
405 | ||
406 | try: | |
407 | description = "SSL Enabled: " + host.website["ssl_enabled"] | |
408 | except: | |
409 | description = "unknown" | |
410 | ||
411 | service_id = self.createAndAddServiceToInterface( | |
412 | host_id = host_id, | |
413 | interface_id = interface_id, | |
414 | name = host.website["name"], | |
415 | protocol = "TCP:HTTP", | |
416 | ports = [80], | |
417 | description = description | |
418 | ) | |
419 | ||
420 | try: | |
421 | ||
422 | text =( | |
423 | "Urls:\n" + host.website["urls"] | |
424 | ) | |
425 | ||
426 | self.createAndAddNoteToService( | |
427 | host_id = host_id, | |
428 | service_id = service_id, | |
429 | name = "URLs", | |
430 | text = text.encode('ascii', 'ignore') | |
431 | ) | |
432 | ||
433 | except: | |
434 | pass | |
435 | ||
436 | if host.mx_record: | |
437 | ||
438 | self.createAndAddServiceToInterface( | |
439 | host_id = host_id, | |
440 | interface_id = interface_id, | |
441 | name = host.mx_record["value"], | |
442 | protocol = "SMTP", | |
443 | ports = [25], | |
444 | description = "E-mail Server", | |
445 | ) | |
446 | ||
447 | if host.ns_record: | |
448 | ||
449 | self.createAndAddServiceToInterface( | |
450 | host_id = host_id, | |
451 | interface_id = interface_id, | |
452 | name = host.ns_record["value"], | |
453 | protocol = "DNS", | |
454 | ports = [53], | |
455 | description = "DNS Server", | |
456 | ) | |
457 | ||
458 | def processCommandString(self, username, current_path, command_string): | |
459 | ||
460 | report = command_string.split("maltego_faraday ")[1] | |
461 | self.report = os.path.join(current_path, report) | |
317 | def __init__(self): | |
318 | core.PluginBase.__init__(self) | |
319 | self.id = "Maltego" | |
320 | self.name = "Maltego MTGX Output Plugin" | |
321 | self.plugin_version = "1.0.1" | |
322 | self.version = "Maltego 3.6" | |
323 | self.framework_version = "1.0.0" | |
324 | self.current_path = None | |
325 | self.options = None | |
326 | self._current_output = None | |
327 | self._command_regex = re.compile( | |
328 | r'^(sudo maltego|maltego|\.\/maltego).*?') | |
329 | global current_path | |
330 | ||
331 | def parseOutputString(self, filename, debug=False): | |
332 | ||
333 | maltego_parser = MaltegoMtgxParser(filename) | |
334 | for host in maltego_parser.parse(): | |
335 | # Create host | |
336 | try: | |
337 | old_hostname = host.dns_name["value"] | |
338 | except: | |
339 | old_hostname = "unknown" | |
340 | ||
341 | host_id = self.createAndAddHost( | |
342 | name=host.ip, | |
343 | old_hostname=old_hostname) | |
344 | ||
345 | # Create interface | |
346 | try: | |
347 | network_segment = host.netblock["ipv4_range"] | |
348 | hostname_resolution = [host.dns_name["value"]] | |
349 | except: | |
350 | network_segment = "unknown" | |
351 | hostname_resolution = "unknown" | |
352 | ||
353 | interface_id = self.createAndAddInterface( | |
354 | host_id=host_id, | |
355 | name=host.ip, | |
356 | ipv4_address=host.ip, | |
357 | network_segment=network_segment, | |
358 | hostname_resolution=hostname_resolution) | |
359 | ||
360 | # Create note with NetBlock information | |
361 | if host.netblock: | |
362 | try: | |
363 | text = ( | |
364 | "Network owner:\n" + | |
365 | host.netblock["network_owner"] or "unknown" + | |
366 | "Country:\n" + host.netblock["country"] or "unknown") | |
367 | except: | |
368 | text = "unknown" | |
369 | ||
370 | self.createAndAddNoteToHost( | |
371 | host_id=host_id, | |
372 | name="Netblock Information", | |
373 | text=text.encode('ascii', 'ignore') | |
374 | ) | |
375 | ||
376 | # Create note with host location | |
377 | if host.location: | |
378 | try: | |
379 | text = ( | |
380 | "Location:\n" + | |
381 | host.location["name"] + | |
382 | "\nArea:\n" + | |
383 | host.location["area"] + | |
384 | "\nArea 2:\n" + | |
385 | host.location["area_2"] + | |
386 | "\nCountry_code:\n" + | |
387 | host.location["country_code"] + | |
388 | "\nLatitude:\n" + | |
389 | host.location["latitude"] + | |
390 | "\nLongitude:\n" + | |
391 | host.location["longitude"]) | |
392 | except: | |
393 | text = "unknown" | |
394 | ||
395 | self.createAndAddNoteToHost( | |
396 | host_id=host_id, | |
397 | name="Location Information", | |
398 | text=text.encode('ascii', 'ignore')) | |
399 | ||
400 | # Create service web server | |
401 | if host.website: | |
402 | try: | |
403 | description = "SSL Enabled: " + host.website["ssl_enabled"] | |
404 | except: | |
405 | description = "unknown" | |
406 | ||
407 | service_id = self.createAndAddServiceToInterface( | |
408 | host_id=host_id, | |
409 | interface_id=interface_id, | |
410 | name=host.website["name"], | |
411 | protocol="TCP:HTTP", | |
412 | ports=[80], | |
413 | description=description | |
414 | ) | |
415 | ||
416 | try: | |
417 | text = "Urls:\n" + host.website["urls"] | |
418 | ||
419 | self.createAndAddNoteToService( | |
420 | host_id=host_id, | |
421 | service_id=service_id, | |
422 | name="URLs", | |
423 | text=text.encode('ascii', 'ignore')) | |
424 | except: | |
425 | pass | |
426 | ||
427 | if host.mx_record: | |
428 | ||
429 | self.createAndAddServiceToInterface( | |
430 | host_id=host_id, | |
431 | interface_id=interface_id, | |
432 | name=host.mx_record["value"], | |
433 | protocol="SMTP", | |
434 | ports=[25], | |
435 | description="E-mail Server") | |
436 | ||
437 | if host.ns_record: | |
438 | ||
439 | self.createAndAddServiceToInterface( | |
440 | host_id=host_id, | |
441 | interface_id=interface_id, | |
442 | name=host.ns_record["value"], | |
443 | protocol="DNS", | |
444 | ports=[53], | |
445 | description="DNS Server") | |
446 | ||
447 | def processReport(self, filepath): | |
448 | self.parseOutputString(filepath) | |
449 | ||
450 | def processCommandString(self, username, current_path, command_string): | |
451 | pass | |
452 | ||
462 | 453 | |
463 | 454 | def createPlugin(): |
464 | 455 | return MaltegoPlugin() |
26 | 26 | __email__ = "[email protected]" |
27 | 27 | __status__ = "Development" |
28 | 28 | |
29 | ||
30 | ||
31 | ||
29 | ||
30 | ||
31 | ||
32 | 32 | |
33 | 33 | class MetagoofilParser(object): |
34 | 34 | """ |
41 | 41 | @param metagoofil_filepath A proper simple report generated by metagoofil |
42 | 42 | """ |
43 | 43 | def __init__(self, output): |
44 | ||
45 | ||
44 | ||
45 | ||
46 | 46 | self.items = [] |
47 | ||
47 | ||
48 | 48 | mfile = open("/root/dev/faraday/trunk/src/del", "r") |
49 | 49 | output = mfile.read() |
50 | 50 | mfile.close() |
51 | print "adentro del init (%s)" % output | |
52 | ||
53 | ||
54 | ||
55 | ||
56 | ||
57 | ||
58 | ||
59 | ||
60 | ||
61 | ||
62 | 51 | |
63 | ||
64 | ||
65 | ||
52 | ||
53 | ||
54 | ||
55 | ||
56 | ||
57 | ||
58 | ||
59 | ||
60 | ||
61 | ||
62 | ||
63 | ||
64 | ||
66 | 65 | mregex = re.search("\[\+\] List of paths and servers found:[-\s]+([^$]+)\[\+\] List of e-mails found:", output,re.M) |
67 | 66 | if mregex is None: |
68 | print "mregex no andubo" | |
69 | 67 | return |
70 | ||
71 | ||
72 | print "email (%s) " % (mregex.group(1)) | |
68 | ||
69 | ||
73 | 70 | self.users = mregex.group(1).split("\n") |
74 | 71 | self.software =mregex.group(2).split("\n") |
75 | 72 | self.servers=mregex.group(1).strip().split("\n") |
76 | ||
73 | ||
77 | 74 | for line in self.servers: |
78 | 75 | line = line.strip() |
79 | 76 | item = {'host' : line, 'ip' : self.resolve(line)} |
80 | print item | |
81 | 77 | self.items.append(item) |
82 | 78 | |
83 | 79 | def resolve(self, host): |
113 | 109 | } |
114 | 110 | |
115 | 111 | global current_path |
116 | ||
117 | ||
112 | ||
113 | ||
118 | 114 | |
119 | 115 | def canParseCommandString(self, current_input): |
120 | 116 | if self._command_regex.match(current_input.strip()): |
131 | 127 | NOTE: if 'debug' is true then it is being run from a test case and the |
132 | 128 | output being sent is valid. |
133 | 129 | """ |
134 | ||
135 | ||
136 | ||
137 | ||
138 | ||
139 | ||
140 | ||
141 | ||
142 | ||
143 | ||
144 | ||
145 | ||
146 | ||
147 | ||
148 | ||
149 | ||
150 | ||
151 | ||
152 | ||
153 | ||
154 | ||
155 | ||
130 | ||
156 | 131 | def processCommandString(self, username, current_path, command_string): |
157 | 132 | """ |
158 | 133 | """ |
7 | 7 | |
8 | 8 | from __future__ import with_statement |
9 | 9 | from plugins import core |
10 | from model import api | |
11 | 10 | import re |
12 | 11 | import os |
13 | import pprint | |
14 | 12 | import sys |
15 | 13 | import random |
16 | 14 | import HTMLParser |
76 | 74 | """ |
77 | 75 | @return items A list of Host instances |
78 | 76 | """ |
79 | for host_node in tree.find('niktoscan').findall('scandetails'): | |
80 | yield Host(host_node) | |
77 | if tree.find('niktoscan'): | |
78 | for host_node in tree.find('niktoscan').findall('scandetails'): | |
79 | yield Host(host_node) | |
80 | else: | |
81 | for host_node in tree.findall('scandetails'): | |
82 | yield Host(host_node) | |
81 | 83 | |
82 | 84 | |
83 | 85 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): |
21 | 21 | except ImportError: |
22 | 22 | import xml.etree.ElementTree as ET |
23 | 23 | ETREE_VERSION = ET.VERSION |
24 | ||
24 | ||
25 | 25 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
26 | 26 | |
27 | 27 | current_path = os.path.abspath(os.getcwd()) |
35 | 35 | __email__ = "[email protected]" |
36 | 36 | __status__ = "Development" |
37 | 37 | |
38 | ||
39 | ||
40 | ||
38 | ||
39 | ||
40 | ||
41 | 41 | |
42 | 42 | class OpenvasXmlParser(object): |
43 | 43 | """ |
55 | 55 | self.host = None |
56 | 56 | |
57 | 57 | tree = self.parse_xml(xml_output) |
58 | ||
58 | ||
59 | 59 | if tree: |
60 | 60 | self.items = [data for data in self.get_items(tree)] |
61 | 61 | else: |
62 | 62 | self.items = [] |
63 | ||
63 | ||
64 | 64 | |
65 | 65 | def parse_xml(self, xml_output): |
66 | 66 | """ |
84 | 84 | @return items A list of Host instances |
85 | 85 | """ |
86 | 86 | bugtype="" |
87 | ||
88 | ||
87 | ||
88 | ||
89 | 89 | node = tree.findall('report')[0] |
90 | 90 | node2 = node.findall('results')[0] |
91 | ||
91 | ||
92 | 92 | for node in node2.findall('result'): |
93 | 93 | yield Item(node) |
94 | ||
95 | ||
96 | ||
97 | ||
94 | ||
95 | ||
96 | ||
97 | ||
98 | 98 | def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name): |
99 | 99 | """ |
100 | 100 | Finds a subnode in the item node and the retrieves a value from it |
103 | 103 | """ |
104 | 104 | global ETREE_VERSION |
105 | 105 | node = None |
106 | ||
106 | ||
107 | 107 | if ETREE_VERSION[0] <= 1 and ETREE_VERSION[1] < 3: |
108 | ||
108 | ||
109 | 109 | match_obj = re.search("([^\@]+?)\[\@([^=]*?)=\'([^\']*?)\'",subnode_xpath_expr) |
110 | 110 | if match_obj is not None: |
111 | 111 | node_to_find = match_obj.group(1) |
127 | 127 | return None |
128 | 128 | |
129 | 129 | |
130 | ||
130 | ||
131 | 131 | |
132 | 132 | |
133 | 133 | class Item(object): |
153 | 153 | self.service="" |
154 | 154 | self.protocol="" |
155 | 155 | port = self.get_text_from_subnode('port') |
156 | ||
156 | ||
157 | 157 | if (re.search("^general",port) is None): |
158 | 158 | mregex = re.search("([\w]+) \(([\d]+)\/([\w]+)\)",port) |
159 | 159 | if mregex is not None: |
163 | 163 | else: |
164 | 164 | info = port.split("/") |
165 | 165 | self.port = info[0] |
166 | self.protocol = info[1] | |
166 | self.protocol = info[1] | |
167 | 167 | else: |
168 | 168 | info = port.split("/") |
169 | 169 | self.service = info[0] |
170 | 170 | self.protocol = info[1] |
171 | ||
172 | ||
171 | ||
172 | ||
173 | 173 | self.nvt = self.node.findall('nvt')[0] |
174 | self.node = self.nvt | |
174 | self.node = self.nvt | |
175 | 175 | self.id=self.node.get('oid') |
176 | 176 | self.name = self.get_text_from_subnode('name') |
177 | 177 | self.cve = self.get_text_from_subnode('cve') if self.get_text_from_subnode('cve') != "NOCVE" else "" |
178 | 178 | self.bid = self.get_text_from_subnode('bid') if self.get_text_from_subnode('bid') != "NOBID" else "" |
179 | 179 | self.xref = self.get_text_from_subnode('xref') if self.get_text_from_subnode('xref') != "NOXREF" else "" |
180 | ||
180 | ||
181 | 181 | def do_clean(self,value): |
182 | 182 | myreturn ="" |
183 | 183 | if value is not None: |
184 | 184 | myreturn = re.sub("\n","",value) |
185 | 185 | return myreturn |
186 | ||
186 | ||
187 | 187 | def get_text_from_subnode(self, subnode_xpath_expr): |
188 | 188 | """ |
189 | 189 | Finds a subnode in the host node and the retrieves a value from it. |
217 | 217 | global current_path |
218 | 218 | self._output_file_path = os.path.join(self.data_path, |
219 | 219 | "openvas_output-%s.xml" % self._rid) |
220 | ||
220 | ||
221 | 221 | |
222 | 222 | def parseOutputString(self, output, debug = False): |
223 | 223 | """ |
226 | 226 | |
227 | 227 | NOTE: if 'debug' is true then it is being run from a test case and the |
228 | 228 | output being sent is valid. |
229 | """ | |
229 | """ | |
230 | 230 | |
231 | 231 | parser = OpenvasXmlParser(output) |
232 | 232 | |
241 | 241 | ref.append(item.bid.encode("utf-8")) |
242 | 242 | if item.xref: |
243 | 243 | ref.append(item.xref.encode("utf-8")) |
244 | ||
244 | ||
245 | 245 | if ids.has_key(item.subnet): |
246 | 246 | h_id=ids[item.host] |
247 | 247 | else: |
248 | 248 | h_id = self.createAndAddHost(item.subnet) |
249 | 249 | ids[item.subnet] = h_id |
250 | ||
250 | ||
251 | 251 | if item.port == "None": |
252 | 252 | v_id = self.createAndAddVulnToHost(h_id,item.name.encode("utf-8"),desc=item.description.encode("utf-8"), |
253 | severity=item.severity.encode("utf-8"), | |
253 | 254 | ref=ref) |
254 | 255 | else: |
255 | ||
256 | ||
256 | 257 | if item.service: |
257 | 258 | web=True if re.search(r'^(www|http)',item.service) else False |
258 | 259 | else: |
259 | 260 | web=True if item.port in ('80','443','8080') else False |
260 | ||
261 | ||
261 | 262 | if ids.has_key(item.subnet+"_"+item.subnet): |
262 | 263 | i_id=ids[item.subnet+"_"+item.subnet] |
263 | 264 | else: |
264 | 265 | |
265 | ||
266 | ||
266 | 267 | if self._isIPV4(item.subnet): |
267 | 268 | i_id = self.createAndAddInterface(h_id, item.subnet, ipv4_address=item.subnet,hostname_resolution=item.host) |
268 | 269 | else: |
269 | 270 | i_id = self.createAndAddInterface(h_id, item.subnet, ipv6_address=item.subnet,hostname_resolution=item.host) |
270 | ||
271 | ||
271 | 272 | ids[item.subnet+"_"+item.subnet] = i_id |
272 | ||
273 | ||
273 | ||
274 | ||
274 | 275 | if ids.has_key(item.subnet+"_"+item.port): |
275 | 276 | s_id=ids[item.subnet+"_"+item.port] |
276 | 277 | else: |
277 | 278 | s_id = self.createAndAddServiceToInterface(h_id, i_id, item.service, |
278 | item.protocol, | |
279 | item.protocol, | |
279 | 280 | ports = [str(item.port)], |
280 | 281 | status = "open") |
281 | 282 | ids[item.subnet+"_"+item.port] = s_id |
283 | 284 | n_id = self.createAndAddNoteToService(h_id,s_id,"website","") |
284 | 285 | n2_id = self.createAndAddNoteToNote(h_id,s_id,n_id,item.host,"") |
285 | 286 | |
286 | if item.name: | |
287 | if item.name: | |
287 | 288 | if web: |
288 | 289 | v_id = self.createAndAddVulnWebToService(h_id, s_id, item.name.encode("utf-8"), |
289 | 290 | desc=item.description.encode("utf-8"),website=item.host, |
293 | 294 | desc=item.description.encode("utf-8"),severity=item.severity.encode("utf-8"),ref=ref) |
294 | 295 | |
295 | 296 | del parser |
296 | ||
297 | ||
298 | ||
299 | ||
297 | ||
298 | ||
299 | ||
300 | ||
300 | 301 | |
301 | 302 | def _isIPV4(self, ip): |
302 | 303 | if len(ip.split(".")) == 4: |
304 | 305 | else: |
305 | 306 | return False |
306 | 307 | |
307 | ||
308 | ||
308 | 309 | def processCommandString(self, username, current_path, command_string): |
309 | 310 | return None |
310 | ||
311 | ||
311 | 312 | |
312 | 313 | def setHost(self): |
313 | 314 | pass |
8 | 8 | ''' |
9 | 9 | from __future__ import with_statement |
10 | 10 | from plugins import core |
11 | from model import api | |
12 | 11 | import re |
13 | 12 | import os |
14 | import pprint | |
15 | 13 | import sys |
16 | 14 | import json |
17 | 15 | import socket |
18 | 16 | import random |
19 | 17 | |
20 | ||
21 | ||
22 | 18 | current_path = os.path.abspath(os.getcwd()) |
23 | 19 | |
24 | __author__ = "Nicolas Rodriguez" | |
25 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
26 | __credits__ = ["Nicolas Rodriguez"] | |
27 | __license__ = "" | |
28 | __version__ = "1.0.0" | |
20 | __author__ = "Nicolas Rodriguez" | |
21 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
22 | __credits__ = ["Nicolas Rodriguez"] | |
23 | __license__ = "" | |
24 | __version__ = "1.0.0" | |
29 | 25 | __maintainer__ = "Francisco Amato" |
30 | __email__ = "[email protected]" | |
31 | __status__ = "Development" | |
26 | __email__ = "[email protected]" | |
27 | __status__ = "Development" | |
32 | 28 | |
33 | 29 | |
34 | 30 | class SkipfishParser(object): |
35 | 31 | """ |
36 | The objective of this class is to parse an xml file generated by the skipfish tool. | |
32 | The objective of this class is to parse an xml file generated by | |
33 | the skipfish tool. | |
37 | 34 | |
38 | 35 | TODO: Handle errors. |
39 | TODO: Test skipfish output version. Handle what happens if the parser doesn't support it. | |
36 | TODO: Test skipfish output version. Handle what happens if the parser | |
37 | doesn't support it. | |
40 | 38 | TODO: Test cases. |
41 | 39 | |
42 | 40 | @param skipfish_filepath A proper xml generated by skipfish |
43 | 41 | """ |
44 | 42 | def __init__(self, skipfish_filepath): |
45 | 43 | self.filepath = skipfish_filepath |
46 | ||
44 | ||
47 | 45 | tmp = open(skipfish_filepath+"/samples.js", "r").read() |
48 | issues = json.loads(self.extract_data(tmp, "var issue_samples =", "];", | |
49 | lambda x: x.replace("'", '"'), False, False)[1] + "]") | |
50 | ||
46 | issues = json.loads( | |
47 | self.extract_data( | |
48 | tmp, | |
49 | "var issue_samples =", "];", | |
50 | lambda x: x.replace("'", '"'), | |
51 | False, | |
52 | False) | |
53 | [1] + "]") | |
54 | ||
51 | 55 | tmp = open(skipfish_filepath+"/index.html", "r").read() |
52 | err_msg = json.loads(self.extract_data(tmp, "var issue_desc=", "};", | |
53 | lambda x: self.convert_quotes(x, "'", '"'), False, False)[1] + "}") | |
54 | ||
55 | self.err_msg=err_msg | |
56 | self.issues=issues | |
57 | ||
58 | def convert_quotes(self,text, quote="'", inside='"'): | |
56 | err_msg = json.loads( | |
57 | self.extract_data( | |
58 | tmp, | |
59 | "var issue_desc=", | |
60 | "};", | |
61 | lambda x: self.convert_quotes(x, "'", '"'), | |
62 | False, | |
63 | False) | |
64 | [1] + "}") | |
65 | ||
66 | self.err_msg = err_msg | |
67 | self.issues = issues | |
68 | ||
69 | def convert_quotes(self, text, quote="'", inside='"'): | |
59 | 70 | start = 0 |
60 | 71 | while True: |
61 | 72 | pos = text.find(quote, start) |
62 | ||
73 | ||
63 | 74 | if pos == -1: |
64 | 75 | break |
65 | ||
76 | ||
66 | 77 | ss = text[:pos - 1] |
67 | 78 | quotes = len(ss) - len(ss.replace(inside, "")) |
68 | ||
79 | ||
69 | 80 | if quotes % 2 == 0: |
70 | 81 | text = text[:pos - 1] + "\\" + quote + text[pos + 1:] |
71 | ||
82 | ||
72 | 83 | start = pos + 1 |
73 | 84 | return text |
74 | ||
85 | ||
75 | 86 | def extract_data(self, samples, start_tag, end_tag, fn=lambda x: x, include_start_tag=True, include_end_tag=True): |
76 | 87 | start = samples.find(start_tag) |
77 | ||
88 | ||
78 | 89 | if start == -1: |
79 | 90 | return (-1, None) |
80 | ||
91 | ||
81 | 92 | end = samples.find(end_tag, start + 1) |
82 | ||
93 | ||
83 | 94 | if end == -1: |
84 | 95 | return (-2, None) |
85 | ||
96 | ||
86 | 97 | data = samples[start:end + len(end_tag)] |
87 | 98 | data = fn(data) |
88 | ||
99 | ||
89 | 100 | if not include_start_tag: |
90 | 101 | data = data[len(start_tag) + 1:] |
91 | ||
102 | ||
92 | 103 | if not include_end_tag: |
93 | 104 | data = data[:-1 * len(end_tag)] |
94 | ||
105 | ||
95 | 106 | return (0, data) |
96 | 107 | |
97 | 108 | |
101 | 112 | """ |
102 | 113 | def __init__(self): |
103 | 114 | core.PluginBase.__init__(self) |
104 | self.id = "Skipfish" | |
105 | self.name = "Skipfish XML Output Plugin" | |
106 | self.plugin_version = "0.0.2" | |
107 | self.version = "2.1.5" | |
108 | self.options = None | |
115 | self.id = "Skipfish" | |
116 | self.name = "Skipfish XML Output Plugin" | |
117 | self.plugin_version = "0.0.2" | |
118 | self.version = "2.1.5" | |
119 | self.options = None | |
109 | 120 | self._current_output = None |
110 | 121 | self.parent = None |
111 | self._command_regex = re.compile(r'^(sudo skipfish|skipfish|sudo skipfish\.pl|skipfish\.pl|perl skipfish\.pl|\.\/skipfish\.pl|\.\/skipfish).*?') | |
112 | self._completition = { | |
113 | "":"Usage: skipfish [ options ... ] -W wordlist -o output_dir start_url [ start_url2 ... ]", | |
114 | "-A":"user:pass - use specified HTTP authentication credentials", | |
115 | "-F":"host=IP - pretend that 'host' resolves to 'IP'", | |
116 | "-C":"name=val - append a custom cookie to all requests", | |
117 | "-H":"name=val - append a custom HTTP header to all requests", | |
118 | "-b":"(i|f|p) - use headers consistent with MSIE / Firefox / iPhone", | |
119 | "-N":"- do not accept any new cookies", | |
120 | "--auth-form":"url - form authentication URL", | |
121 | "--auth-user":"user - form authentication user", | |
122 | "--auth-pass":"pass - form authentication password", | |
123 | "--auth-verify-url":" --auth-verify-url - URL for in-session detection", | |
124 | "-d":"max_depth - maximum crawl tree depth (16)", | |
125 | "-c":"max_child - maximum children to index per node (512)", | |
126 | "-x":"max_desc - maximum descendants to index per branch (8192)", | |
127 | "-r":"r_limit - max total number of requests to send (100000000)", | |
128 | "-p":"crawl% - node and link crawl probability (100%)", | |
129 | "-q":"hex - repeat probabilistic scan with given seed", | |
130 | "-I":"string - only follow URLs matching 'string'", | |
131 | "-X":"string - exclude URLs matching 'string'", | |
132 | "-K":"string - do not fuzz parameters named 'string'", | |
133 | "-D":"domain - crawl cross-site links to another domain", | |
134 | "-B":"domain - trust, but do not crawl, another domain", | |
135 | "-Z":"- do not descend into 5xx locations", | |
136 | "-O":"- do not submit any forms", | |
137 | "-P":"- do not parse HTML, etc, to find new links", | |
138 | "-o":"dir - write output to specified directory (required)", | |
139 | "-M":"- log warnings about mixed content / non-SSL passwords", | |
140 | "-E":"- log all HTTP/1.0 / HTTP/1.1 caching intent mismatches", | |
141 | "-U":"- log all external URLs and e-mails seen", | |
142 | "-Q":"- completely suppress duplicate nodes in reports", | |
143 | "-u":"- be quiet, disable realtime progress stats", | |
144 | "-v":"- enable runtime logging (to stderr)", | |
145 | "-W":"wordlist - use a specified read-write wordlist (required)", | |
146 | "-S":"wordlist - load a supplemental read-only wordlist", | |
147 | "-L":"- do not auto-learn new keywords for the site", | |
148 | "-Y":"- do not fuzz extensions in directory brute-force", | |
149 | "-R":"age - purge words hit more than 'age' scans ago", | |
150 | "-T":"name=val - add new form auto-fill rule", | |
151 | "-G":"max_guess - maximum number of keyword guesses to keep (256)", | |
152 | "-z":"sigfile - load signatures from this file", | |
153 | "-g":"max_conn - max simultaneous TCP connections, global (40)", | |
154 | "-m":"host_conn - max simultaneous connections, per target IP (10)", | |
155 | "-f":"max_fail - max number of consecutive HTTP errors (100)", | |
156 | "-t":"req_tmout - total request response timeout (20 s)", | |
157 | "-w":"rw_tmout - individual network I/O timeout (10 s)", | |
158 | "-i":"idle_tmout - timeout on idle HTTP connections (10 s)", | |
159 | "-s":"s_limit - response size limit (400000 B)", | |
160 | "-e":"- do not keep binary responses for reporting", | |
161 | "-l":"max_req - max requests per second (0.000000)", | |
162 | "-k":"duration - stop scanning after the given duration h:m:s", | |
163 | "--config":"- load the specified configuration file", | |
164 | ||
165 | } | |
166 | ||
122 | self._command_regex = re.compile( | |
123 | r'^(sudo skipfish|skipfish|sudo skipfish\.pl|skipfish\.pl|perl skipfish\.pl|\.\/skipfish\.pl|\.\/skipfish).*?') | |
167 | 124 | global current_path |
168 | 125 | |
169 | ||
170 | def parseOutputString(self, output, debug = False ): | |
171 | """ | |
172 | This method will discard the output the shell sends, it will read it from | |
173 | the xml where it expects it to be present. | |
126 | def parseOutputString(self, output, debug=False): | |
127 | """ | |
128 | This method will discard the output the shell sends, it will read it | |
129 | from the xml where it expects it to be present. | |
174 | 130 | |
175 | 131 | NOTE: if 'debug' is true then it is being run from a test case and the |
176 | 132 | output being sent is valid. |
177 | 133 | """ |
178 | ||
179 | # if (re.search("\r\n",output) is None): | |
180 | # self._output_path=output | |
181 | ||
134 | ||
182 | 135 | if not os.path.exists(self._output_path): |
183 | 136 | return False |
184 | ||
137 | ||
185 | 138 | p = SkipfishParser(self._output_path) |
186 | 139 | |
187 | hostc={} | |
188 | port=80 | |
140 | hostc = {} | |
141 | port = 80 | |
189 | 142 | for issue in p.issues: |
190 | request="" | |
191 | response="" | |
143 | req = "" | |
144 | res = "" | |
192 | 145 | for sample in issue["samples"]: |
193 | 146 | if not sample["url"] in hostc: |
194 | reg = re.search("(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", sample["url"]) | |
195 | ||
147 | reg = re.search( | |
148 | "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", sample["url"]) | |
149 | ||
196 | 150 | protocol = reg.group(1) |
197 | 151 | host = reg.group(4) |
198 | 152 | if reg.group(11) is not None: |
199 | 153 | port = reg.group(11) |
200 | 154 | else: |
201 | 155 | port = 443 if protocol == "https" else 80 |
202 | ||
156 | ||
203 | 157 | ip = self.resolve(host) |
204 | ||
158 | ||
205 | 159 | h_id = self.createAndAddHost(ip) |
206 | i_id = self.createAndAddInterface(h_id, ip, ipv4_address=ip,hostname_resolution=host) | |
207 | s_id = self.createAndAddServiceToInterface(h_id, i_id, "http", | |
208 | "tcp", | |
209 | ports = [port], | |
210 | status = "open") | |
211 | ||
212 | n_id = self.createAndAddNoteToService(h_id,s_id,"website","") | |
213 | n2_id = self.createAndAddNoteToNote(h_id,s_id,n_id,host,"") | |
214 | ||
215 | ||
216 | hostc[sample["url"]]={'h_id':h_id, 'ip':ip,'port':port,'host':host,'protocol':protocol, | |
217 | 'i_id':i_id,'s_id':s_id} | |
218 | ||
219 | ||
160 | i_id = self.createAndAddInterface( | |
161 | h_id, | |
162 | ip, | |
163 | ipv4_address=ip, | |
164 | hostname_resolution=host) | |
165 | ||
166 | s_id = self.createAndAddServiceToInterface( | |
167 | h_id, | |
168 | i_id, | |
169 | "http", | |
170 | "tcp", | |
171 | ports=[port], | |
172 | status="open") | |
173 | ||
174 | n_id = self.createAndAddNoteToService( | |
175 | h_id, | |
176 | s_id, | |
177 | "website", | |
178 | "") | |
179 | ||
180 | self.createAndAddNoteToNote(h_id, s_id, n_id, host, "") | |
181 | ||
182 | hostc[sample["url"]] = { | |
183 | 'h_id': h_id, | |
184 | 'ip': ip, | |
185 | 'port': port, | |
186 | 'host': host, | |
187 | 'protocol': protocol, | |
188 | 'i_id': i_id, | |
189 | 's_id': s_id} | |
190 | ||
220 | 191 | try: |
221 | request =open("%s/request.dat" % sample["dir"], "r").read() | |
192 | req = open("%s/request.dat" % sample["dir"], "r").read() | |
222 | 193 | except: |
223 | 194 | pass |
224 | ||
195 | ||
225 | 196 | try: |
226 | response =open("%s/request.dat" % sample["dir"], "r").read() | |
197 | res = open("%s/request.dat" % sample["dir"], "r").read() | |
227 | 198 | except: |
228 | 199 | pass |
229 | ||
230 | d=hostc[sample["url"]] | |
231 | v_id = self.createAndAddVulnWebToService(d['h_id'], d['s_id'], | |
232 | name=p.err_msg[str(issue["type"])],desc="Extra: " + sample["extra"], website=d['host'], | |
233 | path=sample["url"],severity=issue["severity"]) | |
234 | ||
200 | ||
201 | d = hostc[sample["url"]] | |
202 | self.createAndAddVulnWebToService( | |
203 | d['h_id'], | |
204 | d['s_id'], | |
205 | name=p.err_msg[str(issue["type"])], | |
206 | desc="Extra: " + sample["extra"], | |
207 | website=d['host'], | |
208 | path=sample["url"], | |
209 | severity=issue["severity"]) | |
235 | 210 | |
236 | 211 | def resolve(self, host): |
237 | 212 | try: |
249 | 224 | """ |
250 | 225 | arg_match = self.xml_arg_re.match(command_string) |
251 | 226 | |
252 | self._output_path = os.path.join(self.data_path, | |
253 | "skipfish_output-%s" % random.uniform(1,10)) | |
227 | self._output_path = os.path.join( | |
228 | self.data_path, | |
229 | "skipfish_output-%s" % random.uniform(1, 10)) | |
254 | 230 | |
255 | 231 | if arg_match is None: |
256 | return re.sub(r"(^.*?skipfish)", | |
257 | r"\1 -o %s" % self._output_path, | |
258 | command_string,1) | |
232 | return re.sub( | |
233 | r"(^.*?skipfish)", | |
234 | r"\1 -o %s" % self._output_path, | |
235 | command_string, | |
236 | 1) | |
259 | 237 | else: |
260 | return re.sub(arg_match.group(1), | |
261 | r"-o %s" % self._output_path, | |
262 | command_string,1) | |
238 | return re.sub( | |
239 | arg_match.group(1), | |
240 | r"-o %s" % self._output_path, | |
241 | command_string, | |
242 | 1) | |
263 | 243 | |
264 | 244 | def setHost(self): |
265 | 245 | pass |
10 | 10 | import sys |
11 | 11 | import os |
12 | 12 | |
13 | from plugins import core | |
13 | from plugins.plugin import PluginTerminalOutput | |
14 | 14 | from model import api |
15 | 15 | import re |
16 | 16 | import os |
25 | 25 | from BaseHTTPServer import BaseHTTPRequestHandler |
26 | 26 | from StringIO import StringIO |
27 | 27 | |
28 | ||
28 | ||
29 | 29 | try: |
30 | 30 | import xml.etree.cElementTree as ET |
31 | 31 | import xml.etree.ElementTree as ET_ORIG |
33 | 33 | except ImportError: |
34 | 34 | import xml.etree.ElementTree as ET |
35 | 35 | ETREE_VERSION = ET.VERSION |
36 | ||
36 | ||
37 | 37 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
38 | 38 | |
39 | 39 | current_path = os.path.abspath(os.getcwd()) |
75 | 75 | if statement.lstrip().upper().startswith("SELECT"): |
76 | 76 | return self.cursor.fetchall() |
77 | 77 | |
78 | class SqlmapPlugin(core.PluginBase): | |
78 | class SqlmapPlugin(PluginTerminalOutput): | |
79 | 79 | """ |
80 | 80 | Example plugin to parse sqlmap output. |
81 | 81 | """ |
82 | 82 | def __init__(self): |
83 | core.PluginBase.__init__(self) | |
83 | PluginTerminalOutput.__init__(self) | |
84 | 84 | self.id = "Sqlmap" |
85 | 85 | self.name = "Sqlmap" |
86 | 86 | self.plugin_version = "0.0.2" |
96 | 96 | self.path="" |
97 | 97 | |
98 | 98 | self.addSetting("Sqlmap path", str, "/root/tools/sqlmap") |
99 | ||
99 | ||
100 | 100 | self.db_port = { "MySQL" : 3306, "PostgreSQL":"", "Microsoft SQL Server" : 1433, |
101 | 101 | "Oracle" : 1521, "Firebird" : 3050,"SAP MaxDB":7210, "Sybase" : 5000, |
102 | 102 | "IBM DB2" : 50000, "HSQLDB" :9001} |
108 | 108 | 5: "LIKE double quoted string", |
109 | 109 | } |
110 | 110 | |
111 | self._command_regex = re.compile(r'^(sudo sqlmap|sqlmap|sudo python sqlmap|python sqlmap|\.\/sqlmap).*?') | |
111 | self._command_regex = re.compile(r'^(python2.7 ./sqlmap.py|sudo sqlmap|sqlmap|sudo python sqlmap|python sqlmap|\.\/sqlmap).*?') | |
112 | 112 | |
113 | 113 | global current_path |
114 | 114 | self._output_path = "" |
277 | 277 | "--purge-output":"Safely remove all content from output directory", |
278 | 278 | "--smart":"Conduct through tests only if positive heuristic(s)", |
279 | 279 | "--wizard":"Simple wizard interface for beginner users", |
280 | } | |
280 | } | |
281 | 281 | class HTTPRequest(BaseHTTPRequestHandler): |
282 | 282 | def __init__(self, request_text): |
283 | 283 | self.rfile = StringIO(request_text) |
284 | 284 | self.raw_requestline = self.rfile.readline() |
285 | 285 | self.error_code = self.error_message = None |
286 | 286 | self.parse_request() |
287 | ||
287 | ||
288 | 288 | def send_error(self, code, message): |
289 | 289 | self.error_code = code |
290 | 290 | self.error_message = message |
298 | 298 | """ |
299 | 299 | Helper function for restoring session data from HashDB |
300 | 300 | """ |
301 | ||
301 | ||
302 | 302 | key = "%s%s%s" % (self.url or "%s%s" % (self.hostname, self.port), key, self.HASHDB_MILESTONE_VALUE) |
303 | 303 | retVal="" |
304 | ||
304 | ||
305 | 305 | hash_ = self.hashKey(key) |
306 | 306 | # print "hash_" + str(hash_) + "key=" + key |
307 | 307 | if not retVal: |
319 | 319 | def base64decode(self,value): |
320 | 320 | """ |
321 | 321 | Decodes string value from Base64 to plain format |
322 | ||
322 | ||
323 | 323 | >>> base64decode('Zm9vYmFy') |
324 | 324 | 'foobar' |
325 | 325 | """ |
326 | ||
326 | ||
327 | 327 | return value.decode("base64") |
328 | ||
328 | ||
329 | 329 | def base64encode(self,value): |
330 | 330 | """ |
331 | 331 | Encodes string value from plain to Base64 format |
332 | ||
332 | ||
333 | 333 | >>> base64encode('foobar') |
334 | 334 | 'Zm9vYmFy' |
335 | 335 | """ |
336 | ||
336 | ||
337 | 337 | return value.encode("base64")[:-1].replace("\n", "") |
338 | ||
338 | ||
339 | 339 | def base64unpickle(self,value): |
340 | 340 | """ |
341 | 341 | Decodes value from Base64 to plain format and deserializes (with pickle) its content |
342 | ||
342 | ||
343 | 343 | >>> base64unpickle('gAJVBmZvb2JhcnEALg==') |
344 | 344 | 'foobar' |
345 | 345 | """ |
346 | 346 | if value: |
347 | 347 | return pickle.loads(self.base64decode(value)) |
348 | ||
349 | ||
348 | ||
349 | ||
350 | 350 | def xmlvalue(self,db,name,value="query"): |
351 | ||
351 | ||
352 | 352 | filepath = "%s" % os.path.join(current_path, "plugins/repo/sqlmap/queries.xml") |
353 | 353 | with open(filepath,"r") as f: |
354 | 354 | try: |
356 | 356 | except SyntaxError, err: |
357 | 357 | print "SyntaxError: %s. %s" % (err, filepath) |
358 | 358 | return None |
359 | ||
359 | ||
360 | 360 | for node in tree.findall("dbms[@value='"+db+"']/"+name+""): |
361 | 361 | return node.attrib[value] |
362 | 362 | |
364 | 364 | users = re.findall('database management system users \[[\d]+\]:\r\n(.*?)\r\n\r\n',data, re.S) |
365 | 365 | if users: |
366 | 366 | return map((lambda x: x.replace("[*] ","")), users[0].split("\r\n")) |
367 | ||
367 | ||
368 | 368 | def getdbs(self,data): |
369 | 369 | dbs = re.findall('available databases \[[\d]+\]:\r\n(.*?)\r\n\r\n',data, re.S) |
370 | 370 | if dbs: |
374 | 374 | password = re.findall('database management system users password hashes:\r\n(.*?)\r\n\r\n',data, re.S) |
375 | 375 | if password: |
376 | 376 | for p in password[0].split("[*] ")[1::]: |
377 | ||
377 | ||
378 | 378 | user=re.findall("^(.*?) \[",p)[0] |
379 | 379 | mpass=re.findall("password hash: (.*?)$",p, re.S) |
380 | 380 | mpass=map((lambda x: re.sub(r"[ \r\n]", "", x)), mpass[0].split("password hash: ")) |
381 | 381 | users[user]=mpass |
382 | 382 | return users |
383 | ||
383 | ||
384 | 384 | def getAddress(self, hostname): |
385 | 385 | """ |
386 | 386 | Returns remote IP address from hostname. |
388 | 388 | try: |
389 | 389 | return socket.gethostbyname(hostname) |
390 | 390 | except socket.error, msg: |
391 | ||
391 | ||
392 | 392 | return self.hostname |
393 | ||
393 | ||
394 | 394 | def parseOutputString(self, output, debug = False): |
395 | 395 | """ |
396 | 396 | This method will discard the output the shell sends, it will read it from |
399 | 399 | NOTE: if 'debug' is true then it is being run from a test case and the |
400 | 400 | output being sent is valid. |
401 | 401 | """ |
402 | ||
402 | ||
403 | 403 | sys.path.append(self.getSetting("Sqlmap path")) |
404 | 404 | |
405 | 405 | from lib.core.settings import HASHDB_MILESTONE_VALUE |
408 | 408 | self.HASHDB_MILESTONE_VALUE = HASHDB_MILESTONE_VALUE |
409 | 409 | self.HASHDB_KEYS = HASHDB_KEYS |
410 | 410 | self.UNICODE_ENCODING = UNICODE_ENCODING |
411 | ||
411 | ||
412 | 412 | password = self.getpassword(output) |
413 | 413 | webserver = re.search("web application technology: (.*?)\n",output) |
414 | 414 | if webserver: |
416 | 416 | users = self.getuser(output) |
417 | 417 | # print users |
418 | 418 | dbs = self.getdbs(output) |
419 | ||
419 | ||
420 | 420 | # print "webserver = " + webserver |
421 | 421 | # print "dbs = " + str(dbs) |
422 | 422 | # print "users = " + str(users) |
423 | 423 | # print "password = " + str(password) |
424 | ||
424 | ||
425 | 425 | |
426 | 426 | db = Database(self._output_path) |
427 | db.connect() | |
428 | ||
427 | db.connect() | |
428 | ||
429 | 429 | absFilePaths = self.hashDBRetrieve(self.HASHDB_KEYS.KB_ABS_FILE_PATHS, True, db) |
430 | 430 | tables = self.hashDBRetrieve(self.HASHDB_KEYS.KB_BRUTE_TABLES, True, db) |
431 | 431 | columns = self.hashDBRetrieve(self.HASHDB_KEYS.KB_BRUTE_COLUMNS, True, db) |
432 | 432 | xpCmdshellAvailable = self.hashDBRetrieve(self.HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE, True, db) |
433 | dbms_version = self.hashDBRetrieve(self.HASHDB_KEYS.DBMS, False, db) | |
434 | ||
435 | os = self.hashDBRetrieve(self.HASHDB_KEYS.OS, False, db) | |
436 | ||
433 | dbms_version = self.hashDBRetrieve(self.HASHDB_KEYS.DBMS, False, db) | |
434 | ||
435 | os = self.hashDBRetrieve(self.HASHDB_KEYS.OS, False, db) | |
436 | ||
437 | 437 | self.ip=self.getAddress(self.hostname) |
438 | 438 | |
439 | 439 | dbms=str(dbms_version.split(" ")[0]) |
447 | 447 | version=webserver) |
448 | 448 | n_id = self.createAndAddNoteToService(h_id,s_id,"website","") |
449 | 449 | n2_id = self.createAndAddNoteToNote(h_id,s_id,n_id,self.hostname,"") |
450 | ||
450 | ||
451 | 451 | db_port=self.db_port[dbms] |
452 | ||
452 | ||
453 | 453 | s_id2 = self.createAndAddServiceToInterface(h_id, i_id, |
454 | 454 | name=dbms , |
455 | 455 | protocol="tcp", |
460 | 460 | if users: |
461 | 461 | for v in users: |
462 | 462 | self.createAndAddCredToService(h_id,s_id2,v,"") |
463 | ||
463 | ||
464 | 464 | if password: |
465 | 465 | for k,v in password.iteritems(): |
466 | 466 | for p in v: |
467 | 467 | self.createAndAddCredToService(h_id,s_id2,k,p) |
468 | ||
468 | ||
469 | 469 | if absFilePaths: |
470 | 470 | n_id2 = self.createAndAddNoteToService(h_id,s_id2,"sqlmap.absFilePaths",str(absFilePaths)) |
471 | 471 | if tables: |
474 | 474 | n_id2 = self.createAndAddNoteToService(h_id,s_id2,"sqlmap.brutecolumns",str(columns)) |
475 | 475 | if xpCmdshellAvailable: |
476 | 476 | n_id2 = self.createAndAddNoteToService(h_id,s_id2,"sqlmap.xpCmdshellAvailable",str(xpCmdshellAvailable)) |
477 | ||
477 | ||
478 | 478 | for inj in self.hashDBRetrieve(self.HASHDB_KEYS.KB_INJECTIONS, True,db) or []: |
479 | 479 | # print inj |
480 | # print inj.dbms | |
481 | # print inj.dbms_version | |
482 | # print inj.place | |
483 | # print inj.os | |
484 | # print inj.parameter | |
485 | ||
480 | # print inj.dbms | |
481 | # print inj.dbms_version | |
482 | # print inj.place | |
483 | # print inj.os | |
484 | # print inj.parameter | |
485 | ||
486 | 486 | dbversion = self.hashDBRetrieve("None"+self.xmlvalue(dbms,"banner"), False, db) |
487 | 487 | user = self.hashDBRetrieve("None"+self.xmlvalue(dbms,"current_user"), False, db) |
488 | 488 | dbname = self.hashDBRetrieve("None"+self.xmlvalue(dbms,"current_db"), False, db) |
489 | 489 | hostname = self.hashDBRetrieve("None"+self.xmlvalue(dbms,"hostname"), False, db) |
490 | 490 | |
491 | 491 | # print "username = " + user |
492 | ||
492 | ||
493 | 493 | if user: |
494 | 494 | n_id2 = self.createAndAddNoteToService(h_id,s_id2,"db.user",user) |
495 | 495 | if dbname: |
500 | 500 | n_id2 = self.createAndAddNoteToService(h_id,s_id2,"db.version",dbversion) |
501 | 501 | if dbs: |
502 | 502 | n_id2 = self.createAndAddNoteToService(h_id,s_id2,"db.databases",str(dbs)) |
503 | ||
503 | ||
504 | 504 | for k,v in inj.data.items(): |
505 | 505 | v_id = self.createAndAddVulnWebToService(h_id, s_id, |
506 | 506 | website=self.hostname, |
508 | 508 | desc="Payload:" + str(inj.data[k]['payload'])+ |
509 | 509 | "\nVector:"+ str(inj.data[k]['vector'])+ |
510 | 510 | "\nParam type:" + str(self.ptype[inj.ptype]), |
511 | ref=[], | |
511 | ref=[], | |
512 | 512 | pname=inj.parameter, |
513 | 513 | severity="high", |
514 | 514 | method=inj.place, |
515 | 515 | params=self.params, |
516 | 516 | path=self.fullpath) |
517 | ||
518 | ||
519 | ||
520 | ||
521 | ||
517 | ||
518 | ||
519 | ||
520 | ||
521 | ||
522 | 522 | |
523 | 523 | def processCommandString(self, username, current_path, command_string): |
524 | ||
525 | ||
526 | ||
524 | ||
525 | ||
526 | ||
527 | 527 | parser = argparse.ArgumentParser(conflict_handler='resolve') |
528 | ||
528 | ||
529 | 529 | parser.add_argument('-h') |
530 | 530 | parser.add_argument('-u') |
531 | 531 | parser.add_argument('-s') |
532 | 532 | parser.add_argument('-r') |
533 | ||
534 | ||
533 | ||
534 | ||
535 | 535 | try: |
536 | 536 | args, unknown = parser.parse_known_args(shlex.split(re.sub(r'\-h|\-\-help', r'', command_string))) |
537 | 537 | except SystemExit: |
538 | pass | |
539 | ||
540 | if args.r: | |
538 | pass | |
539 | ||
540 | if args.r: | |
541 | 541 | with open(args.r, 'r') as f: |
542 | 542 | request = self.HTTPRequest(f.read()) |
543 | 543 | args.u="http://"+request.headers['host']+ request.path |
544 | 544 | f.close() |
545 | ||
546 | if args.u: | |
545 | ||
546 | if args.u: | |
547 | 547 | reg = re.search("(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", args.u) |
548 | 548 | self.protocol = reg.group(1) |
549 | 549 | self.hostname = reg.group(4) |
552 | 552 | self.port=443 |
553 | 553 | if reg.group(11) is not None: |
554 | 554 | self.port = reg.group(11) |
555 | ||
555 | ||
556 | 556 | if reg.group(12) is not None: |
557 | 557 | tmp=re.search("/(.*)\?(.*?$)",reg.group(12)) |
558 | 558 | self.path = "/"+tmp.group(1) |
559 | 559 | self.params=tmp.group(2) |
560 | ||
560 | ||
561 | 561 | self.url=self.protocol+"://"+self.hostname+":"+self.port + self.path |
562 | 562 | self.fullpath=self.url+"?"+self.params |
563 | 563 | self._output_path="%s%s" % (os.path.join(self.data_path, "sqlmap_output-"), |
564 | 564 | re.sub(r'[\n\/]', r'', args.u.encode("base64")[:-1])) |
565 | ||
565 | ||
566 | 566 | if not args.s: |
567 | return "%s -s %s\n" % (command_string,self._output_path) | |
568 | ||
567 | return "%s -s %s" % (command_string,self._output_path) | |
568 | ||
569 | 569 | |
570 | 570 | def setHost(self): |
571 | 571 | pass |
573 | 573 | |
574 | 574 | def createPlugin(): |
575 | 575 | return SqlmapPlugin() |
576 |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
0 | 3 | ''' |
1 | 4 | Faraday Penetration Test IDE |
2 | 5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
3 | 6 | See the file 'doc/LICENSE' for the license information |
4 | 7 | |
5 | 8 | ''' |
6 | ||
7 | #!/usr/bin/env python | |
8 | # -*- coding: utf-8 -*- | |
9 | 9 | |
10 | 10 | from lxml import etree as ET |
11 | 11 | |
61 | 61 | self.root = tree.getroot() |
62 | 62 | self.recommended = "\nRecommended configuration changes in web.config:\n" |
63 | 63 | self.xml = xml |
64 | ||
64 | ||
65 | 65 | def xml_export(self,directive,rec): |
66 | if self.xml is not None: | |
66 | if self.xml is not None: | |
67 | 67 | newElement = ET.SubElement(self.xml, directive[0]) |
68 | 68 | if len(directive) == 4: |
69 | 69 | newElement.attrib['name'] = directive[3] |
70 | 70 | newElement.attrib['option'] = directive[1] |
71 | 71 | newElement.attrib['rec'] = rec |
72 | 72 | newElement.text = directive[2] |
73 | ||
74 | ||
73 | ||
74 | ||
75 | 75 | def global_check(self): |
76 | 76 | print "[+]\033[0;41mVulnerabilites/Informations\033[0m:" |
77 | 77 | countforms = 0 |
81 | 81 | if element == "forms": |
82 | 82 | countforms += 1 |
83 | 83 | nameforms.append(tag.attrib['name']) |
84 | if element == "user": | |
84 | if element == "user": | |
85 | 85 | print " \033[1;30m{}\033[0m {}: {} \033[1;30m({})\033[0m".format(element, |
86 | 86 | tag.attrib['name'], |
87 | 87 | rules[element][''][0], |
88 | option) | |
88 | option) | |
89 | 89 | self.recommended += " Not to store passwords or hashes in web.config\n" |
90 | 90 | self.xml_export(directive=[element, tag.attrib['name'],'hardcoded'], |
91 | 91 | rec=rules[element][''][0]) |
92 | 92 | continue |
93 | ||
94 | for option in tag.attrib: | |
93 | ||
94 | for option in tag.attrib: | |
95 | 95 | if element == "customErrors" and rules[element].has_key(option) and not tag.attrib[option].lower() in rules[element][option][1]: |
96 | 96 | print " \033[1;30m{}\033[0m: {} \033[1;30m({})\033[0m".format(element, |
97 | 97 | rules[element][option][0], |
98 | option) | |
98 | option) | |
99 | 99 | self.recommended += " <{} {}=\"{}\"/>\n".format(element,option,rules[element][option][1]) |
100 | 100 | self.xml_export(directive=[element,option,'disabled'], |
101 | 101 | rec=rules[element][option][0]) |
102 | 102 | continue |
103 | ||
103 | ||
104 | 104 | elif element == "roleManager" and option == "cookiePath": |
105 | 105 | print " \033[1;30mroleManager\033[0m: Liberal path defined ('{}') (\033[1;30mcookiePath\033[0m)".format(tag.attrib[option].lower()) |
106 | 106 | self.recommended += " <roleManager cookiePath=\"{abcd1234…}\">\n" |
107 | 107 | self.xml_export(directive=[element,option,'liberal'], |
108 | 108 | rec=rules[element][option][0]) |
109 | 109 | continue |
110 | ||
110 | ||
111 | 111 | if rules[element].has_key(option) and tag.attrib[option].lower() != rules[element][option][1]: |
112 | 112 | if element == "forms": |
113 | 113 | print " \033[1;30m{}\033[0m {}: {} \033[1;30m({})\033[0m".format(element, |
114 | 114 | tag.attrib['name'], |
115 | 115 | rules[element][option][0], |
116 | option) | |
116 | option) | |
117 | 117 | self.xml_export(directive=[element,option,tag.attrib[option],tag.attrib['name']], |
118 | rec=rules[element][option][0]) | |
119 | ||
118 | rec=rules[element][option][0]) | |
119 | ||
120 | 120 | else: |
121 | 121 | print " \033[1;30m{}\033[0m: {} \033[1;30m({})\033[0m".format(element, |
122 | 122 | rules[element][option][0], |
123 | 123 | option) |
124 | ||
124 | ||
125 | 125 | self.xml_export(directive=[element,option,tag.attrib[option]], |
126 | rec=rules[element][option][0]) | |
126 | rec=rules[element][option][0]) | |
127 | 127 | self.recommended += " <{} {}=\"{}\"/>\n".format(element,option,rules[element][option][1]) |
128 | 128 | continue |
129 | ||
129 | ||
130 | 130 | if countforms > 1 and (nameforms.index('.ASPXAUTH') != "-1" or nameforms.index('ASPXAUTH') != "-1"): |
131 | 131 | print " \033[1;30mforms\033[0m: Non-Unique authentication cookie used\033[1;30m (name)\033[0m" |
132 | 132 | self.recommended += " <forms name=\"{abcd1234…}\">\n" |
133 | 133 | self.xml_export(directive=['nameforms','name','false'], |
134 | 134 | rec="Non-Unique authentication cookie used") |
135 | ||
135 | ||
136 | 136 | def scanner(file,recmode,xml): |
137 | 137 | filetoscan = WebConfigScan(file,xml) |
138 | 138 | filetoscan.global_check() |
139 | 139 | if recmode: |
140 | 140 | print filetoscan.recommended |
141 | ||
141 |
0 | #!/usr/bin/env python2.7 | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | ||
10 | import unittest | |
11 | import sys | |
12 | import os | |
13 | sys.path.append(os.path.abspath(os.getcwd())) | |
14 | from plugins.repo.ping.plugin import CmdPingPlugin | |
15 | from model.common import ( | |
16 | factory, ModelObjectVuln, ModelObjectCred, | |
17 | ModelObjectVulnWeb, ModelObjectNote | |
18 | ) | |
19 | from model.hosts import ( | |
20 | Host, Service, Interface | |
21 | ) | |
22 | from plugins.modelactions import modelactions | |
23 | ||
24 | ||
25 | class CmdPingPluginTest(unittest.TestCase): | |
26 | plugin = CmdPingPlugin() | |
27 | outputPingGoogle = ("PING google.com (216.58.222.142) 56(84) bytes of" | |
28 | "data.\n64 bytes from scl03s11-in-f14.1e100.net" | |
29 | "(216.58.222.142): icmp_seq=1 ttl=53 time=28.9 ms") | |
30 | def setUp(self): | |
31 | factory.register(Host) | |
32 | factory.register(Interface) | |
33 | factory.register(Service) | |
34 | factory.register(ModelObjectVuln) | |
35 | factory.register(ModelObjectVulnWeb) | |
36 | factory.register(ModelObjectNote) | |
37 | factory.register(ModelObjectCred) | |
38 | ||
39 | def test_Plugin_Calls_createAndAddHost(self): | |
40 | self.plugin.parseOutputString(self.outputPingGoogle) | |
41 | action = self.plugin._pending_actions.get(block=True) | |
42 | self.assertEqual(action[0], modelactions.CADDHOST) | |
43 | self.assertEqual(action[1], "216.58.222.142") | |
44 | action = self.plugin._pending_actions.get(block=True) | |
45 | self.assertEqual(action[0], modelactions.CADDINTERFACE) | |
46 | self.assertEqual(action[2], "216.58.222.142") | |
47 | ||
48 | if __name__ == '__main__': | |
49 | unittest.main() |
0 | 0 | #!/usr/bin/python |
1 | ''' | |
2 | Faraday Penetration Test IDE | |
1 | """ | |
2 | Faraday Penetration Test IDE. | |
3 | ||
3 | 4 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
4 | 5 | See the file 'doc/LICENSE' for the license information |
6 | """ | |
5 | 7 | |
6 | ''' | |
8 | import mock | |
7 | 9 | import unittest |
8 | 10 | import sys |
9 | 11 | sys.path.append('.') |
10 | 12 | |
11 | from config.configuration import getInstanceConfiguration | |
12 | from model.workspace import Workspace | |
13 | from managers.reports_managers import ReportManager, NoReportsWatchException | |
13 | from plugins.core import PluginControllerForApi | |
14 | from managers.reports_managers import ReportProcessor | |
15 | from managers.reports_managers import ReportParser | |
14 | 16 | |
15 | from persistence.persistence_managers import DBTYPE | |
16 | from mockito import mock, verify, when, any | |
17 | CONF = getInstanceConfiguration() | |
17 | class UnitTestReportParser(unittest.TestCase): | |
18 | 18 | |
19 | from test_cases import common | |
19 | def testSendReportWithPlugin(self): | |
20 | 20 | |
21 | class UnitTestWorkspaceManager(unittest.TestCase): | |
22 | def testCreateReportManager(self): | |
23 | timer = 10 | |
24 | report_manager = ReportManager(timer, mock()) | |
21 | plugin_controller = mock.Mock(spec=PluginControllerForApi) | |
22 | plugin_controller.processCommandInput.return_value = (True, None, None) | |
23 | report_processor = ReportProcessor(plugin_controller) | |
25 | 24 | |
26 | self.assertIsNotNone(report_manager) | |
25 | file_mock = mock.MagicMock(spec=file) | |
26 | file_mock.read.return_value = 'Stringreturned' | |
27 | 27 | |
28 | def testWatchReportPath(self): | |
29 | import os.path | |
30 | import os | |
31 | workspace_name = common.new_random_workspace_name() | |
32 | timer = 10 | |
28 | with mock.patch('__builtin__.open', create=True) as mock_open: | |
29 | res = report_processor._sendReport("nmap", 'strings') | |
30 | self.assertTrue(res, "The plugin should be executed") | |
33 | 31 | |
34 | report_manager = ReportManager(timer, mock()) | |
35 | report_manager.watch(workspace_name) | |
32 | def testSendReportWithoutPlugin(self): | |
36 | 33 | |
37 | self.assertTrue(os.path.exists(os.path.join(CONF.getReportPath(), | |
38 | workspace_name)), 'Report directory not found') | |
39 | ||
40 | def testStartReportNoPathRunsException(self): | |
41 | report_manager = ReportManager(0, mock()) | |
42 | self.assertRaises(NoReportsWatchException, report_manager.startWatch) | |
34 | plugin_controller = mock.Mock(spec=PluginControllerForApi) | |
35 | plugin_controller.processCommandInput.return_value = (False, None, None) | |
36 | report_processor = ReportProcessor(plugin_controller) | |
37 | ||
38 | file_mock = mock.MagicMock(spec=file) | |
39 | file_mock.read.return_value = 'Stringreturned' | |
40 | ||
41 | with mock.patch('__builtin__.open', create=True) as mock_open: | |
42 | res = report_processor._sendReport("nmap", 'strings') | |
43 | self.assertFalse(res, "The plugin should not be executed") | |
43 | 44 | |
44 | 45 | if __name__ == '__main__': |
45 | 46 | unittest.main() |
46 |
2 | 2 | // See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | 4 | angular.module('faradayApp') |
5 | .controller('hostsCtrl', | |
5 | .controller('hostsCtrl', | |
6 | 6 | ['$scope', '$cookies', '$filter', '$location', '$route', '$routeParams', '$uibModal', 'hostsManager', 'workspacesFact', |
7 | 7 | function($scope, $cookies, $filter, $location, $route, $routeParams, $uibModal, hostsManager, workspacesFact) { |
8 | 8 | |
25 | 25 | $scope.hosts = hosts; |
26 | 26 | $scope.loadedVulns = true; |
27 | 27 | $scope.loadIcons(); |
28 | }); | |
29 | ||
30 | hostsManager.getAllServicesCount($scope.workspace) | |
31 | .then(function(servicesCount) { | |
32 | $scope.servicesCount = servicesCount; | |
28 | 33 | }); |
29 | 34 | |
30 | 35 | hostsManager.getAllVulnsCount($scope.workspace) |
51 | 51 | <tr> |
52 | 52 | <th><input type="checkbox" ng-model="selectall" ng-click="checkAll()"/></th> |
53 | 53 | <th> |
54 | <a href="">Services</a> | |
54 | <a href="">Open Services</a> | |
55 | 55 | </th> |
56 | 56 | <th> |
57 | 57 | <a href="" ng-click="toggleSort('name')">Name</a> |
77 | 77 | selection-model-selected-class="multi-selected" |
78 | 78 | selection-model-on-change="selectedHosts()"> |
79 | 79 | <td><input type="checkbox" name="{{host._id}}"/></td> |
80 | <td><a href="#/host/ws/{{workspace}}/hid/{{host._id}}">View</a></td> | |
80 | <td><a href="#/host/ws/{{workspace}}/hid/{{host._id}}" ng-bind="servicesCount[host._id] || 'None'"></a></td> | |
81 | 81 | <td> |
82 | 82 | {{host.name}} |
83 | 83 | <a href="//www.shodan.io/search?query={{host.name}}" uib-tooltip="Search in Shodan" target="_blank"> |
54 | 54 | var deferred = $q.defer(); |
55 | 55 | var self = this; |
56 | 56 | this._objects = {}; |
57 | ||
57 | ||
58 | 58 | $http.get(BASEURL + ws + '/_design/hosts/_view/hosts') |
59 | 59 | .success(function(hostsArray) { |
60 | 60 | var hosts = []; |
118 | 118 | .catch(function() { |
119 | 119 | deferred.reject("Error creating host"); |
120 | 120 | }); |
121 | ||
121 | ||
122 | 122 | return deferred.promise; |
123 | 123 | }; |
124 | 124 | |
185 | 185 | return deferred.promise; |
186 | 186 | }; |
187 | 187 | |
188 | hostsManager.getAllServicesCount = function(ws) { | |
189 | var deferred = $q.defer(); | |
190 | ||
191 | var url = BASEURL + ws + '/_design/hosts/_view/byservicecount?group=true'; | |
192 | ||
193 | $http.get(url) | |
194 | .success(function(allrows) { | |
195 | var services = {}; | |
196 | ||
197 | allrows.rows.forEach(function(service) { | |
198 | services[service.key] = service.value; | |
199 | }); | |
200 | ||
201 | deferred.resolve(services); | |
202 | }) | |
203 | .error(function() { | |
204 | deferred.reject("Unable to load Services"); | |
205 | }); | |
206 | ||
207 | return deferred.promise; | |
208 | }; | |
209 | ||
188 | 210 | hostsManager.getInterfaces = function(ws, id) { |
189 | 211 | var deferred = $q.defer(), |
190 | 212 | self = this; |
67 | 67 | }) |
68 | 68 | return deferred.promise; |
69 | 69 | } |
70 | ||
70 | ||
71 | 71 | servicesManager.getServicesByHost = function(ws, host_id) { |
72 | 72 | var deferred = $q.defer(); |
73 | 73 | var url = BASEURL + "/" + ws + "/_design/services/_view/byhost?key=\"" + host_id + "\""; |
122 | 122 | }) |
123 | 123 | }); |
124 | 124 | }); |
125 | ||
125 | ||
126 | 126 | return deferred.promise; |
127 | 127 | } |
128 | 128 |
60 | 60 | <div class="form-horizontal"> |
61 | 61 | <div class="form-group"> |
62 | 62 | <div class="col-md-12"> |
63 | <h5>CWE</h5> | |
64 | <input type="text" ng-model="modal.cwe_selected" class="form-control input-sm" placeholder="Search for CWE" uib-typeahead="cwe as cwe.name for cwe in modal.cweList | filter:{name: $viewValue} | limitTo:10" typeahead-on-select="modal.populate($item, $model, $label)"> | |
63 | <h5>Search vulnerability database templates <a href="https://github.com/infobyte/faraday/wiki/vulnerabilities-database" target="_blank"><span class="glyphicon glyphicon-question-sign" title="Read more about vulnerability templates in Faraday Documentation"></span></a></h5> | |
64 | <input type="text" ng-model="modal.cwe_selected" class="form-control input-sm" placeholder="Search for vulnerability templates" uib-typeahead="cwe as cwe.name for cwe in modal.cweList | filter:{name: $viewValue} | limitTo:10" typeahead-on-select="modal.populate($item, $model, $label)"> | |
65 | 65 | </div> |
66 | 66 | </div> |
67 | 67 | <div class="form-group"> |
6 | 6 | |
7 | 7 | WORKSPACE=`cat $HOME/.faraday/config/user.xml | grep '<last_workspace' | cut -d '>' -f 2 | cut -d '<' -f 1` |
8 | 8 | STATUS=`curl -s $FARADAY_ZSH_HOST:$FARADAY_ZSH_RPORT/status/check | sed "s/[^0-9]//g" | grep -v '^[[:space:]]*$'` |
9 | PS1="%{${fg_bold[red]}%}[faraday]($WORKSPACE)%{${reset_color}%} $PS1" | |
9 | USERPS1=$PS1 | |
10 | PS1="%{${fg_bold[red]}%}[faraday]($WORKSPACE)%{${reset_color}%} $USERPS1" | |
11 | export FARADAY_OUTPUT= | |
12 | export FARADAY_PLUGIN= | |
13 | alias faraday_b64='base64 -w 0' | |
14 | ||
15 | if [[ $(uname) == 'Darwin' ]]; then | |
16 | alias faraday_b64='base64' | |
17 | fi | |
10 | 18 | |
11 | 19 | echo ">>> WELCOME TO FARADAY" |
12 | 20 | echo "[+] Current Workspace: $WORKSPACE" |
13 | 21 | if [[ -z $STATUS ]]; then |
14 | echo "[-] API: Warning API unreachable" | |
22 | echo "[-] API: Warning API unreachable" | |
15 | 23 | |
16 | elif [[ $STATUS == "200" ]]; then | |
17 | echo "[+] API: OK" | |
18 | else | |
19 | echo "[!] API: $STATUS" | |
24 | elif [[ $STATUS == "200" ]]; then | |
25 | echo "[+] API: OK" | |
26 | else | |
27 | echo "[!] API: $STATUS" | |
20 | 28 | |
21 | 29 | fi |
22 | 30 | |
23 | 31 | setopt multios |
24 | 32 | setopt histignorespace |
25 | 33 | |
26 | plugin_controller_client=$HOME/.faraday/zsh/plugin_controller_client.py | |
27 | 34 | old_cmd= |
28 | 35 | |
29 | add-output() { | |
36 | function add-output() { | |
30 | 37 | old_cmd=$BUFFER |
31 | new_cmd=`python2 $plugin_controller_client send_cmd $BUFFER` | |
32 | BUFFER=" $new_cmd" | |
38 | FARADAY_PLUGIN= | |
39 | FARADAY_OUTPUT= | |
40 | pwd_actual=$(printf "%s" "$(pwd)"| faraday_b64) | |
41 | cmd_encoded=$(printf "%s" "$BUFFER"| faraday_b64) | |
42 | json_response=`curl -s -X POST -H "Content-Type: application/json" -d "{\"cmd\": \"$cmd_encoded\", \"pid\": $$, \"pwd\": \"$pwd_actual\"}" http://$FARADAY_ZSH_HOST:$FARADAY_ZSH_RPORT/cmd/input` | |
43 | if [[ $? -eq 0 ]]; then | |
44 | code=`echo $json_response|env python2.7 -c "import sys, json;print(json.load(sys.stdin)[\"code\"])"` | |
45 | if [[ "$code" == "200" ]]; then | |
46 | FARADAY_PLUGIN=`echo $json_response | env python2.7 -c "import sys, json; print(json.load(sys.stdin)[\"plugin\"])"` | |
47 | new_cmd=`echo $json_response | env python2.7 -c "import sys, json; print(json.load(sys.stdin)[\"cmd\"])"` | |
48 | if [[ "$new_cmd" != "None" ]]; then | |
49 | BUFFER=" $new_cmd" | |
50 | fi | |
51 | FARADAY_OUTPUT=`mktemp tmp.XXXXXXXXXXXXXXXXXXXXXXXXXXXXX` | |
52 | BUFFER="$BUFFER >&1 >> $FARADAY_OUTPUT" | |
53 | fi | |
54 | fi | |
33 | 55 | zle .accept-line "$@" |
34 | 56 | } |
35 | 57 | |
36 | function zshaddhistory() { | |
58 | function send-output() { | |
59 | if [ ! -z "$FARADAY_PLUGIN" ]; then | |
60 | output=`env python2.7 -c "import base64; print(base64.b64encode(open(\"$FARADAY_OUTPUT\",'r').read()))"` | |
61 | temp_file=`mktemp tmp.XXXXXXXXXXXXXXXXXXXXXXXXXXXXX` | |
62 | echo "{\"exit_code\": $?, \"pid\": $$, \"output\": \"$output\" }" >> $temp_file | |
63 | curl=`curl -s -X POST -H "Content-Type: application/json" -d @$temp_file http://$FARADAY_ZSH_HOST:$FARADAY_ZSH_RPORT/cmd/output` | |
64 | rm -f $temp_file | |
65 | fi | |
66 | if [ -f $FARADAY_OUTPUT ];then | |
67 | rm -f $FARADAY_OUTPUT | |
68 | fi | |
69 | FARADAY_OUTPUT= | |
70 | FARADAY_PLUGIN= | |
71 | } | |
72 | ||
73 | zshaddhistory() { | |
37 | 74 | emulate -L zsh |
38 | 75 | print -sr -- "$old_cmd" |
39 | 76 | fc -p |
40 | 77 | return 1 |
41 | 78 | } |
42 | 79 | |
80 | precmd() { | |
81 | send-output | |
82 | WORKSPACE=`cat $HOME/.faraday/config/user.xml | grep '<last_workspace' | cut -d '>' -f 2 | cut -d '<' -f 1` | |
83 | PS1="%{${fg_bold[red]}%}[faraday]($WORKSPACE)%{${reset_color}%} $USERPS1" | |
84 | return 0 | |
85 | } | |
86 | ||
87 | zshexit() { | |
88 | send-output | |
89 | } | |
90 | ||
43 | 91 | zle -N accept-line add-output |
0 | #!/usr/bin/env python | |
0 | #!/usr/bin/env python2.7 | |
1 | 1 | ''' |
2 | 2 | Faraday Penetration Test IDE |
3 | 3 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
28 | 28 | headers = {'Content-type': 'application/json', 'Accept': 'application/json'} |
29 | 29 | |
30 | 30 | |
31 | def send_cmd(cmd): | |
32 | data = {"cmd": cmd} | |
31 | def send_cmd(pid, cmd): | |
32 | ||
33 | data = {'pid': pid, 'cmd': cmd} | |
33 | 34 | new_cmd = cmd |
34 | result = False | |
35 | response = '' | |
36 | ||
35 | 37 | try: |
36 | response = requests.post(url_input, | |
37 | data=json.dumps(data), | |
38 | headers=headers) | |
38 | request = requests.post( | |
39 | url_input, | |
40 | data=json.dumps(data), | |
41 | headers=headers) | |
39 | 42 | |
40 | if response.status_code == 200: | |
41 | json_response = response.json() | |
42 | if "cmd" in json_response.keys(): | |
43 | if json_response.get("cmd") is not None: | |
44 | new_cmd = json_response.get("cmd") | |
45 | if "custom_output_file" in json_response.keys(): | |
46 | output_file = json_response.get("custom_output_file") | |
47 | if output_file is None: | |
48 | output_file = "%s/%s.output" % (output_folder, uuid.uuid4()) | |
49 | new_cmd += " >&1 > %s" % output_file | |
43 | if request.status_code == 200: | |
50 | 44 | |
51 | new_cmd += " && python2 %s send_output %s \"%s\"" % (file_path, base64.b64encode(cmd), output_file) | |
52 | result = True | |
45 | response = request.json() | |
46 | if response.get("cmd") is not None: | |
47 | new_cmd = response.get("cmd") | |
48 | ||
49 | output_file = "%s/%s%s.output" % ( | |
50 | output_folder, data['pid'], uuid.uuid4()) | |
51 | ||
52 | new_cmd += " >&1 > %s" % output_file | |
53 | 53 | except: |
54 | new_cmd = cmd | |
54 | response = '' | |
55 | 55 | finally: |
56 | print new_cmd | |
57 | return result | |
56 | print response | |
57 | return 0 | |
58 | 58 | |
59 | def gen_output(pid): | |
60 | print "%s/%s.%s.output" % (output_folder, pid, uuid.uuid4()) | |
61 | return 0 | |
59 | 62 | |
60 | def send_output(cmd, output_file): | |
63 | def send_output(pid, exit_code, output_file): | |
61 | 64 | output_file = open(output_file) |
62 | 65 | output = output_file.read() |
63 | data = {"cmd": base64.b64decode(cmd), "output": base64.b64encode(output)} | |
66 | ||
67 | data = { | |
68 | 'pid': pid, | |
69 | 'exit_code': exit_code, | |
70 | 'output': base64.b64encode(output) | |
71 | } | |
72 | ||
64 | 73 | response = requests.post(url_output, |
65 | 74 | data=json.dumps(data), |
66 | 75 | headers=headers) |
67 | 76 | if response.status_code != 200: |
68 | print "something wrong" | |
69 | 77 | print response.json() |
70 | return True | |
71 | return False | |
78 | return -1 | |
79 | return 0 | |
72 | 80 | |
73 | 81 | |
74 | 82 | def main(argv): |
75 | if len(argv) < 3: | |
83 | if len(argv) < 2: | |
76 | 84 | sys.exit(0) |
77 | 85 | |
78 | 86 | action = argv[1] |
79 | 87 | |
80 | dispatcher = {'send_cmd': send_cmd, 'send_output': send_output} | |
88 | # dispatcher = { | |
89 | # 'send_cmd': send_cmd, | |
90 | # 'send_output': send_output, | |
91 | # 'gen_output': gen_output} | |
81 | 92 | |
82 | if action in dispatcher.keys(): | |
83 | if len(argv[2:]) > 0: | |
84 | dispatcher[action](*argv[2:]) | |
93 | if action == 'send_cmd' and len(argv[2:]) == 2: | |
94 | send_cmd(argv[2], argv[3]) | |
95 | if action == 'send_output' and len(argv[2:]) == 3: | |
96 | send_cmd(argv[2], argv[3], argv[4]) | |
97 | if action == 'gen_output' and len(argv[2:]) == 1: | |
98 | send_cmd(argv[2]) | |
85 | 99 | |
86 | #sys.exit(0) | |
100 | # if action in dispatcher.keys(): | |
101 | # if len(argv[2:]) > 0: | |
102 | # dispatcher[action](*argv[2:]) | |
103 | ||
87 | 104 | |
88 | 105 | if __name__ == '__main__': |
89 | 106 | main(sys.argv) |