# # BurpLinkFinder - Find links within JS files. # # Copyright (c) 2019 Frans Hendrik Botes # Credit to https://github.com/GerbenJavado/LinkFinder for the idea and regex # from burp import IBurpExtender, IScannerCheck, IScanIssue, ITab from java.io import PrintWriter from java.net import URL from java.util import ArrayList, List from java.util.regex import Matcher, Pattern import binascii import base64 import re from javax import swing from java.awt import Font, Color from threading import Thread from array import array from java.awt import EventQueue from java.lang import Runnable from thread import start_new_thread from javax.swing import JFileChooser # Using the Runnable class for thread-safety with Swing class Run(Runnable): def __init__(self, runner): self.runner = runner def run(self): self.runner() # Needed params JSExclusionList = ['jquery', 'google-analytics','gpt.js'] class BurpExtender(IBurpExtender, IScannerCheck, ITab): def registerExtenderCallbacks(self, callbacks): self.callbacks = callbacks self.helpers = callbacks.getHelpers() callbacks.setExtensionName("BurpJSLinkFinder") callbacks.issueAlert("BurpJSLinkFinder Passive Scanner enabled") stdout = PrintWriter(callbacks.getStdout(), True) stderr = PrintWriter(callbacks.getStderr(), True) callbacks.registerScannerCheck(self) self.initUI() self.callbacks.addSuiteTab(self) print ("Burp JS LinkFinder loaded.") print ("Copyright (c) 2019 Frans Hendrik Botes") self.outputTxtArea.setText("Burp JS LinkFinder loaded." + "\n" + "Copyright (c) 2019 Frans Hendrik Botes" + "\n") def initUI(self): self.tab = swing.JPanel() # UI for Output self.outputLabel = swing.JLabel("LinkFinder Log:") self.outputLabel.setFont(Font("Tahoma", Font.BOLD, 14)) self.outputLabel.setForeground(Color(255,102,52)) self.logPane = swing.JScrollPane() self.outputTxtArea = swing.JTextArea() self.outputTxtArea.setFont(Font("Consolas", Font.PLAIN, 12)) self.outputTxtArea.setLineWrap(True) self.logPane.setViewportView(self.outputTxtArea) self.clearBtn = swing.JButton("Clear Log", actionPerformed=self.clearLog) self.exportBtn = swing.JButton("Export Log", actionPerformed=self.exportLog) self.parentFrm = swing.JFileChooser() # Layout layout = swing.GroupLayout(self.tab) layout.setAutoCreateGaps(True) layout.setAutoCreateContainerGaps(True) self.tab.setLayout(layout) layout.setHorizontalGroup( layout.createParallelGroup() .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup() .addComponent(self.outputLabel) .addComponent(self.logPane) .addComponent(self.clearBtn) .addComponent(self.exportBtn) ) ) ) layout.setVerticalGroup( layout.createParallelGroup() .addGroup(layout.createParallelGroup() .addGroup(layout.createSequentialGroup() .addComponent(self.outputLabel) .addComponent(self.logPane) .addComponent(self.clearBtn) .addComponent(self.exportBtn) ) ) ) def getTabCaption(self): return "BurpJSLinkFinder" def getUiComponent(self): return self.tab def clearLog(self, event): self.outputTxtArea.setText("Burp JS LinkFinder loaded." + "\n" + "Copyright (c) 2019 Frans Hendrik Botes" + "\n" ) def exportLog(self, event): chooseFile = JFileChooser() ret = chooseFile.showDialog(self.logPane, "Choose file") filename = chooseFile.getSelectedFile().getCanonicalPath() print("\n" + "Export to : " + filename) open(filename, 'w', 0).write(self.outputTxtArea.text) def doPassiveScan(self, ihrr): try: urlReq = ihrr.getUrl() testString = str(urlReq) linkA = linkAnalyse(ihrr,self.helpers) # check if JS file if ".js" in str(urlReq): # Exclude casual JS files if any(x in testString for x in JSExclusionList): print("\n" + "[-] URL excluded " + str(urlReq)) else: self.outputTxtArea.append("\n" + "[+] Valid URL found: " + str(urlReq)) issueText = linkA.analyseURL() for counter, issueText in enumerate(issueText): #print("TEST Value returned SUCCESS") self.outputTxtArea.append("\n" + "\t" + str(counter)+' - ' +issueText['link']) issues = ArrayList() issues.add(SRI(ihrr, self.helpers)) return issues except UnicodeEncodeError: print ("Error in URL decode.") return None def consolidateDuplicateIssues(self, isb, isa): return -1 def extensionUnloaded(self): print "Burp JS LinkFinder unloaded" return class linkAnalyse(): def __init__(self, reqres, helpers): self.helpers = helpers self.reqres = reqres regex_str = """ (?:"|') # Start newline delimiter ( ((?:[a-zA-Z]{1,10}://|//) # Match a scheme [a-Z]*1-10 or // [^"'/]{1,}\. # Match a domainname (any character + dot) [a-zA-Z]{2,}[^"']{0,}) # The domainextension and/or path | ((?:/|\.\./|\./) # Start with /,../,./ [^"'><,;| *()(%%$^/\\\[\]] # Next character can't be... [^"'><,;|()]{1,}) # Rest of the characters can't be | ([a-zA-Z0-9_\-/]{1,}/ # Relative endpoint with / [a-zA-Z0-9_\-/]{1,} # Resource name \.(?:[a-zA-Z]{1,4}|action) # Rest + extension (length 1-4 or action) (?:[\?|/][^"|']{0,}|)) # ? mark with parameters | ([a-zA-Z0-9_\-]{1,} # filename \.(?:php|asp|aspx|jsp|json| action|html|js|txt|xml) # . + extension (?:\?[^"|']{0,}|)) # ? mark with parameters ) (?:"|') # End newline delimiter """ def parser_file(self, content, regex_str, mode=1, more_regex=None, no_dup=1): #print ("TEST parselfile #2") regex = re.compile(regex_str, re.VERBOSE) items = [{"link": m.group(1)} for m in re.finditer(regex, content)] if no_dup: # Remove duplication all_links = set() no_dup_items = [] for item in items: if item["link"] not in all_links: all_links.add(item["link"]) no_dup_items.append(item) items = no_dup_items # Match Regex filtered_items = [] for item in items: # Remove other capture groups from regex results if more_regex: if re.search(more_regex, item["link"]): #print ("TEST parselfile #3") filtered_items.append(item) else: filtered_items.append(item) return filtered_items # Potential for use in the future... def threadAnalysis(self): thread = Thread(target=self.analyseURL(), args=(session,)) thread.daemon = True thread.start() def analyseURL(self): endpoints = "" #print("TEST AnalyseURL #1") mime_type=self.helpers.analyzeResponse(self.reqres.getResponse()).getStatedMimeType() if mime_type.lower() == 'script': url = self.reqres.getUrl() encoded_resp=binascii.b2a_base64(self.reqres.getResponse()) decoded_resp=base64.b64decode(encoded_resp) endpoints=self.parser_file(decoded_resp, self.regex_str) #print("TEST AnalyseURL #2") return endpoints return endpoints class SRI(IScanIssue,ITab): def __init__(self, reqres, helpers): self.helpers = helpers self.reqres = reqres def getHost(self): return self.reqres.getHost() def getPort(self): return self.reqres.getPort() def getProtocol(self): return self.reqres.getProtocol() def getUrl(self): return self.reqres.getUrl() def getIssueName(self): return "Linkfinder Analysed JS files" def getIssueType(self): return 0x08000000 # See http:#portswigger.net/burp/help/scanner_issuetypes.html def getSeverity(self): return "Information" # "High", "Medium", "Low", "Information" or "False positive" def getConfidence(self): return "Certain" # "Certain", "Firm" or "Tentative" def getIssueBackground(self): return str("JS files holds links to other parts of web applications. Refer to TAB for results.") def getRemediationBackground(self): return "This is an <b>informational</b> finding only.<br>" def getIssueDetail(self): return str("Burp Scanner has analysed the following JS file for links: <b>" "%s</b><br><br>" % (self.reqres.getUrl().toString())) def getRemediationDetail(self): return None def getHttpMessages(self): #print ("................raising issue................") rra = [self.reqres] return rra def getHttpService(self): return self.reqres.getHttpService() if __name__ in ('__main__', 'main'): EventQueue.invokeLater(Run(BurpExtender))