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,13 +208,6 @@ 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:
if Backend.getIdentifiedDbms() in FROM_TABLE and expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]):
test = False
else:
test = True
if test:
# Count the number of SQL query entries output # Count the number of SQL query entries output
countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0] countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0]
countedExpression = expression.replace(expressionFields, countFirstField, 1) countedExpression = expression.replace(expressionFields, countFirstField, 1)
@ -220,22 +216,13 @@ def errorUse(expression, expected=None, resumeValue=True, dump=False):
untilOrderChar = countedExpression.index(" ORDER BY ") untilOrderChar = countedExpression.index(" ORDER BY ")
countedExpression = countedExpression[:untilOrderChar] countedExpression = countedExpression[:untilOrderChar]
if resumeValue:
count = resume(countedExpression, None) count = resume(countedExpression, None)
if not stopLimit:
if not count or not count.isdigit(): if not count or not count.isdigit():
_, _, _, _, _, _, countedExpressionFields, _ = agent.getFields(countedExpression) _, _, _, _, _, _, countedExpressionFields, _ = agent.getFields(countedExpression)
count = __oneShotErrorUse(countedExpression, countedExpressionFields) count = __oneShotErrorUse(countedExpression, countedExpressionFields)
if isNumPosStrValue(count): if (not count or (count.isdigit() and int(count) == 0)):
stopLimit = int(count)
infoMsg = "the SQL query used returns "
infoMsg += "%d entries" % stopLimit
logger.info(infoMsg)
elif count and not count.isdigit():
warnMsg = "it was not possible to count the number " warnMsg = "it was not possible to count the number "
warnMsg += "of entries for the used SQL query. " warnMsg += "of entries for the used SQL query. "
warnMsg += "sqlmap will assume that it returns only " warnMsg += "sqlmap will assume that it returns only "
@ -243,24 +230,20 @@ def errorUse(expression, expected=None, resumeValue=True, dump=False):
logger.warn(warnMsg) logger.warn(warnMsg)
stopLimit = 1 stopLimit = 1
elif isNumPosStrValue(count):
if isinstance(stopLimit, int) and stopLimit > 0:
stopLimit = min(int(count), int(stopLimit))
else:
stopLimit = int(count)
elif (not count or int(count) == 0): infoMsg = "the SQL query used returns "
warnMsg = "the SQL query used does not " infoMsg += "%d entries" % stopLimit
warnMsg += "return any output" logger.info(infoMsg)
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: try:
for num in xrange(startLimit, stopLimit): for num in xrange(startLimit, stopLimit):
output = __errorFields(expression, expressionFields, expressionFieldsList, expected, num, resumeValue) output = __errorFields(expression, expressionFields, expressionFieldsList, expected, num, resumeValue)
if output and isinstance(output, list) and len(output) == 1: if output and isinstance(output, list) and len(output) == 1:
output = output[0] output = output[0]
@ -271,15 +254,15 @@ def errorUse(expression, expected=None, resumeValue=True, dump=False):
warnMsg = "Ctrl+C detected in dumping phase" warnMsg = "Ctrl+C detected in dumping phase"
logger.warn(warnMsg) logger.warn(warnMsg)
if not outputs:
outputs = __errorFields(expression, expressionFields, expressionFieldsList)
if outputs and isinstance(outputs, list) and len(outputs) == 1 and isinstance(outputs[0], basestring):
outputs = outputs[0]
duration = calculateDeltaSeconds(start) duration = calculateDeltaSeconds(start)
debugMsg = "performed %d queries in %d seconds" % (reqCount, duration) debugMsg = "performed %d queries in %d seconds" % (reqCount, duration)
logger.debug(debugMsg) logger.debug(debugMsg)
if not outputs:
outputs = __errorFields(expression, expressionFields, expressionFieldsList)
if outputs and isinstance(outputs, list) and len(outputs) == 1:
outputs = outputs[0]
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,25 +111,17 @@ 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 = ""
global reqCount
if resetCounter:
reqCount = 0 reqCount = 0
start = time.time()
# Prepare expression with delimiters
if unescape:
expression = agent.concatQuery(expression, unpack)
expression = unescaper.unescape(expression)
if kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == 2 and not direct:
_, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr) _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr)
# We have to check if the SQL query might return multiple entries # We have to check if the SQL query might return multiple entries
@ -102,7 +129,13 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack
# 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(): 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():
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)
@ -159,38 +192,24 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack
if conf.limitStop: if conf.limitStop:
stopLimit = conf.limitStop stopLimit = conf.limitStop
if not stopLimit or stopLimit <= 1:
if Backend.getIdentifiedDbms() in FROM_TABLE and expression.upper().endswith(FROM_TABLE[Backend.getIdentifiedDbms()]):
test = False
else:
test = True
if test:
# Count the number of SQL query entries output # Count the number of SQL query entries output
countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0] countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0]
countedExpression = origExpr.replace(expressionFields, countFirstField, 1) countedExpression = expression.replace(expressionFields, countFirstField, 1)
if re.search(" ORDER BY ", expression, re.I): if re.search(" ORDER BY ", expression, re.I):
untilOrderChar = countedExpression.index(" ORDER BY ") untilOrderChar = countedExpression.index(" ORDER BY ")
countedExpression = countedExpression[:untilOrderChar] countedExpression = countedExpression[:untilOrderChar]
count = resume(countedExpression, None) count = resume(countedExpression, None)
count = parseUnionPage(count, countedExpression)
if not stopLimit:
if not count or not count.isdigit(): if not count or not count.isdigit():
output = unionUse(countedExpression, direct=True) output = __oneShotUnionUse(countedExpression)
if output: if output:
count = parseUnionPage(output, countedExpression) count = parseUnionPage(output, countedExpression)
if isNumPosStrValue(count): if (not count or (count.isdigit() and int(count) == 0)):
stopLimit = int(count)
infoMsg = "the SQL query used returns "
infoMsg += "%d entries" % stopLimit
logger.info(infoMsg)
elif count and not count.isdigit():
warnMsg = "it was not possible to count the number " warnMsg = "it was not possible to count the number "
warnMsg += "of entries for the used SQL query. " warnMsg += "of entries for the used SQL query. "
warnMsg += "sqlmap will assume that it returns only " warnMsg += "sqlmap will assume that it returns only "
@ -198,21 +217,15 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack
logger.warn(warnMsg) logger.warn(warnMsg)
stopLimit = 1 stopLimit = 1
elif isNumPosStrValue(count):
if isinstance(stopLimit, int) and stopLimit > 0:
stopLimit = min(int(count), int(stopLimit))
else:
stopLimit = int(count)
elif (not count or int(count) == 0): infoMsg = "the SQL query used returns "
warnMsg = "the SQL query used does not " infoMsg += "%d entries" % stopLimit
warnMsg += "return any output" logger.info(infoMsg)
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: try:
for num in xrange(startLimit, stopLimit): for num in xrange(startLimit, stopLimit):
if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
@ -226,7 +239,7 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack
output = resume(limitedExpr, None) output = resume(limitedExpr, None)
if not output: if not output:
output = unionUse(limitedExpr, direct=True, unescape=False) output = __oneShotUnionUse(limitedExpr, unescape=unescape)
if output: if output:
value += output value += output
@ -237,30 +250,8 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, unpack
warnMsg = "Ctrl+C detected in dumping phase" warnMsg = "Ctrl+C detected in dumping phase"
logger.warn(warnMsg) logger.warn(warnMsg)
return value if not value:
value = __oneShotUnionUse(expression, unescape=unescape)
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) duration = calculateDeltaSeconds(start)