Patching silent per-thread issue with technique switching (fixes #3784)

This commit is contained in:
Miroslav Stampar 2019-07-01 10:43:05 +02:00
parent 32e09c8dfb
commit 3abd3e1a8d
9 changed files with 74 additions and 53 deletions

View File

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

View File

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

View File

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

View File

@ -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():

View File

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

View File

@ -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()

View File

@ -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):
"""

View File

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

View File

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