From be9381abc5f0ec1f677a6d554d8178f3f97b4548 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 6 May 2016 13:06:59 +0200 Subject: [PATCH] Implements #1845 --- lib/controller/checks.py | 21 +++++++++++---------- lib/controller/controller.py | 27 +++++++++++++++++---------- lib/core/datatype.py | 1 + lib/core/enums.py | 3 +++ lib/core/option.py | 1 + lib/core/settings.py | 4 ++-- lib/core/target.py | 2 +- 7 files changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 0a34729fa..9f3e9a02a 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -55,6 +55,7 @@ from lib.core.enums import HASHDB_KEYS from lib.core.enums import HEURISTIC_TEST from lib.core.enums import HTTP_HEADER from lib.core.enums import HTTPMETHOD +from lib.core.enums import NOTE from lib.core.enums import NULLCONNECTION from lib.core.enums import PAYLOAD from lib.core.enums import PLACE @@ -696,10 +697,10 @@ def checkSqlInjection(place, parameter, value): warnMsg += "problems during data retrieval" logger.warn(warnMsg) - injection = checkFalsePositives(injection) - - if not injection: + if not checkFalsePositives(injection): kb.vulnHosts.remove(conf.hostname) + injection.notes.add(NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE) + else: injection = None @@ -748,7 +749,7 @@ def checkFalsePositives(injection): Checks for false positives (only in single special cases) """ - retVal = injection + retVal = True if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data) or\ (len(injection.data) == 1 and PAYLOAD.TECHNIQUE.UNION in injection.data and "Generic" in injection.data[PAYLOAD.TECHNIQUE.UNION].title): @@ -774,7 +775,7 @@ def checkFalsePositives(injection): break if not checkBooleanExpression("%d=%d" % (randInt1, randInt1)): - retVal = None + retVal = False break # Just in case if DBMS hasn't properly recovered from previous delayed request @@ -782,22 +783,22 @@ def checkFalsePositives(injection): checkBooleanExpression("%d=%d" % (randInt1, randInt2)) if checkBooleanExpression("%d=%d" % (randInt1, randInt3)): # this must not be evaluated to True - retVal = None + retVal = False break elif checkBooleanExpression("%d=%d" % (randInt3, randInt2)): # this must not be evaluated to True - retVal = None + retVal = False break elif not checkBooleanExpression("%d=%d" % (randInt2, randInt2)): # this must be evaluated to True - retVal = None + retVal = False break elif checkBooleanExpression("%d %d" % (randInt3, randInt2)): # this must not be evaluated to True (invalid statement) - retVal = None + retVal = False break - if retVal is None: + if not retVal: warnMsg = "false positive or unexploitable injection point detected" logger.warn(warnMsg) diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 91463de4a..537bcc418 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -45,6 +45,7 @@ from lib.core.enums import CONTENT_TYPE from lib.core.enums import HASHDB_KEYS from lib.core.enums import HEURISTIC_TEST from lib.core.enums import HTTPMETHOD +from lib.core.enums import NOTE from lib.core.enums import PAYLOAD from lib.core.enums import PLACE from lib.core.exception import SqlmapBaseException @@ -225,23 +226,23 @@ def _saveToResultsFile(): results = {} techniques = dict(map(lambda x: (x[1], x[0]), getPublicTypeMembers(PAYLOAD.TECHNIQUE))) - for inj in kb.injections: + for inj in kb.injections + kb.falsePositives: if inj.place is None or inj.parameter is None: continue - key = (inj.place, inj.parameter) + key = (inj.place, inj.parameter, ';'.join(inj.notes)) if key not in results: results[key] = [] results[key].extend(inj.data.keys()) for key, value in results.items(): - place, parameter = key - line = "%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(map(lambda x: techniques[x][0].upper(), sorted(value))), os.linesep) + place, parameter, notes = key + line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(map(lambda x: techniques[x][0].upper(), sorted(value))), notes, os.linesep) conf.resultsFP.writelines(line) if not results: - line = "%s,,,%s" % (conf.url, os.linesep) + line = "%s,,,,%s" % (conf.url, os.linesep) conf.resultsFP.writelines(line) def start(): @@ -522,7 +523,10 @@ def start(): proceed = not kb.endDetection if getattr(injection, "place", None) is not None: - kb.injections.append(injection) + if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes: + kb.falsePositives.append(injection) + else: + kb.injections.append(injection) # In case when user wants to end detection phase (Ctrl+C) if not proceed: @@ -651,6 +655,8 @@ def start(): errMsg = getSafeExString(ex) if conf.multipleTargets: + _saveToResultsFile() + errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg) else: @@ -669,9 +675,10 @@ def start(): if kb.dataOutputFlag and not conf.multipleTargets: logger.info("fetched data logged to text files under '%s'" % conf.outputPath) - if conf.multipleTargets and conf.resultsFilename: - infoMsg = "you can find results of scanning in multiple targets " - infoMsg += "mode inside the CSV file '%s'" % conf.resultsFilename - logger.info(infoMsg) + if conf.multipleTargets: + if conf.resultsFilename: + infoMsg = "you can find results of scanning in multiple targets " + infoMsg += "mode inside the CSV file '%s'" % conf.resultsFilename + logger.info(infoMsg) return True diff --git a/lib/core/datatype.py b/lib/core/datatype.py index 182abe31c..b3df3dae0 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -93,6 +93,7 @@ class InjectionDict(AttribDict): self.prefix = None self.suffix = None self.clause = None + self.notes = set() # data is a dict with various stype, each which is a dict with # all the information specific for that stype diff --git a/lib/core/enums.py b/lib/core/enums.py index 1bb4fcbbf..d71b2c76d 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -351,3 +351,6 @@ class AUTOCOMPLETE_TYPE: SQL = 0 OS = 1 SQLMAP = 2 + +class NOTE: + FALSE_POSITIVE_OR_UNEXPLOITABLE = "false positive or unexploitable" diff --git a/lib/core/option.py b/lib/core/option.py index c2fd63bd3..9c20768e7 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1838,6 +1838,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.extendTests = None kb.errorChunkLength = None kb.errorIsNone = True + kb.falsePositives = [] kb.fileReadMode = False kb.followSitemapRecursion = None kb.forcedDbms = None diff --git a/lib/core/settings.py b/lib/core/settings.py index a0dfd1fec..626da45a7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from lib.core.enums import OS from lib.core.revision import getRevisionNumber # sqlmap version (...) -VERSION = "1.0.5.14" +VERSION = "1.0.5.15" REVISION = getRevisionNumber() STABLE = VERSION.count('.') <= 2 VERSION_STRING = "sqlmap/%s#%s" % (VERSION, "stable" if STABLE else "dev") @@ -530,7 +530,7 @@ HASHDB_FLUSH_RETRIES = 3 HASHDB_END_TRANSACTION_RETRIES = 3 # Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing hash/pickle mechanism) -HASHDB_MILESTONE_VALUE = "WVMqopmuzX" # "".join(random.sample(string.ascii_letters, 10)) +HASHDB_MILESTONE_VALUE = "zYwqRDymvj" # "".join(random.sample(string.ascii_letters, 10)) # Warn user of possible delay due to large page dump in full UNION query injections LARGE_OUTPUT_THRESHOLD = 1024 ** 2 diff --git a/lib/core/target.py b/lib/core/target.py index 286cb278a..0208aaf10 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -542,7 +542,7 @@ def _setResultsFile(): errMsg += "create temporary files and/or directories" raise SqlmapSystemException(errMsg) - conf.resultsFP.writelines("Target URL,Place,Parameter,Techniques%s" % os.linesep) + conf.resultsFP.writelines("Target URL,Place,Parameter,Technique(s),Note(s)%s" % os.linesep) logger.info("using '%s' as the CSV results file in multiple targets mode" % conf.resultsFilename)