├── README.md ├── editortab.png ├── java ├── .gitignore ├── BurpExtender.java ├── build.gradle └── settings.gradle ├── python └── CustomEditorTab.py ├── ruby └── CustomEditorTab.rb └── server ├── Demo.aspx └── server.js /README.md: -------------------------------------------------------------------------------- 1 | # Sample Burp Suite extension: custom editor tab 2 | 3 | This extension demonstrates how you can extend Burp's own HTTP message editor 4 | to handle the display and editing of unsupported data formats. This capability 5 | can let you handle custom serialization implemented by specific applications, 6 | or any other data format that Burp does not natively support. 7 | 8 | In the past, some extensions have handled unsupported serialization formats by 9 | hooking into Burp's HTTP stack, and modifying incoming and outgoing messages, 10 | in order to unpack and repack the serialized data. Although this approach can 11 | work, it is quite restricted in the type of data it can handle. And it is also 12 | inelegant: it would be preferable to customize Burp to understand the custom 13 | format itself, rather than tampering with the integrity of HTTP messages. 14 | 15 | The [extender API](https://portswigger.net/burp/extender/) lets you add custom 16 | tabs to Burp's HTTP message editor. When a message is about to be displayed, 17 | Burp will ask the tab whether it can handle the message. If so, the custom tab 18 | will be shown in the editor, and can support rendering and editing of the 19 | message within its own UI: 20 | 21 |  22 | 23 | The sample extension uses an artificially simple serialization format: the 24 | serialized data is simply Base64-encoded within a request parameter. This 25 | example was chosen so as to keep the code that handles the serialization as 26 | simple as possible. But the format itself isn't the point: what matters is that 27 | you can now easily extend Burp to understand any format that you may encounter 28 | in a test. 29 | 30 | As well as the new API for adding message editor tabs, this example also makes 31 | use of Burp's new helper methods, to carry out common tasks such as parsing and 32 | updating request parameters, encoding and decoding data in different formats, 33 | and conversion of data between String and byte forms. 34 | 35 | This repository includes source code for Java, Python and Ruby. It also includes 36 | a server (for ASP.NET and NodeJS) that encodes and decodes base64 data. 37 | 38 | [Really astute testers might spot a deliberate vulnerability in the sample 39 | server. More on that soon.] 40 | -------------------------------------------------------------------------------- /editortab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/example-custom-editor-tab/8523d25937ca047264db3323d0419a46d6824295/editortab.png -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | -------------------------------------------------------------------------------- /java/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.awt.Component; 4 | 5 | public class BurpExtender implements IBurpExtender, IMessageEditorTabFactory 6 | { 7 | private IBurpExtenderCallbacks callbacks; 8 | private IExtensionHelpers helpers; 9 | 10 | // 11 | // implement IBurpExtender 12 | // 13 | 14 | @Override 15 | public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) 16 | { 17 | // keep a reference to our callbacks object 18 | this.callbacks = callbacks; 19 | 20 | // obtain an extension helpers object 21 | helpers = callbacks.getHelpers(); 22 | 23 | // set our extension name 24 | callbacks.setExtensionName("Serialized input editor"); 25 | 26 | // register ourselves as a message editor tab factory 27 | callbacks.registerMessageEditorTabFactory(this); 28 | } 29 | 30 | // 31 | // implement IMessageEditorTabFactory 32 | // 33 | 34 | @Override 35 | public IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable) 36 | { 37 | // create a new instance of our custom editor tab 38 | return new Base64InputTab(controller, editable); 39 | } 40 | 41 | // 42 | // class implementing IMessageEditorTab 43 | // 44 | 45 | class Base64InputTab implements IMessageEditorTab 46 | { 47 | private boolean editable; 48 | private ITextEditor txtInput; 49 | private byte[] currentMessage; 50 | 51 | public Base64InputTab(IMessageEditorController controller, boolean editable) 52 | { 53 | this.editable = editable; 54 | 55 | // create an instance of Burp's text editor, to display our deserialized data 56 | txtInput = callbacks.createTextEditor(); 57 | txtInput.setEditable(editable); 58 | } 59 | 60 | // 61 | // implement IMessageEditorTab 62 | // 63 | 64 | @Override 65 | public String getTabCaption() 66 | { 67 | return "Serialized input"; 68 | } 69 | 70 | @Override 71 | public Component getUiComponent() 72 | { 73 | return txtInput.getComponent(); 74 | } 75 | 76 | @Override 77 | public boolean isEnabled(byte[] content, boolean isRequest) 78 | { 79 | // enable this tab for requests containing a data parameter 80 | return isRequest && null != helpers.getRequestParameter(content, "data"); 81 | } 82 | 83 | @Override 84 | public void setMessage(byte[] content, boolean isRequest) 85 | { 86 | if (content == null) 87 | { 88 | // clear our display 89 | txtInput.setText(null); 90 | txtInput.setEditable(false); 91 | } 92 | else 93 | { 94 | // retrieve the data parameter 95 | IParameter parameter = helpers.getRequestParameter(content, "data"); 96 | 97 | // deserialize the parameter value 98 | txtInput.setText(helpers.base64Decode(helpers.urlDecode(parameter.getValue()))); 99 | txtInput.setEditable(editable); 100 | } 101 | 102 | // remember the displayed content 103 | currentMessage = content; 104 | } 105 | 106 | @Override 107 | public byte[] getMessage() 108 | { 109 | // determine whether the user modified the deserialized data 110 | if (txtInput.isTextModified()) 111 | { 112 | // reserialize the data 113 | byte[] text = txtInput.getText(); 114 | String input = helpers.urlEncode(helpers.base64Encode(text)); 115 | 116 | // update the request with the new parameter value 117 | return helpers.updateParameter(currentMessage, helpers.buildParameter("data", input, IParameter.PARAM_BODY)); 118 | } 119 | else return currentMessage; 120 | } 121 | 122 | @Override 123 | public boolean isModified() 124 | { 125 | return txtInput.isTextModified(); 126 | } 127 | 128 | @Override 129 | public byte[] getSelectedData() 130 | { 131 | return txtInput.getSelectedText(); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | compile 'net.portswigger.burp.extender:burp-extender-api:1.7.13' 9 | } 10 | 11 | sourceSets { 12 | main { 13 | java { 14 | srcDir '.' 15 | } 16 | } 17 | } 18 | 19 | task fatJar(type: Jar) { 20 | baseName = project.name + '-all' 21 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 22 | with jar 23 | } 24 | -------------------------------------------------------------------------------- /java/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'CustomEditorTab' 2 | -------------------------------------------------------------------------------- /python/CustomEditorTab.py: -------------------------------------------------------------------------------- 1 | from burp import IBurpExtender 2 | from burp import IMessageEditorTabFactory 3 | from burp import IMessageEditorTab 4 | from burp import IParameter 5 | 6 | class BurpExtender(IBurpExtender, IMessageEditorTabFactory): 7 | 8 | # 9 | # implement IBurpExtender 10 | # 11 | 12 | def registerExtenderCallbacks(self, callbacks): 13 | # keep a reference to our callbacks object 14 | self._callbacks = callbacks 15 | 16 | # obtain an extension helpers object 17 | self._helpers = callbacks.getHelpers() 18 | 19 | # set our extension name 20 | callbacks.setExtensionName("Serialized input editor") 21 | 22 | # register ourselves as a message editor tab factory 23 | callbacks.registerMessageEditorTabFactory(self) 24 | 25 | # 26 | # implement IMessageEditorTabFactory 27 | # 28 | 29 | def createNewInstance(self, controller, editable): 30 | # create a new instance of our custom editor tab 31 | return Base64InputTab(self, controller, editable) 32 | 33 | # 34 | # class implementing IMessageEditorTab 35 | # 36 | 37 | class Base64InputTab(IMessageEditorTab): 38 | def __init__(self, extender, controller, editable): 39 | self._extender = extender 40 | self._editable = editable 41 | 42 | # create an instance of Burp's text editor, to display our deserialized data 43 | self._txtInput = extender._callbacks.createTextEditor() 44 | self._txtInput.setEditable(editable) 45 | 46 | # 47 | # implement IMessageEditorTab 48 | # 49 | 50 | def getTabCaption(self): 51 | return "Serialized input" 52 | 53 | def getUiComponent(self): 54 | return self._txtInput.getComponent() 55 | 56 | def isEnabled(self, content, isRequest): 57 | # enable this tab for requests containing a data parameter 58 | return isRequest and not self._extender._helpers.getRequestParameter(content, "data") is None 59 | 60 | def setMessage(self, content, isRequest): 61 | if content is None: 62 | # clear our display 63 | self._txtInput.setText(None) 64 | self._txtInput.setEditable(False) 65 | 66 | else: 67 | # retrieve the data parameter 68 | parameter = self._extender._helpers.getRequestParameter(content, "data") 69 | 70 | # deserialize the parameter value 71 | self._txtInput.setText(self._extender._helpers.base64Decode(self._extender._helpers.urlDecode(parameter.getValue()))) 72 | self._txtInput.setEditable(self._editable) 73 | 74 | # remember the displayed content 75 | self._currentMessage = content 76 | 77 | def getMessage(self): 78 | # determine whether the user modified the deserialized data 79 | if self._txtInput.isTextModified(): 80 | # reserialize the data 81 | text = self._txtInput.getText() 82 | input = self._extender._helpers.urlEncode(self._extender._helpers.base64Encode(text)) 83 | 84 | # update the request with the new parameter value 85 | return self._extender._helpers.updateParameter(self._currentMessage, self._extender._helpers.buildParameter("data", input, IParameter.PARAM_BODY)) 86 | 87 | else: 88 | return self._currentMessage 89 | 90 | def isModified(self): 91 | return self._txtInput.isTextModified() 92 | 93 | def getSelectedData(self): 94 | return self._txtInput.getSelectedText() 95 | -------------------------------------------------------------------------------- /ruby/CustomEditorTab.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | java_import 'burp.IBurpExtender' 3 | java_import 'burp.IMessageEditorTab' 4 | java_import 'burp.IMessageEditorTabFactory' 5 | java_import 'burp.IParameter' 6 | 7 | class BurpExtender 8 | include IBurpExtender, IMessageEditorTabFactory 9 | 10 | attr_accessor :callbacks, :helpers 11 | 12 | def registerExtenderCallbacks(callbacks) 13 | # keep a reference to our callbacks object 14 | @callbacks = callbacks 15 | 16 | # obtain an extension helpers object 17 | @helpers = callbacks.getHelpers 18 | 19 | # set our extension name 20 | callbacks.setExtensionName "Serialized input editor" 21 | 22 | # register ourselves as a message editor tab factory 23 | callbacks.registerMessageEditorTabFactory(self) 24 | 25 | return 26 | end 27 | 28 | # 29 | # implement IMessageEditorTabFactory 30 | # 31 | 32 | def createNewInstance(controller, editable) 33 | # create a new instance of our custom editor tab 34 | Base64InputTab.new self, controller, editable 35 | end 36 | end 37 | 38 | # 39 | # class implementing IMessageEditorTab 40 | # 41 | 42 | class Base64InputTab 43 | include IMessageEditorTab 44 | 45 | def initialize(extender, controller, editable) 46 | @extender = extender 47 | @editable = editable 48 | 49 | # create an instance of Burp's text editor, to display our deserialized data 50 | @txtInput = extender.callbacks.createTextEditor 51 | @txtInput.setEditable editable 52 | end 53 | 54 | # 55 | # implement IMessageEditorTab 56 | # 57 | 58 | def getTabCaption() 59 | "Serialized input" 60 | end 61 | 62 | def getUiComponent() 63 | @txtInput.getComponent 64 | end 65 | 66 | def isEnabled(content, isRequest) 67 | # enable this tab for requests containing a data parameter 68 | isRequest and not @extender.helpers.getRequestParameter(content, "data").nil? 69 | end 70 | 71 | def setMessage(content, isRequest) 72 | if content.nil? 73 | # clear our display 74 | @txtInput.setText nil 75 | @txtInput.setEditable false 76 | else 77 | # retrieve the data parameter 78 | parameter = @extender.helpers.getRequestParameter(content, "data") 79 | 80 | # deserialize the parameter value 81 | @txtInput.setText @extender.helpers.base64Decode(@extender.helpers.urlDecode parameter.getValue) 82 | @txtInput.setEditable @editable 83 | end 84 | 85 | # remember the displayed content 86 | @currentMessage = content 87 | 88 | return 89 | end 90 | 91 | def getMessage() 92 | # determine whether the user modified the deserialized data 93 | if @txtInput.isTextModified 94 | # reserialize the data 95 | text = @txtInput.getText 96 | input = @extender.helpers.urlEncode @extender.helpers.base64Encode(text) 97 | 98 | # update the request with the new parameter value 99 | @extender.helpers.updateParameter @currentMessage, @extender.helpers.buildParameter("data", input, IParameter.PARAM_BODY) 100 | else 101 | @currentMessage 102 | end 103 | end 104 | 105 | def isModified() 106 | @txtInput.isTextModified 107 | end 108 | 109 | def getSelectedData() 110 | @txtInput.getSelectedText 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /server/Demo.aspx: -------------------------------------------------------------------------------- 1 | <%@ Page Language="C#" %> 2 | 3 | <% 4 | string data = Request.Form["data"]; 5 | if (data != null) 6 | { 7 | data = Encoding.ASCII.GetString(Convert.FromBase64String(data)); 8 | data = HttpUtility.ParseQueryString(data)["input"]; 9 | output.InnerHtml = "Input received: " + data; 10 | } 11 | %> 12 | 13 | 14 | 15 |
16 |