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 MiniResponseRenderWidget(QObject): def __init__(self, framework, tabWidget, showRequest, parent = None): QObject.__init__(self, parent) self.framework = framework QObject.connect(self, SIGNAL('destroyed(QObject*)'), self._destroyed) self.tabWidget = tabWidget self.showRequest = showRequest if self.showRequest: self.reqReqEdit_Tab = QWidget(self.tabWidget) self.tabWidget.addTab(self.reqReqEdit_Tab, 'Request') # TODO: must this hard-coded ? self.render_tab_index = 2 else: self.render_tab_index = 1 self.reqResEdit_Tab = QWidget(self.tabWidget) self.tabWidget.addTab(self.reqResEdit_Tab, 'Response') self.reqRenderView_Tab = QWidget(self.tabWidget) self.tabWidget.addTab(self.reqRenderView_Tab, 'Render') # TODO: a common utility method should be used to all scintilla stuff if self.showRequest: self.reqReqEdit_Layout = QVBoxLayout(self.reqReqEdit_Tab) self.reqReqEdit = Qsci.QsciScintilla(self.reqReqEdit_Tab) self.reqReqEdit.zoomTo(self.framework.get_zoom_size()) self.reqReqEdit.setMarginLineNumbers(1, True) self.reqReqEdit.setMarginWidth(1, '1000') self.reqReqEdit.setWrapMode(1) self.reqReqEdit.setWrapVisualFlags(2, 1, 0) self.reqReqEdit_Layout.addWidget(self.reqReqEdit) self.reqResEdit_Layout = QVBoxLayout(self.reqResEdit_Tab) self.reqResEdit = Qsci.QsciScintilla(self.reqResEdit_Tab) self.reqResEdit.zoomTo(self.framework.get_zoom_size()) self.reqResEdit.setMarginLineNumbers(1, True) self.reqResEdit.setMarginWidth(1, '1000') self.reqResEdit.setWrapMode(1) self.reqResEdit.setWrapVisualFlags(2, 1, 0) self.reqResEdit_Layout.addWidget(self.reqResEdit) self.reqRenderView_Layout = QVBoxLayout(self.reqRenderView_Tab) self.requesterPageFactory = StandardPageFactory(self.framework, None, self) self.reqRenderView = RenderingWebView(self.framework, self.requesterPageFactory, self.tabWidget) self.reqRenderView_Layout.addWidget(self.reqRenderView) self.request_url = None self.tabWidget.currentChanged.connect(self.do_render_apply) 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 fill_from_response(self, url, headers, body, content_type = ''): self.reqRenderView.fill_from_response(url, headers, body, content_type) def populate_response_content(self, url, req_headers, req_body, res_headers, res_body, res_content_type = ''): self.request_url = url self.request_headers = req_headers self.request_body = req_body self.response_headers = res_headers self.response_body = res_body self.response_content_type = res_content_type if self.showRequest: self.reqReqEdit.setText(ContentHelper.getCombinedText(self.request_headers, self.request_body, '')) # TODO: should support different lexers based on content type lexerInstance = Qsci.QsciLexerHTML(self.reqResEdit) lexerInstance.setFont(self.framework.get_font()) self.reqResEdit.setLexer(lexerInstance) # TODO: should verify trailing newlines? self.reqResEdit.setText(ContentHelper.getCombinedText(self.response_headers, self.response_body, self.response_content_type)) self.do_render_apply(self.tabWidget.currentIndex()) def do_render_apply(self, index): if self.render_tab_index == index: if self.request_url: self.fill_from_response(self.request_url, self.response_headers, self.response_body, self.response_content_type) def clear_response_render(self): if self.showRequest: self.reqReqEdit.setText('') self.reqResEdit.setText('') self.reqRenderView.setHtml('', QUrl('about:blank')) self.request_url = '' self.request_headers = b'' self.request_body = b'' self.response_headers = b'' self.response_body = b'' self.response_content_type = '' def zoom_in_scintilla(self): if self.showRequest: self.reqReqEdit.zoomIn() self.reqResEdit.zoomIn() def zoom_out_scintilla(self): if self.showRequest: self.reqReqEdit.zoomOut() self.reqResEdit.zoomOut()
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()