├── Argonaut.py ├── BappDescription.html ├── BappManifest.bmf ├── JSONDecoder.py └── README.md /Argonaut.py: -------------------------------------------------------------------------------- 1 | # Burp Extension - Argonaut 2 | # Copyright : Michal Melewski 3 | 4 | # Process all request parameters 5 | # and try to find if they are echoed back in response 6 | 7 | # Version 0.4 8 | # Transformations finally working 9 | 10 | # Version 0.3 11 | # Parsing moved to separate class 12 | 13 | # Version 0.2 14 | # Small fix due to NullPointerException 15 | 16 | 17 | # Burp imports 18 | from burp import IBurpExtender 19 | from burp import IMessageEditorTabFactory 20 | from burp import IMessageEditorTab 21 | from burp import IParameter 22 | 23 | # Java imports 24 | from javax.swing import JTable 25 | from javax.swing import JScrollPane 26 | from javax.swing.table import AbstractTableModel 27 | 28 | # Python imports 29 | from urllib import unquote 30 | 31 | # Consts 32 | MIN_PARAM_LEN = 3 33 | 34 | ## Transformations tables 35 | TRANSFORMATIONS = [ 36 | { "name": "plain", "table": []}, 37 | { "name": "jinja", "table": [ 38 | ('&', '&'), 39 | ('<', '<'), 40 | ('>', '>'), 41 | ('"', '"'), 42 | ('\'', ''')] 43 | }, 44 | ] 45 | 46 | 47 | class BurpExtender(IBurpExtender, IMessageEditorTabFactory): 48 | def registerExtenderCallbacks(self, callbacks): 49 | self._callbacks = callbacks 50 | self._helpers = callbacks.getHelpers() 51 | 52 | callbacks.setExtensionName('Argonaut') 53 | callbacks.registerMessageEditorTabFactory(self) 54 | 55 | return 56 | 57 | def createNewInstance(self, controller, editable): 58 | return ArgonautTab(self, controller, editable) 59 | 60 | 61 | class ArgonautTab(IMessageEditorTab): 62 | def __init__(self, extender, controller, editable): 63 | self._extender = extender 64 | self._controller = controller 65 | self._helpers = extender._helpers 66 | 67 | # Data container 68 | self._dataContainer = ArgonautData() 69 | 70 | # Argonaut Parser 71 | self._argoParser = ArgonautParser(self._dataContainer) 72 | 73 | # Burp View (table) 74 | self._argoTable = ArgonautTable(self._dataContainer) 75 | self._tablePane = JScrollPane(self._argoTable) 76 | 77 | return 78 | 79 | def getTabCaption(self): 80 | return "Argonaut" 81 | 82 | def getUiComponent(self): 83 | return self._tablePane 84 | 85 | def isEnabled(self, content, isRequest): 86 | """Enable if parameters were present. Including cookies""" 87 | if isRequest: 88 | return False 89 | else: 90 | raw_req = self._controller.getRequest() 91 | 92 | if not raw_req: 93 | return False 94 | 95 | req = self._helpers.analyzeRequest(raw_req) 96 | params = req.getParameters() 97 | 98 | if params.isEmpty(): 99 | return False 100 | 101 | return True 102 | 103 | def setMessage(self, content, isRequest): 104 | if isRequest: 105 | return 106 | 107 | # Extract params from pair 108 | req = self._helpers.analyzeRequest(self._controller.getRequest()) 109 | params = req.getParameters() 110 | 111 | # Grab response 112 | rsp = self._helpers.analyzeResponse(content) 113 | body = content[rsp.getBodyOffset():].tostring() 114 | 115 | # Parse 116 | self._dataContainer.reset() 117 | self._argoParser.parse(params, body) 118 | self._dataContainer.fireTableDataChanged() 119 | 120 | return 121 | 122 | def isModified(self): 123 | return False 124 | 125 | 126 | ## ArgonautParser 127 | class ArgonautParser: 128 | def __init__(self, dataContainer): 129 | self.container = dataContainer 130 | 131 | # Transformers to the rescue 132 | self.prime = Optimus() 133 | self.prime.set_transformations(TRANSFORMATIONS) 134 | 135 | def parse(self, params, body): 136 | for param in params: 137 | # TODO: Add different extraction depending on param type 138 | paramValue = unquote(param.getValue()) 139 | 140 | # Param testing 141 | if len(paramValue) < MIN_PARAM_LEN: continue 142 | 143 | # Search body 144 | for name, trns in self.prime.transform(paramValue): 145 | indexes = [x for x in list(self.find_all(trns, body))] 146 | 147 | # Extract snippet 148 | if indexes: 149 | for start, end in indexes: 150 | # TODO: more intelligent snippet 151 | snippet = body[max(0,start-30):min(end+30, len(body))] 152 | 153 | self.container.insertRow(paramValue, name, snippet) 154 | 155 | break 156 | 157 | @staticmethod 158 | def find_all(sub, string): 159 | l = len(sub) 160 | start = 0 161 | while True: 162 | start = string.find(sub, start) 163 | if start == -1: return 164 | yield (start, start+l) 165 | start += l 166 | 167 | 168 | ## Transformers class 169 | class Optimus: 170 | def __init__(self): 171 | self.transformations = [] 172 | 173 | def set_transformations(self, t): 174 | self.transformations = t 175 | 176 | def transform(self, target): 177 | if target: 178 | for t in self.transformations: 179 | yield t['name'], self._translate(target, t["table"]) 180 | 181 | def _translate(self, string, transformation): 182 | return reduce(lambda a, kv: a.replace(*kv), transformation, string) 183 | 184 | 185 | ## Classes related to UITable 186 | # TODO: need serious UI work 187 | class ArgonautTable(JTable): 188 | def __init__(self, dataModel): 189 | self.setModel(dataModel) 190 | return 191 | 192 | 193 | class ArgonautData(AbstractTableModel): 194 | _data = [] 195 | 196 | def reset(self): 197 | self._data = [] 198 | 199 | def insertRow(self, paramValue, transformation, snippet): 200 | entry = { 201 | 'paramValue': paramValue, 202 | 'transformation': transformation, 203 | 'snippet': snippet 204 | } 205 | 206 | self._data.append(entry) 207 | 208 | def getRowCount(self): 209 | return len(self._data) 210 | 211 | def getColumnCount(self): 212 | return 3 213 | 214 | def getColumnName(self, columnIndex): 215 | if columnIndex == 0: 216 | return "Parameter Value" 217 | if columnIndex == 1: 218 | return "Transformation" 219 | if columnIndex == 2: 220 | return "Snippet" 221 | 222 | return "" 223 | 224 | def getValueAt(self, rowIndex, columnIndex): 225 | dataEntry = self._data[rowIndex] 226 | 227 | if columnIndex == 0: 228 | return dataEntry['paramValue'] 229 | if columnIndex == 1: 230 | return dataEntry['transformation'] 231 | if columnIndex == 2: 232 | return dataEntry['snippet'] 233 | 234 | return "" 235 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

This extension adds a new tab to Burp's HTTP message editor, and displays JSON messages in decoded form.

2 |

This extension requires Jython version 2.7 or later.

-------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: ceed5b1568ba4b92abecce0dff1e1f2c 2 | ExtensionType: 2 3 | Name: JSON Decoder 4 | RepoName: json-decoder 5 | ScreenVersion: 1.2 6 | SerialVersion: 2 7 | MinPlatformVersion: 0 8 | ProOnly: False 9 | Author: Michal Melewski 10 | ShortDescription: Displays JSON messages in decoded form. 11 | EntryPoint: JSONDecoder.py 12 | BuildCommand: rm -f Argonaut.py 13 | -------------------------------------------------------------------------------- /JSONDecoder.py: -------------------------------------------------------------------------------- 1 | # Burp Extension - JSON decoder 2 | # Copyright : Michal Melewski 3 | 4 | # Small content-type fix: Nicolas Gregoire 5 | # Force JSON fix: Marcin 'Icewall' Noga 6 | 7 | import json 8 | 9 | from burp import IBurpExtender 10 | from burp import IMessageEditorTabFactory 11 | from burp import IMessageEditorTab 12 | from burp import IParameter 13 | from burp import IContextMenuFactory 14 | 15 | # Java imports 16 | from javax.swing import JMenuItem 17 | from java.util import List, ArrayList 18 | 19 | # Menu items 20 | menuItems = { 21 | False: "Turn JSON active detection on", 22 | True: "Turn JSON active detection off" 23 | } 24 | 25 | # Content types 26 | supportedContentTypes = [ 27 | "application/json", 28 | "text/json", 29 | "text/x-json", 30 | ] 31 | 32 | # Global Switch 33 | _forceJSON = False 34 | 35 | class BurpExtender(IBurpExtender, IMessageEditorTabFactory, IContextMenuFactory): 36 | def registerExtenderCallbacks(self, callbacks): 37 | self._callbacks = callbacks 38 | self._helpers = callbacks.getHelpers() 39 | 40 | callbacks.setExtensionName('JSON Decoder') 41 | callbacks.registerMessageEditorTabFactory(self) 42 | callbacks.registerContextMenuFactory(self) 43 | 44 | return 45 | 46 | def createNewInstance(self, controller, editable): 47 | return JSONDecoderTab(self, controller, editable) 48 | 49 | def createMenuItems(self, IContextMenuInvocation): 50 | global _forceJSON 51 | menuItemList = ArrayList() 52 | menuItemList.add(JMenuItem(menuItems[_forceJSON], actionPerformed = self.onClick)) 53 | 54 | return menuItemList 55 | 56 | def onClick(self, event): 57 | global _forceJSON 58 | _forceJSON = not _forceJSON 59 | 60 | class JSONDecoderTab(IMessageEditorTab): 61 | def __init__(self, extender, controller, editable): 62 | self._extender = extender 63 | self._helpers = extender._helpers 64 | self._editable = editable 65 | 66 | self._txtInput = extender._callbacks.createTextEditor() 67 | self._txtInput.setEditable(editable) 68 | 69 | self._jsonMagicMark = ['{"', '["', '[{'] 70 | 71 | return 72 | 73 | def getTabCaption(self): 74 | return "JSON Decoder" 75 | 76 | def getUiComponent(self): 77 | return self._txtInput.getComponent() 78 | 79 | def isEnabled(self, content, isRequest): 80 | global _forceJSON 81 | 82 | if isRequest: 83 | r = self._helpers.analyzeRequest(content) 84 | else: 85 | r = self._helpers.analyzeResponse(content) 86 | 87 | msg = content[r.getBodyOffset():].tostring() 88 | 89 | if _forceJSON and len(msg) > 2 and msg[:2] in self._jsonMagicMark: 90 | print "Forcing JSON parsing and magic mark found: %s"%msg[:2] 91 | return True 92 | 93 | for header in r.getHeaders(): 94 | if header.lower().startswith("content-type:"): 95 | content_type = header.split(":")[1].lower() 96 | 97 | for allowedType in supportedContentTypes: 98 | if content_type.find(allowedType) > 0: 99 | return True 100 | 101 | return False 102 | 103 | def setMessage(self, content, isRequest): 104 | if content is None: 105 | self._txtInput.setText(None) 106 | self._txtInput.setEditable(False) 107 | else: 108 | if isRequest: 109 | r = self._helpers.analyzeRequest(content) 110 | else: 111 | r = self._helpers.analyzeResponse(content) 112 | 113 | msg = content[r.getBodyOffset():].tostring() 114 | 115 | # find garbage index 116 | # I know, this is not bulletproof, but we have to try 117 | try: 118 | boundary = min( 119 | msg.index('{') if '{' in msg else len(msg), 120 | msg.index('[') if '[' in msg else len(msg) 121 | ) 122 | except ValueError: 123 | print('Sure this is JSON?') 124 | return 125 | 126 | garbage = msg[:boundary] 127 | clean = msg[boundary:] 128 | 129 | try: 130 | pretty_msg = garbage.strip() + '\n' + json.dumps(json.loads(clean), indent=4) 131 | except: 132 | print "problem parsing data in setMessage" 133 | pretty_msg = garbage + clean 134 | 135 | self._txtInput.setText(pretty_msg) 136 | self._txtInput.setEditable(self._editable) 137 | 138 | self._currentMessage = content 139 | return 140 | 141 | def getMessage(self): 142 | if self._txtInput.isTextModified(): 143 | try: 144 | pre_data = self._txtInput.getText().tostring() 145 | 146 | boundary = min(pre_data.index('{'), pre_data.index('[')) 147 | 148 | garbage = pre_data[:boundary] 149 | clean = pre_data[boundary:] 150 | data = garbage + json.dumps(json.loads(clean)) 151 | except: 152 | data = self._helpers.bytesToString(self._txtInput.getText()) 153 | 154 | # Reconstruct request/response 155 | r = self._helpers.analyzeRequest(self._currentMessage) 156 | 157 | return self._helpers.buildHttpMessage(r.getHeaders(), self._helpers.stringToBytes(data)) 158 | else: 159 | return self._currentMessage 160 | 161 | def isModified(self): 162 | return self._txtInput.isTextModified() 163 | 164 | def getSelectedData(self): 165 | return self._txtInput.getSelectedText() 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BurpSuite Extensions 2 | ==================== 3 | This is an assorted set of various BurpSuite extensions that I've created while doing pentesting and other things. 4 | 5 | > **Note** 6 | > Every extension is tested on following configuration 7 | > 8 | >- BurpSuite 1.6.01 and newest one (I use free and pro version respectively) 9 | >- Jython 2.7 10 | > 11 | > To send bug reports, feature requests, or whisky, simply drop a mail to michal.melewski@gmail.com 12 | 13 | ### JSONDecoder (1.1) 14 | Quite simply just a JSON pretty printer with some additional features. 15 | 16 | * Ability to remove json garbage (like }]);) - it does a bit of guessing, so not always reliable 17 | * Ability to force JSON decoding on atypical content-type (by default decodes only application/json and text/javascript) 18 | 19 | ### Argonaut (0.4) 20 | Extension process all request parameters and try to find if they are echoed back in response. Key feature is transformation support so, will also recognize if for example > is translated to &gt;. Transformations/escaping currently implemented: 21 | * Plain (no escaping) 22 | * Jinja2 template 23 | 24 | Let me know what other escaping you would like to see 25 | 26 | _Under heavy development so if you want some features let me know. 27 | Also inform me know about bugs._ 28 | --------------------------------------------------------------------------------