Beispiel #1
0
class RequestResponseWidget(QObject):
    def __init__(self,
                 framework,
                 tabwidget,
                 searchControlPlaceholder,
                 parent=None):
        QObject.__init__(self, parent)

        self.framework = framework
        QObject.connect(self, SIGNAL('destroyed(QObject*)'), self._destroyed)

        self.standardPageFactory = StandardPageFactory(self.framework, None,
                                                       self)
        self.headlessPageFactory = HeadlessPageFactory(self.framework, None,
                                                       self)

        self.qlock = QMutex()

        self.scintillaWidgets = set(
        )  # store scintilla widget reference to handle zoom in/zoom out

        self.contentExtractor = self.framework.getContentExtractor()
        self.htmlExtractor = self.contentExtractor.getExtractor('html')
        self.hexDumper = HexDump()

        self.contentTypeMapping = {
            # TODO: complete
            'json': 'javascript',
            'javascript': 'javascript',
            'text/x-js': 'javascript',
            'html': 'html',
            'text/xml': 'xml',
            'text/html': 'html',
            'text/xhtml': 'html',
            'text/css': 'css',
            'text/plain': 'text',
        }
        self.lexerMapping = {
            'text': None,
            'javascript': Qsci.QsciLexerJavaScript,
            'html': Qsci.QsciLexerHTML,
            'xml': Qsci.QsciLexerXML,
            'css': Qsci.QsciLexerCSS,
        }

        self.setup_ui(tabwidget, searchControlPlaceholder)

        self.Data = None
        self.cursor = None
        self.requestResponse = None
        self.framework.subscribe_database_events(self.db_attach,
                                                 self.db_detach)

        self.framework.subscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.subscribe_zoom_out(self.zoom_out_scintilla)

    def _destroyed(self):
        self.framework.unsubscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.unsubscribe_zoom_out(self.zoom_out_scintilla)

    def db_attach(self):
        self.Data = self.framework.getDB()
        self.cursor = self.Data.allocate_thread_cursor()
        self.clear()

    def db_detach(self):
        self.close_cursor()
        self.Data = None
        self.clear()

    def close_cursor(self):
        if self.cursor:
            self.cursor.close()
            self.Data.release_thread_cursor(self.cursor)
            self.cursor = None

    def setup_ui(self, tabwidget, searchControlPlaceholder):

        self.tabwidget = tabwidget
        self.searchControlPlaceholder = searchControlPlaceholder

        self.networkAccessManager = self.framework.getNetworkAccessManager()

        if self.searchControlPlaceholder is not None:
            self.searchLayout = self.searchControlPlaceholder.layout()
            if not self.searchLayout or 0 == self.searchLayout:
                self.searchLayout = QVBoxLayout(self.searchControlPlaceholder)
            self.searchLayout.addWidget(
                self.makeSearchWidget(self.searchControlPlaceholder))
            self.searchLayout.addWidget(
                self.makeConfirmedUpdateWidget(self.searchControlPlaceholder))
            self.searchLayout.setSpacing(0)
            self.searchLayout.setContentsMargins(-1, 0, -1, 0)
            self.searchControlPlaceholder.updateGeometry()

        self.requestView = QWidget(tabwidget)
        self.requestView.setObjectName(tabwidget.objectName() + 'Request')
        self.tabwidget.addTab(self.requestView, 'Request')

        self.responseView = QWidget(tabwidget)
        self.responseView.setObjectName(tabwidget.objectName() + 'Response')
        self.tabwidget.addTab(self.responseView, 'Response')

        self.hexBody = QWidget(tabwidget)
        self.hexBody.setObjectName(tabwidget.objectName() + 'HexBody')
        self.hexBodyIndex = self.tabwidget.addTab(self.hexBody, 'Hex Body')

        self.scriptsView = QWidget(tabwidget)
        self.scriptsView.setObjectName(tabwidget.objectName() + 'Scripts')
        self.scriptsTabIndex = self.tabwidget.addTab(self.scriptsView,
                                                     'Scripts')

        self.commentsView = QWidget(tabwidget)
        self.commentsView.setObjectName(tabwidget.objectName() + 'Comments')
        self.tabwidget.addTab(self.commentsView, 'Comments')

        self.linksView = QWidget(tabwidget)
        self.linksView.setObjectName(tabwidget.objectName() + 'Links')
        self.tabwidget.addTab(self.linksView, 'Links')

        self.formsView = QWidget(tabwidget)
        self.formsView.setObjectName(tabwidget.objectName() + 'Forms')
        self.tabwidget.addTab(self.formsView, 'Forms')

        self.renderView = QWidget(tabwidget)
        self.renderView.setObjectName(tabwidget.objectName() + 'Render')
        self.renderTabIndex = self.tabwidget.addTab(self.renderView, 'Render')
        self.tabwidget.currentChanged.connect(self.handle_tab_currentChanged)

        self.generatedSourceView = QWidget(tabwidget)
        self.generatedSourceView.setObjectName(tabwidget.objectName() +
                                               'GeneratedSource')
        self.generatedSourceTabIndex = self.tabwidget.addTab(
            self.generatedSourceView, 'Generated Source')

        self.notesView = QWidget(tabwidget)
        self.notesView.setObjectName(tabwidget.objectName() + 'Notes')
        self.notesTabIndex = self.tabwidget.addTab(self.notesView, 'Notes')

        self.tab_item_widgets = []

        self.vlayout0 = QVBoxLayout(self.requestView)
        self.requestScintilla = Qsci.QsciScintilla(self.requestView)
        self.setScintillaProperties(self.requestScintilla)
        self.vlayout0.addWidget(self.requestScintilla)
        self.tab_item_widgets.append(self.requestScintilla)

        self.vlayout1 = QVBoxLayout(self.responseView)
        self.responseScintilla = Qsci.QsciScintilla(self.responseView)
        self.responseScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.responseScintilla)
        self.vlayout1.addWidget(self.responseScintilla)
        self.tab_item_widgets.append(self.responseScintilla)

        self.vlayout2a = QVBoxLayout(self.hexBody)
        self.hexBodyScintilla = Qsci.QsciScintilla(self.hexBody)
        self.hexBodyScintilla.setFont(self.framework.get_monospace_font())
        self.vlayout2a.addWidget(self.hexBodyScintilla)
        self.tab_item_widgets.append(self.hexBodyScintilla)

        self.vlayout2 = QVBoxLayout(self.scriptsView)
        self.scriptsScintilla = Qsci.QsciScintilla(self.scriptsView)
        #        self.scriptsScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.scriptsScintilla, 'javascript')
        self.vlayout2.addWidget(self.scriptsScintilla)
        self.tab_item_widgets.append(self.scriptsScintilla)

        self.vlayout3 = QVBoxLayout(self.commentsView)
        self.commentsScintilla = Qsci.QsciScintilla(self.commentsView)
        #        self.commentsScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.commentsScintilla, 'html')
        self.vlayout3.addWidget(self.commentsScintilla)
        self.tab_item_widgets.append(self.commentsScintilla)

        self.vlayout4 = QVBoxLayout(self.linksView)
        self.linksScintilla = Qsci.QsciScintilla(self.linksView)
        self.setScintillaProperties(self.linksScintilla)
        self.vlayout4.addWidget(self.linksScintilla)
        self.tab_item_widgets.append(self.linksScintilla)

        self.vlayout5 = QVBoxLayout(self.formsView)
        self.formsScintilla = Qsci.QsciScintilla(self.formsView)
        self.setScintillaProperties(self.formsScintilla, 'html')
        self.vlayout5.addWidget(self.formsScintilla)
        self.tab_item_widgets.append(self.formsScintilla)

        self.vlayout6 = QVBoxLayout(self.renderView)
        self.renderWebView = RenderingWebView(self.framework,
                                              self.standardPageFactory,
                                              self.renderView)
        self.renderWebView.page().setNetworkAccessManager(
            self.networkAccessManager)
        self.renderWebView.loadFinished.connect(
            self.render_handle_loadFinished)
        self.vlayout6.addWidget(self.renderWebView)
        self.tab_item_widgets.append(self.renderWebView)

        self.vlayout7 = QVBoxLayout(self.generatedSourceView)
        self.generatedSourceScintilla = Qsci.QsciScintilla(
            self.generatedSourceView)
        self.generatedSourceWebView = RenderingWebView(
            self.framework, self.headlessPageFactory, self.generatedSourceView)
        self.generatedSourceWebView.page().setNetworkAccessManager(
            self.networkAccessManager)
        self.generatedSourceWebView.loadFinished.connect(
            self.generatedSource_handle_loadFinished)
        self.generatedSourceWebView.setVisible(False)
        self.generatedSourceScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.generatedSourceScintilla, 'html')
        self.vlayout7.addWidget(self.generatedSourceWebView)
        self.vlayout7.addWidget(self.generatedSourceScintilla)
        self.tab_item_widgets.append(self.generatedSourceScintilla)

        self.vlayout8 = QVBoxLayout(self.notesView)
        self.notesTextEdit = QTextEdit(self.notesView)
        self.vlayout8.addWidget(self.notesTextEdit)
        self.tab_item_widgets.append(self.notesTextEdit)

        self.clear()

    def setScintillaProperties(self, scintillaWidget, contentType='text'):
        scintillaWidget.setFont(self.framework.get_font())
        scintillaWidget.setWrapMode(1)
        scintillaWidget.zoomTo(self.framework.get_zoom_size())
        # TOOD: set based on line numbers (size is in pixels)
        scintillaWidget.setMarginWidth(1, '1000')
        self.attachLexer(scintillaWidget, contentType)
        self.scintillaWidgets.add(scintillaWidget)

    def zoom_in_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomIn()

    def zoom_out_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomOut()

    def makeSearchWidget(self, parentWidget, tooltip='Search the value'):
        # TODO: these should be store in a class variable list to so that they can be cleared...
        self.searchWidget = QWidget(parentWidget)
        self.searchWidget.setContentsMargins(-1, 0, -1, 0)
        self.search_hlayout = QHBoxLayout(self.searchWidget)
        self.search_label = QLabel(self.searchWidget)
        self.search_label.setText('Search: ')
        self.searchLineEdit = QLineEdit(self.searchWidget)
        self.searchLineEdit.setToolTip(tooltip)
        self.search_hlayout.addWidget(self.search_label)
        self.search_hlayout.addWidget(self.searchLineEdit)
        # always supports regex search
        self.searchReCheckBox = QCheckBox(self.searchWidget)
        self.searchReCheckBox.setText('RE')
        self.searchReCheckBox.setToolTip('Use Regular Expression Syntax')
        self.search_hlayout.addWidget(self.searchReCheckBox)
        self.searchFindButton = QPushButton()
        self.searchFindButton.setText('Find')
        QObject.connect(self.searchFindButton, SIGNAL('clicked()'),
                        self.run_search_find)
        QObject.connect(self.searchLineEdit, SIGNAL('returnPressed()'),
                        self.run_search_find)
        self.search_hlayout.addWidget(self.searchFindButton)
        return self.searchWidget

    def run_search_find(self):
        targetWidget = self.tab_item_widgets[self.tabwidget.currentIndex()]
        if isinstance(targetWidget, Qsci.QsciScintilla):
            self.searchScintilla(targetWidget)
        elif isinstance(targetWidget, QtWebKit.QWebView):
            self.searchWebView(targetWidget)
        else:
            self.searchTextEdit(targetWidget)

    def confirmedButtonStateChanged(self, state):
        # self.emit(SIGNAL('confirmedButtonSet(int)'), state)
        if hasattr(self, 'confirmedCheckBox'):
            self.confirmedCheckBox.setChecked(state)

    def makeConfirmedUpdateWidget(self, parentWidget):
        self.confirmedUpdateWidget = QWidget(parentWidget)
        self.confirmedUpdateWidget.setContentsMargins(-1, 0, -1, 0)
        self.confirmed_hlayout = QHBoxLayout(self.confirmedUpdateWidget)
        self.confirmedCheckBox = QCheckBox(parentWidget)
        self.confirmedCheckBox.setText('Confirmed Vulnerable')
        QObject.connect(self.confirmedCheckBox, SIGNAL('stateChanged(int)'),
                        self.confirmedButtonStateChanged)
        self.quickNotesLabel = QLabel(parentWidget)
        self.quickNotesLabel.setText('Quick Notes: ')
        self.quickNotesEdit = QLineEdit(parentWidget)
        self.confirmed_horizontalSpacer = QSpacerItem(40, 20,
                                                      QSizePolicy.Expanding,
                                                      QSizePolicy.Minimum)
        self.updateButton = QPushButton(parentWidget)
        self.updateButton.setText('Update')
        QObject.connect(self.updateButton, SIGNAL('clicked()'),
                        self.handle_updateButton_clicked)
        self.confirmed_hlayout.addWidget(self.confirmedCheckBox)
        self.confirmed_hlayout.addItem(self.confirmed_horizontalSpacer)
        self.confirmed_hlayout.addWidget(self.quickNotesLabel)
        self.confirmed_hlayout.addWidget(self.quickNotesEdit)
        self.confirmed_hlayout.addWidget(self.updateButton)
        return self.confirmedUpdateWidget

    def handle_updateButton_clicked(self):
        if self.responseId is not None:
            quickNotes = str(self.quickNotesEdit.text()).strip()
            notes = str(self.notesTextEdit.toPlainText())
            confirmed = str(self.confirmedCheckBox.isChecked())
            if len(quickNotes) > 0:
                notes = quickNotes + '\n' + notes
            self.Data.update_responses(self.cursor, notes, '', confirmed,
                                       self.responseId)
            self.quickNotesEdit.setText('')
            self.notesTextEdit.setText(notes)
            # update request response state
            self.requestResponse.confirmed = confirmed
            self.requestResponse.notes = notes
            # TODO: update in datamodel

    def searchTextEdit(self, targetWidget):
        # TODO: simulate regex searching
        searchText = self.searchLineEdit.text()
        return targetWidget.find(searchText)

    def searchScintilla(self, targetWidget):
        searchText = self.searchLineEdit.text()
        line, index = targetWidget.getCursorPosition()
        return targetWidget.findFirst(searchText,
                                      self.searchReCheckBox.isChecked(), False,
                                      False, True, True, line, index)

    def searchWebView(self, targetWidget):
        is_re = self.searchReCheckBox.isChecked()
        searchText = self.searchLineEdit.text()
        # TODO: simulate regex search
        targetWidget.findText(
            '', QtWebKit.QWebPage.FindWrapsAroundDocument
            | QtWebKit.QWebPage.HighlightAllOccurrences)
        return targetWidget.findText(
            searchText, QtWebKit.QWebPage.FindWrapsAroundDocument
            | QtWebKit.QWebPage.HighlightAllOccurrences)

    def viewItemSelected(self, index):
        if index and index.isValid():
            obj = index.internalPointer()
            self.fill(obj.Id)

    def clear(self):
        # clear
        self.responseId = None
        self.requestResponse = None
        self.requestScintilla.setText('')
        self.responseScintilla.setText('')
        self.hexBodyScintilla.setText('')
        self.scriptsScintilla.setText('')
        self.commentsScintilla.setText('')
        self.linksScintilla.setText('')
        self.formsScintilla.setText('')
        self.renderWebView.setHtml('')
        self.generatedSourceWebView.setHtml('')
        self.generatedSourceScintilla.setText('')
        self.contentResults = None
        self.notesTextEdit.setPlainText('')
        self.confirmedButtonStateChanged(Qt.Unchecked)

    def set_search_info(self, searchText, isRE):
        self.searchLineEdit.setText(searchText)
        self.searchReCheckBox.setChecked(isRE)

    def fill(self, Id):
        if self.requestResponse and self.requestResponse.Id == Id:
            # already filled
            return

        if self.qlock.tryLock():
            try:
                self.fill_internal(Id)
            finally:
                self.qlock.unlock()

    def fill_internal(self, Id):

        self.clear()

        if not Id:
            return

        self.responseId = Id
        self.requestResponse = self.framework.get_request_response(Id)
        rr = self.requestResponse

        confirmedState = Qt.Unchecked
        if rr.confirmed and rr.confirmed.lower() in ['y', '1', 'true']:
            confirmedState = Qt.Checked
        self.confirmedButtonStateChanged(confirmedState)

        self.requestScintilla.setText(rr.rawRequest)

        self.attachLexer(self.responseScintilla, rr.responseContentType,
                         rr.responseBody)
        self.responseScintilla.setText(
            ContentHelper.convertBytesToDisplayText(rr.rawResponse))
        self.hexBodyScintilla.setText(self.hexDumper.dump(rr.responseBody))
        self.contentResults = self.generateExtractorResults(
            rr.responseHeaders, rr.responseBody, rr.responseUrl, rr.charset)
        self.notesTextEdit.setText(rr.notes)
        self.handle_tab_currentChanged(self.tabwidget.currentIndex())

    def generateExtractorResults(self, headers, body, url, charset):
        rr = self.requestResponse
        scriptsIO, commentsIO, linksIO, formsIO = StringIO(), StringIO(
        ), StringIO(), StringIO()
        try:
            results = rr.results
            if 'html' == rr.baseType:
                # Create content for parsing HTML
                self.htmlExtractor.process(body, url, charset, results)

                self.tabwidget.setTabText(self.scriptsTabIndex, 'Scripts')
                for script in results.scripts:
                    scriptsIO.write('%s\n\n' % self.flat_str(script))

                self.attachLexer(self.commentsScintilla, 'html')
                for comment in results.comments:
                    commentsIO.write('%s\n\n' % self.flat_str(comment))

                for link in results.links:
                    linksIO.write('%s\n' % self.flat_str(link))

                for form in results.forms:
                    formsIO.write('%s\n' % self.flat_str(form))

                for input in results.other_inputs:
                    formsIO.write('%s\n' % self.flat_str(input))

            elif 'javascript' == rr.baseType:

                self.tabwidget.setTabText(self.scriptsTabIndex, 'Strings')
                for script_string in results.strings:
                    scriptsIO.write('%s\n' % self.flat_str(script_string))

                self.attachLexer(self.commentsScintilla, 'javascript')
                for comment in results.comments:
                    commentsIO.write('%s\n' % self.flat_str(comment))

                for link in results.links:
                    linksIO.write('%s\n' % self.flat_str(link))

                for link in results.relative_links:
                    linksIO.write('%s\n' % self.flat_str(link))

        except Exception as e:
            # TODO: log
            self.framework.report_exception(e)

        self.scriptsScintilla.setText(scriptsIO.getvalue())
        self.commentsScintilla.setText(commentsIO.getvalue())
        self.linksScintilla.setText(linksIO.getvalue())
        self.formsScintilla.setText(formsIO.getvalue())

    def flat_str(self, u):
        if bytes == type(u):
            try:
                s = u.decode('utf-8')
            except UnicodeDecodeError:
                s = repr(u)[2:-1].replace('\\r', '').replace('\\n',
                                                             '\n').replace(
                                                                 '\\t', '\t')

            return s
        else:
            # may be object type implementing str
            s = str(u)
            return s

    def attachLexer(self, scintillaWidget, contentType, data=''):
        lexer = self.getLexer(contentType, data)
        if lexer:
            lexerInstance = lexer(scintillaWidget)
            lexerInstance.setFont(self.framework.get_font())
            scintillaWidget.setLexer(lexerInstance)
        else:
            scintillaWidget.setLexer(None)

    def handle_tab_currentChanged(self, index):
        if index == self.renderTabIndex:
            return self.doRenderApply()
        elif index == self.generatedSourceTabIndex:
            return self.doGeneratedSourceApply()
        return False

    def doRenderApply(self):
        rr = self.requestResponse
        if rr and rr.responseUrl:
            self.renderWebView.fill_from_response(rr.responseUrl,
                                                  rr.responseHeaders,
                                                  rr.responseBody,
                                                  rr.responseContentType)
            return True
        return False

    def doGeneratedSourceApply(self):
        rr = self.requestResponse
        if rr and rr.responseUrl and 'html' == rr.baseType:
            self.generatedSourceWebView.fill_from_response(
                rr.responseUrl, rr.responseHeaders, rr.responseBody,
                rr.responseContentType)
            return True
        return False

    def generatedSource_handle_loadFinished(self):
        self.set_generated_source(self.generatedSourceWebView)

    def render_handle_loadFinished(self):
        self.set_generated_source(self.renderWebView)

    def set_generated_source(self, webview):
        # TODO: consider merging frames sources?
        # TODO: consider other optimizations
        if self.requestResponse:
            rr = self.requestResponse
            xhtml = webview.page().mainFrame().documentElement().toOuterXml()
            self.generatedSourceScintilla.setText(xhtml)
            body_bytes = xhtml.encode('utf-8')
            self.generateExtractorResults(rr.responseHeaders, body_bytes,
                                          rr.responseUrl, rr.charset)

    def getLexer(self, contentType, data):
        lexerContentType = self.inferContentType(contentType, data)
        return self.lexerMapping[lexerContentType]

    def inferContentType(self, contentType, data):
        # TODO: scan data for additional info
        # XXX: data -> bytes
        for comp in list(self.contentTypeMapping.keys()):
            if comp in contentType:
                return self.contentTypeMapping[comp]
        return 'text'

    def set_search(self, tabname, searchText):
        if tabname == 'request':
            self.tabwidget.setCurrentIndex(0)
        elif tabname == 'response':
            self.tabwidget.setCurrentIndex(1)
        self.searchLineEdit.setText(searchText)
        self.requestScintilla.findFirst(searchText, False, True, False, True)
        self.responseScintilla.findFirst(searchText, False, True, False, True)
Beispiel #2
0
class TesterTab(QObject):
    class ClickJackingInteractor:
        def __init__(self, parent):
            self.parent = parent

        def log(self, logtype, logmessage):
            self.parent.clickjacking_console_log(logtype, logmessage)

        def confirm(self, frame, msg):
            return self.parent.clickjacking_browser_confirm(frame, msg)

    DEFAULT_FRAMED_URL = "http://attacker.example.com/framed.html"
    DEFAULT_CSRF_URL = "http://attacker.example.com/csrf.html"

    def __init__(self, framework, mainWindow):
        QObject.__init__(self, mainWindow)
        self.framework = framework
        QObject.connect(self, SIGNAL("destroyed(QObject*)"), self._destroyed)
        self.mainWindow = mainWindow
        self.cjInteractor = TesterTab.ClickJackingInteractor(self)
        self.cjTester = ClickjackingTester(self.framework)

        self.scintillaWidgets = set()  # store scintilla widget reference to handle zoom in/zoom out

        #        self.networkAccessManager = StandardNetworkAccessManager(self.framework, self.framework.get_global_cookie_jar())
        self.pageFactory = TesterPageFactory(self.framework, self.cjInteractor, None, self)

        self.framework.subscribe_populate_tester_csrf(self.tester_populate_csrf)
        self.framework.subscribe_populate_tester_click_jacking(self.tester_populate_click_jacking)

        self.Data = None
        self.cursor = None
        self.framework.subscribe_database_events(self.db_attach, self.db_detach)

        self.setScintillaProperties(self.mainWindow.csrfGenEdit)
        self.mainWindow.testerRegenBtn.clicked.connect(self.regen_csrf)
        self.mainWindow.csrfOpenBtn.clicked.connect(self.handle_csrfOpenBtn_clicked)

        self.setScintillaProperties(self.mainWindow.testerClickjackingEditHtml)
        self.mainWindow.testerClickjackingSimulateButton.clicked.connect(
            self.handle_testerClickjackingSimulateButton_clicked
        )
        self.mainWindow.testerClickjackingOpenInBrowserButton.clicked.connect(
            self.handle_testerClickjackingOpenInBrowserButton_clicked
        )
        self.mainWindow.testerClickjackingGenerateButton.clicked.connect(
            self.handle_testerClickjackingGenerateButton_clicked
        )
        self.mainWindow.testerClickjackingEnableJavascript.clicked.connect(
            self.handle_testerClickjackingEnableJavascript_clicked
        )

        self.clickjackingRenderWebView = RenderingWebView(
            self.framework, self.pageFactory, self.mainWindow.testerClickjackingEmbeddedBrowserPlaceholder
        )
        self.clickjackingRenderWebView.loadFinished.connect(self.handle_clickjackingRenderWebView_loadFinished)
        self.clickjackingRenderWebView.urlChanged.connect(self.handle_clickjackingRenderWebView_urlChanged)
        self.clickjackingRenderWebView.titleChanged.connect(self.handle_clickjackingRenderWebView_titleChanged)

        self.framework.subscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.subscribe_zoom_out(self.zoom_out_scintilla)

    def _destroyed(self):
        self.framework.unsubscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.unsubscribe_zoom_out(self.zoom_out_scintilla)

    def db_attach(self):
        self.Data = self.framework.getDB()
        self.cursor = self.Data.allocate_thread_cursor()

    def db_detach(self):
        self.close_cursor()
        self.Data = None

    def close_cursor(self):
        if self.cursor and self.Data:
            self.cursor.close()
            self.Data.release_thread_cursor(self.cursor)
            self.cursor = None

    def tester_populate_csrf(self, response_id):

        row = self.Data.read_responses_by_id(self.cursor, response_id)

        if not row:
            return

        responseItems = interface.data_row_to_response_items(row)

        url = responseItems[ResponsesTable.URL]
        # Are reqHeaders necessary?
        reqHeaders = str(responseItems[ResponsesTable.REQ_HEADERS], "utf-8", "ignore")
        reqData = str(responseItems[ResponsesTable.REQ_DATA], "utf-8", "ignore")  # TODO: consider replacement

        data = reqHeaders + "\n" + reqData

        # Check to ensure that either a GET or a POST is being used and pass that along to the function
        # check = re.compile("^(GET|POST)", re.I)
        # result = check.match(reqHeaders)
        # if not result:
        #    return()

        GET = re.compile("^GET", re.I)
        POST = re.compile("^POST", re.I)

        if GET.match(reqHeaders):
            htmlresult = CSRFTester.generate_csrf_html(url, reqData, "get")
        elif POST.match(reqHeaders):
            htmlresult = CSRFTester.generate_csrf_html(url, reqData, "post")
        else:
            return ()

        # htmlresult = CSRFTester.generate_csrf_html(url, reqData)

        self.mainWindow.testerCSRFURLEdit.setText(url)
        self.mainWindow.csrfGenEdit.setText(htmlresult)
        self.mainWindow.csrfReqEdit.setPlainText(data)

    def regen_csrf(self):
        # Regenerate CSRF playload based on selection
        if self.mainWindow.testerImgGen.isChecked():
            url = self.mainWindow.testerCSRFURLEdit.text()
            htmlresult = CSRFTester.generate_csrf_img(url, self.mainWindow.csrfGenEdit.text())
            self.mainWindow.csrfGenEdit.setText(htmlresult)

    def handle_csrfOpenBtn_clicked(self):
        url = self.DEFAULT_CSRF_URL  # TODO: exposed this (?)
        body = self.mainWindow.csrfGenEdit.text()
        content_type = "text/html"
        self.framework.open_content_in_browser(url, body, content_type)

    def tester_populate_click_jacking(self, response_id):
        row = self.Data.read_responses_by_id(self.cursor, response_id)
        if not row:
            return
        responseItems = interface.data_row_to_response_items(row)
        url = responseItems[ResponsesTable.URL]
        self.setup_clickjacking_url(url)

    def setup_clickjacking_url(self, url):
        self._clickjacking_simulation_running = False
        self.mainWindow.testerClickjackingUrlEdit.setText("")
        self.mainWindow.testerClickjackingConsoleLogTextEdit.setText("")
        self.clickjackingRenderWebView.setHtml("", QUrl("about:blank"))

        htmlcontent = self.cjTester.make_default_frame_html(url)
        self.mainWindow.testerClickjackingTargetURL.setText(url)
        self.mainWindow.testerClickjackingEditHtml.setText(htmlcontent)

    def handle_testerClickjackingSimulateButton_clicked(self):
        self.mainWindow.testerClickjackingConsoleLogTextEdit.setText("Starting Clickjacking Simulation")
        self.clickjackingRenderWebView.page().set_javascript_enabled(
            self.mainWindow.testerClickjackingEnableJavascript.isChecked()
        )
        self._clickjacking_simulation_running = True
        url = self.mainWindow.testerClickjackingUrlEdit.text()
        # TODO: better way than to force unique URL to reload content ?
        if "?" in url:
            url = url[0 : url.find("?") + 1] + uuid.uuid4().hex
        else:
            url = url + "?" + uuid.uuid4().hex
        headers = ""
        body = self.mainWindow.testerClickjackingEditHtml.text()
        content_type = "text/html"
        self.clickjackingRenderWebView.fill_from_response(url, headers, body, content_type)

    def handle_testerClickjackingGenerateButton_clicked(self):
        entry = self.mainWindow.testerClickjackingTargetURL.text()
        url = QUrl.fromUserInput(entry).toEncoded().data().decode("utf-8")
        self.setup_clickjacking_url(url)

    def handle_testerClickjackingOpenInBrowserButton_clicked(self):
        url = self.clickjackingRenderWebView.url().toEncoded().data().decode("utf-8")
        body = self.mainWindow.testerClickjackingEditHtml.text()
        content_type = "text/html"
        self.framework.open_content_in_browser(url, body, content_type)

    def handle_clickjackingRenderWebView_loadFinished(self):
        self.clickjacking_console_log("info", "Page load finished")
        if not self._clickjacking_simulation_running:
            self.mainWindow.testerClickjackingConsoleLogTextEdit.setPlainText("")

    def handle_clickjackingRenderWebView_urlChanged(self):
        url = self.clickjackingRenderWebView.url().toEncoded().data().decode("utf-8")
        if "about:blank" == url and not self._clickjacking_simulation_running:
            self.mainWindow.testerClickjackingUrlEdit.setText(self.DEFAULT_FRAMED_URL)
        else:
            self.mainWindow.testerClickjackingUrlEdit.setText(url)

    def handle_testerClickjackingEnableJavascript_clicked(self):
        self.clickjackingRenderWebView.page().set_javascript_enabled(
            self.mainWindow.testerClickjackingEnableJavascript.isChecked()
        )

    def handle_clickjackingRenderWebView_titleChanged(self, title):
        self.clickjacking_console_log("info", "Page title changed to [%s]" % (title))

    def clickjacking_browser_confirm(self, frame, msg):
        if not self._clickjacking_simulation_running:
            return True
        if "Clickjacking: Navigate Away" == msg:  # TODO: create a common location for this string
            if self.mainWindow.testerClickjackingIgnoreNavigationRequests.isChecked():
                self.clickjacking_console_log(
                    "info", "Simulating canceled navigation response to confirmation: %s" % (msg)
                )
                return False
        return True

    def clickjacking_console_log(self, logtype, logmessage):
        msg = None
        if "javaScriptConsoleMessage" == logtype:
            (lineNumber, sourceID, message) = logmessage
            msg = "console log from [%s / %s]: %s" % (lineNumber, sourceID, message)
        elif "info":
            msg = logmessage

        if msg:
            curtext = self.mainWindow.testerClickjackingConsoleLogTextEdit.toPlainText()
            curtext += "\n" + msg
            self.mainWindow.testerClickjackingConsoleLogTextEdit.setPlainText(curtext)

    # TODO: refactor into common module in framework
    def setScintillaProperties(self, scintillaWidget, contentType="html"):
        scintillaWidget.setFont(self.framework.get_font())
        scintillaWidget.setWrapMode(1)
        scintillaWidget.zoomTo(self.framework.get_zoom_size())
        # TOOD: set based on line numbers (size is in pixels)
        scintillaWidget.setMarginWidth(1, "1000")
        lexerInstance = Qsci.QsciLexerHTML(scintillaWidget)
        lexerInstance.setFont(self.framework.get_font())
        scintillaWidget.setLexer(lexerInstance)
        self.scintillaWidgets.add(scintillaWidget)

    def zoom_in_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomIn()

    def zoom_out_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomOut()
Beispiel #3
0
class RequestResponseWidget(QObject):
    def __init__(self, framework, tabwidget, searchControlPlaceholder, parent = None):
        QObject.__init__(self, parent)

        self.framework = framework
        QObject.connect(self, SIGNAL('destroyed(QObject*)'), self._destroyed)

        self.standardPageFactory = StandardPageFactory(self.framework, None, self)
        self.headlessPageFactory = HeadlessPageFactory(self.framework, None, self)

        self.qlock = QMutex()

        self.scintillaWidgets = set() # store scintilla widget reference to handle zoom in/zoom out

        self.contentExtractor = self.framework.getContentExtractor()
        self.htmlExtractor = self.contentExtractor.getExtractor('html')
        self.hexDumper = HexDump()

        self.contentTypeMapping = {
            # TODO: complete
            'json' : 'javascript',
            'javascript': 'javascript',
            'text/x-js' : 'javascript',
            'html' : 'html',
            'text/xml' : 'xml',
            'text/html' : 'html',
            'text/xhtml' : 'html',
            'text/css' : 'css',
            'text/plain' : 'text',
            }
        self.lexerMapping = {
            'text' : None,
            'javascript' : Qsci.QsciLexerJavaScript,
            'html' : Qsci.QsciLexerHTML,
            'xml' : Qsci.QsciLexerXML,
            'css' : Qsci.QsciLexerCSS,
            }

        self.setup_ui(tabwidget, searchControlPlaceholder)

        self.Data = None
        self.cursor = None
        self.requestResponse = None
        self.framework.subscribe_database_events(self.db_attach, self.db_detach)

        self.framework.subscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.subscribe_zoom_out(self.zoom_out_scintilla)

    def _destroyed(self):
        self.framework.unsubscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.unsubscribe_zoom_out(self.zoom_out_scintilla)

    def db_attach(self):
        self.Data = self.framework.getDB()
        self.cursor = self.Data.allocate_thread_cursor()
        self.clear()

    def db_detach(self):
        self.close_cursor()
        self.Data = None
        self.clear()

    def close_cursor(self):
        if self.cursor:
            self.cursor.close()
            self.Data.release_thread_cursor(self.cursor)
            self.cursor = None

    def setup_ui(self, tabwidget, searchControlPlaceholder):

        self.tabwidget = tabwidget
        self.searchControlPlaceholder = searchControlPlaceholder

        self.networkAccessManager = self.framework.getNetworkAccessManager()

        if self.searchControlPlaceholder is not None:
            self.searchLayout = self.searchControlPlaceholder.layout()
            if not self.searchLayout or 0 == self.searchLayout:
                self.searchLayout = QVBoxLayout(self.searchControlPlaceholder)
            self.searchLayout.addWidget(self.makeSearchWidget(self.searchControlPlaceholder))
            self.searchLayout.addWidget(self.makeConfirmedUpdateWidget(self.searchControlPlaceholder))
            self.searchLayout.setSpacing(0)
            self.searchLayout.setContentsMargins(-1, 0, -1, 0)
            self.searchControlPlaceholder.updateGeometry()

        self.requestView = QWidget(tabwidget)
        self.requestView.setObjectName(tabwidget.objectName()+'Request')
        self.tabwidget.addTab(self.requestView, 'Request')

        self.responseView = QWidget(tabwidget)
        self.responseView.setObjectName(tabwidget.objectName()+'Response')
        self.tabwidget.addTab(self.responseView, 'Response')

        self.hexBody = QWidget(tabwidget)
        self.hexBody.setObjectName(tabwidget.objectName()+'HexBody')
        self.hexBodyIndex = self.tabwidget.addTab(self.hexBody, 'Hex Body')

        self.scriptsView = QWidget(tabwidget)
        self.scriptsView.setObjectName(tabwidget.objectName()+'Scripts')
        self.scriptsTabIndex = self.tabwidget.addTab(self.scriptsView, 'Scripts')

        self.commentsView = QWidget(tabwidget)
        self.commentsView.setObjectName(tabwidget.objectName()+'Comments')
        self.tabwidget.addTab(self.commentsView, 'Comments')

        self.linksView = QWidget(tabwidget)
        self.linksView.setObjectName(tabwidget.objectName()+'Links')
        self.tabwidget.addTab(self.linksView, 'Links')

        self.formsView = QWidget(tabwidget)
        self.formsView.setObjectName(tabwidget.objectName()+'Forms')
        self.tabwidget.addTab(self.formsView, 'Forms')

        self.renderView = QWidget(tabwidget)
        self.renderView.setObjectName(tabwidget.objectName()+'Render')
        self.renderTabIndex = self.tabwidget.addTab(self.renderView, 'Render')
        self.tabwidget.currentChanged.connect(self.handle_tab_currentChanged)

        self.generatedSourceView = QWidget(tabwidget)
        self.generatedSourceView.setObjectName(tabwidget.objectName()+'GeneratedSource')
        self.generatedSourceTabIndex = self.tabwidget.addTab(self.generatedSourceView, 'Generated Source')

        self.notesView = QWidget(tabwidget)
        self.notesView.setObjectName(tabwidget.objectName()+'Notes')
        self.notesTabIndex = self.tabwidget.addTab(self.notesView, 'Notes')

        self.tab_item_widgets = []

        self.vlayout0 = QVBoxLayout(self.requestView)
        self.requestScintilla = Qsci.QsciScintilla(self.requestView)
        self.setScintillaProperties(self.requestScintilla)
        self.vlayout0.addWidget(self.requestScintilla)
        self.tab_item_widgets.append(self.requestScintilla)

        self.vlayout1 = QVBoxLayout(self.responseView)
        self.responseScintilla = Qsci.QsciScintilla(self.responseView)
        self.responseScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.responseScintilla)
        self.vlayout1.addWidget(self.responseScintilla)
        self.tab_item_widgets.append(self.responseScintilla)

        self.vlayout2a = QVBoxLayout(self.hexBody)
        self.hexBodyScintilla = Qsci.QsciScintilla(self.hexBody)
        self.hexBodyScintilla.setFont(self.framework.get_monospace_font())
        self.vlayout2a.addWidget(self.hexBodyScintilla)
        self.tab_item_widgets.append(self.hexBodyScintilla)

        self.vlayout2 = QVBoxLayout(self.scriptsView)
        self.scriptsScintilla = Qsci.QsciScintilla(self.scriptsView)
#        self.scriptsScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.scriptsScintilla, 'javascript')
        self.vlayout2.addWidget(self.scriptsScintilla)
        self.tab_item_widgets.append(self.scriptsScintilla)

        self.vlayout3 = QVBoxLayout(self.commentsView)
        self.commentsScintilla = Qsci.QsciScintilla(self.commentsView)
#        self.commentsScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.commentsScintilla, 'html')
        self.vlayout3.addWidget(self.commentsScintilla)
        self.tab_item_widgets.append(self.commentsScintilla)

        self.vlayout4 = QVBoxLayout(self.linksView)
        self.linksScintilla = Qsci.QsciScintilla(self.linksView)
        self.setScintillaProperties(self.linksScintilla)
        self.vlayout4.addWidget(self.linksScintilla)
        self.tab_item_widgets.append(self.linksScintilla)

        self.vlayout5 = QVBoxLayout(self.formsView)
        self.formsScintilla = Qsci.QsciScintilla(self.formsView)
        self.setScintillaProperties(self.formsScintilla, 'html')
        self.vlayout5.addWidget(self.formsScintilla)
        self.tab_item_widgets.append(self.formsScintilla)

        self.vlayout6 = QVBoxLayout(self.renderView)
        self.renderWebView = RenderingWebView(self.framework, self.standardPageFactory, self.renderView)
        self.renderWebView.page().setNetworkAccessManager(self.networkAccessManager)
        self.renderWebView.loadFinished.connect(self.render_handle_loadFinished)
        self.vlayout6.addWidget(self.renderWebView)
        self.tab_item_widgets.append(self.renderWebView)

        self.vlayout7 = QVBoxLayout(self.generatedSourceView)
        self.generatedSourceScintilla = Qsci.QsciScintilla(self.generatedSourceView)
        self.generatedSourceWebView = RenderingWebView(self.framework, self.headlessPageFactory, self.generatedSourceView)
        self.generatedSourceWebView.page().setNetworkAccessManager(self.networkAccessManager)
        self.generatedSourceWebView.loadFinished.connect(self.generatedSource_handle_loadFinished)
        self.generatedSourceWebView.setVisible(False)
        self.generatedSourceScintilla.setMarginLineNumbers(1, True)
        self.setScintillaProperties(self.generatedSourceScintilla, 'html')
        self.vlayout7.addWidget(self.generatedSourceWebView)
        self.vlayout7.addWidget(self.generatedSourceScintilla)
        self.tab_item_widgets.append(self.generatedSourceScintilla)

        self.vlayout8 = QVBoxLayout(self.notesView)
        self.notesTextEdit = QTextEdit(self.notesView)
        self.vlayout8.addWidget(self.notesTextEdit)
        self.tab_item_widgets.append(self.notesTextEdit)

        self.clear()

    def setScintillaProperties(self, scintillaWidget, contentType = 'text'):
        scintillaWidget.setFont(self.framework.get_font())
        scintillaWidget.setWrapMode(1)
        scintillaWidget.zoomTo(self.framework.get_zoom_size())
        # TOOD: set based on line numbers (size is in pixels)
        scintillaWidget.setMarginWidth(1, '1000')
        self.attachLexer(scintillaWidget, contentType)
        self.scintillaWidgets.add(scintillaWidget)

    def zoom_in_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomIn()

    def zoom_out_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomOut()

    def makeSearchWidget(self, parentWidget, tooltip = 'Search the value'):
        # TODO: these should be store in a class variable list to so that they can be cleared...
        self.searchWidget = QWidget(parentWidget)
        self.searchWidget.setContentsMargins(-1, 0, -1, 0)
        self.search_hlayout = QHBoxLayout(self.searchWidget)
        self.search_label = QLabel(self.searchWidget)
        self.search_label.setText('Search: ')
        self.searchLineEdit = QLineEdit(self.searchWidget)
        self.searchLineEdit.setToolTip(tooltip)
        self.search_hlayout.addWidget(self.search_label)
        self.search_hlayout.addWidget(self.searchLineEdit)
        # always supports regex search
        self.searchReCheckBox = QCheckBox(self.searchWidget)
        self.searchReCheckBox.setText('RE')
        self.searchReCheckBox.setToolTip('Use Regular Expression Syntax')
        self.search_hlayout.addWidget(self.searchReCheckBox)
        self.searchFindButton = QPushButton()
        self.searchFindButton.setText('Find')
        QObject.connect(self.searchFindButton, SIGNAL('clicked()'), self.run_search_find)
        QObject.connect(self.searchLineEdit, SIGNAL('returnPressed()'), self.run_search_find)
        self.search_hlayout.addWidget(self.searchFindButton)
        return self.searchWidget

    def run_search_find(self):
        targetWidget = self.tab_item_widgets[self.tabwidget.currentIndex()]
        if isinstance(targetWidget, Qsci.QsciScintilla):
            self.searchScintilla(targetWidget)
        elif isinstance(targetWidget,  QtWebKit.QWebView):
            self.searchWebView(targetWidget)
        else:
            self.searchTextEdit(targetWidget)

    def confirmedButtonStateChanged(self, state):
        # self.emit(SIGNAL('confirmedButtonSet(int)'), state)
        if hasattr(self, 'confirmedCheckBox'):
            self.confirmedCheckBox.setChecked(state)

    def makeConfirmedUpdateWidget(self, parentWidget):
        self.confirmedUpdateWidget = QWidget(parentWidget)
        self.confirmedUpdateWidget.setContentsMargins(-1, 0, -1, 0)
        self.confirmed_hlayout = QHBoxLayout(self.confirmedUpdateWidget)
        self.confirmedCheckBox = QCheckBox(parentWidget)
        self.confirmedCheckBox.setText('Confirmed Vulnerable')
        QObject.connect(self.confirmedCheckBox, SIGNAL('stateChanged(int)'), self.confirmedButtonStateChanged)
        self.quickNotesLabel = QLabel(parentWidget)
        self.quickNotesLabel.setText('Quick Notes: ')
        self.quickNotesEdit = QLineEdit(parentWidget)
        self.confirmed_horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.updateButton = QPushButton(parentWidget)
        self.updateButton.setText('Update')
        QObject.connect(self.updateButton, SIGNAL('clicked()'), self.handle_updateButton_clicked)
        self.confirmed_hlayout.addWidget(self.confirmedCheckBox)
        self.confirmed_hlayout.addItem(self.confirmed_horizontalSpacer)
        self.confirmed_hlayout.addWidget(self.quickNotesLabel)
        self.confirmed_hlayout.addWidget(self.quickNotesEdit)
        self.confirmed_hlayout.addWidget(self.updateButton)
        return self.confirmedUpdateWidget

    def handle_updateButton_clicked(self):
        if self.responseId is not None:
            quickNotes = str(self.quickNotesEdit.text()).strip()
            notes = str(self.notesTextEdit.toPlainText())
            confirmed = str(self.confirmedCheckBox.isChecked())
            if len(quickNotes) > 0:
                notes = quickNotes + '\n' + notes
            self.Data.update_responses(self.cursor, notes, '', confirmed, self.responseId)
            self.quickNotesEdit.setText('')
            self.notesTextEdit.setText(notes)
            # update request response state
            self.requestResponse.confirmed = confirmed
            self.requestResponse.notes = notes
            # TODO: update in datamodel

    def searchTextEdit(self, targetWidget):
        # TODO: simulate regex searching
        searchText = self.searchLineEdit.text()
        return targetWidget.find(searchText)

    def searchScintilla(self, targetWidget):
        searchText = self.searchLineEdit.text()
        line, index = targetWidget.getCursorPosition()
        return targetWidget.findFirst(searchText, self.searchReCheckBox.isChecked(), False, False, True, True, line, index)

    def searchWebView(self, targetWidget):
        is_re = self.searchReCheckBox.isChecked()
        searchText = self.searchLineEdit.text()
        # TODO: simulate regex search
        targetWidget.findText('', QtWebKit.QWebPage.FindWrapsAroundDocument|QtWebKit.QWebPage.HighlightAllOccurrences)
        return targetWidget.findText(searchText, QtWebKit.QWebPage.FindWrapsAroundDocument|QtWebKit.QWebPage.HighlightAllOccurrences)

    def viewItemSelected(self, index):
        if index and index.isValid():
            obj = index.internalPointer()
            self.fill(obj.Id)

    def clear(self):
        # clear
        self.responseId = None
        self.requestResponse = None
        self.requestScintilla.setText('')
        self.responseScintilla.setText('')
        self.hexBodyScintilla.setText('')
        self.scriptsScintilla.setText('')
        self.commentsScintilla.setText('')
        self.linksScintilla.setText('')
        self.formsScintilla.setText('')
        self.renderWebView.setHtml('')
        self.generatedSourceWebView.setHtml('')
        self.generatedSourceScintilla.setText('')
        self.contentResults = None
        self.notesTextEdit.setPlainText('')
        self.confirmedButtonStateChanged(Qt.Unchecked)

    def set_search_info(self, searchText, isRE):
        self.searchLineEdit.setText(searchText)
        self.searchReCheckBox.setChecked(isRE)

    def fill(self, Id):
        if self.requestResponse and self.requestResponse.Id == Id:
            # already filled
            return

        if self.qlock.tryLock():
            try:
                self.fill_internal(Id)
            finally:
                self.qlock.unlock()

    def fill_internal(self, Id):

        self.clear()

        if not Id:
            return

        self.responseId = Id
        self.requestResponse = self.framework.get_request_response(Id)
        rr = self.requestResponse

        confirmedState = Qt.Unchecked
        if rr.confirmed and rr.confirmed.lower() in ['y', '1', 'true']:
            confirmedState = Qt.Checked
        self.confirmedButtonStateChanged(confirmedState)

        self.requestScintilla.setText(rr.rawRequest)

        self.attachLexer(self.responseScintilla, rr.responseContentType, rr.responseBody)
        self.responseScintilla.setText(ContentHelper.convertBytesToDisplayText(rr.rawResponse))
        self.hexBodyScintilla.setText(self.hexDumper.dump(rr.responseBody))
        self.contentResults = self.generateExtractorResults(rr.responseHeaders, rr.responseBody, rr.responseUrl, rr.charset)
        self.notesTextEdit.setText(rr.notes)
        self.handle_tab_currentChanged(self.tabwidget.currentIndex())

    def generateExtractorResults(self, headers, body, url, charset):
        rr = self.requestResponse
        scriptsIO, commentsIO, linksIO, formsIO = StringIO(), StringIO(), StringIO(), StringIO()
        try:
            results = rr.results
            if 'html' == rr.baseType:
                # Create content for parsing HTML
                self.htmlExtractor.process(body, url, charset, results)

                self.tabwidget.setTabText(self.scriptsTabIndex, 'Scripts')
                for script in results.scripts:
                    scriptsIO.write('%s\n\n' % self.flat_str(script))

                self.attachLexer(self.commentsScintilla, 'html')
                for comment in results.comments:
                    commentsIO.write('%s\n\n' % self.flat_str(comment))

                for link in results.links:
                    linksIO.write('%s\n' % self.flat_str(link))

                for form in results.forms:
                    formsIO.write('%s\n' % self.flat_str(form))

                for input in results.other_inputs:
                    formsIO.write('%s\n' % self.flat_str(input))

            elif 'javascript' == rr.baseType:

                self.tabwidget.setTabText(self.scriptsTabIndex, 'Strings')
                for script_string in results.strings:
                    scriptsIO.write('%s\n' % self.flat_str(script_string))

                self.attachLexer(self.commentsScintilla, 'javascript')
                for comment in results.comments:
                    commentsIO.write('%s\n' % self.flat_str(comment))

                for link in results.links:
                    linksIO.write('%s\n' % self.flat_str(link))

                for link in results.relative_links:
                    linksIO.write('%s\n' % self.flat_str(link))

        except Exception as e:
            # TODO: log 
            self.framework.report_exception(e)

        self.scriptsScintilla.setText(scriptsIO.getvalue())
        self.commentsScintilla.setText(commentsIO.getvalue())
        self.linksScintilla.setText(linksIO.getvalue())
        self.formsScintilla.setText(formsIO.getvalue())

    def flat_str(self, u):
        if bytes == type(u):
            try:
                s = u.decode('utf-8')
            except UnicodeDecodeError:
                s = repr(u)[2:-1].replace('\\r', '').replace('\\n', '\n').replace('\\t', '\t')

            return s
        else:
            # may be object type implementing str
            s = str(u)
            return s

    def attachLexer(self, scintillaWidget, contentType, data = ''):
        lexer = self.getLexer(contentType, data)
        if lexer:
            lexerInstance = lexer(scintillaWidget)
            lexerInstance.setFont(self.framework.get_font())
            scintillaWidget.setLexer(lexerInstance)
        else:
            scintillaWidget.setLexer(None)

    def handle_tab_currentChanged(self, index):
        if index == self.renderTabIndex:
            return self.doRenderApply()
        elif index == self.generatedSourceTabIndex:
            return self.doGeneratedSourceApply()
        return False
        
    def doRenderApply(self):
        rr = self.requestResponse
        if rr and rr.responseUrl:
            self.renderWebView.fill_from_response(rr.responseUrl, rr.responseHeaders, rr.responseBody, rr.responseContentType)
            return True
        return False

    def doGeneratedSourceApply(self):
        rr = self.requestResponse
        if rr and rr.responseUrl and 'html' == rr.baseType:
            self.generatedSourceWebView.fill_from_response(rr.responseUrl, rr.responseHeaders, rr.responseBody, rr.responseContentType)
            return True
        return False

    def generatedSource_handle_loadFinished(self):
        self.set_generated_source(self.generatedSourceWebView)

    def render_handle_loadFinished(self):
        self.set_generated_source(self.renderWebView)

    def set_generated_source(self, webview):
        # TODO: consider merging frames sources?
        # TODO: consider other optimizations
        if self.requestResponse:
            rr = self.requestResponse
            xhtml = webview.page().mainFrame().documentElement().toOuterXml()
            self.generatedSourceScintilla.setText(xhtml)
            body_bytes = xhtml.encode('utf-8')
            self.generateExtractorResults(rr.responseHeaders, body_bytes, rr.responseUrl, rr.charset)

    def getLexer(self, contentType, data):
        lexerContentType = self.inferContentType(contentType, data)
        return self.lexerMapping[lexerContentType]
        
    def inferContentType(self, contentType, data):
        # TODO: scan data for additional info
        # XXX: data -> bytes
        for comp in list(self.contentTypeMapping.keys()):
            if comp in contentType:
                return self.contentTypeMapping[comp]
        return 'text'
            
    def set_search(self, tabname, searchText):
        if tabname == 'request':
            self.tabwidget.setCurrentIndex(0)
        elif tabname=='response':
            self.tabwidget.setCurrentIndex(1)
        self.searchLineEdit.setText(searchText)
        self.requestScintilla.findFirst(searchText, False, True, False, True)
        self.responseScintilla.findFirst(searchText, False, True, False, True)
Beispiel #4
0
class SequenceDialog(QDialog, SequenceDialog.Ui_seqBuildDialog):
    """ The sequence builder dialog """
    
    def __init__(self, framework, parent=None):
        super(SequenceDialog, self).__init__(parent)
        self.setupUi(self)

        self.framework = framework
        QObject.connect(self, SIGNAL('destroyed(QObject*)'), self._destroyed)

        self.scintillaWidgets = set() # store scintilla widget reference to handle zoom in/zoom out

        # TODO: move to framework constants
        self.known_media_types = ('text/css', 'application/javascript', 'text/javascript', 'image/gif', 'image/png', 'image/jpeg', 'image/bmp')

        self.cookieJar = SequenceBuilderCookieJar(self.framework, self)
        self.networkAccessManager = SequenceBuilderNetworkAccessManager(self.framework, self.cookieJar)
        self.formCapture = SequenceBuilderFormCapture(self.framework, self)
        self.pageFactory = SequenceBuilderPageFactory(self.framework, self.formCapture, self)
        self.standardPageFactory = StandardPageFactory(self.framework, self.networkAccessManager, self)

        QObject.connect(self.networkAccessManager, SIGNAL('finished(QNetworkReply *)'), self.process_request_finished)
        self.embedded = EmbeddedWebkitWidget.EmbeddedWebkitWidget(self.framework, self.networkAccessManager, self.pageFactory, self.webBrowserFrame, self)

        self.sequenceTabWidget.currentChanged.connect(self.handle_currentChanged)
        self.is_recording = False
        self.sequence_items = {}
        self.qlock = QMutex()
        self.startRecordingButton.clicked.connect(self.handle_startRecording_clicked)
        self.stopRecordingButton.clicked.connect(self.handle_stopRecording_clicked)
        self.saveSequenceButton.clicked.connect(self.handle_saveSequence_clicked)
        self.deleteSequenceButton.clicked.connect(self.handle_deleteSequence_clicked)
        self.deleteSequenceButton.setEnabled(False)

        # attach RenderingWebView to renderViewSequenceTabWidget
        self.sequenceRenderView_Layout = QVBoxLayout(self.renderViewSequenceTabWidget)
        self.sequenceRenderView = RenderingWebView(self.framework, self.standardPageFactory, self.renderViewSequenceTabWidget)
        self.sequenceRenderView_Layout.addWidget(self.sequenceRenderView)
        self.sequencePropertiesTabWidget.currentChanged.connect(self.handle_properties_currentChanged)
        self.sequenceRenderView.page().selectionChanged.connect(self.handle_renderView_selectionChanged)

        # use Scintilla for request and response views
        self.sequenceRequestView_Layout = QVBoxLayout(self.requestViewSequenceTabWidget)
        self.sequenceRequestViewEdit = Qsci.QsciScintilla(self.requestViewSequenceTabWidget)
        self.setScintillaProperties(self.sequenceRequestViewEdit)
        self.sequenceRequestView_Layout.addWidget(self.sequenceRequestViewEdit)

        self.sequenceResponseView_Layout = QVBoxLayout(self.responseViewSequenceTabWidget)
        self.sequenceResponseViewEdit = Qsci.QsciScintilla(self.responseViewSequenceTabWidget)
        self.setScintillaProperties(self.sequenceResponseViewEdit, 'html')
        self.sequenceResponseView_Layout.addWidget(self.sequenceResponseViewEdit)

        self.sequenceStepsTreeWidget.itemClicked.connect(self.handle_steps_itemClicked)
        self.sequenceStepsTreeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.menu = QMenu(self.sequenceStepsTreeWidget)
        self.connect(self.sequenceStepsTreeWidget, SIGNAL("customContextMenuRequested(const QPoint&)"), self.sequence_steps_context_menu)
        action = QAction("Remove from sequence", self)
        action.triggered.connect(self.handle_remove_from_sequence)
        self.menu.addAction(action)
        action = QAction("Copy URL", self)
        action.triggered.connect(self.sequence_step_copy_url)
        self.menu.addAction(action)

        self.inSessionPatternEdit.textChanged.connect(self.handle_sessionEdit_textChanged)
        self.inSessionPatternRE.stateChanged.connect(self.handle_sessionRE_stateChanged)
        self.outOfSessionPatternEdit.textChanged.connect(self.handle_sessionEdit_textChanged)
        self.outOfSessionPatternRE.stateChanged.connect(self.handle_sessionRE_stateChanged)

        QObject.connect(self.sequencesComboBox, SIGNAL('currentIndexChanged(const QString &)'), self.handle_sequenceCombo_text_currentIndexChanged)
        QObject.connect(self.sequencesComboBox, SIGNAL('currentIndexChanged(int)'), self.handle_sequenceCombo_currentIndexChanged)

        self.useSessionDetectionCheckbox.stateChanged.connect(self.handle_useSessionDection_stateChanged)
        self.setUseSessionDetection()
        
        self.includeMediaCheckbox.stateChanged.connect(self.handle_includeMedia_stateChanged)

        self.framework.subscribe_add_sequence_builder_response_id(self.add_manual_sequence_builder_item)

        self.originatingResponses = {}
        self.sequenceResponseIds = set()

        self.Data = None
        self.cursor = None
        self.framework.subscribe_database_events(self.db_attach, self.db_detach)

        self.framework.subscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.subscribe_zoom_out(self.zoom_out_scintilla)

    def _destroyed(self):
        self.framework.unsubscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.unsubscribe_zoom_out(self.zoom_out_scintilla)

    def display_confirm_dialog(self, message):
        response = QMessageBox.question(self, 'Confirm', message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if 0 != (response & QMessageBox.Yes):
            return True
        else:
            return False

    def db_attach(self):
        self.Data = self.framework.getDB()
        self.cursor = self.Data.allocate_thread_cursor()
        self.populate_sequence_combo()

    def db_detach(self):
        if self.Data:
            self.close_cursor()
            self.Data = None

    def close_cursor(self):
        if self.cursor and self.Data:
            self.cursor.close()
            self.Data.release_thread_cursor(self.cursor)
            self.cursor = None

    def populate_sequence_combo(self, selectedText = ''):
        self.sequencesComboBox.clear()
        item = self.sequencesComboBox.addItem('New Sequence - %s' % (time.asctime(time.localtime())), '-1')
        
        for row in self.Data.get_all_sequences(self.cursor):
            sequenceItem = [m or '' for m in row]
            name = str(sequenceItem[1])
            Id = str(sequenceItem[0])
            item = self.sequencesComboBox.addItem(name, Id)

        if selectedText:
            index = self.sequencesComboBox.findText(selectedText)
            self.sequencesComboBox.setCurrentIndex(index)
        else:
            self.populate_manual_sequence_items_from_db()

    def handle_deleteSequence_clicked(self):
        sequenceId = self.sequencesComboBox.itemData(self.sequencesComboBox.currentIndex())
        if self.display_confirm_dialog('Delete sequence: %s?' % self.sequencesComboBox.currentText()):
            self.Data.delete_sequence(self.cursor, int(sequenceId))
            
        self.populate_sequence_combo()

    def handle_sequenceCombo_text_currentIndexChanged(self, text):
        pass

    def handle_sequenceCombo_currentIndexChanged(self, index):
        if -1 == index:
            return

        sequenceId = self.sequencesComboBox.itemData(index)

        if '-1' != sequenceId:
            self.deleteSequenceButton.setEnabled(True)
        else:
            self.deleteSequenceButton.setEnabled(False)

        self.fill_sequence_info(sequenceId)

    def handle_saveSequence_clicked(self):
        try:
            currentItem = self.sequencesComboBox.itemData(self.sequencesComboBox.currentIndex())
            sequenceId = currentItem
            print(('current', sequenceId))
            if '' == sequenceId:
                return
            sequenceId = int(sequenceId)
            if -1 == sequenceId:
                sequenceId = self.Data.insert_new_sequence(self.cursor, 
                                                  [None, 
                                                   str(self.sequencesComboBox.currentText()), str(self.sequenceTypeComboBox.currentText()),
                                                   int(self.useSessionDetectionCheckbox.isChecked()), int(self.includeMediaCheckbox.isChecked()),
                                                   int(self.useBrowserCheckbox.isChecked()), 
                                                   str(self.inSessionPatternEdit.text()), int(self.inSessionPatternRE.isChecked()),
                                                   str(self.outOfSessionPatternEdit.text()), int(self.outOfSessionPatternRE.isChecked()),
                                                   int(self.dynamicDataCheckbox.isChecked())
                                                   ])
            else:
                self.Data.update_sequence(self.cursor, 
                                                  [str(self.sequencesComboBox.currentText()), str(self.sequenceTypeComboBox.currentText()),
                                                   int(self.useSessionDetectionCheckbox.isChecked()), int(self.includeMediaCheckbox.isChecked()),
                                                   int(self.useBrowserCheckbox.isChecked()), 
                                                   str(self.inSessionPatternEdit.text()), int(self.inSessionPatternRE.isChecked()),
                                                   str(self.outOfSessionPatternEdit.text()), int(self.outOfSessionPatternRE.isChecked()),
                                                   int(self.dynamicDataCheckbox.isChecked()),
                                                   sequenceId])
                self.Data.clear_sequence_steps(self.cursor, sequenceId)
                self.Data.clear_sequence_parameters(self.cursor, sequenceId)

            # insert steps
            for index in range(0, self.sequenceStepsTreeWidget.topLevelItemCount()):
                item = self.sequenceStepsTreeWidget.topLevelItem(index)
                if item:
                    stepnum  = str(item.text(0))
                    sequence_item = self.sequence_items[stepnum]
                    isEnabled = not (item.isDisabled() or item.isHidden())
                    isHidden = item.isHidden() and not item.isDisabled()
                    self.Data.insert_sequence_step(self.cursor, [sequenceId, int(stepnum), int(sequence_item['responseId']), isEnabled, isHidden])

            # insert parameters
            # TODO: populate and read from tree view
            parameters = self.formCapture.allParameters(self.sequenceResponseIds)
            print(('save parameters', parameters))
            for responseId, requestId, param, value, origin in parameters:
                if 'source' == origin:
                    if responseId:
                        self.Data.insert_sequence_source_parameter(self.cursor, [
                                sequenceId,
                                responseId,
                                param.source,
                                param.position,
                                param.Type,
                                param.name,
                                json.dumps(value).encode('utf-8'),
                                True
                                ])
                elif 'target' == origin:
                    self.Data.insert_sequence_target_parameter(self.cursor, [
                            sequenceId,
                            responseId,
                            param.source,
                            param.position,
                            param.name,
                            json.dumps(value).encode('utf-8'),
                            True
                            ])

            # insert cookies
            # TODO: populate and read from tree view
            cookieList = self.cookieJar.allCookies()
            for cookie in cookieList:
                self.Data.insert_sequence_cookie(self.cursor, [
                        sequenceId,
                        str(cookie.domain()),
                        str(cookie.name(), 'utf-8'),
                        cookie.toRawForm().data(),
                        True
                        ])

            self.Data.commit()
        except Exception as error:
            self.Data.rollback()
            self.framework.report_exception(error)
            raise

        self.framework.signal_sequences_changed()
        self.populate_sequence_combo(self.sequencesComboBox.currentText())

    def add_manual_sequence_builder_item(self, responseId):
        if not self.is_recording:
            self.populate_manual_sequence_items_from_db()

    def populate_manual_sequence_items_from_db(self):
        responseIds = self.Data.get_sequence_builder_manual_items(self.cursor).fetchall()
        if len(responseIds) == 0:
            return
        self.startRecordingButton.setEnabled(False)
        for item in responseIds:
            responseId = str(item[0])
            self.infer_source_information(responseId)
            self.append_sequence_item(responseId)
        self.Data.clear_sequence_builder_manual_items(self.cursor)

    def infer_source_information(self, responseId):
        # TODO: implement
        pass

    def fill_sequence_info(self, sequenceId):
        self.sequenceResponseIds = set()
        sequenceId = int(sequenceId)
        if -1 == sequenceId:
            sequenceItem = (sequenceId, '', '', 0, 0, 0, '', 0, '', 0, 0)
        else:
            row = self.Data.get_sequence_by_id(self.cursor, sequenceId)
            if not row:
                return
            datarow = list(row)
            sequenceItem = [m or '' for m in datarow]

        self.reset_sequence_layout(sequenceId, sequenceItem)

    def reset_sequence_layout(self, sequenceId, sequenceItem):

        index = self.sequenceTypeComboBox.findText(str(sequenceItem[2]))
        if -1 != index:
            self.sequenceTypeComboBox.setCurrentIndex(index)
        else:
            self.sequenceTypeComboBox.setCurrentIndex(0)

        self.useSessionDetectionCheckbox.setChecked(bool(sequenceItem[3]))
        self.includeMediaCheckbox.setChecked(bool(bool(sequenceItem[4])))
        self.useBrowserCheckbox.setChecked(bool(sequenceItem[5]))
        self.inSessionPatternEdit.setText(str(sequenceItem[6]))
        self.inSessionPatternRE.setChecked(bool(sequenceItem[7]))
        self.outOfSessionPatternEdit.setText(str(sequenceItem[8]))
        self.outOfSessionPatternRE.setChecked(bool(sequenceItem[9]))
        self.dynamicDataCheckbox.setChecked(bool(sequenceItem[SequencesTable.DYNAMIC_DATA]))

        self.sequencePropertiesTabWidget.setCurrentIndex(0)
        self.sequenceResponseViewEdit.setText('')
        self.sequenceRequestViewEdit.setText('')
        self.sequenceRenderView.setHtml('', QUrl('about:blank'))

        self.sequenceStepsTreeWidget.clear()
        self.sequence_items = {}

        if -1 != sequenceId:
            sequenceSteps = []
            for row in self.Data.get_sequence_steps(self.cursor, sequenceId):
                stepItems = [m or '' for m in row]
                sequenceSteps.append(stepItems)
            for stepItems in sequenceSteps:
                item = self.append_sequence_item(str(stepItems[SequenceStepsTable.RESPONSE_ID]))
                if not bool(stepItems[SequenceStepsTable.IS_ENABLED]):
                    item.setHidden(True)
                    if not bool(stepItems[SequenceStepsTable.IS_HIDDEN]):
                        item.setDisabled(True)

        self.is_recording = False
        self.stopRecordingButton.setEnabled(False)
        self.startRecordingButton.setEnabled(True)

        self.setUseSessionDetection()
        self.run_pattern_matches()

    def handle_useSessionDection_stateChanged(self, state):
        self.setUseSessionDetection()

    def handle_includeMedia_stateChanged(self):
        for index in range(0, self.sequenceStepsTreeWidget.topLevelItemCount()):
            item = self.sequenceStepsTreeWidget.topLevelItem(index)
            if item and not item.isDisabled():
                contentType = str(item.text(3))
                self.hide_media_type_item(item, contentType)

    def setUseSessionDetection(self):
        self.inSessionPatternEdit.setEnabled(self.useSessionDetectionCheckbox.isChecked())
        self.inSessionPatternRE.setEnabled(self.useSessionDetectionCheckbox.isChecked())
        self.outOfSessionPatternEdit.setEnabled(self.useSessionDetectionCheckbox.isChecked())
        self.outOfSessionPatternRE.setEnabled(self.useSessionDetectionCheckbox.isChecked())
        if not self.useSessionDetectionCheckbox.isChecked():
            self.inSessionPatternEdit.setPalette(QApplication.palette())
            self.outOfSessionPatternEdit.setPalette(QApplication.palette())

    # TODO: refactor this code - 
    def setScintillaProperties(self, scintillaWidget, lexerType = ''):
        scintillaWidget.setFont(self.framework.get_font())
        scintillaWidget.setWrapMode(1)
        scintillaWidget.zoomTo(self.framework.get_zoom_size())
        # TOOD: set based on line numbers (size is in pixels)
        scintillaWidget.setMarginWidth(1, '1000')
        self.scintillaWidgets.add(scintillaWidget)
        if 'html' == lexerType:
            lexerInstance = Qsci.QsciLexerHTML(scintillaWidget)
            lexerInstance.setFont(self.framework.get_font())
            scintillaWidget.setLexer(lexerInstance)

    def zoom_in_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomIn()

    def zoom_out_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomOut()

    def handle_startRecording_clicked(self):
        self.sequenceStepsTreeWidget.clear()
        self.stopRecordingButton.setEnabled(True)
        self.startRecordingButton.setEnabled(False)
        self.cookieJar.start_tracking()
        self.formCapture.start_tracking()
        self.sequence_items = {}
        self.sequenceResponseIds = set()
        self.is_recording = True
        self.sequenceTabWidget.setCurrentIndex(1)

    def handle_stopRecording_clicked(self):
        self.is_recording = False
        self.cookieJar.stop_tracking()
        self.formCapture.stop_tracking()
        self.stopRecordingButton.setEnabled(False)
        self.startRecordingButton.setEnabled(True)

    def handle_currentChanged(self, index):
        # TODO: clean this up
        if 3 == index:
            self.sequenceParametersTreeWidget.clear()
            print(('sequenceResponseIds', type(self.sequenceResponseIds), self.sequenceResponseIds))
            parameters = self.formCapture.allParameters(self.sequenceResponseIds)
            for responseId, requestId, param, value, origin in parameters:
                print((responseId, requestId, param, value, origin))
                rId = ''
                Xref = ''
                source = ''
                target = ''
                value_type = param.Type
                position = str(param.position)
                if 'source' == origin:
#                    rId = requestId
#                    Xref = self.formCapture.get_sequence_transition(requestId)
                    rId = responseId
                    Xref = requestId
                    source = param.source
                elif 'target' == origin:
                    rId = responseId
                    Xref = requestId
                    target = param.source

                item = QTreeWidgetItem([
                        '',  
                        rId,
                        Xref,
                        source,
                        value_type,
                        position,
                        target,
                        param.name,
                        ','.join(value), # value can be list
                        param.url,
                        ])
                self.sequenceParametersTreeWidget.addTopLevelItem(item)
        elif 2 == index:
            self.sequenceCookiesTreeWidget.clear()
            cookieList = self.cookieJar.allCookies()
            for cookie in cookieList:
                item = QTreeWidgetItem([
                        '',  
                        str(cookie.domain()),
                        str(cookie.name()),
                        str(cookie.value()),
                        str(cookie.path()),
                        str(cookie.expirationDate().toUTC().toString('MM/dd/yyyy hh:mm:ss')),
                        str(cookie.isSessionCookie()),
                        str(cookie.isHttpOnly()),
                        str(cookie.isSecure()),
                        ])
                if self.cookieJar.is_cookie_tracked(str(cookie.domain()), str(cookie.name())):
                    item.setCheckState(0, Qt.Checked)
                else:
                    item.setCheckState(0, Qt.Unchecked)
                self.sequenceCookiesTreeWidget.addTopLevelItem(item)

    def process_request_finished(self, reply):
        if not self.is_recording:
            return
        self.qlock.lock()
        try:
            responseId, requestId, xrefId = '', '', ''
            varId = reply.attribute(QNetworkRequest.User)
            if varId is not None:
                responseId = str(varId)
            varId = reply.attribute(QNetworkRequest.User + 1)
            if varId is not None:
                requestId = str(varId)
            varId = reply.attribute(QNetworkRequest.User + 2)
            if varId is not None:
                xrefId = str(varId)

            print(('process_request_finished', responseId, requestId, xrefId))

            if xrefId and requestId and responseId:
                if requestId not in self.originatingResponses:
                    self.originatingResponses[requestId] = responseId

                # TODO: is this necessary?
                originatingObject = reply.request().originatingObject()
                if originatingObject:
                    originatingObject.setProperty('RAFT_responseId', self.originatingResponses[requestId])

#            print(self.originatingResponses)

            if responseId:
                self.append_sequence_item(responseId, requestId)
        finally:
            self.qlock.unlock()

    def append_sequence_item(self, responseId, requestId = ''):
        topItem = self.sequenceStepsTreeWidget.topLevelItem(self.sequenceStepsTreeWidget.topLevelItemCount()-1)
        if topItem is None:
            current_max = 0
        else:
            current_max = int(topItem.text(0))
        stepnum = str(current_max + 1)

        row = self.Data.read_responses_by_id(self.cursor, responseId)
        if not row:
            return

        self.sequenceResponseIds.add(responseId)

        responseItems = interface.data_row_to_response_items(row)

        url = responseItems[ResponsesTable.URL]
        method = responseItems[ResponsesTable.REQ_METHOD]
        contentType = responseItems[ResponsesTable.RES_CONTENT_TYPE].lower().strip()
        charset = ContentHelper.getCharSet(contentType)
        if contentType and ';' in contentType:
            contentType = contentType[0:contentType.index(';')]

        reqHeaders = responseItems[ResponsesTable.REQ_HEADERS]
        reqData = responseItems[ResponsesTable.REQ_DATA]
        requestHeaders, requestBody, rawRequest = ContentHelper.combineRaw(reqHeaders, reqData)

        resHeaders = responseItems[ResponsesTable.RES_HEADERS]
        resData = responseItems[ResponsesTable.RES_DATA]
        responseHeaders, responseBody, rawResponse = ContentHelper.combineRaw(resHeaders, resData, charset)

        sequence_item = {
            'responseUrl' : url,
            'responseId' : responseId,
            'rawResponse' : rawResponse,
            'rawRequest' : rawRequest,
            'method' : method,
            }

        self.sequence_items[stepnum] = sequence_item
        status = self.check_pattern_match(sequence_item)
        item = QTreeWidgetItem([stepnum, status, method, contentType, url])
        self.sequenceStepsTreeWidget.addTopLevelItem(item)

        self.hide_media_type_item(item, contentType)

        self.formCapture.process_target_request(responseId, requestId, method, url, reqHeaders, reqData)

        return item

    def hide_media_type_item(self, item, contentType):
        # TODO: move this to a common module that applies mime type checking
        hide_it = not self.includeMediaCheckbox.isChecked()
        if 'html' in contentType:
            pass
        elif contentType in self.known_media_types:
            item.setHidden(hide_it)
        elif '/' in contentType:
            ctype, stype = contentType.split('/', 1)
            if ctype in ('audio', 'image', 'video'):
                item.setHidden(hide_it)

    def handle_properties_currentChanged(self, index):
        item = self.sequenceStepsTreeWidget.currentItem()
        if item is None:
            return

        self.inSessionPatternEdit.setPalette(QApplication.palette())
        self.outOfSessionPatternEdit.setPalette(QApplication.palette())

        if 0 == index:
            pass
        elif 1 == index:
           self.update_sequenceRequestView(str(item.text(0)))
        elif 2 == index:
            self.update_sequenceResponseView(str(item.text(0)))
        elif 3 == index:
            self.update_sequenceRenderView(str(item.text(0)))

    def handle_renderView_selectionChanged(self):
        pass

    def handle_steps_itemClicked(self, item, column):

        index = self.sequencePropertiesTabWidget.currentIndex()

        if 0 == index:
            return

        if item is None:
            return

        if 1 == index:
           self.update_sequenceRequestView(str(item.text(0)))
        elif 2 == index:
            self.update_sequenceResponseView(str(item.text(0)))
        elif 3 == index:
            self.update_sequenceRenderView(str(item.text(0)))

    def update_sequenceRenderView(self, stepnum):
        sequence_item = self.sequence_items[stepnum]
        self.sequenceRenderView.fill_from_db(sequence_item['responseId'], sequence_item['responseUrl'])

    def update_sequenceResponseView(self, stepnum):
        sequence_item = self.sequence_items[stepnum]
        rawResponse = sequence_item['rawResponse']
        self.sequenceResponseViewEdit.setText(rawResponse)
        self.run_pattern_matches()

    def update_sequenceRequestView(self, stepnum):
        sequence_item = self.sequence_items[stepnum]
        rawRequest = sequence_item['rawRequest']
        self.sequenceRequestViewEdit.setText(rawRequest)
        self.run_pattern_matches()

    def check_pattern_match(self, sequence_item):
        if not self.useSessionDetectionCheckbox.isChecked():
            return ''

        rawResponse = sequence_item['rawResponse']
        is_insession = False
        is_outofsession = False

        searchText = str(self.inSessionPatternEdit.text())
        if searchText:
            if self.inSessionPatternRE.isChecked():
                try:
                    if self.re_insession is None:
                        self.re_insession = re.compile(searchText, re.I)
                    if self.re_insession.search(rawResponse):
                        is_insession = True
                except Exception as e:
                    self.framework.report_implementation_error(e)
            else:
                if -1 != rawResponse.lower().find(searchText.lower()):
                    is_insession = True

        searchText = str(self.outOfSessionPatternEdit.text())
        if searchText:
            if self.outOfSessionPatternRE.isChecked():
                try:
                    if self.re_outofsession is None:
                        self.re_outofsession = re.compile(searchText, re.I)
                    if self.re_outofsession.search(rawResponse):
                        is_outofsession = True
                except Exception as e:
                    self.framework.report_implementation_error(e)

            else:
                if -1 != rawResponse.lower().find(searchText.lower()):
                    is_outofsession = True

        if is_insession and not is_outofsession:
            return 'In-Session'
        elif not is_insession and is_outofsession:
            return 'Out-of-Session'
        elif not is_insession and not is_outofsession:
            return ''
        else:
            return 'Conflict'

    def run_pattern_matches(self):
        if not self.useSessionDetectionCheckbox.isChecked():
            # no pattern matching
            for index in range(0, self.sequenceStepsTreeWidget.topLevelItemCount()):
                item = self.sequenceStepsTreeWidget.topLevelItem(index)
                if item:
                    item.setText(1, '')
            return
        else:
            self.do_apply_pattern_selection(self.inSessionPatternEdit, self.inSessionPatternRE)
            self.do_apply_pattern_selection(self.outOfSessionPatternEdit, self.outOfSessionPatternRE)

            for index in range(0, self.sequenceStepsTreeWidget.topLevelItemCount()):
                item = self.sequenceStepsTreeWidget.topLevelItem(index)
                if item:
                    step_num = str(item.text(0))
                    sequence_item = self.sequence_items[step_num]
                    status = self.check_pattern_match(sequence_item)
                    item.setText(1, status)

    def handle_sessionEdit_textChanged(self, text):
        self.re_insession = None
        self.re_outofsession = None
        self.run_pattern_matches()

    def handle_sessionRE_stateChanged(self, state):
        self.re_insession = None
        self.re_outofsession = None
        self.run_pattern_matches()

    def do_apply_pattern_selection(self, search_line_edit, re_checkbox):
        searchText = search_line_edit.text()
        is_re = re_checkbox.isChecked()

        if not searchText:
            search_line_edit.setPalette(QApplication.palette())
            return

        tabindex = self.sequencePropertiesTabWidget.currentIndex()

        if 0 == tabindex:
            return
        elif 1 == tabindex:
            pass
        elif 2 == tabindex:
            # Scintilla doesn't understand same regex characters as Python
            # TODO: consider improving this emulation
            if is_re:
                try:
                    r = re.compile(str(searchText), re.I)
                    tmp = str(self.sequenceResponseViewEdit.text())
                    m = r.search(tmp)
                    if m:
                        searchText = m.group(0)
                        is_re = False
                except Exception as e:
                    pass
                      
            if not self.sequenceResponseViewEdit.findFirst(searchText, is_re, False, False, True, True, 0, 0):
                p = search_line_edit.palette()
                p.setColor(QPalette.Text, QColor('red'))
                search_line_edit.setPalette(p)
            else:
                search_line_edit.setPalette(QApplication.palette())
        elif 3 == tabindex:
            pass

    def sequence_steps_context_menu(self, point):
        self.menu.exec_(self.sequenceStepsTreeWidget.mapToGlobal(point))

    def handle_remove_from_sequence(self):
        item = self.sequenceStepsTreeWidget.currentItem()
        if item is None:
            return
        item.setDisabled(True)
        item.setHidden(True)

    def sequence_step_copy_url(self):
        item = self.sequenceStepsTreeWidget.currentItem()
        if item is None:
            return
        curUrl = item.text(4)
        if curUrl:
            QApplication.clipboard().setText(curUrl)
Beispiel #5
0
class SequenceDialog(QDialog, SequenceDialog.Ui_seqBuildDialog):
    """ The sequence builder dialog """
    def __init__(self, framework, parent=None):
        super(SequenceDialog, self).__init__(parent)
        self.setupUi(self)

        self.framework = framework
        QObject.connect(self, SIGNAL('destroyed(QObject*)'), self._destroyed)

        self.scintillaWidgets = set(
        )  # store scintilla widget reference to handle zoom in/zoom out

        # TODO: move to framework constants
        self.known_media_types = ('text/css', 'application/javascript',
                                  'text/javascript', 'image/gif', 'image/png',
                                  'image/jpeg', 'image/bmp')

        self.cookieJar = SequenceBuilderCookieJar(self.framework, self)
        self.networkAccessManager = SequenceBuilderNetworkAccessManager(
            self.framework, self.cookieJar)
        self.formCapture = SequenceBuilderFormCapture(self.framework, self)
        self.pageFactory = SequenceBuilderPageFactory(self.framework,
                                                      self.formCapture, self)
        self.standardPageFactory = StandardPageFactory(
            self.framework, self.networkAccessManager, self)

        QObject.connect(self.networkAccessManager,
                        SIGNAL('finished(QNetworkReply *)'),
                        self.process_request_finished)
        self.embedded = EmbeddedWebkitWidget.EmbeddedWebkitWidget(
            self.framework, self.networkAccessManager, self.pageFactory,
            self.webBrowserFrame, self)

        self.sequenceTabWidget.currentChanged.connect(
            self.handle_currentChanged)
        self.is_recording = False
        self.sequence_items = {}
        self.qlock = QMutex()
        self.startRecordingButton.clicked.connect(
            self.handle_startRecording_clicked)
        self.stopRecordingButton.clicked.connect(
            self.handle_stopRecording_clicked)
        self.saveSequenceButton.clicked.connect(
            self.handle_saveSequence_clicked)
        self.deleteSequenceButton.clicked.connect(
            self.handle_deleteSequence_clicked)
        self.deleteSequenceButton.setEnabled(False)

        # attach RenderingWebView to renderViewSequenceTabWidget
        self.sequenceRenderView_Layout = QVBoxLayout(
            self.renderViewSequenceTabWidget)
        self.sequenceRenderView = RenderingWebView(
            self.framework, self.standardPageFactory,
            self.renderViewSequenceTabWidget)
        self.sequenceRenderView_Layout.addWidget(self.sequenceRenderView)
        self.sequencePropertiesTabWidget.currentChanged.connect(
            self.handle_properties_currentChanged)
        self.sequenceRenderView.page().selectionChanged.connect(
            self.handle_renderView_selectionChanged)

        # use Scintilla for request and response views
        self.sequenceRequestView_Layout = QVBoxLayout(
            self.requestViewSequenceTabWidget)
        self.sequenceRequestViewEdit = Qsci.QsciScintilla(
            self.requestViewSequenceTabWidget)
        self.setScintillaProperties(self.sequenceRequestViewEdit)
        self.sequenceRequestView_Layout.addWidget(self.sequenceRequestViewEdit)

        self.sequenceResponseView_Layout = QVBoxLayout(
            self.responseViewSequenceTabWidget)
        self.sequenceResponseViewEdit = Qsci.QsciScintilla(
            self.responseViewSequenceTabWidget)
        self.setScintillaProperties(self.sequenceResponseViewEdit, 'html')
        self.sequenceResponseView_Layout.addWidget(
            self.sequenceResponseViewEdit)

        self.sequenceStepsTreeWidget.itemClicked.connect(
            self.handle_steps_itemClicked)
        self.sequenceStepsTreeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.menu = QMenu(self.sequenceStepsTreeWidget)
        self.connect(self.sequenceStepsTreeWidget,
                     SIGNAL("customContextMenuRequested(const QPoint&)"),
                     self.sequence_steps_context_menu)
        action = QAction("Remove from sequence", self)
        action.triggered.connect(self.handle_remove_from_sequence)
        self.menu.addAction(action)
        action = QAction("Copy URL", self)
        action.triggered.connect(self.sequence_step_copy_url)
        self.menu.addAction(action)

        self.inSessionPatternEdit.textChanged.connect(
            self.handle_sessionEdit_textChanged)
        self.inSessionPatternRE.stateChanged.connect(
            self.handle_sessionRE_stateChanged)
        self.outOfSessionPatternEdit.textChanged.connect(
            self.handle_sessionEdit_textChanged)
        self.outOfSessionPatternRE.stateChanged.connect(
            self.handle_sessionRE_stateChanged)

        QObject.connect(self.sequencesComboBox,
                        SIGNAL('currentIndexChanged(const QString &)'),
                        self.handle_sequenceCombo_text_currentIndexChanged)
        QObject.connect(self.sequencesComboBox,
                        SIGNAL('currentIndexChanged(int)'),
                        self.handle_sequenceCombo_currentIndexChanged)

        self.useSessionDetectionCheckbox.stateChanged.connect(
            self.handle_useSessionDection_stateChanged)
        self.setUseSessionDetection()

        self.includeMediaCheckbox.stateChanged.connect(
            self.handle_includeMedia_stateChanged)

        self.framework.subscribe_add_sequence_builder_response_id(
            self.add_manual_sequence_builder_item)

        self.originatingResponses = {}
        self.sequenceResponseIds = set()

        self.Data = None
        self.cursor = None
        self.framework.subscribe_database_events(self.db_attach,
                                                 self.db_detach)

        self.framework.subscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.subscribe_zoom_out(self.zoom_out_scintilla)

    def _destroyed(self):
        self.framework.unsubscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.unsubscribe_zoom_out(self.zoom_out_scintilla)

    def display_confirm_dialog(self, message):
        response = QMessageBox.question(self, 'Confirm', message,
                                        QMessageBox.Yes | QMessageBox.No,
                                        QMessageBox.No)
        if 0 != (response & QMessageBox.Yes):
            return True
        else:
            return False

    def db_attach(self):
        self.Data = self.framework.getDB()
        self.cursor = self.Data.allocate_thread_cursor()
        self.populate_sequence_combo()

    def db_detach(self):
        if self.Data:
            self.close_cursor()
            self.Data = None

    def close_cursor(self):
        if self.cursor and self.Data:
            self.cursor.close()
            self.Data.release_thread_cursor(self.cursor)
            self.cursor = None

    def populate_sequence_combo(self, selectedText=''):
        self.sequencesComboBox.clear()
        item = self.sequencesComboBox.addItem(
            'New Sequence - %s' % (time.asctime(time.localtime())), '-1')

        for row in self.Data.get_all_sequences(self.cursor):
            sequenceItem = [m or '' for m in row]
            name = str(sequenceItem[1])
            Id = str(sequenceItem[0])
            item = self.sequencesComboBox.addItem(name, Id)

        if selectedText:
            index = self.sequencesComboBox.findText(selectedText)
            self.sequencesComboBox.setCurrentIndex(index)
        else:
            self.populate_manual_sequence_items_from_db()

    def handle_deleteSequence_clicked(self):
        sequenceId = self.sequencesComboBox.itemData(
            self.sequencesComboBox.currentIndex())
        if self.display_confirm_dialog('Delete sequence: %s?' %
                                       self.sequencesComboBox.currentText()):
            self.Data.delete_sequence(self.cursor, int(sequenceId))

        self.populate_sequence_combo()

    def handle_sequenceCombo_text_currentIndexChanged(self, text):
        pass

    def handle_sequenceCombo_currentIndexChanged(self, index):
        if -1 == index:
            return

        sequenceId = self.sequencesComboBox.itemData(index)

        if '-1' != sequenceId:
            self.deleteSequenceButton.setEnabled(True)
        else:
            self.deleteSequenceButton.setEnabled(False)

        self.fill_sequence_info(sequenceId)

    def handle_saveSequence_clicked(self):
        try:
            currentItem = self.sequencesComboBox.itemData(
                self.sequencesComboBox.currentIndex())
            sequenceId = currentItem
            print(('current', sequenceId))
            if '' == sequenceId:
                return
            sequenceId = int(sequenceId)
            if -1 == sequenceId:
                sequenceId = self.Data.insert_new_sequence(
                    self.cursor, [
                        None,
                        str(self.sequencesComboBox.currentText()),
                        str(self.sequenceTypeComboBox.currentText()),
                        int(self.useSessionDetectionCheckbox.isChecked()),
                        int(self.includeMediaCheckbox.isChecked()),
                        int(self.useBrowserCheckbox.isChecked()),
                        str(self.inSessionPatternEdit.text()),
                        int(self.inSessionPatternRE.isChecked()),
                        str(self.outOfSessionPatternEdit.text()),
                        int(self.outOfSessionPatternRE.isChecked()),
                        int(self.dynamicDataCheckbox.isChecked())
                    ])
            else:
                self.Data.update_sequence(self.cursor, [
                    str(self.sequencesComboBox.currentText()),
                    str(self.sequenceTypeComboBox.currentText()),
                    int(self.useSessionDetectionCheckbox.isChecked()),
                    int(self.includeMediaCheckbox.isChecked()),
                    int(self.useBrowserCheckbox.isChecked()),
                    str(self.inSessionPatternEdit.text()),
                    int(self.inSessionPatternRE.isChecked()),
                    str(self.outOfSessionPatternEdit.text()),
                    int(self.outOfSessionPatternRE.isChecked()),
                    int(self.dynamicDataCheckbox.isChecked()), sequenceId
                ])
                self.Data.clear_sequence_steps(self.cursor, sequenceId)
                self.Data.clear_sequence_parameters(self.cursor, sequenceId)

            # insert steps
            for index in range(
                    0, self.sequenceStepsTreeWidget.topLevelItemCount()):
                item = self.sequenceStepsTreeWidget.topLevelItem(index)
                if item:
                    stepnum = str(item.text(0))
                    sequence_item = self.sequence_items[stepnum]
                    isEnabled = not (item.isDisabled() or item.isHidden())
                    isHidden = item.isHidden() and not item.isDisabled()
                    self.Data.insert_sequence_step(self.cursor, [
                        sequenceId,
                        int(stepnum),
                        int(sequence_item['responseId']), isEnabled, isHidden
                    ])

            # insert parameters
            # TODO: populate and read from tree view
            parameters = self.formCapture.allParameters(
                self.sequenceResponseIds)
            print(('save parameters', parameters))
            for responseId, requestId, param, value, origin in parameters:
                if 'source' == origin:
                    if responseId:
                        self.Data.insert_sequence_source_parameter(
                            self.cursor, [
                                sequenceId, responseId, param.source,
                                param.position, param.Type, param.name,
                                json.dumps(value).encode('utf-8'), True
                            ])
                elif 'target' == origin:
                    self.Data.insert_sequence_target_parameter(
                        self.cursor, [
                            sequenceId, responseId, param.source,
                            param.position, param.name,
                            json.dumps(value).encode('utf-8'), True
                        ])

            # insert cookies
            # TODO: populate and read from tree view
            cookieList = self.cookieJar.allCookies()
            for cookie in cookieList:
                self.Data.insert_sequence_cookie(self.cursor, [
                    sequenceId,
                    str(cookie.domain()),
                    str(cookie.name(), 'utf-8'),
                    cookie.toRawForm().data(), True
                ])

            self.Data.commit()
        except Exception as error:
            self.Data.rollback()
            self.framework.report_exception(error)
            raise

        self.framework.signal_sequences_changed()
        self.populate_sequence_combo(self.sequencesComboBox.currentText())

    def add_manual_sequence_builder_item(self, responseId):
        if not self.is_recording:
            self.populate_manual_sequence_items_from_db()

    def populate_manual_sequence_items_from_db(self):
        responseIds = self.Data.get_sequence_builder_manual_items(
            self.cursor).fetchall()
        if len(responseIds) == 0:
            return
        self.startRecordingButton.setEnabled(False)
        for item in responseIds:
            responseId = str(item[0])
            self.infer_source_information(responseId)
            self.append_sequence_item(responseId)
        self.Data.clear_sequence_builder_manual_items(self.cursor)

    def infer_source_information(self, responseId):
        # TODO: implement
        pass

    def fill_sequence_info(self, sequenceId):
        self.sequenceResponseIds = set()
        sequenceId = int(sequenceId)
        if -1 == sequenceId:
            sequenceItem = (sequenceId, '', '', 0, 0, 0, '', 0, '', 0, 0)
        else:
            row = self.Data.get_sequence_by_id(self.cursor, sequenceId)
            if not row:
                return
            datarow = list(row)
            sequenceItem = [m or '' for m in datarow]

        self.reset_sequence_layout(sequenceId, sequenceItem)

    def reset_sequence_layout(self, sequenceId, sequenceItem):

        index = self.sequenceTypeComboBox.findText(str(sequenceItem[2]))
        if -1 != index:
            self.sequenceTypeComboBox.setCurrentIndex(index)
        else:
            self.sequenceTypeComboBox.setCurrentIndex(0)

        self.useSessionDetectionCheckbox.setChecked(bool(sequenceItem[3]))
        self.includeMediaCheckbox.setChecked(bool(bool(sequenceItem[4])))
        self.useBrowserCheckbox.setChecked(bool(sequenceItem[5]))
        self.inSessionPatternEdit.setText(str(sequenceItem[6]))
        self.inSessionPatternRE.setChecked(bool(sequenceItem[7]))
        self.outOfSessionPatternEdit.setText(str(sequenceItem[8]))
        self.outOfSessionPatternRE.setChecked(bool(sequenceItem[9]))
        self.dynamicDataCheckbox.setChecked(
            bool(sequenceItem[SequencesTable.DYNAMIC_DATA]))

        self.sequencePropertiesTabWidget.setCurrentIndex(0)
        self.sequenceResponseViewEdit.setText('')
        self.sequenceRequestViewEdit.setText('')
        self.sequenceRenderView.setHtml('', QUrl('about:blank'))

        self.sequenceStepsTreeWidget.clear()
        self.sequence_items = {}

        if -1 != sequenceId:
            sequenceSteps = []
            for row in self.Data.get_sequence_steps(self.cursor, sequenceId):
                stepItems = [m or '' for m in row]
                sequenceSteps.append(stepItems)
            for stepItems in sequenceSteps:
                item = self.append_sequence_item(
                    str(stepItems[SequenceStepsTable.RESPONSE_ID]))
                if not bool(stepItems[SequenceStepsTable.IS_ENABLED]):
                    item.setHidden(True)
                    if not bool(stepItems[SequenceStepsTable.IS_HIDDEN]):
                        item.setDisabled(True)

        self.is_recording = False
        self.stopRecordingButton.setEnabled(False)
        self.startRecordingButton.setEnabled(True)

        self.setUseSessionDetection()
        self.run_pattern_matches()

    def handle_useSessionDection_stateChanged(self, state):
        self.setUseSessionDetection()

    def handle_includeMedia_stateChanged(self):
        for index in range(0,
                           self.sequenceStepsTreeWidget.topLevelItemCount()):
            item = self.sequenceStepsTreeWidget.topLevelItem(index)
            if item and not item.isDisabled():
                contentType = str(item.text(3))
                self.hide_media_type_item(item, contentType)

    def setUseSessionDetection(self):
        self.inSessionPatternEdit.setEnabled(
            self.useSessionDetectionCheckbox.isChecked())
        self.inSessionPatternRE.setEnabled(
            self.useSessionDetectionCheckbox.isChecked())
        self.outOfSessionPatternEdit.setEnabled(
            self.useSessionDetectionCheckbox.isChecked())
        self.outOfSessionPatternRE.setEnabled(
            self.useSessionDetectionCheckbox.isChecked())
        if not self.useSessionDetectionCheckbox.isChecked():
            self.inSessionPatternEdit.setPalette(QApplication.palette())
            self.outOfSessionPatternEdit.setPalette(QApplication.palette())

    # TODO: refactor this code -
    def setScintillaProperties(self, scintillaWidget, lexerType=''):
        scintillaWidget.setFont(self.framework.get_font())
        scintillaWidget.setWrapMode(1)
        scintillaWidget.zoomTo(self.framework.get_zoom_size())
        # TOOD: set based on line numbers (size is in pixels)
        scintillaWidget.setMarginWidth(1, '1000')
        self.scintillaWidgets.add(scintillaWidget)
        if 'html' == lexerType:
            lexerInstance = Qsci.QsciLexerHTML(scintillaWidget)
            lexerInstance.setFont(self.framework.get_font())
            scintillaWidget.setLexer(lexerInstance)

    def zoom_in_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomIn()

    def zoom_out_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomOut()

    def handle_startRecording_clicked(self):
        self.sequenceStepsTreeWidget.clear()
        self.stopRecordingButton.setEnabled(True)
        self.startRecordingButton.setEnabled(False)
        self.cookieJar.start_tracking()
        self.formCapture.start_tracking()
        self.sequence_items = {}
        self.sequenceResponseIds = set()
        self.is_recording = True
        self.sequenceTabWidget.setCurrentIndex(1)

    def handle_stopRecording_clicked(self):
        self.is_recording = False
        self.cookieJar.stop_tracking()
        self.formCapture.stop_tracking()
        self.stopRecordingButton.setEnabled(False)
        self.startRecordingButton.setEnabled(True)

    def handle_currentChanged(self, index):
        # TODO: clean this up
        if 3 == index:
            self.sequenceParametersTreeWidget.clear()
            print(('sequenceResponseIds', type(self.sequenceResponseIds),
                   self.sequenceResponseIds))
            parameters = self.formCapture.allParameters(
                self.sequenceResponseIds)
            for responseId, requestId, param, value, origin in parameters:
                print((responseId, requestId, param, value, origin))
                rId = ''
                Xref = ''
                source = ''
                target = ''
                value_type = param.Type
                position = str(param.position)
                if 'source' == origin:
                    #                    rId = requestId
                    #                    Xref = self.formCapture.get_sequence_transition(requestId)
                    rId = responseId
                    Xref = requestId
                    source = param.source
                elif 'target' == origin:
                    rId = responseId
                    Xref = requestId
                    target = param.source

                item = QTreeWidgetItem([
                    '',
                    rId,
                    Xref,
                    source,
                    value_type,
                    position,
                    target,
                    param.name,
                    ','.join(value),  # value can be list
                    param.url,
                ])
                self.sequenceParametersTreeWidget.addTopLevelItem(item)
        elif 2 == index:
            self.sequenceCookiesTreeWidget.clear()
            cookieList = self.cookieJar.allCookies()
            for cookie in cookieList:
                item = QTreeWidgetItem([
                    '',
                    str(cookie.domain()),
                    str(cookie.name()),
                    str(cookie.value()),
                    str(cookie.path()),
                    str(cookie.expirationDate().toUTC().toString(
                        'MM/dd/yyyy hh:mm:ss')),
                    str(cookie.isSessionCookie()),
                    str(cookie.isHttpOnly()),
                    str(cookie.isSecure()),
                ])
                if self.cookieJar.is_cookie_tracked(str(cookie.domain()),
                                                    str(cookie.name())):
                    item.setCheckState(0, Qt.Checked)
                else:
                    item.setCheckState(0, Qt.Unchecked)
                self.sequenceCookiesTreeWidget.addTopLevelItem(item)

    def process_request_finished(self, reply):
        if not self.is_recording:
            return
        self.qlock.lock()
        try:
            responseId, requestId, xrefId = '', '', ''
            varId = reply.attribute(QNetworkRequest.User)
            if varId is not None:
                responseId = str(varId)
            varId = reply.attribute(QNetworkRequest.User + 1)
            if varId is not None:
                requestId = str(varId)
            varId = reply.attribute(QNetworkRequest.User + 2)
            if varId is not None:
                xrefId = str(varId)

            print(('process_request_finished', responseId, requestId, xrefId))

            if xrefId and requestId and responseId:
                if requestId not in self.originatingResponses:
                    self.originatingResponses[requestId] = responseId

                # TODO: is this necessary?
                originatingObject = reply.request().originatingObject()
                if originatingObject:
                    originatingObject.setProperty(
                        'RAFT_responseId',
                        self.originatingResponses[requestId])

#            print(self.originatingResponses)

            if responseId:
                self.append_sequence_item(responseId, requestId)
        finally:
            self.qlock.unlock()

    def append_sequence_item(self, responseId, requestId=''):
        topItem = self.sequenceStepsTreeWidget.topLevelItem(
            self.sequenceStepsTreeWidget.topLevelItemCount() - 1)
        if topItem is None:
            current_max = 0
        else:
            current_max = int(topItem.text(0))
        stepnum = str(current_max + 1)

        row = self.Data.read_responses_by_id(self.cursor, responseId)
        if not row:
            return

        self.sequenceResponseIds.add(responseId)

        responseItems = interface.data_row_to_response_items(row)

        url = responseItems[ResponsesTable.URL]
        method = responseItems[ResponsesTable.REQ_METHOD]
        contentType = responseItems[
            ResponsesTable.RES_CONTENT_TYPE].lower().strip()
        charset = ContentHelper.getCharSet(contentType)
        if contentType and ';' in contentType:
            contentType = contentType[0:contentType.index(';')]

        reqHeaders = responseItems[ResponsesTable.REQ_HEADERS]
        reqData = responseItems[ResponsesTable.REQ_DATA]
        requestHeaders, requestBody, rawRequest = ContentHelper.combineRaw(
            reqHeaders, reqData)

        resHeaders = responseItems[ResponsesTable.RES_HEADERS]
        resData = responseItems[ResponsesTable.RES_DATA]
        responseHeaders, responseBody, rawResponse = ContentHelper.combineRaw(
            resHeaders, resData, charset)

        sequence_item = {
            'responseUrl': url,
            'responseId': responseId,
            'rawResponse': rawResponse,
            'rawRequest': rawRequest,
            'method': method,
        }

        self.sequence_items[stepnum] = sequence_item
        status = self.check_pattern_match(sequence_item)
        item = QTreeWidgetItem([stepnum, status, method, contentType, url])
        self.sequenceStepsTreeWidget.addTopLevelItem(item)

        self.hide_media_type_item(item, contentType)

        self.formCapture.process_target_request(responseId, requestId, method,
                                                url, reqHeaders, reqData)

        return item

    def hide_media_type_item(self, item, contentType):
        # TODO: move this to a common module that applies mime type checking
        hide_it = not self.includeMediaCheckbox.isChecked()
        if 'html' in contentType:
            pass
        elif contentType in self.known_media_types:
            item.setHidden(hide_it)
        elif '/' in contentType:
            ctype, stype = contentType.split('/', 1)
            if ctype in ('audio', 'image', 'video'):
                item.setHidden(hide_it)

    def handle_properties_currentChanged(self, index):
        item = self.sequenceStepsTreeWidget.currentItem()
        if item is None:
            return

        self.inSessionPatternEdit.setPalette(QApplication.palette())
        self.outOfSessionPatternEdit.setPalette(QApplication.palette())

        if 0 == index:
            pass
        elif 1 == index:
            self.update_sequenceRequestView(str(item.text(0)))
        elif 2 == index:
            self.update_sequenceResponseView(str(item.text(0)))
        elif 3 == index:
            self.update_sequenceRenderView(str(item.text(0)))

    def handle_renderView_selectionChanged(self):
        pass

    def handle_steps_itemClicked(self, item, column):

        index = self.sequencePropertiesTabWidget.currentIndex()

        if 0 == index:
            return

        if item is None:
            return

        if 1 == index:
            self.update_sequenceRequestView(str(item.text(0)))
        elif 2 == index:
            self.update_sequenceResponseView(str(item.text(0)))
        elif 3 == index:
            self.update_sequenceRenderView(str(item.text(0)))

    def update_sequenceRenderView(self, stepnum):
        sequence_item = self.sequence_items[stepnum]
        self.sequenceRenderView.fill_from_db(sequence_item['responseId'],
                                             sequence_item['responseUrl'])

    def update_sequenceResponseView(self, stepnum):
        sequence_item = self.sequence_items[stepnum]
        rawResponse = sequence_item['rawResponse']
        self.sequenceResponseViewEdit.setText(rawResponse)
        self.run_pattern_matches()

    def update_sequenceRequestView(self, stepnum):
        sequence_item = self.sequence_items[stepnum]
        rawRequest = sequence_item['rawRequest']
        self.sequenceRequestViewEdit.setText(rawRequest)
        self.run_pattern_matches()

    def check_pattern_match(self, sequence_item):
        if not self.useSessionDetectionCheckbox.isChecked():
            return ''

        rawResponse = sequence_item['rawResponse']
        is_insession = False
        is_outofsession = False

        searchText = str(self.inSessionPatternEdit.text())
        if searchText:
            if self.inSessionPatternRE.isChecked():
                try:
                    if self.re_insession is None:
                        self.re_insession = re.compile(searchText, re.I)
                    if self.re_insession.search(rawResponse):
                        is_insession = True
                except Exception as e:
                    self.framework.report_implementation_error(e)
            else:
                if -1 != rawResponse.lower().find(searchText.lower()):
                    is_insession = True

        searchText = str(self.outOfSessionPatternEdit.text())
        if searchText:
            if self.outOfSessionPatternRE.isChecked():
                try:
                    if self.re_outofsession is None:
                        self.re_outofsession = re.compile(searchText, re.I)
                    if self.re_outofsession.search(rawResponse):
                        is_outofsession = True
                except Exception as e:
                    self.framework.report_implementation_error(e)

            else:
                if -1 != rawResponse.lower().find(searchText.lower()):
                    is_outofsession = True

        if is_insession and not is_outofsession:
            return 'In-Session'
        elif not is_insession and is_outofsession:
            return 'Out-of-Session'
        elif not is_insession and not is_outofsession:
            return ''
        else:
            return 'Conflict'

    def run_pattern_matches(self):
        if not self.useSessionDetectionCheckbox.isChecked():
            # no pattern matching
            for index in range(
                    0, self.sequenceStepsTreeWidget.topLevelItemCount()):
                item = self.sequenceStepsTreeWidget.topLevelItem(index)
                if item:
                    item.setText(1, '')
            return
        else:
            self.do_apply_pattern_selection(self.inSessionPatternEdit,
                                            self.inSessionPatternRE)
            self.do_apply_pattern_selection(self.outOfSessionPatternEdit,
                                            self.outOfSessionPatternRE)

            for index in range(
                    0, self.sequenceStepsTreeWidget.topLevelItemCount()):
                item = self.sequenceStepsTreeWidget.topLevelItem(index)
                if item:
                    step_num = str(item.text(0))
                    sequence_item = self.sequence_items[step_num]
                    status = self.check_pattern_match(sequence_item)
                    item.setText(1, status)

    def handle_sessionEdit_textChanged(self, text):
        self.re_insession = None
        self.re_outofsession = None
        self.run_pattern_matches()

    def handle_sessionRE_stateChanged(self, state):
        self.re_insession = None
        self.re_outofsession = None
        self.run_pattern_matches()

    def do_apply_pattern_selection(self, search_line_edit, re_checkbox):
        searchText = search_line_edit.text()
        is_re = re_checkbox.isChecked()

        if not searchText:
            search_line_edit.setPalette(QApplication.palette())
            return

        tabindex = self.sequencePropertiesTabWidget.currentIndex()

        if 0 == tabindex:
            return
        elif 1 == tabindex:
            pass
        elif 2 == tabindex:
            # Scintilla doesn't understand same regex characters as Python
            # TODO: consider improving this emulation
            if is_re:
                try:
                    r = re.compile(str(searchText), re.I)
                    tmp = str(self.sequenceResponseViewEdit.text())
                    m = r.search(tmp)
                    if m:
                        searchText = m.group(0)
                        is_re = False
                except Exception as e:
                    pass

            if not self.sequenceResponseViewEdit.findFirst(
                    searchText, is_re, False, False, True, True, 0, 0):
                p = search_line_edit.palette()
                p.setColor(QPalette.Text, QColor('red'))
                search_line_edit.setPalette(p)
            else:
                search_line_edit.setPalette(QApplication.palette())
        elif 3 == tabindex:
            pass

    def sequence_steps_context_menu(self, point):
        self.menu.exec_(self.sequenceStepsTreeWidget.mapToGlobal(point))

    def handle_remove_from_sequence(self):
        item = self.sequenceStepsTreeWidget.currentItem()
        if item is None:
            return
        item.setDisabled(True)
        item.setHidden(True)

    def sequence_step_copy_url(self):
        item = self.sequenceStepsTreeWidget.currentItem()
        if item is None:
            return
        curUrl = item.text(4)
        if curUrl:
            QApplication.clipboard().setText(curUrl)
Beispiel #6
0
class TesterTab(QObject):
    class ClickJackingInteractor():
        def __init__(self, parent):
            self.parent = parent

        def log(self, logtype, logmessage):
            self.parent.clickjacking_console_log(logtype, logmessage)

        def confirm(self, frame, msg):
            return self.parent.clickjacking_browser_confirm(frame, msg)

    DEFAULT_FRAMED_URL = 'http://attacker.example.com/framed.html'
    DEFAULT_CSRF_URL = 'http://attacker.example.com/csrf.html'

    def __init__(self, framework, mainWindow):
        QObject.__init__(self, mainWindow)
        self.framework = framework
        QObject.connect(self, SIGNAL('destroyed(QObject*)'), self._destroyed)
        self.mainWindow = mainWindow
        self.cjInteractor = TesterTab.ClickJackingInteractor(self)
        self.cjTester = ClickjackingTester(self.framework)

        self.scintillaWidgets = set(
        )  # store scintilla widget reference to handle zoom in/zoom out

        #        self.networkAccessManager = StandardNetworkAccessManager(self.framework, self.framework.get_global_cookie_jar())
        self.pageFactory = TesterPageFactory(self.framework, self.cjInteractor,
                                             None, self)

        self.framework.subscribe_populate_tester_csrf(
            self.tester_populate_csrf)
        self.framework.subscribe_populate_tester_click_jacking(
            self.tester_populate_click_jacking)

        self.Data = None
        self.cursor = None
        self.framework.subscribe_database_events(self.db_attach,
                                                 self.db_detach)

        self.setScintillaProperties(self.mainWindow.csrfGenEdit)
        self.mainWindow.testerRegenBtn.clicked.connect(self.regen_csrf)
        self.mainWindow.csrfOpenBtn.clicked.connect(
            self.handle_csrfOpenBtn_clicked)

        self.setScintillaProperties(self.mainWindow.testerClickjackingEditHtml)
        self.mainWindow.testerClickjackingSimulateButton.clicked.connect(
            self.handle_testerClickjackingSimulateButton_clicked)
        self.mainWindow.testerClickjackingOpenInBrowserButton.clicked.connect(
            self.handle_testerClickjackingOpenInBrowserButton_clicked)
        self.mainWindow.testerClickjackingGenerateButton.clicked.connect(
            self.handle_testerClickjackingGenerateButton_clicked)
        self.mainWindow.testerClickjackingEnableJavascript.clicked.connect(
            self.handle_testerClickjackingEnableJavascript_clicked)

        self.clickjackingRenderWebView = RenderingWebView(
            self.framework, self.pageFactory,
            self.mainWindow.testerClickjackingEmbeddedBrowserPlaceholder)
        self.clickjackingRenderWebView.loadFinished.connect(
            self.handle_clickjackingRenderWebView_loadFinished)
        self.clickjackingRenderWebView.urlChanged.connect(
            self.handle_clickjackingRenderWebView_urlChanged)
        self.clickjackingRenderWebView.titleChanged.connect(
            self.handle_clickjackingRenderWebView_titleChanged)

        self.framework.subscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.subscribe_zoom_out(self.zoom_out_scintilla)

    def _destroyed(self):
        self.framework.unsubscribe_zoom_in(self.zoom_in_scintilla)
        self.framework.unsubscribe_zoom_out(self.zoom_out_scintilla)

    def db_attach(self):
        self.Data = self.framework.getDB()
        self.cursor = self.Data.allocate_thread_cursor()

    def db_detach(self):
        self.close_cursor()
        self.Data = None

    def close_cursor(self):
        if self.cursor and self.Data:
            self.cursor.close()
            self.Data.release_thread_cursor(self.cursor)
            self.cursor = None

    def tester_populate_csrf(self, response_id):

        row = self.Data.read_responses_by_id(self.cursor, response_id)

        if not row:
            return

        responseItems = interface.data_row_to_response_items(row)

        url = responseItems[ResponsesTable.URL]
        # Are reqHeaders necessary?
        reqHeaders = str(responseItems[ResponsesTable.REQ_HEADERS], 'utf-8',
                         'ignore')
        reqData = str(responseItems[ResponsesTable.REQ_DATA], 'utf-8',
                      'ignore')  # TODO: consider replacement

        data = reqHeaders + "\n" + reqData

        # Check to ensure that either a GET or a POST is being used and pass that along to the function
        # check = re.compile("^(GET|POST)", re.I)
        # result = check.match(reqHeaders)
        # if not result:
        #    return()

        GET = re.compile("^GET", re.I)
        POST = re.compile("^POST", re.I)

        if GET.match(reqHeaders):
            htmlresult = CSRFTester.generate_csrf_html(url, reqData, "get")
        elif POST.match(reqHeaders):
            htmlresult = CSRFTester.generate_csrf_html(url, reqData, "post")
        else:
            return ()

        # htmlresult = CSRFTester.generate_csrf_html(url, reqData)

        self.mainWindow.testerCSRFURLEdit.setText(url)
        self.mainWindow.csrfGenEdit.setText(htmlresult)
        self.mainWindow.csrfReqEdit.setPlainText(data)

    def regen_csrf(self):
        # Regenerate CSRF playload based on selection
        if self.mainWindow.testerImgGen.isChecked():
            url = self.mainWindow.testerCSRFURLEdit.text()
            htmlresult = CSRFTester.generate_csrf_img(
                url, self.mainWindow.csrfGenEdit.text())
            self.mainWindow.csrfGenEdit.setText(htmlresult)

    def handle_csrfOpenBtn_clicked(self):
        url = self.DEFAULT_CSRF_URL  # TODO: exposed this (?)
        body = self.mainWindow.csrfGenEdit.text()
        content_type = 'text/html'
        self.framework.open_content_in_browser(url, body, content_type)

    def tester_populate_click_jacking(self, response_id):
        row = self.Data.read_responses_by_id(self.cursor, response_id)
        if not row:
            return
        responseItems = interface.data_row_to_response_items(row)
        url = responseItems[ResponsesTable.URL]
        self.setup_clickjacking_url(url)

    def setup_clickjacking_url(self, url):
        self._clickjacking_simulation_running = False
        self.mainWindow.testerClickjackingUrlEdit.setText('')
        self.mainWindow.testerClickjackingConsoleLogTextEdit.setText('')
        self.clickjackingRenderWebView.setHtml('', QUrl('about:blank'))

        htmlcontent = self.cjTester.make_default_frame_html(url)
        self.mainWindow.testerClickjackingTargetURL.setText(url)
        self.mainWindow.testerClickjackingEditHtml.setText(htmlcontent)

    def handle_testerClickjackingSimulateButton_clicked(self):
        self.mainWindow.testerClickjackingConsoleLogTextEdit.setText(
            'Starting Clickjacking Simulation')
        self.clickjackingRenderWebView.page().set_javascript_enabled(
            self.mainWindow.testerClickjackingEnableJavascript.isChecked())
        self._clickjacking_simulation_running = True
        url = self.mainWindow.testerClickjackingUrlEdit.text()
        # TODO: better way than to force unique URL to reload content ?
        if '?' in url:
            url = url[0:url.find('?') + 1] + uuid.uuid4().hex
        else:
            url = url + '?' + uuid.uuid4().hex
        headers = ''
        body = self.mainWindow.testerClickjackingEditHtml.text()
        content_type = 'text/html'
        self.clickjackingRenderWebView.fill_from_response(
            url, headers, body, content_type)

    def handle_testerClickjackingGenerateButton_clicked(self):
        entry = self.mainWindow.testerClickjackingTargetURL.text()
        url = QUrl.fromUserInput(entry).toEncoded().data().decode('utf-8')
        self.setup_clickjacking_url(url)

    def handle_testerClickjackingOpenInBrowserButton_clicked(self):
        url = self.clickjackingRenderWebView.url().toEncoded().data().decode(
            'utf-8')
        body = self.mainWindow.testerClickjackingEditHtml.text()
        content_type = 'text/html'
        self.framework.open_content_in_browser(url, body, content_type)

    def handle_clickjackingRenderWebView_loadFinished(self):
        self.clickjacking_console_log('info', 'Page load finished')
        if not self._clickjacking_simulation_running:
            self.mainWindow.testerClickjackingConsoleLogTextEdit.setPlainText(
                '')

    def handle_clickjackingRenderWebView_urlChanged(self):
        url = self.clickjackingRenderWebView.url().toEncoded().data().decode(
            'utf-8')
        if 'about:blank' == url and not self._clickjacking_simulation_running:
            self.mainWindow.testerClickjackingUrlEdit.setText(
                self.DEFAULT_FRAMED_URL)
        else:
            self.mainWindow.testerClickjackingUrlEdit.setText(url)

    def handle_testerClickjackingEnableJavascript_clicked(self):
        self.clickjackingRenderWebView.page().set_javascript_enabled(
            self.mainWindow.testerClickjackingEnableJavascript.isChecked())

    def handle_clickjackingRenderWebView_titleChanged(self, title):
        self.clickjacking_console_log('info',
                                      'Page title changed to [%s]' % (title))

    def clickjacking_browser_confirm(self, frame, msg):
        if not self._clickjacking_simulation_running:
            return True
        if 'Clickjacking: Navigate Away' == msg:  # TODO: create a common location for this string
            if self.mainWindow.testerClickjackingIgnoreNavigationRequests.isChecked(
            ):
                self.clickjacking_console_log(
                    'info',
                    'Simulating canceled navigation response to confirmation: %s'
                    % (msg))
                return False
        return True

    def clickjacking_console_log(self, logtype, logmessage):
        msg = None
        if 'javaScriptConsoleMessage' == logtype:
            (lineNumber, sourceID, message) = logmessage
            msg = 'console log from [%s / %s]: %s' % (lineNumber, sourceID,
                                                      message)
        elif 'info':
            msg = logmessage

        if msg:
            curtext = self.mainWindow.testerClickjackingConsoleLogTextEdit.toPlainText(
            )
            curtext += '\n' + msg
            self.mainWindow.testerClickjackingConsoleLogTextEdit.setPlainText(
                curtext)

    # TODO: refactor into common module in framework
    def setScintillaProperties(self, scintillaWidget, contentType='html'):
        scintillaWidget.setFont(self.framework.get_font())
        scintillaWidget.setWrapMode(1)
        scintillaWidget.zoomTo(self.framework.get_zoom_size())
        # TOOD: set based on line numbers (size is in pixels)
        scintillaWidget.setMarginWidth(1, '1000')
        lexerInstance = Qsci.QsciLexerHTML(scintillaWidget)
        lexerInstance.setFont(self.framework.get_font())
        scintillaWidget.setLexer(lexerInstance)
        self.scintillaWidgets.add(scintillaWidget)

    def zoom_in_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomIn()

    def zoom_out_scintilla(self):
        for scintillaWidget in self.scintillaWidgets:
            scintillaWidget.zoomOut()