diff --git a/lib/controller/action.py b/lib/controller/action.py index 5142404ac..686399136 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -101,6 +101,10 @@ def action(): dumper.userSettings("database management system users privileges", conf.dbmsHandler.getPrivileges(), "privilege") + if conf.getRoles: + dumper.userSettings("database management system users roles", + conf.dbmsHandler.getRoles(), "role") + if conf.getDbs: dumper.lister("available databases", conf.dbmsHandler.getDbs()) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index c2e034f90..412b5e740 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -87,6 +87,7 @@ optDict = { "getUsers": "boolean", "getPasswordHashes": "boolean", "getPrivileges": "boolean", + "getRoles": "boolean", "getDbs": "boolean", "getTables": "boolean", "getColumns": "boolean", diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 7ec114d27..af0643603 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -247,6 +247,10 @@ def cmdLineParser(): action="store_true", help="Enumerate DBMS users privileges") + enumeration.add_option("--roles", dest="getRoles", + action="store_true", + help="Enumerate DBMS users roles") + enumeration.add_option("--dbs", dest="getDbs", action="store_true", help="Enumerate DBMS databases") diff --git a/lib/parse/queriesfile.py b/lib/parse/queriesfile.py index 3da921056..98cccc583 100644 --- a/lib/parse/queriesfile.py +++ b/lib/parse/queriesfile.py @@ -177,6 +177,14 @@ class queriesHandler(ContentHandler): self.__queries.privileges = self.__privileges + elif name == "roles": + self.__roles = {} + self.__roles["inband"] = { "query": self.__inband, "query2": self.__inband2, "condition": self.__conditionInband, "condition2": self.__conditionInband2 } + self.__roles["blind"] = { "query": self.__blind, "query2": self.__blind2, + "count": self.__count, "count2": self.__count2 } + + self.__queries.roles = self.__roles + elif name == "dbs": self.__dbs = {} self.__dbs["inband"] = { "query": self.__inband, "query2": self.__inband2 } diff --git a/plugins/dbms/oracle/enumeration.py b/plugins/dbms/oracle/enumeration.py index 25552d4a8..25b47475c 100644 --- a/plugins/dbms/oracle/enumeration.py +++ b/plugins/dbms/oracle/enumeration.py @@ -22,7 +22,12 @@ with sqlmap; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ +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.exception import sqlmapNoneDataException +from lib.request import inject from plugins.generic.enumeration import Enumeration as GenericEnumeration @@ -30,6 +35,145 @@ class Enumeration(GenericEnumeration): def __init__(self): GenericEnumeration.__init__(self, "Oracle") + def getRoles(self, query2=False): + infoMsg = "fetching database users roles" + + rootQuery = queries[kb.dbms].roles + + if conf.user == "CU": + infoMsg += " for current user" + conf.user = self.getCurrentUser() + + logger.info(infoMsg) + + # Set containing the list of DBMS administrators + areAdmins = set() + + if kb.unionPosition: + if 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 " + query += " OR ".join("%s = '%s'" % (condition, user) for user in users) + + values = inject.getValue(query, blind=False) + + if not values and not query2: + infoMsg = "trying with table USER_ROLE_PRIVS" + logger.info(infoMsg) + + return self.getRoles(query2=True) + + if values: + for value in values: + user = None + roles = 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 roles + else: + role = value[count] + + # In Oracle we get the list of roles as string + roles.add(role) + + if self.__isAdminFromPrivileges(roles): + areAdmins.add(user) + + if kb.data.cachedUsersRoles.has_key(user): + kb.data.cachedUsersRoles[user].extend(roles) + else: + kb.data.cachedUsersRoles[user] = list(roles) + + if not kb.data.cachedUsersRoles: + conditionChar = "=" + + if conf.user: + 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 user in retrievedUsers: + continue + + infoMsg = "fetching number of roles " + infoMsg += "for user '%s'" % user + logger.info(infoMsg) + + if unescapedUser: + queryUser = unescapedUser + else: + queryUser = user + + if 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 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 += "roles for user '%s'" % user + logger.warn(warnMsg) + continue + + infoMsg = "fetching roles for user '%s'" % user + logger.info(infoMsg) + + roles = set() + + indexRange = getRange(count, plusOne=True) + + for index in indexRange: + if query2: + query = rootQuery["blind"]["query2"] % (queryUser, index) + else: + query = rootQuery["blind"]["query"] % (queryUser, index) + role = inject.getValue(query, inband=False) + + # In Oracle we get the list of roles as string + roles.add(role) + + if roles: + kb.data.cachedUsersRoles[user] = list(roles) + else: + warnMsg = "unable to retrieve the roles " + warnMsg += "for user '%s'" % user + logger.warn(warnMsg) + + retrievedUsers.add(user) + + if not kb.data.cachedUsersRoles: + errMsg = "unable to retrieve the roles " + errMsg += "for the database users" + raise sqlmapNoneDataException, errMsg + + return ( kb.data.cachedUsersRoles, areAdmins ) + def getDbs(self): warnMsg = "on Oracle it is not possible to enumerate databases" logger.warn(warnMsg) diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 7d3f4a51a..bd47f563e 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -60,6 +60,7 @@ class Enumeration: kb.data.cachedUsers = [] kb.data.cachedUsersPasswords = {} kb.data.cachedUsersPrivileges = {} + kb.data.cachedUsersRoles = {} kb.data.cachedDbs = [] kb.data.cachedTables = {} kb.data.cachedColumns = {} @@ -327,9 +328,14 @@ class Enumeration: # 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): + def getPrivileges(self, query2=False): infoMsg = "fetching database users privileges" rootQuery = queries[kb.dbms].privileges @@ -377,7 +383,7 @@ class Enumeration: ( 2, "super" ), ( 3, "catupd" ), ) - + firebirdPrivs = { "S": "SELECT", "I": "INSERT", @@ -391,38 +397,32 @@ class Enumeration: 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: - if "," in 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) + 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: - if kb.dbms == "MySQL": - parsedUser = re.search("[\047]*(.*?)[\047]*\@", conf.user) - - if parsedUser: - conf.user = parsedUser.groups()[0] - - # 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 += " WHERE %s LIKE '%s'" % (condition, queryUser) - else: - query += " WHERE %s = '%s'" % (condition, conf.user) + 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 @@ -482,13 +482,8 @@ class Enumeration: conf.user = parsedUser.groups()[0] users = [ "%" + conf.user + "%" ] - - elif "," in conf.user: - users = conf.user.split(",") - else: - users = [ conf.user ] - + users = conf.user.split(",") else: if not len(kb.data.cachedUsers): users = self.getUsers() @@ -519,11 +514,19 @@ class Enumeration: 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) @@ -545,6 +548,8 @@ class Enumeration: 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: @@ -585,6 +590,8 @@ class Enumeration: privileges.add(mysqlPriv) i += 1 + + # In Firebird we get one letter for each privilege elif kb.dbms == "Firebird": privileges.add(firebirdPrivs[privilege.strip()]) @@ -613,6 +620,11 @@ class Enumeration: 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" + self.getPrivileges(query2) + def getDbs(self): if kb.dbms == "MySQL" and not kb.data.has_information_schema: warnMsg = "information_schema not available, " diff --git a/sqlmap.conf b/sqlmap.conf index 9e1466447..09c38a99d 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -248,6 +248,10 @@ getPasswordHashes = False # Valid: True or False getPrivileges = False +# Enumerate back-end database management system users roles. +# Valid: True or False +getRoles = False + # Enumerate back-end database management system databases. # Valid: True or False getDbs = False diff --git a/xml/queries.xml b/xml/queries.xml index 1b476aa51..9a7b25352 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -42,6 +42,7 @@ + @@ -83,9 +84,13 @@ - + - + + @@ -94,10 +99,22 @@ + - - + + + + + + + @@ -160,6 +177,7 @@ + @@ -214,6 +232,7 @@ + @@ -265,6 +284,7 @@ + @@ -339,6 +359,7 @@ +