├── BurpParamFlagger.py ├── README.md ├── issue.png ├── paramname.png └── value.png /BurpParamFlagger.py: -------------------------------------------------------------------------------- 1 | from burp import IBurpExtender 2 | from burp import IScannerCheck 3 | from burp import IScanIssue 4 | from array import array 5 | import sys 6 | 7 | 8 | ssrfParamChecks = ["icon_url","url","uri","authorization_url","redirect_uri","redirect_url","redirect","referrer","origin","location","return_url","link","starturl","return","preview","previewurl","preview_url","loc","path","template","forward","goto","fetch","domain","check","dest","continue","next","site","html","callback","returnto","return_to","feed","host","to","out","view","show","open","viewurl","go","fromurl","from","from_url","fromuri","from_uri","redir","website","profileurl","profile_url","icon","avatar","targeturl","target_url","start","baseurl","oembed"] 9 | lfiParamChecks = ["samplefile","file","html_file","src","source","upload","download","content","template","attachment","image","path","page","location","loc","include","dir","document","folder","root","pg","p","style","pdf","php_path","doc","icon","directory"] 10 | fileExtensions = [".js", ".svg", ".jpeg", ".jpg", ".csv", ".xml", ".html", ".php", ".asp", ".aspx", ".png", ".ico", ".json", ".pdf", ".css", ".jsp", ".zip", ".gz", ".swf", ".woff"] 11 | webRef = ["https", "http", "www"] 12 | 13 | class BurpExtender(IBurpExtender, IScannerCheck): 14 | 15 | def registerExtenderCallbacks(self, callbacks): 16 | self._callbacks = callbacks 17 | self._helpers = callbacks.getHelpers() 18 | 19 | callbacks.setExtensionName("BurpParamFlagger") 20 | 21 | sys.stdout = callbacks.getStdout() 22 | sys.stderr = callbacks.getStderr() 23 | 24 | callbacks.registerScannerCheck(self) 25 | 26 | def _check_params(self, reqInfo): 27 | findings = {} 28 | params = reqInfo.getParameters() 29 | url = reqInfo.getUrl() 30 | for param in params: 31 | name = param.getName() 32 | value = param.getValue() 33 | 34 | if name.lower() in ssrfParamChecks or "_" + name.lower() in ssrfParamChecks or value.lower().startswith(tuple(webRef)): 35 | if "SSRF" not in findings.keys(): 36 | findings["SSRF"] = [] 37 | findings["SSRF"].append(name) 38 | 39 | if name.lower() in lfiParamChecks or "_" + name.lower() in lfiParamChecks or value.lower().endswith(tuple(fileExtensions)): 40 | if "LFI" not in findings.keys(): 41 | findings["LFI"] = [] 42 | findings["LFI"].append(name) 43 | 44 | return findings 45 | 46 | def doPassiveScan(self, baseRequestResponse): 47 | if self._callbacks.isInScope(self._helpers.analyzeRequest(baseRequestResponse).getUrl()): 48 | issues = [] 49 | 50 | print(baseRequestResponse.getUrl()) 51 | analyzed = self._helpers.analyzeRequest(baseRequestResponse.getHttpService(), baseRequestResponse.getRequest()) 52 | matches = self._check_params(analyzed) 53 | 54 | if len(matches) == 0: 55 | return None 56 | 57 | print(matches) 58 | print(type(matches)) 59 | print(matches.keys()) 60 | req = baseRequestResponse.getRequest() 61 | 62 | for category, params in matches.items(): 63 | for param in params: 64 | start = self._helpers.indexOf(req, 65 | param + "=", True, 0, len(req)) 66 | offset = array('i', [0, 0]) 67 | offset[0] = start 68 | offset[1] = start + len(param + "=") 69 | 70 | issues.append(ScanIssue( 71 | baseRequestResponse.getHttpService(), 72 | self._helpers.analyzeRequest(baseRequestResponse).getUrl(), 73 | [self._callbacks.applyMarkers(baseRequestResponse, [offset], None)], 74 | "Potential Target Parameter for {}".format(category), 75 | "The request has the parameter: {}

The name and/or value of this parameter indicates it may be a good place to test for {}.".format(param, category), 76 | "Information")) 77 | return issues 78 | else: 79 | print("Out of Scope") 80 | print(self._helpers.analyzeRequest(baseRequestResponse).getUrl()) 81 | 82 | 83 | def consolidateDuplicateIssues(self, existingIssue, newIssue): 84 | if (existingIssue.getIssueName() == newIssue.getIssueName()) and (existingIssue.getIssueDetail() == newIssue.getIssueDetail()) and (existingIssue.getUrl() == newIssue.getUrl()): 85 | print("Duplicate") 86 | print(existingIssue.getIssueDetail()) 87 | return -1 88 | return 0 89 | 90 | class ScanIssue (IScanIssue): 91 | def __init__(self, httpService, url, httpMessages, name, detail, severity): 92 | self._url = url 93 | self._name = name 94 | self._detail = detail 95 | self._severity = severity 96 | self._httpMessages = httpMessages 97 | self._httpService = httpService 98 | 99 | def getUrl(self): 100 | return self._url 101 | 102 | def getIssueName(self): 103 | return self._name 104 | 105 | def getIssueDetail(self): 106 | return self._detail 107 | 108 | def getSeverity(self): 109 | return self._severity 110 | 111 | def getConfidence(self): 112 | return "Certain" 113 | 114 | def getIssueBackground(self): 115 | return None 116 | 117 | def getRemediationBackground(self): 118 | return None 119 | 120 | def getRemediationDetail(self): 121 | return None 122 | 123 | def getIssueType(self): 124 | return 0 125 | 126 | def getHttpMessages(self): 127 | return self._httpMessages 128 | 129 | def getHttpService(self): 130 | return self._httpService 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BurpParamFlagger 2 | 3 | A Burp extension adding a passive scan check to flag parameters whose name or value may indicate a possible insertion point for SSRF or LFI. 4 | 5 | *Note:* I believe that Burp Pro is required to use this extension, since it adds onto the scanner functionality, which isn't included in the Community version. 6 | 7 | ![ScreenShot](issue.png) 8 | 9 | The extension will look at both the **name** of a parameter and the **value** of that parameter and look for any common words or patterns indicating that it could be an insertion point for SSRF or LFI. 10 | 11 | For example, SSRF checks include looking for parameter names like 'redirect', 'url', or 'domain', as well as looking for values that look like a URL. 12 | 13 | LFI checks look for names like 'include', 'attach', or 'file', and look for values that have a file extension. 14 | 15 | A few basic examples: 16 | 17 | ![ScreenShot](paramname.png) 18 | ![ScreenShot](value.png) 19 | 20 | 21 | ## Installation 22 | 23 | Just clone the repo and load the extension into Burp: Go to the Extender tab, click 'Add', change the extension type to 'Python', provide the cloned BurpParamFlagger.py file, and follow the next prompts. 24 | 25 | 26 | ## Usage 27 | 28 | Once the extension is loaded, nothing more is needed. You should start seeing any flagged requests with your other scanner issues on the Dashboard. 29 | -------------------------------------------------------------------------------- /issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allyomalley/BurpParamFlagger/e4aa3f0e906a9a961fac599ab43b20b09a61d0bc/issue.png -------------------------------------------------------------------------------- /paramname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allyomalley/BurpParamFlagger/e4aa3f0e906a9a961fac599ab43b20b09a61d0bc/paramname.png -------------------------------------------------------------------------------- /value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allyomalley/BurpParamFlagger/e4aa3f0e906a9a961fac599ab43b20b09a61d0bc/value.png --------------------------------------------------------------------------------