Projects STRLCPY headers-analyzer Commits 7e733bf1
🤬
  • ■ ■ ■ ■ ■ ■
    BoringHeaders.txt
     1 +accept-ranges
     2 +access-control-allow-origin
     3 +age
     4 +allow
     5 +alternate-protocol
     6 +alternates
     7 +authorization
     8 +cache-control
     9 +connection
     10 +cneonction
     11 +content-encoding
     12 +content-language
     13 +content-length
     14 +content-location
     15 +content-md5
     16 +content-range
     17 +content-type
     18 +dasl
     19 +date
     20 +dav
     21 +edge-control
     22 +etag
     23 +expect
     24 +expires
     25 +from
     26 +header
     27 +host
     28 +if-match
     29 +if-modified-since
     30 +if-none-match
     31 +if-range
     32 +if-unmodified-since
     33 +keep-alive
     34 +last-modified
     35 +lfy
     36 +link
     37 +location
     38 +max-forwards
     39 +mime-version
     40 +nncoection
     41 +p3p
     42 +persistent-auth
     43 +pragma
     44 +proxy-authenticate
     45 +proxy-authorization
     46 +proxy-connection
     47 +public
     48 +range
     49 +referer
     50 +resin-trace
     51 +retry-after
     52 +rlogid
     53 +set-cookie
     54 +sfy
     55 +status
     56 +strict-transport-security
     57 +te
     58 +trailer
     59 +transfer-encoding
     60 +upgrade
     61 +vary
     62 +via
     63 +warning
     64 +whisker
     65 +www-authenticate
     66 +x-amz-id-1
     67 +x-amz-id-2
     68 +x-auto-login
     69 +x-cache
     70 +x-cache-hits
     71 +x-cnection
     72 +x-content-type-options
     73 +x-drupal-cache
     74 +x-frame-options
     75 +x-fs-uuid
     76 +x-id
     77 +x-li-fabric
     78 +x-li-pop
     79 +x-li-uuid
     80 +x-msedge-ref
     81 +x-nc
     82 +x-pad
     83 +x-pingback
     84 +x-rs-ops
     85 +x-rs-time
     86 +x-ua-compatible
     87 +x-varnish
     88 +x-varnish-cache
     89 +x-xrds-location
     90 +x-xss-protection
     91 +xmlns
  • ■ ■ ■ ■ ■ ■
    HeadersAnalyzer.py
     1 +# Copyright (c) 2014, Antonio Sanchez
     2 +# All rights reserved.
     3 +#
     4 +# Redistribution and use in source and binary forms, with or without
     5 +# modification, are permitted provided that the following conditions are met:
     6 +#
     7 +# * Redistributions of source code must retain the above copyright notice, this
     8 +# list of conditions and the following disclaimer.
     9 +#
     10 +# * Redistributions in binary form must reproduce the above copyright notice,
     11 +# this list of conditions and the following disclaimer in the documentation
     12 +# and/or other materials provided with the distribution.
     13 +#
     14 +# * Neither the name of the author nor the names of its
     15 +# contributors may be used to endorse or promote products derived from
     16 +# this software without specific prior written permission.
     17 +#
     18 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     19 +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     20 +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21 +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
     22 +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     23 +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     24 +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
     25 +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     26 +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 +
     29 +from burp import IBurpExtender
     30 +from burp import IScannerCheck
     31 +from burp import IScanIssue
     32 +from burp import ITab
     33 +from javax import swing
     34 +from java.awt import Font
     35 +from java.awt.datatransfer import StringSelection
     36 +from java.awt.datatransfer import DataFlavor
     37 +from java.awt import Toolkit
     38 +import java.lang as lang
     39 +import re
     40 +
     41 +class BurpExtender(IBurpExtender, IScannerCheck, ITab):
     42 +
     43 + def registerExtenderCallbacks(self, callbacks):
     44 +
     45 + print "Loading...\n"
     46 +
     47 + self._callbacks = callbacks
     48 + self._callbacks.setExtensionName("Headers Analyzer")
     49 + self._callbacks.registerScannerCheck(self)
     50 +
     51 + self.initGui()
     52 + self._callbacks.addSuiteTab(self)
     53 +
     54 + # Variable to keep a browsable structure of the issues find on each host
     55 + # later used in the export function.
     56 + self.global_issues = {}
     57 +
     58 + print "Loaded!\n"
     59 +
     60 + return
     61 +
     62 + def initGui(self):
     63 +
     64 + # Define elements
     65 + self.tab = swing.JPanel()
     66 + self.settingsLabel = swing.JLabel("Settings:")
     67 + self.settingsLabel.setFont(Font("Tahoma", 1, 12));
     68 + self.boringHeadersLabel = swing.JLabel("Boring Headers")
     69 + self.pasteButton = swing.JButton("Paste", actionPerformed=self.paste)
     70 + self.loadButton = swing.JButton("Load", actionPerformed=self.load)
     71 + self.removeButton = swing.JButton("Remove", actionPerformed=self.remove)
     72 + self.clearButton = swing.JButton("Clear", actionPerformed=self.clear)
     73 + self.jScrollPane1 = swing.JScrollPane()
     74 + self.boringHeadersList = swing.JList()
     75 + self.addButton = swing.JButton("Add", actionPerformed=self.add)
     76 + self.addTF = swing.JTextField("New item...", focusGained=self.emptyTF, focusLost=self.fillTF)
     77 + self.interestingHeadersCB = swing.JCheckBox("Check for Interesting Headers")
     78 + self.securityHeadersCB = swing.JCheckBox("Check for Security Headers", actionPerformed=self.onSelect)
     79 + self.xFrameOptionsCB = swing.JCheckBox("X-Frame-Options")
     80 + self.xContentTypeOptionsCB = swing.JCheckBox("X-Content-Type-Options")
     81 + self.xXssProtectionCB = swing.JCheckBox("X-XSS-Protection")
     82 + self.HstsCB = swing.JCheckBox("Strict-Transport-Security (HSTS)")
     83 + self.CorsCB = swing.JCheckBox("Access-Control-Allow-Origin (CORS)")
     84 + self.contentSecurityPolicyCB = swing.JCheckBox("Content-Security-Policy")
     85 + self.xPermittedCrossDomainPoliciesCB = swing.JCheckBox("X-Permitted-Cross-Domain-Policies")
     86 + self.outputLabel = swing.JLabel("Output:")
     87 + self.outputLabel.setFont(Font("Tahoma", 1, 12));
     88 + self.logsLabel = swing.JLabel("Logs")
     89 + self.jScrollPane2 = swing.JScrollPane()
     90 + self.logsTA = swing.JTextArea()
     91 + self.exportButton = swing.JButton("Export in report friendly format", actionPerformed=self.export)
     92 +
     93 + self.jScrollPane1.setViewportView(self.boringHeadersList)
     94 + self.interestingHeadersCB.setSelected(True)
     95 + self.securityHeadersCB.setSelected(True)
     96 + self.xFrameOptionsCB.setSelected(True)
     97 + self.xContentTypeOptionsCB.setSelected(True)
     98 + self.xXssProtectionCB.setSelected(True)
     99 + self.HstsCB.setSelected(True)
     100 + self.CorsCB.setSelected(True)
     101 + self.contentSecurityPolicyCB.setSelected(True)
     102 + self.xPermittedCrossDomainPoliciesCB.setSelected(True)
     103 + self.logsTA.setColumns(20)
     104 + self.logsTA.setRows(7)
     105 + self.jScrollPane2.setViewportView(self.logsTA)
     106 +
     107 + # Configure layout
     108 +
     109 + layout = swing.GroupLayout(self.tab)
     110 + self.tab.setLayout(layout)
     111 + layout.setHorizontalGroup(
     112 + layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     113 + .addGroup(layout.createSequentialGroup()
     114 + .addGap(33, 33, 33)
     115 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     116 + .addGroup(layout.createSequentialGroup()
     117 + .addGap(83, 83, 83)
     118 + .addComponent(self.boringHeadersLabel))
     119 + .addComponent(self.settingsLabel)
     120 + .addGroup(layout.createSequentialGroup()
     121 + .addComponent(self.interestingHeadersCB)
     122 + .addGap(149, 149, 149)
     123 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     124 + .addComponent(self.securityHeadersCB)
     125 + .addComponent(self.HstsCB)
     126 + .addGroup(layout.createSequentialGroup()
     127 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     128 + .addGroup(swing.GroupLayout.Alignment.TRAILING, layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     129 + .addGroup(layout.createSequentialGroup()
     130 + .addComponent(self.xFrameOptionsCB)
     131 + .addGap(83, 83, 83))
     132 + .addGroup(layout.createSequentialGroup()
     133 + .addComponent(self.xContentTypeOptionsCB)
     134 + .addGap(47, 47, 47)))
     135 + .addGroup(layout.createSequentialGroup()
     136 + .addComponent(self.xXssProtectionCB)
     137 + .addGap(83, 83, 83)))
     138 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     139 + .addComponent(self.xPermittedCrossDomainPoliciesCB)
     140 + .addComponent(self.contentSecurityPolicyCB)
     141 + .addComponent(self.CorsCB)))))
     142 + .addGroup(layout.createSequentialGroup()
     143 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.TRAILING)
     144 + .addComponent(self.addButton)
     145 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     146 + .addComponent(self.outputLabel)
     147 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.TRAILING, False)
     148 + .addComponent(self.removeButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE)
     149 + .addComponent(self.pasteButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE)
     150 + .addComponent(self.loadButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE)
     151 + .addComponent(self.clearButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.PREFERRED_SIZE, lang.Short.MAX_VALUE))))
     152 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
     153 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     154 + .addComponent(self.jScrollPane1, swing.GroupLayout.PREFERRED_SIZE, 200, swing.GroupLayout.PREFERRED_SIZE)
     155 + .addComponent(self.addTF, swing.GroupLayout.PREFERRED_SIZE, 200, swing.GroupLayout.PREFERRED_SIZE)
     156 + .addComponent(self.jScrollPane2, swing.GroupLayout.PREFERRED_SIZE, 450, swing.GroupLayout.PREFERRED_SIZE)
     157 + .addComponent(self.logsLabel)
     158 + .addComponent(self.exportButton))))
     159 + .addContainerGap(26, lang.Short.MAX_VALUE))
     160 + )
     161 +
     162 + layout.setVerticalGroup(
     163 + layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     164 + .addGroup(layout.createSequentialGroup()
     165 + .addGap(41, 41, 41)
     166 + .addComponent(self.settingsLabel)
     167 + .addGap(31, 31, 31)
     168 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
     169 + .addComponent(self.interestingHeadersCB)
     170 + .addComponent(self.securityHeadersCB))
     171 + .addGap(26, 26, 26)
     172 + .addComponent(self.boringHeadersLabel)
     173 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
     174 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     175 + .addGroup(layout.createSequentialGroup()
     176 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     177 + .addGroup(layout.createSequentialGroup()
     178 + .addComponent(self.pasteButton)
     179 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
     180 + .addComponent(self.loadButton)
     181 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
     182 + .addComponent(self.removeButton)
     183 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
     184 + .addComponent(self.clearButton))
     185 + .addComponent(self.jScrollPane1, swing.GroupLayout.PREFERRED_SIZE, 138, swing.GroupLayout.PREFERRED_SIZE))
     186 + .addGap(18, 18, 18)
     187 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
     188 + .addComponent(self.addButton)
     189 + .addComponent(self.addTF, swing.GroupLayout.PREFERRED_SIZE, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.PREFERRED_SIZE)))
     190 + .addGroup(layout.createSequentialGroup()
     191 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
     192 + .addComponent(self.xFrameOptionsCB)
     193 + .addComponent(self.CorsCB))
     194 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
     195 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
     196 + .addComponent(self.xContentTypeOptionsCB)
     197 + .addComponent(self.contentSecurityPolicyCB, swing.GroupLayout.Alignment.TRAILING))
     198 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
     199 + .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
     200 + .addComponent(self.xXssProtectionCB)
     201 + .addComponent(self.xPermittedCrossDomainPoliciesCB))
     202 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
     203 + .addComponent(self.HstsCB)))
     204 + .addGap(30, 30, 30)
     205 + .addComponent(self.outputLabel)
     206 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
     207 + .addComponent(self.logsLabel)
     208 + .addGap(8, 8, 8)
     209 + .addComponent(self.jScrollPane2, swing.GroupLayout.PREFERRED_SIZE, 250, swing.GroupLayout.PREFERRED_SIZE)
     210 + .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
     211 + .addComponent(self.exportButton)
     212 + .addContainerGap(swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE))
     213 + )
     214 +
     215 + # ITab
     216 + def getTabCaption(self):
     217 + return("Headers Analyzer")
     218 +
     219 + def getUiComponent(self):
     220 + return self.tab
     221 +
     222 + def consolidateDuplicateIssues(self, existingIssue, newIssue):
     223 + if (existingIssue.getIssueName() == newIssue.getIssueName()):
     224 + return -1
     225 + else:
     226 + return 0
     227 +
     228 + # Event listeners
     229 + def emptyTF(self,e):
     230 + source = e.getSource()
     231 + if source.getText() == "New item...":
     232 + source.setText("")
     233 +
     234 + def fillTF(self,e):
     235 + source = e.getSource()
     236 + if not source.getText():
     237 + source.setText("New item...")
     238 +
     239 + def onSelect(self, e):
     240 + source = e.getSource()
     241 + if source.isSelected():
     242 + self.xFrameOptionsCB.setEnabled(True)
     243 + self.xContentTypeOptionsCB.setEnabled(True)
     244 + self.xXssProtectionCB.setEnabled(True)
     245 + self.HstsCB.setEnabled(True)
     246 + self.CorsCB.setEnabled(True)
     247 + self.contentSecurityPolicyCB.setEnabled(True)
     248 + self.xPermittedCrossDomainPoliciesCB.setEnabled(True)
     249 + else:
     250 + self.xFrameOptionsCB.setEnabled(False)
     251 + self.xContentTypeOptionsCB.setEnabled(False)
     252 + self.xXssProtectionCB.setEnabled(False)
     253 + self.HstsCB.setEnabled(False)
     254 + self.CorsCB.setEnabled(False)
     255 + self.contentSecurityPolicyCB.setEnabled(False)
     256 + self.xPermittedCrossDomainPoliciesCB.setEnabled(False)
     257 +
     258 + def paste(self, e):
     259 + clipboard = self.getClipboardText()
     260 +
     261 + if clipboard != None and clipboard != "":
     262 + model = self.boringHeadersList.getModel()
     263 + lines = clipboard.split('\n')
     264 + current = []
     265 +
     266 + for i in range(0, model.getSize()):
     267 + current.append(model.getElementAt(i))
     268 +
     269 + for line in lines:
     270 + if line not in current and not line.isspace():
     271 + current.append(line)
     272 +
     273 + self.boringHeadersList.setListData(current)
     274 +
     275 + def clear(self, e):
     276 + empty = []
     277 + self.boringHeadersList.setListData(empty)
     278 +
     279 + def remove(self, e):
     280 + indices = self.boringHeadersList.getSelectedIndices().tolist()
     281 + model = self.boringHeadersList.getModel()
     282 + current = []
     283 +
     284 + for i in range(0, model.getSize()):
     285 + current.append(model.getElementAt(i))
     286 +
     287 + for index in reversed(indices):
     288 + del current[index]
     289 +
     290 + self.boringHeadersList.setListData(current)
     291 +
     292 + def load(self, e):
     293 + chooseFile = swing.JFileChooser()
     294 + ret = chooseFile.showDialog(self.tab, "Choose file")
     295 +
     296 + if ret == swing.JFileChooser.APPROVE_OPTION:
     297 + file = chooseFile.getSelectedFile()
     298 + filename = file.getCanonicalPath()
     299 + try:
     300 + f = open(filename, "r")
     301 + text = f.readlines()
     302 +
     303 + if text:
     304 + text = [line for line in text if not line.isspace()]
     305 + text = [line.rstrip('\n') for line in text]
     306 + self.boringHeadersList.setListData(text)
     307 + except IOError as e:
     308 + print "Error reading file.\n" + str(e)
     309 +
     310 + def add(self, e):
     311 + source = e.getSource()
     312 + model = self.boringHeadersList.getModel()
     313 + current = []
     314 +
     315 + for i in range(0, model.getSize()):
     316 + current.append(model.getElementAt(i))
     317 +
     318 + current.append(self.addTF.getText())
     319 + self.boringHeadersList.setListData(current)
     320 +
     321 + self.addTF.setText("New item...")
     322 +
     323 + # Browses the "global_issues" var.
     324 + def export(self, e):
     325 + output = ""
     326 +
     327 + for host,headers in self.global_issues.iteritems(): # For each host
     328 + output += "\nHost: " + host
     329 +
     330 + for issue, headers_list in headers.iteritems(): # For each type of issue (interesting, missing, misconfigured)
     331 + if len(headers_list) > 0:
     332 + output += "\n" + issue + ":\n"
     333 +
     334 + for item in headers_list: # For each header found in that type of issue
     335 + output += item + "\n"
     336 +
     337 + self.setClipboardText(output)
     338 + print output
     339 +
     340 + swing.JOptionPane.showMessageDialog(self.tab, "Output copied to the clipboard and sent to standard output!", "Information", swing.JOptionPane.INFORMATION_MESSAGE)
     341 +
     342 + # Aux functions to get and set system clipboard
     343 + def getClipboardText(self):
     344 + clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
     345 + contents = clipboard.getContents(None)
     346 + gotText = (contents != None) and contents.isDataFlavorSupported(DataFlavor.stringFlavor)
     347 +
     348 + if gotText:
     349 + return contents.getTransferData(DataFlavor.stringFlavor)
     350 + else:
     351 + return None
     352 +
     353 + def setClipboardText(self, text):
     354 + clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
     355 + clipboard.setContents(StringSelection(text), None)
     356 +
     357 + # Burp Scanner invokes this method for each base request/response that is passively scanned.
     358 + def doPassiveScan(self, baseRequestResponse):
     359 + self._requestResponse = baseRequestResponse
     360 +
     361 + scan_issues = []
     362 + scan_issues = self.findHeaders()
     363 +
     364 + # doPassiveScan needs to return a list of scan issues, if any, and None otherwise
     365 + if len(scan_issues) > 0:
     366 + return scan_issues
     367 + else:
     368 + return None
     369 +
     370 + def findHeaders(self):
     371 + self._helpers = self._callbacks.getHelpers()
     372 + self.scan_issues = []
     373 +
     374 + response = self._requestResponse.getResponse()
     375 + requestInfo = self._helpers.analyzeResponse(response)
     376 + headers = requestInfo.getHeaders()
     377 + headers_dict = {}
     378 +
     379 + host = self._requestResponse.getHttpService().getHost()
     380 +
     381 + # If host hasn't been scanned before, we create it in global_issues
     382 + if host not in self.global_issues:
     383 + self.global_issues[host] ={}
     384 + self.global_issues[host]["Interesting"] = []
     385 + self.global_issues[host]["Missing"] = []
     386 + self.global_issues[host]["Misconfigured"] = []
     387 +
     388 + # Store headers in a dict to facilitate their manipulation
     389 + for header in headers:
     390 + header_split = header.split(':', 1)
     391 + if len(header_split) > 1: # To get rid of "HTTP/1.1 200 OK" and other response codes
     392 + headers_dict[header_split[0].lower()] = header_split[1]
     393 +
     394 + if self.interestingHeadersCB.isSelected():
     395 + self.findInteresting(headers_dict)
     396 +
     397 + if self.securityHeadersCB.isSelected():
     398 + self.findSecure(headers_dict)
     399 +
     400 + return (self.scan_issues)
     401 +
     402 + def findInteresting(self, headers):
     403 + list_boring_headers = []
     404 + model = self.boringHeadersList.getModel()
     405 +
     406 + # Get list of boring headers from the GUI JList
     407 + for i in range(0, model.getSize()):
     408 + list_boring_headers.append(model.getElementAt(i))
     409 +
     410 + issuename = "Interesting Header(s)"
     411 + issuelevel = "Low"
     412 + issuedetail = "<p>The response includes the following potentially interesting headers:</p><ul>"
     413 + log = "[+] Interesting Headers found:\n"
     414 + found = 0
     415 +
     416 + for header in headers:
     417 + if header.lower() not in list_boring_headers:
     418 + issuedetail += "<li>Header name: <b>" + header + "</b>. Header value: <b>" + headers[header] + "</b></li>"
     419 +
     420 + log += " Header name:" + header + " Header value:" + headers[header] + "\n"
     421 +
     422 + host = self._requestResponse.getHttpService().getHost()
     423 + report = header + ":" + headers[header]
     424 + if report not in self.global_issues[host]["Interesting"]: # If header not already in the list we store it
     425 + self.global_issues[host]["Interesting"].append(report)
     426 +
     427 + found += 1
     428 +
     429 + issuedetail += "</ul>"
     430 +
     431 + if found > 0:
     432 + # Create a ScanIssue object and append it to our list of issues, marking the reflected parameter value in the response.
     433 + self.scan_issues.append(ScanIssue(self._requestResponse.getHttpService(),
     434 + self._helpers.analyzeRequest(self._requestResponse).getUrl(),
     435 + issuename, issuelevel, issuedetail))
     436 +
     437 + self.logsTA.append(log)
     438 +
     439 + def findSecure(self, headers):
     440 + issuename = "Lack or Misconfiguration of Security Header(s)"
     441 + issuelevel = "Low"
     442 + issuedetail = """<p>The response lacks or includes the following misconfigured security headers.</p>
     443 + <p>Please note that some of these issues could be false positives, a manual review
     444 + is recommended</p><br>
     445 + """
     446 + badheaders = []
     447 + missingsecurity = []
     448 +
     449 + if self.xFrameOptionsCB.isSelected():
     450 + # X-Frame-Options
     451 + try:
     452 + m = re.search("SAMEORIGIN|DENY", headers["x-frame-options"], re.IGNORECASE)
     453 + if not m:
     454 + badheaders.append("x-frame-options")
     455 + except Exception as e:
     456 + missingsecurity.append("x-frame-options")
     457 +
     458 + if self.xContentTypeOptionsCB.isSelected():
     459 + # X-Content-Type-Options: nosniff
     460 + try:
     461 + m = re.search("nosniff", headers["x-content-type-options"], re.IGNORECASE)
     462 + if not m:
     463 + badheaders.append("x-content-type-options")
     464 + except Exception as e:
     465 + missingsecurity.append("x-content-type-options")
     466 +
     467 + if self.xXssProtectionCB.isSelected():
     468 + # X-XSS-Protection
     469 + try:
     470 + m = re.search("0", headers["x-xss-protection"], re.IGNORECASE)
     471 + if not m:
     472 + badheaders.append("x-xss-protection")
     473 + except Exception as e:
     474 + pass
     475 +
     476 + if self.HstsCB.isSelected():
     477 + # Strict-Transport-Security (HSTS)
     478 + try:
     479 + m = re.search("max-age=(\d+)", headers["strict-transport-security"], re.IGNORECASE)
     480 + if int(m.group(1)) < (60*60*24 * 30): # Flag if less than 30 days
     481 + badheaders.append("strict-transport-security")
     482 + except Exception as e:
     483 + missingsecurity.append("strict-transport-security")
     484 +
     485 + if self.CorsCB.isSelected():
     486 + # Access-Control-Allow-Origin (CORS)
     487 + try:
     488 + m = re.search("\*", headers["access-control-allow-origin"], re.IGNORECASE)
     489 + if not m:
     490 + badheaders.append("x-xss-protection")
     491 + except Exception as e:
     492 + pass
     493 +
     494 + if self.contentSecurityPolicyCB.isSelected():
     495 + # Content-Security-Policy
     496 + if not ("content-security-policy" in headers or "x-content-security-policy" in headers or "x-webkit-csp" in headers):
     497 + missingsecurity.append("content-security-policy")
     498 +
     499 + if self.xPermittedCrossDomainPoliciesCB.isSelected():
     500 + # X-Permitted-Cross-Domain-Policies
     501 + try:
     502 + m = re.search("master-only", headers["x-permitted-cross-domain-policies"], re.IGNORECASE)
     503 + if not m:
     504 + badheaders.append("x-permitted-cross-domain-policies")
     505 + except Exception as e:
     506 + missingsecurity.append("x-permitted-cross-domain-policies")
     507 +
     508 + if len(badheaders) > 0 or len(missingsecurity) > 0:
     509 + if len(badheaders) > 0:
     510 + issuedetail += "<p>Potentially misconfigured headers:</p><ul>"
     511 + log = "[+] Potentially miconfigured headers found:\n"
     512 +
     513 + for bad in badheaders:
     514 + issuedetail += "<li>Header name: <b>" + bad + "</b>. Header value: <b>" + headers[bad] + "</b></li>"
     515 +
     516 + log += " Header name:" + bad + " Header value:" + headers[bad] + "\n"
     517 +
     518 + host = self._requestResponse.getHttpService().getHost()
     519 + report = bad + ":" + headers[bad]
     520 + if report not in self.global_issues[host]["Misconfigured"]: # If header not already in the list we store it
     521 + self.global_issues[host]["Misconfigured"].append(report)
     522 +
     523 + issuedetail += "</ul>"
     524 +
     525 + self.logsTA.append(log)
     526 +
     527 + if len(missingsecurity) > 0:
     528 + issuedetail += "<p>Missing headers:</p><ul>"
     529 + log = "[+] Missing headers found:\n"
     530 +
     531 + for missing in missingsecurity:
     532 + issuedetail += "<li>Header name: <b>" + missing + "</b>.</li>"
     533 + log += " Header name:" + missing + "\n"
     534 +
     535 + host = self._requestResponse.getHttpService().getHost()
     536 + if missing not in self.global_issues[host]["Missing"]: # If header not already in the list we store it
     537 + self.global_issues[host]["Missing"].append(missing)
     538 +
     539 + issuedetail += "</ul>"
     540 +
     541 + self.logsTA.append(log)
     542 +
     543 + # Create a ScanIssue object and append it to our list of issues, marking
     544 + # the reflected parameter value in the response.
     545 + self.scan_issues.append(ScanIssue(self._requestResponse.getHttpService(),
     546 + self._helpers.analyzeRequest(self._requestResponse).getUrl(),
     547 + issuename, issuelevel, issuedetail))
     548 +
     549 +# Implementation of the IScanIssue interface with simple constructor and getter methods
     550 +class ScanIssue(IScanIssue):
     551 + def __init__(self, httpservice, url, name, severity, detailmsg):
     552 + self._url = url
     553 + self._httpservice = httpservice
     554 + self._name = name
     555 + self._severity = severity
     556 + self._detailmsg = detailmsg
     557 +
     558 + def getUrl(self):
     559 + return self._url
     560 +
     561 + def getHttpMessages(self):
     562 + return None
     563 +
     564 + def getHttpService(self):
     565 + return self._httpservice
     566 +
     567 + def getRemediationDetail(self):
     568 + return None
     569 +
     570 + def getIssueDetail(self):
     571 + return self._detailmsg
     572 +
     573 + def getIssueBackground(self):
     574 + return None
     575 +
     576 + def getRemediationBackground(self):
     577 + return None
     578 +
     579 + def getIssueType(self):
     580 + return 0
     581 +
     582 + def getIssueName(self):
     583 + return self._name
     584 +
     585 + def getSeverity(self):
     586 + return self._severity
     587 +
     588 + def getConfidence(self):
     589 + return "Certain"
     590 + 
  • ■ ■ ■ ■ ■ ■
    LICENSE
     1 +Copyright (c) 2014, Antonio Sanchez
     2 +All rights reserved.
     3 + 
     4 +Redistribution and use in source and binary forms, with or without
     5 +modification, are permitted provided that the following conditions are met:
     6 + 
     7 +* Redistributions of source code must retain the above copyright notice, this
     8 + list of conditions and the following disclaimer.
     9 + 
     10 +* Redistributions in binary form must reproduce the above copyright notice,
     11 + this list of conditions and the following disclaimer in the documentation
     12 + and/or other materials provided with the distribution.
     13 + 
     14 +* Neither the name of the author nor the names of its
     15 + contributors may be used to endorse or promote products derived from
     16 + this software without specific prior written permission.
     17 + 
     18 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     19 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     20 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
     22 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     23 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     24 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
     25 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     26 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 + 
  • ■ ■ ■ ■ ■ ■
    Python.gitignore
     1 +# Byte-compiled / optimized / DLL files
     2 +__pycache__/
     3 +*.py[cod]
     4 + 
     5 +# C extensions
     6 +*.so
     7 + 
     8 +# Distribution / packaging
     9 +.Python
     10 +env/
     11 +build/
     12 +develop-eggs/
     13 +dist/
     14 +eggs/
     15 +lib/
     16 +lib64/
     17 +parts/
     18 +sdist/
     19 +var/
     20 +*.egg-info/
     21 +.installed.cfg
     22 +*.egg
     23 + 
     24 +# PyInstaller
     25 +# Usually these files are written by a python script from a template
     26 +# before PyInstaller builds the exe, so as to inject date/other infos into it.
     27 +*.manifest
     28 +*.spec
     29 + 
     30 +# Installer logs
     31 +pip-log.txt
     32 +pip-delete-this-directory.txt
     33 + 
     34 +# Unit test / coverage reports
     35 +htmlcov/
     36 +.tox/
     37 +.coverage
     38 +.cache
     39 +nosetests.xml
     40 +coverage.xml
     41 + 
     42 +# Translations
     43 +*.mo
     44 +*.pot
     45 + 
     46 +# Django stuff:
     47 +*.log
     48 + 
     49 +# Sphinx documentation
     50 +docs/_build/
     51 + 
     52 +# PyBuilder
     53 +target/
     54 + 
  • ■ ■ ■ ■ ■ ■
    README.md
     1 +Headers Analyzer
     2 +================
     3 + 
     4 +Headers Analyzer is a Burp extension written in Python that making use of the "Passive Scanner" functionality checks for:
     5 + 
     6 + - Headers that might disclose some interesting information.
     7 + - Missing security headers.
     8 + - Misconfigured security headers
     9 + 
     10 +Issues found will be reported and added to the passive scanner tab in a similar way to the native issues reported by Burp.
     11 + 
     12 +The extension will add a new tab to Burp's UI to allow the user to configure it, including the following aspects:
     13 + 
     14 + - Select what kind of headers the extension should analyze.
     15 + - Boring headers, a list of boring headers that the extension will omit.
     16 + - Export the results in a "report friendly" format.
     17 + 
     18 +An additional file "BoringHeaders.txt" is included apart from the extension. This file includes a predefined list of boring headers that might prove useful for the user.
     19 + 
     20 +Version
     21 +----
     22 + 
     23 +1.0
     24 + 
     25 + 
     26 +Installation
     27 +--------------
     28 + 
     29 +Jython is needed for this extension to work properly, so remember to set it up in Burp before adding the extension.
     30 +After that, just add a new extension in the "Extensions" tab, choose "Python" as the extension type, and point to the "HeadersAnalyzer.py" file.
     31 + 
     32 +Check the "Output" and "Errors" tabs for possible feedback.
     33 + 
Please wait...
Page is in error, reload to recover