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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+