# Copyright (c) 2014, Antonio Sanchez
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the author nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from burp import IBurpExtender
from burp import IScannerCheck
from burp import IScanIssue
from burp import ITab
from burp import IExtensionStateListener
from javax import swing
from java.awt import Font
from java.awt.datatransfer import StringSelection
from java.awt.datatransfer import DataFlavor
from java.awt import Toolkit
import java.lang as lang
import re
import pickle

class BurpExtender(IBurpExtender, IScannerCheck, ITab, IExtensionStateListener):

    def	registerExtenderCallbacks(self, callbacks):
        
        print "Loading..."

        self._callbacks = callbacks
        self._callbacks.setExtensionName("Headers Analyzer")
        self._callbacks.registerScannerCheck(self)
        self._callbacks.registerExtensionStateListener(self)
        
        self.initGui()
        self._callbacks.addSuiteTab(self)
        self.extensionLoaded()

        # Variable to keep a browsable structure of the issues find on each host
        # later used in the export function.
        self.global_issues = {} 

        print "Loaded!"

        return

    def saveExtensionSetting(self, name, value):
        try:
            self._callbacks.saveExtensionSetting(name, value)
        except Exception:
            print ('Error saving extension settings')

    # Save current settings when the extension is unloaded or Burp is closed
    def extensionUnloaded(self):
        config = {
            'interestingHeadersCB' : self.interestingHeadersCB.isSelected(),
            'securityHeadersCB' : self.securityHeadersCB.isSelected(),
            'xFrameOptionsCB' : self.xFrameOptionsCB.isSelected(),
            'xContentTypeOptionsCB' : self.xContentTypeOptionsCB.isSelected(),
            'xXssProtectionCB' : self.xXssProtectionCB.isSelected(),
            'HstsCB' : self.HstsCB.isSelected(),
            'CorsCB' : self.CorsCB.isSelected(),
            'contentSecurityPolicyCB' : self.contentSecurityPolicyCB.isSelected(),
            'xPermittedCrossDomainPoliciesCB' : self.xPermittedCrossDomainPoliciesCB.isSelected(),
            'boringHeadersList' : self.getBoringHeadersList() 
        }
        
        for key, value in config.iteritems():   # For each config value
            self.saveExtensionSetting(key, pickle.dumps(value))

        return

    # Restore last configuration
    def extensionLoaded(self):
        try:
            self.interestingHeadersCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('interestingHeadersCB')))
            self.securityHeadersCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('securityHeadersCB')))
            self.xFrameOptionsCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('xFrameOptionsCB')))
            self.xContentTypeOptionsCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('xContentTypeOptionsCB')))
            self.xXssProtectionCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('xXssProtectionCB')))
            self.HstsCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('HstsCB')))
            self.CorsCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('CorsCB')))
            self.contentSecurityPolicyCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('contentSecurityPolicyCB')))
            self.xPermittedCrossDomainPoliciesCB.setSelected(pickle.loads(self._callbacks.loadExtensionSetting('xPermittedCrossDomainPoliciesCB')))
            self.boringHeadersList.setListData(pickle.loads(self._callbacks.loadExtensionSetting('boringHeadersList')))

            print "Extension settings restored!"
        except:
            self.interestingHeadersCB.setSelected(True)
            self.securityHeadersCB.setSelected(True)
            self.xFrameOptionsCB.setSelected(True)
            self.xContentTypeOptionsCB.setSelected(True)
            self.xXssProtectionCB.setSelected(True)
            self.HstsCB.setSelected(True)
            self.CorsCB.setSelected(True)
            self.contentSecurityPolicyCB.setSelected(True)
            self.xPermittedCrossDomainPoliciesCB.setSelected(True)
            empty = []
            self.boringHeadersList.setListData(empty)

            print "Error restoring extension settings (first time loading the extension?)"

    def initGui(self):

        # Define elements        
        self.tab = swing.JPanel()
        self.settingsLabel = swing.JLabel("Settings:")
        self.settingsLabel.setFont(Font("Tahoma", 1, 12));
        self.boringHeadersLabel = swing.JLabel("Boring Headers")
        self.pasteButton = swing.JButton("Paste", actionPerformed=self.paste)
        self.loadButton = swing.JButton("Load", actionPerformed=self.load)
        self.removeButton = swing.JButton("Remove", actionPerformed=self.remove)
        self.clearButton = swing.JButton("Clear", actionPerformed=self.clear)
        self.jScrollPane1 = swing.JScrollPane()
        self.boringHeadersList = swing.JList()
        self.addButton = swing.JButton("Add", actionPerformed=self.add)
        self.addTF = swing.JTextField("New item...", focusGained=self.emptyTF, focusLost=self.fillTF)
        self.interestingHeadersCB = swing.JCheckBox("Check for Interesting Headers")
        self.securityHeadersCB = swing.JCheckBox("Check for Security Headers", actionPerformed=self.onSelect)
        self.xFrameOptionsCB = swing.JCheckBox("X-Frame-Options")
        self.xContentTypeOptionsCB = swing.JCheckBox("X-Content-Type-Options")
        self.xXssProtectionCB = swing.JCheckBox("X-XSS-Protection")
        self.HstsCB = swing.JCheckBox("Strict-Transport-Security (HSTS)")
        self.CorsCB = swing.JCheckBox("Access-Control-Allow-Origin (CORS)")
        self.contentSecurityPolicyCB = swing.JCheckBox("Content-Security-Policy")
        self.xPermittedCrossDomainPoliciesCB = swing.JCheckBox("X-Permitted-Cross-Domain-Policies")
        self.outputLabel = swing.JLabel("Output:")
        self.outputLabel.setFont(Font("Tahoma", 1, 12));
        self.logsLabel = swing.JLabel("Logs")
        self.jScrollPane2 = swing.JScrollPane()
        self.logsTA = swing.JTextArea()
        self.exportButton = swing.JButton("Export in report friendly format", actionPerformed=self.export)

        self.jScrollPane1.setViewportView(self.boringHeadersList)
        self.logsTA.setColumns(20)
        self.logsTA.setRows(7)
        self.jScrollPane2.setViewportView(self.logsTA)

        # Configure layout

        layout = swing.GroupLayout(self.tab)
        self.tab.setLayout(layout)
        layout.setHorizontalGroup(
            layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(33, 33, 33)
                .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(83, 83, 83)
                        .addComponent(self.boringHeadersLabel))
                    .addComponent(self.settingsLabel)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(self.interestingHeadersCB)
                        .addGap(149, 149, 149)
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                            .addComponent(self.securityHeadersCB)
                            .addComponent(self.HstsCB)
                            .addGroup(layout.createSequentialGroup()
                                .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                                    .addGroup(swing.GroupLayout.Alignment.TRAILING, layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                                        .addGroup(layout.createSequentialGroup()
                                            .addComponent(self.xFrameOptionsCB)
                                            .addGap(83, 83, 83))
                                        .addGroup(layout.createSequentialGroup()
                                            .addComponent(self.xContentTypeOptionsCB)
                                            .addGap(47, 47, 47)))
                                    .addGroup(layout.createSequentialGroup()
                                        .addComponent(self.xXssProtectionCB)
                                        .addGap(83, 83, 83)))
                                .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(self.xPermittedCrossDomainPoliciesCB)
                                    .addComponent(self.contentSecurityPolicyCB)
                                    .addComponent(self.CorsCB)))))
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.TRAILING)
                            .addComponent(self.addButton)
                            .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                                .addComponent(self.outputLabel)
                                .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.TRAILING, False)
                                    .addComponent(self.removeButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE)
                                    .addComponent(self.pasteButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE)
                                    .addComponent(self.loadButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE)
                                    .addComponent(self.clearButton, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.PREFERRED_SIZE, lang.Short.MAX_VALUE))))
                        .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                            .addComponent(self.jScrollPane1, swing.GroupLayout.PREFERRED_SIZE, 200, swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(self.addTF, swing.GroupLayout.PREFERRED_SIZE, 200, swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(self.jScrollPane2, swing.GroupLayout.PREFERRED_SIZE, 450, swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(self.logsLabel)
                            .addComponent(self.exportButton))))
                .addContainerGap(26, lang.Short.MAX_VALUE))
        )

        layout.setVerticalGroup(
            layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(41, 41, 41)
                .addComponent(self.settingsLabel)
                .addGap(31, 31, 31)
                .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(self.interestingHeadersCB)
                    .addComponent(self.securityHeadersCB))
                .addGap(26, 26, 26)
                .addComponent(self.boringHeadersLabel)
                .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(self.pasteButton)
                                .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(self.loadButton)
                                .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(self.removeButton)
                                .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(self.clearButton))
                            .addComponent(self.jScrollPane1, swing.GroupLayout.PREFERRED_SIZE, 138, swing.GroupLayout.PREFERRED_SIZE))
                        .addGap(18, 18, 18)
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(self.addButton)
                            .addComponent(self.addTF, swing.GroupLayout.PREFERRED_SIZE, swing.GroupLayout.DEFAULT_SIZE, swing.GroupLayout.PREFERRED_SIZE)))
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(self.xFrameOptionsCB)
                            .addComponent(self.CorsCB))
                        .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.LEADING)
                            .addComponent(self.xContentTypeOptionsCB)
                            .addComponent(self.contentSecurityPolicyCB, swing.GroupLayout.Alignment.TRAILING))
                        .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(self.xXssProtectionCB)
                            .addComponent(self.xPermittedCrossDomainPoliciesCB))
                        .addPreferredGap(swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(self.HstsCB)))
                .addGap(30, 30, 30)
                .addComponent(self.outputLabel)
                .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(self.logsLabel)
                .addGap(8, 8, 8)
                .addComponent(self.jScrollPane2, swing.GroupLayout.PREFERRED_SIZE, 250, swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(self.exportButton)
                .addContainerGap(swing.GroupLayout.DEFAULT_SIZE, lang.Short.MAX_VALUE))
        )

    # ITab 
    def getTabCaption(self):
        return("Headers Analyzer")

    def getUiComponent(self):
        return self.tab
 
    def consolidateDuplicateIssues(self, existingIssue, newIssue):
        if (existingIssue.getIssueName() == newIssue.getIssueName()):
            return -1
        else:
            return 0

    # Event listeners
    def emptyTF(self,e):
        source = e.getSource()
        if source.getText() == "New item...":
            source.setText("")

    def fillTF(self,e):
        source = e.getSource()
        if not source.getText():
            source.setText("New item...")

    def onSelect(self, e):
        source = e.getSource()
        if source.isSelected():
            self.xFrameOptionsCB.setEnabled(True)
            self.xContentTypeOptionsCB.setEnabled(True)
            self.xXssProtectionCB.setEnabled(True)
            self.HstsCB.setEnabled(True)
            self.CorsCB.setEnabled(True)
            self.contentSecurityPolicyCB.setEnabled(True)
            self.xPermittedCrossDomainPoliciesCB.setEnabled(True)
        else:
            self.xFrameOptionsCB.setEnabled(False)
            self.xContentTypeOptionsCB.setEnabled(False)
            self.xXssProtectionCB.setEnabled(False)
            self.HstsCB.setEnabled(False)
            self.CorsCB.setEnabled(False)
            self.contentSecurityPolicyCB.setEnabled(False)
            self.xPermittedCrossDomainPoliciesCB.setEnabled(False)

    def paste(self, e):
        clipboard = self.getClipboardText()
        
        if clipboard != None and clipboard != "":
            lines = clipboard.split('\n')
            current = self.getBoringHeadersList()
            
            for line in lines:
                if line not in current and not line.isspace():
                    current.append(line)
            
            self.boringHeadersList.setListData(current)

    def clear(self, e):
        empty = []
        self.boringHeadersList.setListData(empty) 

    def remove(self, e):
        indices = self.boringHeadersList.getSelectedIndices().tolist()
        current = self.getBoringHeadersList()

        for index in reversed(indices):   
            del current[index]

        self.boringHeadersList.setListData(current)

    def load(self, e):
        chooseFile = swing.JFileChooser()
        ret = chooseFile.showDialog(self.tab, "Choose file")

        if ret == swing.JFileChooser.APPROVE_OPTION:
            file = chooseFile.getSelectedFile()
            filename = file.getCanonicalPath()
            try:
                f = open(filename, "r")
                text = f.readlines()
        
                if text:
                    text = [line for line in text if not line.isspace()]
                    text = [line.rstrip('\n') for line in text]
                    self.boringHeadersList.setListData(text)
            except IOError as e:
                print "Error reading file.\n" + str(e)

    def add(self, e):
        source = e.getSource()

        current = self.getBoringHeadersList()
        current.append(self.addTF.getText())
        self.boringHeadersList.setListData(current)

        self.addTF.setText("New item...")

    def getBoringHeadersList(self):
        model = self.boringHeadersList.getModel()
        current = []
   
        for i in range(0, model.getSize()):
            current.append(model.getElementAt(i))

        return current


    # Browses the "global_issues" var. 
    def export(self, e):
        output = ""

        for host,headers in self.global_issues.iteritems(): # For each host
            output += "\nHost: " + host 

            for issue, headers_list in headers.iteritems():  # For each type of issue (interesting, missing, misconfigured)
                if len(headers_list) > 0:
                    output += "\n" + issue + ":\n"

                    for item in headers_list:   # For each header found in that type of issue
                        output += item + "\n"

        self.setClipboardText(output)
        print output

        swing.JOptionPane.showMessageDialog(self.tab, "Output copied to the clipboard and sent to standard output!", "Information", swing.JOptionPane.INFORMATION_MESSAGE)
    
    # Aux functions to get and set system clipboard
    def getClipboardText(self):
        clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
        contents = clipboard.getContents(None)
        gotText = (contents != None) and contents.isDataFlavorSupported(DataFlavor.stringFlavor)
        
        if gotText:
            return contents.getTransferData(DataFlavor.stringFlavor)
        else:
            return None

    def setClipboardText(self, text):
        clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
        clipboard.setContents(StringSelection(text), None)
       
    # Burp Scanner invokes this method for each base request/response that is passively scanned.
    def doPassiveScan(self, baseRequestResponse):       
        self._requestResponse = baseRequestResponse
        
        scan_issues = []
        scan_issues = self.findHeaders()
        
        # doPassiveScan needs to return a list of scan issues, if any, and None otherwise
        if len(scan_issues) > 0:
            return scan_issues
        else:
            return None
            
    def findHeaders(self):
        self._helpers = self._callbacks.getHelpers()
        self.scan_issues = []
        
        response = self._requestResponse.getResponse()
        requestInfo = self._helpers.analyzeResponse(response)
        headers = requestInfo.getHeaders()
        headers_dict = {}

        host = self._requestResponse.getHttpService().getHost() 

        # If host hasn't been scanned before, we create it in global_issues
        if host not in self.global_issues:  
            self.global_issues[host] ={} 
            self.global_issues[host]["Interesting"] = []
            self.global_issues[host]["Missing"] = []
            self.global_issues[host]["Misconfigured"] = []

        # Store headers in a dict to facilitate their manipulation
        for header in headers:
            header_split = header.split(':', 1)
            if len(header_split) > 1:   # To get rid of "HTTP/1.1 200 OK" and other response codes 
                headers_dict[header_split[0].lower()] = header_split[1]

        if self.interestingHeadersCB.isSelected():
            self.findInteresting(host, headers_dict)
        
        if self.securityHeadersCB.isSelected():
            self.findSecure(host, headers_dict)
        
        return (self.scan_issues)
        
    def findInteresting(self, host, headers):
        list_boring_headers = []
        model = self.boringHeadersList.getModel()
   
        # Get list of boring headers from the GUI JList
        for i in range(0, model.getSize()):
            list_boring_headers.append(model.getElementAt(i))
        
        issuename = "Interesting Header(s)"
        issuelevel = "Low"
        issuedetail = "<p>The response includes the following potentially interesting headers:</p><ul>"
        log = "[+] Interesting Headers found: " + host + "\n"
        found = 0

        for header in headers:
            if header.lower() not in list_boring_headers:
                issuedetail += "<li>Header name: <b>" + header + "</b>. Header value: <b>" + headers[header] + "</b></li>"

                log += "    Header name:" + header + " Header value:" + headers[header] + "\n"
                
                host = self._requestResponse.getHttpService().getHost()
                report = header + ":" + headers[header]
                if report not in self.global_issues[host]["Interesting"]:   # If header not already in the list we store it
                    self.global_issues[host]["Interesting"].append(report)

                found += 1
        
        issuedetail += "</ul>"

        if found > 0:
            # Create a ScanIssue object and append it to our list of issues, marking the reflected parameter value in the response.
            self.scan_issues.append(ScanIssue(self._requestResponse.getHttpService(),
	            self._helpers.analyzeRequest(self._requestResponse).getUrl(), 
                issuename, issuelevel, issuedetail))

            self.logsTA.append(log)
          
    def findSecure(self, host, headers):
        issuename = "Lack or Misconfiguration of Security Header(s)"
        issuelevel = "Low"
        issuedetail = """<p>The response lacks or includes the following misconfigured security headers.</p>
                         <p>Please note that some of these issues could be false positives, a manual review 
                         is recommended</p><br>  
                      """
        badheaders = []
        missingsecurity = []
        
        if self.xFrameOptionsCB.isSelected():
            # X-Frame-Options
            try:
                m = re.search("SAMEORIGIN|DENY", headers["x-frame-options"], re.IGNORECASE)
                if not m:
                    badheaders.append("x-frame-options")
            except Exception as e:
                missingsecurity.append("x-frame-options")
        
        if self.xContentTypeOptionsCB.isSelected():
            # X-Content-Type-Options: nosniff
            try:
                m = re.search("nosniff", headers["x-content-type-options"], re.IGNORECASE)
                if not m:
                    badheaders.append("x-content-type-options")
            except Exception as e:
                missingsecurity.append("x-content-type-options")

        if self.xXssProtectionCB.isSelected():
            # X-XSS-Protection
            try:
                m = re.search("0", headers["x-xss-protection"], re.IGNORECASE)
                if not m:
                    badheaders.append("x-xss-protection")
            except Exception as e:
                pass

        if self.HstsCB.isSelected():
            # Strict-Transport-Security (HSTS)
            try:
                m = re.search("max-age=(\d+)", headers["strict-transport-security"], re.IGNORECASE)
                if int(m.group(1)) < (60*60*24 * 30):     # Flag if less than 30 days
                    badheaders.append("strict-transport-security")
            except Exception as e:
                missingsecurity.append("strict-transport-security")
        
        if self.CorsCB.isSelected():
            # Access-Control-Allow-Origin (CORS)
            try:
                m = re.search("\*", headers["access-control-allow-origin"], re.IGNORECASE)
                if not m:
                    badheaders.append("x-xss-protection")
            except Exception as e:
                pass

        if self.contentSecurityPolicyCB.isSelected():
            # Content-Security-Policy
            if not ("content-security-policy" in headers or "x-content-security-policy" in headers or "x-webkit-csp" in headers):
                        missingsecurity.append("content-security-policy")    
       
        if self.xPermittedCrossDomainPoliciesCB.isSelected():
            # X-Permitted-Cross-Domain-Policies
            try:
                m = re.search("master-only", headers["x-permitted-cross-domain-policies"], re.IGNORECASE)
                if not m:
                    badheaders.append("x-permitted-cross-domain-policies")
            except Exception as e:
                missingsecurity.append("x-permitted-cross-domain-policies")
        
        if len(badheaders) > 0 or len(missingsecurity) > 0:     
            if len(badheaders) > 0:
                issuedetail += "<p>Potentially misconfigured headers:</p><ul>"
                log = "[+] Potentially miconfigured headers found: " + host + "\n"
                
                for bad in badheaders:
                    issuedetail += "<li>Header name: <b>" + bad + "</b>. Header value: <b>" + headers[bad] + "</b></li>"

                    log += "    Header name:" + bad + " Header value:" + headers[bad] + "\n"
                    
                    host = self._requestResponse.getHttpService().getHost()
                    report = bad + ":" + headers[bad]
                    if report not in self.global_issues[host]["Misconfigured"]:     # If header not already in the list we store it
                        self.global_issues[host]["Misconfigured"].append(report)
            
                issuedetail += "</ul>"
                
                self.logsTA.append(log)
                    
            if len(missingsecurity) > 0:
                issuedetail += "<p>Missing headers:</p><ul>"
                log = "[+] Missing security headers: " + host + "\n"
                
                for missing in missingsecurity:
                    issuedetail += "<li>Header name: <b>" + missing + "</b>.</li>"
                    log += "    Header name:" + missing + "\n"
                
                    host = self._requestResponse.getHttpService().getHost()
                    if missing not in self.global_issues[host]["Missing"]:      # If header not already in the list we store it
                        self.global_issues[host]["Missing"].append(missing)

                issuedetail += "</ul>"

                self.logsTA.append(log)

            # Create a ScanIssue object and append it to our list of issues, marking
            # the reflected parameter value in the response.
            self.scan_issues.append(ScanIssue(self._requestResponse.getHttpService(),
	            self._helpers.analyzeRequest(self._requestResponse).getUrl(),
                issuename, issuelevel, issuedetail))

# Implementation of the IScanIssue interface with simple constructor and getter methods
class ScanIssue(IScanIssue):
    def __init__(self, httpservice, url, name, severity, detailmsg):
        self._url = url
        self._httpservice = httpservice
        self._name = name
        self._severity = severity
        self._detailmsg = detailmsg

    def getUrl(self):
        return self._url

    def getHttpMessages(self):
        return None

    def getHttpService(self):
        return self._httpservice 

    def getRemediationDetail(self):
        return None

    def getIssueDetail(self):
        return self._detailmsg

    def getIssueBackground(self):
        return None

    def getRemediationBackground(self):
        return None

    def getIssueType(self):
        return 0

    def getIssueName(self):
        return self._name

    def getSeverity(self):
        return self._severity

    def getConfidence(self):
        return "Certain"