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;

This commit is contained in:
Bernardo Damele 2008-11-02 18:17:12 +00:00
parent 91a47246f8
commit 09ca578ca1
8 changed files with 144 additions and 102 deletions

View File

@ -3,7 +3,7 @@ sqlmap (0.6.2-1) stable; urgency=low
* Major bug fix to correctly dump tables entries when --stop is not * Major bug fix to correctly dump tables entries when --stop is not
specified; specified;
* Major bug fix so that the users' privileges enumeration now works * 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 * Major bug fix when the request is POST to also send the url parameters
if any have been provided; if any have been provided;
* Major improvement to correctly enumerate tables, columns and dump * Major improvement to correctly enumerate tables, columns and dump

View File

@ -33,8 +33,8 @@ class Unescaper:
self.__unescaper = unescapeFunction self.__unescaper = unescapeFunction
def unescape(self, expression): def unescape(self, expression, quote=True):
return self.__unescaper(expression) return self.__unescaper(expression, quote=quote)
unescaper = Unescaper() unescaper = Unescaper()

View File

@ -67,7 +67,8 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
@staticmethod @staticmethod
def unescape(expression): def unescape(expression, quote=True):
if quote:
while True: while True:
index = expression.find("'") index = expression.find("'")
if index == -1: if index == -1:
@ -81,16 +82,18 @@ class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover):
lastIndex = firstIndex + index lastIndex = firstIndex + index
old = "'%s'" % expression[firstIndex:lastIndex] old = "'%s'" % expression[firstIndex:lastIndex]
#unescaped = "" #unescaped = "("
unescaped = "(" unescaped = ""
for i in range(firstIndex, lastIndex): for i in range(firstIndex, lastIndex):
unescaped += "CHAR(%d)" % (ord(expression[i])) unescaped += "CHAR(%d)" % (ord(expression[i]))
if i < lastIndex - 1: if i < lastIndex - 1:
unescaped += "+" unescaped += "+"
unescaped += ")" #unescaped += ")"
expression = expression.replace(old, unescaped) expression = expression.replace(old, unescaped)
else:
expression = "+".join("CHAR(%d)" % ord(c) for c in expression)
return expression return expression

View File

@ -66,7 +66,8 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
@staticmethod @staticmethod
def unescape(expression): def unescape(expression, quote=True):
if quote:
while True: while True:
index = expression.find("'") index = expression.find("'")
if index == -1: if index == -1:
@ -88,6 +89,12 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
unescaped += "," 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 return expression

View File

@ -59,7 +59,8 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
@staticmethod @staticmethod
def unescape(expression): def unescape(expression, quote=True):
if quote:
while True: while True:
index = expression.find("'") index = expression.find("'")
if index == -1: if index == -1:
@ -73,16 +74,18 @@ class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover):
lastIndex = firstIndex + index lastIndex = firstIndex + index
old = "'%s'" % expression[firstIndex:lastIndex] old = "'%s'" % expression[firstIndex:lastIndex]
#unescaped = "" #unescaped = "("
unescaped = "(" unescaped = ""
for i in range(firstIndex, lastIndex): for i in range(firstIndex, lastIndex):
unescaped += "CHR(%d)" % (ord(expression[i])) unescaped += "CHR(%d)" % (ord(expression[i]))
if i < lastIndex - 1: if i < lastIndex - 1:
unescaped += "||" unescaped += "||"
unescaped += ")" #unescaped += ")"
expression = expression.replace(old, unescaped) expression = expression.replace(old, unescaped)
else:
expression = "||".join("CHR(%d)" % ord(c) for c in expression)
return expression return expression

View File

@ -59,7 +59,8 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
@staticmethod @staticmethod
def unescape(expression): def unescape(expression, quote=True):
if quote:
while True: while True:
index = expression.find("'") index = expression.find("'")
if index == -1: if index == -1:
@ -73,15 +74,18 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
lastIndex = firstIndex + index lastIndex = firstIndex + index
old = "'%s'" % expression[firstIndex:lastIndex] old = "'%s'" % expression[firstIndex:lastIndex]
unescaped = "(" #unescaped = "("
unescaped = ""
for i in range(firstIndex, lastIndex): for i in range(firstIndex, lastIndex):
unescaped += "CHR(%d)" % (ord(expression[i])) unescaped += "CHR(%d)" % (ord(expression[i]))
if i < lastIndex - 1: if i < lastIndex - 1:
unescaped += "||" unescaped += "||"
unescaped += ")" #unescaped += ")"
expression = expression.replace(old, unescaped) expression = expression.replace(old, unescaped)
else:
expression = "||".join("CHR(%d)" % ord(c) for c in expression)
return expression return expression

View File

@ -40,6 +40,7 @@ from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapUndefinedMethod from lib.core.exception import sqlmapUndefinedMethod
from lib.core.exception import sqlmapUnsupportedFeatureException from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.core.shell import autoCompletion from lib.core.shell import autoCompletion
from lib.core.unescaper import unescaper
from lib.request import inject from lib.request import inject
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
@ -346,19 +347,19 @@ class Enumeration:
if "," in conf.user: if "," in conf.user:
users = conf.user.split(",") users = conf.user.split(",")
query += " WHERE " query += " WHERE "
# NOTE: we need this here only for MySQL 5.0 because # NOTE: I assume that the user provided is not in
# of a known issue explained in queries.xml # MySQL >= 5.0 syntax 'user'@'host'
if kb.dbms == "MySQL" and self.has_information_schema: 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) query += " OR ".join("%s LIKE '%s'" % (condition, "%" + user + "%") for user in users)
else: else:
query += " OR ".join("%s = '%s'" % (condition, user) for user in users) query += " OR ".join("%s = '%s'" % (condition, user) for user in users)
else: else:
# NOTE: we need this here only for MySQL 5.0 because # NOTE: I assume that the user provided is not in
# of a known issue explained in queries.xml # MySQL >= 5.0 syntax 'user'@'host'
if kb.dbms == "MySQL" and self.has_information_schema: if kb.dbms == "MySQL" and self.has_information_schema:
likeUser = "%" + conf.user + "%" queryUser = "%" + conf.user + "%"
query += " WHERE %s LIKE '%s'" % (condition, likeUser) query += " WHERE %s LIKE '%s'" % (condition, queryUser)
else: else:
query += " WHERE %s = '%s'" % (condition, conf.user) query += " WHERE %s = '%s'" % (condition, conf.user)
@ -406,11 +407,25 @@ class Enumeration:
self.cachedUsersPrivileges[user] = list(privileges) self.cachedUsersPrivileges[user] = list(privileges)
if not self.cachedUsersPrivileges: if not self.cachedUsersPrivileges:
conditionChar = "="
if conf.user: if conf.user:
if kb.dbms == "MySQL" and self.has_information_schema:
conditionChar = " LIKE "
if "," in conf.user: 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(",") users = conf.user.split(",")
else: else:
users = [ conf.user ] users = [ conf.user ]
else: else:
if not len(self.cachedUsers): if not len(self.cachedUsers):
users = self.getUsers() users = self.getUsers()
@ -420,11 +435,10 @@ class Enumeration:
retrievedUsers = set() retrievedUsers = set()
for user in users: for user in users:
if kb.dbms == "MySQL": unescapedUser = None
parsedUser = re.search("\047(.*?)\047@'", user)
if parsedUser: if kb.dbms == "MySQL" and self.has_information_schema:
user = parsedUser.groups()[0].replace("'", "") unescapedUser = unescaper.unescape(user, quote=False)
if user in retrievedUsers: if user in retrievedUsers:
continue continue
@ -433,24 +447,26 @@ class Enumeration:
logMsg += "for user '%s'" % user logMsg += "for user '%s'" % user
logger.info(logMsg) logger.info(logMsg)
if kb.dbms == "MySQL" and self.has_information_schema: if unescapedUser:
likeUser = "%" + user + "%" queryUser = unescapedUser
else: else:
likeUser = user queryUser = user
if kb.dbms == "MySQL" and not self.has_information_schema: 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: else:
query = rootQuery["blind"]["count"] % likeUser query = rootQuery["blind"]["count"] % queryUser
count = inject.getValue(query, inband=False) count = inject.getValue(query, inband=False)
if not len(count) or count == "0": if not len(count) or count == "0":
warnMsg = "unable to retrieve the number of " warnMsg = "unable to retrieve the number of "
warnMsg += "privileges for user '%s'" % likeUser warnMsg += "privileges for user '%s'" % user
logger.warn(warnMsg) logger.warn(warnMsg)
continue continue
logMsg = "fetching privileges for user '%s'" % likeUser logMsg = "fetching privileges for user '%s'" % user
logger.info(logMsg) logger.info(logMsg)
privileges = set() privileges = set()
@ -458,13 +474,15 @@ class Enumeration:
for index in indexRange: for index in indexRange:
if kb.dbms == "MySQL" and not self.has_information_schema: 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: else:
query = rootQuery["blind"]["query"] % (likeUser, index) query = rootQuery["blind"]["query"] % (queryUser, index)
privilege = inject.getValue(query, inband=False) privilege = inject.getValue(query, inband=False)
# In PostgreSQL we return 1 if the privilege # In PostgreSQL we get 1 if the privilege is True,
# if True, otherwise 0 # 0 otherwise
if kb.dbms == "PostgreSQL" and ", " in privilege: if kb.dbms == "PostgreSQL" and ", " in privilege:
privilege = privilege.replace(", ", ",") privilege = privilege.replace(", ", ",")
privs = privilege.split(",") privs = privilege.split(",")
@ -501,6 +519,12 @@ class Enumeration:
if self.__isAdminFromPrivileges(privileges): if self.__isAdminFromPrivileges(privileges):
areAdmins.add(user) 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: if privileges:
self.cachedUsersPrivileges[user] = list(privileges) self.cachedUsersPrivileges[user] = list(privileges)
else: else:

View File

@ -29,8 +29,7 @@
</passwords> </passwords>
<privileges> <privileges>
<inband query="SELECT grantee, privilege_type FROM information_schema.USER_PRIVILEGES" condition="grantee" query2="SELECT user, select_priv, insert_priv, update_priv, delete_priv, create_priv, drop_priv, reload_priv, shutdown_priv, process_priv, file_priv, grant_priv, references_priv, index_priv, alter_priv, show_db_priv, super_priv, create_tmp_table_priv, lock_tables_priv, execute_priv, repl_slave_priv, repl_client_priv, create_view_priv, show_view_priv, create_routine_priv, alter_routine_priv, create_user_priv FROM mysql.user" condition2="user"/> <inband query="SELECT grantee, privilege_type FROM information_schema.USER_PRIVILEGES" condition="grantee" query2="SELECT user, select_priv, insert_priv, update_priv, delete_priv, create_priv, drop_priv, reload_priv, shutdown_priv, process_priv, file_priv, grant_priv, references_priv, index_priv, alter_priv, show_db_priv, super_priv, create_tmp_table_priv, lock_tables_priv, execute_priv, repl_slave_priv, repl_client_priv, create_view_priv, show_view_priv, create_routine_priv, alter_routine_priv, create_user_priv FROM mysql.user" condition2="user"/>
<!-- TODO: the LIKE clause only affects MySQL >= 5.0. No way so far to use [...] WHERE grantee='%s' in query and count attributes because the unescaper function will forge it as [...] WHERE grantee="CHAR(114,111,111,116)@CHAR(108,111,99,97,108,104,111,115,116)" --> <blind query="SELECT DISTINCT(privilege_type) FROM information_schema.USER_PRIVILEGES WHERE grantee%s%s LIMIT %d, 1" query2="SELECT select_priv, insert_priv, update_priv, delete_priv, create_priv, drop_priv, reload_priv, shutdown_priv, process_priv, file_priv, grant_priv, references_priv, index_priv, alter_priv, show_db_priv, super_priv, create_tmp_table_priv, lock_tables_priv, execute_priv, repl_slave_priv, repl_client_priv, create_view_priv, show_view_priv, create_routine_priv, alter_routine_priv, create_user_priv FROM mysql.user WHERE user='%s' LIMIT %d, 1" count="SELECT COUNT(DISTINCT(privilege_type)) FROM information_schema.USER_PRIVILEGES WHERE grantee%s%s" count2="SELECT COUNT(*) FROM mysql.user WHERE user='%s'"/>
<blind query="SELECT DISTINCT(privilege_type) FROM information_schema.USER_PRIVILEGES WHERE grantee LIKE '%s' LIMIT %d, 1" query2="SELECT select_priv, insert_priv, update_priv, delete_priv, create_priv, drop_priv, reload_priv, shutdown_priv, process_priv, file_priv, grant_priv, references_priv, index_priv, alter_priv, show_db_priv, super_priv, create_tmp_table_priv, lock_tables_priv, execute_priv, repl_slave_priv, repl_client_priv, create_view_priv, show_view_priv, create_routine_priv, alter_routine_priv, create_user_priv FROM mysql.user WHERE user='%s' LIMIT %d, 1" count="SELECT COUNT(DISTINCT(privilege_type)) FROM information_schema.USER_PRIVILEGES WHERE grantee LIKE '%s'" count2="SELECT COUNT(*) FROM mysql.user WHERE user='%s'"/>
</privileges> </privileges>
<dbs> <dbs>
<inband query="SELECT schema_name FROM information_schema.SCHEMATA" query2="SELECT db FROM mysql.db"/> <inband query="SELECT schema_name FROM information_schema.SCHEMATA" query2="SELECT db FROM mysql.db"/>
@ -177,10 +176,12 @@
<inband query="SELECT name FROM master..sysdatabases"/> <inband query="SELECT name FROM master..sysdatabases"/>
<blind query="SELECT TOP 1 name FROM master..sysdatabases WHERE name NOT IN (SELECT TOP %d name FROM master..sysdatabases)" count="SELECT LTRIM(STR(COUNT(name))) FROM master..sysdatabases"/> <blind query="SELECT TOP 1 name FROM master..sysdatabases WHERE name NOT IN (SELECT TOP %d name FROM master..sysdatabases)" count="SELECT LTRIM(STR(COUNT(name))) FROM master..sysdatabases"/>
</dbs> </dbs>
<!-- TODO: condition? -->
<tables> <tables>
<inband query="SELECT name FROM %s..sysobjects WHERE xtype IN ('u', 'v')"/> <inband query="SELECT name FROM %s..sysobjects WHERE xtype IN ('u', 'v')"/>
<blind query="SELECT TOP 1 name FROM %s..sysobjects WHERE xtype IN ('u', 'v') AND name NOT IN (SELECT TOP %d name FROM %s..sysobjects WHERE xtype IN ('u', 'v'))" count="SELECT LTRIM(STR(COUNT(name))) FROM %s..sysobjects WHERE xtype IN ('u', 'v')"/> <blind query="SELECT TOP 1 name FROM %s..sysobjects WHERE xtype IN ('u', 'v') AND name NOT IN (SELECT TOP %d name FROM %s..sysobjects WHERE xtype IN ('u', 'v'))" count="SELECT LTRIM(STR(COUNT(name))) FROM %s..sysobjects WHERE xtype IN ('u', 'v')"/>
</tables> </tables>
<!-- TODO: getRange like Oracle? -->
<columns> <columns>
<inband query="SELECT %s..syscolumns.name, TYPE_NAME(%s..syscolumns.xtype) FROM %s..syscolumns, %s..sysobjects WHERE %s..syscolumns.id=%s..sysobjects.id AND %s..sysobjects.name='%s'"/> <inband query="SELECT %s..syscolumns.name, TYPE_NAME(%s..syscolumns.xtype) FROM %s..syscolumns, %s..sysobjects WHERE %s..syscolumns.id=%s..sysobjects.id AND %s..sysobjects.name='%s'"/>
<blind query="SELECT TOP 1 name FROM (SELECT TOP %s name FROM %s..syscolumns WHERE id=(SELECT id FROM %s..sysobjects WHERE name='%s')) CTABLE" query2="SELECT TYPE_NAME(%s..syscolumns.xtype) FROM %s..syscolumns, %s..sysobjects WHERE %s..syscolumns.name='%s' AND %s..syscolumns.id=%s..sysobjects.id AND %s..sysobjects.name='%s'" count="SELECT LTRIM(STR(COUNT(name))) FROM %s..syscolumns WHERE id=(SELECT id FROM %s..sysobjects WHERE name='%s')"/> <blind query="SELECT TOP 1 name FROM (SELECT TOP %s name FROM %s..syscolumns WHERE id=(SELECT id FROM %s..sysobjects WHERE name='%s')) CTABLE" query2="SELECT TYPE_NAME(%s..syscolumns.xtype) FROM %s..syscolumns, %s..sysobjects WHERE %s..syscolumns.name='%s' AND %s..syscolumns.id=%s..sysobjects.id AND %s..sysobjects.name='%s'" count="SELECT LTRIM(STR(COUNT(name))) FROM %s..syscolumns WHERE id=(SELECT id FROM %s..sysobjects WHERE name='%s')"/>