From 2ef07c80dbd851715ffa3b59accd201cc7de7278 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 12 Jan 2021 13:21:51 +0100 Subject: [PATCH] Some more refactoring --- lib/controller/checks.py | 12 ++++++------ lib/core/datatype.py | 8 ++++++-- lib/core/option.py | 7 +------ lib/core/settings.py | 2 +- lib/request/connect.py | 16 ++++++++-------- lib/request/redirecthandler.py | 8 ++++---- lib/utils/brute.py | 12 ++++++------ lib/utils/hash.py | 6 +++--- lib/utils/search.py | 6 +++--- plugins/dbms/raima/connector.py | 2 +- plugins/dbms/raima/enumeration.py | 28 ++++++++++++++-------------- plugins/dbms/raima/filesystem.py | 4 ++-- plugins/dbms/raima/takeover.py | 8 ++++---- plugins/generic/databases.py | 19 ++++++++++++------- 14 files changed, 71 insertions(+), 67 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 2119233ef..a435917c0 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1167,7 +1167,7 @@ def checkDynParam(place, parameter, value): dynamicity might depend on another parameter. """ - if kb.redirectChoice: + if kb.choices.redirect: return None kb.matchRatio = None @@ -1268,7 +1268,7 @@ def checkStability(): secondPage, _, _ = Request.queryPage(content=True, noteResponseTime=False, raise404=False) - if kb.redirectChoice: + if kb.choices.redirect: return None kb.pageStable = (firstPage == secondPage) @@ -1415,11 +1415,11 @@ def checkWaf(): value = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + DEFAULT_GET_POST_DELIMITER value += "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload)) - pushValue(kb.redirectChoice) + pushValue(kb.choices.redirect) pushValue(kb.resendPostOnRedirect) pushValue(conf.timeout) - kb.redirectChoice = REDIRECTION.YES + kb.choices.redirect = REDIRECTION.YES kb.resendPostOnRedirect = False conf.timeout = IPS_WAF_CHECK_TIMEOUT @@ -1432,7 +1432,7 @@ def checkWaf(): conf.timeout = popValue() kb.resendPostOnRedirect = popValue() - kb.redirectChoice = popValue() + kb.choices.redirect = popValue() hashDBWrite(HASHDB_KEYS.CHECK_WAF_RESULT, retVal, True) @@ -1565,7 +1565,7 @@ def checkConnection(suppressOutput=False): else: kb.errorIsNone = True - if kb.redirectChoice == REDIRECTION.YES and threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID: + if kb.choices.redirect == REDIRECTION.YES and threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID: if (threadData.lastRedirectURL[1] or "").startswith("https://") and conf.hostname in getUnicode(threadData.lastRedirectURL[1]): conf.url = re.sub(r"https?://", "https://", conf.url) match = re.search(r":(\d+)", threadData.lastRedirectURL[1]) diff --git a/lib/core/datatype.py b/lib/core/datatype.py index 9b6754431..ab3032dee 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -21,13 +21,14 @@ class AttribDict(dict): 1 """ - def __init__(self, indict=None, attribute=None): + def __init__(self, indict=None, attribute=None, keycheck=True): if indict is None: indict = {} # Set any attributes here - before initialisation # these remain as normal attributes self.attribute = attribute + self.keycheck = keycheck dict.__init__(self, indict) self.__initialised = True @@ -43,7 +44,10 @@ class AttribDict(dict): try: return self.__getitem__(item) except KeyError: - raise AttributeError("unable to access item '%s'" % item) + if self.keycheck: + raise AttributeError("unable to access item '%s'" % item) + else: + return None def __setattr__(self, item, value): """ diff --git a/lib/core/option.py b/lib/core/option.py index 1ef78a367..e4cba0cc9 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2013,12 +2013,10 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.chars.stop = "%s%s%s" % (KB_CHARS_BOUNDARY_CHAR, randomStr(length=3, alphabet=KB_CHARS_LOW_FREQUENCY_ALPHABET), KB_CHARS_BOUNDARY_CHAR) kb.chars.at, kb.chars.space, kb.chars.dollar, kb.chars.hash_ = ("%s%s%s" % (KB_CHARS_BOUNDARY_CHAR, _, KB_CHARS_BOUNDARY_CHAR) for _ in randomStr(length=4, lowercase=True)) + kb.choices = AttribDict(keycheck=False) kb.codePage = None - kb.columnExistsChoice = None kb.commonOutputs = None - kb.connErrorChoice = None kb.connErrorCounter = 0 - kb.cookieEncodeChoice = None kb.copyExecTest = None kb.counters = {} kb.customInjectionMark = CUSTOM_INJECTION_MARK_CHAR @@ -2122,7 +2120,6 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.proxyAuthHeader = None kb.queryCounter = 0 kb.randomPool = {} - kb.redirectChoice = None kb.reflectiveMechanism = True kb.reflectiveCounters = {REFLECTIVE_COUNTER.MISS: 0, REFLECTIVE_COUNTER.HIT: 0} kb.requestCounter = 0 @@ -2142,9 +2139,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.reduceTests = None kb.sslSuccess = False kb.stickyDBMS = False - kb.storeHashesChoice = None kb.suppressResumeInfo = False - kb.tableExistsChoice = None kb.tableFrom = None kb.technique = None kb.tempDir = None diff --git a/lib/core/settings.py b/lib/core/settings.py index f0d5a11ee..36e259998 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -18,7 +18,7 @@ from lib.core.enums import OS from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.5.1.18" +VERSION = "1.5.1.19" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/request/connect.py b/lib/request/connect.py index 881266855..bf4bc1257 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -610,8 +610,8 @@ class Connect(object): # Get HTTP response if hasattr(conn, "redurl"): - page = (threadData.lastRedirectMsg[1] if kb.redirectChoice == REDIRECTION.NO else Connect._connReadProxy(conn)) if not skipRead else None - skipLogTraffic = kb.redirectChoice == REDIRECTION.NO + page = (threadData.lastRedirectMsg[1] if kb.choices.redirect == REDIRECTION.NO else Connect._connReadProxy(conn)) if not skipRead else None + skipLogTraffic = kb.choices.redirect == REDIRECTION.NO code = conn.redcode if not finalCode else code else: page = Connect._connReadProxy(conn) if not skipRead else None @@ -844,13 +844,13 @@ class Connect(object): with kb.locks.connError: kb.connErrorCounter += 1 - if kb.connErrorCounter >= MAX_CONSECUTIVE_CONNECTION_ERRORS and kb.connErrorChoice is None: + if kb.connErrorCounter >= MAX_CONSECUTIVE_CONNECTION_ERRORS and kb.choices.connError is None: message = "there seems to be a continuous problem with connection to the target. " message += "Are you sure that you want to continue? [y/N] " - kb.connErrorChoice = readInput(message, default='N', boolean=True) + kb.choices.connError = readInput(message, default='N', boolean=True) - if kb.connErrorChoice is False: + if kb.choices.connError is False: raise SqlmapSkipTargetException if "forcibly closed" in tbMsg: @@ -1025,10 +1025,10 @@ class Connect(object): skip = False if place == PLACE.COOKIE or place == PLACE.CUSTOM_HEADER and value.split(',')[0].upper() == HTTP_HEADER.COOKIE.upper(): - if kb.cookieEncodeChoice is None: + if kb.choices.cookieEncode is None: msg = "do you want to URL encode cookie values (implementation specific)? %s" % ("[Y/n]" if not conf.url.endswith(".aspx") else "[y/N]") # Reference: https://support.microsoft.com/en-us/kb/313282 - kb.cookieEncodeChoice = readInput(msg, default='Y' if not conf.url.endswith(".aspx") else 'N', boolean=True) - if not kb.cookieEncodeChoice: + kb.choices.cookieEncode = readInput(msg, default='Y' if not conf.url.endswith(".aspx") else 'N', boolean=True) + if not kb.choices.cookieEncode: skip = True if not skip: diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index e2de7536f..ffb5605b9 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -48,13 +48,13 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): def _ask_redirect_choice(self, redcode, redurl, method): with kb.locks.redirect: - if kb.redirectChoice is None: + if kb.choices.redirect is None: msg = "got a %d redirect to " % redcode msg += "'%s'. Do you want to follow? [Y/n] " % redurl - kb.redirectChoice = REDIRECTION.YES if readInput(msg, default='Y', boolean=True) else REDIRECTION.NO + kb.choices.redirect = REDIRECTION.YES if readInput(msg, default='Y', boolean=True) else REDIRECTION.NO - if kb.redirectChoice == REDIRECTION.YES and method == HTTPMETHOD.POST and kb.resendPostOnRedirect is None: + if kb.choices.redirect == REDIRECTION.YES and method == HTTPMETHOD.POST and kb.resendPostOnRedirect is None: msg = "redirect is a result of a " msg += "POST request. Do you want to " msg += "resend original POST data to a new " @@ -116,7 +116,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): redurl = None result = fp - if redurl and kb.redirectChoice == REDIRECTION.YES: + if redurl and kb.choices.redirect == REDIRECTION.YES: parseResponse(content, headers) req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl) diff --git a/lib/utils/brute.py b/lib/utils/brute.py index 7fdc140e2..1d8a3e290 100644 --- a/lib/utils/brute.py +++ b/lib/utils/brute.py @@ -63,15 +63,15 @@ def _addPageTextWords(): @stackedmethod def tableExists(tableFile, regex=None): - if kb.tableExistsChoice is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct: + if kb.choices.tableExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct: warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED]) warnMsg += "for common table existence check" logger.warn(warnMsg) message = "are you sure you want to continue? [y/N] " - kb.tableExistsChoice = readInput(message, default='N', boolean=True) + kb.choices.tableExists = readInput(message, default='N', boolean=True) - if not kb.tableExistsChoice: + if not kb.choices.tableExists: return None result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), randomStr()))) @@ -187,15 +187,15 @@ def tableExists(tableFile, regex=None): return kb.data.cachedTables def columnExists(columnFile, regex=None): - if kb.columnExistsChoice is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct: + if kb.choices.columnExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct: warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED]) warnMsg += "for common column existence check" logger.warn(warnMsg) message = "are you sure you want to continue? [y/N] " - kb.columnExistsChoice = readInput(message, default='N', boolean=True) + kb.choices.columnExists = readInput(message, default='N', boolean=True) - if not kb.columnExistsChoice: + if not kb.choices.columnExists: return None if not conf.tbl: diff --git a/lib/utils/hash.py b/lib/utils/hash.py index a69d96d22..7510104a5 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -637,13 +637,13 @@ def storeHashesToFile(attack_dict): if item and item not in items: items.add(item) - if kb.storeHashesChoice is None: + if kb.choices.storeHashes is None: message = "do you want to store hashes to a temporary file " message += "for eventual further processing with other tools [y/N] " - kb.storeHashesChoice = readInput(message, default='N', boolean=True) + kb.choices.storeHashes = readInput(message, default='N', boolean=True) - if items and kb.storeHashesChoice: + if items and kb.choices.storeHashes: handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.HASHES, suffix=".txt") os.close(handle) diff --git a/lib/utils/search.py b/lib/utils/search.py index 009f5797e..2b7aee5ea 100644 --- a/lib/utils/search.py +++ b/lib/utils/search.py @@ -184,8 +184,8 @@ def _search(dork): @stackedmethod def search(dork): - pushValue(kb.redirectChoice) - kb.redirectChoice = REDIRECTION.YES + pushValue(kb.choices.redirect) + kb.choices.redirect = REDIRECTION.YES try: return _search(dork) @@ -203,7 +203,7 @@ def search(dork): else: raise finally: - kb.redirectChoice = popValue() + kb.choices.redirect = popValue() def setHTTPHandlers(): # Cross-referenced function raise NotImplementedError diff --git a/plugins/dbms/raima/connector.py b/plugins/dbms/raima/connector.py index 1ddfb7181..3274991f6 100644 --- a/plugins/dbms/raima/connector.py +++ b/plugins/dbms/raima/connector.py @@ -10,6 +10,6 @@ from plugins.generic.connector import Connector as GenericConnector class Connector(GenericConnector): def connect(self): - errMsg = "on Raima it is not (currently) possible to establish a " + errMsg = "on Raima Database Manager it is not (currently) possible to establish a " errMsg += "direct connection" raise SqlmapUnsupportedFeatureException(errMsg) diff --git a/plugins/dbms/raima/enumeration.py b/plugins/dbms/raima/enumeration.py index 980d2e3a2..34d4fa368 100644 --- a/plugins/dbms/raima/enumeration.py +++ b/plugins/dbms/raima/enumeration.py @@ -10,75 +10,75 @@ from plugins.generic.enumeration import Enumeration as GenericEnumeration class Enumeration(GenericEnumeration): def getBanner(self): - warnMsg = "on Raima it is not possible to get the banner" + warnMsg = "on Raima Database Manager it is not possible to get the banner" logger.warn(warnMsg) return None def getCurrentUser(self): - warnMsg = "on Raima it is not possible to enumerate the current user" + warnMsg = "on Raima Database Manager it is not possible to enumerate the current user" logger.warn(warnMsg) def getCurrentDb(self): - warnMsg = "on Raima it is not possible to get name of the current database" + warnMsg = "on Raima Database Manager it is not possible to get name of the current database" logger.warn(warnMsg) def isDba(self, user=None): - warnMsg = "on Raima it is not possible to test if current user is DBA" + warnMsg = "on Raima Database Manager it is not possible to test if current user is DBA" logger.warn(warnMsg) def getUsers(self): - warnMsg = "on Raima it is not possible to enumerate the users" + warnMsg = "on Raima Database Manager it is not possible to enumerate the users" logger.warn(warnMsg) return [] def getPasswordHashes(self): - warnMsg = "on Raima it is not possible to enumerate the user password hashes" + warnMsg = "on Raima Database Manager it is not possible to enumerate the user password hashes" logger.warn(warnMsg) return {} def getPrivileges(self, *args, **kwargs): - warnMsg = "on Raima it is not possible to enumerate the user privileges" + warnMsg = "on Raima Database Manager it is not possible to enumerate the user privileges" logger.warn(warnMsg) return {} def getDbs(self): - warnMsg = "on Raima it is not possible to enumerate databases (use only '--tables')" + warnMsg = "on Raima Database Manager it is not possible to enumerate databases (use only '--tables')" logger.warn(warnMsg) return [] def searchDb(self): - warnMsg = "on Raima it is not possible to search databases" + warnMsg = "on Raima Database Manager it is not possible to search databases" logger.warn(warnMsg) return [] def searchTable(self): - warnMsg = "on Raima it is not possible to search tables" + warnMsg = "on Raima Database Manager it is not possible to search tables" logger.warn(warnMsg) return [] def searchColumn(self): - warnMsg = "on Raima it is not possible to search columns" + warnMsg = "on Raima Database Manager it is not possible to search columns" logger.warn(warnMsg) return [] def search(self): - warnMsg = "on Raima search option is not available" + warnMsg = "on Raima Database Manager search option is not available" logger.warn(warnMsg) def getHostname(self): - warnMsg = "on Raima it is not possible to enumerate the hostname" + warnMsg = "on Raima Database Manager it is not possible to enumerate the hostname" logger.warn(warnMsg) def getStatements(self): - warnMsg = "on Raima it is not possible to enumerate the SQL statements" + warnMsg = "on Raima Database Manager it is not possible to enumerate the SQL statements" logger.warn(warnMsg) return [] diff --git a/plugins/dbms/raima/filesystem.py b/plugins/dbms/raima/filesystem.py index f9982e22c..da17a0d1e 100644 --- a/plugins/dbms/raima/filesystem.py +++ b/plugins/dbms/raima/filesystem.py @@ -10,9 +10,9 @@ from plugins.generic.filesystem import Filesystem as GenericFilesystem class Filesystem(GenericFilesystem): def readFile(self, remoteFile): - errMsg = "on Raima it is not possible to read files" + errMsg = "on Raima Database Manager it is not possible to read files" raise SqlmapUnsupportedFeatureException(errMsg) def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): - errMsg = "on Raima it is not possible to write files" + errMsg = "on Raima Database Manager it is not possible to write files" raise SqlmapUnsupportedFeatureException(errMsg) diff --git a/plugins/dbms/raima/takeover.py b/plugins/dbms/raima/takeover.py index 17ba3671c..1ab013347 100644 --- a/plugins/dbms/raima/takeover.py +++ b/plugins/dbms/raima/takeover.py @@ -10,19 +10,19 @@ from plugins.generic.takeover import Takeover as GenericTakeover class Takeover(GenericTakeover): def osCmd(self): - errMsg = "on Raima it is not possible to execute commands" + errMsg = "on Raima Database Manager it is not possible to execute commands" raise SqlmapUnsupportedFeatureException(errMsg) def osShell(self): - errMsg = "on Raima it is not possible to execute commands" + errMsg = "on Raima Database Manager it is not possible to execute commands" raise SqlmapUnsupportedFeatureException(errMsg) def osPwn(self): - errMsg = "on Raima it is not possible to establish an " + errMsg = "on Raima Database Manager it is not possible to establish an " errMsg += "out-of-band connection" raise SqlmapUnsupportedFeatureException(errMsg) def osSmb(self): - errMsg = "on Raima it is not possible to establish an " + errMsg = "on Raima Database Manager it is not possible to establish an " errMsg += "out-of-band connection" raise SqlmapUnsupportedFeatureException(errMsg) diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index 37386a80b..86853c58e 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -540,8 +540,8 @@ class Databases(object): elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): warnMsg = "cannot retrieve column names, " - warnMsg += "back-end DBMS is %s" % Backend.getIdentifiedDbms() - logger.warn(warnMsg) + warnMsg += "back-end DBMS is '%s'" % Backend.getIdentifiedDbms() + singleTimeWarnMessage(warnMsg) bruteForce = True if bruteForce: @@ -571,12 +571,17 @@ class Databases(object): return kb.data.cachedColumns - message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") - choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() + if kb.choices.columnExists is None: + message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") + kb.choices.columnExists = readInput(message, default='Y' if 'Y' in message else 'N').upper() - if choice == 'N': - return - elif choice == 'Q': + if kb.choices.columnExists == 'N': + if dumpMode and colList: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): dict((_, None) for _ in colList)} + return kb.data.cachedColumns + else: + return None + elif kb.choices.columnExists == 'Q': raise SqlmapUserQuitException else: return columnExists(paths.COMMON_COLUMNS)