From a5968fff3e3067e1f1c283319b6bc7caa4650f66 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Sat, 30 Apr 2011 00:22:22 +0000 Subject: [PATCH] Added --count switch to count the number of entries for a specific table (when -T is provided), all database's tables (when only -D is provided) or all databases' tables when neither -D nor -T are provided --- lib/controller/action.py | 3 ++ lib/core/common.py | 3 ++ lib/core/dump.py | 46 +++++++++++++++++++++++++++-- lib/core/optiondict.py | 1 + lib/parse/cmdline.py | 3 ++ plugins/generic/enumeration.py | 54 +++++++++++++++++++++++++++++++++- sqlmap.conf | 4 +++ 7 files changed, 111 insertions(+), 3 deletions(-) diff --git a/lib/controller/action.py b/lib/controller/action.py index 3f31d93a2..47ee79c8b 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -99,6 +99,9 @@ def action(): if conf.getColumns: conf.dumper.dbTableColumns(conf.dbmsHandler.getColumns()) + if conf.getCount: + conf.dumper.dbTablesCount(conf.dbmsHandler.getCount()) + if conf.commonColumns: conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS)) diff --git a/lib/core/common.py b/lib/core/common.py index 937576031..8dc218987 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2527,17 +2527,20 @@ def safeSQLIdentificatorNaming(name, isTable=False): """ retVal = name + if isinstance(name, basestring): if isTable and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and '.' not in name: name = "%s.%s" % (DEFAULT_MSSQL_SCHEMA, name) parts = name.split('.') + for i in range(len(parts)): if not re.match(r"\A[A-Za-z0-9_]+\Z", parts[i]): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS): parts[i] = "`%s`" % parts[i].strip("`") elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE, DBMS.PGSQL): parts[i] = "\"%s\"" % parts[i].strip("\"") + retVal = ".".join(parts) return retVal diff --git a/lib/core/dump.py b/lib/core/dump.py index a49fdf84e..366997000 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -211,11 +211,11 @@ class Dump: maxlength2 = max(maxlength2, len(colType)) maxlength1 = max(maxlength1, len("COLUMN")) - lines1 = "-" * (int(maxlength1) + 2) + lines1 = "-" * (maxlength1 + 2) if colType is not None: maxlength2 = max(maxlength2, len("TYPE")) - lines2 = "-" * (int(maxlength2) + 2) + lines2 = "-" * (maxlength2 + 2) self.__write("Database: %s\nTable: %s" % (db, table)) @@ -256,6 +256,48 @@ class Dump: else: self.__write("+%s+\n" % lines1) + def dbTablesCount(self, dbTables): + if isinstance(dbTables, dict) and len(dbTables) > 0: + maxlength1 = len("Table") + maxlength2 = len("Entries") + + for ctables in dbTables.values(): + for tables in ctables.values(): + for table in tables: + maxlength1 = max(maxlength1, len(normalizeUnicode(table) or str(table))) + + for db, counts in dbTables.items(): + self.__write("Database: %s" % db) + + lines1 = "-" * (maxlength1 + 2) + blank1 = " " * (maxlength1 - len("Table")) + lines2 = "-" * (maxlength2 + 2) + blank2 = " " * (maxlength2 - len("Entries")) + + self.__write("+%s+%s+" % (lines1, lines2)) + self.__write("| Table%s | Entries%s |" % (blank1, blank2)) + self.__write("+%s+%s+" % (lines1, lines2)) + + sortedCounts = counts.keys() + sortedCounts.sort(reverse=True) + + for count in sortedCounts: + tables = counts[count] + + if count is None: + count = "Unknown" + + tables.sort(key=lambda x: x.lower() if isinstance(x, basestring) else x) + + for table in tables: + blank1 = " " * (maxlength1 - len(normalizeUnicode(table) or str(table))) + blank2 = " " * (maxlength2 - len(str(count))) + self.__write("| %s%s | %d%s |" % (table, blank1, count, blank2)) + + self.__write("+%s+%s+\n" % (lines1, lines2)) + else: + logger.error("unable to retrieve the number of entries for any table") + def dbTableValues(self, tableValues): replication = None rtable = None diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 0191d5ea1..e97d64dfd 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -93,6 +93,7 @@ optDict = { "getTables": ("boolean", "Tables"), "getColumns": ("boolean", "Columns"), "getSchema": "boolean", + "getCount": "boolean", "dumpTable": "boolean", "dumpAll": "boolean", "search": "boolean", diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index fbbcf141e..2a41e117e 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -279,6 +279,9 @@ def cmdLineParser(): enumeration.add_option("--schema", dest="getSchema", action="store_true", default=False, help="Enumerate DBMS schema") + enumeration.add_option("--count", dest="getCount", action="store_true", + default=False, help="Retrieve number of entries for table(s)") + enumeration.add_option("--dump", dest="dumpTable", action="store_true", default=False, help="Dump DBMS database table entries") diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 315bb8953..adb44e3e3 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -85,6 +85,7 @@ class Enumeration: kb.data.cachedDbs = [] kb.data.cachedTables = {} kb.data.cachedColumns = {} + kb.data.cachedCounts = {} kb.data.dumpedTable = {} kb.data.processChar = None self.alwaysRetrieveSqlOutput = False @@ -839,6 +840,7 @@ class Enumeration: for db, table in value: db = safeSQLIdentificatorNaming(db) table = safeSQLIdentificatorNaming(table, True) + if not kb.data.cachedTables.has_key(db): kb.data.cachedTables[db] = [table] else: @@ -885,6 +887,7 @@ class Enumeration: query = rootQuery.blind.query % index else: query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(db), index) + table = inject.getValue(query, inband=False, error=False) kb.hintValue = table table = safeSQLIdentificatorNaming(table, True) @@ -1174,6 +1177,56 @@ class Enumeration: return kb.data.cachedColumns + def __tableGetCount(self, db, table): + query = "SELECT COUNT(*) FROM %s.%s" % (safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True)) + count = inject.getValue(query, expected=EXPECTED.INT, charsetType=2) + + if count is not None and isinstance(count, basestring) and count.isdigit(): + if unsafeSQLIdentificatorNaming(db) not in kb.data.cachedCounts: + kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)] = {} + + if int(count) in kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)]: + kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)][int(count)].append(unsafeSQLIdentificatorNaming(table)) + else: + kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)][int(count)] = [unsafeSQLIdentificatorNaming(table)] + + def getCount(self): + if not conf.tbl: + warnMsg = "missing table parameter, sqlmap will retrieve " + warnMsg += "the number of entries for all database " + warnMsg += "management system databases' tables" + logger.warn(warnMsg) + + elif "." in conf.tbl: + if not conf.db: + conf.db, conf.tbl = conf.tbl.split(".") + + if conf.tbl is not None and conf.db is None: + warnMsg = "missing database parameter, sqlmap is going to " + warnMsg += "use the current database to retrieve the " + warnMsg += "number of entries for table '%s'" % conf.tbl + logger.warn(warnMsg) + + conf.db = self.getCurrentDb() + + self.forceDbmsEnum() + + if conf.db: + conf.db = safeSQLIdentificatorNaming(conf.db) + + if conf.tbl: + for table in conf.tbl.split(","): + table = safeSQLIdentificatorNaming(table, True) + self.__tableGetCount(conf.db, table) + else: + self.getTables() + + for db, tables in kb.data.cachedTables.items(): + for table in tables: + self.__tableGetCount(db, table) + + return kb.data.cachedCounts + def __pivotDumpTable(self, table, colList, count=None, blind=True): lengths = {} entries = {} @@ -1580,7 +1633,6 @@ class Enumeration: infoMsg = "skipping table '%s'" % table logger.info(infoMsg) - def dumpFoundColumn(self, dbs, foundCols, colConsider): if not dbs: warnMsg = "no databases have tables containing any of the " diff --git a/sqlmap.conf b/sqlmap.conf index 2e0125e2d..402cf75b1 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -314,6 +314,10 @@ getColumns = False # Valid: True or False getSchema = False +# Retrieve number of entries for table(s). +# Valid: True or False +getCount = False + # Dump back-end database management system database table entries. # Requires: tbl and/or col # Optional: db