Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/plugins/dbms/mysql/fingerprint.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
import re
9
10
from lib.core.common import Backend
11
from lib.core.common import Format
12
from lib.core.common import hashDBRetrieve
13
from lib.core.common import hashDBWrite
14
from lib.core.compat import xrange
15
from lib.core.convert import getUnicode
16
from lib.core.data import conf
17
from lib.core.data import kb
18
from lib.core.data import logger
19
from lib.core.enums import DBMS
20
from lib.core.enums import FORK
21
from lib.core.enums import HASHDB_KEYS
22
from lib.core.enums import OS
23
from lib.core.session import setDbms
24
from lib.core.settings import MYSQL_ALIASES
25
from lib.request import inject
26
from plugins.generic.fingerprint import Fingerprint as GenericFingerprint
27
28
class Fingerprint(GenericFingerprint):
29
def __init__(self):
30
GenericFingerprint.__init__(self, DBMS.MYSQL)
31
32
def _commentCheck(self):
33
infoMsg = "executing %s comment injection fingerprint" % DBMS.MYSQL
34
logger.info(infoMsg)
35
36
result = inject.checkBooleanExpression("[RANDNUM]=[RANDNUM]/* NoValue */")
37
38
if not result:
39
warnMsg = "unable to perform %s comment injection" % DBMS.MYSQL
40
logger.warning(warnMsg)
41
42
return None
43
44
# Reference: https://downloads.mysql.com/archives/community/
45
# Reference: https://dev.mysql.com/doc/relnotes/mysql/<major>.<minor>/en/
46
47
versions = (
48
(90200, 90202), # MySQL 9.2
49
(90100, 90102), # MySQL 9.1
50
(90000, 90002), # MySQL 9.0
51
(80400, 80405), # MySQL 8.4
52
(80300, 80302), # MySQL 8.3
53
(80200, 80202), # MySQL 8.2
54
(80100, 80102), # MySQL 8.1
55
(80000, 80041), # MySQL 8.0
56
(60000, 60014), # MySQL 6.0
57
(50700, 50745), # MySQL 5.7
58
(50600, 50652), # MySQL 5.6
59
(50500, 50563), # MySQL 5.5
60
(50400, 50404), # MySQL 5.4
61
(50100, 50174), # MySQL 5.1
62
(50000, 50097), # MySQL 5.0
63
(40100, 40131), # MySQL 4.1
64
(40000, 40032), # MySQL 4.0
65
(32300, 32359), # MySQL 3.23
66
(32200, 32235), # MySQL 3.22
67
)
68
69
found = False
70
for candidate in versions:
71
result = inject.checkBooleanExpression("[RANDNUM]=[RANDNUM]/*!%d AND [RANDNUM1]=[RANDNUM2]*/" % candidate[0])
72
73
if not result:
74
found = True
75
break
76
77
if found:
78
for version in xrange(candidate[1], candidate[0] - 1, -1):
79
version = getUnicode(version)
80
result = inject.checkBooleanExpression("[RANDNUM]=[RANDNUM]/*!%s AND [RANDNUM1]=[RANDNUM2]*/" % version)
81
82
if not result:
83
if version[0] == "3":
84
midVer = version[1:3]
85
else:
86
midVer = version[2]
87
88
trueVer = "%s.%s.%s" % (version[0], midVer, version[3:])
89
90
return trueVer
91
92
return None
93
94
def getFingerprint(self):
95
fork = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK)
96
97
if fork is None:
98
if inject.checkBooleanExpression("VERSION() LIKE '%MariaDB%'"):
99
fork = FORK.MARIADB
100
elif inject.checkBooleanExpression("VERSION() LIKE '%TiDB%'"):
101
fork = FORK.TIDB
102
elif inject.checkBooleanExpression("@@VERSION_COMMENT LIKE '%drizzle%'"):
103
fork = FORK.DRIZZLE
104
elif inject.checkBooleanExpression("@@VERSION_COMMENT LIKE '%Percona%'"):
105
fork = FORK.PERCONA
106
elif inject.checkBooleanExpression("@@VERSION_COMMENT LIKE '%Doris%'"):
107
fork = FORK.DORIS
108
elif inject.checkBooleanExpression("@@VERSION_COMMENT LIKE '%StarRocks%'"):
109
fork = FORK.STARROCKS
110
elif inject.checkBooleanExpression("AURORA_VERSION() LIKE '%'"): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/
111
fork = FORK.AURORA
112
else:
113
fork = ""
114
115
hashDBWrite(HASHDB_KEYS.DBMS_FORK, fork)
116
117
value = ""
118
wsOsFp = Format.getOs("web server", kb.headersFp)
119
120
if wsOsFp and not conf.api:
121
value += "%s\n" % wsOsFp
122
123
if kb.data.banner:
124
dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp)
125
126
if dbmsOsFp and not conf.api:
127
value += "%s\n" % dbmsOsFp
128
129
value += "back-end DBMS: "
130
actVer = Format.getDbms()
131
132
if not conf.extensiveFp:
133
value += actVer
134
if fork:
135
value += " (%s fork)" % fork
136
return value
137
138
comVer = self._commentCheck()
139
blank = " " * 15
140
value += "active fingerprint: %s" % actVer
141
142
if comVer:
143
comVer = Format.getDbms([comVer])
144
value += "\n%scomment injection fingerprint: %s" % (blank, comVer)
145
146
if kb.bannerFp:
147
banVer = kb.bannerFp.get("dbmsVersion")
148
149
if banVer:
150
if banVer and re.search(r"-log$", kb.data.banner or ""):
151
banVer += ", logging enabled"
152
153
banVer = Format.getDbms([banVer])
154
value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer)
155
156
htmlErrorFp = Format.getErrorParsedDBMSes()
157
158
if htmlErrorFp:
159
value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp)
160
161
if fork:
162
value += "\n%sfork fingerprint: %s" % (blank, fork)
163
164
return value
165
166
def checkDbms(self):
167
"""
168
References for fingerprint:
169
170
* http://dev.mysql.com/doc/refman/5.0/en/news-5-0-x.html (up to 5.0.89)
171
* http://dev.mysql.com/doc/refman/5.1/en/news-5-1-x.html (up to 5.1.42)
172
* http://dev.mysql.com/doc/refman/5.4/en/news-5-4-x.html (up to 5.4.4)
173
* http://dev.mysql.com/doc/refman/5.5/en/news-5-5-x.html (up to 5.5.0)
174
* http://dev.mysql.com/doc/refman/6.0/en/news-6-0-x.html (manual has been withdrawn)
175
"""
176
177
if not conf.extensiveFp and Backend.isDbmsWithin(MYSQL_ALIASES):
178
setDbms("%s %s" % (DBMS.MYSQL, Backend.getVersion()))
179
180
if Backend.isVersionGreaterOrEqualThan("5") or inject.checkBooleanExpression("DATABASE() LIKE SCHEMA()"):
181
kb.data.has_information_schema = True
182
self.getBanner()
183
184
return True
185
186
infoMsg = "testing %s" % DBMS.MYSQL
187
logger.info(infoMsg)
188
189
result = inject.checkBooleanExpression("QUARTER(NULL XOR NULL) IS NULL")
190
191
if result:
192
infoMsg = "confirming %s" % DBMS.MYSQL
193
logger.info(infoMsg)
194
195
result = inject.checkBooleanExpression("COALESCE(SESSION_USER(),USER()) IS NOT NULL")
196
197
if not result:
198
# Note: MemSQL doesn't support SESSION_USER()
199
result = inject.checkBooleanExpression("GEOGRAPHY_AREA(NULL) IS NULL")
200
201
if result:
202
hashDBWrite(HASHDB_KEYS.DBMS_FORK, FORK.MEMSQL)
203
204
if not result:
205
warnMsg = "the back-end DBMS is not %s" % DBMS.MYSQL
206
logger.warning(warnMsg)
207
208
return False
209
210
# reading information_schema on some platforms is causing annoying timeout exits
211
# Reference: http://bugs.mysql.com/bug.php?id=15855
212
213
kb.data.has_information_schema = True
214
215
# Determine if it is MySQL >= 9.0.0
216
if inject.checkBooleanExpression("ISNULL(VECTOR_DIM(NULL))"):
217
Backend.setVersion(">= 9.0.0")
218
setDbms("%s 9" % DBMS.MYSQL)
219
self.getBanner()
220
221
# Determine if it is MySQL >= 8.0.0
222
elif inject.checkBooleanExpression("ISNULL(JSON_STORAGE_FREE(NULL))"):
223
Backend.setVersion(">= 8.0.0")
224
setDbms("%s 8" % DBMS.MYSQL)
225
self.getBanner()
226
227
# Determine if it is MySQL >= 5.0.0
228
elif inject.checkBooleanExpression("ISNULL(TIMESTAMPADD(MINUTE,[RANDNUM],NULL))"):
229
Backend.setVersion(">= 5.0.0")
230
setDbms("%s 5" % DBMS.MYSQL)
231
self.getBanner()
232
233
if not conf.extensiveFp:
234
return True
235
236
infoMsg = "actively fingerprinting %s" % DBMS.MYSQL
237
logger.info(infoMsg)
238
239
# Check if it is MySQL >= 5.7
240
if inject.checkBooleanExpression("ISNULL(JSON_QUOTE(NULL))"):
241
Backend.setVersion(">= 5.7")
242
243
# Check if it is MySQL >= 5.6
244
elif inject.checkBooleanExpression("ISNULL(VALIDATE_PASSWORD_STRENGTH(NULL))"):
245
Backend.setVersion(">= 5.6")
246
247
# Check if it is MySQL >= 5.5
248
elif inject.checkBooleanExpression("TO_SECONDS(950501)>0"):
249
Backend.setVersion(">= 5.5")
250
251
# Check if it is MySQL >= 5.1.2 and < 5.5.0
252
elif inject.checkBooleanExpression("@@table_open_cache=@@table_open_cache"):
253
if inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.GLOBAL_STATUS LIMIT 0, 1)"):
254
Backend.setVersionList([">= 5.1.12", "< 5.5.0"])
255
elif inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PROCESSLIST LIMIT 0, 1)"):
256
Backend.setVersionList([">= 5.1.7", "< 5.1.12"])
257
elif inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PARTITIONS LIMIT 0, 1)"):
258
Backend.setVersion("= 5.1.6")
259
elif inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM information_schema.PLUGINS LIMIT 0, 1)"):
260
Backend.setVersionList([">= 5.1.5", "< 5.1.6"])
261
else:
262
Backend.setVersionList([">= 5.1.2", "< 5.1.5"])
263
264
# Check if it is MySQL >= 5.0.0 and < 5.1.2
265
elif inject.checkBooleanExpression("@@hostname=@@hostname"):
266
Backend.setVersionList([">= 5.0.38", "< 5.1.2"])
267
elif inject.checkBooleanExpression("@@character_set_filesystem=@@character_set_filesystem"):
268
Backend.setVersionList([">= 5.0.19", "< 5.0.38"])
269
elif not inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM DUAL WHERE [RANDNUM1]!=[RANDNUM2])"):
270
Backend.setVersionList([">= 5.0.11", "< 5.0.19"])
271
elif inject.checkBooleanExpression("@@div_precision_increment=@@div_precision_increment"):
272
Backend.setVersionList([">= 5.0.6", "< 5.0.11"])
273
elif inject.checkBooleanExpression("@@automatic_sp_privileges=@@automatic_sp_privileges"):
274
Backend.setVersionList([">= 5.0.3", "< 5.0.6"])
275
else:
276
Backend.setVersionList([">= 5.0.0", "< 5.0.3"])
277
278
elif inject.checkBooleanExpression("DATABASE() LIKE SCHEMA()"):
279
Backend.setVersion(">= 5.0.2")
280
setDbms("%s 5" % DBMS.MYSQL)
281
self.getBanner()
282
283
elif inject.checkBooleanExpression("STRCMP(LOWER(CURRENT_USER()), UPPER(CURRENT_USER()))=0"):
284
Backend.setVersion("< 5.0.0")
285
setDbms("%s 4" % DBMS.MYSQL)
286
self.getBanner()
287
288
kb.data.has_information_schema = False
289
290
if not conf.extensiveFp:
291
return True
292
293
# Check which version of MySQL < 5.0.0 it is
294
if inject.checkBooleanExpression("3=(SELECT COERCIBILITY(USER()))"):
295
Backend.setVersionList([">= 4.1.11", "< 5.0.0"])
296
elif inject.checkBooleanExpression("2=(SELECT COERCIBILITY(USER()))"):
297
Backend.setVersionList([">= 4.1.1", "< 4.1.11"])
298
elif inject.checkBooleanExpression("CURRENT_USER()=CURRENT_USER()"):
299
Backend.setVersionList([">= 4.0.6", "< 4.1.1"])
300
301
if inject.checkBooleanExpression("'utf8'=(SELECT CHARSET(CURRENT_USER()))"):
302
Backend.setVersion("= 4.1.0")
303
else:
304
Backend.setVersionList([">= 4.0.6", "< 4.1.0"])
305
else:
306
Backend.setVersionList([">= 4.0.0", "< 4.0.6"])
307
else:
308
Backend.setVersion("< 4.0.0")
309
setDbms("%s 3" % DBMS.MYSQL)
310
self.getBanner()
311
312
kb.data.has_information_schema = False
313
314
return True
315
else:
316
warnMsg = "the back-end DBMS is not %s" % DBMS.MYSQL
317
logger.warning(warnMsg)
318
319
return False
320
321
def checkDbmsOs(self, detailed=False):
322
if Backend.getOs():
323
return
324
325
infoMsg = "fingerprinting the back-end DBMS operating system"
326
logger.info(infoMsg)
327
328
result = inject.checkBooleanExpression("'W'=UPPER(MID(@@version_compile_os,1,1))")
329
330
if result:
331
Backend.setOs(OS.WINDOWS)
332
elif not result:
333
Backend.setOs(OS.LINUX)
334
335
if Backend.getOs():
336
infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs()
337
logger.info(infoMsg)
338
else:
339
self.userChooseDbmsOs()
340
341
self.cleanup(onlyFileTbl=True)
342
343