From 09ca578ca16ba1afe1c9dc21f8968cfc03d0ee94 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Sun, 2 Nov 2008 18:17:12 +0000 Subject: [PATCH] Major bug fix so that the users' privileges enumeration now works properly also on both MySQL < 5.0 and MySQL >= 5.0 also if the user has provided one or more users with -U option; --- doc/ChangeLog | 2 +- lib/core/unescaper.py | 4 +- plugins/dbms/mssqlserver.py | 41 ++++++++++--------- plugins/dbms/mysql.py | 41 +++++++++++-------- plugins/dbms/oracle.py | 41 ++++++++++--------- plugins/dbms/postgresql.py | 40 ++++++++++--------- plugins/generic/enumeration.py | 72 ++++++++++++++++++++++------------ xml/queries.xml | 5 ++- 8 files changed, 144 insertions(+), 102 deletions(-) diff --git a/doc/ChangeLog b/doc/ChangeLog index 314f13316..cd0c4edd5 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -3,7 +3,7 @@ sqlmap (0.6.2-1) stable; urgency=low * Major bug fix to correctly dump tables entries when --stop is not specified; * Major bug fix so that the users' privileges enumeration now works - properly also on MySQL < 5.0; + properly also on both MySQL < 5.0 and MySQL >= 5.0; * Major bug fix when the request is POST to also send the url parameters if any have been provided; * Major improvement to correctly enumerate tables, columns and dump diff --git a/lib/core/unescaper.py b/lib/core/unescaper.py index 586406fd7..0dbc9465f 100644 --- a/lib/core/unescaper.py +++ b/lib/core/unescaper.py @@ -33,8 +33,8 @@ class Unescaper: self.__unescaper = unescapeFunction - def unescape(self, expression): - return self.__unescaper(expression) + def unescape(self, expression, quote=True): + return self.__unescaper(expression, quote=quote) unescaper = Unescaper() diff --git a/plugins/dbms/mssqlserver.py b/plugins/dbms/mssqlserver.py index 32c229043..45f124417 100644 --- a/plugins/dbms/mssqlserver.py +++ b/plugins/dbms/mssqlserver.py @@ -67,30 +67,33 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover): @staticmethod - def unescape(expression): - while True: - index = expression.find("'") - if index == -1: - break + def unescape(expression, quote=True): + if quote: + while True: + index = expression.find("'") + if index == -1: + break - firstIndex = index + 1 - index = expression[firstIndex:].find("'") + firstIndex = index + 1 + index = expression[firstIndex:].find("'") - if index == -1: - raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression - lastIndex = firstIndex + index - old = "'%s'" % expression[firstIndex:lastIndex] - #unescaped = "" - unescaped = "(" + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + #unescaped = "(" + unescaped = "" - for i in range(firstIndex, lastIndex): - unescaped += "CHAR(%d)" % (ord(expression[i])) - if i < lastIndex - 1: - unescaped += "+" + for i in range(firstIndex, lastIndex): + unescaped += "CHAR(%d)" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "+" - unescaped += ")" - expression = expression.replace(old, unescaped) + #unescaped += ")" + expression = expression.replace(old, unescaped) + else: + expression = "+".join("CHAR(%d)" % ord(c) for c in expression) return expression diff --git a/plugins/dbms/mysql.py b/plugins/dbms/mysql.py index 77129ebfd..81452b1da 100644 --- a/plugins/dbms/mysql.py +++ b/plugins/dbms/mysql.py @@ -66,28 +66,35 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): @staticmethod - def unescape(expression): - while True: - index = expression.find("'") - if index == -1: - break + def unescape(expression, quote=True): + if quote: + while True: + index = expression.find("'") + if index == -1: + break - firstIndex = index + 1 - index = expression[firstIndex:].find("'") + firstIndex = index + 1 + index = expression[firstIndex:].find("'") - if index == -1: - raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression - lastIndex = firstIndex + index - old = "'%s'" % expression[firstIndex:lastIndex] - unescaped = "" + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + unescaped = "" - for i in range(firstIndex, lastIndex): - unescaped += "%d" % (ord(expression[i])) - if i < lastIndex - 1: - unescaped += "," + for i in range(firstIndex, lastIndex): + unescaped += "%d" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "," - expression = expression.replace(old, "CHAR(%s)" % unescaped) + expression = expression.replace(old, "CHAR(%s)" % unescaped) + else: + unescaped = "CHAR(" + unescaped += ",".join("%d" % ord(c) for c in expression) + unescaped += ")" + + expression = unescaped return expression diff --git a/plugins/dbms/oracle.py b/plugins/dbms/oracle.py index 57ee93dff..ac98f2a37 100644 --- a/plugins/dbms/oracle.py +++ b/plugins/dbms/oracle.py @@ -59,30 +59,33 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover): @staticmethod - def unescape(expression): - while True: - index = expression.find("'") - if index == -1: - break + def unescape(expression, quote=True): + if quote: + while True: + index = expression.find("'") + if index == -1: + break - firstIndex = index + 1 - index = expression[firstIndex:].find("'") + firstIndex = index + 1 + index = expression[firstIndex:].find("'") - if index == -1: - raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression - lastIndex = firstIndex + index - old = "'%s'" % expression[firstIndex:lastIndex] - #unescaped = "" - unescaped = "(" + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + #unescaped = "(" + unescaped = "" - for i in range(firstIndex, lastIndex): - unescaped += "CHR(%d)" % (ord(expression[i])) - if i < lastIndex - 1: - unescaped += "||" + for i in range(firstIndex, lastIndex): + unescaped += "CHR(%d)" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "||" - unescaped += ")" - expression = expression.replace(old, unescaped) + #unescaped += ")" + expression = expression.replace(old, unescaped) + else: + expression = "||".join("CHR(%d)" % ord(c) for c in expression) return expression diff --git a/plugins/dbms/postgresql.py b/plugins/dbms/postgresql.py index 0164507c6..ca1ad85f0 100644 --- a/plugins/dbms/postgresql.py +++ b/plugins/dbms/postgresql.py @@ -59,29 +59,33 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover): @staticmethod - def unescape(expression): - while True: - index = expression.find("'") - if index == -1: - break + def unescape(expression, quote=True): + if quote: + while True: + index = expression.find("'") + if index == -1: + break - firstIndex = index + 1 - index = expression[firstIndex:].find("'") + firstIndex = index + 1 + index = expression[firstIndex:].find("'") - if index == -1: - raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression - lastIndex = firstIndex + index - old = "'%s'" % expression[firstIndex:lastIndex] - unescaped = "(" + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + #unescaped = "(" + unescaped = "" - for i in range(firstIndex, lastIndex): - unescaped += "CHR(%d)" % (ord(expression[i])) - if i < lastIndex - 1: - unescaped += "||" + for i in range(firstIndex, lastIndex): + unescaped += "CHR(%d)" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "||" - unescaped += ")" - expression = expression.replace(old, unescaped) + #unescaped += ")" + expression = expression.replace(old, unescaped) + else: + expression = "||".join("CHR(%d)" % ord(c) for c in expression) return expression diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 823f0676e..4c021e8b7 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -40,6 +40,7 @@ from lib.core.exception import sqlmapNoneDataException from lib.core.exception import sqlmapUndefinedMethod from lib.core.exception import sqlmapUnsupportedFeatureException from lib.core.shell import autoCompletion +from lib.core.unescaper import unescaper from lib.request import inject from lib.request.connect import Connect as Request @@ -346,19 +347,19 @@ class Enumeration: if "," in conf.user: users = conf.user.split(",") query += " WHERE " - # NOTE: we need this here only for MySQL 5.0 because - # of a known issue explained in queries.xml + # NOTE: I assume that the user provided is not in + # MySQL >= 5.0 syntax 'user'@'host' if kb.dbms == "MySQL" and self.has_information_schema: - likeUser = "%" + conf.user + "%" + 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) else: - # NOTE: we need this here only for MySQL 5.0 because - # of a known issue explained in queries.xml + # NOTE: I assume that the user provided is not in + # MySQL >= 5.0 syntax 'user'@'host' if kb.dbms == "MySQL" and self.has_information_schema: - likeUser = "%" + conf.user + "%" - query += " WHERE %s LIKE '%s'" % (condition, likeUser) + queryUser = "%" + conf.user + "%" + query += " WHERE %s LIKE '%s'" % (condition, queryUser) else: query += " WHERE %s = '%s'" % (condition, conf.user) @@ -406,11 +407,25 @@ class Enumeration: self.cachedUsersPrivileges[user] = list(privileges) if not self.cachedUsersPrivileges: + conditionChar = "=" + if conf.user: - if "," in conf.user: + if kb.dbms == "MySQL" and self.has_information_schema: + conditionChar = " LIKE " + + if "," in conf.user: + users = set() + for user in conf.user.split(","): + users.add("%" + user + "%") + else: + users = [ "%" + conf.user + "%" ] + + elif "," in conf.user: users = conf.user.split(",") + else: - users = [conf.user] + users = [ conf.user ] + else: if not len(self.cachedUsers): users = self.getUsers() @@ -420,11 +435,10 @@ class Enumeration: retrievedUsers = set() for user in users: - if kb.dbms == "MySQL": - parsedUser = re.search("\047(.*?)\047@'", user) + unescapedUser = None - if parsedUser: - user = parsedUser.groups()[0].replace("'", "") + if kb.dbms == "MySQL" and self.has_information_schema: + unescapedUser = unescaper.unescape(user, quote=False) if user in retrievedUsers: continue @@ -433,24 +447,26 @@ class Enumeration: logMsg += "for user '%s'" % user logger.info(logMsg) - if kb.dbms == "MySQL" and self.has_information_schema: - likeUser = "%" + user + "%" + if unescapedUser: + queryUser = unescapedUser else: - likeUser = user + queryUser = user if kb.dbms == "MySQL" and not self.has_information_schema: - query = rootQuery["blind"]["count2"] % likeUser + query = rootQuery["blind"]["count2"] % queryUser + elif kb.dbms == "MySQL" and self.has_information_schema: + query = rootQuery["blind"]["count"] % (conditionChar, queryUser) else: - query = rootQuery["blind"]["count"] % likeUser + query = rootQuery["blind"]["count"] % queryUser count = inject.getValue(query, inband=False) if not len(count) or count == "0": warnMsg = "unable to retrieve the number of " - warnMsg += "privileges for user '%s'" % likeUser + warnMsg += "privileges for user '%s'" % user logger.warn(warnMsg) continue - logMsg = "fetching privileges for user '%s'" % likeUser + logMsg = "fetching privileges for user '%s'" % user logger.info(logMsg) privileges = set() @@ -458,13 +474,15 @@ class Enumeration: for index in indexRange: if kb.dbms == "MySQL" and not self.has_information_schema: - query = rootQuery["blind"]["query2"] % (likeUser, index) + query = rootQuery["blind"]["query2"] % (queryUser, index) + elif kb.dbms == "MySQL" and self.has_information_schema: + query = rootQuery["blind"]["query"] % (conditionChar, queryUser, index) else: - query = rootQuery["blind"]["query"] % (likeUser, index) + query = rootQuery["blind"]["query"] % (queryUser, index) privilege = inject.getValue(query, inband=False) - # In PostgreSQL we return 1 if the privilege - # if True, otherwise 0 + # 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(",") @@ -501,6 +519,12 @@ class Enumeration: 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 self.has_information_schema: + break + if privileges: self.cachedUsersPrivileges[user] = list(privileges) else: diff --git a/xml/queries.xml b/xml/queries.xml index ad2a64123..0e987b35c 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -29,8 +29,7 @@ - - + @@ -177,10 +176,12 @@ + +