From 194a9e7b88933d6ba941d229062c44ce09022207 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Jan 2013 12:34:57 +0100 Subject: [PATCH 1/5] Implementation for an Issue #377 --- lib/controller/checks.py | 48 ++++++++++++++++++++++++++++++++++------ lib/core/common.py | 1 + lib/core/option.py | 1 + lib/core/settings.py | 3 +++ 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index e9c387b41..3af3adc4a 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -22,6 +22,7 @@ from lib.core.common import extractTextTagContent from lib.core.common import findDynamicContent from lib.core.common import Format from lib.core.common import getLastRequestHTTPError +from lib.core.common import getPublicTypeMembers from lib.core.common import getSortedInjectionTests from lib.core.common import getUnicode from lib.core.common import intersect @@ -42,6 +43,8 @@ from lib.core.data import kb from lib.core.data import logger from lib.core.datatype import AttribDict from lib.core.datatype import InjectionDict +from lib.core.dicts import FROM_DUMMY_TABLE +from lib.core.enums import DBMS from lib.core.enums import HEURISTIC_TEST from lib.core.enums import HTTPHEADER from lib.core.enums import HTTPMETHOD @@ -55,7 +58,7 @@ from lib.core.exception import SqlmapUserQuitException from lib.core.settings import FORMAT_EXCEPTION_STRINGS from lib.core.settings import HEURISTIC_CHECK_ALPHABET from lib.core.settings import SUHOSIN_MAX_VALUE_LENGTH -from lib.core.settings import UNKNOWN_DBMS_VERSION +from lib.core.settings import UNKNOWN_DBMS from lib.core.settings import LOWER_RATIO_BOUND from lib.core.settings import UPPER_RATIO_BOUND from lib.core.settings import IDS_WAF_CHECK_PAYLOAD @@ -441,11 +444,21 @@ def checkSqlInjection(place, parameter, value): configUnion(test.request.char, test.request.columns) if not Backend.getIdentifiedDbms(): - warnMsg = "using unescaped version of the test " - warnMsg += "because of zero knowledge of the " - warnMsg += "back-end DBMS. You can try to " - warnMsg += "explicitly set it using option '--dbms'" - singleTimeWarnMessage(warnMsg) + if not kb.heuristicDbms: + kb.heuristicDbms = heuristicCheckDbms(injection) or UNKNOWN_DBMS + + if kb.heuristicDbms == UNKNOWN_DBMS: + warnMsg = "using unescaped version of the test " + warnMsg += "because of zero knowledge of the " + warnMsg += "back-end DBMS. You can try to " + warnMsg += "explicitly set it using option '--dbms'" + singleTimeWarnMessage(warnMsg) + else: + warnMsg = "heuristic test showed that the back-end DBMS " + warnMsg += "could be '%s' " % kb.heuristicDbms + singleTimeWarnMessage(warnMsg) + + Backend.forceDbms(kb.heuristicDbms) if unionExtended: infoMsg = "automatically extending ranges " @@ -582,6 +595,27 @@ def checkSqlInjection(place, parameter, value): return injection +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() + + 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 + + Backend.flushForcedDbms() + kb.injection = popValue() + + return retVal + def checkFalsePositives(injection): """ Checks for false positives (only in single special cases) @@ -723,7 +757,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_VERSION) + infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS) logger.info(infoMsg) else: diff --git a/lib/core/common.py b/lib/core/common.py index 141eda6b4..9d19ff8b9 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -122,6 +122,7 @@ from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import TEXT_TAG_REGEX from lib.core.settings import TIME_STDEV_COEFF from lib.core.settings import UNICODE_ENCODING +from lib.core.settings import UNKNOWN_DBMS from lib.core.settings import UNKNOWN_DBMS_VERSION from lib.core.settings import URI_QUESTION_MARKER from lib.core.settings import URLENCODE_CHAR_LIMIT diff --git a/lib/core/option.py b/lib/core/option.py index b40027877..f5579dc64 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1526,6 +1526,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.fileReadMode = False kb.forcedDbms = None kb.headersFp = {} + kb.heuristicDbms = None kb.heuristicTest = None kb.hintValue = None kb.htmlFp = [] diff --git a/lib/core/settings.py b/lib/core/settings.py index 873880730..22dc012c6 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -111,6 +111,9 @@ INFERENCE_EQUALS_CHAR = "=" # Character used for operation "not-equals" in inference INFERENCE_NOT_EQUALS_CHAR = "!=" +# String used for representation of unknown dbms +UNKNOWN_DBMS = "Unknown" + # String used for representation of unknown dbms version UNKNOWN_DBMS_VERSION = "Unknown" From 479f791112ac0bfc69c46a11577dc99e9092ea19 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Jan 2013 12:41:51 +0100 Subject: [PATCH 2/5] Minor fix --- lib/request/connect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/request/connect.py b/lib/request/connect.py index 1394fc49e..e2ea2ac3e 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -687,7 +687,7 @@ class Connect(object): else: uri = conf.url - if place == PLACE.CUSTOM_HEADER: + if value and place == PLACE.CUSTOM_HEADER: if not auxHeaders: auxHeaders = {} auxHeaders[value.split(',')[0]] = value.split(',', 1)[1] From 8c84a16cb71674fe2d577c2e694f1a8d769fde02 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Jan 2013 12:52:31 +0100 Subject: [PATCH 3/5] Minor style update for an Issue #377 --- lib/controller/checks.py | 9 +++++---- lib/core/settings.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 3af3adc4a..4aa005d10 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -454,10 +454,6 @@ def checkSqlInjection(place, parameter, value): warnMsg += "explicitly set it using option '--dbms'" singleTimeWarnMessage(warnMsg) else: - warnMsg = "heuristic test showed that the back-end DBMS " - warnMsg += "could be '%s' " % kb.heuristicDbms - singleTimeWarnMessage(warnMsg) - Backend.forceDbms(kb.heuristicDbms) if unionExtended: @@ -614,6 +610,11 @@ def heuristicCheckDbms(injection): Backend.flushForcedDbms() kb.injection = popValue() + if retVal: + infoMsg = "heuristic test showed that the back-end DBMS " + infoMsg += "could be '%s' " % retVal + logger.info(infoMsg) + return retVal def checkFalsePositives(injection): diff --git a/lib/core/settings.py b/lib/core/settings.py index 22dc012c6..322e2c174 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -462,7 +462,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") +BOLD_PATTERNS = ("' injectable", "might be injectable", "' is vulnerable", "is not injectable", "test failed", "test passed", "live test final result", "heuristic test showed") # Generic www root directory names GENERIC_DOC_ROOT_DIRECTORY_NAMES = ("htdocs", "wwwroot", "www") From f9b44d6ff754cc5f62d2e6dc9937ebe24939d046 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Jan 2013 16:07:27 +0100 Subject: [PATCH 4/5] Adding test cases for using custom injection marks --- xml/livetests.xml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/xml/livetests.xml b/xml/livetests.xml index 115b738d7..455e1da55 100644 --- a/xml/livetests.xml +++ b/xml/livetests.xml @@ -3354,6 +3354,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c06f94e2c82daaee44f38fb92bb743808ca873d9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Jan 2013 16:38:41 +0100 Subject: [PATCH 5/5] Fix for an Issue #378 --- lib/core/settings.py | 3 ++- lib/techniques/blind/inference.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 322e2c174..3edb4ecfd 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -36,7 +36,8 @@ UPPER_RATIO_BOUND = 0.98 # Markers for special cases when parameter values contain html encoded characters PARAMETER_AMP_MARKER = "__AMP__" PARAMETER_SEMICOLON_MARKER = "__SEMICOLON__" -PARTIAL_VALUE_MARKER = "__PARTIAL__" +PARTIAL_VALUE_MARKER = "__PARTIAL_VALUE__" +PARTIAL_HEX_VALUE_MARKER = "__PARTIAL_HEX_VALUE__" URI_QUESTION_MARKER = "__QUESTION_MARK__" ASTERISK_MARKER = "__ASTERISK_MARK__" diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index ab0dbcc98..0fd592662 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -42,6 +42,7 @@ from lib.core.settings import INFERENCE_GREATER_CHAR from lib.core.settings import INFERENCE_EQUALS_CHAR from lib.core.settings import INFERENCE_NOT_EQUALS_CHAR from lib.core.settings import MAX_TIME_REVALIDATION_STEPS +from lib.core.settings import PARTIAL_HEX_VALUE_MARKER from lib.core.settings import PARTIAL_VALUE_MARKER from lib.core.settings import VALID_TIME_CHARS_RUN_THRESHOLD from lib.core.threads import getCurrentThreadData @@ -65,10 +66,17 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None retVal = hashDBRetrieve(expression, checkConf=True) if retVal: - if PARTIAL_VALUE_MARKER in retVal: + if PARTIAL_HEX_VALUE_MARKER in retVal: + retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "") + + if retVal and conf.hexConvert: + partialValue = retVal + infoMsg = "resuming partial value: %s" % safecharencode(partialValue) + logger.info(infoMsg) + elif PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") - if retVal: + if retVal and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) @@ -545,7 +553,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: - hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER, partialValue)) + hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag: infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength)