├── Jsonp_Hunter.py └── README.md /Jsonp_Hunter.py: -------------------------------------------------------------------------------- 1 | from burp import IBurpExtender 2 | from burp import ITab 3 | from burp import IScannerCheck 4 | from burp import IMessageEditorController 5 | from burp import IParameter 6 | from java.awt import Component; 7 | from java.io import PrintWriter; 8 | from java.util import ArrayList; 9 | from java.util import List; 10 | from javax.swing import JScrollPane; 11 | from javax.swing import JSplitPane; 12 | from javax.swing import JTabbedPane; 13 | from javax.swing import JTable; 14 | from javax.swing import SwingUtilities; 15 | from javax.swing.table import AbstractTableModel; 16 | from threading import Lock 17 | from urlparse import urlparse 18 | import re 19 | 20 | jsonp_string = '&callback=jsonp1&cb=jsonp2&jsonp=jsonp3&jsonpcallback=jsonp4&jsonpcb=jsonp5&jsonp_cb=jsonp6&call=jsonp7&jcb=jsonp8&json=jsonp9&cbk=jsonp10&jsonpCallback=jsonp11&jsonpcb=jsonp12&jsoncallback=jsonp13&method=jsonp14&callbackStatus=jsonp15&jsonp_callback=jsonp16' 21 | jsonp_dict = {'callback':'jsonp1','cb':'jsonp2','jsonp':'jsonp3','jsonpcallback':'jsonp4','jsonpcb':'jsonp5','jsonp_cb':'jsonp6','call':'jsonp7','jcb':'jsonp8','json':'jsonp9','cbk':'jsonp10','jsonpCallback':'jsonp11','jsonpcb':'jsonp12','jsoncallback':'jsonp13','method':'jsonp14','callbackStatus':'jsonp15','jsonp_callback':'jsonp16'} 22 | black_list = ['js','css','jpg','gif','png','zip','rar','bmp','jpeg','mp3','mp4','ico','txt'] 23 | 24 | """ 25 | use for find jsonp feature in response just like callback({userinfo}) 26 | """ 27 | 28 | class BurpExtender(IBurpExtender, ITab, IScannerCheck, IMessageEditorController, AbstractTableModel): 29 | 30 | # 31 | # implement IBurpExtender 32 | # 33 | 34 | def registerExtenderCallbacks(self, callbacks): 35 | # keep a reference to our callbacks object 36 | self._callbacks = callbacks 37 | 38 | # obtain an extension helpers object 39 | self._helpers = callbacks.getHelpers() 40 | 41 | # set our extension name 42 | callbacks.setExtensionName("JSONP Hunter") 43 | 44 | # create the log and a lock on which to synchronize when adding log entries 45 | self._log = ArrayList() 46 | self._lock = Lock() 47 | 48 | # main split pane 49 | self._splitpane = JSplitPane(JSplitPane.VERTICAL_SPLIT) 50 | 51 | # table of log entries 52 | logTable = Table(self) 53 | scrollPane = JScrollPane(logTable) 54 | self._splitpane.setLeftComponent(scrollPane) 55 | 56 | # tabs with request/response viewers 57 | tabs = JTabbedPane() 58 | self._requestViewer = callbacks.createMessageEditor(self, False) 59 | self._responseViewer = callbacks.createMessageEditor(self, False) 60 | tabs.addTab("Request", self._requestViewer.getComponent()) 61 | tabs.addTab("Response", self._responseViewer.getComponent()) 62 | self._splitpane.setRightComponent(tabs) 63 | 64 | # customize our UI components 65 | callbacks.customizeUiComponent(self._splitpane) 66 | callbacks.customizeUiComponent(logTable) 67 | callbacks.customizeUiComponent(scrollPane) 68 | callbacks.customizeUiComponent(tabs) 69 | 70 | # add the custom tab to Burp's UI 71 | callbacks.addSuiteTab(self) 72 | 73 | # register ourselves as an HTTP listener 74 | callbacks.registerScannerCheck(self) 75 | 76 | # id for column 77 | self.id = 0 78 | 79 | return 80 | 81 | # 82 | # implement ITab 83 | # 84 | 85 | def getTabCaption(self): 86 | return "JsonpHunter" 87 | 88 | def getUiComponent(self): 89 | return self._splitpane 90 | 91 | # 92 | # implement IHttpListener 93 | # 94 | 95 | # def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo): 96 | # # only process requests 97 | # if messageIsRequest: 98 | # return 99 | 100 | # # create a new log entry with the message details 101 | # self._lock.acquire() 102 | # row = self._log.size() 103 | # self._log.add(LogEntry(toolFlag, self._callbacks.saveBuffersToTempFiles(messageInfo), self._helpers.analyzeRequest(messageInfo).getUrl())) 104 | # self.fireTableRowsInserted(row, row) 105 | # self._lock.release() 106 | 107 | # implement IScannerCheck 108 | 109 | def doActiveScan(self,baseRequestResponse,insertionPoint): 110 | pass 111 | 112 | def doPassiveScan(self,baseRequestResponse): 113 | self.baseRequestResponse = baseRequestResponse 114 | # service = baseRequestResponse.getHttpService() 115 | result = self.scancheck(baseRequestResponse) 116 | if result != [] and result !='' and result != None: 117 | param,url = result 118 | self.id +=1 119 | #analyze_request = self._helpers.analyzeRequest(service,baseRequestResponse.getRequest()) 120 | self._lock.acquire() 121 | row = self._log.size() 122 | self._log.add(LogEntry(self.id,baseRequestResponse,param,url)) 123 | self.fireTableRowsInserted(row, row) 124 | self._lock.release() 125 | return 126 | # 127 | # extend AbstractTableModel 128 | # 129 | 130 | def scancheck(self,baseRequestResponse): 131 | host,port,protocol,method,headers,params,url,reqBodys,analyze_request = self.Get_RequestInfo(baseRequestResponse) 132 | status_code,body = self.Get_ResponseInfo(baseRequestResponse) 133 | """ 134 | deal with black_list if path like xxx.jpg or xxx.jpeg 135 | """ 136 | parse_url = urlparse(url.toString()) 137 | url_path = parse_url.path 138 | if url_path != '': 139 | if '.' in url_path: 140 | if url_path.split('.')[-1:][0] in black_list: 141 | return '' 142 | 143 | if method == "GET": 144 | if params !='': 145 | """ 146 | extract value in response use value({}) like jsonp 147 | """ 148 | split_params = params.split('&') 149 | for param in split_params: 150 | if '=' in param: 151 | if len(param.split('=')) == 2: 152 | key,value = param.split('=') 153 | if value !='': 154 | jsonp_pattern = value + '\(\{.*?\}\)' 155 | re_result = re.findall(jsonp_pattern,body,re.S) 156 | if re_result: 157 | return [key,url.toString()] 158 | """ 159 | extract use jsonp_string 160 | """ 161 | againReq_headers = headers 162 | againReq_headers[0] = headers[0].replace(params,params+jsonp_string) 163 | againReq = self._helpers.buildHttpMessage(againReq_headers,reqBodys) 164 | if protocol == 'https': 165 | is_https = True 166 | else: 167 | is_https = False 168 | againRes = self._callbacks.makeHttpRequest(host, port, is_https, againReq) 169 | analyze_againRes = self._helpers.analyzeResponse(againRes) 170 | againResBodys = againRes[analyze_againRes.getBodyOffset():].tostring() 171 | for key,value in jsonp_dict.items(): 172 | jsonp_pattern = value + '\(\{.*?\}\)' 173 | re_result = re.findall(jsonp_pattern,againResBodys,re.S) 174 | if re_result: 175 | link = againReq_headers[0].split(' ')[1] 176 | host = againReq_headers[1].split(' ')[1] 177 | url = protocol+'://'+host+link 178 | return [key,str(url)] 179 | """ 180 | extract use jsonp_string with no params 181 | """ 182 | else: 183 | if '?' not in url.toString(): 184 | path = headers[0].split(' ')[1] 185 | againReq_headers_use_noparam = headers 186 | againReq_headers_use_noparam[0] = headers[0].replace('GET '+path,'GET '+path+'?'+jsonp_string[1:]) 187 | againReq = self._helpers.buildHttpMessage(againReq_headers_use_noparam, reqBodys) 188 | if protocol == 'https': 189 | is_https = True 190 | else: 191 | is_https = False 192 | againRes = self._callbacks.makeHttpRequest(host, port, is_https, againReq) 193 | analyze_againRes = self._helpers.analyzeResponse(againRes) 194 | againResBodys = againRes[analyze_againRes.getBodyOffset():].tostring() 195 | for key,value in jsonp_dict.items(): 196 | jsonp_pattern = value + '\(\{.*?\}\)' 197 | re_result = re.findall(jsonp_pattern,againResBodys,re.S) 198 | if re_result: 199 | link = againReq_headers_use_noparam[0].split(' ')[1] 200 | host = againReq_headers_use_noparam[1].split(' ')[1] 201 | url = protocol+'://'+host+link 202 | return [key,str(url)] 203 | 204 | return '' 205 | 206 | 207 | 208 | def Get_RequestInfo(self,baseRequestResponse): 209 | """ 210 | extract about service 211 | """ 212 | service = baseRequestResponse.getHttpService() 213 | host = service.getHost() 214 | port = service.getPort() 215 | protocol = service.getProtocol() 216 | """ 217 | extract request 218 | """ 219 | analyze_request = self._helpers.analyzeRequest(service,baseRequestResponse.getRequest()) 220 | reqBodys = baseRequestResponse.getRequest()[analyze_request.getBodyOffset():].tostring() 221 | url = analyze_request.getUrl() 222 | headers = analyze_request.getHeaders() 223 | method = analyze_request.getMethod() 224 | params = [i for i in analyze_request.getParameters() if i.getType() == IParameter.PARAM_URL] 225 | extract_params = '&'.join([('%s=%s' % (c.getName(),c.getValue())) for c in params ]) 226 | 227 | return host,port,protocol,method,headers,extract_params,url,reqBodys,analyze_request 228 | 229 | def Get_ResponseInfo(self,baseRequestResponse): 230 | """ 231 | extract response 232 | """ 233 | analyze_response = self._helpers.analyzeResponse(baseRequestResponse.getResponse()) 234 | status_code = analyze_response.getStatusCode() 235 | body = baseRequestResponse.getResponse()[analyze_response.getBodyOffset():].tostring() 236 | 237 | return status_code,body 238 | 239 | def getRowCount(self): 240 | try: 241 | return self._log.size() 242 | except: 243 | return 0 244 | 245 | def getColumnCount(self): 246 | return 3 247 | 248 | def getColumnName(self, columnIndex): 249 | if columnIndex == 0: 250 | return "ID" 251 | if columnIndex == 1: 252 | return "PARAM" 253 | if columnIndex == 2: 254 | return "URL" 255 | return "" 256 | 257 | def getValueAt(self, rowIndex, columnIndex): 258 | logEntry = self._log.get(rowIndex) 259 | if columnIndex == 0: 260 | return logEntry._id 261 | if columnIndex == 1: 262 | return logEntry._param 263 | if columnIndex == 2: 264 | return logEntry._url 265 | 266 | return "" 267 | 268 | # 269 | # implement IMessageEditorController 270 | # this allows our request/response viewers to obtain details about the messages being displayed 271 | # 272 | 273 | def getHttpService(self): 274 | return self._currentlyDisplayedItem.getHttpService() 275 | 276 | def getRequest(self): 277 | return self._currentlyDisplayedItem.getRequest() 278 | 279 | def getResponse(self): 280 | return self._currentlyDisplayedItem.getResponse() 281 | 282 | # 283 | # extend JTable to handle cell selection 284 | # 285 | 286 | class Table(JTable): 287 | def __init__(self, extender): 288 | self._extender = extender 289 | self.setModel(extender) 290 | 291 | def changeSelection(self, row, col, toggle, extend): 292 | 293 | # show the log entry for the selected row 294 | logEntry = self._extender._log.get(row) 295 | self._extender._requestViewer.setMessage(logEntry._requestResponse.getRequest(), True) 296 | self._extender._responseViewer.setMessage(logEntry._requestResponse.getResponse(), False) 297 | self._extender._currentlyDisplayedItem = logEntry._requestResponse 298 | 299 | JTable.changeSelection(self, row, col, toggle, extend) 300 | 301 | # 302 | # class to hold details of each log entry 303 | # 304 | 305 | class LogEntry: 306 | def __init__(self,record_id,requestResponse, param, url): 307 | self._id = record_id 308 | self._param = param 309 | self._requestResponse = requestResponse 310 | self._url = url 311 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONP-Hunter 2 | ## How to use 3 | 4 | 该插件用Python编写,所以如果想要使用的话,需要安装Jython环境,网上已有许多教程,这里不再重复叙述,装好环境之后导入插件即可。 5 | 6 | 存在JSONP特征的url会保存在插件目录下的jsonp.txt中。 7 | 8 | 9 | ## update 2019.1.7 10 | 11 | add ui to show the info. 12 | 13 | just like this: 14 | 15 | ![photo](https://s2.ax1x.com/2020/01/07/lyHUMQ.jpg) 16 | --------------------------------------------------------------------------------