def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks if self.opt.file: if os.path.isfile(self.opt.file): self.restoreState(self.opt.file) self.issueAlert('Restored state from %s' % (self.opt.file,)) else: self.issueAlert('Could not restore state from %s:' 'file does not exist' % (self.opt.file,)) for module, _ in self._menus.options(): if self._menus.getbool(module) is True: for menu in _get_menus(module): menu(self) for component, _ in self._components.options(): if self._components.getbool(component) is True: _get_plugins(component) if not self.opt.disable_reloading: self._monitor_item(self.config) self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('Burp extender ready...') return
def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks if self.opt.file: if os.path.isfile(self.opt.file): self.restoreState(self.opt.file) self.issueAlert('Restored state from %s' % (self.opt.file,)) else: self.issueAlert('Could not restore state from %s:' 'file does not exist' % (self.opt.file,)) for module, _ in self.menus.options(): if self.menus.getbool(module) is True: for MenuItemHandler in _get_menus(module): MenuItemHandler(self) for component, _ in self._components.options(): if self._components.getbool(component) is True: _get_plugins(component) if not self.opt.disable_reloading: self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('Burp extender ready...') return
def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks if self.opt.file: if os.path.isfile(self.opt.file): self.restoreState(self.opt.file) self.issueAlert('restored state from %s' % (self.opt.file,)) else: self.issueAlert('could not restore state from %s:' 'file does not exist' % (self.opt.file,)) if self.opt.interactive: ConsoleMenu(_burp=self) if not self.opt.disable_reloading: self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('burp extender ready...') return
def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks if self.opt.file: if os.path.isfile(self.opt.file): self.restoreState(self.opt.file) self.issueAlert('Restored state from %s' % (self.opt.file, )) else: self.issueAlert('Could not restore state from %s:' 'file does not exist' % (self.opt.file, )) for module, _ in self._menus.options(): if self._menus.getbool(module) is True: for menu in _get_menus(module): menu(self) for component, _ in self._components.options(): if self._components.getbool(component) is True: _get_plugins(component) if not self.opt.disable_reloading: self._monitor_item(self.config) self.monitor = PluginMonitorThread(self) self.monitor.start() try: self.setExtensionName('jython-burp-api') except Exception: pass self.issueAlert('Burp extender ready...') return
class BurpExtender(IBurpExtender, ComponentManager): _components = ConfigSection('components', '') menus = ConfigSection('menus', '') def __init__(self): ComponentManager.__init__(self) self.log = logging.getLogger(self.__class__.__name__) self.monitoring = [] def __repr__(self): return '<BurpExtender at %#x>' % (id(self),) def componentActivated(self, component): component.burp = self component.config = self.config component.log = self.log def setCommandLineArgs(self, args): ''' This method is invoked immediately after the implementation's constructor to pass any command-line arguments that were passed to Burp Suite on startup. The following command-line options have been made available: -i, --interactive Run Burp in interactive mode (Jython Console) -f <FILE> Restore from burp state file upon startup -d, --debug Set log level to DEBUG -v, --verbose Set log level to INFO -C, --config Specify an alternate config (default: burp.ini) --disable-reloading Disable monitoring of plugins for changes -h ''' from optparse import OptionParser parser = OptionParser() parser.add_option('-i', '--interactive', action='store_true', help='Run Burp in interactive mode (Jython Console)') parser.add_option('-f', '--file', metavar='FILE', help='Restore Burp state from FILE on startup') parser.add_option('-d', '--debug', action='store_true', help='Set log level to DEBUG') parser.add_option('-v', '--verbose', action='store_true', help='Set log level to INFO') parser.add_option('-P', '--python-path', default='', help='Set PYTHONPATH used by Jython') parser.add_option('-C', '--config', default='burp.ini', help='Specify alternate jython-burp config file') parser.add_option('--disable-reloading', action='store_true', help='Disable hot-reloading when a file is changed') opt, args = parser.parse_args(list(args)) if opt.debug: logging.basicConfig( filename='jython-burp.log', format='%(asctime)-15s - %(levelname)s - %(message)s', level=logging.DEBUG) elif opt.verbose: logging.basicConfig( filename='jython-burp.log', format='%(asctime)-15s - %(levelname)s - %(message)s', level=logging.INFO) self.config = Configuration(opt.config) if opt.interactive: from java.util import Properties pre_properties = System.getProperties() pre_properties['python.console'] = 'org.python.util.ReadlineConsole' post_properties = Properties() if opt.python_path: post_properties['python.path'] = opt.python_path PythonInterpreter.initialize( pre_properties, post_properties, sys.argv[1:]) self.console = JLineConsole() self.console.exec('import __builtin__ as __builtins__') self.console.exec('from gds.burp import HttpRequest, HttpResponse') self.console.set('Burp', self) sys.stderr.write('Launching interactive session...\n') ConsoleThread(self.console).start() self.opt, self.args = opt, args return def applicationClosing(self): ''' This method is invoked immediately before Burp Suite exits. ''' self.log.debug('Shutting down Burp') return def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks if self.opt.file: if os.path.isfile(self.opt.file): self.restoreState(self.opt.file) self.issueAlert('Restored state from %s' % (self.opt.file,)) else: self.issueAlert('Could not restore state from %s:' 'file does not exist' % (self.opt.file,)) for module, _ in self.menus.options(): if self.menus.getbool(module) is True: for MenuItemHandler in _get_menus(module): MenuItemHandler(self) for component, _ in self._components.options(): if self._components.getbool(component) is True: _get_plugins(component) if not self.opt.disable_reloading: self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('Burp extender ready...') return def _check_cb(self): if hasattr(self, '_callbacks'): return getattr(self, '_callbacks') def _check_and_callback(self, method, *args): cb = self._check_cb() if not hasattr(cb, method.__name__): raise Exception("%s not available in your version of Burp" % ( method.__name__,)) return getattr(cb, method.__name__)(*args) cb = property(_check_cb) @callback def makeHttpRequest(self, host, port, useHttps, request): return @callback def sendToRepeater(self, host, port, useHttps, request, tabCaption): return @callback def sendToIntruder(self, host, port, useHttps, request, *args): return @callback def sendToSpider(self, url): return @callback def doActiveScan(self, host, port, useHttps, request, *args): return @callback def doPassiveScan(self, host, port, useHttps, request, response): return @callback def getScanIssues(self, urlPrefix): return def registerMenuItem(self, menuItemCaption, menuItemHandler): ''' This method can be used to register a new menu item which will appear on the various context menus that are used throughout Burp Suite to handle user-driven actions. :param menuItemCaption: The caption to be displayed on the menu item. :param menuItemHandler: The handler to be invoked when the user clicks on the menu item. ''' # don't monitor objects initialized in the interpreter if menuItemHandler.__module__ != '__main__': _module = menuItemHandler.__module__ _filename = inspect.getsourcefile(menuItemHandler.__class__) _class = menuItemHandler.__class__.__name__ self.monitoring.append({ 'filename': _filename, 'class': _class, 'module': _module, 'type': 'IMenuItemHandler', 'instance': weakref.ref(menuItemHandler), }) self._check_and_callback( self.registerMenuItem, menuItemCaption, menuItemHandler) return def newScanIssue(self, issue): ''' This method is invoked whenever Burp Scanner discovers a new, unique issue, and can be used to perform customised reporting or logging of issues. Plugins should implement the :meth:`~INewScanIssueHandler.newScanIssue` method of the :class:`INewScanIssueHandler` interface to act upon new scan issues as they are identified. :param issue: Details of the new scan issue. ''' return NewScanIssueDispatcher(self).newScanIssue(issue) def processHttpMessage(self, toolName, messageIsRequest, messageInfo): ''' This method is invoked whenever any of Burp's tools makes an HTTP request or receives a response. It allows extensions to intercept and modify the HTTP traffic of all Burp tools. For each request, the method is invoked after the request has been fully processed by the invoking tool and is about to be made on the network. For each response, the method is invoked after the response has been received from the network and before any processing is performed by the invoking tool. Plugins should implement the :meth:`processRequest` and/or :meth:`processResponse` methods of one or more interfaces in :module:`gds.burp.api`. A plugin may implement more than one interface, and implement both `processRequest` and `processResponse` methods. This allows plugins to only hook certain tools in specific scenarios, such as "only hook requests sent via Intruder or Scanner, and only hook responses received via Proxy tool. An example is provided below that only modifies requests as they are made via Repeater and Intruder. .. code-block:: python class MyPlugin(Component): implements(IIntruderRequestHandler, IRepeaterRequestHandler) def processRequest(self, request): # replace all occurrences of 'somestring' in HTTP # request with 'anotherstring'. request.raw = request.raw.replace('somestring', 'anotherstring') ''' return PluginDispatcher(self).processHttpMessage( toolName, messageIsRequest, messageInfo) def getProxyHistory(self, *args): ''' This method returns a generator of all items in the proxy history. :params *args: Optional strings to match against url. ''' if args: matchers = [re.compile(arg) for arg in args] for request in self._check_and_callback(self.getProxyHistory): for matcher in matchers: if matcher.search(request.getUrl().toString()): yield HttpRequest(request, _burp=self) else: for request in self._check_and_callback(self.getProxyHistory): yield HttpRequest(request, _burp=self) @callback def addToSiteMap(self, item): return def getSiteMap(self, *urlPrefixes): ''' This method returns a generator of details of items in the site map. :params *urlPrefixes: Optional URL prefixes, in order to extract a specific subset of the site map. The method performs a simple case-sensitive text match, returning all site map items whose URL begins with the specified prefix. If this parameter is null, the entire site map is returned. ''' for urlPrefix in urlPrefixes: for item in self._check_and_callback(self.getSiteMap, urlPrefix): yield HttpRequest(item, _burp=self) @callback def excludeFromScope(self, url): return @callback def includeInScope(self, url): return @callback def isInScope(self, url): return @callback def issueAlert(self, message): ''' This method can be used to display a specified message in the Burp Suite alerts tab. :param message: The alert message to display. ''' return def restoreState(self, filename): ''' This method can be used to restore Burp's state from a specified saved state file. :param filename: The filename containing Burp's saved state. ''' return self._check_and_callback(self.restoreState, File(filename)) def saveState(self, filename): ''' This method can be used to save Burp's state to a specified file. This method blocks until the save operation is completed, and must not be called from the event thread. :param filename: The filename to save Burp's state in. ''' return self._check_and_callback(self.saveState, File(filename)) @callback def loadConfig(self, config): ''' This method causes Burp to load a new configuration from a dictionary of key/value pairs provided. Any settings not specified in the dict will be restored to their default values. To selectively update only some settings and leave the rest unchanged, you should first call saveConfig to obtain Burp's current configuration, modify the relevant items in the dict, and then call loadConfig with the same dict. :param config: A dict of key/value pairs to use as Burp's new configuration. ''' return def saveConfig(self): ''' This method causes Burp to return its current configuration as a dictionary of key/value pairs. ''' return dict(self._check_and_callback(self.saveConfig)) @callback def setProxyInterceptionEnabled(self, enabled): ''' This method sets the interception mode for Burp Proxy. :param enabled: Indicates whether interception of proxy messages should be enabled. ''' return def getBurpVersion(self): ''' This method retrieves information about the version of Burp in which the extension is running. It can be used by extensions to dynamically adjust their behavior depending on the functionality and APIs supported by the current version. ''' return list(self._check_and_callback(self.getBurpVersion)) def exitSuite(self, promptUser=False): ''' This method can be used to shut down Burp programmatically, with an optional prompt to the user. If the method returns, the user cancelled the shutdown prompt. :param promptUser: Indicates whether to prompt the user to confirm the shutdown (default is False: no prompt). ''' if promptUser is True: return self._check_and_callback(self.exitSuite, True) return self._check_and_callback(self.exitSuite, False)
class BurpExtender(IBurpExtender, ComponentManager): _components = ConfigSection('components', '') _menus = ConfigSection('menus', '') def __init__(self): ComponentManager.__init__(self) self.log = logging.getLogger(self.__class__.__name__) self.monitoring = {} def __repr__(self): return '<BurpExtender at %#x>' % (id(self), ) def __iter__(self): for request in self.getProxyHistory(): yield request def _monitor_item(self, obj): # don't monitor objects initialized in the interpreter if obj.__module__ == '__main__': return mod = obj.__module__ cls = obj.__class__.__name__ # Monitor the actual configuration file rather than the # module the Configuration class is defined in if isinstance(obj, Configuration): filename = obj.filename elif isinstance(obj, (Component, IMenuItemHandler)): filename = inspect.getsourcefile(obj.__class__) elif isinstance(obj, type): filename = inspect.getsourcefile(obj) monitoring = self.monitoring.setdefault(filename, []) monitoring.append({ 'class': cls, 'instance': weakref.ref(obj), 'module': mod, }) return def componentActivated(self, component): self.log.debug('Activating component: %r', component) component.burp = self component.config = self.config component.log = self.log return def applicationClosing(self): ''' This method is invoked immediately before Burp Suite exits. ''' self.log.debug('Shutting down Burp') return def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks try: self.setExtensionName(self.getExtensionName()) except Exception: pass try: log_filename = self.loadExtensionSetting(*settings.LOG_FILENAME) log_format = self.loadExtensionSetting(*settings.LOG_FORMAT) log_level = self.loadExtensionSetting(*settings.LOG_LEVEL) self.log.setLevel(log_level) fileHandler = logging.FileHandler( log_filename, encoding='utf-8', delay=True) streamHandler = logging.StreamHandler() formatter = logging.Formatter(fmt=log_format) fileHandler.setFormatter(formatter) streamHandler.setFormatter(formatter) self.log.addHandler(fileHandler) self.log.addHandler(streamHandler) self._handler = fileHandler except Exception: self.log.exception('Could not load extension logging settings') try: config = self.loadExtensionSetting(*settings.CONFIG_FILENAME) self.config = Configuration(os.path.abspath(config)) except Exception: self.log.exception('Could not load extension config settings') try: from gds.burp.listeners import PluginListener, \ SaveConfigurationOnUnload, \ ScannerListener SaveConfigurationOnUnload(self) PluginListener(self) ScannerListener(self) except Exception: self.log.exception('Could not load extension listener') try: from gds.burp.ui import ConsoleTab self._console_tab = ConsoleTab(self) self.console = self._console_tab.interpreter except Exception as e: self.log.exception('Could not load console tab') for module, _ in self._menus.options(): if self._menus.getbool(module) is True: for menu in _get_menus(module): menu(self) for component, _ in self._components.options(): if self._components.getbool(component) is True: _get_plugins(component) self._monitor_item(self.config) self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('Burp extender ready...') return def _check_cb(self): if hasattr(self, '_callbacks'): return getattr(self, '_callbacks') def _check_and_callback(self, method, *args): cb = self._check_cb() if not hasattr(cb, method.__name__): raise Exception("%s() not available in your version of Burp" % ( method.__name__, )) try: return getattr(cb, method.__name__)(*args) except AbstractMethodError: raise Exception("%s() not available in your version of Burp" % ( method.__name__, )) cb = property(_check_cb) @callback def makeHttpRequest(self, host, port, useHttps, request): return @callback def sendToRepeater(self, host, port, useHttps, request, tabCaption): return @callback def sendToIntruder(self, host, port, useHttps, request, *args): return def sendToSpider(self, url): if not self.isInScope(url): self.includeInScope(url) self._check_and_callback(self.sendToSpider, URL(str(url))) return @callback def doActiveScan(self, host, port, useHttps, request, *args): return @callback def doPassiveScan(self, host, port, useHttps, request, response): return @callback def getScanIssues(self, urlPrefix): return def registerMenuItem(self, menuItemCaption, menuItemHandler): ''' This method can be used to register a new menu item which will appear on the various context menus that are used throughout Burp Suite to handle user-driven actions. :param menuItemCaption: The caption to be displayed on the menu item. :param menuItemHandler: The handler to be invoked when the user clicks on the menu item. ''' self._monitor_item(menuItemHandler) self._check_and_callback( self.registerMenuItem, menuItemCaption, menuItemHandler) return def newScanIssue(self, issue): ''' This method is invoked whenever Burp Scanner discovers a new, unique issue, and can be used to perform customised reporting or logging of issues. Plugins should implement the :meth:`~INewScanIssueHandler.newScanIssue` method of the :class:`INewScanIssueHandler` interface to act upon new scan issues as they are identified. :param issue: Details of the new scan issue. ''' return NewScanIssueDispatcher(self).newScanIssue(issue) def processHttpMessage(self, toolName, messageIsRequest, messageInfo): ''' This method is invoked whenever any of Burp's tools makes an HTTP request or receives a response. It allows extensions to intercept and modify the HTTP traffic of all Burp tools. For each request, the method is invoked after the request has been fully processed by the invoking tool and is about to be made on the network. For each response, the method is invoked after the response has been received from the network and before any processing is performed by the invoking tool. Plugins should implement the :meth:`processRequest` and/or :meth:`processResponse` methods of one or more interfaces in :module:`gds.burp.api`. A plugin may implement more than one interface, and implement both `processRequest` and `processResponse` methods. This allows plugins to only hook certain tools in specific scenarios, such as "only hook requests sent via Intruder or Scanner, and only hook responses received via Proxy tool. An example is provided below that only modifies requests as they are made via Repeater and Intruder. .. code-block:: python class MyPlugin(Component): implements(IIntruderRequestHandler, IRepeaterRequestHandler) def processRequest(self, request): # replace all occurrences of 'somestring' in HTTP # request with 'anotherstring'. request.raw = request.raw.replace('somestring', 'anotherstring') ''' return PluginDispatcher(self).processHttpMessage( toolName, messageIsRequest, messageInfo) def getProxyHistory(self, *args): ''' This method returns a generator of all items in the proxy history. :params *args: Optional strings to match against url. ''' if args: matchers = [re.compile(arg) for arg in args] for request in self._check_and_callback(self.getProxyHistory): for matcher in matchers: if matcher.search(request.getUrl().toString()): yield HttpRequest(request, _burp=self) break else: for request in self._check_and_callback(self.getProxyHistory): yield HttpRequest(request, _burp=self) history = property(lambda burp: list(burp.getProxyHistory())) @callback def addToSiteMap(self, item): return def getSiteMap(self, *urlPrefixes): ''' This method returns a generator of details of items in the site map. :params *urlPrefixes: Optional URL prefixes, in order to extract a specific subset of the site map. The method performs a simple case-sensitive text match, returning all site map items whose URL begins with the specified prefix. If this parameter is null, the entire site map is returned. ''' for urlPrefix in urlPrefixes or ('http', ): for item in self._check_and_callback(self.getSiteMap, urlPrefix): yield HttpRequest(item, _burp=self) def excludeFromScope(self, url): self._check_and_callback(self.excludeFromScope, URL(str(url))) return def includeInScope(self, url): self._check_and_callback(self.includeInScope, URL(str(url))) return def isInScope(self, url): return self._check_and_callback(self.isInScope, URL(str(url))) @callback def issueAlert(self, message): ''' This method can be used to display a specified message in the Burp Suite alerts tab. :param message: The alert message to display. ''' return def restoreState(self, filename): ''' This method can be used to restore Burp's state from a specified saved state file. :param filename: The filename containing Burp's saved state. ''' return self._check_and_callback(self.restoreState, File(filename)) def saveState(self, filename): ''' This method can be used to save Burp's state to a specified file. This method blocks until the save operation is completed, and must not be called from the event thread. :param filename: The filename to save Burp's state in. ''' return self._check_and_callback(self.saveState, File(filename)) @callback def loadConfig(self, config): ''' This method causes Burp to load a new configuration from a dictionary of key/value pairs provided. Any settings not specified in the dict will be restored to their default values. To selectively update only some settings and leave the rest unchanged, you should first call saveConfig to obtain Burp's current configuration, modify the relevant items in the dict, and then call loadConfig with the same dict. :param config: A dict of key/value pairs to use as Burp's new configuration. ''' return def saveConfig(self): ''' This method causes Burp to return its current configuration as a dictionary of key/value pairs. ''' return dict(self._check_and_callback(self.saveConfig)) @callback def setProxyInterceptionEnabled(self, enabled): ''' This method sets the interception mode for Burp Proxy. :param enabled: Indicates whether interception of proxy messages should be enabled. ''' return def getBurpVersion(self): ''' This method retrieves information about the version of Burp in which the extension is running. It can be used by extensions to dynamically adjust their behavior depending on the functionality and APIs supported by the current version. ''' return list(self._check_and_callback(self.getBurpVersion)) version = property(getBurpVersion) def exitSuite(self, promptUser=False): ''' This method can be used to shut down Burp programmatically, with an optional prompt to the user. If the method returns, the user cancelled the shutdown prompt. :param promptUser: Indicates whether to prompt the user to confirm the shutdown (default is False: no prompt). ''' if promptUser is True: return self._check_and_callback(self.exitSuite, True) return self._check_and_callback(self.exitSuite, False) @callback def addScanIssue(self, issue): ''' This method is used to register a new Scanner issue. Note: Wherever possible, extensions should implement custom Scanner checks using IScannerCheck and report issues via those checks, so as to integrate with Burp's user-driven workflow, and ensure proper consolidation of duplicate reported issues. This method is only designed for tasks outside of the normal testing workflow, such as importing results from other scanning tools. :param issue: An object created by the extension that implements the IScanIssue interface. ''' return @callback def addSuiteTab(self, tab): return @callback def applyMarkers(self, request, requestMarkers=None, responseMarkers=None): return @callback def createMessageEditor(self, controller, editable): return @callback def createTextEditor(self): return @callback def customizeUiComponent(self, component): return @callback def getHelpers(self): return helpers = property(lambda burp: burp.getHelpers()) @callback def getStderr(self): return stderr = property(lambda burp: burp.getStderr()) @callback def getStdout(self): return stdout = property(lambda burp: burp.getStdout()) @callback def getToolName(self, toolFlag): return @callback def registerContextMenuFactory(self, factory): return @callback def registerExtensionStateListener(self, listener): return @callback def registerHttpListener(self, listener): return @callback def registerIntruderPayloadGeneratorFactory(self, factory): return @callback def registerIntruderPayloadProcessor(self, processor): return @callback def registerMessageEditorTabFactory(self, factory): return @callback def registerProxyListener(self, listener): return @callback def registerScannerCheck(self, check): return @callback def registerScannerInsertionPointProvider(self, provider): return @callback def registerScannerListener(self, listener): return @callback def registerSessionHandlingAction(self, action): return @callback def removeSuiteTab(self, tab): return @callback def saveBuffersToTempFiles(self, request): return @callback def saveToTempFile(self, buffer): return @callback def setExtensionName(self, name): return def getExtensionName(self): return self.loadExtensionSetting(*settings.EXTENSION_NAME) def loadExtensionSetting(self, name, default=None): if name.startswith('jython.'): settings = self._check_and_callback(self.loadExtensionSetting, 'settings') if settings: settings = json.loads(settings) return settings.get(name, default) return default value = self._check_and_callback(self.loadExtensionSetting, name) if not value and default is not None: return default return value def saveExtensionSetting(self, name, value): if name.startswith('jython.'): settings = self._check_and_callback(self.loadExtensionSetting, 'settings') if settings: settings = json.loads(settings) else: settings = {} settings[name] = value self._check_and_callback(self.saveExtensionSetting, 'settings', json.dumps(settings)) return self._check_and_callback(self.saveExtensionSetting, name, value) return
def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks try: self.setExtensionName(self.getExtensionName()) except Exception: pass try: log_filename = self.loadExtensionSetting(*settings.LOG_FILENAME) log_format = self.loadExtensionSetting(*settings.LOG_FORMAT) log_level = self.loadExtensionSetting(*settings.LOG_LEVEL) self.log.setLevel(log_level) fileHandler = logging.FileHandler( log_filename, encoding='utf-8', delay=True) streamHandler = logging.StreamHandler() formatter = logging.Formatter(fmt=log_format) fileHandler.setFormatter(formatter) streamHandler.setFormatter(formatter) self.log.addHandler(fileHandler) self.log.addHandler(streamHandler) self._handler = fileHandler except Exception: self.log.exception('Could not load extension logging settings') try: config = self.loadExtensionSetting(*settings.CONFIG_FILENAME) self.config = Configuration(os.path.abspath(config)) except Exception: self.log.exception('Could not load extension config settings') try: from gds.burp.listeners import PluginListener, \ SaveConfigurationOnUnload, \ ScannerListener SaveConfigurationOnUnload(self) PluginListener(self) ScannerListener(self) except Exception: self.log.exception('Could not load extension listener') try: from gds.burp.ui import ConsoleTab self._console_tab = ConsoleTab(self) self.console = self._console_tab.interpreter except Exception as e: self.log.exception('Could not load console tab') for module, _ in self._menus.options(): if self._menus.getbool(module) is True: for menu in _get_menus(module): menu(self) for component, _ in self._components.options(): if self._components.getbool(component) is True: _get_plugins(component) self._monitor_item(self.config) self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('Burp extender ready...') return
class BurpExtender(IBurpExtender): def __init__(self): self.monitoring = [] def __repr__(self): return '<BurpExtender %#x>' % (id(self),) def setCommandLineArgs(self, args): ''' This method is invoked immediately after the implementation's constructor to pass any command-line arguments that were passed to Burp Suite on startup. The following command-line options have been made available: -i, --interactive Run Burp in interactive mode (Jython Console) -f <FILE> Restore from burp state file upon startup -h ''' from optparse import OptionParser parser = OptionParser() parser.add_option('-i', '--interactive', action='store_true', help='Run Burp in interactive mode (Jython Console)') parser.add_option('-f', '--file', metavar='FILE', help='Restore Burp state from FILE on startup') parser.add_option('-P', '--python-path', default='', help='Set PYTHONPATH used by Jython') parser.add_option('--disable-reloading', action='store_true', help='Disable hot-reloading when a file is changed') opt, args = parser.parse_args(list(args)) if opt.interactive: from java.util import Properties pre_properties = System.getProperties() pre_properties['python.console'] = 'org.python.util.ReadlineConsole' post_properties = Properties() if opt.python_path: post_properties['python.path'] = opt.python_path PythonInterpreter.initialize(pre_properties, post_properties, sys.argv[1:]) self.console = JLineConsole() self.console.exec('import __builtin__ as __builtins__') self.console.exec('from gds.burp import HttpRequest, HttpResponse') self.console.set('Burp', self) sys.stderr.write('Launching interactive session...\n') ConsoleThread(self.console).start() self.opt, self.args = opt, args return def applicationClosing(self): ''' This method is invoked immediately before Burp Suite exits. ''' return def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks if self.opt.file: if os.path.isfile(self.opt.file): self.restoreState(self.opt.file) self.issueAlert('restored state from %s' % (self.opt.file,)) else: self.issueAlert('could not restore state from %s:' 'file does not exist' % (self.opt.file,)) if self.opt.interactive: ConsoleMenu(_burp=self) if not self.opt.disable_reloading: self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('burp extender ready...') return def _check_cb(self): if hasattr(self, '_callbacks'): return getattr(self, '_callbacks') def _check_and_callback(self, method, *args): cb = self._check_cb() if not hasattr(cb, method.__name__): raise Exception("%s not available in your version of Burp" % ( method.__name__,)) return getattr(cb, method.__name__)(*args) cb = property(_check_cb) @callback def makeHttpRequest(self, host, port, useHttps, request): return @callback def sendToRepeater(self, host, port, useHttps, request, tabCaption): return @callback def sendToIntruder(self, host, port, useHttps, request, *args): return @callback def sendToSpider(self, url): return @callback def doActiveScan(self, host, port, useHttps, request, *args): return @callback def doPassiveScan(self, host, port, useHttps, request, response): return @callback def getScanIssues(self, urlPrefix): return def registerMenuItem(self, menuItemCaption, menuItemHandler): ''' This method can be used to register a new menu item which will appear on the various context menus that are used throughout Burp Suite to handle user-driven actions. :param menuItemCaption: The caption to be displayed on the menu item. :param menuItemHandler: The handler to be invoked when the user clicks on the menu item. ''' # don't monitor objects initialized in the interpreter if menuItemHandler.__module__ != '__main__': _module = menuItemHandler.__module__ _filename = sys.modules[_module].__file__ _class = menuItemHandler.__class__.__name__ self.monitoring.append({ 'filename': _filename.replace('$py.class', '.py'), 'class': _class, 'module': _module, 'type': 'IMenuItemHandler', 'instance': weakref.ref(menuItemHandler), }) self._check_and_callback( self.registerMenuItem, menuItemCaption, menuItemHandler) return def getProxyHistory(self, *args): ''' This method returns a generator of all items in the proxy history. :params *args: Optional strings to match against url. ''' if args: matchers = [re.compile(arg) for arg in args] for request in self._check_and_callback(self.getProxyHistory): for matcher in matchers: if matcher.search(request.getUrl().toString()): yield HttpRequest(request, _burp=self) else: for request in self._check_and_callback(self.getProxyHistory): yield HttpRequest(request, _burp=self) @callback def addToSiteMap(self, item): return def getSiteMap(self, *urlPrefixes): ''' This method returns a generator of details of items in the site map. :params *urlPrefixes: Optional URL prefixes, in order to extract a specific subset of the site map. The method performs a simple case-sensitive text match, returning all site map items whose URL begins with the specified prefix. If this parameter is null, the entire site map is returned. ''' for urlPrefix in urlPrefixes: for item in self._check_and_callback(self.getSiteMap, urlPrefix): yield HttpRequest(item, _burp=self) @callback def excludeFromScope(self, url): return @callback def includeInScope(self, url): return @callback def isInScope(self, url): return @callback def issueAlert(self, message): ''' This method can be used to display a specified message in the Burp Suite alerts tab. :param message: The alert message to display. ''' return def restoreState(self, filename): ''' This method can be used to restore Burp's state from a specified saved state file. :param filename: The filename containing Burp's saved state. ''' return self._check_and_callback(self.restoreState, File(filename)) def saveState(self, filename): ''' This method can be used to save Burp's state to a specified file. This method blocks until the save operation is completed, and must not be called from the event thread. :param filename: The filename to save Burp's state in. ''' return self._check_and_callback(self.saveState, File(filename)) @callback def loadConfig(self, config): ''' This method causes Burp to load a new configuration from a dictionary of key/value pairs provided. Any settings not specified in the dict will be restored to their default values. To selectively update only some settings and leave the rest unchanged, you should first call saveConfig to obtain Burp's current configuration, modify the relevant items in the dict, and then call loadConfig with the same dict. :param config: A dict of key/value pairs to use as Burp's new configuration. ''' return def saveConfig(self): ''' This method causes Burp to return its current configuration as a dictionary of key/value pairs. ''' return dict(self._check_and_callback(self.saveConfig)) @callback def setProxyInterceptionEnabled(self, enabled): ''' This method sets the interception mode for Burp Proxy. :param enabled: Indicates whether interception of proxy messages should be enabled. ''' return def getBurpVersion(self): ''' This method retrieves information about the version of Burp in which the extension is running. It can be used by extensions to dynamically adjust their behavior depending on the functionality and APIs supported by the current version. ''' return list(self._check_and_callback(self.getBurpVersion)) def exitSuite(self, promptUser=False): ''' This method can be used to shut down Burp programmatically, with an optional prompt to the user. If the method returns, the user cancelled the shutdown prompt. :param promptUser: Indicates whether to prompt the user to confirm the shutdown (default is False: no prompt). ''' if promptUser is True: return self._check_and_callback(self.exitSuite, True) return self._check_and_callback(self.exitSuite, False)
class BurpExtender(IBurpExtender, ComponentManager): _components = ConfigSection('components', '') _menus = ConfigSection('menus', '') def __init__(self): ComponentManager.__init__(self) self.log = logging.getLogger(self.__class__.__name__) self.monitoring = {} def __repr__(self): return '<BurpExtender at %#x>' % (id(self),) def _monitor_item(self, obj): # don't monitor objects initialized in the interpreter if obj.__module__ == '__main__': return mod = obj.__module__ cls = obj.__class__.__name__ # Monitor the actual configuration file rather than the # module the Configuration class is defined in if isinstance(obj, Configuration): filename = obj.filename elif isinstance(obj, (Component, IMenuItemHandler)): filename = inspect.getsourcefile(obj.__class__) elif isinstance(obj, type): filename = inspect.getsourcefile(obj) monitoring = self.monitoring.setdefault(filename, []) monitoring.append({ 'class': cls, 'instance': weakref.ref(obj), 'module': mod, }) return def componentActivated(self, component): self.log.debug('Activating component: %r', component) component.burp = self component.config = self.config component.log = self.log return def setCommandLineArgs(self, args): ''' This method is invoked immediately after the implementation's constructor to pass any command-line arguments that were passed to Burp Suite on startup. The following command-line options have been made available: -i, --interactive Run Burp in interactive mode (Jython Console) -f <FILE> Restore from burp state file upon startup -d, --debug Set log level to DEBUG -v, --verbose Set log level to INFO -C, --config Specify an alternate config (default: burp.ini) --disable-reloading Disable monitoring of plugins for changes -h ''' from optparse import OptionParser parser = OptionParser() parser.add_option('-i', '--interactive', action='store_true', help='Run Burp in interactive mode (Jython Console)') parser.add_option('-f', '--file', metavar='FILE', help='Restore Burp state from FILE on startup') parser.add_option('-d', '--debug', action='store_true', help='Set log level to DEBUG') parser.add_option('-v', '--verbose', action='store_true', help='Set log level to INFO') parser.add_option('-P', '--python-path', default='', help='Set PYTHONPATH used by Jython') parser.add_option('-C', '--config', default='burp.ini', help='Specify alternate jython-burp config file') parser.add_option('--disable-reloading', action='store_true', help='Disable hot-reloading when a file is changed') opt, args = parser.parse_args(list(args)) if opt.debug: logging.basicConfig( filename='jython-burp.log', format='%(asctime)-15s - %(levelname)s - %(message)s', level=logging.DEBUG) elif opt.verbose: logging.basicConfig( filename='jython-burp.log', format='%(asctime)-15s - %(levelname)s - %(message)s', level=logging.INFO) self.config = Configuration(os.path.abspath(opt.config)) if opt.interactive: from java.util import Properties pre_properties = System.getProperties() pre_properties['python.console'] = 'org.python.util.ReadlineConsole' post_properties = Properties() if opt.python_path: post_properties['python.path'] = opt.python_path PythonInterpreter.initialize( pre_properties, post_properties, sys.argv[1:]) self.console = JLineConsole() self.console.exec('import __builtin__ as __builtins__') self.console.exec('from gds.burp import HttpRequest, HttpResponse') self.console.set('Burp', self) sys.stderr.write('Launching interactive session...\n') ConsoleThread(self.console).start() self.opt, self.args = opt, args return def applicationClosing(self): ''' This method is invoked immediately before Burp Suite exits. ''' self.log.debug('Shutting down Burp') return def registerExtenderCallbacks(self, callbacks): ''' This method is invoked on startup. ''' self._callbacks = callbacks if self.opt.file: if os.path.isfile(self.opt.file): self.restoreState(self.opt.file) self.issueAlert('Restored state from %s' % (self.opt.file,)) else: self.issueAlert('Could not restore state from %s:' 'file does not exist' % (self.opt.file,)) for module, _ in self._menus.options(): if self._menus.getbool(module) is True: for menu in _get_menus(module): menu(self) for component, _ in self._components.options(): if self._components.getbool(component) is True: _get_plugins(component) if not self.opt.disable_reloading: self._monitor_item(self.config) self.monitor = PluginMonitorThread(self) self.monitor.start() self.issueAlert('Burp extender ready...') return def _check_cb(self): if hasattr(self, '_callbacks'): return getattr(self, '_callbacks') def _check_and_callback(self, method, *args): cb = self._check_cb() if not hasattr(cb, method.__name__): raise Exception("%s not available in your version of Burp" % ( method.__name__,)) return getattr(cb, method.__name__)(*args) cb = property(_check_cb) @callback def makeHttpRequest(self, host, port, useHttps, request): return @callback def sendToRepeater(self, host, port, useHttps, request, tabCaption): return @callback def sendToIntruder(self, host, port, useHttps, request, *args): return @callback def sendToSpider(self, url): return @callback def doActiveScan(self, host, port, useHttps, request, *args): return @callback def doPassiveScan(self, host, port, useHttps, request, response): return @callback def getScanIssues(self, urlPrefix): return def registerMenuItem(self, menuItemCaption, menuItemHandler): ''' This method can be used to register a new menu item which will appear on the various context menus that are used throughout Burp Suite to handle user-driven actions. :param menuItemCaption: The caption to be displayed on the menu item. :param menuItemHandler: The handler to be invoked when the user clicks on the menu item. ''' self._monitor_item(menuItemHandler) self._check_and_callback( self.registerMenuItem, menuItemCaption, menuItemHandler) return def newScanIssue(self, issue): ''' This method is invoked whenever Burp Scanner discovers a new, unique issue, and can be used to perform customised reporting or logging of issues. Plugins should implement the :meth:`~INewScanIssueHandler.newScanIssue` method of the :class:`INewScanIssueHandler` interface to act upon new scan issues as they are identified. :param issue: Details of the new scan issue. ''' return NewScanIssueDispatcher(self).newScanIssue(issue) def processHttpMessage(self, toolName, messageIsRequest, messageInfo): ''' This method is invoked whenever any of Burp's tools makes an HTTP request or receives a response. It allows extensions to intercept and modify the HTTP traffic of all Burp tools. For each request, the method is invoked after the request has been fully processed by the invoking tool and is about to be made on the network. For each response, the method is invoked after the response has been received from the network and before any processing is performed by the invoking tool. Plugins should implement the :meth:`processRequest` and/or :meth:`processResponse` methods of one or more interfaces in :module:`gds.burp.api`. A plugin may implement more than one interface, and implement both `processRequest` and `processResponse` methods. This allows plugins to only hook certain tools in specific scenarios, such as "only hook requests sent via Intruder or Scanner, and only hook responses received via Proxy tool. An example is provided below that only modifies requests as they are made via Repeater and Intruder. .. code-block:: python class MyPlugin(Component): implements(IIntruderRequestHandler, IRepeaterRequestHandler) def processRequest(self, request): # replace all occurrences of 'somestring' in HTTP # request with 'anotherstring'. request.raw = request.raw.replace('somestring', 'anotherstring') ''' return PluginDispatcher(self).processHttpMessage( toolName, messageIsRequest, messageInfo) def getProxyHistory(self, *args): ''' This method returns a generator of all items in the proxy history. :params *args: Optional strings to match against url. ''' if args: matchers = [re.compile(arg) for arg in args] for request in self._check_and_callback(self.getProxyHistory): for matcher in matchers: if matcher.search(request.getUrl().toString()): yield HttpRequest(request, _burp=self) break else: for request in self._check_and_callback(self.getProxyHistory): yield HttpRequest(request, _burp=self) @callback def addToSiteMap(self, item): return def getSiteMap(self, *urlPrefixes): ''' This method returns a generator of details of items in the site map. :params *urlPrefixes: Optional URL prefixes, in order to extract a specific subset of the site map. The method performs a simple case-sensitive text match, returning all site map items whose URL begins with the specified prefix. If this parameter is null, the entire site map is returned. ''' for urlPrefix in urlPrefixes or ('http',): for item in self._check_and_callback(self.getSiteMap, urlPrefix): yield HttpRequest(item, _burp=self) @callback def excludeFromScope(self, url): return @callback def includeInScope(self, url): return @callback def isInScope(self, url): return @callback def issueAlert(self, message): ''' This method can be used to display a specified message in the Burp Suite alerts tab. :param message: The alert message to display. ''' return def restoreState(self, filename): ''' This method can be used to restore Burp's state from a specified saved state file. :param filename: The filename containing Burp's saved state. ''' return self._check_and_callback(self.restoreState, File(filename)) def saveState(self, filename): ''' This method can be used to save Burp's state to a specified file. This method blocks until the save operation is completed, and must not be called from the event thread. :param filename: The filename to save Burp's state in. ''' return self._check_and_callback(self.saveState, File(filename)) @callback def loadConfig(self, config): ''' This method causes Burp to load a new configuration from a dictionary of key/value pairs provided. Any settings not specified in the dict will be restored to their default values. To selectively update only some settings and leave the rest unchanged, you should first call saveConfig to obtain Burp's current configuration, modify the relevant items in the dict, and then call loadConfig with the same dict. :param config: A dict of key/value pairs to use as Burp's new configuration. ''' return def saveConfig(self): ''' This method causes Burp to return its current configuration as a dictionary of key/value pairs. ''' return dict(self._check_and_callback(self.saveConfig)) @callback def setProxyInterceptionEnabled(self, enabled): ''' This method sets the interception mode for Burp Proxy. :param enabled: Indicates whether interception of proxy messages should be enabled. ''' return def getBurpVersion(self): ''' This method retrieves information about the version of Burp in which the extension is running. It can be used by extensions to dynamically adjust their behavior depending on the functionality and APIs supported by the current version. ''' return list(self._check_and_callback(self.getBurpVersion)) def exitSuite(self, promptUser=False): ''' This method can be used to shut down Burp programmatically, with an optional prompt to the user. If the method returns, the user cancelled the shutdown prompt. :param promptUser: Indicates whether to prompt the user to confirm the shutdown (default is False: no prompt). ''' if promptUser is True: return self._check_and_callback(self.exitSuite, True) return self._check_and_callback(self.exitSuite, False)