diff --git a/lib/core/agent.py b/lib/core/agent.py index 17b4bc68c..577e856ae 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -12,6 +12,7 @@ from lib.core.common import Backend from lib.core.common import extractRegexResult from lib.core.common import filterNone from lib.core.common import getSQLSnippet +from lib.core.common import getTechnique from lib.core.common import isDBMSVersionAtLeast from lib.core.common import isNumber from lib.core.common import isTechniqueAvailable @@ -89,8 +90,8 @@ class Agent(object): if kb.forceWhere: where = kb.forceWhere - elif where is None and isTechniqueAvailable(kb.technique): - where = kb.injection.data[kb.technique].where + elif where is None and isTechniqueAvailable(getTechnique()): + where = kb.injection.data[getTechnique()].where if kb.injection.place is not None: place = kb.injection.place @@ -234,8 +235,8 @@ class Agent(object): expression = unescaper.escape(expression) query = None - if where is None and kb.technique and kb.technique in kb.injection.data: - where = kb.injection.data[kb.technique].where + if where is None and getTechnique() is not None and getTechnique() in kb.injection.data: + where = kb.injection.data[getTechnique()].where # If we are replacing (<where>) the parameter original value with # our payload do not prepend with the prefix @@ -244,7 +245,7 @@ class Agent(object): # If the technique is stacked queries (<stype>) do not put a space # after the prefix or it is in GROUP BY / ORDER BY (<clause>) - elif kb.technique == PAYLOAD.TECHNIQUE.STACKED: + elif getTechnique() == PAYLOAD.TECHNIQUE.STACKED: query = kb.injection.prefix elif kb.injection.clause == [2, 3] or kb.injection.clause == [2] or kb.injection.clause == [3]: query = kb.injection.prefix @@ -282,9 +283,9 @@ class Agent(object): # Take default values if None suffix = kb.injection.suffix if kb.injection and suffix is None else suffix - if kb.technique and kb.technique in kb.injection.data: - where = kb.injection.data[kb.technique].where if where is None else where - comment = kb.injection.data[kb.technique].comment if comment is None else comment + if getTechnique() is not None and getTechnique() in kb.injection.data: + where = kb.injection.data[getTechnique()].where if where is None else where + comment = kb.injection.data[getTechnique()].comment if comment is None else comment if Backend.getIdentifiedDbms() == DBMS.ACCESS and any((comment or "").startswith(_) for _ in ("--", "[GENERIC_SQL_COMMENT]")): comment = queries[DBMS.ACCESS].comment.query diff --git a/lib/core/common.py b/lib/core/common.py index 42c017220..608e2d1f3 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1125,6 +1125,20 @@ def readInput(message, default=None, checkBatch=True, boolean=False): return retVal or "" +def setTechnique(technique): + """ + Thread-safe setting of currently used technique (Note: dealing with cases of per-thread technique switching) + """ + + getCurrentThreadData().technique = technique + +def getTechnique(): + """ + Thread-safe getting of currently used technique + """ + + return getCurrentThreadData().technique or kb.technique + def randomRange(start=0, stop=1000, seed=None): """ Returns random integer value in given range @@ -3231,18 +3245,16 @@ def isHeavyQueryBased(technique=None): Returns True whether current (kb.)technique is heavy-query based >>> pushValue(kb.injection.data) - >>> pushValue(kb.technique) - >>> kb.technique = PAYLOAD.TECHNIQUE.STACKED - >>> kb.injection.data[kb.technique] = [test for test in getSortedInjectionTests() if "heavy" in test["title"].lower()][0] + >>> setTechnique(PAYLOAD.TECHNIQUE.STACKED) + >>> kb.injection.data[getTechnique()] = [test for test in getSortedInjectionTests() if "heavy" in test["title"].lower()][0] >>> isHeavyQueryBased() True - >>> kb.technique = popValue() >>> kb.injection.data = popValue() """ retVal = False - technique = technique or kb.technique + technique = technique or getTechnique() if isTechniqueAvailable(technique): data = getTechniqueData(technique) @@ -3630,7 +3642,7 @@ def unhandledExceptionMessage(): errMsg += "Python version: %s\n" % PYVERSION errMsg += "Operating system: %s\n" % platform.platform() errMsg += "Command line: %s\n" % re.sub(r".+?\bsqlmap\.py\b", "sqlmap.py", getUnicode(" ".join(sys.argv), encoding=sys.stdin.encoding)) - errMsg += "Technique: %s\n" % (enumValueToNameLookup(PAYLOAD.TECHNIQUE, kb.technique) if kb.get("technique") else ("DIRECT" if conf.get("direct") else None)) + errMsg += "Technique: %s\n" % (enumValueToNameLookup(PAYLOAD.TECHNIQUE, getTechnique()) if getTechnique() is not None else ("DIRECT" if conf.get("direct") else None)) errMsg += "Back-end DBMS:" if Backend.getDbms() is not None: diff --git a/lib/core/settings.py b/lib/core/settings.py index b336ec671..c1d9b8874 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -18,7 +18,7 @@ from lib.core.enums import OS from thirdparty.six import unichr as _unichr # sqlmap version (<major>.<minor>.<month>.<monthly commit>) -VERSION = "1.3.6.58" +VERSION = "1.3.7.0" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/core/threads.py b/lib/core/threads.py index 3fa8263ae..010534a00 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -63,6 +63,7 @@ class _ThreadData(threading.local): self.retriesCount = 0 self.seqMatcher = difflib.SequenceMatcher(None) self.shared = shared + self.technique = None self.validationRun = 0 self.valueStack = [] @@ -113,6 +114,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio kb.threadContinue = True kb.threadException = False + kb.technique = ThreadData.technique if threadChoice and numThreads == 1 and not (kb.injection.data and not any(_ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in kb.injection.data)): while True: @@ -206,6 +208,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio finally: kb.threadContinue = True kb.threadException = False + kb.technique = None for lock in kb.locks.values(): if lock.locked(): diff --git a/lib/request/inject.py b/lib/request/inject.py index 6a84838e9..865d373e1 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -19,6 +19,7 @@ from lib.core.common import expandAsteriskForColumns from lib.core.common import extractExpectedValue from lib.core.common import filterNone from lib.core.common import getPublicTypeMembers +from lib.core.common import getTechnique from lib.core.common import getTechniqueData from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite @@ -31,6 +32,7 @@ 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 setTechnique from lib.core.common import singleTimeWarnMessage from lib.core.compat import xrange from lib.core.data import conf @@ -88,7 +90,7 @@ def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar if value is not None: return value - timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) + timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) if timeBasedCompare and conf.threads > 1 and kb.forceThreads is None: msg = "multi-threading is considered unsafe in " @@ -160,9 +162,9 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char parameter through a bisection algorithm. """ - initTechnique(kb.technique) + initTechnique(getTechnique()) - query = agent.prefixQuery(kb.injection.data[kb.technique].vector) + query = agent.prefixQuery(kb.injection.data[getTechnique()].vector) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) count = None @@ -307,10 +309,10 @@ def _goBooleanProxy(expression): Retrieve the output of a boolean based SQL query """ - initTechnique(kb.technique) + initTechnique(getTechnique()) if conf.dnsDomain: - query = agent.prefixQuery(kb.injection.data[kb.technique].vector) + query = agent.prefixQuery(kb.injection.data[getTechnique()].vector) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) output = _goDns(payload, expression) @@ -318,13 +320,13 @@ def _goBooleanProxy(expression): if output is not None: return output - vector = kb.injection.data[kb.technique].vector + vector = kb.injection.data[getTechnique()].vector vector = vector.replace(INFERENCE_MARKER, expression) query = agent.prefixQuery(vector) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) - timeBasedCompare = kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) + timeBasedCompare = getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) output = hashDBRetrieve(expression, checkConf=True) @@ -401,7 +403,7 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser if not conf.forceDns: if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): - kb.technique = PAYLOAD.TECHNIQUE.UNION + setTechnique(PAYLOAD.TECHNIQUE.UNION) kb.forcePartialUnion = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector[8] fallback = not expected and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion @@ -433,7 +435,7 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser singleTimeWarnMessage(warnMsg) if error and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) and not found: - kb.technique = PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY + setTechnique(PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY) value = errorUse(forgeCaseExpression if expected == EXPECTED.BOOL else query, dump) count += 1 found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE @@ -446,7 +448,7 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser singleTimeWarnMessage(warnMsg) if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found: - kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN + setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN) if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) @@ -461,9 +463,9 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser kb.responseTimeMode = "%s|%s" % (match.group(1), match.group(2)) if match else None if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME): - kb.technique = PAYLOAD.TECHNIQUE.TIME + setTechnique(PAYLOAD.TECHNIQUE.TIME) else: - kb.technique = PAYLOAD.TECHNIQUE.STACKED + setTechnique(PAYLOAD.TECHNIQUE.STACKED) if expected == EXPECTED.BOOL: value = _goBooleanProxy(booleanExpression) @@ -505,12 +507,12 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser def goStacked(expression, silent=False): if PAYLOAD.TECHNIQUE.STACKED in kb.injection.data: - kb.technique = PAYLOAD.TECHNIQUE.STACKED + setTechnique(PAYLOAD.TECHNIQUE.STACKED) else: for technique in getPublicTypeMembers(PAYLOAD.TECHNIQUE, True): _ = getTechniqueData(technique) if _ and "stacked" in _["title"].lower(): - kb.technique = technique + setTechnique(technique) break expression = cleanQuery(expression) diff --git a/lib/takeover/web.py b/lib/takeover/web.py index 1904081dc..1a12e3cb0 100644 --- a/lib/takeover/web.py +++ b/lib/takeover/web.py @@ -20,6 +20,7 @@ from lib.core.common import getAutoDirectories from lib.core.common import getManualDirectories from lib.core.common import getPublicTypeMembers from lib.core.common import getSQLSnippet +from lib.core.common import getTechnique from lib.core.common import isTechniqueAvailable from lib.core.common import isWindowsDriveLetterPath from lib.core.common import normalizePath @@ -147,8 +148,8 @@ class Web(object): uplQuery = getUnicode(fileContent).replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) query = "" - if isTechniqueAvailable(kb.technique): - where = kb.injection.data[kb.technique].where + if isTechniqueAvailable(getTechnique()): + where = kb.injection.data[getTechnique()].where if where == PAYLOAD.WHERE.NEGATIVE: randInt = randomInt() diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 71fbe68c9..d827f939d 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -22,6 +22,7 @@ from lib.core.common import filterControlChars from lib.core.common import getCharset from lib.core.common import getCounter from lib.core.common import getPartRun +from lib.core.common import getTechnique from lib.core.common import goGoodSamaritan from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite @@ -80,7 +81,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() - timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) + timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: @@ -201,7 +202,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None forgedPayload = agent.extractPayload(payload) forgedPayload = safeStringFormat(forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)).replace(markingValue, unescapedCharValue) result = Request.queryPage(agent.replacePayload(payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) if result: return hintValue[idx - 1] @@ -228,13 +229,13 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) - if result and timeBasedCompare and kb.injection.data[kb.technique].trueCode: - result = threadData.lastCode == kb.injection.data[kb.technique].trueCode + if result and timeBasedCompare and kb.injection.data[getTechnique()].trueCode: + result = threadData.lastCode == kb.injection.data[getTechnique()].trueCode if not result: - warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (threadData.lastCode, kb.injection.data[kb.technique].trueCode) + warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (threadData.lastCode, kb.injection.data[getTechnique()].trueCode) singleTimeWarnMessage(warnMsg) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) return result @@ -269,7 +270,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None elif len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) if result: return decodeIntToUnicode(charTbl[0]) @@ -338,10 +339,10 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None kb.responseTimePayload = None result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) if not timeBasedCompare: - unexpectedCode |= threadData.lastCode not in (kb.injection.data[kb.technique].falseCode, kb.injection.data[kb.technique].trueCode) + unexpectedCode |= threadData.lastCode not in (kb.injection.data[getTechnique()].falseCode, kb.injection.data[getTechnique()].trueCode) if unexpectedCode: warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode singleTimeWarnMessage(warnMsg) @@ -439,7 +440,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, "&%d%s" % (mask, INFERENCE_GREATER_CHAR)), (expressionUnescaped, idx, 0)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) if result: candidates = [_ for _ in candidates if _ & mask > 0] @@ -451,7 +452,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if candidates: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, candidates[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) if result: return decodeIntToUnicode(candidates[0]) @@ -569,12 +570,12 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None # One-shot query containing equals commonValue testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False) - query = kb.injection.data[kb.technique].vector + query = kb.injection.data[getTechnique()].vector query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)%s%s" % (expressionUnescaped, INFERENCE_EQUALS_CHAR, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) # Did we have luck? if result: @@ -593,12 +594,12 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern)) testValue = unescaper.escape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.escape("%s" % commonPattern, quote=False) - query = kb.injection.data[kb.technique].vector + query = kb.injection.data[getTechnique()].vector query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)=%s" % (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) - incrementCounter(kb.technique) + incrementCounter(getTechnique()) # Did we have luck? if result: @@ -678,7 +679,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None _ = finalValue or partialValue - return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _ + return getCounter(getTechnique()), safecharencode(_) if kb.safeCharEncode else _ def queryOutputLength(expression, payload): """ diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index f608c1c9a..e9939d7e4 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -21,6 +21,7 @@ from lib.core.common import extractRegexResult from lib.core.common import firstNotNone from lib.core.common import getConsoleWidth from lib.core.common import getPartRun +from lib.core.common import getTechnique from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite from lib.core.common import incrementCounter @@ -43,7 +44,6 @@ from lib.core.dicts import FROM_DUMMY_TABLE from lib.core.enums import DBMS from lib.core.enums import HASHDB_KEYS from lib.core.enums import HTTP_HEADER -from lib.core.enums import PAYLOAD from lib.core.exception import SqlmapDataException from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD from lib.core.settings import MAX_ERROR_CHUNK_LENGTH @@ -124,7 +124,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, kb.errorChunkLength) # Forge the error-based SQL injection request - vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector + vector = kb.injection.data[getTechnique()].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression @@ -135,7 +135,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): # Perform the request page, headers, _ = Request.queryPage(payload, content=True, raise404=False) - incrementCounter(PAYLOAD.TECHNIQUE.ERROR) + incrementCounter(getTechnique()) if page and conf.noEscape: page = re.sub(r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) @@ -299,7 +299,7 @@ def errorUse(expression, dump=False): SQL injection vulnerability on the affected parameter. """ - initTechnique(PAYLOAD.TECHNIQUE.ERROR) + initTechnique(getTechnique()) abortedFlag = False count = None @@ -461,7 +461,7 @@ def errorUse(expression, dump=False): duration = calculateDeltaSeconds(start) if not kb.bruteMode: - debugMsg = "performed %d queries in %.2f seconds" % (kb.counters[PAYLOAD.TECHNIQUE.ERROR], duration) + debugMsg = "performed %d queries in %.2f seconds" % (kb.counters[getTechnique()], duration) logger.debug(debugMsg) return value diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index 0e5163aff..6de181381 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -20,6 +20,7 @@ from lib.core.common import randomInt from lib.core.common import randomStr from lib.core.common import readInput from lib.core.common import removeReflectiveValues +from lib.core.common import setTechnique from lib.core.common import singleTimeLogMessage from lib.core.common import singleTimeWarnMessage from lib.core.common import stdev @@ -323,7 +324,7 @@ def unionTest(comment, place, parameter, value, prefix, suffix): return negativeLogic = kb.negativeLogic - kb.technique = PAYLOAD.TECHNIQUE.UNION + setTechnique(PAYLOAD.TECHNIQUE.UNION) try: if negativeLogic: