Bug fixes and proper packing/unpacking of custom statements and predefined queries for both error-based and UNION query techniques.

Now it deals in UNION query also with --start and --stop and resume has been enhanced for both techniques too.
This commit is contained in:
Bernardo Damele 2011-02-01 22:07:42 +00:00
parent 2619e4895f
commit 9b342a4c95
3 changed files with 199 additions and 225 deletions

View File

@ -378,7 +378,7 @@ def __goInband(expression, expected=None, sort=True, resumeValue=True, unpack=Tr
partial = True partial = True
if output is None: if output is None:
output = unionUse(expression, resetCounter=True, unpack=unpack, dump=dump) output = unionUse(expression, unpack=unpack, dump=dump)
if output: if output:
data = parseUnionPage(output, expression, partial, None, sort) data = parseUnionPage(output, expression, partial, None, sort)

View File

@ -120,17 +120,15 @@ def errorUse(expression, expected=None, resumeValue=True, dump=False):
initTechnique(PAYLOAD.TECHNIQUE.ERROR) initTechnique(PAYLOAD.TECHNIQUE.ERROR)
global reqCount
count = None count = None
start = time.time() start = time.time()
startLimit = 0 startLimit = 0
stopLimit = None stopLimit = None
outputs = [] outputs = []
test = None
untilLimitChar = None untilLimitChar = None
untilOrderChar = None untilOrderChar = None
global reqCount
reqCount = 0 reqCount = 0
if resumeValue: if resumeValue:
@ -148,7 +146,12 @@ def errorUse(expression, expected=None, resumeValue=True, dump=False):
# entry per time # entry per time
# NOTE: I assume that only queries that get data from a table can # NOTE: I assume that only queries that get data from a table can
# return multiple entries # return multiple entries
if " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_TABLE) or (Backend.getIdentifiedDbms() in FROM_TABLE and not expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]))) and "EXISTS(" not in expression.upper() and "(CASE" not in expression.upper(): if (dump and (conf.limitStart or conf.limitStop)) or (" FROM " in \
expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_TABLE) \
or (Backend.getIdentifiedDbms() in FROM_TABLE and not \
expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]))) \
and "EXISTS(" not in expression.upper() and "(CASE" not in expression.upper()):
limitRegExp = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, expression, re.I) limitRegExp = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, expression, re.I)
topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I) topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I)
@ -205,81 +208,61 @@ def errorUse(expression, expected=None, resumeValue=True, dump=False):
if conf.limitStop: if conf.limitStop:
stopLimit = conf.limitStop stopLimit = conf.limitStop
if not stopLimit or stopLimit <= 1: # Count the number of SQL query entries output
if Backend.getIdentifiedDbms() in FROM_TABLE and expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]): countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0]
test = False countedExpression = expression.replace(expressionFields, countFirstField, 1)
if re.search(" ORDER BY ", expression, re.I):
untilOrderChar = countedExpression.index(" ORDER BY ")
countedExpression = countedExpression[:untilOrderChar]
count = resume(countedExpression, None)
if not count or not count.isdigit():
_, _, _, _, _, _, countedExpressionFields, _ = agent.getFields(countedExpression)
count = __oneShotErrorUse(countedExpression, countedExpressionFields)
if (not count or (count.isdigit() and int(count) == 0)):
warnMsg = "it was not possible to count the number "
warnMsg += "of entries for the used SQL query. "
warnMsg += "sqlmap will assume that it returns only "
warnMsg += "one entry"
logger.warn(warnMsg)
stopLimit = 1
elif isNumPosStrValue(count):
if isinstance(stopLimit, int) and stopLimit > 0:
stopLimit = min(int(count), int(stopLimit))
else: else:
test = True stopLimit = int(count)
if test: infoMsg = "the SQL query used returns "
# Count the number of SQL query entries output infoMsg += "%d entries" % stopLimit
countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0] logger.info(infoMsg)
countedExpression = expression.replace(expressionFields, countFirstField, 1)
if re.search(" ORDER BY ", expression, re.I): try:
untilOrderChar = countedExpression.index(" ORDER BY ") for num in xrange(startLimit, stopLimit):
countedExpression = countedExpression[:untilOrderChar] output = __errorFields(expression, expressionFields, expressionFieldsList, expected, num, resumeValue)
if resumeValue: if output and isinstance(output, list) and len(output) == 1:
count = resume(countedExpression, None) output = output[0]
if not stopLimit: outputs.append(output)
if not count or not count.isdigit():
_, _, _, _, _, _, countedExpressionFields, _ = agent.getFields(countedExpression)
count = __oneShotErrorUse(countedExpression, countedExpressionFields)
if isNumPosStrValue(count): except KeyboardInterrupt:
stopLimit = int(count) print
warnMsg = "Ctrl+C detected in dumping phase"
infoMsg = "the SQL query used returns " logger.warn(warnMsg)
infoMsg += "%d entries" % stopLimit
logger.info(infoMsg)
elif count and not count.isdigit():
warnMsg = "it was not possible to count the number "
warnMsg += "of entries for the used SQL query. "
warnMsg += "sqlmap will assume that it returns only "
warnMsg += "one entry"
logger.warn(warnMsg)
stopLimit = 1
elif (not count or int(count) == 0):
warnMsg = "the SQL query used does not "
warnMsg += "return any output"
logger.warn(warnMsg)
return None
elif (not count or int(count) == 0) and (not stopLimit or stopLimit == 0):
warnMsg = "the SQL query used does not "
warnMsg += "return any output"
logger.warn(warnMsg)
return None
try:
for num in xrange(startLimit, stopLimit):
output = __errorFields(expression, expressionFields, expressionFieldsList, expected, num, resumeValue)
if output and isinstance(output, list) and len(output) == 1:
output = output[0]
outputs.append(output)
except KeyboardInterrupt:
print
warnMsg = "Ctrl+C detected in dumping phase"
logger.warn(warnMsg)
duration = calculateDeltaSeconds(start)
debugMsg = "performed %d queries in %d seconds" % (reqCount, duration)
logger.debug(debugMsg)
if not outputs: if not outputs:
outputs = __errorFields(expression, expressionFields, expressionFieldsList) outputs = __errorFields(expression, expressionFields, expressionFieldsList)
if outputs and isinstance(outputs, list) and len(outputs) == 1: if outputs and isinstance(outputs, list) and len(outputs) == 1 and isinstance(outputs[0], basestring):
outputs = outputs[0] outputs = outputs[0]
duration = calculateDeltaSeconds(start)
debugMsg = "performed %d queries in %d seconds" % (reqCount, duration)
logger.debug(debugMsg)
return outputs return outputs

View File

@ -32,6 +32,41 @@ from lib.utils.resume import resume
reqCount = 0 reqCount = 0
def __oneShotUnionUse(expression, unpack=True, unescape=True):
global reqCount
# Prepare expression with delimiters
if unescape:
expression = agent.concatQuery(expression, unpack)
expression = unescaper.unescape(expression)
if conf.limitStart or conf.limitStop:
where = 2
else:
where = None
# Forge the inband SQL injection request
vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
query = agent.forgeInbandQuery(expression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5])
payload = agent.payload(newValue=query, where=where)
# Perform the request
page, headers = Request.queryPage(payload, content=True, raise404=False)
content = "%s%s" % (page or "", listToStrValue(headers.headers if headers else None) or "")
reqCount += 1
if kb.misc.start not in content or kb.misc.stop not in content:
return None
# Parse the returned page to get the exact inband
# sql injection output
startPosition = content.index(kb.misc.start)
endPosition = content.rindex(kb.misc.stop) + len(kb.misc.stop)
value = getUnicode(content[startPosition:endPosition])
return value
def configUnion(char=None, columns=None): def configUnion(char=None, columns=None):
def __configUnionChar(char): def __configUnionChar(char):
if char.isdigit() or char == "NULL": if char.isdigit() or char == "NULL":
@ -67,7 +102,7 @@ def configUnion(char=None, columns=None):
elif isinstance(columns, basestring): elif isinstance(columns, basestring):
__configUnionCols(columns) __configUnionCols(columns)
def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack=True, dump=False): def unionUse(expression, direct=False, unescape=True, unpack=True, dump=False):
""" """
This function tests for an inband SQL injection on the target This function tests for an inband SQL injection on the target
url then call its subsidiary function to effectively perform an url then call its subsidiary function to effectively perform an
@ -76,38 +111,47 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack
initTechnique(PAYLOAD.TECHNIQUE.UNION) initTechnique(PAYLOAD.TECHNIQUE.UNION)
global reqCount
count = None count = None
origExpr = expression origExpr = expression
start = time.time()
startLimit = 0 startLimit = 0
stopLimit = None stopLimit = None
test = True test = True
value = "" value = ""
reqCount = 0
start = time.time()
global reqCount _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr)
if resetCounter: # We have to check if the SQL query might return multiple entries
reqCount = 0 # and in such case forge the SQL limiting the query output one
# entry per time
# NOTE: I assume that only queries that get data from a table can
# return multiple entries
if (kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == 2 or \
(dump and (conf.limitStart or conf.limitStop))) and \
" FROM " in expression.upper() and ((Backend.getIdentifiedDbms() \
not in FROM_TABLE) or (Backend.getIdentifiedDbms() in FROM_TABLE \
and not expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]))) \
and "EXISTS(" not in expression.upper() and "(CASE" not in expression.upper():
# Prepare expression with delimiters limitRegExp = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, expression, re.I)
if unescape: topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I)
expression = agent.concatQuery(expression, unpack)
expression = unescaper.unescape(expression)
if kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == 2 and not direct: if limitRegExp or (Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit):
_, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query
limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query
# We have to check if the SQL query might return multiple entries if limitGroupStart.isdigit():
# and in such case forge the SQL limiting the query output one startLimit = int(limitRegExp.group(int(limitGroupStart)))
# entry per time
# NOTE: I assume that only queries that get data from a table can
# return multiple entries
if " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_TABLE) or (Backend.getIdentifiedDbms() in FROM_TABLE and not expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]))) and "EXISTS(" not in expression.upper():
limitRegExp = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, expression, re.I)
topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I)
if limitRegExp or (Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit): stopLimit = limitRegExp.group(int(limitGroupStop))
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): limitCond = int(stopLimit) > 1
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
if limitRegExp:
limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query
limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query
@ -116,155 +160,102 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack
stopLimit = limitRegExp.group(int(limitGroupStop)) stopLimit = limitRegExp.group(int(limitGroupStop))
limitCond = int(stopLimit) > 1 limitCond = int(stopLimit) > 1
elif topLimit:
startLimit = 0
stopLimit = int(topLimit.group(1))
limitCond = int(stopLimit) > 1
elif Backend.getIdentifiedDbms() == DBMS.ORACLE:
limitCond = False
else:
limitCond = True
# I assume that only queries NOT containing a "LIMIT #, 1"
# (or similar depending on the back-end DBMS) can return
# multiple entries
if limitCond:
if limitRegExp:
stopLimit = int(stopLimit)
# From now on we need only the expression until the " LIMIT "
# (or similar, depending on the back-end DBMS) word
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
stopLimit += startLimit
untilLimitChar = expression.index(queries[Backend.getIdentifiedDbms()].limitstring.query)
expression = expression[:untilLimitChar]
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
if limitRegExp: stopLimit += startLimit
limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query elif dump:
limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query if conf.limitStart:
startLimit = conf.limitStart
if conf.limitStop:
stopLimit = conf.limitStop
if limitGroupStart.isdigit(): # Count the number of SQL query entries output
startLimit = int(limitRegExp.group(int(limitGroupStart))) countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0]
countedExpression = expression.replace(expressionFields, countFirstField, 1)
stopLimit = limitRegExp.group(int(limitGroupStop)) if re.search(" ORDER BY ", expression, re.I):
limitCond = int(stopLimit) > 1 untilOrderChar = countedExpression.index(" ORDER BY ")
elif topLimit: countedExpression = countedExpression[:untilOrderChar]
startLimit = 0
stopLimit = int(topLimit.group(1))
limitCond = int(stopLimit) > 1
elif Backend.getIdentifiedDbms() == DBMS.ORACLE: count = resume(countedExpression, None)
limitCond = False count = parseUnionPage(count, countedExpression)
else:
limitCond = True
# I assume that only queries NOT containing a "LIMIT #, 1" if not count or not count.isdigit():
# (or similar depending on the back-end DBMS) can return output = __oneShotUnionUse(countedExpression)
# multiple entries
if limitCond:
if limitRegExp:
stopLimit = int(stopLimit)
# From now on we need only the expression until the " LIMIT " if output:
# (or similar, depending on the back-end DBMS) word count = parseUnionPage(output, countedExpression)
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
stopLimit += startLimit
untilLimitChar = expression.index(queries[Backend.getIdentifiedDbms()].limitstring.query)
expression = expression[:untilLimitChar]
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): if (not count or (count.isdigit() and int(count) == 0)):
stopLimit += startLimit warnMsg = "it was not possible to count the number "
elif dump: warnMsg += "of entries for the used SQL query. "
if conf.limitStart: warnMsg += "sqlmap will assume that it returns only "
startLimit = conf.limitStart warnMsg += "one entry"
if conf.limitStop: logger.warn(warnMsg)
stopLimit = conf.limitStop
if not stopLimit or stopLimit <= 1: stopLimit = 1
if Backend.getIdentifiedDbms() in FROM_TABLE and expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]): elif isNumPosStrValue(count):
test = False if isinstance(stopLimit, int) and stopLimit > 0:
stopLimit = min(int(count), int(stopLimit))
else:
stopLimit = int(count)
infoMsg = "the SQL query used returns "
infoMsg += "%d entries" % stopLimit
logger.info(infoMsg)
try:
for num in xrange(startLimit, stopLimit):
if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
field = expressionFieldsList[0]
elif Backend.getIdentifiedDbms() == DBMS.ORACLE:
field = expressionFieldsList
else: else:
test = True field = None
if test: limitedExpr = agent.limitQuery(num, expression, field)
# Count the number of SQL query entries output output = resume(limitedExpr, None)
countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0]
countedExpression = origExpr.replace(expressionFields, countFirstField, 1)
if re.search(" ORDER BY ", expression, re.I): if not output:
untilOrderChar = countedExpression.index(" ORDER BY ") output = __oneShotUnionUse(limitedExpr, unescape=unescape)
countedExpression = countedExpression[:untilOrderChar]
count = resume(countedExpression, None) if output:
value += output
parseUnionPage(output, limitedExpr)
if not stopLimit: except KeyboardInterrupt:
if not count or not count.isdigit(): print
output = unionUse(countedExpression, direct=True) warnMsg = "Ctrl+C detected in dumping phase"
logger.warn(warnMsg)
if output: if not value:
count = parseUnionPage(output, countedExpression) value = __oneShotUnionUse(expression, unescape=unescape)
if isNumPosStrValue(count): duration = calculateDeltaSeconds(start)
stopLimit = int(count)
infoMsg = "the SQL query used returns " debugMsg = "performed %d queries in %d seconds" % (reqCount, duration)
infoMsg += "%d entries" % stopLimit logger.debug(debugMsg)
logger.info(infoMsg)
elif count and not count.isdigit():
warnMsg = "it was not possible to count the number "
warnMsg += "of entries for the used SQL query. "
warnMsg += "sqlmap will assume that it returns only "
warnMsg += "one entry"
logger.warn(warnMsg)
stopLimit = 1
elif (not count or int(count) == 0):
warnMsg = "the SQL query used does not "
warnMsg += "return any output"
logger.warn(warnMsg)
return None
elif (not count or int(count) == 0) and (not stopLimit or stopLimit == 0):
warnMsg = "the SQL query used does not "
warnMsg += "return any output"
logger.warn(warnMsg)
return None
try:
for num in xrange(startLimit, stopLimit):
if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
field = expressionFieldsList[0]
elif Backend.getIdentifiedDbms() == DBMS.ORACLE:
field = expressionFieldsList
else:
field = None
limitedExpr = agent.limitQuery(num, expression, field)
output = resume(limitedExpr, None)
if not output:
output = unionUse(limitedExpr, direct=True, unescape=False)
if output:
value += output
parseUnionPage(output, limitedExpr)
except KeyboardInterrupt:
print
warnMsg = "Ctrl+C detected in dumping phase"
logger.warn(warnMsg)
return value
value = unionUse(expression, direct=True, unescape=False)
else:
# Forge the inband SQL injection request
vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
query = agent.forgeInbandQuery(expression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5])
payload = agent.payload(newValue=query)
# Perform the request
page, headers = Request.queryPage(payload, content=True, raise404=False)
content = "%s%s" % (page or "", listToStrValue(headers.headers if headers else None) or "")
reqCount += 1
if kb.misc.start not in content or kb.misc.stop not in content:
return None
# Parse the returned page to get the exact inband
# sql injection output
startPosition = content.index(kb.misc.start)
endPosition = content.rindex(kb.misc.stop) + len(kb.misc.stop)
value = getUnicode(content[startPosition:endPosition])
duration = calculateDeltaSeconds(start)
debugMsg = "performed %d queries in %d seconds" % (reqCount, duration)
logger.debug(debugMsg)
return value return value