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)
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()
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)
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)
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)
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()