From 9fb0e0fc85de0d74154f6a5131228ad37f45c5af Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 27 Dec 2010 14:17:20 +0000 Subject: [PATCH] resume of brute forced data is now available --- lib/core/option.py | 4 +++ lib/core/session.py | 30 ++++++++++++++++++ lib/request/inject.py | 3 +- lib/techniques/brute/use.py | 18 +++++++++-- plugins/generic/enumeration.py | 57 +++++++++++++++++++++++++++++----- 5 files changed, 101 insertions(+), 11 deletions(-) diff --git a/lib/core/option.py b/lib/core/option.py index 7bcef5f99..c953f824c 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1128,6 +1128,10 @@ def __setKnowledgeBaseAttributes(flushAll=True): kb.authHeader = None kb.bannerFp = advancedDict() + kb.brute = advancedDict() + kb.brute.tables = [] + kb.brute.columns = [] + kb.cache = advancedDict() kb.cache.content = {} kb.cache.regex = {} diff --git a/lib/core/session.py b/lib/core/session.py index 784edd096..288483c43 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -21,6 +21,7 @@ from lib.core.data import logger from lib.core.datatype import injectionDict from lib.core.enums import PAYLOAD from lib.core.enums import PLACE +from lib.core.settings import METADB_SUFFIX from lib.core.settings import MSSQL_ALIASES from lib.core.settings import MYSQL_ALIASES from lib.core.settings import PGSQL_ALIASES @@ -357,6 +358,35 @@ def resumeConfKb(expression, url, value): else: conf.os = os + elif expression == "TABLE_EXISTS" and url == conf.url: + table = unSafeFormatString(value[:-1]) + + if '.' in table: + db, table = table.split('.') + else: + db = "%s%s" % (kb.dbms, METADB_SUFFIX) + + logMsg = "resuming brute forced table name " + logMsg += "'%s' from session file" % table + logger.info(logMsg) + + kb.brute.tables.append((db, table)) + + elif expression == "COLUMN_EXISTS" and url == conf.url: + table, column = unSafeFormatString(value[:-1]).split('..') + colName, colType = column.split(' ') + + if '.' in table: + db, table = table.split('.') + else: + db = "%s%s" % (kb.dbms, METADB_SUFFIX) + + logMsg = "resuming brute forced column name " + logMsg += "'%s' for table '%s' from session file" % (colName, table) + logger.info(logMsg) + + kb.brute.columns.append((db, table, colName, colType)) + elif expression == "Union comment" and url == conf.url: kb.unionComment = unSafeFormatString(value[:-1]) diff --git a/lib/request/inject.py b/lib/request/inject.py index ff16f10a9..827770601 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -403,7 +403,8 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse query = expandAsteriskForColumns(query) value = None found = False - query = query.replace("DISTINCT ", "") + if query and not 'COUNT(*)' in query: + query = query.replace("DISTINCT ", "") count = 0 if expected == EXPECTED.BOOL: diff --git a/lib/techniques/brute/use.py b/lib/techniques/brute/use.py index 0bdbbc889..5df783f88 100644 --- a/lib/techniques/brute/use.py +++ b/lib/techniques/brute/use.py @@ -11,6 +11,7 @@ import threading import time from lib.core.common import clearConsoleLine +from lib.core.common import dataToSessionFile from lib.core.common import dataToStdout from lib.core.common import filterListValue from lib.core.common import getFileItems @@ -26,6 +27,7 @@ from lib.core.enums import DBMS from lib.core.exception import sqlmapMissingMandatoryOptionException from lib.core.exception import sqlmapThreadException from lib.core.settings import METADB_SUFFIX +from lib.core.session import safeFormatString from lib.request import inject def tableExists(tableFile, regex=None): @@ -59,13 +61,19 @@ def tableExists(tableFile, regex=None): tbllock.release() if conf.db and not conf.db.endswith(METADB_SUFFIX): - table = "%s.%s" % (conf.db, table) - result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %d FROM %s)", (randomInt(1), table))) + fullTableName = "%s.%s" % (conf.db, table) + else: + fullTableName = table + result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %d FROM %s)", (randomInt(1), fullTableName))) iolock.acquire() if result: retVal.append(table) + dataToSessionFile("[%s][%s][%s][TABLE_EXISTS][%s]\n" % (conf.url,\ + kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]),\ + safeFormatString(fullTableName))) + if conf.verbose in (1, 2): clearConsoleLine(True) infoMsg = "\r[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), table) @@ -227,13 +235,17 @@ def columnExists(columnFile, regex=None): columns = {} for column in retVal: - result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE RND(%s)>0)", (column, table, column))) + result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)>0)", (column, table, column))) if result: columns[column] = 'numeric' else: columns[column] = 'non-numeric' + dataToSessionFile("[%s][%s][%s][COLUMN_EXISTS][%s..%s %s]\n" % (conf.url, kb.injection.place,\ + safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(table),\ + safeFormatString(column), safeFormatString(columns[column]))) + kb.data.cachedColumns[conf.db] = {conf.tbl: columns} return kb.data.cachedColumns diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 69cf35cbe..61010ff76 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -725,6 +725,8 @@ class Enumeration: def getTables(self): bruteForce = False + self.forceDbmsEnum() + if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" @@ -738,6 +740,22 @@ class Enumeration: bruteForce = True if bruteForce: + resumeAvailable = False + for db, table in kb.brute.tables: + if db == conf.db: + resumeAvailable = True + break + + if resumeAvailable: + for db, table in kb.brute.tables: + if db == conf.db: + if not kb.data.cachedTables.has_key(conf.db): + kb.data.cachedTables[conf.db] = [table] + else: + kb.data.cachedTables[conf.db].append(table) + + return kb.data.cachedTables + message = "do you want to use common table existance check? [Y/n/q]" test = readInput(message, default="Y") @@ -748,8 +766,6 @@ class Enumeration: else: return tableExists(paths.COMMON_TABLES) - self.forceDbmsEnum() - infoMsg = "fetching tables" if conf.db: infoMsg += " for database '%s'" % conf.db @@ -869,6 +885,11 @@ class Enumeration: def getColumns(self, onlyColNames=False): bruteForce = False + if "." in conf.tbl: + conf.db, conf.tbl = conf.tbl.split(".") + + self.forceDbmsEnum() + if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" @@ -882,6 +903,21 @@ class Enumeration: bruteForce = True if bruteForce: + resumeAvailable = False + for db, table, colName, colType in kb.brute.columns: + if db == conf.db and table == conf.tbl: + resumeAvailable = True + break + + if resumeAvailable: + columns = {} + for db, table, colName, colType in kb.brute.columns: + if db == conf.db and table == conf.tbl: + columns[colName] = colType + + kb.data.cachedColumns[conf.db] = {conf.tbl: columns} + return kb.data.cachedColumns + message = "do you want to use common columns existance check? [Y/n/q]" test = readInput(message, default="Y") @@ -896,11 +932,6 @@ class Enumeration: errMsg = "missing table parameter" raise sqlmapMissingMandatoryOptionException, errMsg - if "." in conf.tbl: - conf.db, conf.tbl = conf.tbl.split(".") - - self.forceDbmsEnum() - if not conf.db: warnMsg = "missing database parameter, sqlmap is going to " warnMsg += "use the current database to enumerate table " @@ -1219,6 +1250,7 @@ class Enumeration: if kb.dbms == DBMS.ACCESS: validColumnList = False + validPivotValue = False for column in colList: infoMsg = "fetching number of distinct " @@ -1235,6 +1267,8 @@ class Enumeration: infoMsg += "for retrieving row data" logger.info(infoMsg) + validPivotValue = True + colList.remove(column) colList.insert(0, column) break @@ -1243,8 +1277,16 @@ class Enumeration: errMsg = "all column name(s) provided are non-existent" raise sqlmapNoneDataException, errMsg + if not validPivotValue: + warnMsg = "no proper pivot column provided (with unique values)." + warnMsg += " all row data won't be retrieved." + logger.warn(warnMsg) + pivotValue = " " + breakRetrieval = False for index in indexRange: + if breakRetrieval: + break for column in colList: if column not in lengths: lengths[column] = 0 @@ -1264,6 +1306,7 @@ class Enumeration: value = inject.getValue(query, inband=False) if column == colList[0]: if not value: + breakRetrieval = True break else: pivotValue = value