def heuristicCheckSqlInjection(place, parameter): if kb.nullConnection: debugMsg = "heuristic checking skipped " debugMsg += "because NULL connection used" logger.debug(debugMsg) return None if wasLastRequestDBMSError(): debugMsg = "heuristic checking skipped " debugMsg += "because original page content " debugMsg += "contains DBMS error" logger.debug(debugMsg) return None prefix = "" suffix = "" if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix payload = "%s%s%s" % (prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix) payload = agent.payload(place, parameter, newValue=payload) page, _ = Request.queryPage(payload, place, content=True, raise404=False) parseFilePaths(page) result = wasLastRequestDBMSError() infoMsg = "heuristic test shows that %s " % place infoMsg += "parameter '%s' might " % parameter kb.heuristicTest = result if not result and kb.dynamicParameter: _ = conf.paramDict[place][parameter] if _.isdigit(): randInt = int(randomInt()) payload = "%s%s%s" % (prefix, "%s-%s" % (int(_) + randInt, randInt), suffix) payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE) result = Request.queryPage(payload, place, raise404=False) if result: infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION) logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warn(infoMsg) return result
def heuristicCheckSqlInjection(place, parameter): if kb.nullConnection: debugMsg = "heuristic checking skipped " debugMsg += "because NULL connection used" logger.debug(debugMsg) return prefix = "" suffix = "" if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix payload = "%s%s%s" % (prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix) payload = agent.payload(place, parameter, newValue=payload) Request.queryPage(payload, place, content=True, raise404=False) result = wasLastRequestDBMSError() infoMsg = "heuristic test shows that %s " % place infoMsg += "parameter '%s' might " % parameter if result: infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION) logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warn(infoMsg) return result
def checkConnection(suppressOutput=False): try: socket.gethostbyname(conf.hostname) except socket.gaierror: errMsg = "host '%s' does not exist" % conf.hostname raise sqlmapConnectionException, errMsg if not suppressOutput: infoMsg = "testing connection to the target url" logger.info(infoMsg) try: page, _ = Request.queryPage(content=True) kb.originalPage = kb.pageTemplate = page kb.errorIsNone = False if not kb.originalPage: errMsg = "unable to retrieve page content" raise sqlmapConnectionException, errMsg elif wasLastRequestDBMSError(): warnMsg = "there is a DBMS error found in the HTTP response body" warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) elif wasLastRequestHTTPError(): warnMsg = "the web server responded with an HTTP error code " warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) else: kb.errorIsNone = True except sqlmapConnectionException, errMsg: errMsg = getUnicode(errMsg) raise sqlmapConnectionException, errMsg
def checkConnection(suppressOutput=False): if not any([conf.proxy, conf.tor]): try: socket.getaddrinfo(conf.hostname, None) except socket.gaierror: errMsg = "host '%s' does not exist" % conf.hostname raise sqlmapConnectionException, errMsg if not suppressOutput: infoMsg = "testing connection to the target url" logger.info(infoMsg) try: page, _ = Request.queryPage(content=True, noteResponseTime=False) kb.originalPage = kb.pageTemplate = page kb.errorIsNone = False if not kb.originalPage and wasLastRequestHTTPError(): errMsg = "unable to retrieve page content" raise sqlmapConnectionException, errMsg elif wasLastRequestDBMSError(): warnMsg = "there is a DBMS error found in the HTTP response body" warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) elif wasLastRequestHTTPError(): warnMsg = "the web server responded with an HTTP error code " warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) else: kb.errorIsNone = True except sqlmapConnectionException, errMsg: errMsg = getUnicode(errMsg) raise sqlmapConnectionException, errMsg
def heuristicCheckSqlInjection(place, parameter, value): if kb.nullConnection: debugMsg = "heuristic checking skipped " debugMsg += "because NULL connection used" logger.debug(debugMsg) return prefix = "" suffix = "" if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix payload = "%s%s%s%s" % (value, prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix) payload = agent.payload(place, parameter, value, payload) Request.queryPage(payload, place, raise404=False) result = wasLastRequestDBMSError() infoMsg = "heuristics shows that %s " % place infoMsg += "parameter '%s' might " % parameter if result: infoMsg += "be injectable (possible DBMS: %s)" % (kb.htmlFp[-1] if kb.htmlFp else 'Unknown') logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warning(infoMsg)
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.unescape(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 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 " warnMsg += "(probably due to its length and/or content): " warnMsg += safecharencode(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 checkConnection(suppressOutput=False): if not any([conf.proxy, conf.tor]): try: socket.getaddrinfo(conf.hostname, None) except socket.gaierror: errMsg = "host '%s' does not exist" % conf.hostname raise sqlmapConnectionException, errMsg if not suppressOutput: infoMsg = "testing connection to the target url" logger.info(infoMsg) try: page, _ = Request.queryPage(content=True, noteResponseTime=False) kb.originalPage = kb.pageTemplate = page kb.errorIsNone = False if not kb.originalPage and wasLastRequestHTTPError(): errMsg = "unable to retrieve page content" raise sqlmapConnectionException, errMsg elif wasLastRequestDBMSError(): warnMsg = "there is a DBMS error found in the HTTP response body" warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) elif wasLastRequestHTTPError(): warnMsg = "the web server responded with an HTTP error code (%d) " % getLastRequestHTTPError( ) warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) else: kb.errorIsNone = True except sqlmapConnectionException, errMsg: errMsg = getUnicode(errMsg) logger.critical(errMsg) if conf.ipv6: warnMsg = "check connection to a provided " warnMsg += "IPv6 address with a tool like ping6 " warnMsg += "(e.g. 'ping6 %s') " % conf.hostname warnMsg += "prior to running sqlmap to avoid " warnMsg += "any addressing issues" singleTimeWarnMessage(warnMsg) if any(code in kb.httpErrorCodes for code in (httplib.NOT_FOUND, )): if conf.multipleTargets: return False msg = "it is not recommended to continue in this kind of cases. Do you want to quit and make sure that everything is set up properly? [Y/n] " if readInput(msg, default="Y") not in ("n", "N"): raise sqlmapSilentQuitException else: kb.ignoreNotFound = True else: raise
def checkConnection(suppressOutput=False): if not any([conf.proxy, conf.tor]): try: socket.getaddrinfo(conf.hostname, None) except socket.gaierror: errMsg = "host '%s' does not exist" % conf.hostname raise SqlmapConnectionException, errMsg if not suppressOutput: infoMsg = "testing connection to the target url" logger.info(infoMsg) try: page, _ = Request.queryPage(content=True, noteResponseTime=False) kb.originalPage = kb.pageTemplate = page kb.errorIsNone = False if not kb.originalPage and wasLastRequestHTTPError(): errMsg = "unable to retrieve page content" raise SqlmapConnectionException, errMsg elif wasLastRequestDBMSError(): warnMsg = "there is a DBMS error found in the HTTP response body " warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) elif wasLastRequestHTTPError(): warnMsg = "the web server responded with an HTTP error code (%d) " % getLastRequestHTTPError() warnMsg += "which could interfere with the results of the tests" logger.warn(warnMsg) else: kb.errorIsNone = True except SqlmapConnectionException, errMsg: errMsg = getUnicode(errMsg) logger.critical(errMsg) if conf.ipv6: warnMsg = "check connection to a provided " warnMsg += "IPv6 address with a tool like ping6 " warnMsg += "(e.g. 'ping6 %s') " % conf.hostname warnMsg += "prior to running sqlmap to avoid " warnMsg += "any addressing issues" singleTimeWarnMessage(warnMsg) if any(code in kb.httpErrorCodes for code in (httplib.NOT_FOUND, )): if conf.multipleTargets: return False msg = "it is not recommended to continue in this kind of cases. Do you want to quit and make sure that everything is set up properly? [Y/n] " if readInput(msg, default="Y") not in ("n", "N"): raise SqlmapSilentQuitException else: kb.ignoreNotFound = True else: raise
def simpletonCheckSqlInjection(place, parameter, value): """ This is a function for the quickest and simplest SQL injection check (e.g. AND 1=1) - only works with integer parameters """ result = False randInt = randomInt() if value.isdigit(): payload = "%s AND %d=%d" % (value, randInt, randInt) else: return False payload = agent.payload(place, parameter, value, payload) firstPage, _ = Request.queryPage(payload, place, content=True, raise404=False) if not (wasLastRequestDBMSError() or wasLastRequestHTTPError()): if getComparePageRatio(kb.originalPage, firstPage, filtered=True) > CONSTANT_RATIO: payload = "%s AND %d=%d" % (value, randInt, randInt + 1) payload = agent.payload(place, parameter, value, payload) secondPage, _ = Request.queryPage(payload, place, content=True, raise404=False) result = getComparePageRatio(firstPage, secondPage, filtered=True) <= CONSTANT_RATIO infoMsg = "simpleton test shows that %s " % place infoMsg += "parameter '%s' might " % parameter if result: infoMsg += "be injectable" logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warn(infoMsg) return result
def __getDatabaseDir(self): retVal = None infoMsg = "searching for database directory" logger.info(infoMsg) randInt = randomInt() randStr = randomStr() _ = inject.checkBooleanExpression("EXISTS(SELECT * FROM %s.%s WHERE %d=%d)" % (randStr, randStr, randInt, randInt)) if wasLastRequestDBMSError(): threadData = getCurrentThreadData() match = re.search("Could not find file\s+'([^']+?)'", threadData.lastErrorPage[1]) if match: retVal = match.group(1).rstrip("%s.mdb" % randStr) if retVal.endswith('\\'): retVal = retVal[:-1] return retVal
def __getDatabaseDir(self): retVal = None infoMsg = "searching for database directory" logger.info(infoMsg) randInt = randomInt() randStr = randomStr() query = agent.prefixQuery("AND EXISTS(SELECT * FROM %s.%s WHERE %d=%d)" % (randStr, randStr, randInt, randInt)) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) page = Request.queryPage(payload, content=True) if wasLastRequestDBMSError(): match = re.search("Could not find file\s+'([^']+?)'", page[0]) if match: retVal = match.group(1).rstrip("%s.mdb" % randStr) if retVal.endswith("\\"): retVal = retVal[:-1] return retVal
def heuristicCheckSqlInjection(place, parameter): if kb.nullConnection: debugMsg = "heuristic checking skipped " debugMsg += "because NULL connection used" logger.debug(debugMsg) return prefix = "" suffix = "" if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix payload = "%s%s%s" % (prefix, randomStr(length=10, alphabet=['"', "'", ")", "("]), suffix) payload = agent.payload(place, parameter, newValue=payload) Request.queryPage(payload, place, content=True, raise404=False) result = wasLastRequestDBMSError() infoMsg = "heuristic test shows that %s " % place infoMsg += "parameter '%s' might " % parameter kb.heuristicTest = result if result: infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION) logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warn(infoMsg) return result
def __getDatabaseDir(self): retVal = None infoMsg = "searching for database directory" logger.info(infoMsg) randInt = randomInt() randStr = randomStr() _ = inject.checkBooleanExpression( "EXISTS(SELECT * FROM %s.%s WHERE %d=%d)" % (randStr, randStr, randInt, randInt)) if wasLastRequestDBMSError(): threadData = getCurrentThreadData() match = re.search("Could not find file\s+'([^']+?)'", threadData.lastErrorPage[1]) if match: retVal = match.group(1).rstrip("%s.mdb" % randStr) if retVal.endswith('\\'): retVal = retVal[:-1] 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 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.unescape(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 = 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 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, PAYLOAD.WHERE.NEGATIVE, 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 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 _comparison(page, headers, code, getRatioValue, pageLength): threadData = getCurrentThreadData() if kb.testMode: threadData.lastComparisonPage = page if page is None and pageLength is None: return None seqMatcher = threadData.seqMatcher seqMatcher.set_seq1(kb.pageTemplate) if any((conf.string, conf.notString, conf.regexp)): rawResponse = "%s%s" % ( listToStrValue(headers.headers if headers else ""), page) # String to match in page when the query is True and/or valid if conf.string: return conf.string in rawResponse # String to match in page when the query is False and/or invalid if conf.notString: return conf.notString not in rawResponse # Regular expression to match in page when the query is True and/or valid if conf.regexp: return re.search(conf.regexp, rawResponse, re.I | re.M) is not None # HTTP code to match when the query is valid if conf.code: return conf.code == code if page: # In case of an DBMS error page return None if kb.errorIsNone and (wasLastRequestDBMSError() or wasLastRequestHTTPError()): return None # Dynamic content lines to be excluded before comparison if not kb.nullConnection: page = removeDynamicContent(page) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) if not pageLength: pageLength = len(page) if kb.nullConnection and pageLength: if not seqMatcher.a: errMsg = "problem occured while retrieving original page content " errMsg += "which prevents sqlmap from continuation. Please rerun, " errMsg += "and if the problem persists turn off any optimization switches" raise SqlmapNoneDataException(errMsg) ratio = 1. * pageLength / len(seqMatcher.a) if ratio > 1.: ratio = 1. / ratio else: # Preventing "Unicode equal comparison failed to convert both arguments to Unicode" # (e.g. if one page is PDF and the other is HTML) if isinstance(seqMatcher.a, str) and isinstance(page, unicode): page = page.encode(kb.pageEncoding or DEFAULT_PAGE_ENCODING, 'ignore') elif isinstance(seqMatcher.a, unicode) and isinstance(page, str): seqMatcher.a = seqMatcher.a.encode( kb.pageEncoding or DEFAULT_PAGE_ENCODING, 'ignore') seq1, seq2 = None, None if conf.titles: seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) seq2 = extractRegexResult(HTML_TITLE_REGEX, page) else: seq1 = getFilteredPageContent( seqMatcher.a, True) if conf.textOnly else seqMatcher.a seq2 = getFilteredPageContent(page, True) if conf.textOnly else page if seq1 is None or seq2 is None: return None seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") count = 0 while count < min(len(seq1), len(seq2)): if seq1[count] == seq2[count]: count += 1 else: break if count: seq1 = seq1[count:] seq2 = seq2[count:] seqMatcher.set_seq1(seq1) seqMatcher.set_seq2(seq2) ratio = round(seqMatcher.quick_ratio(), 3) # If the url is stable and we did not set yet the match ratio and the # current injected value changes the url page content if kb.matchRatio is None: if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: kb.matchRatio = ratio logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio) # If it has been requested to return the ratio and not a comparison # response if getRatioValue: return ratio elif ratio > UPPER_RATIO_BOUND: return True elif kb.matchRatio is None: return None else: return (ratio - kb.matchRatio) > DIFF_TOLERANCE
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 comparison(page, getRatioValue=False, pageLength=None): if page is None and pageLength is None: return None regExpResults = None seqMatcher = getCurrentThreadData().seqMatcher seqMatcher.set_seq1(kb.pageTemplate) if page: # String to match in page when the query is valid if conf.string: condition = conf.string in page return condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO) # Regular expression to match in page when the query is valid if conf.regexp: condition = re.search(conf.regexp, page, re.I | re.M) is not None return condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO) # In case of an DBMS error page return None if kb.errorIsNone and (wasLastRequestDBMSError() or wasLastRequestHTTPError()): return None # Dynamic content lines to be excluded before comparison if not kb.nullConnection: page = removeDynamicContent(page) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) if not pageLength: pageLength = len(page) if kb.nullConnection and pageLength: if not seqMatcher.a: errMsg = "problem occured while retrieving original page content " errMsg += "which prevents sqlmap from continuation. please rerun, " errMsg += "and if problem persists please turn off optimization switches" raise sqlmapNoneDataException, errMsg ratio = 1. * pageLength / len(seqMatcher.a) if ratio > 1.: ratio = 1. / ratio else: seqMatcher.set_seq1(getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a) seqMatcher.set_seq2(getFilteredPageContent(page, True) if conf.textOnly else page) ratio = round(seqMatcher.quick_ratio(), 3) # If the url is stable and we did not set yet the match ratio and the # current injected value changes the url page content if kb.matchRatio is None: if kb.pageStable and ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: kb.matchRatio = ratio logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio) elif not kb.pageStable: kb.matchRatio = CONSTANT_RATIO logger.debug("setting match ratio for current parameter to default value 0.900") # If it has been requested to return the ratio and not a comparison # response if getRatioValue: return ratio elif ratio > UPPER_RATIO_BOUND: return True elif kb.matchRatio is None: return None else: if kb.matchRatio == CONSTANT_RATIO: return ratio > kb.matchRatio else: return (ratio - kb.matchRatio) > DIFF_TOLERANCE
def heuristicCheckSqlInjection(place, parameter): if kb.nullConnection: debugMsg = "heuristic checking skipped " debugMsg += "because NULL connection used" logger.debug(debugMsg) return None if wasLastRequestDBMSError(): debugMsg = "heuristic checking skipped " debugMsg += "because original page content " debugMsg += "contains DBMS error" logger.debug(debugMsg) return None origValue = conf.paramDict[place][parameter] prefix = "" suffix = "" if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix randStr = "" while '\'' not in randStr: randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET) payload = "%s%s%s" % (prefix, randStr, suffix) payload = agent.payload(place, parameter, newValue=payload) page, _ = Request.queryPage(payload, place, content=True, raise404=False) parseFilePaths(page) result = wasLastRequestDBMSError() infoMsg = "heuristic test shows that %s " % place infoMsg += "parameter '%s' might " % parameter def _(page): return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS) casting = _(page) and not _(kb.originalPage) if not casting and not result and kb.dynamicParameter and origValue.isdigit(): randInt = int(randomInt()) payload = "%s%s%s" % (prefix, "%d-%d" % (int(origValue) + randInt, randInt), suffix) payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE) result = Request.queryPage(payload, place, raise404=False) if not result: randStr = randomStr() payload = "%s%s%s" % (prefix, "%s%s" % (origValue, randStr), suffix) payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE) casting = Request.queryPage(payload, place, raise404=False) kb.heuristicTest = HEURISTIC_TEST.CASTED if casting else HEURISTIC_TEST.NEGATIVE if not result else HEURISTIC_TEST.POSITIVE if casting: errMsg = "possible %s casting " % ("integer" if origValue.isdigit() else "type") errMsg += "detected (e.g. %s=(int)$_REQUEST('%s')) " % (parameter, parameter) errMsg += "at the back-end web application" logger.error(errMsg) if kb.ignoreCasted is None: message = "do you want to skip those kind of cases (and save scanning time)? %s " % ("[Y/n]" if conf.multipleTargets else "[y/N]") kb.ignoreCasted = readInput(message, default='Y' if conf.multipleTargets else 'N').upper() != 'N' elif result: infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION) logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warn(infoMsg) return kb.heuristicTest
def comparison(page, headers=None, getSeqMatcher=False, pageLength=None): if page is None and pageLength is None: return None regExpResults = None if page: # String to be excluded before calculating page hash if conf.eString and conf.eString in page: index = page.index(conf.eString) length = len(conf.eString) pageWithoutString = page[:index] pageWithoutString += page[index + length:] page = pageWithoutString # Regular expression matches to be excluded before calculating page hash if conf.eRegexp: regExpResults = re.findall(conf.eRegexp, page, re.I | re.M) if regExpResults: for regExpResult in regExpResults: index = page.index(regExpResult) length = len(regExpResult) pageWithoutRegExp = page[:index] pageWithoutRegExp += page[index + length:] page = pageWithoutRegExp # String to match in page when the query is valid if conf.string: return conf.string in page # Regular expression to match in page when the query is valid if conf.regexp: return re.search(conf.regexp, page, re.I | re.M) is not None # In case of an DBMS error page return None if wasLastRequestDBMSError(): return None # Dynamic content lines to be excluded before comparison if not kb.nullConnection and not conf.longestCommon: for item in kb.dynamicMarkings: prefix, suffix = item if prefix is None: page = re.sub('(?s)^.+%s' % suffix, suffix, page) elif suffix is None: page = re.sub('(?s)%s.+$' % prefix, prefix, page) else: page = re.sub('(?s)%s.+%s' % (prefix, suffix), '%s%s' % (prefix, suffix), page) if not pageLength: pageLength = len(page) if kb.locks.seqLock: kb.locks.seqLock.acquire() if conf.longestCommon: (firstPage, secondPage) = (conf.seqMatcher.a, page) match = SequenceMatcher(None, firstPage, secondPage).find_longest_match( 0, len(firstPage), 0, len(secondPage)) ratio = round( SequenceMatcher(None, firstPage[match[0]:match[0] + match[2]], secondPage[match[1]:match[1] + match[2]]).ratio(), 3) elif not conf.eRegexp and not conf.eString and kb.nullConnection and pageLength: ratio = 1. * pageLength / len(conf.seqMatcher.a) if ratio > 1.: ratio = 1. / ratio else: conf.seqMatcher.set_seq2(page) ratio = round(conf.seqMatcher.ratio(), 3) if kb.locks.seqLock: kb.locks.seqLock.release() # If the url is stable and we did not set yet the match ratio and the # current injected value changes the url page content if conf.matchRatio is None: if conf.thold: conf.matchRatio = conf.thold elif kb.pageStable and ratio > 0.6 and ratio < 1: conf.matchRatio = ratio logger.debug("setting match ratio for current parameter to %.3f" % conf.matchRatio) elif not kb.pageStable or (kb.pageStable and ratio < 0.6): conf.matchRatio = CONSTANT_RATIO logger.debug( "setting match ratio for current parameter to default value 0.900" ) # If it has been requested to return the ratio and not a comparison # response if getSeqMatcher: return ratio elif ratio == 1: return True elif conf.matchRatio is None: return None else: if conf.matchRatio == CONSTANT_RATIO or conf.thold: return ratio > conf.matchRatio else: return (ratio - conf.matchRatio) > DIFF_TOLERANCE
def heuristicCheckSqlInjection(place, parameter): if kb.nullConnection: debugMsg = "heuristic checking skipped " debugMsg += "because NULL connection used" logger.debug(debugMsg) return None if wasLastRequestDBMSError(): debugMsg = "heuristic checking skipped " debugMsg += "because original page content " debugMsg += "contains DBMS error" logger.debug(debugMsg) return None origValue = conf.paramDict[place][parameter] prefix = "" suffix = "" if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix randStr = "" while '\'' not in randStr: randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET) payload = "%s%s%s" % (prefix, randStr, suffix) payload = agent.payload(place, parameter, newValue=payload) page, _ = Request.queryPage(payload, place, content=True, raise404=False) parseFilePaths(page) result = wasLastRequestDBMSError() infoMsg = "heuristic test shows that %s " % place infoMsg += "parameter '%s' might " % parameter def _(page): return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS) casting = _(page) and not _(kb.originalPage) if not casting and not result and kb.dynamicParameter and origValue.isdigit( ): randInt = int(randomInt()) payload = "%s%s%s" % (prefix, "%d-%d" % (int(origValue) + randInt, randInt), suffix) payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE) result = Request.queryPage(payload, place, raise404=False) if not result: randStr = randomStr() payload = "%s%s%s" % (prefix, "%s%s" % (origValue, randStr), suffix) payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE) casting = Request.queryPage(payload, place, raise404=False) kb.heuristicTest = HEURISTIC_TEST.CASTED if casting else HEURISTIC_TEST.NEGATIVE if not result else HEURISTIC_TEST.POSITIVE if casting: errMsg = "possible %s casting " % ("integer" if origValue.isdigit() else "type") errMsg += "detected (e.g. %s=(int)$_REQUEST('%s')) " % (parameter, parameter) errMsg += "at the back-end web application" logger.error(errMsg) if kb.ignoreCasted is None: message = "do you want to skip those kind of cases (and save scanning time)? %s " % ( "[Y/n]" if conf.multipleTargets else "[y/N]") kb.ignoreCasted = readInput( message, default='Y' if conf.multipleTargets else 'N').upper() != 'N' elif result: infoMsg += "be injectable (possible DBMS: %s)" % ( Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION) logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warn(infoMsg) return kb.heuristicTest
def _comparison(page, headers, code, getRatioValue, pageLength): threadData = getCurrentThreadData() if kb.testMode: threadData.lastComparisonHeaders = listToStrValue(headers.headers) if headers else "" threadData.lastComparisonPage = page if page is None and pageLength is None: return None seqMatcher = threadData.seqMatcher seqMatcher.set_seq1(kb.pageTemplate) if any((conf.string, conf.notString, conf.regexp)): rawResponse = "%s%s" % (listToStrValue(headers.headers) if headers else "", page) # String to match in page when the query is True and/or valid if conf.string: return conf.string in rawResponse # String to match in page when the query is False and/or invalid if conf.notString: return conf.notString not in rawResponse # Regular expression to match in page when the query is True and/or valid if conf.regexp: return re.search(conf.regexp, rawResponse, re.I | re.M) is not None # HTTP code to match when the query is valid if conf.code: return conf.code == code if page: # In case of an DBMS error page return None if kb.errorIsNone and (wasLastRequestDBMSError() or wasLastRequestHTTPError()): return None # Dynamic content lines to be excluded before comparison if not kb.nullConnection: page = removeDynamicContent(page) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) if not pageLength: pageLength = len(page) if kb.nullConnection and pageLength: if not seqMatcher.a: errMsg = "problem occured while retrieving original page content " errMsg += "which prevents sqlmap from continuation. Please rerun, " errMsg += "and if the problem persists turn off any optimization switches" raise SqlmapNoneDataException(errMsg) ratio = 1. * pageLength / len(seqMatcher.a) if ratio > 1.: ratio = 1. / ratio else: # Preventing "Unicode equal comparison failed to convert both arguments to Unicode" # (e.g. if one page is PDF and the other is HTML) if isinstance(seqMatcher.a, str) and isinstance(page, unicode): page = page.encode(kb.pageEncoding or DEFAULT_PAGE_ENCODING, 'ignore') elif isinstance(seqMatcher.a, unicode) and isinstance(page, str): seqMatcher.a = seqMatcher.a.encode(kb.pageEncoding or DEFAULT_PAGE_ENCODING, 'ignore') seq1, seq2 = None, None if conf.titles: seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) seq2 = extractRegexResult(HTML_TITLE_REGEX, page) else: seq1 = getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a seq2 = getFilteredPageContent(page, True) if conf.textOnly else page if seq1 is None or seq2 is None: return None seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") count = 0 while count < min(len(seq1), len(seq2)): if seq1[count] == seq2[count]: count += 1 else: break if count: seq1 = seq1[count:] seq2 = seq2[count:] seqMatcher.set_seq1(seq1) seqMatcher.set_seq2(seq2) ratio = round(seqMatcher.quick_ratio(), 3) # If the url is stable and we did not set yet the match ratio and the # current injected value changes the url page content if kb.matchRatio is None: if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: kb.matchRatio = ratio logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio) # If it has been requested to return the ratio and not a comparison # response if getRatioValue: return ratio elif ratio > UPPER_RATIO_BOUND: return True elif kb.matchRatio is None: return None else: return (ratio - kb.matchRatio) > DIFF_TOLERANCE
def comparison(page, getRatioValue=False, pageLength=None): if page is None and pageLength is None: return None regExpResults = None seqMatcher = getCurrentThreadData().seqMatcher seqMatcher.set_seq1(kb.pageTemplate) if page: # String to match in page when the query is valid if conf.string: condition = conf.string in page return condition if not getRatioValue else ( MAX_RATIO if condition else MIN_RATIO) # Regular expression to match in page when the query is valid if conf.regexp: condition = re.search(conf.regexp, page, re.I | re.M) is not None return condition if not getRatioValue else ( MAX_RATIO if condition else MIN_RATIO) # In case of an DBMS error page return None if kb.errorIsNone and (wasLastRequestDBMSError() or wasLastRequestHTTPError()): return None # Dynamic content lines to be excluded before comparison if not kb.nullConnection: page = removeDynamicContent(page) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) if not pageLength: pageLength = len(page) if kb.nullConnection and pageLength: if not seqMatcher.a: errMsg = "problem occured while retrieving original page content " errMsg += "which prevents sqlmap from continuation. please rerun, " errMsg += "and if problem persists please turn off optimization switches" raise sqlmapNoneDataException, errMsg ratio = 1. * pageLength / len(seqMatcher.a) if ratio > 1.: ratio = 1. / ratio else: seqMatcher.set_seq1( getFilteredPageContent(seqMatcher.a, True) if conf. textOnly else seqMatcher.a) seqMatcher.set_seq2( getFilteredPageContent(page, True) if conf.textOnly else page) ratio = round(seqMatcher.quick_ratio(), 3) # If the url is stable and we did not set yet the match ratio and the # current injected value changes the url page content if kb.matchRatio is None: if kb.pageStable and ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: kb.matchRatio = ratio logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio) elif not kb.pageStable: kb.matchRatio = CONSTANT_RATIO logger.debug( "setting match ratio for current parameter to default value 0.900" ) # If it has been requested to return the ratio and not a comparison # response if getRatioValue: return ratio elif ratio > UPPER_RATIO_BOUND: return True elif kb.matchRatio is None: return None else: if kb.matchRatio == CONSTANT_RATIO: return ratio > kb.matchRatio else: return (ratio - kb.matchRatio) > DIFF_TOLERANCE