diff --git a/plugins/generic/custom.py b/plugins/generic/custom.py new file mode 100644 index 000000000..411af608d --- /dev/null +++ b/plugins/generic/custom.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +import re + +from lib.core.common import Backend +from lib.core.common import dataToStdout +from lib.core.common import getSQLSnippet +from lib.core.common import isTechniqueAvailable +from lib.core.convert import utf8decode +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.enums import PAYLOAD +from lib.core.settings import PARAMETER_SPLITTING_REGEX +from lib.core.settings import SQL_STATEMENTS +from lib.core.shell import autoCompletion +from lib.request import inject + +class Custom: + """ + This class defines custom enumeration functionalities for plugins. + """ + + def __init__(self): + pass + + def sqlQuery(self, query): + output = None + sqlType = None + query = query.rstrip(';') + kb.unescape = False + + for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): + for sqlStatement in sqlStatements: + if query.lower().startswith(sqlStatement): + sqlType = sqlTitle + break + + if 'OPENROWSET' not in query.upper() and (not sqlType or 'SELECT' in sqlType): + infoMsg = "fetching %s query output: '%s'" % (sqlType if sqlType is not None else "SQL", query) + logger.info(infoMsg) + + output = inject.getValue(query, fromUser=True) + kb.unescape = True + + return output + elif not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct: + warnMsg = "execution of custom SQL queries is only " + warnMsg += "available when stacked queries are supported" + logger.warn(warnMsg) + + kb.unescape = True + + return None + else: + if sqlType: + debugMsg = "executing %s query: '%s'" % (sqlType if sqlType is not None else "SQL", query) + else: + debugMsg = "executing unknown SQL type query: '%s'" % query + logger.debug(debugMsg) + + inject.goStacked(query) + + debugMsg = "done" + logger.debug(debugMsg) + + output = False + + kb.unescape = True + + return output + + def sqlShell(self): + infoMsg = "calling %s shell. To quit type " % Backend.getIdentifiedDbms() + infoMsg += "'x' or 'q' and press ENTER" + logger.info(infoMsg) + + autoCompletion(sqlShell=True) + + while True: + query = None + + try: + query = raw_input("sql-shell> ") + query = utf8decode(query) + except KeyboardInterrupt: + print + errMsg = "user aborted" + logger.error(errMsg) + except EOFError: + print + errMsg = "exit" + logger.error(errMsg) + break + + if not query: + continue + + if query.lower() in ("x", "q", "exit", "quit"): + break + + output = self.sqlQuery(query) + + if output and output != "Quit": + conf.dumper.query(query, output) + + elif not output: + pass + + elif output != "Quit": + dataToStdout("No output\n") + + def sqlFile(self): + infoMsg = "executing SQL statements from given file(s)" + logger.info(infoMsg) + + for sfile in re.split(PARAMETER_SPLITTING_REGEX, conf.sqlFile): + sfile = sfile.strip() + + if not sfile: + continue + + query = getSQLSnippet(Backend.getDbms(), sfile) + + infoMsg = "executing SQL statement%s from file '%s'" % ("s" if ";" in query else "", sfile) + logger.info(infoMsg) + + conf.dumper.query(query, self.sqlQuery(query)) diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py new file mode 100644 index 000000000..2314d9c68 --- /dev/null +++ b/plugins/generic/databases.py @@ -0,0 +1,755 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +from lib.core.agent import agent +from lib.core.common import arrayizeValue +from lib.core.common import Backend +from lib.core.common import filterPairValues +from lib.core.common import getLimitRange +from lib.core.common import isInferenceAvailable +from lib.core.common import isListLike +from lib.core.common import isNoneValue +from lib.core.common import isNumPosStrValue +from lib.core.common import isTechniqueAvailable +from lib.core.common import parseSqliteTableSchema +from lib.core.common import popValue +from lib.core.common import pushValue +from lib.core.common import readInput +from lib.core.common import safeSQLIdentificatorNaming +from lib.core.common import unArrayizeValue +from lib.core.common import unsafeSQLIdentificatorNaming +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.data import queries +from lib.core.dicts import firebirdTypes +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import DBMS +from lib.core.enums import EXPECTED +from lib.core.enums import PAYLOAD +from lib.core.exception import sqlmapMissingMandatoryOptionException +from lib.core.exception import sqlmapNoneDataException +from lib.core.exception import sqlmapUserQuitException +from lib.core.settings import CURRENT_DB +from lib.request import inject +from lib.techniques.brute.use import columnExists +from lib.techniques.brute.use import tableExists + +class Databases: + """ + This class defines databases' enumeration functionalities for plugins. + """ + + def __init__(self): + kb.data.currentDb = "" + kb.data.cachedDbs = [] + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + kb.data.cachedCounts = {} + kb.data.dumpedTable = {} + + def getCurrentDb(self): + infoMsg = "fetching current database" + logger.info(infoMsg) + + query = queries[Backend.getIdentifiedDbms()].current_db.query + + if not kb.data.currentDb: + kb.data.currentDb = unArrayizeValue(inject.getValue(query, safeCharEncode=False)) + + return kb.data.currentDb + + def getDbs(self): + if len(kb.data.cachedDbs) > 0: + return kb.data.cachedDbs + + infoMsg = None + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + warnMsg = "information_schema not available, " + warnMsg += "back-end DBMS is MySQL < 5. database " + warnMsg += "names will be fetched from 'mysql' database" + logger.warn(warnMsg) + + elif Backend.isDbms(DBMS.ORACLE): + warnMsg = "schema names are going to be used on Oracle " + warnMsg += "for enumeration as the counterpart to database " + warnMsg += "names on other DBMSes" + logger.warn(warnMsg) + + infoMsg = "fetching database (schema) names" + elif Backend.isDbms(DBMS.DB2): + warnMsg = "schema names are going to be used on IBM DB2 " + warnMsg += "for enumeration as the counterpart to database " + warnMsg += "names on other DBMSes" + logger.warn(warnMsg) + + infoMsg = "fetching database (schema) names" + else: + infoMsg = "fetching database names" + + if infoMsg: + logger.info(infoMsg) + + rootQuery = queries[Backend.getIdentifiedDbms()].dbs + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.inband.query2 + else: + query = rootQuery.inband.query + value = inject.getValue(query, blind=False) + + if not isNoneValue(value): + kb.data.cachedDbs = arrayizeValue(value) + + if not kb.data.cachedDbs and isInferenceAvailable() and not conf.direct: + infoMsg = "fetching number of databases" + logger.info(infoMsg) + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.blind.count2 + else: + query = rootQuery.blind.count + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + errMsg = "unable to retrieve the number of databases" + logger.error(errMsg) + else: + plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) + indexRange = getLimitRange(count, plusOne=plusOne) + + for index in indexRange: + if Backend.isDbms(DBMS.SYBASE): + query = rootQuery.blind.query % (kb.data.cachedDbs[-1] if kb.data.cachedDbs else " ") + elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.blind.query2 % index + else: + query = rootQuery.blind.query % index + db = inject.getValue(query, inband=False, error=False) + + if db: + kb.data.cachedDbs.append(safeSQLIdentificatorNaming(db)) + + if not kb.data.cachedDbs and Backend.isDbms(DBMS.MSSQL): + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + blinds = (False, True) + else: + blinds = (True,) + + for blind in blinds: + count = 0 + kb.data.cachedDbs = [] + while True: + query = rootQuery.inband.query2 % count + value = inject.getValue(query, blind=blind) + if not value: + break + else: + kb.data.cachedDbs.append(unArrayizeValue(value)) + count += 1 + if kb.data.cachedDbs: + break + + if not kb.data.cachedDbs: + infoMsg = "falling back to current database" + logger.info(infoMsg) + self.getCurrentDb() + + if kb.data.currentDb: + kb.data.cachedDbs = [kb.data.currentDb] + else: + errMsg = "unable to retrieve the database names" + raise sqlmapNoneDataException, errMsg + else: + kb.data.cachedDbs.sort() + + return kb.data.cachedDbs + + def getTables(self, bruteForce=None): + if len(kb.data.cachedTables) > 0: + return kb.data.cachedTables + + self.forceDbmsEnum() + + if bruteForce is None: + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + logger.error(errMsg) + bruteForce = True + + elif Backend.isDbms(DBMS.ACCESS): + try: + tables = self.getTables(False) + except sqlmapNoneDataException: + tables = None + + if not tables: + errMsg = "cannot retrieve table names, " + errMsg += "back-end DBMS is Access" + logger.error(errMsg) + bruteForce = True + else: + return tables + + if conf.db == CURRENT_DB: + conf.db = self.getCurrentDb() + + if conf.db and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + conf.db = conf.db.upper() + + if conf.db: + dbs = conf.db.split(",") + else: + dbs = self.getDbs() + + for db in dbs: + dbs[dbs.index(db)] = safeSQLIdentificatorNaming(db) + + dbs = filter(None, dbs) + + 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 conf.db not in kb.data.cachedTables: + 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 existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") + test = readInput(message, default="Y" if "Y" in message else "N") + + if test[0] in ("n", "N"): + return + elif test[0] in ("q", "Q"): + raise sqlmapUserQuitException + else: + return tableExists(paths.COMMON_TABLES) + + infoMsg = "fetching tables for database" + infoMsg += "%s: '%s'" % ("s" if len(dbs) > 1 else "", ", ".join(db if isinstance(db, basestring) else db[0] for db in sorted(dbs))) + logger.info(infoMsg) + + rootQuery = queries[Backend.getIdentifiedDbms()].tables + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + query = rootQuery.inband.query + condition = rootQuery.inband.condition if 'condition' in rootQuery.inband else None + + if condition: + if conf.excludeSysDbs: + query += " WHERE " + query += " AND ".join("%s != '%s'" % (condition, unsafeSQLIdentificatorNaming(db)) for db in self.excludeDbsList) + infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) + logger.info(infoMsg) + elif not Backend.isDbms(DBMS.SQLITE): + query += " WHERE " + query += " OR ".join("%s = '%s'" % (condition, unsafeSQLIdentificatorNaming(db)) for db in sorted(dbs)) + + if len(dbs) < 2 and ("%s," % condition) in query: + query = query.replace("%s," % condition, "", 1) + + value = inject.getValue(query, blind=False) + + if not isNoneValue(value): + value = filter(None, arrayizeValue(value)) + + if len(value) > 0 and not isListLike(value[0]): + value = map(lambda x: (dbs[0], x), value) + + for db, table in filterPairValues(value): + db = safeSQLIdentificatorNaming(db) + table = safeSQLIdentificatorNaming(table, True) + + if db not in kb.data.cachedTables: + kb.data.cachedTables[db] = [table] + else: + kb.data.cachedTables[db].append(table) + + if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct: + for db in dbs: + if conf.excludeSysDbs and db in self.excludeDbsList: + infoMsg = "skipping system database '%s'" % db + logger.info(infoMsg) + + continue + + infoMsg = "fetching number of tables for " + infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(db) + logger.info(infoMsg) + + if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.ACCESS): + query = rootQuery.blind.count + else: + query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(db) + + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + warnMsg = "unable to retrieve the number of " + warnMsg += "tables for database '%s'" % unsafeSQLIdentificatorNaming(db) + logger.warn(warnMsg) + continue + + tables = [] + + plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) + indexRange = getLimitRange(count, plusOne=plusOne) + + for index in indexRange: + if Backend.isDbms(DBMS.SYBASE): + query = rootQuery.blind.query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")) + elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS): + query = rootQuery.blind.query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ") + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): + query = rootQuery.blind.query % index + else: + query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(db), index) + + table = inject.getValue(query, inband=False, error=False) + if not isNoneValue(table): + kb.hintValue = table + table = safeSQLIdentificatorNaming(table, True) + tables.append(table) + + if tables: + kb.data.cachedTables[db] = tables + else: + warnMsg = "unable to retrieve the table names " + warnMsg += "for database '%s'" % unsafeSQLIdentificatorNaming(db) + logger.warn(warnMsg) + + if isNoneValue(kb.data.cachedTables): + kb.data.cachedTables.clear() + + if not kb.data.cachedTables: + errMsg = "unable to retrieve the table names for any database" + if bruteForce is None: + logger.error(errMsg) + return self.getTables(bruteForce=True) + else: + raise sqlmapNoneDataException, errMsg + else: + for db, tables in kb.data.cachedTables.items(): + kb.data.cachedTables[db] = sorted(tables) if tables else tables + + return kb.data.cachedTables + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None): + self.forceDbmsEnum() + + if conf.db is None or conf.db == CURRENT_DB: + if conf.db is None: + warnMsg = "missing database parameter, sqlmap is going " + warnMsg += "to use the current database to enumerate " + warnMsg += "table(s) columns" + logger.warn(warnMsg) + + conf.db = self.getCurrentDb() + + elif conf.db is not None: + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + conf.db = conf.db.upper() + + if ',' in conf.db: + errMsg = "only one database name is allowed when enumerating " + errMsg += "the tables' columns" + raise sqlmapMissingMandatoryOptionException, errMsg + + conf.db = safeSQLIdentificatorNaming(conf.db) + + if conf.col: + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + conf.col = conf.col.upper() + + colList = conf.col.split(",") + else: + colList = [] + + for col in colList: + colList[colList.index(col)] = safeSQLIdentificatorNaming(col) + + colList = filter(None, colList) + + if conf.tbl: + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + conf.tbl = conf.tbl.upper() + + tblList = conf.tbl.split(",") + else: + self.getTables() + + if len(kb.data.cachedTables) > 0: + if conf.db in kb.data.cachedTables: + tblList = kb.data.cachedTables[conf.db] + else: + tblList = kb.data.cachedTables.values() + + if isinstance(tblList[0], (set, tuple, list)): + tblList = tblList[0] + + tblList = list(tblList) + else: + errMsg = "unable to retrieve the tables " + errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + raise sqlmapNoneDataException, errMsg + + for tbl in tblList: + tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) + + if bruteForce is None: + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + logger.error(errMsg) + bruteForce = True + + elif Backend.isDbms(DBMS.ACCESS): + errMsg = "cannot retrieve column names, " + errMsg += "back-end DBMS is Access" + logger.error(errMsg) + bruteForce = True + + if bruteForce or colList: + resumeAvailable = False + + for tbl in tblList: + for db, table, colName, colType in kb.brute.columns: + if db == conf.db and table == tbl: + resumeAvailable = True + break + + if resumeAvailable or colList: + columns = {} + + for column in colList: + columns[column] = None + + for tbl in tblList: + for db, table, colName, colType in kb.brute.columns: + if db == conf.db and table == tbl: + columns[colName] = colType + + if conf.db in kb.data.cachedColumns: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns + else: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns} + + return kb.data.cachedColumns + + message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") + test = readInput(message, default="Y" if "Y" in message else "N") + + if test[0] in ("n", "N"): + return + elif test[0] in ("q", "Q"): + raise sqlmapUserQuitException + else: + return columnExists(paths.COMMON_COLUMNS) + + rootQuery = queries[Backend.getIdentifiedDbms()].columns + condition = rootQuery.blind.condition if 'condition' in rootQuery.blind else None + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + for tbl in tblList: + if conf.db is not None and len(kb.data.cachedColumns) > 0 \ + and conf.db in kb.data.cachedColumns and tbl in \ + kb.data.cachedColumns[conf.db]: + infoMsg = "fetched tables' columns on " + infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + return {conf.db: kb.data.cachedColumns[conf.db]} + + infoMsg = "fetching columns " + + if len(colList) > 0: + if colTuple is None: + colConsider, colCondParam = self.likeOrExact("column") + else: + colConsider, colCondParam = colTuple + condQueryStr = "%%s%s" % colCondParam + condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) + + if colConsider == "1": + infoMsg += "like '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + else: + infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + else: + condQuery = "" + + infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + query += condQuery + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + query = rootQuery.inband.query % unsafeSQLIdentificatorNaming(tbl.upper()) + query += condQuery + elif Backend.isDbms(DBMS.MSSQL): + query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, + conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) + query += condQuery.replace("[DB]", conf.db) + elif Backend.isDbms(DBMS.SQLITE): + query = rootQuery.inband.query % tbl + + value = inject.getValue(query, blind=False) + + if Backend.isDbms(DBMS.SQLITE): + parseSqliteTableSchema(value) + elif not isNoneValue(value): + table = {} + columns = {} + + for columnData in value: + if not isNoneValue(columnData): + name = safeSQLIdentificatorNaming(columnData[0]) + + if name: + if len(columnData) == 1: + columns[name] = "" + else: + columns[name] = columnData[1] + + if conf.db in kb.data.cachedColumns: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns + else: + table[safeSQLIdentificatorNaming(tbl, True)] = columns + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table + + elif isInferenceAvailable() and not conf.direct: + for tbl in tblList: + if conf.db is not None and len(kb.data.cachedColumns) > 0 \ + and conf.db in kb.data.cachedColumns and tbl in \ + kb.data.cachedColumns[conf.db]: + infoMsg = "fetched tables' columns on " + infoMsg += "database '%s'" % conf.db + logger.info(infoMsg) + + return {conf.db: kb.data.cachedColumns[conf.db]} + + infoMsg = "fetching columns " + + if len(colList) > 0: + if colTuple is None: + colConsider, colCondParam = self.likeOrExact("column") + else: + colConsider, colCondParam = colTuple + condQueryStr = "%%s%s" % colCondParam + condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) + + if colConsider == "1": + infoMsg += "like '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + else: + infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + else: + condQuery = "" + + infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + query += condQuery + + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(tbl.upper()) + query += condQuery + + elif Backend.isDbms(DBMS.MSSQL): + query = rootQuery.blind.count % (conf.db, conf.db, \ + unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) + query += condQuery.replace("[DB]", conf.db) + + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.count % (tbl) + query += condQuery + + elif Backend.isDbms(DBMS.SQLITE): + query = rootQuery.blind.query % tbl + value = inject.getValue(query, inband=False, error=False) + parseSqliteTableSchema(value) + return kb.data.cachedColumns + + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + errMsg = "unable to retrieve the number of columns " + errMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.error(errMsg) + + continue + + table = {} + columns = {} + + indexRange = getLimitRange(count) + + for index in indexRange: + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + query += condQuery + field = None + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl.upper()) + query += condQuery + field = None + elif Backend.isDbms(DBMS.MSSQL): + query = rootQuery.blind.query % (conf.db, conf.db, conf.db, conf.db, + conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) + query += condQuery.replace("[DB]", conf.db) + field = condition.replace("[DB]", conf.db) + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.query % (tbl) + query += condQuery + field = None + + query = agent.limitQuery(index, query, field) + column = inject.getValue(query, inband=False, error=False) + + if not isNoneValue(column): + if not onlyColNames: + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column) + elif Backend.isDbms(DBMS.MSSQL): + query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, column, conf.db, + conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.query2 % (tbl, column) + + colType = inject.getValue(query, inband=False, error=False) + + if Backend.isDbms(DBMS.FIREBIRD): + colType = firebirdTypes.get(colType, colType) + + column = safeSQLIdentificatorNaming(column) + columns[column] = colType + else: + column = safeSQLIdentificatorNaming(column) + columns[column] = None + + if columns: + if conf.db in kb.data.cachedColumns: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns + else: + table[safeSQLIdentificatorNaming(tbl, True)] = columns + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table + + if not kb.data.cachedColumns: + errMsg = "unable to retrieve the columns for any " + errMsg += "table in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.error(errMsg) + + if bruteForce is None: + return self.getColumns(onlyColNames=onlyColNames, colTuple=colTuple, bruteForce=True) + + return kb.data.cachedColumns + + def getSchema(self): + infoMsg = "enumerating database management system schema" + logger.info(infoMsg) + + pushValue(conf.db) + pushValue(conf.tbl) + pushValue(conf.col) + + conf.db = None + conf.tbl = None + conf.col = None + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + + self.getTables() + + infoMsg = "fetched tables: " + infoMsg += ", ".join(["%s" % ", ".join("%s%s%s" % (unsafeSQLIdentificatorNaming(db), ".." if \ + Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) \ + else ".", unsafeSQLIdentificatorNaming(t)) for t in tbl) for db, tbl in \ + kb.data.cachedTables.items()]) + logger.info(infoMsg) + + for db, tables in kb.data.cachedTables.items(): + for tbl in tables: + conf.db = db + conf.tbl = tbl + + self.getColumns() + + conf.col = popValue() + conf.tbl = popValue() + conf.db = popValue() + + return kb.data.cachedColumns + + def __tableGetCount(self, db, table): + if Backend.isDbms(DBMS.DB2): + query = "SELECT %s FROM %s.%s--" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db.upper()), safeSQLIdentificatorNaming(table.upper(), True)) + else: + query = "SELECT %s FROM %s.%s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True)) + + count = inject.getValue(query, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if isNumPosStrValue(count): + if safeSQLIdentificatorNaming(db) not in kb.data.cachedCounts: + kb.data.cachedCounts[safeSQLIdentificatorNaming(db)] = {} + + if int(count) in kb.data.cachedCounts[safeSQLIdentificatorNaming(db)]: + kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)].append(safeSQLIdentificatorNaming(table, True)) + else: + kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)] = [safeSQLIdentificatorNaming(table, True)] + + 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'" % unsafeSQLIdentificatorNaming(conf.tbl) + logger.warn(warnMsg) + + conf.db = self.getCurrentDb() + + self.forceDbmsEnum() + + if conf.tbl: + for table in conf.tbl.split(","): + 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 diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py new file mode 100644 index 000000000..9920fa591 --- /dev/null +++ b/plugins/generic/entries.py @@ -0,0 +1,554 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +from extra.safe2bin.safe2bin import safechardecode +from lib.core.bigarray import BigArray +from lib.core.common import Backend +from lib.core.common import clearConsoleLine +from lib.core.common import getLimitRange +from lib.core.common import getUnicode +from lib.core.common import isInferenceAvailable +from lib.core.common import isListLike +from lib.core.common import isNoneValue +from lib.core.common import isNumPosStrValue +from lib.core.common import isTechniqueAvailable +from lib.core.common import prioritySortColumns +from lib.core.common import readInput +from lib.core.common import safeSQLIdentificatorNaming +from lib.core.common import singleTimeWarnMessage +from lib.core.common import unArrayizeValue +from lib.core.common import unsafeSQLIdentificatorNaming +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import DBMS +from lib.core.enums import EXPECTED +from lib.core.enums import PAYLOAD +from lib.core.exception import sqlmapConnectionException +from lib.core.exception import sqlmapMissingMandatoryOptionException +from lib.core.exception import sqlmapNoneDataException +from lib.core.exception import sqlmapUnsupportedFeatureException +from lib.core.settings import BLANK +from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD +from lib.core.settings import CURRENT_DB +from lib.core.settings import MAX_INT +from lib.core.settings import NULL +from lib.request import inject +from lib.utils.hash import attackDumpedTable + +class Entries: + """ + This class defines entries' enumeration functionalities for plugins. + """ + + def __init__(self): + pass + + def __pivotDumpTable(self, table, colList, count=None, blind=True): + lengths = {} + entries = {} + + dumpNode = queries[Backend.getIdentifiedDbms()].dump_table.blind + + validColumnList = False + validPivotValue = False + + if count is None: + query = dumpNode.count % table + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if blind else inject.getValue(query, blind=False, expected=EXPECTED.INT) + + if isinstance(count, basestring) and count.isdigit(): + count = int(count) + + if count == 0: + infoMsg = "table '%s' appears to be empty" % unsafeSQLIdentificatorNaming(table) + logger.info(infoMsg) + + for column in colList: + lengths[column] = len(column) + entries[column] = [] + + return entries, lengths + + elif not isNumPosStrValue(count): + return None + + for column in colList: + lengths[column] = 0 + entries[column] = BigArray() + + colList = filter(None, sorted(colList, key=lambda x: len(x) if x else MAX_INT)) + + for column in colList: + infoMsg = "fetching number of distinct " + infoMsg += "values for column '%s'" % column + logger.info(infoMsg) + + query = dumpNode.count2 % (column, table) + value = inject.getValue(query, blind=blind, inband=not blind, error=not blind, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if isNumPosStrValue(value): + validColumnList = True + + if value == count: + infoMsg = "using column '%s' as a pivot " % column + infoMsg += "for retrieving row data" + logger.info(infoMsg) + + validPivotValue = True + + colList.remove(column) + colList.insert(0, column) + break + + if not validColumnList: + 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 += " It won't be possible to retrieve all rows" + logger.warn(warnMsg) + + pivotValue = " " + breakRetrieval = False + + try: + for i in xrange(count): + if breakRetrieval: + break + + for column in colList: + # Correction for pivotValues with unrecognized/problematic chars + for char in ('\'', '?'): + if pivotValue and char in pivotValue and pivotValue[0] != char: + pivotValue = pivotValue.split(char)[0] + pivotValue = pivotValue[:-1] + chr(ord(pivotValue[-1]) + 1) + break + if column == colList[0]: + query = dumpNode.query % (column, table, column, pivotValue) + else: + query = dumpNode.query2 % (column, table, colList[0], pivotValue) + + value = inject.getValue(query, blind=blind, inband=not blind, error=not blind) + + if column == colList[0]: + if isNoneValue(value): + breakRetrieval = True + break + else: + pivotValue = safechardecode(value) + + if conf.limitStart or conf.limitStop: + if conf.limitStart and (i + 1) < conf.limitStart: + warnMsg = "skipping first %d pivot " % conf.limitStart + warnMsg += "point values" + singleTimeWarnMessage(warnMsg) + break + elif conf.limitStop and (i + 1) > conf.limitStop: + breakRetrieval = True + break + + value = "" if isNoneValue(value) else unArrayizeValue(value) + + lengths[column] = max(lengths[column], len(value) if value else 0) + entries[column].append(value) + + except KeyboardInterrupt: + warnMsg = "user aborted during enumeration. sqlmap " + warnMsg += "will display partial output" + logger.warn(warnMsg) + + except sqlmapConnectionException, e: + errMsg = "connection exception detected. sqlmap " + errMsg += "will display partial output" + errMsg += "'%s'" % e + logger.critical(errMsg) + + return entries, lengths + + def dumpTable(self, foundData=None): + self.forceDbmsEnum() + + if conf.db is None or conf.db == CURRENT_DB: + if conf.db is None: + warnMsg = "missing database parameter, sqlmap is going " + warnMsg += "to use the current database to enumerate " + warnMsg += "table(s) entries" + logger.warn(warnMsg) + + conf.db = self.getCurrentDb() + + elif conf.db is not None: + if Backend.isDbms(DBMS.ORACLE): + conf.db = conf.db.upper() + + if ',' in conf.db: + errMsg = "only one database name is allowed when enumerating " + errMsg += "the tables' columns" + raise sqlmapMissingMandatoryOptionException, errMsg + + conf.db = safeSQLIdentificatorNaming(conf.db) + + if conf.tbl: + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + conf.tbl = conf.tbl.upper() + + tblList = conf.tbl.split(",") + else: + self.getTables() + + if len(kb.data.cachedTables) > 0: + tblList = kb.data.cachedTables.values() + + if isinstance(tblList[0], (set, tuple, list)): + tblList = tblList[0] + else: + errMsg = "unable to retrieve the tables " + errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + raise sqlmapNoneDataException, errMsg + + for tbl in tblList: + tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) + + for tbl in tblList: + conf.tbl = tbl + kb.data.dumpedTable = {} + + if foundData is None: + kb.data.cachedColumns = {} + self.getColumns(onlyColNames=True) + else: + kb.data.cachedColumns = foundData + + try: + kb.dumpTable = "%s.%s" % (conf.db, tbl) + + if not safeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \ + or safeSQLIdentificatorNaming(tbl, True) not in \ + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] \ + or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]: + warnMsg = "unable to enumerate the columns for table " + warnMsg += "'%s' in database" % unsafeSQLIdentificatorNaming(tbl) + warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(conf.db) + warnMsg += ", skipping" if len(tblList) > 1 else "" + logger.warn(warnMsg) + + continue + + colList = sorted(filter(None, kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)].keys())) + colString = ", ".join(column for column in colList) + rootQuery = queries[Backend.getIdentifiedDbms()].dump_table + + infoMsg = "fetching entries" + if conf.col: + infoMsg += " of column(s) '%s'" % colString + infoMsg += " for table '%s'" % unsafeSQLIdentificatorNaming(tbl) + infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + entriesCount = 0 + + if any([isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION), isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR), conf.direct]): + entries = [] + query = None + + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + query = rootQuery.inband.query % (colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MAXDB): + query = rootQuery.inband.query % (colString, tbl) + elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): + # Partial inband and error + if not (isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL): + table = "%s.%s" % (conf.db, tbl) + + retVal = self.__pivotDumpTable(table, colList, blind=False) + + if retVal: + entries, _ = retVal + entries = zip(*[entries[colName] for colName in colList]) + else: + query = rootQuery.inband.query % (colString, conf.db, tbl) + elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + query = rootQuery.inband.query % (colString, conf.db, tbl, prioritySortColumns(colList)[0]) + else: + query = rootQuery.inband.query % (colString, conf.db, tbl) + + if not entries and query: + entries = inject.getValue(query, blind=False, dump=True) + + if isNoneValue(entries): + entries = [] + elif isinstance(entries, basestring): + entries = [entries] + elif not isListLike(entries): + entries = [] + + entriesCount = len(entries) + + for index, column in enumerate(colList): + colLen = len(column) + + if column not in kb.data.dumpedTable: + kb.data.dumpedTable[column] = {"length": colLen, "values": BigArray()} + + for entry in entries: + if entry is None or len(entry) == 0: + continue + + if isinstance(entry, basestring): + colEntry = entry + else: + colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u'' + + colEntryLen = len({" ": NULL, "": BLANK}.get(getUnicode(colEntry), getUnicode(colEntry))) + maxLen = max(colLen, colEntryLen) + + if maxLen > kb.data.dumpedTable[column]["length"]: + kb.data.dumpedTable[column]["length"] = maxLen + + kb.data.dumpedTable[column]["values"].append(colEntry) + + if not kb.data.dumpedTable and isInferenceAvailable() and not conf.direct: + infoMsg = "fetching number of " + if conf.col: + infoMsg += "column(s) '%s' " % colString + infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + query = rootQuery.blind.count % (tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): + query = rootQuery.blind.count % tbl + elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): + query = rootQuery.blind.count % ("%s.%s" % (conf.db, tbl)) + elif Backend.isDbms(DBMS.MAXDB): + query = rootQuery.blind.count % tbl + else: + query = rootQuery.blind.count % (conf.db, tbl) + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + lengths = {} + entries = {} + + if count == 0: + warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming(tbl) + warnMsg += "in database '%s' " % unsafeSQLIdentificatorNaming(conf.db) + warnMsg += "appears to be empty" + logger.warn(warnMsg) + + for column in colList: + lengths[column] = len(column) + entries[column] = [] + + elif not isNumPosStrValue(count): + warnMsg = "unable to retrieve the number of " + if conf.col: + warnMsg += "column(s) '%s' " % colString + warnMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.warn(warnMsg) + + continue + + elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL): + if Backend.isDbms(DBMS.ACCESS): + table = tbl + elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): + table = "%s.%s" % (conf.db, tbl) + elif Backend.isDbms(DBMS.MAXDB): + table = "%s.%s" % (conf.db, tbl) + + retVal = self.__pivotDumpTable(table, colList, count, blind=True) + + if retVal: + entries, lengths = retVal + + else: + emptyColumns = [] + plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) + indexRange = getLimitRange(count, dump=True, plusOne=plusOne) + + if len(colList) < len(indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD: + for column in colList: + if inject.getValue("SELECT COUNT(%s) FROM %s" % (column, kb.dumpTable), inband=False, error=False) == '0': + emptyColumns.append(column) + debugMsg = "column '%s' of table '%s' will not be " % (column, kb.dumpTable) + debugMsg += "dumped as it appears to be empty" + logger.debug(debugMsg) + + try: + for index in indexRange: + for column in colList: + value = "" + + if column not in lengths: + lengths[column] = 0 + + if column not in entries: + entries[column] = BigArray() + + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + query = rootQuery.blind.query % (column, conf.db, conf.tbl, sorted(colList, key=len)[0], index) + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + query = rootQuery.blind.query % (column, column, + tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), + index) + elif Backend.isDbms(DBMS.SQLITE): + query = rootQuery.blind.query % (column, tbl, index) + + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.query % (index, column, tbl) + + value = NULL if column in emptyColumns else inject.getValue(query, inband=False, error=False, dump=True) + + lengths[column] = max(lengths[column], len(value) if value else 0) + entries[column].append(value) + + except KeyboardInterrupt: + clearConsoleLine() + warnMsg = "Ctrl+C detected in dumping phase" + logger.warn(warnMsg) + + for column, columnEntries in entries.items(): + length = max(lengths[column], len(column)) + + kb.data.dumpedTable[column] = {"length": length, "values": columnEntries} + + entriesCount = len(columnEntries) + + if len(kb.data.dumpedTable) == 0 or (entriesCount == 0 and kb.permissionFlag): + warnMsg = "unable to retrieve the entries " + if conf.col: + warnMsg += "of columns '%s' " % colString + warnMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + warnMsg += "in database '%s'%s" % (unsafeSQLIdentificatorNaming(conf.db), " (permission denied)" if kb.permissionFlag else "") + logger.warn(warnMsg) + else: + kb.data.dumpedTable["__infos__"] = {"count": entriesCount, + "table": safeSQLIdentificatorNaming(tbl, True), + "db": safeSQLIdentificatorNaming(conf.db)} + attackDumpedTable() + conf.dumper.dbTableValues(kb.data.dumpedTable) + + except sqlmapConnectionException, e: + errMsg = "connection exception detected in dumping phase: " + errMsg += "'%s'" % e + logger.critical(errMsg) + + finally: + kb.dumpTable = None + + def dumpAll(self): + if conf.db is not None and conf.tbl is None: + self.dumpTable() + return + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + raise sqlmapUnsupportedFeatureException, errMsg + + infoMsg = "sqlmap will dump entries of all tables from all databases now" + logger.info(infoMsg) + + conf.tbl = None + conf.col = None + + self.getTables() + + if kb.data.cachedTables: + if isinstance(kb.data.cachedTables, list): + kb.data.cachedTables = { None: kb.data.cachedTables } + + for db, tables in kb.data.cachedTables.items(): + conf.db = db + + for table in tables: + try: + conf.tbl = table + kb.data.cachedColumns = {} + kb.data.dumpedTable = {} + + self.dumpTable() + except sqlmapNoneDataException: + 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 " + warnMsg += "provided columns" + logger.warn(warnMsg) + return + + conf.dumper.dbColumns(foundCols, colConsider, dbs) + + message = "do you want to dump entries? [Y/n] " + output = readInput(message, default="Y") + + if output and output[0] not in ("y", "Y"): + return + + dumpFromDbs = [] + message = "which database(s)?\n[a]ll (default)\n" + + for db, tblData in dbs.items(): + if tblData: + message += "[%s]\n" % db + + message += "[q]uit" + test = readInput(message, default="a") + + if not test or test in ("a", "A"): + dumpFromDbs = dbs.keys() + elif test in ("q", "Q"): + return + else: + dumpFromDbs = test.replace(" ", "").split(",") + + for db, tblData in dbs.items(): + if db not in dumpFromDbs or not tblData: + continue + + conf.db = db + dumpFromTbls = [] + message = "which table(s) of database '%s'?\n" % db + message += "[a]ll (default)\n" + + for tbl in tblData: + message += "[%s]\n" % tbl + + message += "[s]kip\n" + message += "[q]uit" + test = readInput(message, default="a") + + if not test or test in ("a", "A"): + dumpFromTbls = tblData + elif test in ("s", "S"): + continue + elif test in ("q", "Q"): + return + else: + dumpFromTbls = test.replace(" ", "").split(",") + + for table, columns in tblData.items(): + if table not in dumpFromTbls: + continue + + conf.tbl = table + conf.col = ",".join(column for column in filter(None, sorted(columns))) + kb.data.cachedColumns = {} + kb.data.dumpedTable = {} + + data = self.dumpTable(dbs) + + if data: + conf.dumper.dbTableValues(data) diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 9f15f2137..bd376d377 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -5,76 +5,23 @@ Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ -import re - -from extra.safe2bin.safe2bin import safechardecode -from lib.core.agent import agent -from lib.core.bigarray import BigArray -from lib.core.common import arrayizeValue from lib.core.common import Backend -from lib.core.common import clearConsoleLine -from lib.core.common import dataToStdout -from lib.core.common import filterPairValues -from lib.core.common import getLimitRange -from lib.core.common import getSQLSnippet -from lib.core.common import getUnicode -from lib.core.common import isInferenceAvailable -from lib.core.common import isListLike -from lib.core.common import isNoneValue -from lib.core.common import isNumPosStrValue -from lib.core.common import isTechniqueAvailable -from lib.core.common import parsePasswordHash -from lib.core.common import parseSqliteTableSchema -from lib.core.common import popValue -from lib.core.common import prioritySortColumns -from lib.core.common import pushValue -from lib.core.common import randomStr -from lib.core.common import readInput -from lib.core.common import safeSQLIdentificatorNaming -from lib.core.common import singleTimeWarnMessage -from lib.core.common import strToHex from lib.core.common import unArrayizeValue -from lib.core.common import unsafeSQLIdentificatorNaming -from lib.core.convert import utf8decode from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger -from lib.core.data import paths from lib.core.data import queries -from lib.core.dicts import firebirdTypes -from lib.core.dicts import mysqlPrivs -from lib.core.dicts import pgsqlPrivs -from lib.core.dicts import firebirdPrivs -from lib.core.dicts import db2Privs -from lib.core.enums import CHARSET_TYPE from lib.core.enums import DBMS -from lib.core.enums import EXPECTED -from lib.core.enums import PAYLOAD -from lib.core.exception import sqlmapConnectionException -from lib.core.exception import sqlmapMissingMandatoryOptionException -from lib.core.exception import sqlmapNoneDataException -from lib.core.exception import sqlmapUnsupportedFeatureException -from lib.core.exception import sqlmapUserQuitException from lib.core.session import setOs -from lib.core.settings import BLANK -from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD -from lib.core.settings import CONCAT_ROW_DELIMITER -from lib.core.settings import CONCAT_VALUE_DELIMITER -from lib.core.settings import CURRENT_DB -from lib.core.settings import MAX_INT -from lib.core.settings import NULL -from lib.core.settings import PARAMETER_SPLITTING_REGEX -from lib.core.settings import SQL_STATEMENTS -from lib.core.shell import autoCompletion -from lib.core.threads import getCurrentThreadData from lib.parse.banner import bannerParser from lib.request import inject -from lib.techniques.brute.use import columnExists -from lib.techniques.brute.use import tableExists -from lib.utils.hash import attackDumpedTable -from lib.utils.hash import attackCachedUsersPasswords +from plugins.generic.custom import Custom +from plugins.generic.databases import Databases +from plugins.generic.entries import Entries +from plugins.generic.search import Search +from plugins.generic.users import Users -class Enumeration: +class Enumeration(Custom, Databases, Entries, Search, Users): """ This class defines generic enumeration functionalities for plugins. """ @@ -82,20 +29,15 @@ class Enumeration: def __init__(self): kb.data.has_information_schema = False kb.data.banner = None - kb.data.currentUser = "" - kb.data.currentDb = "" kb.data.hostname = "" - kb.data.cachedUsers = [] - kb.data.cachedUsersPasswords = {} - kb.data.cachedUsersPrivileges = {} - kb.data.cachedUsersRoles = {} - kb.data.cachedDbs = [] - kb.data.cachedTables = {} - kb.data.cachedColumns = {} - kb.data.cachedCounts = {} - kb.data.dumpedTable = {} kb.data.processChar = None + Custom.__init__(self) + Databases.__init__(self) + Entries.__init__(self) + Search.__init__(self) + Users.__init__(self) + def getBanner(self): if not conf.getBanner: return @@ -130,28 +72,6 @@ class Enumeration: return kb.data.banner - def getCurrentUser(self): - infoMsg = "fetching current user" - logger.info(infoMsg) - - query = queries[Backend.getIdentifiedDbms()].current_user.query - - if not kb.data.currentUser: - kb.data.currentUser = unArrayizeValue(inject.getValue(query)) - - return kb.data.currentUser - - def getCurrentDb(self): - infoMsg = "fetching current database" - logger.info(infoMsg) - - query = queries[Backend.getIdentifiedDbms()].current_db.query - - if not kb.data.currentDb: - kb.data.currentDb = unArrayizeValue(inject.getValue(query, safeCharEncode=False)) - - return kb.data.currentDb - def getHostname(self): infoMsg = "fetching server hostname" logger.info(infoMsg) @@ -162,2348 +82,3 @@ class Enumeration: kb.data.hostname = unArrayizeValue(inject.getValue(query, safeCharEncode=False)) return kb.data.hostname - - def isDba(self, user=None): - infoMsg = "testing if current user is DBA" - logger.info(infoMsg) - - if Backend.isDbms(DBMS.MYSQL): - self.getCurrentUser() - query = queries[Backend.getIdentifiedDbms()].is_dba.query % (kb.data.currentUser.split("@")[0] if kb.data.currentUser else None) - elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and user is not None: - query = queries[Backend.getIdentifiedDbms()].is_dba.query2 % user - else: - query = queries[Backend.getIdentifiedDbms()].is_dba.query - - query = agent.forgeCaseStatement(query) - kb.data.isDba = unArrayizeValue(inject.getValue(query, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY)) - - return kb.data.isDba == "1" - - def getUsers(self): - infoMsg = "fetching database users" - logger.info(infoMsg) - - rootQuery = queries[Backend.getIdentifiedDbms()].users - - condition = (Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008"))) - condition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema) - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - if condition: - query = rootQuery.inband.query2 - else: - query = rootQuery.inband.query - value = inject.getValue(query, blind=False) - - if not isNoneValue(value): - kb.data.cachedUsers = arrayizeValue(value) - - if not kb.data.cachedUsers and isInferenceAvailable() and not conf.direct: - infoMsg = "fetching number of database users" - logger.info(infoMsg) - - if condition: - query = rootQuery.blind.count2 - else: - query = rootQuery.blind.count - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - errMsg = "unable to retrieve the number of database users" - raise sqlmapNoneDataException, errMsg - - plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) - indexRange = getLimitRange(count, plusOne=plusOne) - - for index in indexRange: - if Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MAXDB): - query = rootQuery.blind.query % (kb.data.cachedUsers[-1] if kb.data.cachedUsers else " ") - elif condition: - query = rootQuery.blind.query2 % index - else: - query = rootQuery.blind.query % index - user = inject.getValue(query, inband=False, error=False) - - if user: - kb.data.cachedUsers.append(user) - - if not kb.data.cachedUsers: - errMsg = "unable to retrieve the database users" - raise sqlmapNoneDataException, errMsg - - return kb.data.cachedUsers - - def getPasswordHashes(self): - infoMsg = "fetching database users password hashes" - - rootQuery = queries[Backend.getIdentifiedDbms()].passwords - - if conf.user == "CU": - infoMsg += " for current user" - conf.user = self.getCurrentUser() - - logger.info(infoMsg) - - if conf.user and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.user = conf.user.upper() - - if conf.user: - users = conf.user.split(",") - - if Backend.isDbms(DBMS.MYSQL): - for user in users: - parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) - - if parsedUser: - users[users.index(user)] = parsedUser.groups()[0] - else: - users = [] - - users = filter(None, users) - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): - query = rootQuery.inband.query2 - else: - query = rootQuery.inband.query - - condition = rootQuery.inband.condition - - if conf.user: - query += " WHERE " - query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) - - if Backend.isDbms(DBMS.SYBASE): - randStr = randomStr() - getCurrentThreadData().disableStdOut = True - - retVal = self.__pivotDumpTable("(%s) AS %s" % (query, randStr), ['%s.name' % randStr, '%s.password' % randStr], blind=False) - - if retVal: - for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])): - # password = "0x%s" % strToHex(password) - if user not in kb.data.cachedUsersPasswords: - kb.data.cachedUsersPasswords[user] = [password] - else: - kb.data.cachedUsersPasswords[user].append(password) - - getCurrentThreadData().disableStdOut = False - else: - value = inject.getValue(query, blind=False) - - for user, password in filterPairValues(value): - if not user or user == " ": - continue - - password = parsePasswordHash(password) - - if user not in kb.data.cachedUsersPasswords: - kb.data.cachedUsersPasswords[user] = [password] - else: - kb.data.cachedUsersPasswords[user].append(password) - - if not kb.data.cachedUsersPasswords and isInferenceAvailable() and not conf.direct: - if not len(users): - users = self.getUsers() - - if Backend.isDbms(DBMS.MYSQL): - for user in users: - parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) - - if parsedUser: - users[users.index(user)] = parsedUser.groups()[0] - - if Backend.isDbms(DBMS.SYBASE): - getCurrentThreadData().disableStdOut = True - - randStr = randomStr() - query = rootQuery.inband.query - - retVal = self.__pivotDumpTable("(%s) AS %s" % (query, randStr), ['%s.name' % randStr, '%s.password' % randStr], blind=True) - - if retVal: - for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])): - password = "0x%s" % strToHex(password) - - if user not in kb.data.cachedUsersPasswords: - kb.data.cachedUsersPasswords[user] = [password] - else: - kb.data.cachedUsersPasswords[user].append(password) - - getCurrentThreadData().disableStdOut = False - else: - retrievedUsers = set() - - for user in users: - if user in retrievedUsers: - continue - - infoMsg = "fetching number of password hashes " - infoMsg += "for user '%s'" % user - logger.info(infoMsg) - - if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): - query = rootQuery.blind.count2 % user - else: - query = rootQuery.blind.count % user - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - warnMsg = "unable to retrieve the number of password " - warnMsg += "hashes for user '%s'" % user - logger.warn(warnMsg) - continue - - infoMsg = "fetching password hashes for user '%s'" % user - logger.info(infoMsg) - - passwords = [] - - plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) - indexRange = getLimitRange(count, plusOne=plusOne) - - for index in indexRange: - if Backend.isDbms(DBMS.MSSQL): - if Backend.isVersionWithin(("2005", "2008")): - query = rootQuery.blind.query2 % (user, index, user) - else: - query = rootQuery.blind.query % (user, index, user) - else: - query = rootQuery.blind.query % (user, index) - password = inject.getValue(query, inband=False, error=False) - password = parsePasswordHash(password) - passwords.append(password) - - if passwords: - kb.data.cachedUsersPasswords[user] = passwords - else: - warnMsg = "unable to retrieve the password " - warnMsg += "hashes for user '%s'" % user - logger.warn(warnMsg) - - retrievedUsers.add(user) - - if not kb.data.cachedUsersPasswords: - errMsg = "unable to retrieve the password hashes for the " - errMsg += "database users (most probably because the session " - errMsg += "user has no read privileges over the relevant " - errMsg += "system database table)" - raise sqlmapNoneDataException, errMsg - else: - for user in kb.data.cachedUsersPasswords: - kb.data.cachedUsersPasswords[user] = list(set(kb.data.cachedUsersPasswords[user])) - - - message = "do you want to perform a dictionary-based attack " - message += "against retrieved password hashes? [Y/n/q]" - test = readInput(message, default="Y") - - if test[0] in ("n", "N"): - pass - elif test[0] in ("q", "Q"): - raise sqlmapUserQuitException - else: - attackCachedUsersPasswords() - - return kb.data.cachedUsersPasswords - - def __isAdminFromPrivileges(self, privileges): - # In PostgreSQL the usesuper privilege means that the - # user is DBA - dbaCondition = (Backend.isDbms(DBMS.PGSQL) and "super" in privileges) - - # In Oracle the DBA privilege means that the - # user is DBA - dbaCondition |= (Backend.isDbms(DBMS.ORACLE) and "DBA" in privileges) - - # In MySQL >= 5.0 the SUPER privilege means - # that the user is DBA - dbaCondition |= (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema and "SUPER" in privileges) - - # In MySQL < 5.0 the super_priv privilege means - # that the user is DBA - dbaCondition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema and "super_priv" in privileges) - - # In Firebird there is no specific privilege that means - # that the user is DBA - # TODO: confirm - dbaCondition |= (Backend.isDbms(DBMS.FIREBIRD) and "SELECT" in privileges and "INSERT" in privileges and "UPDATE" in privileges and "DELETE" in privileges and "REFERENCES" in privileges and "EXECUTE" in privileges) - - return dbaCondition - - def getPrivileges(self, query2=False): - infoMsg = "fetching database users privileges" - - rootQuery = queries[Backend.getIdentifiedDbms()].privileges - - if conf.user == "CU": - infoMsg += " for current user" - conf.user = self.getCurrentUser() - - logger.info(infoMsg) - - if conf.user and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.user = conf.user.upper() - - if conf.user: - users = conf.user.split(",") - - if Backend.isDbms(DBMS.MYSQL): - for user in users: - parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) - - if parsedUser: - users[users.index(user)] = parsedUser.groups()[0] - else: - users = [] - - users = filter(None, users) - - # Set containing the list of DBMS administrators - areAdmins = set() - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.inband.query2 - condition = rootQuery.inband.condition2 - elif Backend.isDbms(DBMS.ORACLE) and query2: - query = rootQuery.inband.query2 - condition = rootQuery.inband.condition2 - else: - query = rootQuery.inband.query - condition = rootQuery.inband.condition - - if conf.user: - query += " WHERE " - - if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - query += " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users)) - else: - query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) - - values = inject.getValue(query, blind=False) - - if not values and Backend.isDbms(DBMS.ORACLE) and not query2: - infoMsg = "trying with table USER_SYS_PRIVS" - logger.info(infoMsg) - - return self.getPrivileges(query2=True) - - if not isNoneValue(values): - for value in values: - user = None - privileges = set() - - for count in xrange(0, len(value)): - # The first column is always the username - if count == 0: - user = value[count] - - # The other columns are the privileges - else: - privilege = value[count] - - # In PostgreSQL we get 1 if the privilege is - # True, 0 otherwise - if Backend.isDbms(DBMS.PGSQL) and getUnicode(privilege).isdigit(): - if int(privilege) == 1: - privileges.add(pgsqlPrivs[count]) - - # In MySQL >= 5.0 and Oracle we get the list - # of privileges as string - elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema): - privileges.add(privilege) - - # In MySQL < 5.0 we get Y if the privilege is - # True, N otherwise - elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - if privilege.upper() == "Y": - privileges.add(mysqlPrivs[count]) - - # In DB2 we get Y or G if the privilege is - # True, N otherwise - elif Backend.isDbms(DBMS.DB2): - privs = privilege.split(",") - privilege = privs[0] - privs = privs[1] - privs = list(privs.strip()) - i = 1 - - for priv in privs: - if priv.upper() in ("Y", "G"): - for position, db2Priv in db2Privs.items(): - if position == i: - privilege += ", " + db2Priv - - i += 1 - - privileges.add(privilege) - - if self.__isAdminFromPrivileges(privileges): - areAdmins.add(user) - - if user in kb.data.cachedUsersPrivileges: - kb.data.cachedUsersPrivileges[user].extend(privileges) - else: - kb.data.cachedUsersPrivileges[user] = list(privileges) - - if not kb.data.cachedUsersPrivileges and isInferenceAvailable() and not conf.direct: - if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - conditionChar = " LIKE " - else: - conditionChar = "=" - - if not len(users): - users = self.getUsers() - - if Backend.isDbms(DBMS.MYSQL): - for user in users: - parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) - - if parsedUser: - users[users.index(user)] = parsedUser.groups()[0] - - retrievedUsers = set() - - for user in users: - if user in retrievedUsers: - continue - - if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - user = "%%%s%%" % user - - infoMsg = "fetching number of privileges " - infoMsg += "for user '%s'" % user - logger.info(infoMsg) - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.count2 % user - elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - query = rootQuery.blind.count % (conditionChar, user) - elif Backend.isDbms(DBMS.ORACLE) and query2: - query = rootQuery.blind.count2 % user - else: - query = rootQuery.blind.count % user - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - if Backend.isDbms(DBMS.ORACLE) and not query2: - infoMsg = "trying with table USER_SYS_PRIVS" - logger.info(infoMsg) - - return self.getPrivileges(query2=True) - - warnMsg = "unable to retrieve the number of " - warnMsg += "privileges for user '%s'" % user - logger.warn(warnMsg) - continue - - infoMsg = "fetching privileges for user '%s'" % user - logger.info(infoMsg) - - privileges = set() - - plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) - indexRange = getLimitRange(count, plusOne=plusOne) - - for index in indexRange: - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.query2 % (user, index) - elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - query = rootQuery.blind.query % (conditionChar, user, index) - elif Backend.isDbms(DBMS.ORACLE) and query2: - query = rootQuery.blind.query2 % (user, index) - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query % (index, user) - else: - query = rootQuery.blind.query % (user, index) - privilege = inject.getValue(query, inband=False, error=False) - - # In PostgreSQL we get 1 if the privilege is True, - # 0 otherwise - if Backend.isDbms(DBMS.PGSQL) and ", " in privilege: - privilege = privilege.replace(", ", ",") - privs = privilege.split(",") - i = 1 - - for priv in privs: - if priv.isdigit() and int(priv) == 1: - for position, pgsqlPriv in pgsqlPrivs.items(): - if position == i: - privileges.add(pgsqlPriv) - - i += 1 - - # In MySQL >= 5.0 and Oracle we get the list - # of privileges as string - elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema): - privileges.add(privilege) - - # In MySQL < 5.0 we get Y if the privilege is - # True, N otherwise - elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - privilege = privilege.replace(", ", ",") - privs = privilege.split(",") - i = 1 - - for priv in privs: - if priv.upper() == "Y": - for position, mysqlPriv in mysqlPrivs.items(): - if position == i: - privileges.add(mysqlPriv) - - i += 1 - - # In Firebird we get one letter for each privilege - elif Backend.isDbms(DBMS.FIREBIRD): - privileges.add(firebirdPrivs[privilege.strip()]) - - # In DB2 we get Y or G if the privilege is - # True, N otherwise - elif Backend.isDbms(DBMS.DB2): - privs = privilege.split(",") - privilege = privs[0] - privs = privs[1] - privs = list(privs.strip()) - i = 1 - - for priv in privs: - if priv.upper() in ("Y", "G"): - for position, db2Priv in db2Privs.items(): - if position == i: - privilege += ", " + db2Priv - - i += 1 - - privileges.add(privilege) - - if self.__isAdminFromPrivileges(privileges): - areAdmins.add(user) - - # In MySQL < 5.0 we break the cycle after the first - # time we get the user's privileges otherwise we - # duplicate the same query - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - break - - if privileges: - kb.data.cachedUsersPrivileges[user] = list(privileges) - else: - warnMsg = "unable to retrieve the privileges " - warnMsg += "for user '%s'" % user - logger.warn(warnMsg) - - retrievedUsers.add(user) - - if not kb.data.cachedUsersPrivileges: - errMsg = "unable to retrieve the privileges " - errMsg += "for the database users" - raise sqlmapNoneDataException, errMsg - - return (kb.data.cachedUsersPrivileges, areAdmins) - - def getRoles(self, query2=False): - warnMsg = "on %s the concept of roles does not " % Backend.getIdentifiedDbms() - warnMsg += "exist. sqlmap will enumerate privileges instead" - logger.warn(warnMsg) - - return self.getPrivileges(query2) - - def getDbs(self): - if len(kb.data.cachedDbs) > 0: - return kb.data.cachedDbs - - infoMsg = None - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - warnMsg = "information_schema not available, " - warnMsg += "back-end DBMS is MySQL < 5. database " - warnMsg += "names will be fetched from 'mysql' database" - logger.warn(warnMsg) - - elif Backend.isDbms(DBMS.ORACLE): - warnMsg = "schema names are going to be used on Oracle " - warnMsg += "for enumeration as the counterpart to database " - warnMsg += "names on other DBMSes" - logger.warn(warnMsg) - - infoMsg = "fetching database (schema) names" - elif Backend.isDbms(DBMS.DB2): - warnMsg = "schema names are going to be used on IBM DB2 " - warnMsg += "for enumeration as the counterpart to database " - warnMsg += "names on other DBMSes" - logger.warn(warnMsg) - - infoMsg = "fetching database (schema) names" - else: - infoMsg = "fetching database names" - - if infoMsg: - logger.info(infoMsg) - - rootQuery = queries[Backend.getIdentifiedDbms()].dbs - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.inband.query2 - else: - query = rootQuery.inband.query - value = inject.getValue(query, blind=False) - - if not isNoneValue(value): - kb.data.cachedDbs = arrayizeValue(value) - - if not kb.data.cachedDbs and isInferenceAvailable() and not conf.direct: - infoMsg = "fetching number of databases" - logger.info(infoMsg) - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.count2 - else: - query = rootQuery.blind.count - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - errMsg = "unable to retrieve the number of databases" - logger.error(errMsg) - else: - plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) - indexRange = getLimitRange(count, plusOne=plusOne) - - for index in indexRange: - if Backend.isDbms(DBMS.SYBASE): - query = rootQuery.blind.query % (kb.data.cachedDbs[-1] if kb.data.cachedDbs else " ") - elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.query2 % index - else: - query = rootQuery.blind.query % index - db = inject.getValue(query, inband=False, error=False) - - if db: - kb.data.cachedDbs.append(safeSQLIdentificatorNaming(db)) - - if not kb.data.cachedDbs and Backend.isDbms(DBMS.MSSQL): - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - blinds = (False, True) - else: - blinds = (True,) - - for blind in blinds: - count = 0 - kb.data.cachedDbs = [] - while True: - query = rootQuery.inband.query2 % count - value = inject.getValue(query, blind=blind) - if not value: - break - else: - kb.data.cachedDbs.append(unArrayizeValue(value)) - count += 1 - if kb.data.cachedDbs: - break - - if not kb.data.cachedDbs: - infoMsg = "falling back to current database" - logger.info(infoMsg) - self.getCurrentDb() - - if kb.data.currentDb: - kb.data.cachedDbs = [kb.data.currentDb] - else: - errMsg = "unable to retrieve the database names" - raise sqlmapNoneDataException, errMsg - else: - kb.data.cachedDbs.sort() - - return kb.data.cachedDbs - - def getTables(self, bruteForce=None): - if len(kb.data.cachedTables) > 0: - return kb.data.cachedTables - - self.forceDbmsEnum() - - if bruteForce is None: - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - errMsg = "information_schema not available, " - errMsg += "back-end DBMS is MySQL < 5.0" - logger.error(errMsg) - bruteForce = True - - elif Backend.isDbms(DBMS.ACCESS): - try: - tables = self.getTables(False) - except sqlmapNoneDataException: - tables = None - - if not tables: - errMsg = "cannot retrieve table names, " - errMsg += "back-end DBMS is Access" - logger.error(errMsg) - bruteForce = True - else: - return tables - - if conf.db == CURRENT_DB: - conf.db = self.getCurrentDb() - - if conf.db and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.db = conf.db.upper() - - if conf.db: - dbs = conf.db.split(",") - else: - dbs = self.getDbs() - - for db in dbs: - dbs[dbs.index(db)] = safeSQLIdentificatorNaming(db) - - dbs = filter(None, dbs) - - 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 conf.db not in kb.data.cachedTables: - 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 existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") - test = readInput(message, default="Y" if "Y" in message else "N") - - if test[0] in ("n", "N"): - return - elif test[0] in ("q", "Q"): - raise sqlmapUserQuitException - else: - return tableExists(paths.COMMON_TABLES) - - infoMsg = "fetching tables for database" - infoMsg += "%s: '%s'" % ("s" if len(dbs) > 1 else "", ", ".join(db if isinstance(db, basestring) else db[0] for db in sorted(dbs))) - logger.info(infoMsg) - - rootQuery = queries[Backend.getIdentifiedDbms()].tables - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - query = rootQuery.inband.query - condition = rootQuery.inband.condition if 'condition' in rootQuery.inband else None - - if condition: - if conf.excludeSysDbs: - query += " WHERE " - query += " AND ".join("%s != '%s'" % (condition, unsafeSQLIdentificatorNaming(db)) for db in self.excludeDbsList) - infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) - logger.info(infoMsg) - elif not Backend.isDbms(DBMS.SQLITE): - query += " WHERE " - query += " OR ".join("%s = '%s'" % (condition, unsafeSQLIdentificatorNaming(db)) for db in sorted(dbs)) - - if len(dbs) < 2 and ("%s," % condition) in query: - query = query.replace("%s," % condition, "", 1) - - value = inject.getValue(query, blind=False) - - if not isNoneValue(value): - value = filter(None, arrayizeValue(value)) - - if len(value) > 0 and not isListLike(value[0]): - value = map(lambda x: (dbs[0], x), value) - - for db, table in filterPairValues(value): - db = safeSQLIdentificatorNaming(db) - table = safeSQLIdentificatorNaming(table, True) - - if db not in kb.data.cachedTables: - kb.data.cachedTables[db] = [table] - else: - kb.data.cachedTables[db].append(table) - - if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct: - for db in dbs: - if conf.excludeSysDbs and db in self.excludeDbsList: - infoMsg = "skipping system database '%s'" % db - logger.info(infoMsg) - - continue - - infoMsg = "fetching number of tables for " - infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(db) - logger.info(infoMsg) - - if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.ACCESS): - query = rootQuery.blind.count - else: - query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(db) - - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - warnMsg = "unable to retrieve the number of " - warnMsg += "tables for database '%s'" % unsafeSQLIdentificatorNaming(db) - logger.warn(warnMsg) - continue - - tables = [] - - plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) - indexRange = getLimitRange(count, plusOne=plusOne) - - for index in indexRange: - if Backend.isDbms(DBMS.SYBASE): - query = rootQuery.blind.query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")) - elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS): - query = rootQuery.blind.query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ") - elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): - query = rootQuery.blind.query % index - else: - query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(db), index) - - table = inject.getValue(query, inband=False, error=False) - if not isNoneValue(table): - kb.hintValue = table - table = safeSQLIdentificatorNaming(table, True) - tables.append(table) - - if tables: - kb.data.cachedTables[db] = tables - else: - warnMsg = "unable to retrieve the table names " - warnMsg += "for database '%s'" % unsafeSQLIdentificatorNaming(db) - logger.warn(warnMsg) - - if isNoneValue(kb.data.cachedTables): - kb.data.cachedTables.clear() - - if not kb.data.cachedTables: - errMsg = "unable to retrieve the table names for any database" - if bruteForce is None: - logger.error(errMsg) - return self.getTables(bruteForce=True) - else: - raise sqlmapNoneDataException, errMsg - else: - for db, tables in kb.data.cachedTables.items(): - kb.data.cachedTables[db] = sorted(tables) if tables else tables - - return kb.data.cachedTables - - def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None): - self.forceDbmsEnum() - - if conf.db is None or conf.db == CURRENT_DB: - if conf.db is None: - warnMsg = "missing database parameter, sqlmap is going " - warnMsg += "to use the current database to enumerate " - warnMsg += "table(s) columns" - logger.warn(warnMsg) - - conf.db = self.getCurrentDb() - - elif conf.db is not None: - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.db = conf.db.upper() - - if ',' in conf.db: - errMsg = "only one database name is allowed when enumerating " - errMsg += "the tables' columns" - raise sqlmapMissingMandatoryOptionException, errMsg - - conf.db = safeSQLIdentificatorNaming(conf.db) - - if conf.col: - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.col = conf.col.upper() - - colList = conf.col.split(",") - else: - colList = [] - - for col in colList: - colList[colList.index(col)] = safeSQLIdentificatorNaming(col) - - colList = filter(None, colList) - - if conf.tbl: - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.tbl = conf.tbl.upper() - - tblList = conf.tbl.split(",") - else: - self.getTables() - - if len(kb.data.cachedTables) > 0: - if conf.db in kb.data.cachedTables: - tblList = kb.data.cachedTables[conf.db] - else: - tblList = kb.data.cachedTables.values() - - if isinstance(tblList[0], (set, tuple, list)): - tblList = tblList[0] - - tblList = list(tblList) - else: - errMsg = "unable to retrieve the tables " - errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - raise sqlmapNoneDataException, errMsg - - for tbl in tblList: - tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) - - if bruteForce is None: - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - errMsg = "information_schema not available, " - errMsg += "back-end DBMS is MySQL < 5.0" - logger.error(errMsg) - bruteForce = True - - elif Backend.isDbms(DBMS.ACCESS): - errMsg = "cannot retrieve column names, " - errMsg += "back-end DBMS is Access" - logger.error(errMsg) - bruteForce = True - - if bruteForce or colList: - resumeAvailable = False - - for tbl in tblList: - for db, table, colName, colType in kb.brute.columns: - if db == conf.db and table == tbl: - resumeAvailable = True - break - - if resumeAvailable or colList: - columns = {} - - for column in colList: - columns[column] = None - - for tbl in tblList: - for db, table, colName, colType in kb.brute.columns: - if db == conf.db and table == tbl: - columns[colName] = colType - - if conf.db in kb.data.cachedColumns: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns - else: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns} - - return kb.data.cachedColumns - - message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") - test = readInput(message, default="Y" if "Y" in message else "N") - - if test[0] in ("n", "N"): - return - elif test[0] in ("q", "Q"): - raise sqlmapUserQuitException - else: - return columnExists(paths.COMMON_COLUMNS) - - rootQuery = queries[Backend.getIdentifiedDbms()].columns - condition = rootQuery.blind.condition if 'condition' in rootQuery.blind else None - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - for tbl in tblList: - if conf.db is not None and len(kb.data.cachedColumns) > 0 \ - and conf.db in kb.data.cachedColumns and tbl in \ - kb.data.cachedColumns[conf.db]: - infoMsg = "fetched tables' columns on " - infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - return {conf.db: kb.data.cachedColumns[conf.db]} - - infoMsg = "fetching columns " - - if len(colList) > 0: - if colTuple is None: - colConsider, colCondParam = self.likeOrExact("column") - else: - colConsider, colCondParam = colTuple - condQueryStr = "%%s%s" % colCondParam - condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) - - if colConsider == "1": - infoMsg += "like '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - else: - infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - else: - condQuery = "" - - infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) - query += condQuery - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.inband.query % unsafeSQLIdentificatorNaming(tbl.upper()) - query += condQuery - elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, - conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) - query += condQuery.replace("[DB]", conf.db) - elif Backend.isDbms(DBMS.SQLITE): - query = rootQuery.inband.query % tbl - - value = inject.getValue(query, blind=False) - - if Backend.isDbms(DBMS.SQLITE): - parseSqliteTableSchema(value) - elif not isNoneValue(value): - table = {} - columns = {} - - for columnData in value: - if not isNoneValue(columnData): - name = safeSQLIdentificatorNaming(columnData[0]) - - if name: - if len(columnData) == 1: - columns[name] = "" - else: - columns[name] = columnData[1] - - if conf.db in kb.data.cachedColumns: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns - else: - table[safeSQLIdentificatorNaming(tbl, True)] = columns - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table - - elif isInferenceAvailable() and not conf.direct: - for tbl in tblList: - if conf.db is not None and len(kb.data.cachedColumns) > 0 \ - and conf.db in kb.data.cachedColumns and tbl in \ - kb.data.cachedColumns[conf.db]: - infoMsg = "fetched tables' columns on " - infoMsg += "database '%s'" % conf.db - logger.info(infoMsg) - - return {conf.db: kb.data.cachedColumns[conf.db]} - - infoMsg = "fetching columns " - - if len(colList) > 0: - if colTuple is None: - colConsider, colCondParam = self.likeOrExact("column") - else: - colConsider, colCondParam = colTuple - condQueryStr = "%%s%s" % colCondParam - condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) - - if colConsider == "1": - infoMsg += "like '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - else: - infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - else: - condQuery = "" - - infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) - query += condQuery - - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(tbl.upper()) - query += condQuery - - elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.blind.count % (conf.db, conf.db, \ - unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) - query += condQuery.replace("[DB]", conf.db) - - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.count % (tbl) - query += condQuery - - elif Backend.isDbms(DBMS.SQLITE): - query = rootQuery.blind.query % tbl - value = inject.getValue(query, inband=False, error=False) - parseSqliteTableSchema(value) - return kb.data.cachedColumns - - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - errMsg = "unable to retrieve the number of columns " - errMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.error(errMsg) - - continue - - table = {} - columns = {} - - indexRange = getLimitRange(count) - - for index in indexRange: - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) - query += condQuery - field = None - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl.upper()) - query += condQuery - field = None - elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.blind.query % (conf.db, conf.db, conf.db, conf.db, - conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) - query += condQuery.replace("[DB]", conf.db) - field = condition.replace("[DB]", conf.db) - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query % (tbl) - query += condQuery - field = None - - query = agent.limitQuery(index, query, field) - column = inject.getValue(query, inband=False, error=False) - - if not isNoneValue(column): - if not onlyColNames: - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column) - elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, column, conf.db, - conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query2 % (tbl, column) - - colType = inject.getValue(query, inband=False, error=False) - - if Backend.isDbms(DBMS.FIREBIRD): - colType = firebirdTypes.get(colType, colType) - - column = safeSQLIdentificatorNaming(column) - columns[column] = colType - else: - column = safeSQLIdentificatorNaming(column) - columns[column] = None - - if columns: - if conf.db in kb.data.cachedColumns: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns - else: - table[safeSQLIdentificatorNaming(tbl, True)] = columns - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table - - if not kb.data.cachedColumns: - errMsg = "unable to retrieve the columns for any " - errMsg += "table in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.error(errMsg) - - if bruteForce is None: - return self.getColumns(onlyColNames=onlyColNames, colTuple=colTuple, bruteForce=True) - - return kb.data.cachedColumns - - def getSchema(self): - infoMsg = "enumerating database management system schema" - logger.info(infoMsg) - - pushValue(conf.db) - pushValue(conf.tbl) - pushValue(conf.col) - - conf.db = None - conf.tbl = None - conf.col = None - kb.data.cachedTables = {} - kb.data.cachedColumns = {} - - self.getTables() - - infoMsg = "fetched tables: " - infoMsg += ", ".join(["%s" % ", ".join("%s%s%s" % (unsafeSQLIdentificatorNaming(db), ".." if \ - Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) \ - else ".", unsafeSQLIdentificatorNaming(t)) for t in tbl) for db, tbl in \ - kb.data.cachedTables.items()]) - logger.info(infoMsg) - - for db, tables in kb.data.cachedTables.items(): - for tbl in tables: - conf.db = db - conf.tbl = tbl - - self.getColumns() - - conf.col = popValue() - conf.tbl = popValue() - conf.db = popValue() - - return kb.data.cachedColumns - - def __tableGetCount(self, db, table): - if Backend.isDbms(DBMS.DB2): - query = "SELECT %s FROM %s.%s--" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db.upper()), safeSQLIdentificatorNaming(table.upper(), True)) - else: - query = "SELECT %s FROM %s.%s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True)) - - count = inject.getValue(query, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if isNumPosStrValue(count): - if safeSQLIdentificatorNaming(db) not in kb.data.cachedCounts: - kb.data.cachedCounts[safeSQLIdentificatorNaming(db)] = {} - - if int(count) in kb.data.cachedCounts[safeSQLIdentificatorNaming(db)]: - kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)].append(safeSQLIdentificatorNaming(table, True)) - else: - kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)] = [safeSQLIdentificatorNaming(table, True)] - - 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'" % unsafeSQLIdentificatorNaming(conf.tbl) - logger.warn(warnMsg) - - conf.db = self.getCurrentDb() - - self.forceDbmsEnum() - - if conf.tbl: - for table in conf.tbl.split(","): - 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 = {} - - dumpNode = queries[Backend.getIdentifiedDbms()].dump_table.blind - - validColumnList = False - validPivotValue = False - - if count is None: - query = dumpNode.count % table - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if blind else inject.getValue(query, blind=False, expected=EXPECTED.INT) - - if isinstance(count, basestring) and count.isdigit(): - count = int(count) - - if count == 0: - infoMsg = "table '%s' appears to be empty" % unsafeSQLIdentificatorNaming(table) - logger.info(infoMsg) - - for column in colList: - lengths[column] = len(column) - entries[column] = [] - - return entries, lengths - - elif not isNumPosStrValue(count): - return None - - for column in colList: - lengths[column] = 0 - entries[column] = BigArray() - - colList = filter(None, sorted(colList, key=lambda x: len(x) if x else MAX_INT)) - - for column in colList: - infoMsg = "fetching number of distinct " - infoMsg += "values for column '%s'" % column - logger.info(infoMsg) - - query = dumpNode.count2 % (column, table) - value = inject.getValue(query, blind=blind, inband=not blind, error=not blind, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if isNumPosStrValue(value): - validColumnList = True - - if value == count: - infoMsg = "using column '%s' as a pivot " % column - infoMsg += "for retrieving row data" - logger.info(infoMsg) - - validPivotValue = True - - colList.remove(column) - colList.insert(0, column) - break - - if not validColumnList: - 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 += " It won't be possible to retrieve all rows" - logger.warn(warnMsg) - - pivotValue = " " - breakRetrieval = False - - try: - for i in xrange(count): - if breakRetrieval: - break - - for column in colList: - # Correction for pivotValues with unrecognized/problematic chars - for char in ('\'', '?'): - if pivotValue and char in pivotValue and pivotValue[0] != char: - pivotValue = pivotValue.split(char)[0] - pivotValue = pivotValue[:-1] + chr(ord(pivotValue[-1]) + 1) - break - if column == colList[0]: - query = dumpNode.query % (column, table, column, pivotValue) - else: - query = dumpNode.query2 % (column, table, colList[0], pivotValue) - - value = inject.getValue(query, blind=blind, inband=not blind, error=not blind) - - if column == colList[0]: - if isNoneValue(value): - breakRetrieval = True - break - else: - pivotValue = safechardecode(value) - - if conf.limitStart or conf.limitStop: - if conf.limitStart and (i + 1) < conf.limitStart: - warnMsg = "skipping first %d pivot " % conf.limitStart - warnMsg += "point values" - singleTimeWarnMessage(warnMsg) - break - elif conf.limitStop and (i + 1) > conf.limitStop: - breakRetrieval = True - break - - value = "" if isNoneValue(value) else unArrayizeValue(value) - - lengths[column] = max(lengths[column], len(value) if value else 0) - entries[column].append(value) - - except KeyboardInterrupt: - warnMsg = "user aborted during enumeration. sqlmap " - warnMsg += "will display partial output" - logger.warn(warnMsg) - - except sqlmapConnectionException, e: - errMsg = "connection exception detected. sqlmap " - errMsg += "will display partial output" - errMsg += "'%s'" % e - logger.critical(errMsg) - - return entries, lengths - - def dumpTable(self, foundData=None): - self.forceDbmsEnum() - - if conf.db is None or conf.db == CURRENT_DB: - if conf.db is None: - warnMsg = "missing database parameter, sqlmap is going " - warnMsg += "to use the current database to enumerate " - warnMsg += "table(s) entries" - logger.warn(warnMsg) - - conf.db = self.getCurrentDb() - - elif conf.db is not None: - if Backend.isDbms(DBMS.ORACLE): - conf.db = conf.db.upper() - - if ',' in conf.db: - errMsg = "only one database name is allowed when enumerating " - errMsg += "the tables' columns" - raise sqlmapMissingMandatoryOptionException, errMsg - - conf.db = safeSQLIdentificatorNaming(conf.db) - - if conf.tbl: - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.tbl = conf.tbl.upper() - - tblList = conf.tbl.split(",") - else: - self.getTables() - - if len(kb.data.cachedTables) > 0: - tblList = kb.data.cachedTables.values() - - if isinstance(tblList[0], (set, tuple, list)): - tblList = tblList[0] - else: - errMsg = "unable to retrieve the tables " - errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - raise sqlmapNoneDataException, errMsg - - for tbl in tblList: - tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) - - for tbl in tblList: - conf.tbl = tbl - kb.data.dumpedTable = {} - - if foundData is None: - kb.data.cachedColumns = {} - self.getColumns(onlyColNames=True) - else: - kb.data.cachedColumns = foundData - - try: - kb.dumpTable = "%s.%s" % (conf.db, tbl) - - if not safeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \ - or safeSQLIdentificatorNaming(tbl, True) not in \ - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] \ - or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]: - warnMsg = "unable to enumerate the columns for table " - warnMsg += "'%s' in database" % unsafeSQLIdentificatorNaming(tbl) - warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(conf.db) - warnMsg += ", skipping" if len(tblList) > 1 else "" - logger.warn(warnMsg) - - continue - - colList = sorted(filter(None, kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)].keys())) - colString = ", ".join(column for column in colList) - rootQuery = queries[Backend.getIdentifiedDbms()].dump_table - - infoMsg = "fetching entries" - if conf.col: - infoMsg += " of column(s) '%s'" % colString - infoMsg += " for table '%s'" % unsafeSQLIdentificatorNaming(tbl) - infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - entriesCount = 0 - - if any([isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION), isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR), conf.direct]): - entries = [] - query = None - - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.inband.query % (colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) - elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MAXDB): - query = rootQuery.inband.query % (colString, tbl) - elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): - # Partial inband and error - if not (isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL): - table = "%s.%s" % (conf.db, tbl) - - retVal = self.__pivotDumpTable(table, colList, blind=False) - - if retVal: - entries, _ = retVal - entries = zip(*[entries[colName] for colName in colList]) - else: - query = rootQuery.inband.query % (colString, conf.db, tbl) - elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - query = rootQuery.inband.query % (colString, conf.db, tbl, prioritySortColumns(colList)[0]) - else: - query = rootQuery.inband.query % (colString, conf.db, tbl) - - if not entries and query: - entries = inject.getValue(query, blind=False, dump=True) - - if isNoneValue(entries): - entries = [] - elif isinstance(entries, basestring): - entries = [entries] - elif not isListLike(entries): - entries = [] - - entriesCount = len(entries) - - for index, column in enumerate(colList): - colLen = len(column) - - if column not in kb.data.dumpedTable: - kb.data.dumpedTable[column] = {"length": colLen, "values": BigArray()} - - for entry in entries: - if entry is None or len(entry) == 0: - continue - - if isinstance(entry, basestring): - colEntry = entry - else: - colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u'' - - colEntryLen = len({" ": NULL, "": BLANK}.get(getUnicode(colEntry), getUnicode(colEntry))) - maxLen = max(colLen, colEntryLen) - - if maxLen > kb.data.dumpedTable[column]["length"]: - kb.data.dumpedTable[column]["length"] = maxLen - - kb.data.dumpedTable[column]["values"].append(colEntry) - - if not kb.data.dumpedTable and isInferenceAvailable() and not conf.direct: - infoMsg = "fetching number of " - if conf.col: - infoMsg += "column(s) '%s' " % colString - infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.count % (tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) - elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): - query = rootQuery.blind.count % tbl - elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): - query = rootQuery.blind.count % ("%s.%s" % (conf.db, tbl)) - elif Backend.isDbms(DBMS.MAXDB): - query = rootQuery.blind.count % tbl - else: - query = rootQuery.blind.count % (conf.db, tbl) - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - lengths = {} - entries = {} - - if count == 0: - warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming(tbl) - warnMsg += "in database '%s' " % unsafeSQLIdentificatorNaming(conf.db) - warnMsg += "appears to be empty" - logger.warn(warnMsg) - - for column in colList: - lengths[column] = len(column) - entries[column] = [] - - elif not isNumPosStrValue(count): - warnMsg = "unable to retrieve the number of " - if conf.col: - warnMsg += "column(s) '%s' " % colString - warnMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.warn(warnMsg) - - continue - - elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL): - if Backend.isDbms(DBMS.ACCESS): - table = tbl - elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): - table = "%s.%s" % (conf.db, tbl) - elif Backend.isDbms(DBMS.MAXDB): - table = "%s.%s" % (conf.db, tbl) - - retVal = self.__pivotDumpTable(table, colList, count, blind=True) - - if retVal: - entries, lengths = retVal - - else: - emptyColumns = [] - plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) - indexRange = getLimitRange(count, dump=True, plusOne=plusOne) - - if len(colList) < len(indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD: - for column in colList: - if inject.getValue("SELECT COUNT(%s) FROM %s" % (column, kb.dumpTable), inband=False, error=False) == '0': - emptyColumns.append(column) - debugMsg = "column '%s' of table '%s' will not be " % (column, kb.dumpTable) - debugMsg += "dumped as it appears to be empty" - logger.debug(debugMsg) - - try: - for index in indexRange: - for column in colList: - value = "" - - if column not in lengths: - lengths[column] = 0 - - if column not in entries: - entries[column] = BigArray() - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - query = rootQuery.blind.query % (column, conf.db, conf.tbl, sorted(colList, key=len)[0], index) - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - query = rootQuery.blind.query % (column, column, - tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), - index) - elif Backend.isDbms(DBMS.SQLITE): - query = rootQuery.blind.query % (column, tbl, index) - - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query % (index, column, tbl) - - value = NULL if column in emptyColumns else inject.getValue(query, inband=False, error=False, dump=True) - - lengths[column] = max(lengths[column], len(value) if value else 0) - entries[column].append(value) - - except KeyboardInterrupt: - clearConsoleLine() - warnMsg = "Ctrl+C detected in dumping phase" - logger.warn(warnMsg) - - for column, columnEntries in entries.items(): - length = max(lengths[column], len(column)) - - kb.data.dumpedTable[column] = {"length": length, "values": columnEntries} - - entriesCount = len(columnEntries) - - if len(kb.data.dumpedTable) == 0 or (entriesCount == 0 and kb.permissionFlag): - warnMsg = "unable to retrieve the entries " - if conf.col: - warnMsg += "of columns '%s' " % colString - warnMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - warnMsg += "in database '%s'%s" % (unsafeSQLIdentificatorNaming(conf.db), " (permission denied)" if kb.permissionFlag else "") - logger.warn(warnMsg) - else: - kb.data.dumpedTable["__infos__"] = {"count": entriesCount, - "table": safeSQLIdentificatorNaming(tbl, True), - "db": safeSQLIdentificatorNaming(conf.db)} - attackDumpedTable() - conf.dumper.dbTableValues(kb.data.dumpedTable) - - except sqlmapConnectionException, e: - errMsg = "connection exception detected in dumping phase: " - errMsg += "'%s'" % e - logger.critical(errMsg) - - finally: - kb.dumpTable = None - - def dumpAll(self): - if conf.db is not None and conf.tbl is None: - self.dumpTable() - return - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - errMsg = "information_schema not available, " - errMsg += "back-end DBMS is MySQL < 5.0" - raise sqlmapUnsupportedFeatureException, errMsg - - infoMsg = "sqlmap will dump entries of all tables from all databases now" - logger.info(infoMsg) - - conf.tbl = None - conf.col = None - - self.getTables() - - if kb.data.cachedTables: - if isinstance(kb.data.cachedTables, list): - kb.data.cachedTables = { None: kb.data.cachedTables } - - for db, tables in kb.data.cachedTables.items(): - conf.db = db - - for table in tables: - try: - conf.tbl = table - kb.data.cachedColumns = {} - kb.data.dumpedTable = {} - - self.dumpTable() - except sqlmapNoneDataException: - 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 " - warnMsg += "provided columns" - logger.warn(warnMsg) - return - - conf.dumper.dbColumns(foundCols, colConsider, dbs) - - message = "do you want to dump entries? [Y/n] " - output = readInput(message, default="Y") - - if output and output[0] not in ("y", "Y"): - return - - dumpFromDbs = [] - message = "which database(s)?\n[a]ll (default)\n" - - for db, tblData in dbs.items(): - if tblData: - message += "[%s]\n" % db - - message += "[q]uit" - test = readInput(message, default="a") - - if not test or test in ("a", "A"): - dumpFromDbs = dbs.keys() - elif test in ("q", "Q"): - return - else: - dumpFromDbs = test.replace(" ", "").split(",") - - for db, tblData in dbs.items(): - if db not in dumpFromDbs or not tblData: - continue - - conf.db = db - dumpFromTbls = [] - message = "which table(s) of database '%s'?\n" % db - message += "[a]ll (default)\n" - - for tbl in tblData: - message += "[%s]\n" % tbl - - message += "[s]kip\n" - message += "[q]uit" - test = readInput(message, default="a") - - if not test or test in ("a", "A"): - dumpFromTbls = tblData - elif test in ("s", "S"): - continue - elif test in ("q", "Q"): - return - else: - dumpFromTbls = test.replace(" ", "").split(",") - - for table, columns in tblData.items(): - if table not in dumpFromTbls: - continue - - conf.tbl = table - conf.col = ",".join(column for column in filter(None, sorted(columns))) - kb.data.cachedColumns = {} - kb.data.dumpedTable = {} - - data = self.dumpTable(dbs) - - if data: - conf.dumper.dbTableValues(data) - - def searchDb(self): - foundDbs = [] - rootQuery = queries[Backend.getIdentifiedDbms()].search_db - dbList = conf.db.split(",") - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - dbCond = rootQuery.inband.condition2 - else: - dbCond = rootQuery.inband.condition - - dbConsider, dbCondParam = self.likeOrExact("database") - - for db in dbList: - db = safeSQLIdentificatorNaming(db) - - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - db = db.upper() - - infoMsg = "searching database" - if dbConsider == "1": - infoMsg += "s like" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) - logger.info(infoMsg) - - if conf.excludeSysDbs: - exclDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) - infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) - logger.info(infoMsg) - else: - exclDbsQuery = "" - - dbQuery = "%s%s" % (dbCond, dbCondParam) - dbQuery = dbQuery % unsafeSQLIdentificatorNaming(db) - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - if Backend.isDbms(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) - - if not isNoneValue(values): - if isinstance(values, basestring): - values = [values] - - for value in values: - value = safeSQLIdentificatorNaming(value) - foundDbs.append(value) - else: - infoMsg = "fetching number of databases" - if dbConsider == "1": - infoMsg += "s like" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) - logger.info(infoMsg) - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.count2 - else: - query = rootQuery.blind.count - query += dbQuery - query += exclDbsQuery - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - warnMsg = "no database" - if dbConsider == "1": - warnMsg += "s like" - warnMsg += " '%s' found" % unsafeSQLIdentificatorNaming(db) - logger.warn(warnMsg) - - continue - - indexRange = getLimitRange(count) - - for index in indexRange: - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.query2 - else: - query = rootQuery.blind.query - query += dbQuery - query += exclDbsQuery - if Backend.isDbms(DBMS.DB2): - query += ") AS foobar" - query = agent.limitQuery(index, query, dbCond) - - value = inject.getValue(query, inband=False, error=False) - value = safeSQLIdentificatorNaming(value) - foundDbs.append(value) - - return foundDbs - - def searchTable(self): - bruteForce = False - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - errMsg = "information_schema not available, " - errMsg += "back-end DBMS is MySQL < 5.0" - bruteForce = True - - if bruteForce: - message = "do you want to use common table existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") - test = readInput(message, default="Y" if "Y" in message else "N") - - if test[0] in ("n", "N"): - return - elif test[0] in ("q", "Q"): - raise sqlmapUserQuitException - else: - regex = "|".join(conf.tbl.split(",")) - return tableExists(paths.COMMON_TABLES, regex) - - foundTbls = {} - tblList = conf.tbl.split(",") - - rootQuery = queries[Backend.getIdentifiedDbms()].search_table - tblCond = rootQuery.inband.condition - dbCond = rootQuery.inband.condition2 - whereDbsQuery = "" - - tblConsider, tblCondParam = self.likeOrExact("table") - - for tbl in tblList: - tbl = safeSQLIdentificatorNaming(tbl, True) - - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - tbl = tbl.upper() - - infoMsg = "searching table" - if tblConsider == "1": - infoMsg += "s like" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) - - if conf.db and conf.db != CURRENT_DB: - _ = conf.db.split(",") - whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" - infoMsg += " for database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _)) - elif conf.excludeSysDbs: - whereDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) - infoMsg2 = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) - logger.info(infoMsg2) - - logger.info(infoMsg) - - tblQuery = "%s%s" % (tblCond, tblCondParam) - tblQuery = tblQuery % tbl - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - query = rootQuery.inband.query - query += tblQuery - query += whereDbsQuery - values = inject.getValue(query, blind=False) - - for foundDb, foundTbl in filterPairValues(values): - foundDb = safeSQLIdentificatorNaming(foundDb) - foundTbl = safeSQLIdentificatorNaming(foundTbl, True) - - if foundDb is None or foundTbl is None: - continue - - if foundDb in foundTbls: - foundTbls[foundDb].append(foundTbl) - else: - foundTbls[foundDb] = [foundTbl] - else: - infoMsg = "fetching number of databases with table" - if tblConsider == "1": - infoMsg += "s like" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) - logger.info(infoMsg) - - query = rootQuery.blind.count - query += tblQuery - query += whereDbsQuery - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - warnMsg = "no databases have table" - if tblConsider == "1": - warnMsg += "s like" - warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) - logger.warn(warnMsg) - - continue - - indexRange = getLimitRange(count) - - for index in indexRange: - query = rootQuery.blind.query - query += tblQuery - query += whereDbsQuery - if Backend.isDbms(DBMS.DB2): - query += ") AS foobar" - query = agent.limitQuery(index, query) - foundDb = inject.getValue(query, inband=False, error=False) - foundDb = safeSQLIdentificatorNaming(foundDb) - - if foundDb not in foundTbls: - foundTbls[foundDb] = [] - - if tblConsider == "2": - foundTbls[foundDb].append(tbl) - - if tblConsider == "2": - continue - - for db in foundTbls.keys(): - db = safeSQLIdentificatorNaming(db) - - infoMsg = "fetching number of table" - if tblConsider == "1": - infoMsg += "s like" - infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(tbl), db) - logger.info(infoMsg) - - query = rootQuery.blind.count2 - query = query % unsafeSQLIdentificatorNaming(db) - query += " AND %s" % tblQuery - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - warnMsg = "no table" - if tblConsider == "1": - warnMsg += "s like" - warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(tbl) - warnMsg += "in database '%s'" % db - logger.warn(warnMsg) - - continue - - indexRange = getLimitRange(count) - - for index in indexRange: - query = rootQuery.blind.query2 - query = query % unsafeSQLIdentificatorNaming(db) - query += " AND %s" % tblQuery - query = agent.limitQuery(index, query) - foundTbl = inject.getValue(query, inband=False, error=False) - kb.hintValue = foundTbl - foundTbl = safeSQLIdentificatorNaming(foundTbl, True) - foundTbls[db].append(foundTbl) - - return foundTbls - - def searchColumn(self): - bruteForce = False - - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - errMsg = "information_schema not available, " - errMsg += "back-end DBMS is MySQL < 5.0" - bruteForce = True - - if bruteForce: - message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") - test = readInput(message, default="Y" if "Y" in message else "N") - - if test[0] in ("n", "N"): - return - elif test[0] in ("q", "Q"): - raise sqlmapUserQuitException - else: - regex = "|".join(conf.col.split(",")) - conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS, regex)) - - message = "do you want to dump entries? [Y/n] " - output = readInput(message, default="Y") - - if output and output[0] not in ("n", "N"): - self.dumpAll() - - return - - rootQuery = queries[Backend.getIdentifiedDbms()].search_column - foundCols = {} - dbs = {} - whereDbsQuery = "" - whereTblsQuery = "" - infoMsgTbl = "" - infoMsgDb = "" - colList = conf.col.split(",") - colCond = rootQuery.inband.condition - dbCond = rootQuery.inband.condition2 - tblCond = rootQuery.inband.condition3 - - colConsider, colCondParam = self.likeOrExact("column") - - for column in colList: - column = safeSQLIdentificatorNaming(column) - - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - column = column.upper() - - infoMsg = "searching column" - if colConsider == "1": - infoMsg += "s like" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) - - foundCols[column] = {} - - if conf.tbl: - _ = conf.tbl.split(",") - whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in _) + ")" - infoMsgTbl = " for table%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(tbl for tbl in _)) - - if conf.db and conf.db != CURRENT_DB: - _ = conf.db.split(",") - whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" - infoMsgDb = " in database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _)) - elif conf.excludeSysDbs: - whereDbsQuery = "".join(" AND %s != '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in self.excludeDbsList) - infoMsg2 = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) - logger.info(infoMsg2) - else: - infoMsgDb = " across all databases" - - logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) - - colQuery = "%s%s" % (colCond, colCondParam) - colQuery = colQuery % unsafeSQLIdentificatorNaming(column) - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: - if not all((conf.db, conf.tbl)): - # Enumerate tables containing the column provided if - # either of database(s) or table(s) is not provided - query = rootQuery.inband.query - query += colQuery - query += whereDbsQuery - query += whereTblsQuery - values = inject.getValue(query, blind=False) - else: - # Assume provided databases' tables contain the - # column(s) provided - values = [] - - for db in conf.db.split(","): - for tbl in conf.tbl.split(","): - values.append([db, tbl]) - - for db, tbl in filterPairValues(values): - db = safeSQLIdentificatorNaming(db) - tbls = tbl.split(",") - - for tbl in tbls: - tbl = safeSQLIdentificatorNaming(tbl, True) - - if db is None or tbl is None: - continue - - conf.db = db - conf.tbl = tbl - conf.col = column - - self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) - - if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: - if db not in dbs: - dbs[db] = {} - - if tbl not in dbs[db]: - dbs[db][tbl] = {} - - dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) - - if db in foundCols[column]: - foundCols[column][db].append(tbl) - else: - foundCols[column][db] = [tbl] - - kb.data.cachedColumns = {} - else: - if not conf.db: - infoMsg = "fetching number of databases with tables containing column" - if colConsider == "1": - infoMsg += "s like" - infoMsg += " '%s'" % column - logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) - - query = rootQuery.blind.count - query += colQuery - query += whereDbsQuery - query += whereTblsQuery - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - warnMsg = "no databases have tables containing column" - if colConsider == "1": - warnMsg += "s like" - warnMsg += " '%s'" % column - logger.warn("%s%s" % (warnMsg, infoMsgTbl)) - - continue - - indexRange = getLimitRange(count) - - for index in indexRange: - query = rootQuery.blind.query - query += colQuery - query += whereDbsQuery - query += whereTblsQuery - if Backend.isDbms(DBMS.DB2): - query += ") AS foobar" - query = agent.limitQuery(index, query) - db = inject.getValue(query, inband=False, error=False) - db = safeSQLIdentificatorNaming(db) - - if db not in dbs: - dbs[db] = {} - - if db not in foundCols[column]: - foundCols[column][db] = [] - else: - for db in conf.db.split(","): - if db not in foundCols[column]: - foundCols[column][db] = [] - - for column, dbData in foundCols.items(): - colQuery = "%s%s" % (colCond, colCondParam) - colQuery = colQuery % column - - for db in dbData: - db = safeSQLIdentificatorNaming(db) - - infoMsg = "fetching number of tables containing column" - if colConsider == "1": - infoMsg += "s like" - infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(column), db) - logger.info(infoMsg) - - query = rootQuery.blind.count2 - query = query % db - query += " AND %s" % colQuery - query += whereTblsQuery - count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): - warnMsg = "no tables contain column" - if colConsider == "1": - warnMsg += "s like" - warnMsg += " '%s' " % column - warnMsg += "in database '%s'" % db - logger.warn(warnMsg) - - continue - - indexRange = getLimitRange(count) - - for index in indexRange: - query = rootQuery.blind.query2 - query = query % db - query += " AND %s" % colQuery - query += whereTblsQuery - query = agent.limitQuery(index, query) - tbl = inject.getValue(query, inband=False, error=False) - kb.hintValue = tbl - - tbl = safeSQLIdentificatorNaming(tbl, True) - - conf.db = db - conf.tbl = tbl - conf.col = column - - self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) - - if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: - if db not in dbs: - dbs[db] = {} - - if tbl not in dbs[db]: - dbs[db][tbl] = {} - - dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) - - kb.data.cachedColumns = {} - - if db in foundCols[column]: - foundCols[column][db].append(tbl) - else: - foundCols[column][db] = [tbl] - - self.dumpFoundColumn(dbs, foundCols, colConsider) - - def search(self): - if conf.db and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - for item in ('db', 'tbl', 'col'): - if getattr(conf, item, None): - setattr(conf, item, getattr(conf, item).upper()) - - if conf.col: - self.searchColumn() - - elif conf.tbl: - conf.dumper.dbTables(self.searchTable()) - - elif conf.db: - conf.dumper.lister("found databases", self.searchDb()) - - else: - errMsg = "missing parameter, provide -D, -T or -C together " - errMsg += "with --search" - raise sqlmapMissingMandatoryOptionException, errMsg - - def sqlQuery(self, query): - output = None - sqlType = None - query = query.rstrip(';') - kb.unescape = False - - for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): - for sqlStatement in sqlStatements: - if query.lower().startswith(sqlStatement): - sqlType = sqlTitle - break - - if 'OPENROWSET' not in query.upper() and (not sqlType or 'SELECT' in sqlType): - infoMsg = "fetching %s query output: '%s'" % (sqlType if sqlType is not None else "SQL", query) - logger.info(infoMsg) - - output = inject.getValue(query, fromUser=True) - kb.unescape = True - - return output - elif not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct: - warnMsg = "execution of custom SQL queries is only " - warnMsg += "available when stacked queries are supported" - logger.warn(warnMsg) - - kb.unescape = True - - return None - else: - if sqlType: - debugMsg = "executing %s query: '%s'" % (sqlType if sqlType is not None else "SQL", query) - else: - debugMsg = "executing unknown SQL type query: '%s'" % query - logger.debug(debugMsg) - - inject.goStacked(query) - - debugMsg = "done" - logger.debug(debugMsg) - - output = False - - kb.unescape = True - - return output - - def sqlShell(self): - infoMsg = "calling %s shell. To quit type " % Backend.getIdentifiedDbms() - infoMsg += "'x' or 'q' and press ENTER" - logger.info(infoMsg) - - autoCompletion(sqlShell=True) - - while True: - query = None - - try: - query = raw_input("sql-shell> ") - query = utf8decode(query) - except KeyboardInterrupt: - print - errMsg = "user aborted" - logger.error(errMsg) - except EOFError: - print - errMsg = "exit" - logger.error(errMsg) - break - - if not query: - continue - - if query.lower() in ("x", "q", "exit", "quit"): - break - - output = self.sqlQuery(query) - - if output and output != "Quit": - conf.dumper.query(query, output) - - elif not output: - pass - - elif output != "Quit": - dataToStdout("No output\n") - - def sqlFile(self): - infoMsg = "executing SQL statements from given file(s)" - logger.info(infoMsg) - - for sfile in re.split(PARAMETER_SPLITTING_REGEX, conf.sqlFile): - sfile = sfile.strip() - - if not sfile: - continue - - query = getSQLSnippet(Backend.getDbms(), sfile) - - infoMsg = "executing SQL statement%s from file '%s'" % ("s" if ";" in query else "", sfile) - logger.info(infoMsg) - - conf.dumper.query(query, self.sqlQuery(query)) diff --git a/plugins/generic/search.py b/plugins/generic/search.py new file mode 100644 index 000000000..89af024aa --- /dev/null +++ b/plugins/generic/search.py @@ -0,0 +1,542 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +from lib.core.agent import agent +from lib.core.common import Backend +from lib.core.common import filterPairValues +from lib.core.common import getLimitRange +from lib.core.common import isNoneValue +from lib.core.common import isNumPosStrValue +from lib.core.common import isTechniqueAvailable +from lib.core.common import readInput +from lib.core.common import safeSQLIdentificatorNaming +from lib.core.common import unsafeSQLIdentificatorNaming +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.data import queries +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import DBMS +from lib.core.enums import EXPECTED +from lib.core.enums import PAYLOAD +from lib.core.exception import sqlmapMissingMandatoryOptionException +from lib.core.exception import sqlmapUserQuitException +from lib.core.settings import CURRENT_DB +from lib.request import inject +from lib.techniques.brute.use import columnExists +from lib.techniques.brute.use import tableExists + +class Search: + """ + This class defines search functionalities for plugins. + """ + + def __init__(self): + pass + + def searchDb(self): + foundDbs = [] + rootQuery = queries[Backend.getIdentifiedDbms()].search_db + dbList = conf.db.split(",") + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + dbCond = rootQuery.inband.condition2 + else: + dbCond = rootQuery.inband.condition + + dbConsider, dbCondParam = self.likeOrExact("database") + + for db in dbList: + db = safeSQLIdentificatorNaming(db) + + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + db = db.upper() + + infoMsg = "searching database" + if dbConsider == "1": + infoMsg += "s like" + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) + logger.info(infoMsg) + + if conf.excludeSysDbs: + exclDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) + infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) + logger.info(infoMsg) + else: + exclDbsQuery = "" + + dbQuery = "%s%s" % (dbCond, dbCondParam) + dbQuery = dbQuery % unsafeSQLIdentificatorNaming(db) + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + if Backend.isDbms(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) + + if not isNoneValue(values): + if isinstance(values, basestring): + values = [values] + + for value in values: + value = safeSQLIdentificatorNaming(value) + foundDbs.append(value) + else: + infoMsg = "fetching number of databases" + if dbConsider == "1": + infoMsg += "s like" + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) + logger.info(infoMsg) + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.blind.count2 + else: + query = rootQuery.blind.count + query += dbQuery + query += exclDbsQuery + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + warnMsg = "no database" + if dbConsider == "1": + warnMsg += "s like" + warnMsg += " '%s' found" % unsafeSQLIdentificatorNaming(db) + logger.warn(warnMsg) + + continue + + indexRange = getLimitRange(count) + + for index in indexRange: + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.blind.query2 + else: + query = rootQuery.blind.query + query += dbQuery + query += exclDbsQuery + if Backend.isDbms(DBMS.DB2): + query += ") AS foobar" + query = agent.limitQuery(index, query, dbCond) + + value = inject.getValue(query, inband=False, error=False) + value = safeSQLIdentificatorNaming(value) + foundDbs.append(value) + + return foundDbs + + def searchTable(self): + bruteForce = False + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + bruteForce = True + + if bruteForce: + message = "do you want to use common table existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") + test = readInput(message, default="Y" if "Y" in message else "N") + + if test[0] in ("n", "N"): + return + elif test[0] in ("q", "Q"): + raise sqlmapUserQuitException + else: + regex = "|".join(conf.tbl.split(",")) + return tableExists(paths.COMMON_TABLES, regex) + + foundTbls = {} + tblList = conf.tbl.split(",") + + rootQuery = queries[Backend.getIdentifiedDbms()].search_table + tblCond = rootQuery.inband.condition + dbCond = rootQuery.inband.condition2 + whereDbsQuery = "" + + tblConsider, tblCondParam = self.likeOrExact("table") + + for tbl in tblList: + tbl = safeSQLIdentificatorNaming(tbl, True) + + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + tbl = tbl.upper() + + infoMsg = "searching table" + if tblConsider == "1": + infoMsg += "s like" + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) + + if conf.db and conf.db != CURRENT_DB: + _ = conf.db.split(",") + whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" + infoMsg += " for database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _)) + elif conf.excludeSysDbs: + whereDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) + infoMsg2 = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) + logger.info(infoMsg2) + + logger.info(infoMsg) + + tblQuery = "%s%s" % (tblCond, tblCondParam) + tblQuery = tblQuery % tbl + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + query = rootQuery.inband.query + query += tblQuery + query += whereDbsQuery + values = inject.getValue(query, blind=False) + + for foundDb, foundTbl in filterPairValues(values): + foundDb = safeSQLIdentificatorNaming(foundDb) + foundTbl = safeSQLIdentificatorNaming(foundTbl, True) + + if foundDb is None or foundTbl is None: + continue + + if foundDb in foundTbls: + foundTbls[foundDb].append(foundTbl) + else: + foundTbls[foundDb] = [foundTbl] + else: + infoMsg = "fetching number of databases with table" + if tblConsider == "1": + infoMsg += "s like" + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) + logger.info(infoMsg) + + query = rootQuery.blind.count + query += tblQuery + query += whereDbsQuery + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + warnMsg = "no databases have table" + if tblConsider == "1": + warnMsg += "s like" + warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) + logger.warn(warnMsg) + + continue + + indexRange = getLimitRange(count) + + for index in indexRange: + query = rootQuery.blind.query + query += tblQuery + query += whereDbsQuery + if Backend.isDbms(DBMS.DB2): + query += ") AS foobar" + query = agent.limitQuery(index, query) + foundDb = inject.getValue(query, inband=False, error=False) + foundDb = safeSQLIdentificatorNaming(foundDb) + + if foundDb not in foundTbls: + foundTbls[foundDb] = [] + + if tblConsider == "2": + foundTbls[foundDb].append(tbl) + + if tblConsider == "2": + continue + + for db in foundTbls.keys(): + db = safeSQLIdentificatorNaming(db) + + infoMsg = "fetching number of table" + if tblConsider == "1": + infoMsg += "s like" + infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(tbl), db) + logger.info(infoMsg) + + query = rootQuery.blind.count2 + query = query % unsafeSQLIdentificatorNaming(db) + query += " AND %s" % tblQuery + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + warnMsg = "no table" + if tblConsider == "1": + warnMsg += "s like" + warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(tbl) + warnMsg += "in database '%s'" % db + logger.warn(warnMsg) + + continue + + indexRange = getLimitRange(count) + + for index in indexRange: + query = rootQuery.blind.query2 + query = query % unsafeSQLIdentificatorNaming(db) + query += " AND %s" % tblQuery + query = agent.limitQuery(index, query) + foundTbl = inject.getValue(query, inband=False, error=False) + kb.hintValue = foundTbl + foundTbl = safeSQLIdentificatorNaming(foundTbl, True) + foundTbls[db].append(foundTbl) + + return foundTbls + + def searchColumn(self): + bruteForce = False + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + bruteForce = True + + if bruteForce: + message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") + test = readInput(message, default="Y" if "Y" in message else "N") + + if test[0] in ("n", "N"): + return + elif test[0] in ("q", "Q"): + raise sqlmapUserQuitException + else: + regex = "|".join(conf.col.split(",")) + conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS, regex)) + + message = "do you want to dump entries? [Y/n] " + output = readInput(message, default="Y") + + if output and output[0] not in ("n", "N"): + self.dumpAll() + + return + + rootQuery = queries[Backend.getIdentifiedDbms()].search_column + foundCols = {} + dbs = {} + whereDbsQuery = "" + whereTblsQuery = "" + infoMsgTbl = "" + infoMsgDb = "" + colList = conf.col.split(",") + colCond = rootQuery.inband.condition + dbCond = rootQuery.inband.condition2 + tblCond = rootQuery.inband.condition3 + + colConsider, colCondParam = self.likeOrExact("column") + + for column in colList: + column = safeSQLIdentificatorNaming(column) + + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + column = column.upper() + + infoMsg = "searching column" + if colConsider == "1": + infoMsg += "s like" + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) + + foundCols[column] = {} + + if conf.tbl: + _ = conf.tbl.split(",") + whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in _) + ")" + infoMsgTbl = " for table%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(tbl for tbl in _)) + + if conf.db and conf.db != CURRENT_DB: + _ = conf.db.split(",") + whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" + infoMsgDb = " in database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _)) + elif conf.excludeSysDbs: + whereDbsQuery = "".join(" AND %s != '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in self.excludeDbsList) + infoMsg2 = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) + logger.info(infoMsg2) + else: + infoMsgDb = " across all databases" + + logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) + + colQuery = "%s%s" % (colCond, colCondParam) + colQuery = colQuery % unsafeSQLIdentificatorNaming(column) + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + if not all((conf.db, conf.tbl)): + # Enumerate tables containing the column provided if + # either of database(s) or table(s) is not provided + query = rootQuery.inband.query + query += colQuery + query += whereDbsQuery + query += whereTblsQuery + values = inject.getValue(query, blind=False) + else: + # Assume provided databases' tables contain the + # column(s) provided + values = [] + + for db in conf.db.split(","): + for tbl in conf.tbl.split(","): + values.append([db, tbl]) + + for db, tbl in filterPairValues(values): + db = safeSQLIdentificatorNaming(db) + tbls = tbl.split(",") + + for tbl in tbls: + tbl = safeSQLIdentificatorNaming(tbl, True) + + if db is None or tbl is None: + continue + + conf.db = db + conf.tbl = tbl + conf.col = column + + self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) + + if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: + if db not in dbs: + dbs[db] = {} + + if tbl not in dbs[db]: + dbs[db][tbl] = {} + + dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) + + if db in foundCols[column]: + foundCols[column][db].append(tbl) + else: + foundCols[column][db] = [tbl] + + kb.data.cachedColumns = {} + else: + if not conf.db: + infoMsg = "fetching number of databases with tables containing column" + if colConsider == "1": + infoMsg += "s like" + infoMsg += " '%s'" % column + logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) + + query = rootQuery.blind.count + query += colQuery + query += whereDbsQuery + query += whereTblsQuery + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + warnMsg = "no databases have tables containing column" + if colConsider == "1": + warnMsg += "s like" + warnMsg += " '%s'" % column + logger.warn("%s%s" % (warnMsg, infoMsgTbl)) + + continue + + indexRange = getLimitRange(count) + + for index in indexRange: + query = rootQuery.blind.query + query += colQuery + query += whereDbsQuery + query += whereTblsQuery + if Backend.isDbms(DBMS.DB2): + query += ") AS foobar" + query = agent.limitQuery(index, query) + db = inject.getValue(query, inband=False, error=False) + db = safeSQLIdentificatorNaming(db) + + if db not in dbs: + dbs[db] = {} + + if db not in foundCols[column]: + foundCols[column][db] = [] + else: + for db in conf.db.split(","): + if db not in foundCols[column]: + foundCols[column][db] = [] + + for column, dbData in foundCols.items(): + colQuery = "%s%s" % (colCond, colCondParam) + colQuery = colQuery % column + + for db in dbData: + db = safeSQLIdentificatorNaming(db) + + infoMsg = "fetching number of tables containing column" + if colConsider == "1": + infoMsg += "s like" + infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(column), db) + logger.info(infoMsg) + + query = rootQuery.blind.count2 + query = query % db + query += " AND %s" % colQuery + query += whereTblsQuery + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + warnMsg = "no tables contain column" + if colConsider == "1": + warnMsg += "s like" + warnMsg += " '%s' " % column + warnMsg += "in database '%s'" % db + logger.warn(warnMsg) + + continue + + indexRange = getLimitRange(count) + + for index in indexRange: + query = rootQuery.blind.query2 + query = query % db + query += " AND %s" % colQuery + query += whereTblsQuery + query = agent.limitQuery(index, query) + tbl = inject.getValue(query, inband=False, error=False) + kb.hintValue = tbl + + tbl = safeSQLIdentificatorNaming(tbl, True) + + conf.db = db + conf.tbl = tbl + conf.col = column + + self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) + + if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: + if db not in dbs: + dbs[db] = {} + + if tbl not in dbs[db]: + dbs[db][tbl] = {} + + dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) + + kb.data.cachedColumns = {} + + if db in foundCols[column]: + foundCols[column][db].append(tbl) + else: + foundCols[column][db] = [tbl] + + self.dumpFoundColumn(dbs, foundCols, colConsider) + + def search(self): + if conf.db and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + for item in ('db', 'tbl', 'col'): + if getattr(conf, item, None): + setattr(conf, item, getattr(conf, item).upper()) + + if conf.col: + self.searchColumn() + + elif conf.tbl: + conf.dumper.dbTables(self.searchTable()) + + elif conf.db: + conf.dumper.lister("found databases", self.searchDb()) + + else: + errMsg = "missing parameter, provide -D, -T or -C together " + errMsg += "with --search" + raise sqlmapMissingMandatoryOptionException, errMsg diff --git a/plugins/generic/users.py b/plugins/generic/users.py new file mode 100644 index 000000000..b6759baf3 --- /dev/null +++ b/plugins/generic/users.py @@ -0,0 +1,612 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +import re + +from lib.core.agent import agent +from lib.core.common import arrayizeValue +from lib.core.common import Backend +from lib.core.common import filterPairValues +from lib.core.common import getLimitRange +from lib.core.common import getUnicode +from lib.core.common import isInferenceAvailable +from lib.core.common import isNoneValue +from lib.core.common import isNumPosStrValue +from lib.core.common import isTechniqueAvailable +from lib.core.common import parsePasswordHash +from lib.core.common import randomStr +from lib.core.common import readInput +from lib.core.common import strToHex +from lib.core.common import unArrayizeValue +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.dicts import mysqlPrivs +from lib.core.dicts import pgsqlPrivs +from lib.core.dicts import firebirdPrivs +from lib.core.dicts import db2Privs +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import DBMS +from lib.core.enums import EXPECTED +from lib.core.enums import PAYLOAD +from lib.core.exception import sqlmapNoneDataException +from lib.core.exception import sqlmapUserQuitException +from lib.core.threads import getCurrentThreadData +from lib.request import inject +from lib.utils.hash import attackCachedUsersPasswords + +class Users: + """ + This class defines users' enumeration functionalities for plugins. + """ + + def __init__(self): + kb.data.currentUser = "" + kb.data.isDba = None + kb.data.cachedUsers = [] + kb.data.cachedUsersPasswords = {} + kb.data.cachedUsersPrivileges = {} + kb.data.cachedUsersRoles = {} + + def getCurrentUser(self): + infoMsg = "fetching current user" + logger.info(infoMsg) + + query = queries[Backend.getIdentifiedDbms()].current_user.query + + if not kb.data.currentUser: + kb.data.currentUser = unArrayizeValue(inject.getValue(query)) + + return kb.data.currentUser + + def isDba(self, user=None): + infoMsg = "testing if current user is DBA" + logger.info(infoMsg) + + if Backend.isDbms(DBMS.MYSQL): + self.getCurrentUser() + query = queries[Backend.getIdentifiedDbms()].is_dba.query % (kb.data.currentUser.split("@")[0] if kb.data.currentUser else None) + elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and user is not None: + query = queries[Backend.getIdentifiedDbms()].is_dba.query2 % user + else: + query = queries[Backend.getIdentifiedDbms()].is_dba.query + + query = agent.forgeCaseStatement(query) + kb.data.isDba = unArrayizeValue(inject.getValue(query, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY)) + + return kb.data.isDba == "1" + + def getUsers(self): + infoMsg = "fetching database users" + logger.info(infoMsg) + + rootQuery = queries[Backend.getIdentifiedDbms()].users + + condition = (Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008"))) + condition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema) + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + if condition: + query = rootQuery.inband.query2 + else: + query = rootQuery.inband.query + value = inject.getValue(query, blind=False) + + if not isNoneValue(value): + kb.data.cachedUsers = arrayizeValue(value) + + if not kb.data.cachedUsers and isInferenceAvailable() and not conf.direct: + infoMsg = "fetching number of database users" + logger.info(infoMsg) + + if condition: + query = rootQuery.blind.count2 + else: + query = rootQuery.blind.count + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + errMsg = "unable to retrieve the number of database users" + raise sqlmapNoneDataException, errMsg + + plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) + indexRange = getLimitRange(count, plusOne=plusOne) + + for index in indexRange: + if Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MAXDB): + query = rootQuery.blind.query % (kb.data.cachedUsers[-1] if kb.data.cachedUsers else " ") + elif condition: + query = rootQuery.blind.query2 % index + else: + query = rootQuery.blind.query % index + user = inject.getValue(query, inband=False, error=False) + + if user: + kb.data.cachedUsers.append(user) + + if not kb.data.cachedUsers: + errMsg = "unable to retrieve the database users" + raise sqlmapNoneDataException, errMsg + + return kb.data.cachedUsers + + def getPasswordHashes(self): + infoMsg = "fetching database users password hashes" + + rootQuery = queries[Backend.getIdentifiedDbms()].passwords + + if conf.user == "CU": + infoMsg += " for current user" + conf.user = self.getCurrentUser() + + logger.info(infoMsg) + + if conf.user and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + conf.user = conf.user.upper() + + if conf.user: + users = conf.user.split(",") + + if Backend.isDbms(DBMS.MYSQL): + for user in users: + parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) + + if parsedUser: + users[users.index(user)] = parsedUser.groups()[0] + else: + users = [] + + users = filter(None, users) + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): + query = rootQuery.inband.query2 + else: + query = rootQuery.inband.query + + condition = rootQuery.inband.condition + + if conf.user: + query += " WHERE " + query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + + if Backend.isDbms(DBMS.SYBASE): + randStr = randomStr() + getCurrentThreadData().disableStdOut = True + + retVal = self.__pivotDumpTable("(%s) AS %s" % (query, randStr), ['%s.name' % randStr, '%s.password' % randStr], blind=False) + + if retVal: + for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])): + # password = "0x%s" % strToHex(password) + if user not in kb.data.cachedUsersPasswords: + kb.data.cachedUsersPasswords[user] = [password] + else: + kb.data.cachedUsersPasswords[user].append(password) + + getCurrentThreadData().disableStdOut = False + else: + value = inject.getValue(query, blind=False) + + for user, password in filterPairValues(value): + if not user or user == " ": + continue + + password = parsePasswordHash(password) + + if user not in kb.data.cachedUsersPasswords: + kb.data.cachedUsersPasswords[user] = [password] + else: + kb.data.cachedUsersPasswords[user].append(password) + + if not kb.data.cachedUsersPasswords and isInferenceAvailable() and not conf.direct: + if not len(users): + users = self.getUsers() + + if Backend.isDbms(DBMS.MYSQL): + for user in users: + parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) + + if parsedUser: + users[users.index(user)] = parsedUser.groups()[0] + + if Backend.isDbms(DBMS.SYBASE): + getCurrentThreadData().disableStdOut = True + + randStr = randomStr() + query = rootQuery.inband.query + + retVal = self.__pivotDumpTable("(%s) AS %s" % (query, randStr), ['%s.name' % randStr, '%s.password' % randStr], blind=True) + + if retVal: + for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])): + password = "0x%s" % strToHex(password) + + if user not in kb.data.cachedUsersPasswords: + kb.data.cachedUsersPasswords[user] = [password] + else: + kb.data.cachedUsersPasswords[user].append(password) + + getCurrentThreadData().disableStdOut = False + else: + retrievedUsers = set() + + for user in users: + if user in retrievedUsers: + continue + + infoMsg = "fetching number of password hashes " + infoMsg += "for user '%s'" % user + logger.info(infoMsg) + + if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): + query = rootQuery.blind.count2 % user + else: + query = rootQuery.blind.count % user + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + warnMsg = "unable to retrieve the number of password " + warnMsg += "hashes for user '%s'" % user + logger.warn(warnMsg) + continue + + infoMsg = "fetching password hashes for user '%s'" % user + logger.info(infoMsg) + + passwords = [] + + plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) + indexRange = getLimitRange(count, plusOne=plusOne) + + for index in indexRange: + if Backend.isDbms(DBMS.MSSQL): + if Backend.isVersionWithin(("2005", "2008")): + query = rootQuery.blind.query2 % (user, index, user) + else: + query = rootQuery.blind.query % (user, index, user) + else: + query = rootQuery.blind.query % (user, index) + password = inject.getValue(query, inband=False, error=False) + password = parsePasswordHash(password) + passwords.append(password) + + if passwords: + kb.data.cachedUsersPasswords[user] = passwords + else: + warnMsg = "unable to retrieve the password " + warnMsg += "hashes for user '%s'" % user + logger.warn(warnMsg) + + retrievedUsers.add(user) + + if not kb.data.cachedUsersPasswords: + errMsg = "unable to retrieve the password hashes for the " + errMsg += "database users (most probably because the session " + errMsg += "user has no read privileges over the relevant " + errMsg += "system database table)" + raise sqlmapNoneDataException, errMsg + else: + for user in kb.data.cachedUsersPasswords: + kb.data.cachedUsersPasswords[user] = list(set(kb.data.cachedUsersPasswords[user])) + + + message = "do you want to perform a dictionary-based attack " + message += "against retrieved password hashes? [Y/n/q]" + test = readInput(message, default="Y") + + if test[0] in ("n", "N"): + pass + elif test[0] in ("q", "Q"): + raise sqlmapUserQuitException + else: + attackCachedUsersPasswords() + + return kb.data.cachedUsersPasswords + + def __isAdminFromPrivileges(self, privileges): + # In PostgreSQL the usesuper privilege means that the + # user is DBA + dbaCondition = (Backend.isDbms(DBMS.PGSQL) and "super" in privileges) + + # In Oracle the DBA privilege means that the + # user is DBA + dbaCondition |= (Backend.isDbms(DBMS.ORACLE) and "DBA" in privileges) + + # In MySQL >= 5.0 the SUPER privilege means + # that the user is DBA + dbaCondition |= (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema and "SUPER" in privileges) + + # In MySQL < 5.0 the super_priv privilege means + # that the user is DBA + dbaCondition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema and "super_priv" in privileges) + + # In Firebird there is no specific privilege that means + # that the user is DBA + # TODO: confirm + dbaCondition |= (Backend.isDbms(DBMS.FIREBIRD) and "SELECT" in privileges and "INSERT" in privileges and "UPDATE" in privileges and "DELETE" in privileges and "REFERENCES" in privileges and "EXECUTE" in privileges) + + return dbaCondition + + def getPrivileges(self, query2=False): + infoMsg = "fetching database users privileges" + + rootQuery = queries[Backend.getIdentifiedDbms()].privileges + + if conf.user == "CU": + infoMsg += " for current user" + conf.user = self.getCurrentUser() + + logger.info(infoMsg) + + if conf.user and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): + conf.user = conf.user.upper() + + if conf.user: + users = conf.user.split(",") + + if Backend.isDbms(DBMS.MYSQL): + for user in users: + parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) + + if parsedUser: + users[users.index(user)] = parsedUser.groups()[0] + else: + users = [] + + users = filter(None, users) + + # Set containing the list of DBMS administrators + areAdmins = set() + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR)) or conf.direct: + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.inband.query2 + condition = rootQuery.inband.condition2 + elif Backend.isDbms(DBMS.ORACLE) and query2: + query = rootQuery.inband.query2 + condition = rootQuery.inband.condition2 + else: + query = rootQuery.inband.query + condition = rootQuery.inband.condition + + if conf.user: + query += " WHERE " + + if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: + query += " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users)) + else: + query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + + values = inject.getValue(query, blind=False) + + if not values and Backend.isDbms(DBMS.ORACLE) and not query2: + infoMsg = "trying with table USER_SYS_PRIVS" + logger.info(infoMsg) + + return self.getPrivileges(query2=True) + + if not isNoneValue(values): + for value in values: + user = None + privileges = set() + + for count in xrange(0, len(value)): + # The first column is always the username + if count == 0: + user = value[count] + + # The other columns are the privileges + else: + privilege = value[count] + + # In PostgreSQL we get 1 if the privilege is + # True, 0 otherwise + if Backend.isDbms(DBMS.PGSQL) and getUnicode(privilege).isdigit(): + if int(privilege) == 1: + privileges.add(pgsqlPrivs[count]) + + # In MySQL >= 5.0 and Oracle we get the list + # of privileges as string + elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema): + privileges.add(privilege) + + # In MySQL < 5.0 we get Y if the privilege is + # True, N otherwise + elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + if privilege.upper() == "Y": + privileges.add(mysqlPrivs[count]) + + # In DB2 we get Y or G if the privilege is + # True, N otherwise + elif Backend.isDbms(DBMS.DB2): + privs = privilege.split(",") + privilege = privs[0] + privs = privs[1] + privs = list(privs.strip()) + i = 1 + + for priv in privs: + if priv.upper() in ("Y", "G"): + for position, db2Priv in db2Privs.items(): + if position == i: + privilege += ", " + db2Priv + + i += 1 + + privileges.add(privilege) + + if self.__isAdminFromPrivileges(privileges): + areAdmins.add(user) + + if user in kb.data.cachedUsersPrivileges: + kb.data.cachedUsersPrivileges[user].extend(privileges) + else: + kb.data.cachedUsersPrivileges[user] = list(privileges) + + if not kb.data.cachedUsersPrivileges and isInferenceAvailable() and not conf.direct: + if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: + conditionChar = " LIKE " + else: + conditionChar = "=" + + if not len(users): + users = self.getUsers() + + if Backend.isDbms(DBMS.MYSQL): + for user in users: + parsedUser = re.search("[\047]*(.*?)[\047]*\@", user) + + if parsedUser: + users[users.index(user)] = parsedUser.groups()[0] + + retrievedUsers = set() + + for user in users: + if user in retrievedUsers: + continue + + if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: + user = "%%%s%%" % user + + infoMsg = "fetching number of privileges " + infoMsg += "for user '%s'" % user + logger.info(infoMsg) + + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.blind.count2 % user + elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: + query = rootQuery.blind.count % (conditionChar, user) + elif Backend.isDbms(DBMS.ORACLE) and query2: + query = rootQuery.blind.count2 % user + else: + query = rootQuery.blind.count % user + count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if not isNumPosStrValue(count): + if Backend.isDbms(DBMS.ORACLE) and not query2: + infoMsg = "trying with table USER_SYS_PRIVS" + logger.info(infoMsg) + + return self.getPrivileges(query2=True) + + warnMsg = "unable to retrieve the number of " + warnMsg += "privileges for user '%s'" % user + logger.warn(warnMsg) + continue + + infoMsg = "fetching privileges for user '%s'" % user + logger.info(infoMsg) + + privileges = set() + + plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) + indexRange = getLimitRange(count, plusOne=plusOne) + + for index in indexRange: + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + query = rootQuery.blind.query2 % (user, index) + elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: + query = rootQuery.blind.query % (conditionChar, user, index) + elif Backend.isDbms(DBMS.ORACLE) and query2: + query = rootQuery.blind.query2 % (user, index) + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.query % (index, user) + else: + query = rootQuery.blind.query % (user, index) + privilege = inject.getValue(query, inband=False, error=False) + + # In PostgreSQL we get 1 if the privilege is True, + # 0 otherwise + if Backend.isDbms(DBMS.PGSQL) and ", " in privilege: + privilege = privilege.replace(", ", ",") + privs = privilege.split(",") + i = 1 + + for priv in privs: + if priv.isdigit() and int(priv) == 1: + for position, pgsqlPriv in pgsqlPrivs.items(): + if position == i: + privileges.add(pgsqlPriv) + + i += 1 + + # In MySQL >= 5.0 and Oracle we get the list + # of privileges as string + elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema): + privileges.add(privilege) + + # In MySQL < 5.0 we get Y if the privilege is + # True, N otherwise + elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + privilege = privilege.replace(", ", ",") + privs = privilege.split(",") + i = 1 + + for priv in privs: + if priv.upper() == "Y": + for position, mysqlPriv in mysqlPrivs.items(): + if position == i: + privileges.add(mysqlPriv) + + i += 1 + + # In Firebird we get one letter for each privilege + elif Backend.isDbms(DBMS.FIREBIRD): + privileges.add(firebirdPrivs[privilege.strip()]) + + # In DB2 we get Y or G if the privilege is + # True, N otherwise + elif Backend.isDbms(DBMS.DB2): + privs = privilege.split(",") + privilege = privs[0] + privs = privs[1] + privs = list(privs.strip()) + i = 1 + + for priv in privs: + if priv.upper() in ("Y", "G"): + for position, db2Priv in db2Privs.items(): + if position == i: + privilege += ", " + db2Priv + + i += 1 + + privileges.add(privilege) + + if self.__isAdminFromPrivileges(privileges): + areAdmins.add(user) + + # In MySQL < 5.0 we break the cycle after the first + # time we get the user's privileges otherwise we + # duplicate the same query + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + break + + if privileges: + kb.data.cachedUsersPrivileges[user] = list(privileges) + else: + warnMsg = "unable to retrieve the privileges " + warnMsg += "for user '%s'" % user + logger.warn(warnMsg) + + retrievedUsers.add(user) + + if not kb.data.cachedUsersPrivileges: + errMsg = "unable to retrieve the privileges " + errMsg += "for the database users" + raise sqlmapNoneDataException, errMsg + + return (kb.data.cachedUsersPrivileges, areAdmins) + + def getRoles(self, query2=False): + warnMsg = "on %s the concept of roles does not " % Backend.getIdentifiedDbms() + warnMsg += "exist. sqlmap will enumerate privileges instead" + logger.warn(warnMsg) + + return self.getPrivileges(query2)