diff --git a/lib/controller/checks.py b/lib/controller/checks.py index e9c387b41..4aa005d10 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,17 @@ 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: + Backend.forceDbms(kb.heuristicDbms) if unionExtended: infoMsg = "automatically extending ranges " @@ -582,6 +591,32 @@ 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() + + if retVal: + infoMsg = "heuristic test showed that the back-end DBMS " + infoMsg += "could be '%s' " % retVal + logger.info(infoMsg) + + return retVal + def checkFalsePositives(injection): """ Checks for false positives (only in single special cases) @@ -723,7 +758,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 90831b0c4..39476b559 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..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__" @@ -111,6 +112,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" @@ -459,7 +463,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") 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] 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) 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +