From daebb0010b44c08d209bc9cb1400a65b80c3ac9f Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Tue, 18 Jan 2011 23:02:11 +0000 Subject: [PATCH] Major bug fix to properly process custom queries (--sql-query/--sql-shell) when technique in use is error-based. Alignment of SQL statement payload packing/unpacking between all of the techniques. Minor bug fix to use the proper charset (2, numbers) when dealing with COUNT() in custom queries too. Minor code cleanup. --- lib/core/agent.py | 74 +++---- lib/core/settings.py | 7 +- lib/request/inject.py | 162 +++++++-------- lib/techniques/error/use.py | 256 +++++++++++++++++++++--- lib/techniques/inband/union/test.py | 10 +- lib/techniques/inband/union/use.py | 101 +++++----- plugins/dbms/mssqlserver/enumeration.py | 24 +-- plugins/dbms/oracle/enumeration.py | 16 +- plugins/generic/enumeration.py | 96 ++++----- 9 files changed, 486 insertions(+), 260 deletions(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index c00db48f2..9c99c3f5a 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -27,7 +27,7 @@ from lib.core.enums import DBMS from lib.core.enums import PAYLOAD from lib.core.enums import PLACE from lib.core.exception import sqlmapNoneDataException -from lib.core.settings import INBAND_FROM_TABLE +from lib.core.settings import FROM_TABLE from lib.core.settings import PAYLOAD_DELIMITER class Agent: @@ -312,9 +312,9 @@ class Agent: if not kb.dbmsDetected: return fields - fields = fields.replace(", ", ",") - fieldsSplitted = fields.split(",") - dbmsDelimiter = queries[getIdentifiedDBMS()].delimiter.query + fields = fields.replace(", ", ",") + fieldsSplitted = fields.split(",") + dbmsDelimiter = queries[getIdentifiedDBMS()].delimiter.query nulledCastedFields = [] for field in fieldsSplitted: @@ -342,14 +342,14 @@ class Agent: @rtype: C{str} """ - prefixRegex = "(?:\s+(?:FIRST|SKIP)\s+\d+)*" - fieldsSelectTop = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", query, re.I) + prefixRegex = "(?:\s+(?:FIRST|SKIP)\s+\d+)*" + fieldsSelectTop = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", query, re.I) fieldsSelectDistinct = re.search("\ASELECT%s\s+DISTINCT\((.+?)\)\s+FROM" % prefixRegex, query, re.I) - fieldsSelectCase = re.search("\ASELECT%s\s+(\(CASE WHEN\s+.+\s+END\))" % prefixRegex, query, re.I) - fieldsSelectFrom = re.search("\ASELECT%s\s+(.+?)\s+FROM\s+" % prefixRegex, query, re.I) - fieldsExists = re.search("EXISTS(.*)", query, re.I) - fieldsSelect = re.search("\ASELECT%s\s+(.*)" % prefixRegex, query, re.I) - fieldsNoSelect = query + fieldsSelectCase = re.search("\ASELECT%s\s+(\(CASE WHEN\s+.+\s+END\))" % prefixRegex, query, re.I) + fieldsSelectFrom = re.search("\ASELECT%s\s+(.+?)\s+FROM\s+" % prefixRegex, query, re.I) + fieldsExists = re.search("EXISTS(.*)", query, re.I) + fieldsSelect = re.search("\ASELECT%s\s+(.*)" % prefixRegex, query, re.I) + fieldsNoSelect = query if fieldsExists: fieldsToCastStr = fieldsSelect.groups()[0] @@ -366,7 +366,7 @@ class Agent: elif fieldsNoSelect: fieldsToCastStr = fieldsNoSelect - if re.search("\A\w+\(.*\)", fieldsToCastStr, re.I): #function + if re.search("\A\w+\(.*\)", fieldsToCastStr, re.I): # Function fieldsToCastList = [fieldsToCastStr] else: fieldsToCastList = fieldsToCastStr.replace(", ", ",") @@ -380,7 +380,7 @@ class Agent: if getIdentifiedDBMS() == DBMS.MYSQL: concatenatedQuery = "CONCAT(%s,%s)" % (query1, query2) - elif getIdentifiedDBMS() in ( DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE ): + elif getIdentifiedDBMS() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE): concatenatedQuery = "%s||%s" % (query1, query2) elif getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): @@ -416,10 +416,10 @@ class Agent: if unpack: concatenatedQuery = "" - query = query.replace(", ", ",") + query = query.replace(", ", ",") fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, _, fieldsToCastStr, fieldsExists = self.getFields(query) - castedFields = self.nullCastConcatFields(fieldsToCastStr) + castedFields = self.nullCastConcatFields(fieldsToCastStr) concatenatedQuery = query.replace(fieldsToCastStr, castedFields, 1) else: concatenatedQuery = query @@ -438,25 +438,25 @@ class Agent: elif fieldsNoSelect: concatenatedQuery = "CONCAT('%s',%s,'%s')" % (kb.misc.start, concatenatedQuery, kb.misc.stop) - elif getIdentifiedDBMS() in ( DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE ): + elif getIdentifiedDBMS() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE): if fieldsExists: - concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.misc.start, 1) + concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.misc.start, 1) concatenatedQuery += "||'%s'" % kb.misc.stop elif fieldsSelectFrom: concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.misc.start, 1) concatenatedQuery = concatenatedQuery.replace(" FROM ", "||'%s' FROM " % kb.misc.stop, 1) elif fieldsSelect or fieldsSelectCase: - concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.misc.start, 1) + concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.misc.start, 1) concatenatedQuery += "||'%s'" % kb.misc.stop elif fieldsNoSelect: concatenatedQuery = "'%s'||%s||'%s'" % (kb.misc.start, concatenatedQuery, kb.misc.stop) - if getIdentifiedDBMS() == DBMS.ORACLE and " FROM " not in concatenatedQuery and ( fieldsSelect or fieldsNoSelect ): + if getIdentifiedDBMS() == DBMS.ORACLE and " FROM " not in concatenatedQuery and (fieldsSelect or fieldsNoSelect): concatenatedQuery += " FROM DUAL" elif getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): if fieldsExists: - concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.misc.start, 1) + concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.misc.start, 1) concatenatedQuery += "+'%s'" % kb.misc.stop elif fieldsSelectTop: topNum = re.search("\ASELECT\s+TOP\s+([\d]+)\s+", concatenatedQuery, re.I).group(1) @@ -466,7 +466,7 @@ class Agent: concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.misc.start, 1) concatenatedQuery = concatenatedQuery.replace(" FROM ", "+'%s' FROM " % kb.misc.stop, 1) elif fieldsSelect or fieldsSelectCase: - concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.misc.start, 1) + concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.misc.start, 1) concatenatedQuery += "+'%s'" % kb.misc.stop elif fieldsNoSelect: concatenatedQuery = "'%s'+%s+'%s'" % (kb.misc.start, concatenatedQuery, kb.misc.stop) @@ -520,8 +520,8 @@ class Agent: intoRegExp = intoRegExp.group(1) query = query[:query.index(intoRegExp)] - if getIdentifiedDBMS() in INBAND_FROM_TABLE and inbandQuery.endswith(INBAND_FROM_TABLE[getIdentifiedDBMS()]): - inbandQuery = inbandQuery[:-len(INBAND_FROM_TABLE[getIdentifiedDBMS()])] + if getIdentifiedDBMS() in FROM_TABLE and inbandQuery.endswith(FROM_TABLE[getIdentifiedDBMS()]): + inbandQuery = inbandQuery[:-len(FROM_TABLE[getIdentifiedDBMS()])] for element in range(count): if element > 0: @@ -540,9 +540,9 @@ class Agent: conditionIndex = query.index(" FROM ") inbandQuery += query[conditionIndex:] - if getIdentifiedDBMS() in INBAND_FROM_TABLE: + if getIdentifiedDBMS() in FROM_TABLE: if " FROM " not in inbandQuery: - inbandQuery += INBAND_FROM_TABLE[getIdentifiedDBMS()] + inbandQuery += FROM_TABLE[getIdentifiedDBMS()] if intoRegExp: inbandQuery += intoRegExp @@ -559,8 +559,8 @@ class Agent: else: inbandQuery += char - if getIdentifiedDBMS() in INBAND_FROM_TABLE: - inbandQuery += INBAND_FROM_TABLE[getIdentifiedDBMS()] + if getIdentifiedDBMS() in FROM_TABLE: + inbandQuery += FROM_TABLE[getIdentifiedDBMS()] inbandQuery = self.suffixQuery(inbandQuery, comment, suffix) @@ -595,7 +595,7 @@ class Agent: fromFrom = limitedQuery[fromIndex+1:] orderBy = False - if getIdentifiedDBMS() in ( DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE ): + if getIdentifiedDBMS() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE): limitStr = queries[getIdentifiedDBMS()].limit.query % (num, 1) limitedQuery += " %s" % limitStr @@ -612,7 +612,7 @@ class Agent: limitedQuery = "%s FROM (%s, %s" % (untilFrom, untilFrom, limitStr) else: limitedQuery = "%s FROM (SELECT %s, %s" % (untilFrom, ", ".join(f for f in field), limitStr) - limitedQuery = limitedQuery % fromFrom + limitedQuery = limitedQuery % fromFrom limitedQuery += "=%d" % (num + 1) elif getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): @@ -634,21 +634,21 @@ class Agent: if topNums: topNums = topNums.groups() quantityTopNums = topNums[0] - limitedQuery = limitedQuery.replace("TOP %s" % quantityTopNums, "TOP 1", 1) - startTopNums = topNums[1] - limitedQuery = limitedQuery.replace(" (SELECT TOP %s" % startTopNums, " (SELECT TOP %d" % num) - forgeNotIn = False + limitedQuery = limitedQuery.replace("TOP %s" % quantityTopNums, "TOP 1", 1) + startTopNums = topNums[1] + limitedQuery = limitedQuery.replace(" (SELECT TOP %s" % startTopNums, " (SELECT TOP %d" % num) + forgeNotIn = False else: - topNum = re.search("TOP\s+([\d]+)\s+", limitedQuery, re.I).group(1) - limitedQuery = limitedQuery.replace("TOP %s " % topNum, "") + topNum = re.search("TOP\s+([\d]+)\s+", limitedQuery, re.I).group(1) + limitedQuery = limitedQuery.replace("TOP %s " % topNum, "") if forgeNotIn: limitedQuery = limitedQuery.replace("SELECT ", (limitStr % 1), 1) if " WHERE " in limitedQuery: - limitedQuery = "%s AND %s " % (limitedQuery, field) + limitedQuery = "%s AND %s " % (limitedQuery, field) else: - limitedQuery = "%s WHERE %s " % (limitedQuery, field) + limitedQuery = "%s WHERE %s " % (limitedQuery, field) limitedQuery += "NOT IN (%s" % (limitStr % num) limitedQuery += "%s %s)" % (field, fromFrom) diff --git a/lib/core/settings.py b/lib/core/settings.py index 3f02fc6b9..7764725c9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -132,7 +132,12 @@ SYBASE_ALIASES = [ "sybase", "sybase sql server" ] SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES SUPPORTED_OS = ( "linux", "windows" ) -INBAND_FROM_TABLE = {DBMS.ORACLE: " FROM DUAL", DBMS.ACCESS: " FROM MSysObjects", DBMS.FIREBIRD: " FROM RDB$DATABASE", DBMS.MAXDB: " FROM VERSIONS"} +FROM_TABLE = { + DBMS.ORACLE: " FROM DUAL", + DBMS.ACCESS: " FROM MSysObjects", + DBMS.FIREBIRD: " FROM RDB$DATABASE", + DBMS.MAXDB: " FROM VERSIONS" + } SQL_STATEMENTS = { "SQL SELECT statement": ( diff --git a/lib/request/inject.py b/lib/request/inject.py index 2ea5c3871..0f1760cd1 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -18,6 +18,7 @@ from lib.core.common import expandAsteriskForColumns from lib.core.common import getIdentifiedDBMS from lib.core.common import getPublicTypeMembers from lib.core.common import initTechnique +from lib.core.common import isNumPosStrValue from lib.core.common import isTechniqueAvailable from lib.core.common import parseUnionPage from lib.core.common import popValue @@ -34,6 +35,8 @@ from lib.core.enums import DBMS from lib.core.enums import EXPECTED from lib.core.enums import PAYLOAD from lib.core.exception import sqlmapNotVulnerableException +from lib.core.exception import sqlmapUserQuitException +from lib.core.settings import FROM_TABLE from lib.core.settings import MIN_TIME_RESPONSES from lib.core.settings import MAX_TECHNIQUES_PER_VALUE from lib.core.threads import getCurrentThreadData @@ -51,7 +54,7 @@ def __goInference(payload, expression, charsetType=None, firstChar=None, lastCha timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) - if ( conf.eta or conf.threads > 1 ) and getIdentifiedDBMS() and not timeBasedCompare: + if (conf.eta or conf.threads > 1) and getIdentifiedDBMS() and not timeBasedCompare: _, length, _ = queryOutputLength(expression, payload) else: length = None @@ -66,8 +69,8 @@ def __goInference(payload, expression, charsetType=None, firstChar=None, lastCha return value def __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected=None, num=None, resumeValue=True, charsetType=None, firstChar=None, lastChar=None): - outputs = [] - origExpr = None + outputs = [] + origExpr = None for field in expressionFieldsList: output = None @@ -76,7 +79,7 @@ def __goInferenceFields(expression, expressionFields, expressionFieldsList, payl continue if isinstance(num, int): - origExpr = expression + origExpr = expression expression = agent.limitQuery(num, expression, field) if "ROWNUM" in expressionFieldsList: @@ -87,9 +90,9 @@ def __goInferenceFields(expression, expressionFields, expressionFieldsList, payl if resumeValue: output = resume(expressionReplaced, payload) - if not output or ( expected == EXPECTED.INT and not output.isdigit() ): + if not output or (expected == EXPECTED.INT and not output.isdigit()): if output: - warnMsg = "expected value type %s, resumed '%s', " % (expected, output) + warnMsg = "expected value type %s, resumed '%s', " % (expected, output) warnMsg += "sqlmap is going to retrieve the value again" logger.warn(warnMsg) @@ -102,31 +105,6 @@ def __goInferenceFields(expression, expressionFields, expressionFieldsList, payl return outputs -def __goBooleanProxy(expression, resumeValue=True): - """ - Retrieve the output of a boolean based SQL query - """ - - initTechnique(kb.technique) - - vector = kb.injection.data[kb.technique].vector - vector = vector.replace("[INFERENCE]", expression) - vector = agent.cleanupPayload(vector) - query = agent.prefixQuery(vector) - query = agent.suffixQuery(query) - payload = agent.payload(newValue=query) - timeBasedCompare = kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) - - if resumeValue: - output = resume(expression, payload) - else: - output = None - - if not output: - output = Request.queryPage(payload, timeBasedCompare=timeBasedCompare, raise404=False) - - return output - def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, resumeValue=True, unpack=True, charsetType=None, firstChar=None, lastChar=None): """ Retrieve the output of a SQL query characted by character taking @@ -153,7 +131,7 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r else: output = None - if output and ( expected is None or ( expected == EXPECTED.INT and output.isdigit() ) ): + if output and (expected is None or (expected == EXPECTED.INT and output.isdigit())): return output if not unpack: @@ -178,14 +156,14 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r # forge the SQL limiting the query output one entry per time # NOTE: I assume that only queries that get data from a table # can return multiple entries - if fromUser and " FROM " in expression: + if fromUser and " FROM " in expression.upper() and ((getIdentifiedDBMS() not in FROM_TABLE) or (getIdentifiedDBMS() in FROM_TABLE and not expression.upper().endswith(FROM_TABLE[getIdentifiedDBMS()]))): limitRegExp = re.search(queries[getIdentifiedDBMS()].limitregexp.query, expression, re.I) - topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I) + topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I) - if limitRegExp or ( getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit ): - if getIdentifiedDBMS() in ( DBMS.MYSQL, DBMS.PGSQL ): + if limitRegExp or (getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit): + if getIdentifiedDBMS() in (DBMS.MYSQL, DBMS.PGSQL): limitGroupStart = queries[getIdentifiedDBMS()].limitgroupstart.query - limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query + limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) @@ -196,7 +174,7 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r elif getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): if limitRegExp: limitGroupStart = queries[getIdentifiedDBMS()].limitgroupstart.query - limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query + limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) @@ -205,8 +183,8 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r limitCond = int(stopLimit) > 1 elif topLimit: startLimit = 0 - stopLimit = int(topLimit.group(1)) - limitCond = int(stopLimit) > 1 + stopLimit = int(topLimit.group(1)) + limitCond = int(stopLimit) > 1 elif getIdentifiedDBMS() == DBMS.ORACLE: limitCond = False @@ -222,7 +200,7 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word - if getIdentifiedDBMS() in ( DBMS.MYSQL, DBMS.PGSQL ): + if getIdentifiedDBMS() in (DBMS.MYSQL, DBMS.PGSQL): stopLimit += startLimit untilLimitChar = expression.index(queries[getIdentifiedDBMS()].limitstring.query) expression = expression[:untilLimitChar] @@ -231,18 +209,14 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r stopLimit += startLimit if not stopLimit or stopLimit <= 1: - if getIdentifiedDBMS() == DBMS.ORACLE and expression.endswith("FROM DUAL"): - test = "n" - elif batch: - test = "y" + if getIdentifiedDBMS() in FROM_TABLE and expression.upper().endswith(FROM_TABLE[getIdentifiedDBMS()]): + test = False else: - message = "can the SQL query provided return " - message += "multiple entries? [Y/n] " - test = readInput(message, default="Y") + test = True - if not test or test[0] in ("y", "Y"): + if test: # Count the number of SQL query entries output - countFirstField = queries[getIdentifiedDBMS()].count.query % expressionFieldsList[0] + countFirstField = queries[getIdentifiedDBMS()].count.query % expressionFieldsList[0] countedExpression = expression.replace(expressionFields, countFirstField, 1) if re.search(" ORDER BY ", expression, re.I): @@ -254,16 +228,16 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r if not stopLimit: if not count or not count.isdigit(): - count = __goInference(payload, countedExpression, charsetType, firstChar, lastChar) + count = __goInference(payload, countedExpression, 2, firstChar, lastChar) - if count and count.isdigit() and int(count) > 0: + if isNumPosStrValue(count): count = int(count) if batch: stopLimit = count else: - message = "the SQL query provided can return " - message += "up to %d entries. How many " % count + message = "the SQL query provided can return " + message += "%d entries. How many " % count message += "entries do you want to retrieve?\n" message += "[a] All (default)\n[#] Specific number\n" message += "[q] Quit" @@ -273,21 +247,21 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r stopLimit = count elif test[0] in ("q", "Q"): - return "Quit" + raise sqlmapUserQuitException elif test.isdigit() and int(test) > 0 and int(test) <= count: stopLimit = int(test) - infoMsg = "sqlmap is now going to retrieve the " + infoMsg = "sqlmap is now going to retrieve the " infoMsg += "first %d query output entries" % stopLimit logger.info(infoMsg) elif test[0] in ("#", "s", "S"): - message = "How many? " + message = "how many? " stopLimit = readInput(message, default="10") if not stopLimit.isdigit(): - errMsg = "Invalid choice" + errMsg = "invalid choice" logger.error(errMsg) return None @@ -296,13 +270,13 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r stopLimit = int(stopLimit) else: - errMsg = "Invalid choice" + errMsg = "invalid choice" logger.error(errMsg) return None elif count and not count.isdigit(): - warnMsg = "it was not possible to count the number " + warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" @@ -310,39 +284,68 @@ def __goInferenceProxy(expression, fromUser=False, expected=None, batch=False, r stopLimit = 1 - elif ( not count or int(count) == 0 ): - warnMsg = "the SQL query provided does not " + elif (not count or int(count) == 0): + warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return None - elif ( not count or int(count) == 0 ) and ( not stopLimit or stopLimit == 0 ): - warnMsg = "the SQL query provided does not " + elif (not count or int(count) == 0) and (not stopLimit or stopLimit == 0): + warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return None - for num in xrange(startLimit, stopLimit): - output = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected, num, resumeValue=resumeValue, charsetType=charsetType, firstChar=firstChar, lastChar=lastChar) - outputs.append(output) + try: + for num in xrange(startLimit, stopLimit): + output = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected, num, resumeValue=resumeValue, charsetType=charsetType, firstChar=firstChar, lastChar=lastChar) + outputs.append(output) + + except KeyboardInterrupt: + print + warnMsg = "Ctrl+C detected in dumping phase" + logger.warn(warnMsg) return outputs - elif getIdentifiedDBMS() == DBMS.ORACLE and expression.startswith("SELECT ") and " FROM " not in expression: - expression = "%s FROM DUAL" % expression + elif getIdentifiedDBMS() in FROM_TABLE and expression.upper().startswith("SELECT ") and " FROM " not in expression.upper(): + expression += FROM_TABLE[getIdentifiedDBMS()] outputs = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected, resumeValue=resumeValue, charsetType=charsetType, firstChar=firstChar, lastChar=lastChar) - returnValue = ", ".join([output for output in outputs]) - else: returnValue = __goInference(payload, expression, charsetType, firstChar, lastChar) return returnValue -def __goError(expression, resumeValue=True): +def __goBooleanProxy(expression, resumeValue=True): + """ + Retrieve the output of a boolean based SQL query + """ + + initTechnique(kb.technique) + + vector = kb.injection.data[kb.technique].vector + vector = vector.replace("[INFERENCE]", expression) + vector = agent.cleanupPayload(vector) + query = agent.prefixQuery(vector) + query = agent.suffixQuery(query) + payload = agent.payload(newValue=query) + timeBasedCompare = kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) + + if resumeValue: + output = resume(expression, payload) + else: + output = None + + if not output: + output = Request.queryPage(payload, timeBasedCompare=timeBasedCompare, raise404=False) + + return output + +def __goError(expression, expected=None, resumeValue=True, dump=False): """ Retrieve the output of a SQL query taking advantage of an error-based SQL injection vulnerability on the affected parameter. @@ -357,8 +360,7 @@ def __goError(expression, resumeValue=True): result = resume(expression, None) if not result: - result = errorUse(expression) - dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(result))) + result = errorUse(expression, expected, resumeValue, dump) return result @@ -368,14 +370,14 @@ def __goInband(expression, expected=None, sort=True, resumeValue=True, unpack=Tr injection vulnerability on the affected parameter. """ - output = None + output = None partial = False - data = [] + data = [] if resumeValue: output = resume(expression, None) - if not output or ( expected == EXPECTED.INT and not output.isdigit() ): + if not output or (expected == EXPECTED.INT and not output.isdigit()): partial = True if not output: @@ -405,8 +407,10 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse query = expandAsteriskForColumns(query) value = None found = False + if query and not 'COUNT(*)' in query: query = query.replace("DISTINCT ", "") + count = 0 if expected == EXPECTED.BOOL: @@ -432,9 +436,9 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse kb.technique = PAYLOAD.TECHNIQUE.ERROR if expected == EXPECTED.BOOL: - value = __goError(forgeCaseExpression, resumeValue) + value = __goError(forgeCaseExpression, expected, resumeValue, dump) else: - value = __goError(query, resumeValue) + value = __goError(query, expected, resumeValue, dump) count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index 1bf5bc2a7..68949083e 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -11,10 +11,12 @@ import re import time from lib.core.agent import agent -from lib.core.common import dataToStdout +from lib.core.common import calculateDeltaSeconds +from lib.core.common import dataToSessionFile from lib.core.common import extractRegexResult from lib.core.common import getIdentifiedDBMS from lib.core.common import initTechnique +from lib.core.common import isNumPosStrValue from lib.core.common import randomInt from lib.core.common import replaceNewlineTabs from lib.core.common import safeStringFormat @@ -23,41 +25,249 @@ from lib.core.data import kb from lib.core.data import logger from lib.core.data import queries from lib.core.enums import DBMS +from lib.core.enums import EXPECTED from lib.core.enums import PAYLOAD +from lib.core.settings import FROM_TABLE from lib.core.unescaper import unescaper from lib.request.connect import Connect as Request +from lib.utils.resume import resume -def errorUse(expression): - """ - Retrieve the output of a SQL query taking advantage of an error SQL - injection vulnerability on the affected parameter. - """ +reqCount = 0 - initTechnique(PAYLOAD.TECHNIQUE.ERROR) +def __oneShotErrorUse(expression, field): + global reqCount - output = None + check = "%s(?P.*?)%s" % (kb.misc.start, kb.misc.stop) + nulledCastedField = agent.nullAndCastField(field) + + if getIdentifiedDBMS() == DBMS.MYSQL: + # Fix for MySQL odd behaviour ('Subquery returns more than 1 row') + nulledCastedField = nulledCastedField.replace("AS CHAR)", "AS CHAR(100))") + + # Forge the error-based SQL injection request vector = agent.cleanupPayload(kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector) query = unescaper.unescape(vector) query = agent.prefixQuery(query) query = agent.suffixQuery(query) - check = "%s(?P.*?)%s" % (kb.misc.start, kb.misc.stop) + injExpression = expression.replace(field, nulledCastedField, 1) + injExpression = unescaper.unescape(injExpression) + injExpression = query.replace("[QUERY]", injExpression) + payload = agent.payload(newValue=injExpression) - _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) - nulledCastedField = agent.nullAndCastField(fieldToCastStr) - - if getIdentifiedDBMS() == DBMS.MYSQL: - nulledCastedField = nulledCastedField.replace("AS CHAR)", "AS CHAR(100))") # fix for that 'Subquery returns more than 1 row' - - expression = expression.replace(fieldToCastStr, nulledCastedField, 1) - expression = unescaper.unescape(expression) - expression = query.replace("[QUERY]", expression) - - payload = agent.payload(newValue=expression) + # Perform the request page, _ = Request.queryPage(payload, content=True) + reqCount += 1 + + # Parse the returned page to get the exact error-based + # sql injection output output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) - if output: - output = output.replace(kb.misc.space, " ") - dataToStdout("[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), replaceNewlineTabs(output, stdout=True))) + dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(output))) return output + +def __errorFields(expression, expressionFields, expressionFieldsList, expected=None, num=None, resumeValue=True): + outputs = [] + origExpr = None + + for field in expressionFieldsList: + output = None + + if field.startswith("ROWNUM "): + continue + + if isinstance(num, int): + origExpr = expression + expression = agent.limitQuery(num, expression, field) + + if "ROWNUM" in expressionFieldsList: + expressionReplaced = expression + else: + expressionReplaced = expression.replace(expressionFields, field, 1) + + if resumeValue: + output = resume(expressionReplaced, None) + + if not output or (expected == EXPECTED.INT and not output.isdigit()): + if output: + warnMsg = "expected value type %s, resumed '%s', " % (expected, output) + warnMsg += "sqlmap is going to retrieve the value again" + logger.warn(warnMsg) + + output = __oneShotErrorUse(expressionReplaced, field) + logger.info("retrieved: %s" % output) + + if isinstance(num, int): + expression = origExpr + + if output: + output = output.replace(kb.misc.space, " ") + outputs.append(output) + + return outputs + +def errorUse(expression, expected=None, resumeValue=True, dump=False): + """ + Retrieve the output of a SQL query taking advantage of the error-based + SQL injection vulnerability on the affected parameter. + """ + + initTechnique(PAYLOAD.TECHNIQUE.ERROR) + + count = None + start = time.time() + startLimit = 0 + stopLimit = None + outputs = [] + test = None + untilLimitChar = None + untilOrderChar = None + + global reqCount + + reqCount = 0 + + if resumeValue: + output = resume(expression, None) + else: + output = None + + if output and (expected is None or (expected == EXPECTED.INT and output.isdigit())): + return output + + _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(expression) + + # We have to check if the SQL query might return multiple entries + # and in such case forge the SQL limiting the query output one + # entry per time + # NOTE: I assume that only queries that get data from a table can + # return multiple entries + if " FROM " in expression.upper() and ((getIdentifiedDBMS() not in FROM_TABLE) or (getIdentifiedDBMS() in FROM_TABLE and not expression.upper().endswith(FROM_TABLE[getIdentifiedDBMS()]))) and "EXISTS(" not in expression.upper(): + limitRegExp = re.search(queries[getIdentifiedDBMS()].limitregexp.query, expression, re.I) + topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I) + + if limitRegExp or (getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit): + if getIdentifiedDBMS() in (DBMS.MYSQL, DBMS.PGSQL): + limitGroupStart = queries[getIdentifiedDBMS()].limitgroupstart.query + limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query + + if limitGroupStart.isdigit(): + startLimit = int(limitRegExp.group(int(limitGroupStart))) + + stopLimit = limitRegExp.group(int(limitGroupStop)) + limitCond = int(stopLimit) > 1 + + elif getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): + if limitRegExp: + limitGroupStart = queries[getIdentifiedDBMS()].limitgroupstart.query + limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query + + if limitGroupStart.isdigit(): + startLimit = int(limitRegExp.group(int(limitGroupStart))) + + stopLimit = limitRegExp.group(int(limitGroupStop)) + limitCond = int(stopLimit) > 1 + elif topLimit: + startLimit = 0 + stopLimit = int(topLimit.group(1)) + limitCond = int(stopLimit) > 1 + + elif getIdentifiedDBMS() == DBMS.ORACLE: + limitCond = False + else: + limitCond = True + + # I assume that only queries NOT containing a "LIMIT #, 1" + # (or similar depending on the back-end DBMS) can return + # multiple entries + if limitCond: + if limitRegExp: + stopLimit = int(stopLimit) + + # From now on we need only the expression until the " LIMIT " + # (or similar, depending on the back-end DBMS) word + if getIdentifiedDBMS() in (DBMS.MYSQL, DBMS.PGSQL): + stopLimit += startLimit + untilLimitChar = expression.index(queries[getIdentifiedDBMS()].limitstring.query) + expression = expression[:untilLimitChar] + + elif getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): + stopLimit += startLimit + elif dump: + if conf.limitStart: + startLimit = conf.limitStart + if conf.limitStop: + stopLimit = conf.limitStop + + if not stopLimit or stopLimit <= 1: + if getIdentifiedDBMS() in FROM_TABLE and expression.upper().endswith(FROM_TABLE[getIdentifiedDBMS()]): + test = False + else: + test = True + + if test: + # Count the number of SQL query entries output + countFirstField = queries[getIdentifiedDBMS()].count.query % expressionFieldsList[0] + countedExpression = expression.replace(expressionFields, countFirstField, 1) + + if re.search(" ORDER BY ", expression, re.I): + untilOrderChar = countedExpression.index(" ORDER BY ") + countedExpression = countedExpression[:untilOrderChar] + + if resumeValue: + count = resume(countedExpression, None) + + if not stopLimit: + if not count or not count.isdigit(): + count = __oneShotErrorUse(countedExpression, expressionFields) + + if isNumPosStrValue(count): + stopLimit = int(count) + + infoMsg = "the SQL query used returns " + infoMsg += "%d entries" % stopLimit + logger.info(infoMsg) + + elif count and not count.isdigit(): + warnMsg = "it was not possible to count the number " + warnMsg += "of entries for the used SQL query. " + warnMsg += "sqlmap will assume that it returns only " + warnMsg += "one entry" + logger.warn(warnMsg) + + stopLimit = 1 + + elif (not count or int(count) == 0): + warnMsg = "the SQL query used does not " + warnMsg += "return any output" + logger.warn(warnMsg) + + return None + + elif (not count or int(count) == 0) and (not stopLimit or stopLimit == 0): + warnMsg = "the SQL query used does not " + warnMsg += "return any output" + logger.warn(warnMsg) + + return None + + try: + for num in xrange(startLimit, stopLimit): + output = __errorFields(expression, expressionFields, expressionFieldsList, expected, num, resumeValue) + outputs.append(output) + + except KeyboardInterrupt: + print + warnMsg = "Ctrl+C detected in dumping phase" + logger.warn(warnMsg) + + duration = calculateDeltaSeconds(start) + + debugMsg = "performed %d queries in %d seconds" % (reqCount, duration) + logger.debug(debugMsg) + + return outputs + else: + return __oneShotErrorUse(expression, expressionFields) + + return outputs diff --git a/lib/techniques/inband/union/test.py b/lib/techniques/inband/union/test.py index 63cb00c53..dcfe60b36 100644 --- a/lib/techniques/inband/union/test.py +++ b/lib/techniques/inband/union/test.py @@ -22,7 +22,7 @@ from lib.core.data import logger from lib.core.data import queries from lib.core.enums import DBMS from lib.core.enums import PAYLOAD -from lib.core.settings import INBAND_FROM_TABLE +from lib.core.settings import FROM_TABLE from lib.core.unescaper import unescaper from lib.parse.html import htmlParser from lib.request.connect import Connect as Request @@ -98,14 +98,14 @@ def __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix query = agent.prefixQuery("UNION ALL SELECT %s" % conf.uChar) for count in range(conf.uColsStart, conf.uColsStop+1): - if getIdentifiedDBMS() in INBAND_FROM_TABLE and query.endswith(INBAND_FROM_TABLE[getIdentifiedDBMS()]): - query = query[:-len(INBAND_FROM_TABLE[getIdentifiedDBMS()])] + if getIdentifiedDBMS() in FROM_TABLE and query.endswith(FROM_TABLE[getIdentifiedDBMS()]): + query = query[:-len(FROM_TABLE[getIdentifiedDBMS()])] if count: query += ", %s" % conf.uChar - if getIdentifiedDBMS() in INBAND_FROM_TABLE: - query += INBAND_FROM_TABLE[getIdentifiedDBMS()] + if getIdentifiedDBMS() in FROM_TABLE: + query += FROM_TABLE[getIdentifiedDBMS()] status = "%d/%d" % (count, conf.uColsStop) debugMsg = "testing %s columns (%d%%)" % (status, round(100.0*count/conf.uColsStop)) diff --git a/lib/techniques/inband/union/use.py b/lib/techniques/inband/union/use.py index ee0246861..599ce6de2 100644 --- a/lib/techniques/inband/union/use.py +++ b/lib/techniques/inband/union/use.py @@ -17,6 +17,7 @@ from lib.core.common import dataToStdout from lib.core.common import getIdentifiedDBMS from lib.core.common import getUnicode from lib.core.common import initTechnique +from lib.core.common import isNumPosStrValue from lib.core.common import parseUnionPage from lib.core.data import conf from lib.core.data import kb @@ -25,7 +26,7 @@ from lib.core.data import queries from lib.core.enums import DBMS from lib.core.enums import PAYLOAD from lib.core.exception import sqlmapSyntaxException -from lib.core.settings import INBAND_FROM_TABLE +from lib.core.settings import FROM_TABLE from lib.core.unescaper import unescaper from lib.request.connect import Connect as Request from lib.utils.resume import resume @@ -76,13 +77,13 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack initTechnique(PAYLOAD.TECHNIQUE.UNION) - count = None - origExpr = expression - start = time.time() + count = None + origExpr = expression + start = time.time() startLimit = 0 - stopLimit = None - test = True - value = "" + stopLimit = None + test = True + value = "" global reqCount @@ -102,13 +103,14 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack # entry per time # NOTE: I assume that only queries that get data from a table can # return multiple entries - if " FROM " in expression.upper() and " FROM DUAL" not in expression.upper() and "EXISTS(" not in expression.upper(): + if " FROM " in expression.upper() and ((getIdentifiedDBMS() not in FROM_TABLE) or (getIdentifiedDBMS() in FROM_TABLE and not expression.upper().endswith(FROM_TABLE[getIdentifiedDBMS()]))) and "EXISTS(" not in expression.upper(): limitRegExp = re.search(queries[getIdentifiedDBMS()].limitregexp.query, expression, re.I) + topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I) - if limitRegExp: - if getIdentifiedDBMS() in ( DBMS.MYSQL, DBMS.PGSQL ): + if limitRegExp or (getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit): + if getIdentifiedDBMS() in (DBMS.MYSQL, DBMS.PGSQL): limitGroupStart = queries[getIdentifiedDBMS()].limitgroupstart.query - limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query + limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) @@ -117,14 +119,19 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack limitCond = int(stopLimit) > 1 elif getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): - limitGroupStart = queries[getIdentifiedDBMS()].limitgroupstart.query - limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query + if limitRegExp: + limitGroupStart = queries[getIdentifiedDBMS()].limitgroupstart.query + limitGroupStop = queries[getIdentifiedDBMS()].limitgroupstop.query - if limitGroupStart.isdigit(): - startLimit = int(limitRegExp.group(int(limitGroupStart))) + if limitGroupStart.isdigit(): + startLimit = int(limitRegExp.group(int(limitGroupStart))) - stopLimit = limitRegExp.group(int(limitGroupStop)) - limitCond = int(stopLimit) > 1 + stopLimit = limitRegExp.group(int(limitGroupStop)) + limitCond = int(stopLimit) > 1 + elif topLimit: + startLimit = 0 + stopLimit = int(topLimit.group(1)) + limitCond = int(stopLimit) > 1 elif getIdentifiedDBMS() == DBMS.ORACLE: limitCond = False @@ -140,7 +147,7 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word - if getIdentifiedDBMS() in ( DBMS.MYSQL, DBMS.PGSQL ): + if getIdentifiedDBMS() in (DBMS.MYSQL, DBMS.PGSQL): stopLimit += startLimit untilLimitChar = expression.index(queries[getIdentifiedDBMS()].limitstring.query) expression = expression[:untilLimitChar] @@ -154,14 +161,14 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack stopLimit = conf.limitStop if not stopLimit or stopLimit <= 1: - if getIdentifiedDBMS() in INBAND_FROM_TABLE and expression.endswith(INBAND_FROM_TABLE[getIdentifiedDBMS()]): + if getIdentifiedDBMS() in FROM_TABLE and expression.upper().endswith(FROM_TABLE[getIdentifiedDBMS()]): test = False else: test = True if test: # Count the number of SQL query entries output - countFirstField = queries[getIdentifiedDBMS()].count.query % expressionFieldsList[0] + countFirstField = queries[getIdentifiedDBMS()].count.query % expressionFieldsList[0] countedExpression = origExpr.replace(expressionFields, countFirstField, 1) if re.search(" ORDER BY ", expression, re.I): @@ -177,15 +184,15 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack if output: count = parseUnionPage(output, countedExpression) - if count and count.isdigit() and int(count) > 0: + if isNumPosStrValue(count): stopLimit = int(count) - infoMsg = "the SQL query used returns " + infoMsg = "the SQL query used returns " infoMsg += "%d entries" % stopLimit logger.info(infoMsg) elif count and not count.isdigit(): - warnMsg = "it was not possible to count the number " + warnMsg = "it was not possible to count the number " warnMsg += "of entries for the used SQL query. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" @@ -193,44 +200,44 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack stopLimit = 1 - elif ( not count or int(count) == 0 ): - warnMsg = "the SQL query used does not " + elif (not count or int(count) == 0): + warnMsg = "the SQL query used does not " warnMsg += "return any output" logger.warn(warnMsg) - return + return None - elif ( not count or int(count) == 0 ) and ( not stopLimit or stopLimit == 0 ): - warnMsg = "the SQL query used does not " + elif (not count or int(count) == 0) and (not stopLimit or stopLimit == 0): + warnMsg = "the SQL query used does not " warnMsg += "return any output" logger.warn(warnMsg) - return + return None try: for num in xrange(startLimit, stopLimit): - if getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): - field = expressionFieldsList[0] - elif getIdentifiedDBMS() == DBMS.ORACLE: - field = expressionFieldsList - else: - field = None + if getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): + field = expressionFieldsList[0] + elif getIdentifiedDBMS() == DBMS.ORACLE: + field = expressionFieldsList + else: + field = None - limitedExpr = agent.limitQuery(num, expression, field) - output = resume(limitedExpr, None) + limitedExpr = agent.limitQuery(num, expression, field) + output = resume(limitedExpr, None) - if not output: - output = unionUse(limitedExpr, direct=True, unescape=False) + if not output: + output = unionUse(limitedExpr, direct=True, unescape=False) - if output: - value += output - parseUnionPage(output, limitedExpr) + if output: + value += output + parseUnionPage(output, limitedExpr) - if conf.verbose in (1, 2): - length = stopLimit - startLimit - count = num - startLimit + 1 - status = '%d/%d entries (%d%s)' % (count, length, round(100.0*count/length), '%') - dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), status), True) + if conf.verbose in (1, 2): + length = stopLimit - startLimit + count = num - startLimit + 1 + status = '%d/%d entries (%d%s)' % (count, length, round(100.0*count/length), '%') + dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), status), True) dataToStdout("\n") diff --git a/plugins/dbms/mssqlserver/enumeration.py b/plugins/dbms/mssqlserver/enumeration.py index 1535b154d..43a20fe57 100644 --- a/plugins/dbms/mssqlserver/enumeration.py +++ b/plugins/dbms/mssqlserver/enumeration.py @@ -54,7 +54,7 @@ class Enumeration(GenericEnumeration): else: dbs = [conf.db] - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: for db in dbs: if conf.excludeSysDbs and db in self.excludeDbsList: infoMsg = "skipping system database '%s'" % db @@ -63,7 +63,7 @@ class Enumeration(GenericEnumeration): continue query = rootQuery.inband.query % db - value = inject.getValue(query, blind=False, error=False) + value = inject.getValue(query, blind=False) if value: kb.data.cachedTables[db] = arrayizeValue(value) @@ -81,7 +81,7 @@ class Enumeration(GenericEnumeration): logger.info(infoMsg) query = rootQuery.blind.count % db - count = inject.getValue(query, inband=False, charsetType=2) + count = inject.getValue(query, inband=False, error=False, charsetType=2) if not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " @@ -93,7 +93,7 @@ class Enumeration(GenericEnumeration): for index in range(int(count)): query = rootQuery.blind.query % (db, index, db) - table = inject.getValue(query, inband=False) + table = inject.getValue(query, inband=False, error=False) tables.append(table) kb.hintValue = table @@ -144,10 +144,10 @@ class Enumeration(GenericEnumeration): continue - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query % db query += tblQuery - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if values: if isinstance(values, basestring): @@ -165,7 +165,7 @@ class Enumeration(GenericEnumeration): query = rootQuery.blind.count2 query = query % db query += " AND %s" % tblQuery - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no table" @@ -184,7 +184,7 @@ class Enumeration(GenericEnumeration): query = query % db query += " AND %s" % tblQuery query = agent.limitQuery(index, query, tblCond) - tbl = inject.getValue(query, inband=False) + tbl = inject.getValue(query, inband=False, error=False) kb.hintValue = tbl foundTbls[db].append(tbl) @@ -229,10 +229,10 @@ class Enumeration(GenericEnumeration): continue - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query % (db, db, db, db, db) query += " AND %s" % colQuery.replace("[DB]", db) - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if values: if isinstance(values, basestring): @@ -270,7 +270,7 @@ class Enumeration(GenericEnumeration): query = rootQuery.blind.count2 query = query % (db, db, db, db, db) query += " AND %s" % colQuery.replace("[DB]", db) - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no tables contain column" @@ -289,7 +289,7 @@ class Enumeration(GenericEnumeration): query = query % (db, db, db, db, db) query += " AND %s" % colQuery.replace("[DB]", db) query = agent.limitQuery(index, query, colCond.replace("[DB]", db)) - tbl = inject.getValue(query, inband=False) + tbl = inject.getValue(query, inband=False, error=False) kb.hintValue = tbl if tbl not in dbs[db]: diff --git a/plugins/dbms/oracle/enumeration.py b/plugins/dbms/oracle/enumeration.py index baf6b16cc..cbf397fa7 100644 --- a/plugins/dbms/oracle/enumeration.py +++ b/plugins/dbms/oracle/enumeration.py @@ -41,7 +41,7 @@ class Enumeration(GenericEnumeration): # Set containing the list of DBMS administrators areAdmins = set() - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if query2: query = rootQuery.inband.query2 condition = rootQuery.inband.condition2 @@ -54,7 +54,7 @@ class Enumeration(GenericEnumeration): query += " WHERE " query += " OR ".join("%s = '%s'" % (condition, user) for user in users) - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if not values and not query2: infoMsg = "trying with table USER_ROLE_PRIVS" @@ -119,7 +119,7 @@ class Enumeration(GenericEnumeration): query = rootQuery.blind.count2 % queryUser else: query = rootQuery.blind.count % queryUser - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): if not count.isdigit() and not query2: @@ -145,7 +145,7 @@ class Enumeration(GenericEnumeration): query = rootQuery.blind.query2 % (queryUser, index) else: query = rootQuery.blind.query % (queryUser, index) - role = inject.getValue(query, inband=False) + role = inject.getValue(query, inband=False, error=False) # In Oracle we get the list of roles as string roles.add(role) @@ -201,10 +201,10 @@ class Enumeration(GenericEnumeration): colQuery = colQuery % column for db in dbs.keys(): - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query query += colQuery - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if values: if isinstance(values, basestring): @@ -241,7 +241,7 @@ class Enumeration(GenericEnumeration): query = rootQuery.blind.count2 query += " WHERE %s" % colQuery - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no tables contain column" @@ -259,7 +259,7 @@ class Enumeration(GenericEnumeration): query = rootQuery.blind.query2 query += " WHERE %s" % colQuery query = agent.limitQuery(index, query) - tbl = inject.getValue(query, inband=False) + tbl = inject.getValue(query, inband=False, error=False) kb.hintValue = tbl if tbl not in dbs[db]: diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 2c9aa3511..63ec2656e 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -149,12 +149,12 @@ class Enumeration: condition = ( getIdentifiedDBMS() == DBMS.MSSQL and kb.dbmsVersion[0] in ( "2005", "2008" ) ) condition |= ( getIdentifiedDBMS() == DBMS.MYSQL and not kb.data.has_information_schema ) - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if condition: query = rootQuery.inband.query2 else: query = rootQuery.inband.query - value = inject.getValue(query, blind=False, error=False) + value = inject.getValue(query, blind=False) if value: kb.data.cachedUsers = arrayizeValue(value) @@ -167,7 +167,7 @@ class Enumeration: query = rootQuery.blind.count2 else: query = rootQuery.blind.count - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): errMsg = "unable to retrieve the number of database users" @@ -186,7 +186,7 @@ class Enumeration: query = rootQuery.blind.query2 % index else: query = rootQuery.blind.query % index - user = inject.getValue(query, inband=False) + user = inject.getValue(query, inband=False, error=False) if user: kb.data.cachedUsers.append(user) @@ -208,7 +208,7 @@ class Enumeration: logger.info(infoMsg) - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if getIdentifiedDBMS() == DBMS.MSSQL and kb.dbmsVersion[0] in ( "2005", "2008" ): query = rootQuery.inband.query2 else: @@ -230,7 +230,7 @@ class Enumeration: query += " WHERE %s = '%s'" % (condition, conf.user) - value = inject.getValue(query, blind=False, error=False) + value = inject.getValue(query, blind=False) if value: for user, password in value: @@ -276,7 +276,7 @@ class Enumeration: query = rootQuery.blind.count2 % user else: query = rootQuery.blind.count % user - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of password " @@ -312,7 +312,7 @@ class Enumeration: query = rootQuery.blind.query % (user, index, user) else: query = rootQuery.blind.query % (user, index) - password = inject.getValue(query, inband=False) + password = inject.getValue(query, inband=False, error=False) if getIdentifiedDBMS() == DBMS.SYBASE: getCurrentThreadData().disableStdOut = False password = "0x%s" % strToHex(password) @@ -429,7 +429,7 @@ class Enumeration: "E": "EXECUTE" } - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if getIdentifiedDBMS() == DBMS.MYSQL and not kb.data.has_information_schema: query = rootQuery.inband.query2 condition = rootQuery.inband.condition2 @@ -451,7 +451,7 @@ class Enumeration: else: query += " OR ".join("%s = '%s'" % (condition, user) for user in users) - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if not values and getIdentifiedDBMS() == DBMS.ORACLE and not query2: infoMsg = "trying with table USER_SYS_PRIVS" @@ -554,7 +554,7 @@ class Enumeration: query = rootQuery.blind.count2 % queryUser else: query = rootQuery.blind.count % queryUser - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): if not (isinstance(count, basestring) and count.isdigit()) and getIdentifiedDBMS() == DBMS.ORACLE and not query2: @@ -590,7 +590,7 @@ class Enumeration: query = rootQuery.blind.query % (index, queryUser) else: query = rootQuery.blind.query % (queryUser, index) - privilege = inject.getValue(query, inband=False) + privilege = inject.getValue(query, inband=False, error=False) # In PostgreSQL we get 1 if the privilege is True, # 0 otherwise @@ -675,12 +675,12 @@ class Enumeration: rootQuery = queries[getIdentifiedDBMS()].dbs - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if getIdentifiedDBMS() == DBMS.MYSQL and not kb.data.has_information_schema: query = rootQuery.inband.query2 else: query = rootQuery.inband.query - value = inject.getValue(query, blind=False, error=False) + value = inject.getValue(query, blind=False) if value: kb.data.cachedDbs = arrayizeValue(value) @@ -693,7 +693,7 @@ class Enumeration: query = rootQuery.blind.count2 else: query = rootQuery.blind.count - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): errMsg = "unable to retrieve the number of databases" @@ -708,7 +708,7 @@ class Enumeration: query = rootQuery.blind.query2 % index else: query = rootQuery.blind.query % index - db = inject.getValue(query, inband=False) + db = inject.getValue(query, inband=False, error=False) if db: kb.data.cachedDbs.append(db) @@ -782,7 +782,7 @@ class Enumeration: else: dbs = kb.data.cachedDbs - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query condition = rootQuery.inband.condition if 'condition' in rootQuery.inband else None @@ -802,7 +802,7 @@ class Enumeration: if getIdentifiedDBMS() in (DBMS.MSSQL, DBMS.SYBASE): query = safeStringFormat(query, conf.db) - value = inject.getValue(query, blind=False, error=False) + value = inject.getValue(query, blind=False) if value: if getIdentifiedDBMS() == DBMS.SQLITE: @@ -838,7 +838,7 @@ class Enumeration: query = rootQuery.blind.count else: query = rootQuery.blind.count % db - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " @@ -863,7 +863,7 @@ class Enumeration: query = rootQuery.blind.query % index else: query = rootQuery.blind.query % (db, index) - table = inject.getValue(query, inband=False) + table = inject.getValue(query, inband=False, error=False) tables.append(table) kb.hintValue = table @@ -975,7 +975,7 @@ class Enumeration: infoMsg += "on database '%s'" % conf.db logger.info(infoMsg) - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if getIdentifiedDBMS() in ( DBMS.MYSQL, DBMS.PGSQL ): query = rootQuery.inband.query % (conf.tbl, conf.db) query += condQuery @@ -991,7 +991,7 @@ class Enumeration: elif getIdentifiedDBMS() == DBMS.SQLITE: query = rootQuery.inband.query % conf.tbl - value = inject.getValue(query, blind=False, error=False) + value = inject.getValue(query, blind=False) if getIdentifiedDBMS() == DBMS.SQLITE: parseSqliteTableSchema(value) @@ -1025,13 +1025,13 @@ class Enumeration: query += condQuery elif getIdentifiedDBMS() == DBMS.SQLITE: query = rootQuery.blind.query % conf.tbl - value = inject.getValue(query, inband=False) + value = inject.getValue(query, inband=False, error=False) parseSqliteTableSchema(value) return kb.data.cachedColumns - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): errMsg = "unable to retrieve the number of columns " @@ -1066,7 +1066,7 @@ class Enumeration: field = None query = agent.limitQuery(index, query, field) - column = inject.getValue(query, inband=False) + column = inject.getValue(query, inband=False, error=False) if not onlyColNames: if getIdentifiedDBMS() in ( DBMS.MYSQL, DBMS.PGSQL ): @@ -1080,7 +1080,7 @@ class Enumeration: elif getIdentifiedDBMS() == DBMS.FIREBIRD: query = rootQuery.blind.query2 % (conf.tbl, column) - colType = inject.getValue(query, inband=False) + colType = inject.getValue(query, inband=False, error=False) if getIdentifiedDBMS() == DBMS.FIREBIRD: colType = firebirdTypes[colType] if colType in firebirdTypes else colType @@ -1172,14 +1172,14 @@ class Enumeration: entriesCount = 0 - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if getIdentifiedDBMS() == DBMS.ORACLE: query = rootQuery.inband.query % (colString, conf.tbl.upper()) elif getIdentifiedDBMS() == DBMS.SQLITE: query = rootQuery.inband.query % (colString, conf.tbl) else: query = rootQuery.inband.query % (colString, conf.db, conf.tbl) - entries = inject.getValue(query, blind=False, error=False, dump=True) + entries = inject.getValue(query, blind=False, dump=True) if entries: if isinstance(entries, basestring): @@ -1227,7 +1227,7 @@ class Enumeration: query = rootQuery.blind.count % conf.tbl else: query = rootQuery.blind.count % (conf.db, conf.tbl) - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " @@ -1260,7 +1260,7 @@ class Enumeration: logger.info(infoMsg) query = rootQuery.blind.count2 % (column, conf.tbl) - value = inject.getValue(query, inband=False) + value = inject.getValue(query, inband=False, error=False) if isNumPosStrValue(value): validColumnList = True @@ -1307,7 +1307,7 @@ class Enumeration: else: query = rootQuery.blind.query2 % (column, conf.tbl, colList[0], pivotValue) - value = inject.getValue(query, inband=False) + value = inject.getValue(query, inband=False, error=False) if column == colList[0]: if not value: @@ -1345,7 +1345,7 @@ class Enumeration: elif getIdentifiedDBMS() == DBMS.FIREBIRD: query = rootQuery.blind.query % (index, column, conf.tbl) - value = inject.getValue(query, inband=False) + value = inject.getValue(query, inband=False, error=False) lengths[column] = max(lengths[column], len(value) if value else 0) entries[column].append(value) @@ -1514,14 +1514,14 @@ class Enumeration: dbQuery = "%s%s" % (dbCond, dbCondParam) dbQuery = dbQuery % db - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if getIdentifiedDBMS() == DBMS.MYSQL and not kb.data.has_information_schema: query = rootQuery.inband.query2 else: query = rootQuery.inband.query query += dbQuery query += exclDbsQuery - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if values: if isinstance(values, basestring): @@ -1542,7 +1542,7 @@ class Enumeration: query = rootQuery.blind.count query += dbQuery query += exclDbsQuery - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no database" @@ -1564,7 +1564,7 @@ class Enumeration: query += exclDbsQuery query = agent.limitQuery(index, query, dbCond) - foundDbs.append(inject.getValue(query, inband=False)) + foundDbs.append(inject.getValue(query, inband=False, error=False)) return foundDbs @@ -1622,11 +1622,11 @@ class Enumeration: tblQuery = "%s%s" % (tblCond, tblCondParam) tblQuery = tblQuery % tbl - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query query += tblQuery query += exclDbsQuery - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if values: if isinstance(values, basestring): @@ -1647,7 +1647,7 @@ class Enumeration: query = rootQuery.blind.count query += tblQuery query += exclDbsQuery - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no databases have table" @@ -1665,7 +1665,7 @@ class Enumeration: query += tblQuery query += exclDbsQuery query = agent.limitQuery(index, query) - foundDb = inject.getValue(query, inband=False) + foundDb = inject.getValue(query, inband=False, error=False) if foundDb not in foundTbls: foundTbls[foundDb] = [] @@ -1685,7 +1685,7 @@ class Enumeration: query = rootQuery.blind.count2 query = query % db query += " AND %s" % tblQuery - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no table" @@ -1704,7 +1704,7 @@ class Enumeration: query = query % db query += " AND %s" % tblQuery query = agent.limitQuery(index, query) - foundTbl = inject.getValue(query, inband=False) + foundTbl = inject.getValue(query, inband=False, error=False) kb.hintValue = foundTbl foundTbls[db].append(foundTbl) @@ -1772,11 +1772,11 @@ class Enumeration: colQuery = "%s%s" % (colCond, colCondParam) colQuery = colQuery % column - if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or conf.direct: + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query query += colQuery query += exclDbsQuery - values = inject.getValue(query, blind=False, error=False) + values = inject.getValue(query, blind=False) if values: if isinstance(values, basestring): @@ -1815,7 +1815,7 @@ class Enumeration: query = rootQuery.blind.count query += colQuery query += exclDbsQuery - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no databases have tables containing column" @@ -1833,7 +1833,7 @@ class Enumeration: query += colQuery query += exclDbsQuery query = agent.limitQuery(index, query) - db = inject.getValue(query, inband=False) + db = inject.getValue(query, inband=False, error=False) if db not in dbs: dbs[db] = {} @@ -1855,7 +1855,7 @@ class Enumeration: query = rootQuery.blind.count2 query = query % db query += " AND %s" % colQuery - count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no tables contain column" @@ -1874,7 +1874,7 @@ class Enumeration: query = query % db query += " AND %s" % colQuery query = agent.limitQuery(index, query) - tbl = inject.getValue(query, inband=False) + tbl = inject.getValue(query, inband=False, error=False) kb.hintValue = tbl if tbl not in dbs[db]: