class BurpExtender(IBurpExtender, IProxyListener, ITab): def getTabCaption(self): ### ITab return NAME def getUiComponent(self): ### ITab return self.settings def registerExtenderCallbacks(self, this_callbacks): ### IBurpExtender global callbacks, helpers global extension_enable global remove_csrf_headers, remove_csrf_params, change_method_to_post global change_ct_to_json, change_ct_to_plain, change_to_get callbacks = this_callbacks helpers = callbacks.getHelpers() callbacks.setExtensionName(NAME) self.settings = JPanel(GridBagLayout()) c = GridBagConstraints() self.extension_enable_box = JCheckBox('Enable extension', extension_enable) self.extension_enable_box.setFont(Font("Serif", Font.BOLD, 20)) self.extension_enable_box.setForeground(Color(0, 0, 153)) c.insets = Insets(5, 5, 5, 5) c.gridx = 0 c.gridy = 0 c.gridwidth = 1 c.weightx = 1 c.fill = GridBagConstraints.NONE c.anchor = GridBagConstraints.WEST self.settings.add(self.extension_enable_box, c) self.remove_csrf_headers_box = JCheckBox('Remove CSRF headers', remove_csrf_params) self.remove_csrf_headers_box.setFont(Font("Serif", Font.BOLD, 20)) self.remove_csrf_headers_box.setForeground(Color(0, 0, 153)) c.insets = Insets(40, 5, 5, 5) c.gridx = 0 c.gridy = 1 self.settings.add(self.remove_csrf_headers_box, c) remove_csrf_headers_box_lbl = JLabel( 'Check to remove headers with CSRF tokens from all requests.') c.insets = Insets(5, 5, 5, 5) c.gridx = 0 c.gridy = 2 self.settings.add(remove_csrf_headers_box_lbl, c) self.remove_csrf_params_box = JCheckBox('Remove CSRF parameters', remove_csrf_params) self.remove_csrf_params_box.setFont(Font("Serif", Font.BOLD, 20)) self.remove_csrf_params_box.setForeground(Color(0, 0, 153)) c.gridx = 0 c.gridy = 3 self.settings.add(self.remove_csrf_params_box, c) remove_csrf_params_box_lbl = JLabel( 'Check to remove URL/body parameters with CSRF tokens from all requests. URL-encoded, multipart, JSON parameters are supported.' ) c.gridx = 0 c.gridy = 4 self.settings.add(remove_csrf_params_box_lbl, c) self.change_method_to_post_box = JCheckBox( 'Change HTTP method to POST', change_method_to_post) self.change_method_to_post_box.setFont(Font("Serif", Font.BOLD, 20)) self.change_method_to_post_box.setForeground(Color(0, 0, 153)) c.gridx = 0 c.gridy = 5 self.settings.add(self.change_method_to_post_box, c) change_method_to_post_lbl = JLabel( 'Check to convert PUT/DELETE/PATCH method to POST in all requests.' ) c.gridx = 0 c.gridy = 6 self.settings.add(change_method_to_post_lbl, c) self.change_ct_to_json_box = JCheckBox('Change media type to json', change_ct_to_json) self.change_ct_to_json_box.setFont(Font("Serif", Font.BOLD, 20)) self.change_ct_to_json_box.setForeground(Color(0, 0, 153)) c.gridx = 0 c.gridy = 7 self.settings.add(self.change_ct_to_json_box, c) change_ct_to_json_lbl = JLabel( 'Check to convert body to json and set Content-Type to application/json in url-encoded requests.' ) c.gridx = 0 c.gridy = 8 self.settings.add(change_ct_to_json_lbl, c) self.change_ct_to_plain_box = JCheckBox( 'Change Content-Type to text/plain', change_ct_to_plain) self.change_ct_to_plain_box.setFont(Font("Serif", Font.BOLD, 20)) self.change_ct_to_plain_box.setForeground(Color(0, 0, 153)) c.gridx = 0 c.gridy = 9 self.settings.add(self.change_ct_to_plain_box, c) change_ct_to_plain_lbl = JLabel( 'Check to set Content-Type to text/plain in request with non-simple media type. Simple media types - application/application/x-www-form-urlencoded, text/plain, multipart/form-data.' ) c.gridx = 0 c.gridy = 10 self.settings.add(change_ct_to_plain_lbl, c) self.change_to_get_box = JCheckBox('Change to GET', change_to_get) self.change_to_get_box.setFont(Font("Serif", Font.BOLD, 20)) self.change_to_get_box.setForeground(Color(0, 0, 153)) c.gridx = 0 c.gridy = 11 self.settings.add(self.change_to_get_box, c) change_to_get_lbl = JLabel( 'Check to convert POST/PUT/DELETE/PATCH url-encoded requests to GET.' ) c.gridx = 0 c.gridy = 12 self.settings.add(change_to_get_lbl, c) callbacks.customizeUiComponent(self.settings) callbacks.addSuiteTab(self) callbacks.registerProxyListener(self) print "Successfully loaded %s v%s" % (NAME, VERSION) def processProxyMessage(self, messageIsRequest, message): ### IProxyListener extension_enable = self.extension_enable_box.isSelected() if not extension_enable: return # Do nothing remove_csrf_headers = self.remove_csrf_headers_box.isSelected() remove_csrf_params = self.remove_csrf_params_box.isSelected() change_method_to_post = self.change_method_to_post_box.isSelected() change_ct_to_json = self.change_ct_to_json_box.isSelected() change_ct_to_plain = self.change_ct_to_plain_box.isSelected() change_to_get = self.change_to_get_box.isSelected() request_response = message.getMessageInfo() request_info = helpers.analyzeRequest(request_response) request_method = request_info.getMethod() if not messageIsRequest or request_method not in [ 'POST', 'PUT', 'DELETE', 'PATCH' ]: return # Do nothing http_service = request_response.getHttpService() request = request_response.getRequest() headers = request_info.getHeaders() parameters = request_info.getParameters() new_headers = headers if remove_csrf_headers: new_headers = filter_headers(headers) # Remove CSRF headers if change_ct_to_plain and request_info.getContentType() not in [ IRequestInfo.CONTENT_TYPE_URL_ENCODED, IRequestInfo.CONTENT_TYPE_MULTIPART ]: for i in range(len(new_headers)): if new_headers[i].lower().startswith( 'content-type'): # Change CT to text/plain new_headers[i] = 'Content-Type: text/plain' if remove_csrf_params: for parameter in parameters: # Remove CSRF parameters from request's body or URL for csrf_param in csrf_params_names: if parameter.getType() != IParameter.PARAM_COOKIE and \ csrf_param.lower() in parameter.getName().lower(): if request_info.getContentType( ) == IRequestInfo.CONTENT_TYPE_MULTIPART: start = parameter.getNameStart() end = parameter.getNameEnd() request = request[:start] + helpers.stringToBytes( "REPLACEMENT") + request[end:] elif parameter.getType() == IParameter.PARAM_JSON: start = parameter.getNameStart() - 1 end = parameter.getValueEnd() + 1 request = request[:start] + request[end:] offset = helpers.analyzeRequest( http_service, request).getBodyOffset() body = request[offset:] body = re.sub(",\s*,", ",", body) body = re.sub("{\s*,", "{", body) body = re.sub(",\s*}", "}", body) request = helpers.buildHttpMessage(headers, body) elif parameter.getType() in [ IParameter.PARAM_URL, IParameter.PARAM_BODY ]: request = helpers.removeParameter( request, parameter) offset = helpers.analyzeRequest(http_service, request).getBodyOffset() body = request[offset:] if change_ct_to_json and request_info.getContentType( ) == IRequestInfo.CONTENT_TYPE_URL_ENCODED: for i in range(len(new_headers)): if new_headers[i].lower().startswith( 'content-type'): # Change to JSON from URL-encoded new_headers[i] = 'Content-Type: application/json' body = safe_bytes_to_string(body) d = dict((k, v if len(v) > 1 else v[0]) for k, v in parse_qs(body).iteritems()) body = dumps(d) if change_method_to_post or (change_to_get and not change_ct_to_json and \ request_info.getContentType() == IRequestInfo.CONTENT_TYPE_URL_ENCODED): for i in range(len(new_headers)): if new_headers[i].startswith("PUT") or new_headers[i].startswith("DELETE") \ or new_headers[i].startswith("PATCH"): new_headers[i] = new_headers[i].replace( request_method, 'POST', 1) break new_request = helpers.buildHttpMessage( new_headers, body) # Create new request with valid Content-Length if change_to_get and not change_ct_to_json and request_info.getContentType( ) == IRequestInfo.CONTENT_TYPE_URL_ENCODED: new_request = helpers.toggleRequestMethod( new_request) # Change any URL-encoded request to GET message.setInterceptAction( IInterceptedProxyMessage.ACTION_FOLLOW_RULES_AND_REHOOK) message.getMessageInfo().setRequest(new_request) message.getMessageInfo().setHighlight('red')
class BurpExtender(IBurpExtender, IExtensionStateListener, IHttpListener, ITab, FocusListener, ActionListener, MouseAdapter): _version = "0.2" _name = "PyRules" _varsStorage = _name + "_vars" _scriptStorage = _name + "_script" _enabled = 0 _vars = {} def registerExtenderCallbacks(self, callbacks): print "Load:" + self._name + " " + self._version self.callbacks = callbacks self.helpers = callbacks.helpers #Create Tab layout self.jVarsPane = JTextPane() self.jVarsPane.setFont(Font('Monospaced', Font.PLAIN, 11)) self.jVarsPane.addFocusListener(self) self.jMenuPanel = JPanel() self.jLeftUpPanel = JPanel() self.jEnable = JCheckBox() self.jEnable.setFont(Font('Monospaced', Font.BOLD, 11)) self.jEnable.setForeground(Color(0, 0, 204)) self.jEnable.setText(self._name) self.jEnable.addActionListener(self) self.jDocs = JLabel() self.jDocs.setFont(Font('Monospaced', Font.PLAIN, 11)) self.jDocs.setForeground(Color(51, 102, 255)) self.jDocs.setText(Strings.docs_titel) self.jDocs.setToolTipText(Strings.docs_tooltip) self.jDocs.addMouseListener(self) self.jConsoleText = JTextArea() self.jConsoleText.setFont(Font('Monospaced', Font.PLAIN, 10)) self.jConsoleText.setBackground(Color(244, 246, 247)) self.jConsoleText.setEditable(0) self.jConsoleText.setWrapStyleWord(1) self.jConsoleText.setRows(10) self.jScrollConsolePane = JScrollPane() self.jScrollConsolePane.setViewportView(self.jConsoleText) #set initial text self.jConsoleText.setText(Strings.console_disable) self.jMenuPanelLayout = GroupLayout(self.jMenuPanel) self.jMenuPanel.setLayout(self.jMenuPanelLayout) self.jMenuPanelLayout.setHorizontalGroup( self.jMenuPanelLayout.createParallelGroup( GroupLayout.Alignment.LEADING).addGroup( self.jMenuPanelLayout.createSequentialGroup().addComponent( self.jEnable).addPreferredGap( LayoutStyle.ComponentPlacement.RELATED, 205, 32767).addComponent(self.jDocs))) self.jMenuPanelLayout.setVerticalGroup( self.jMenuPanelLayout.createParallelGroup( GroupLayout.Alignment.LEADING).addGroup( self.jMenuPanelLayout.createSequentialGroup().addGroup( self.jMenuPanelLayout.createParallelGroup( GroupLayout.Alignment.BASELINE).addComponent( self.jEnable).addComponent(self.jDocs)).addGap( 0, 7, 32767))) self.jConsolePane = JPanel() self.jConsoleLayout = GroupLayout(self.jConsolePane) self.jConsolePane.setLayout(self.jConsoleLayout) self.jConsoleLayout.setHorizontalGroup( self.jConsoleLayout.createParallelGroup( GroupLayout.Alignment.LEADING).addComponent( self.jScrollConsolePane)) self.jConsoleLayout.setVerticalGroup( self.jConsoleLayout.createParallelGroup( GroupLayout.Alignment.LEADING).addGroup( GroupLayout.Alignment.TRAILING, self.jConsoleLayout.createSequentialGroup().addComponent( self.jScrollConsolePane, GroupLayout.DEFAULT_SIZE, 154, 32767).addContainerGap())) self.jLeftUpPanelLayout = GroupLayout(self.jLeftUpPanel) self.jLeftUpPanel.setLayout(self.jLeftUpPanelLayout) self.jLeftUpPanelLayout.setHorizontalGroup( self.jLeftUpPanelLayout.createParallelGroup( GroupLayout.Alignment.LEADING).addComponent( self.jConsolePane, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, 32767).addComponent(self.jMenuPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) self.jLeftUpPanelLayout.setVerticalGroup( self.jLeftUpPanelLayout. createParallelGroup(GroupLayout.Alignment.LEADING).addGroup( GroupLayout.Alignment.TRAILING, self.jLeftUpPanelLayout.createSequentialGroup().addComponent( self.jMenuPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE).addPreferredGap( LayoutStyle.ComponentPlacement.RELATED).addComponent( self.jConsolePane, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, 32767))) self.jScrollpaneLeftDown = JScrollPane() self.jScrollpaneLeftDown.setViewportView(self.jVarsPane) self.jSplitPaneLeft = JSplitPane(JSplitPane.VERTICAL_SPLIT, self.jLeftUpPanel, self.jScrollpaneLeftDown) self.jSplitPaneLeft.setDividerLocation(300) self.jScriptPane = JTextPane() self.jScriptPane.setFont(Font('Monospaced', Font.PLAIN, 11)) self.jScriptPane.addMouseListener(self) self.JScrollPaneRight = JScrollPane() self.JScrollPaneRight.setViewportView(self.jScriptPane) self.jSplitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, self.jSplitPaneLeft, self.JScrollPaneRight) self.jSplitPane.setDividerLocation(400) #Load saved saved settings ##Load vars vars = callbacks.loadExtensionSetting(self._varsStorage) if vars: vars = base64.b64decode(vars) else: # try to load the example try: with open("examples/Simple-CSRF-vars.py") as fvars: vars = fvars.read() # load the default text except: vars = Strings.vars ## initiate the persistant variables locals_ = {} try: exec(vars, {}, locals_) except Exception as e: print e self._vars = locals_ ## update the vars screen self.jVarsPane.document.insertString(self.jVarsPane.document.length, vars, SimpleAttributeSet()) ##Load script script = callbacks.loadExtensionSetting(self._scriptStorage) if script: script = base64.b64decode(script) else: # try to load the example try: with open("examples/Simple-CSRF-script.py") as fscript: script = fscript.read() # load the default text except: script = Strings.script ## compile the rules self._script = script self._code = '' try: self._code = compile(script, '<string>', 'exec') except Exception as e: print( '{}\nReload extension after you correct the error.'.format(e)) ## update the rules screen self.jScriptPane.document.insertString( self.jScriptPane.document.length, script, SimpleAttributeSet()) #Register Extension callbacks.customizeUiComponent(self.getUiComponent()) callbacks.addSuiteTab(self) callbacks.registerExtensionStateListener(self) callbacks.registerHttpListener(self) self.jScriptPane.requestFocus() def getUiComponent(self): return self.jSplitPane def getTabCaption(self): return self._name def actionPerformed(self, event): #Check box was clicked if self.jEnable == event.getSource(): if self._enabled == 1: self._enabled = 0 # console content shows help self.jConsoleText.setText(Strings.console_disable) else: self._enabled = 1 # console content displays the current persistent variable state self.jConsoleText.setText(Strings.console_state) self.jConsoleText.append(pformat(self._vars)) self.jConsoleText.append(Strings.extra_line) self.jConsoleText.append(Strings.console_log) return def mouseClicked(self, event): if event.source == self.jDocs: uri = URI.create("https://github.com/DanNegrea/PyRules") if uri and Desktop.isDesktopSupported() and Desktop.getDesktop( ).isSupported(Desktop.Action.BROWSE): Desktop.getDesktop().browse(uri) return def focusGained(self, event): if self.jConsolePane == event.getSource(): pass #print "Status pane gained focus" #debug return def focusLost(self, event): #Reinitialize the persistent values if self.jVarsPane == event.getSource(): # get the text from the pane end = self.jVarsPane.document.length vars = self.jVarsPane.document.getText(0, end) # compute the new values locals_ = {} exec(vars, {}, locals_) self._vars = locals_ # display the new result in console self.jConsoleText.append(Strings.console_state) self.jConsoleText.append(pformat(self._vars)) self.jConsoleText.append(Strings.extra_line) self.jConsoleText.append(Strings.console_log) # scroll to bottom verticalScrollBar = self.jScrollConsolePane.getVerticalScrollBar() verticalScrollBar.setValue(verticalScrollBar.getMaximum()) return def extensionUnloaded(self): try: #Save the latestest vars and script text ## save vars end = self.jVarsPane.document.length vars = self.jVarsPane.document.getText(0, end) vars = base64.b64encode(vars) self.callbacks.saveExtensionSetting(self._varsStorage, vars) ## save script/rules end = self.jScriptPane.document.length script = self.jScriptPane.document.getText(0, end) script = base64.b64encode(script) self.callbacks.saveExtensionSetting(self._scriptStorage, script) except Exception: traceback.print_exc(file=self.callbacks.getStderr()) print "Unloaded" #debug return def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo): if self._enabled == 0: return try: locals_ = { 'extender': self, 'callbacks': self.callbacks, 'helpers': self.helpers, 'toolFlag': toolFlag, 'messageIsRequest': messageIsRequest, 'messageInfo': messageInfo, 'log': self.log } # add the _vars as gloval variables locals_ = dict(locals_, **self._vars) # execute the script/rules try: exec(self.getCode, {}, locals_) # catch exit() call inside the rule except SystemExit: pass # update the persistant variables by searching the local variables with the same name for key in self._vars: # assumption self._vars dictionary is smaller than locals_ if key in locals_: self._vars[key] = locals_[key] except Exception: traceback.print_exc(file=self.callbacks.getStderr()) return #Returns the compiled script @property def getCode(self): end = self.jScriptPane.document.length script = self.jScriptPane.document.getText(0, end) # if the script hasn't changed return the already compile text if script == self._script: return self._code self._script = script # compile, store and return the result self._code = compile(script, '<string>', 'exec') return self._code #Log the information into the console screen def log(self, obj): # if string just append. else use pformat from pprint if isinstance(obj, str): self.jConsoleText.append(obj + "\n") else: self.jConsoleText.append(pformat(obj) + "\n") # scroll to bottom verticalScrollBar = self.jScrollConsolePane.getVerticalScrollBar() verticalScrollBar.setValue(verticalScrollBar.getMaximum()) return