├── README.md └── plugin.py /README.md: -------------------------------------------------------------------------------- 1 | # burp-xss-sql-plugin 2 | 3 | Publishing plugin which I used for years which helped me to find several bugbounty-worthy XSSes, OpenRedirects and SQLi. 4 | 5 | __HTML Inj__: Special symbols are checked one-by-one if they appear in output. WAF/base64encoding/location/content-type/etc detections. 6 | 7 | __SQL Inj__: All parameters are transfered through SQLMap API to host, which in used for asynchronous scanning. 8 | 9 | __Tip__: Change Burp's Active Scan scope so it will automatically append new HTTP requests into queue, e.g.: 10 | 11 | ``` 12 | Host: bugbounty.com 13 | File: (? $task) { 24 | $task_data = json_decode(file_get_contents("http://0.0.0.0:8775/scan/".$id."/data"), true); 25 | if(count($task_data['data']) > 0) 26 | echo "[".$id."]

SQL Inj!


"; 27 | //else echo "[".$id."] None...
"; 28 | } 29 | } 30 | 31 | ?> 32 | ``` 33 | -------------------------------------------------------------------------------- /plugin.py: -------------------------------------------------------------------------------- 1 | from burp import IBurpExtender, IScanIssue, IScannerCheck, IHttpListener, IProxyListener, IScannerListener, IExtensionStateListener 2 | from java.io import PrintWriter 3 | from java.lang import RuntimeException 4 | from uuid import uuid4 5 | import urllib2 6 | import array 7 | import re 8 | import json 9 | import hashlib 10 | import os.path 11 | import string 12 | import random 13 | 14 | class BurpExtender(IBurpExtender, IScannerCheck, IHttpListener, IProxyListener, IScannerListener, IExtensionStateListener): 15 | 16 | def registerExtenderCallbacks(self, callbacks): 17 | 18 | global burp_callbacks 19 | burp_callbacks = callbacks 20 | global burp_helpers 21 | burp_helpers = burp_callbacks.getHelpers() 22 | burp_callbacks.setExtensionName("BugBountyPlugin") 23 | 24 | self.stdout = PrintWriter(burp_callbacks.getStdout(), True) 25 | 26 | self.println("SQL/XSS custom plugin (c) @httpsonly") 27 | 28 | burp_callbacks.registerScannerCheck(self) 29 | burp_callbacks.registerProxyListener(self) 30 | return 31 | 32 | def doActiveScan(self, baseRequestResponse, insertionPoint): 33 | textIssue = "" 34 | tags = "" 35 | doSqlmap = 1 36 | name = insertionPoint.getInsertionPointName() 37 | value = insertionPoint.getBaseValue() 38 | if re.search(r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$', value) is not None: 39 | doSqlmap = 0 40 | #textIssue = "Input parameter '"+name+"' is Base64-encoded
" 41 | if re.search(r'(?i)(.*?)http[s]*:\/\/(.*?)', value) is not None: 42 | doSqlmap = 0 43 | textIssue = "Input parameter '"+name+"' has http(s)://
" 44 | if re.search(r'(?i)(.*?)<\?xml(.*?)', value) is not None: 45 | doSqlmap = 0 46 | textIssue = textIssue + "Input parameter '"+name+"' has <?xml
" 47 | if doSqlmap == 1: 48 | #sqlmap_host = '10.0.70.51' 49 | sqlmap_host = 'mydomain.com' 50 | reqHeaders = burp_helpers.bytesToString(baseRequestResponse.getRequest()) 51 | referer = None 52 | cookie = None 53 | postdata = None 54 | reqInfo = burp_helpers.analyzeRequest(baseRequestResponse) 55 | url = str(reqInfo.getUrl()) 56 | if re.search(r'(?i)Cookie: (.*?)[\r|\n]', reqHeaders) is not None: 57 | cookie = re.search(r'(?i)Cookie: (.*?)[\r|\n]', reqHeaders).group(1) 58 | if re.search(r'[\r|\n]{2}(.*?)$', reqHeaders) is not None: 59 | postdata = re.search(r'[\r|\n]{2}(.*?)$', reqHeaders).group(1) 60 | #SQLMap API 61 | try: 62 | req = urllib2.Request('http://'+sqlmap_host+':8775/task/new') 63 | resp = json.load(urllib2.urlopen(req)) 64 | if resp['success'] == True and resp['taskid']: 65 | sqlitask = resp['taskid'] 66 | sqliopts = {'delay': 0, 'risk': 1, 'timeout': 30, 'level': 1, 'answers': 'crack=N,dict=N', 'cookie': cookie, 'threads': 1, 'url': url, 'referer': referer, 'retries': 3, 'timeSec': 5, 'getBanner': True, 'data': postdata, 'timeSec': 5} 67 | try: 68 | req = urllib2.Request('http://'+sqlmap_host+':8775/option/' + sqlitask + '/set') 69 | req.add_header('Content-Type', 'application/json') 70 | resp = json.load(urllib2.urlopen(req, json.dumps(sqliopts))) 71 | if resp['success'] == True: 72 | sqliopts = {'url': url} 73 | try: 74 | checkreq = urllib2.Request('http://'+sqlmap_host+':8775/option/' + sqlitask + '/list') 75 | checkresp = json.load(urllib2.urlopen(checkreq)) 76 | except: 77 | print 'Failed to get list of options from SQLMap API\n' 78 | try: 79 | req = urllib2.Request('http://'+sqlmap_host+':8775/scan/' + sqlitask + '/start') 80 | req.add_header('Content-Type', 'application/json') 81 | resp = json.load(urllib2.urlopen(req, json.dumps(sqliopts))) 82 | if resp['success'] == True: 83 | print 'Started SQLMap Scan on Task ' + sqlitask +'\n' 84 | else: 85 | print 'Failed to start SQLMap Scan for Task: ' + sqlitask + '\n' 86 | except: 87 | print 'Failed to start SQLMap Scan for Task: ' + sqlitask + '\n' 88 | else: 89 | print 'Failed to set options on SQLMap Task: ' + sqlitask + '\n' 90 | except: 91 | print 'Failed to set options on SQLMap Task: ' + sqlitask + '\n' 92 | else: 93 | print 'SQLMap task creation failed\n' 94 | except: 95 | print 'SQLMap task creation failed\n' 96 | score = 0 97 | flag1 = 0 98 | attack1 = burp_callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), insertionPoint.buildRequest(burp_helpers.stringToBytes("yyyyyyyyy"))) 99 | response1 = attack1.getResponse() 100 | response_str1 = burp_helpers.bytesToString(response1) 101 | # if re.search(r'[\r|\n](?i)(Content-type:[\s]*application\/(?!xml)(.*?))[\r|\n]', response_str1) is not None: 102 | # return None 103 | # break 104 | #m = re.search(r'<([a-zA-Z0-9]+)[^>]*([^<]*?)yyyyyyyyy', response_str1) 105 | # if m.group(1) is not None: 106 | # textIssue = textIssue + "Input parameter gets into tag '"+m.group(1)+"'
" 107 | # flag1 = 1 108 | # if m.group(1) == 'script': score = 4 109 | if re.search(r'(?is)(]*yyyyyyyyy(.*?)>', response_str1) 112 | if result: 113 | textIssue = textIssue + "Input parameter gets into on* - header
" 114 | flag1 = 1 115 | # score = 4 116 | if re.search(r'[\r|\n](?i)Location:(.*?)yyyyyyyyy(.*?)[\r|\n]', response_str1) is not None: 117 | textIssue = textIssue+"Location: header injection
" 118 | flag1 = 1 119 | payload_array = ["'\"", "<", ">", "\\\\'ttt", "\\\\\"ggg", "\\"] 120 | payload_all = "" 121 | rand_str = "jjjjjjj" 122 | for payload in payload_array: 123 | payload_all = payload_all+rand_str+payload 124 | #payload_all = payload_all+rand_str 125 | payload_bytes = burp_helpers.stringToBytes(payload_all) 126 | attack = burp_callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), insertionPoint.buildRequest(payload_bytes)) 127 | response = attack.getResponse() 128 | response_str = burp_helpers.bytesToString(response) 129 | if_found_payload = "" 130 | non_encoded_symbols = "" 131 | severity = "Low" 132 | for check_payload in payload_array: 133 | if_found_payload = rand_str+check_payload 134 | if if_found_payload in response_str: 135 | non_encoded_symbols = non_encoded_symbols+" "+check_payload.replace('<', '<') 136 | score = score+1 137 | flag1 = 1 138 | if score > 2: severity = "Medium" 139 | if score > 3: severity = "High" 140 | if non_encoded_symbols == " \\\\'ttt": 141 | severity = "Information" 142 | if non_encoded_symbols != '': 143 | textIssue = textIssue + "

Symbols not encoded: "+non_encoded_symbols+"
" 144 | if 'jjjjjjj' not in response_str and flag1 == 1: 145 | textIssue = textIssue + '

WAF deleted payload string with symbols! Please bypass

' 146 | if flag1 == 1: 147 | return [CustomScanIssue(burp_helpers.analyzeRequest(attack).getUrl(), "BugBounty Plugin", 134217728, severity, "Certain", None, None, textIssue, None, [attack], attack.getHttpService())] 148 | 149 | 150 | def doPassiveScan(self, baseRequestResponse): 151 | pass 152 | 153 | def println(self, message): 154 | self.stdout.println(message) 155 | 156 | def randstring(n): 157 | a = string.ascii_letters + string.digits 158 | return ''.join([random.choice(a) for i in range(n)]) 159 | 160 | 161 | class CustomScanIssue(IScanIssue): 162 | def __init__(self, Url, IssueName, IssueType, Severity, Confidence, IssueBackground, 163 | RemediationBackground, IssueDetail, RemediationDetail, HttpMessages, HttpService): 164 | self._Url = Url 165 | self._IssueName = IssueName 166 | self._IssueType = IssueType 167 | self._Severity = Severity 168 | self._Confidence = Confidence 169 | self._IssueBackground = IssueBackground 170 | self._RemediationBackground = RemediationBackground 171 | self._IssueDetail = IssueDetail 172 | self._RemediationDetail = RemediationDetail 173 | self._HttpMessages = HttpMessages 174 | self._HttpService = HttpService 175 | 176 | def getUrl(self): 177 | return self._Url 178 | 179 | def getIssueName(self): 180 | return self._IssueName 181 | 182 | def getIssueType(self): 183 | return self._IssueType 184 | 185 | def getSeverity(self): 186 | return self._Severity 187 | 188 | def getConfidence(self): 189 | return self._Confidence 190 | 191 | def getIssueBackground(self): 192 | return self._IssueBackground 193 | 194 | def getRemediationBackground(self): 195 | return self._RemediationBackground 196 | 197 | def getIssueDetail(self): 198 | return self._IssueDetail 199 | 200 | def getRemediationDetail(self): 201 | return self._RemediationDetail 202 | 203 | def getHttpMessages(self): 204 | return self._HttpMessages 205 | 206 | def getHttpService(self): 207 | return self._HttpService 208 | 209 | 210 | --------------------------------------------------------------------------------