Skip to content

Commit 09ca578

Browse files
committed
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;
1 parent 91a4724 commit 09ca578

File tree

8 files changed

+159
-117
lines changed

8 files changed

+159
-117
lines changed

doc/ChangeLog

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ sqlmap (0.6.2-1) stable; urgency=low
33
* Major bug fix to correctly dump tables entries when --stop is not
44
specified;
55
* Major bug fix so that the users' privileges enumeration now works
6-
properly also on MySQL < 5.0;
6+
properly also on both MySQL < 5.0 and MySQL >= 5.0;
77
* Major bug fix when the request is POST to also send the url parameters
88
if any have been provided;
99
* Major improvement to correctly enumerate tables, columns and dump

lib/core/unescaper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def setUnescape(self, unescapeFunction):
3333
self.__unescaper = unescapeFunction
3434

3535

36-
def unescape(self, expression):
37-
return self.__unescaper(expression)
36+
def unescape(self, expression, quote=True):
37+
return self.__unescaper(expression, quote=quote)
3838

3939

4040
unescaper = Unescaper()

plugins/dbms/mssqlserver.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,30 +67,33 @@ def __init__(self):
6767

6868

6969
@staticmethod
70-
def unescape(expression):
71-
while True:
72-
index = expression.find("'")
73-
if index == -1:
74-
break
75-
76-
firstIndex = index + 1
77-
index = expression[firstIndex:].find("'")
78-
79-
if index == -1:
80-
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
81-
82-
lastIndex = firstIndex + index
83-
old = "'%s'" % expression[firstIndex:lastIndex]
84-
#unescaped = ""
85-
unescaped = "("
86-
87-
for i in range(firstIndex, lastIndex):
88-
unescaped += "CHAR(%d)" % (ord(expression[i]))
89-
if i < lastIndex - 1:
90-
unescaped += "+"
91-
92-
unescaped += ")"
93-
expression = expression.replace(old, unescaped)
70+
def unescape(expression, quote=True):
71+
if quote:
72+
while True:
73+
index = expression.find("'")
74+
if index == -1:
75+
break
76+
77+
firstIndex = index + 1
78+
index = expression[firstIndex:].find("'")
79+
80+
if index == -1:
81+
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
82+
83+
lastIndex = firstIndex + index
84+
old = "'%s'" % expression[firstIndex:lastIndex]
85+
#unescaped = "("
86+
unescaped = ""
87+
88+
for i in range(firstIndex, lastIndex):
89+
unescaped += "CHAR(%d)" % (ord(expression[i]))
90+
if i < lastIndex - 1:
91+
unescaped += "+"
92+
93+
#unescaped += ")"
94+
expression = expression.replace(old, unescaped)
95+
else:
96+
expression = "+".join("CHAR(%d)" % ord(c) for c in expression)
9497

9598
return expression
9699

plugins/dbms/mysql.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,28 +66,35 @@ def __init__(self):
6666

6767

6868
@staticmethod
69-
def unescape(expression):
70-
while True:
71-
index = expression.find("'")
72-
if index == -1:
73-
break
69+
def unescape(expression, quote=True):
70+
if quote:
71+
while True:
72+
index = expression.find("'")
73+
if index == -1:
74+
break
7475

75-
firstIndex = index + 1
76-
index = expression[firstIndex:].find("'")
76+
firstIndex = index + 1
77+
index = expression[firstIndex:].find("'")
7778

78-
if index == -1:
79-
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
79+
if index == -1:
80+
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
8081

81-
lastIndex = firstIndex + index
82-
old = "'%s'" % expression[firstIndex:lastIndex]
83-
unescaped = ""
82+
lastIndex = firstIndex + index
83+
old = "'%s'" % expression[firstIndex:lastIndex]
84+
unescaped = ""
8485

85-
for i in range(firstIndex, lastIndex):
86-
unescaped += "%d" % (ord(expression[i]))
87-
if i < lastIndex - 1:
88-
unescaped += ","
86+
for i in range(firstIndex, lastIndex):
87+
unescaped += "%d" % (ord(expression[i]))
88+
if i < lastIndex - 1:
89+
unescaped += ","
90+
91+
expression = expression.replace(old, "CHAR(%s)" % unescaped)
92+
else:
93+
unescaped = "CHAR("
94+
unescaped += ",".join("%d" % ord(c) for c in expression)
95+
unescaped += ")"
8996

90-
expression = expression.replace(old, "CHAR(%s)" % unescaped)
97+
expression = unescaped
9198

9299
return expression
93100

plugins/dbms/oracle.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,30 +59,33 @@ def __init__(self):
5959

6060

6161
@staticmethod
62-
def unescape(expression):
63-
while True:
64-
index = expression.find("'")
65-
if index == -1:
66-
break
67-
68-
firstIndex = index + 1
69-
index = expression[firstIndex:].find("'")
70-
71-
if index == -1:
72-
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
73-
74-
lastIndex = firstIndex + index
75-
old = "'%s'" % expression[firstIndex:lastIndex]
76-
#unescaped = ""
77-
unescaped = "("
78-
79-
for i in range(firstIndex, lastIndex):
80-
unescaped += "CHR(%d)" % (ord(expression[i]))
81-
if i < lastIndex - 1:
82-
unescaped += "||"
83-
84-
unescaped += ")"
85-
expression = expression.replace(old, unescaped)
62+
def unescape(expression, quote=True):
63+
if quote:
64+
while True:
65+
index = expression.find("'")
66+
if index == -1:
67+
break
68+
69+
firstIndex = index + 1
70+
index = expression[firstIndex:].find("'")
71+
72+
if index == -1:
73+
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
74+
75+
lastIndex = firstIndex + index
76+
old = "'%s'" % expression[firstIndex:lastIndex]
77+
#unescaped = "("
78+
unescaped = ""
79+
80+
for i in range(firstIndex, lastIndex):
81+
unescaped += "CHR(%d)" % (ord(expression[i]))
82+
if i < lastIndex - 1:
83+
unescaped += "||"
84+
85+
#unescaped += ")"
86+
expression = expression.replace(old, unescaped)
87+
else:
88+
expression = "||".join("CHR(%d)" % ord(c) for c in expression)
8689

8790
return expression
8891

plugins/dbms/postgresql.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,29 +59,33 @@ def __init__(self):
5959

6060

6161
@staticmethod
62-
def unescape(expression):
63-
while True:
64-
index = expression.find("'")
65-
if index == -1:
66-
break
67-
68-
firstIndex = index + 1
69-
index = expression[firstIndex:].find("'")
70-
71-
if index == -1:
72-
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
73-
74-
lastIndex = firstIndex + index
75-
old = "'%s'" % expression[firstIndex:lastIndex]
76-
unescaped = "("
77-
78-
for i in range(firstIndex, lastIndex):
79-
unescaped += "CHR(%d)" % (ord(expression[i]))
80-
if i < lastIndex - 1:
81-
unescaped += "||"
82-
83-
unescaped += ")"
84-
expression = expression.replace(old, unescaped)
62+
def unescape(expression, quote=True):
63+
if quote:
64+
while True:
65+
index = expression.find("'")
66+
if index == -1:
67+
break
68+
69+
firstIndex = index + 1
70+
index = expression[firstIndex:].find("'")
71+
72+
if index == -1:
73+
raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression
74+
75+
lastIndex = firstIndex + index
76+
old = "'%s'" % expression[firstIndex:lastIndex]
77+
#unescaped = "("
78+
unescaped = ""
79+
80+
for i in range(firstIndex, lastIndex):
81+
unescaped += "CHR(%d)" % (ord(expression[i]))
82+
if i < lastIndex - 1:
83+
unescaped += "||"
84+
85+
#unescaped += ")"
86+
expression = expression.replace(old, unescaped)
87+
else:
88+
expression = "||".join("CHR(%d)" % ord(c) for c in expression)
8589

8690
return expression
8791

plugins/generic/enumeration.py

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from lib.core.exception import sqlmapUndefinedMethod
4141
from lib.core.exception import sqlmapUnsupportedFeatureException
4242
from lib.core.shell import autoCompletion
43+
from lib.core.unescaper import unescaper
4344
from lib.request import inject
4445
from lib.request.connect import Connect as Request
4546

@@ -346,19 +347,19 @@ def getPrivileges(self):
346347
if "," in conf.user:
347348
users = conf.user.split(",")
348349
query += " WHERE "
349-
# NOTE: we need this here only for MySQL 5.0 because
350-
# of a known issue explained in queries.xml
350+
# NOTE: I assume that the user provided is not in
351+
# MySQL >= 5.0 syntax 'user'@'host'
351352
if kb.dbms == "MySQL" and self.has_information_schema:
352-
likeUser = "%" + conf.user + "%"
353+
queryUser = "%" + conf.user + "%"
353354
query += " OR ".join("%s LIKE '%s'" % (condition, "%" + user + "%") for user in users)
354355
else:
355356
query += " OR ".join("%s = '%s'" % (condition, user) for user in users)
356357
else:
357-
# NOTE: we need this here only for MySQL 5.0 because
358-
# of a known issue explained in queries.xml
358+
# NOTE: I assume that the user provided is not in
359+
# MySQL >= 5.0 syntax 'user'@'host'
359360
if kb.dbms == "MySQL" and self.has_information_schema:
360-
likeUser = "%" + conf.user + "%"
361-
query += " WHERE %s LIKE '%s'" % (condition, likeUser)
361+
queryUser = "%" + conf.user + "%"
362+
query += " WHERE %s LIKE '%s'" % (condition, queryUser)
362363
else:
363364
query += " WHERE %s = '%s'" % (condition, conf.user)
364365

@@ -406,11 +407,25 @@ def getPrivileges(self):
406407
self.cachedUsersPrivileges[user] = list(privileges)
407408

408409
if not self.cachedUsersPrivileges:
410+
conditionChar = "="
411+
409412
if conf.user:
410-
if "," in conf.user:
413+
if kb.dbms == "MySQL" and self.has_information_schema:
414+
conditionChar = " LIKE "
415+
416+
if "," in conf.user:
417+
users = set()
418+
for user in conf.user.split(","):
419+
users.add("%" + user + "%")
420+
else:
421+
users = [ "%" + conf.user + "%" ]
422+
423+
elif "," in conf.user:
411424
users = conf.user.split(",")
425+
412426
else:
413-
users = [conf.user]
427+
users = [ conf.user ]
428+
414429
else:
415430
if not len(self.cachedUsers):
416431
users = self.getUsers()
@@ -420,11 +435,10 @@ def getPrivileges(self):
420435
retrievedUsers = set()
421436

422437
for user in users:
423-
if kb.dbms == "MySQL":
424-
parsedUser = re.search("\047(.*?)\047@'", user)
438+
unescapedUser = None
425439

426-
if parsedUser:
427-
user = parsedUser.groups()[0].replace("'", "")
440+
if kb.dbms == "MySQL" and self.has_information_schema:
441+
unescapedUser = unescaper.unescape(user, quote=False)
428442

429443
if user in retrievedUsers:
430444
continue
@@ -433,38 +447,42 @@ def getPrivileges(self):
433447
logMsg += "for user '%s'" % user
434448
logger.info(logMsg)
435449

436-
if kb.dbms == "MySQL" and self.has_information_schema:
437-
likeUser = "%" + user + "%"
450+
if unescapedUser:
451+
queryUser = unescapedUser
438452
else:
439-
likeUser = user
453+
queryUser = user
440454

441455
if kb.dbms == "MySQL" and not self.has_information_schema:
442-
query = rootQuery["blind"]["count2"] % likeUser
456+
query = rootQuery["blind"]["count2"] % queryUser
457+
elif kb.dbms == "MySQL" and self.has_information_schema:
458+
query = rootQuery["blind"]["count"] % (conditionChar, queryUser)
443459
else:
444-
query = rootQuery["blind"]["count"] % likeUser
460+
query = rootQuery["blind"]["count"] % queryUser
445461
count = inject.getValue(query, inband=False)
446462

447463
if not len(count) or count == "0":
448464
warnMsg = "unable to retrieve the number of "
449-
warnMsg += "privileges for user '%s'" % likeUser
465+
warnMsg += "privileges for user '%s'" % user
450466
logger.warn(warnMsg)
451467
continue
452468

453-
logMsg = "fetching privileges for user '%s'" % likeUser
469+
logMsg = "fetching privileges for user '%s'" % user
454470
logger.info(logMsg)
455471

456472
privileges = set()
457473
indexRange = getRange(count)
458474

459475
for index in indexRange:
460476
if kb.dbms == "MySQL" and not self.has_information_schema:
461-
query = rootQuery["blind"]["query2"] % (likeUser, index)
477+
query = rootQuery["blind"]["query2"] % (queryUser, index)
478+
elif kb.dbms == "MySQL" and self.has_information_schema:
479+
query = rootQuery["blind"]["query"] % (conditionChar, queryUser, index)
462480
else:
463-
query = rootQuery["blind"]["query"] % (likeUser, index)
481+
query = rootQuery["blind"]["query"] % (queryUser, index)
464482
privilege = inject.getValue(query, inband=False)
465483

466-
# In PostgreSQL we return 1 if the privilege
467-
# if True, otherwise 0
484+
# In PostgreSQL we get 1 if the privilege is True,
485+
# 0 otherwise
468486
if kb.dbms == "PostgreSQL" and ", " in privilege:
469487
privilege = privilege.replace(", ", ",")
470488
privs = privilege.split(",")
@@ -501,6 +519,12 @@ def getPrivileges(self):
501519
if self.__isAdminFromPrivileges(privileges):
502520
areAdmins.add(user)
503521

522+
# In MySQL < 5.0 we break the cycle after the first
523+
# time we get the user's privileges otherwise we
524+
# duplicate the same query
525+
if kb.dbms == "MySQL" and not self.has_information_schema:
526+
break
527+
504528
if privileges:
505529
self.cachedUsersPrivileges[user] = list(privileges)
506530
else:

0 commit comments

Comments
 (0)