1#!/usr/bin/env python
2
3"""
4Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
5See the file 'LICENSE' for copying permission
6"""
7
8import re
9
10from lib.core.agent import agent
11from lib.core.common import arrayizeValue
12from lib.core.common import getLimitRange
13from lib.core.common import isInferenceAvailable
14from lib.core.common import isNoneValue
15from lib.core.common import isNumPosStrValue
16from lib.core.common import isTechniqueAvailable
17from lib.core.common import safeSQLIdentificatorNaming
18from lib.core.common import safeStringFormat
19from lib.core.common import singleTimeLogMessage
20from lib.core.common import unArrayizeValue
21from lib.core.common import unsafeSQLIdentificatorNaming
22from lib.core.compat import xrange
23from lib.core.data import conf
24from lib.core.data import kb
25from lib.core.data import logger
26from lib.core.data import queries
27from lib.core.enums import CHARSET_TYPE
28from lib.core.enums import DBMS
29from lib.core.enums import EXPECTED
30from lib.core.enums import PAYLOAD
31from lib.core.exception import SqlmapNoneDataException
32from lib.core.settings import CURRENT_DB
33from lib.request import inject
34from plugins.generic.enumeration import Enumeration as GenericEnumeration
35from thirdparty import six
36
37class Enumeration(GenericEnumeration):
38    def getPrivileges(self, *args, **kwargs):
39        warnMsg = "on Microsoft SQL Server it is not possible to fetch "
40        warnMsg += "database users privileges, sqlmap will check whether "
41        warnMsg += "or not the database users are database administrators"
42        logger.warn(warnMsg)
43
44        users = []
45        areAdmins = set()
46
47        if conf.user:
48            users = [conf.user]
49        elif not len(kb.data.cachedUsers):
50            users = self.getUsers()
51        else:
52            users = kb.data.cachedUsers
53
54        for user in users:
55            user = unArrayizeValue(user)
56
57            if user is None:
58                continue
59
60            isDba = self.isDba(user)
61
62            if isDba is True:
63                areAdmins.add(user)
64
65            kb.data.cachedUsersPrivileges[user] = None
66
67        return (kb.data.cachedUsersPrivileges, areAdmins)
68
69    def getTables(self):
70        if len(kb.data.cachedTables) > 0:
71            return kb.data.cachedTables
72
73        self.forceDbmsEnum()
74
75        if conf.db == CURRENT_DB:
76            conf.db = self.getCurrentDb()
77
78        if conf.db:
79            dbs = conf.db.split(',')
80        else:
81            dbs = self.getDbs()
82
83        for db in dbs:
84            dbs[dbs.index(db)] = safeSQLIdentificatorNaming(db)
85
86        dbs = [_ for _ in dbs if _]
87
88        infoMsg = "fetching tables for database"
89        infoMsg += "%s: %s" % ("s" if len(dbs) > 1 else "", ", ".join(db if isinstance(db, six.string_types) else db[0] for db in sorted(dbs)))
90        logger.info(infoMsg)
91
92        rootQuery = queries[DBMS.MSSQL].tables
93
94        if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
95            for db in dbs:
96                if conf.excludeSysDbs and db in self.excludeDbsList:
97                    infoMsg = "skipping system database '%s'" % db
98                    singleTimeLogMessage(infoMsg)
99                    continue
100
101                if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
102                    infoMsg = "skipping database '%s'" % db
103                    singleTimeLogMessage(infoMsg)
104                    continue
105
106                for query in (rootQuery.inband.query, rootQuery.inband.query2, rootQuery.inband.query3):
107                    query = query.replace("%s", db)
108                    value = inject.getValue(query, blind=False, time=False)
109                    if not isNoneValue(value):
110                        break
111
112                if not isNoneValue(value):
113                    value = [_ for _ in arrayizeValue(value) if _]
114                    value = [safeSQLIdentificatorNaming(unArrayizeValue(_), True) for _ in value]
115                    kb.data.cachedTables[db] = value
116
117        if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct:
118            for db in dbs:
119                if conf.excludeSysDbs and db in self.excludeDbsList:
120                    infoMsg = "skipping system database '%s'" % db
121                    singleTimeLogMessage(infoMsg)
122                    continue
123
124                if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
125                    infoMsg = "skipping database '%s'" % db
126                    singleTimeLogMessage(infoMsg)
127                    continue
128
129                infoMsg = "fetching number of tables for "
130                infoMsg += "database '%s'" % db
131                logger.info(infoMsg)
132
133                for query in (rootQuery.blind.count, rootQuery.blind.count2, rootQuery.blind.count3):
134                    _ = query.replace("%s", db)
135                    count = inject.getValue(_, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
136                    if not isNoneValue(count):
137                        break
138
139                if not isNumPosStrValue(count):
140                    if count != 0:
141                        warnMsg = "unable to retrieve the number of "
142                        warnMsg += "tables for database '%s'" % db
143                        logger.warn(warnMsg)
144                    continue
145
146                tables = []
147
148                for index in xrange(int(count)):
149                    _ = safeStringFormat((rootQuery.blind.query if query == rootQuery.blind.count else rootQuery.blind.query2 if query == rootQuery.blind.count2 else rootQuery.blind.query3).replace("%s", db), index)
150
151                    table = inject.getValue(_, union=False, error=False)
152                    if not isNoneValue(table):
153                        kb.hintValue = table
154                        table = safeSQLIdentificatorNaming(table, True)
155                        tables.append(table)
156
157                if tables:
158                    kb.data.cachedTables[db] = tables
159                else:
160                    warnMsg = "unable to retrieve the tables "
161                    warnMsg += "for database '%s'" % db
162                    logger.warn(warnMsg)
163
164        if not kb.data.cachedTables and not conf.search:
165            errMsg = "unable to retrieve the tables for any database"
166            raise SqlmapNoneDataException(errMsg)
167        else:
168            for db, tables in kb.data.cachedTables.items():
169                kb.data.cachedTables[db] = sorted(tables) if tables else tables
170
171        return kb.data.cachedTables
172
173    def searchTable(self):
174        foundTbls = {}
175        tblList = conf.tbl.split(',')
176        rootQuery = queries[DBMS.MSSQL].search_table
177        tblCond = rootQuery.inband.condition
178        tblConsider, tblCondParam = self.likeOrExact("table")
179
180        if conf.db == CURRENT_DB:
181            conf.db = self.getCurrentDb()
182
183        if conf.db:
184            enumDbs = conf.db.split(',')
185        elif not len(kb.data.cachedDbs):
186            enumDbs = self.getDbs()
187        else:
188            enumDbs = kb.data.cachedDbs
189
190        for db in enumDbs:
191            db = safeSQLIdentificatorNaming(db)
192            foundTbls[db] = []
193
194        for tbl in tblList:
195            tbl = safeSQLIdentificatorNaming(tbl, True)
196
197            infoMsg = "searching table"
198            if tblConsider == "1":
199                infoMsg += "s LIKE"
200            infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl)
201            logger.info(infoMsg)
202
203            tblQuery = "%s%s" % (tblCond, tblCondParam)
204            tblQuery = tblQuery % unsafeSQLIdentificatorNaming(tbl)
205
206            for db in foundTbls.keys():
207                db = safeSQLIdentificatorNaming(db)
208
209                if conf.excludeSysDbs and db in self.excludeDbsList:
210                    infoMsg = "skipping system database '%s'" % db
211                    singleTimeLogMessage(infoMsg)
212                    continue
213
214                if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
215                    infoMsg = "skipping database '%s'" % db
216                    singleTimeLogMessage(infoMsg)
217                    continue
218
219                if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
220                    query = rootQuery.inband.query.replace("%s", db)
221                    query += tblQuery
222                    values = inject.getValue(query, blind=False, time=False)
223
224                    if not isNoneValue(values):
225                        if isinstance(values, six.string_types):
226                            values = [values]
227
228                        for foundTbl in values:
229                            if foundTbl is None:
230                                continue
231
232                            foundTbls[db].append(foundTbl)
233                else:
234                    infoMsg = "fetching number of table"
235                    if tblConsider == "1":
236                        infoMsg += "s LIKE"
237                    infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(db))
238                    logger.info(infoMsg)
239
240                    query = rootQuery.blind.count
241                    query = query.replace("%s", db)
242                    query += " AND %s" % tblQuery
243                    count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
244
245                    if not isNumPosStrValue(count):
246                        warnMsg = "no table"
247                        if tblConsider == "1":
248                            warnMsg += "s LIKE"
249                        warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(tbl)
250                        warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db)
251                        logger.warn(warnMsg)
252
253                        continue
254
255                    indexRange = getLimitRange(count)
256
257                    for index in indexRange:
258                        query = rootQuery.blind.query
259                        query = query.replace("%s", db)
260                        query += " AND %s" % tblQuery
261                        query = agent.limitQuery(index, query, tblCond)
262                        tbl = inject.getValue(query, union=False, error=False)
263                        kb.hintValue = tbl
264                        foundTbls[db].append(tbl)
265
266        for db, tbls in list(foundTbls.items()):
267            if len(tbls) == 0:
268                foundTbls.pop(db)
269
270        if not foundTbls:
271            warnMsg = "no databases contain any of the provided tables"
272            logger.warn(warnMsg)
273            return
274
275        conf.dumper.dbTables(foundTbls)
276        self.dumpFoundTables(foundTbls)
277
278    def searchColumn(self):
279        rootQuery = queries[DBMS.MSSQL].search_column
280        foundCols = {}
281        dbs = {}
282        whereTblsQuery = ""
283        infoMsgTbl = ""
284        infoMsgDb = ""
285        colList = conf.col.split(',')
286
287        if conf.exclude:
288            colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
289
290        origTbl = conf.tbl
291        origDb = conf.db
292        colCond = rootQuery.inband.condition
293        tblCond = rootQuery.inband.condition2
294        colConsider, colCondParam = self.likeOrExact("column")
295
296        if conf.db == CURRENT_DB:
297            conf.db = self.getCurrentDb()
298
299        if conf.db:
300            enumDbs = conf.db.split(',')
301        elif not len(kb.data.cachedDbs):
302            enumDbs = self.getDbs()
303        else:
304            enumDbs = kb.data.cachedDbs
305
306        for db in enumDbs:
307            db = safeSQLIdentificatorNaming(db)
308            dbs[db] = {}
309
310        for column in colList:
311            column = safeSQLIdentificatorNaming(column)
312            conf.db = origDb
313            conf.tbl = origTbl
314
315            infoMsg = "searching column"
316            if colConsider == "1":
317                infoMsg += "s LIKE"
318            infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column)
319
320            foundCols[column] = {}
321
322            if conf.tbl:
323                _ = conf.tbl.split(',')
324                whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in _) + ")"
325                infoMsgTbl = " for table%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(tbl for tbl in _))
326
327            if conf.db == CURRENT_DB:
328                conf.db = self.getCurrentDb()
329
330            if conf.db:
331                _ = conf.db.split(',')
332                infoMsgDb = " in database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _))
333            elif conf.excludeSysDbs:
334                infoMsgDb = " not in system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList))
335            else:
336                infoMsgDb = " across all databases"
337
338            logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb))
339
340            colQuery = "%s%s" % (colCond, colCondParam)
341            colQuery = colQuery % unsafeSQLIdentificatorNaming(column)
342
343            for db in (_ for _ in dbs if _):
344                db = safeSQLIdentificatorNaming(db)
345
346                if conf.excludeSysDbs and db in self.excludeDbsList:
347                    continue
348
349                if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
350                    continue
351
352                if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
353                    query = rootQuery.inband.query % (db, db, db, db, db, db)
354                    query += " AND %s" % colQuery.replace("[DB]", db)
355                    query += whereTblsQuery.replace("[DB]", db)
356                    values = inject.getValue(query, blind=False, time=False)
357
358                    if not isNoneValue(values):
359                        if isinstance(values, six.string_types):
360                            values = [values]
361
362                        for foundTbl in values:
363                            foundTbl = safeSQLIdentificatorNaming(unArrayizeValue(foundTbl), True)
364
365                            if foundTbl is None:
366                                continue
367
368                            if foundTbl not in dbs[db]:
369                                dbs[db][foundTbl] = {}
370
371                            if colConsider == '1':
372                                conf.db = db
373                                conf.tbl = foundTbl
374                                conf.col = column
375
376                                self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False)
377
378                                if db in kb.data.cachedColumns and foundTbl in kb.data.cachedColumns[db] and not isNoneValue(kb.data.cachedColumns[db][foundTbl]):
379                                    dbs[db][foundTbl].update(kb.data.cachedColumns[db][foundTbl])
380
381                                kb.data.cachedColumns = {}
382                            else:
383                                dbs[db][foundTbl][column] = None
384
385                            if db in foundCols[column]:
386                                foundCols[column][db].append(foundTbl)
387                            else:
388                                foundCols[column][db] = [foundTbl]
389                else:
390                    foundCols[column][db] = []
391
392                    infoMsg = "fetching number of tables containing column"
393                    if colConsider == "1":
394                        infoMsg += "s LIKE"
395                    infoMsg += " '%s' in database '%s'" % (column, db)
396                    logger.info("%s%s" % (infoMsg, infoMsgTbl))
397
398                    query = rootQuery.blind.count
399                    query = query % (db, db, db, db, db, db)
400                    query += " AND %s" % colQuery.replace("[DB]", db)
401                    query += whereTblsQuery.replace("[DB]", db)
402                    count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
403
404                    if not isNumPosStrValue(count):
405                        warnMsg = "no tables contain column"
406                        if colConsider == "1":
407                            warnMsg += "s LIKE"
408                        warnMsg += " '%s' " % column
409                        warnMsg += "in database '%s'" % db
410                        logger.warn(warnMsg)
411
412                        continue
413
414                    indexRange = getLimitRange(count)
415
416                    for index in indexRange:
417                        query = rootQuery.blind.query
418                        query = query % (db, db, db, db, db, db)
419                        query += " AND %s" % colQuery.replace("[DB]", db)
420                        query += whereTblsQuery.replace("[DB]", db)
421                        query = agent.limitQuery(index, query, colCond.replace("[DB]", db))
422                        tbl = inject.getValue(query, union=False, error=False)
423                        kb.hintValue = tbl
424
425                        tbl = safeSQLIdentificatorNaming(tbl, True)
426
427                        if tbl not in dbs[db]:
428                            dbs[db][tbl] = {}
429
430                        if colConsider == "1":
431                            conf.db = db
432                            conf.tbl = tbl
433                            conf.col = column
434
435                            self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False)
436
437                            if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]:
438                                dbs[db][tbl].update(kb.data.cachedColumns[db][tbl])
439                            kb.data.cachedColumns = {}
440                        else:
441                            dbs[db][tbl][column] = None
442
443                        foundCols[column][db].append(tbl)
444
445        conf.dumper.dbColumns(foundCols, colConsider, dbs)
446        self.dumpFoundColumn(dbs, foundCols, colConsider)
447