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 Backend
13from lib.core.common import extractRegexResult
14from lib.core.common import filterNone
15from lib.core.common import filterPairValues
16from lib.core.common import flattenValue
17from lib.core.common import getLimitRange
18from lib.core.common import isInferenceAvailable
19from lib.core.common import isListLike
20from lib.core.common import isNoneValue
21from lib.core.common import isNumPosStrValue
22from lib.core.common import isTechniqueAvailable
23from lib.core.common import parseSqliteTableSchema
24from lib.core.common import popValue
25from lib.core.common import pushValue
26from lib.core.common import randomStr
27from lib.core.common import readInput
28from lib.core.common import safeSQLIdentificatorNaming
29from lib.core.common import singleTimeLogMessage
30from lib.core.common import singleTimeWarnMessage
31from lib.core.common import unArrayizeValue
32from lib.core.common import unsafeSQLIdentificatorNaming
33from lib.core.data import conf
34from lib.core.data import kb
35from lib.core.data import logger
36from lib.core.data import paths
37from lib.core.data import queries
38from lib.core.decorators import stackedmethod
39from lib.core.dicts import FIREBIRD_TYPES
40from lib.core.dicts import INFORMIX_TYPES
41from lib.core.enums import CHARSET_TYPE
42from lib.core.enums import DBMS
43from lib.core.enums import EXPECTED
44from lib.core.enums import PAYLOAD
45from lib.core.exception import SqlmapMissingMandatoryOptionException
46from lib.core.exception import SqlmapNoneDataException
47from lib.core.exception import SqlmapUserQuitException
48from lib.core.settings import CURRENT_DB
49from lib.core.settings import REFLECTED_VALUE_MARKER
50from lib.request import inject
51from lib.techniques.union.use import unionUse
52from lib.utils.brute import columnExists
53from lib.utils.brute import tableExists
54from thirdparty import six
55
56class Databases(object):
57    """
58    This class defines databases' enumeration functionalities for plugins.
59    """
60
61    def __init__(self):
62        kb.data.currentDb = ""
63        kb.data.cachedDbs = []
64        kb.data.cachedTables = {}
65        kb.data.cachedColumns = {}
66        kb.data.cachedCounts = {}
67        kb.data.dumpedTable = {}
68        kb.data.cachedStatements = []
69
70    def getCurrentDb(self):
71        infoMsg = "fetching current database"
72        logger.info(infoMsg)
73
74        query = queries[Backend.getIdentifiedDbms()].current_db.query
75
76        if not kb.data.currentDb:
77            kb.data.currentDb = unArrayizeValue(inject.getValue(query, safeCharEncode=False))
78
79        if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL):
80            warnMsg = "on %s you'll need to use " % Backend.getIdentifiedDbms()
81            warnMsg += "schema names for enumeration as the counterpart to database "
82            warnMsg += "names on other DBMSes"
83            singleTimeWarnMessage(warnMsg)
84
85        return kb.data.currentDb
86
87    def getDbs(self):
88        if len(kb.data.cachedDbs) > 0:
89            return kb.data.cachedDbs
90
91        infoMsg = None
92
93        if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
94            warnMsg = "information_schema not available, "
95            warnMsg += "back-end DBMS is MySQL < 5. database "
96            warnMsg += "names will be fetched from 'mysql' database"
97            logger.warn(warnMsg)
98
99        elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL):
100            warnMsg = "schema names are going to be used on %s " % Backend.getIdentifiedDbms()
101            warnMsg += "for enumeration as the counterpart to database "
102            warnMsg += "names on other DBMSes"
103            logger.warn(warnMsg)
104
105            infoMsg = "fetching database (schema) names"
106
107        else:
108            infoMsg = "fetching database names"
109
110        if infoMsg:
111            logger.info(infoMsg)
112
113        rootQuery = queries[Backend.getIdentifiedDbms()].dbs
114
115        if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
116            if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
117                query = rootQuery.inband.query2
118            else:
119                query = rootQuery.inband.query
120            values = inject.getValue(query, blind=False, time=False)
121
122            if not isNoneValue(values):
123                kb.data.cachedDbs = arrayizeValue(values)
124
125        if not kb.data.cachedDbs and isInferenceAvailable() and not conf.direct:
126            infoMsg = "fetching number of databases"
127            logger.info(infoMsg)
128
129            if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
130                query = rootQuery.blind.count2
131            else:
132                query = rootQuery.blind.count
133            count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
134
135            if not isNumPosStrValue(count):
136                errMsg = "unable to retrieve the number of databases"
137                logger.error(errMsg)
138            else:
139                plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2)
140                indexRange = getLimitRange(count, plusOne=plusOne)
141
142                for index in indexRange:
143                    if Backend.isDbms(DBMS.SYBASE):
144                        query = rootQuery.blind.query % (kb.data.cachedDbs[-1] if kb.data.cachedDbs else " ")
145                    elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
146                        query = rootQuery.blind.query2 % index
147                    else:
148                        query = rootQuery.blind.query % index
149
150                    db = unArrayizeValue(inject.getValue(query, union=False, error=False))
151
152                    if not isNoneValue(db):
153                        kb.data.cachedDbs.append(safeSQLIdentificatorNaming(db))
154
155        if not kb.data.cachedDbs and Backend.isDbms(DBMS.MSSQL):
156            if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
157                blinds = (False, True)
158            else:
159                blinds = (True,)
160
161            for blind in blinds:
162                count = 0
163                kb.data.cachedDbs = []
164                while True:
165                    query = rootQuery.inband.query2 % count
166                    value = unArrayizeValue(inject.getValue(query, blind=blind))
167                    if not (value or "").strip():
168                        break
169                    else:
170                        kb.data.cachedDbs.append(value)
171                        count += 1
172                if kb.data.cachedDbs:
173                    break
174
175        if not kb.data.cachedDbs:
176            infoMsg = "falling back to current database"
177            logger.info(infoMsg)
178            self.getCurrentDb()
179
180            if kb.data.currentDb:
181                kb.data.cachedDbs = [kb.data.currentDb]
182            else:
183                errMsg = "unable to retrieve the database names"
184                raise SqlmapNoneDataException(errMsg)
185        else:
186            kb.data.cachedDbs.sort()
187
188        if kb.data.cachedDbs:
189            kb.data.cachedDbs = [_ for _ in set(flattenValue(kb.data.cachedDbs)) if _]
190
191        return kb.data.cachedDbs
192
193    def getTables(self, bruteForce=None):
194        if len(kb.data.cachedTables) > 0:
195            return kb.data.cachedTables
196
197        self.forceDbmsEnum()
198
199        if bruteForce is None:
200            if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
201                errMsg = "information_schema not available, "
202                errMsg += "back-end DBMS is MySQL < 5.0"
203                logger.error(errMsg)
204                bruteForce = True
205
206            elif Backend.isDbms(DBMS.ACCESS):
207                try:
208                    tables = self.getTables(False)
209                except SqlmapNoneDataException:
210                    tables = None
211
212                if not tables:
213                    errMsg = "cannot retrieve table names, "
214                    errMsg += "back-end DBMS is Access"
215                    logger.error(errMsg)
216                    bruteForce = True
217                else:
218                    return tables
219
220        if conf.db == CURRENT_DB:
221            conf.db = self.getCurrentDb()
222
223        if conf.db and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB):
224            conf.db = conf.db.upper()
225
226        if conf.db:
227            dbs = conf.db.split(',')
228        else:
229            dbs = self.getDbs()
230
231        dbs = [_ for _ in dbs if _ and _.strip()]
232
233        for db in dbs:
234            dbs[dbs.index(db)] = safeSQLIdentificatorNaming(db)
235
236        if bruteForce:
237            resumeAvailable = False
238
239            for db, table in kb.brute.tables:
240                if db == conf.db:
241                    resumeAvailable = True
242                    break
243
244            if resumeAvailable and not conf.freshQueries:
245                for db, table in kb.brute.tables:
246                    if db == conf.db:
247                        if conf.db not in kb.data.cachedTables:
248                            kb.data.cachedTables[conf.db] = [table]
249                        else:
250                            kb.data.cachedTables[conf.db].append(table)
251
252                return kb.data.cachedTables
253
254            message = "do you want to use common table existence check? %s " % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]")
255            choice = readInput(message, default='Y' if 'Y' in message else 'N').upper()
256
257            if choice == 'N':
258                return
259            elif choice == 'Q':
260                raise SqlmapUserQuitException
261            else:
262                return tableExists(paths.COMMON_TABLES)
263
264        infoMsg = "fetching tables for database"
265        infoMsg += "%s: '%s'" % ("s" if len(dbs) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(unArrayizeValue(db)) for db in sorted(dbs)))
266        logger.info(infoMsg)
267
268        rootQuery = queries[Backend.getIdentifiedDbms()].tables
269
270        if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
271            values = []
272
273            for query, condition in ((rootQuery.inband.query, getattr(rootQuery.inband, "condition", None)), (getattr(rootQuery.inband, "query2", None), getattr(rootQuery.inband, "condition2", None))):
274                if not isNoneValue(values) or not query:
275                    break
276
277                if condition:
278                    if not Backend.isDbms(DBMS.SQLITE):
279                        query += " WHERE %s" % condition
280
281                        if conf.excludeSysDbs:
282                            infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList))
283                            logger.info(infoMsg)
284                            query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if db not in self.excludeDbsList)
285                        else:
286                            query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs))
287
288                    if len(dbs) < 2 and ("%s," % condition) in query:
289                        query = query.replace("%s," % condition, "", 1)
290
291                if query:
292                    values = inject.getValue(query, blind=False, time=False)
293
294            if not isNoneValue(values):
295                values = [_ for _ in arrayizeValue(values) if _]
296
297                if len(values) > 0 and not isListLike(values[0]):
298                    values = [(dbs[0], _) for _ in values]
299
300                for db, table in filterPairValues(values):
301                    table = unArrayizeValue(table)
302
303                    if not isNoneValue(table):
304                        db = safeSQLIdentificatorNaming(db)
305                        table = safeSQLIdentificatorNaming(table, True)
306
307                        if conf.getComments:
308                            _ = queries[Backend.getIdentifiedDbms()].table_comment
309                            if hasattr(_, "query"):
310                                if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
311                                    query = _.query % (unsafeSQLIdentificatorNaming(db.upper()), unsafeSQLIdentificatorNaming(table.upper()))
312                                else:
313                                    query = _.query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(table))
314
315                                comment = unArrayizeValue(inject.getValue(query, blind=False, time=False))
316                                if not isNoneValue(comment):
317                                    infoMsg = "retrieved comment '%s' for table '%s' " % (comment, unsafeSQLIdentificatorNaming(table))
318                                    infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db)
319                                    logger.info(infoMsg)
320                            else:
321                                warnMsg = "on %s it is not " % Backend.getIdentifiedDbms()
322                                warnMsg += "possible to get table comments"
323                                singleTimeWarnMessage(warnMsg)
324
325                        if db not in kb.data.cachedTables:
326                            kb.data.cachedTables[db] = [table]
327                        else:
328                            kb.data.cachedTables[db].append(table)
329
330        if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct:
331            for db in dbs:
332                if conf.excludeSysDbs and db in self.excludeDbsList:
333                    infoMsg = "skipping system database '%s'" % unsafeSQLIdentificatorNaming(db)
334                    logger.info(infoMsg)
335                    continue
336
337                if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
338                    infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(db)
339                    singleTimeLogMessage(infoMsg)
340                    continue
341
342                infoMsg = "fetching number of tables for "
343                infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(db)
344                logger.info(infoMsg)
345
346                if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.ACCESS):
347                    query = rootQuery.blind.count
348                else:
349                    query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(db)
350
351                count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
352
353                if count == 0:
354                    warnMsg = "database '%s' " % unsafeSQLIdentificatorNaming(db)
355                    warnMsg += "appears to be empty"
356                    logger.warn(warnMsg)
357                    continue
358
359                elif not isNumPosStrValue(count):
360                    warnMsg = "unable to retrieve the number of "
361                    warnMsg += "tables for database '%s'" % unsafeSQLIdentificatorNaming(db)
362                    logger.warn(warnMsg)
363                    continue
364
365                tables = []
366
367                plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2)
368                indexRange = getLimitRange(count, plusOne=plusOne)
369
370                for index in indexRange:
371                    if Backend.isDbms(DBMS.SYBASE):
372                        query = rootQuery.blind.query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " "))
373                    elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS):
374                        query = rootQuery.blind.query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")
375                    elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD):
376                        query = rootQuery.blind.query % index
377                    elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX):
378                        query = rootQuery.blind.query % (index, unsafeSQLIdentificatorNaming(db))
379                    else:
380                        query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(db), index)
381
382                    table = unArrayizeValue(inject.getValue(query, union=False, error=False))
383
384                    if not isNoneValue(table):
385                        kb.hintValue = table
386                        table = safeSQLIdentificatorNaming(table, True)
387                        tables.append(table)
388
389                        if conf.getComments:
390                            _ = queries[Backend.getIdentifiedDbms()].table_comment
391                            if hasattr(_, "query"):
392                                if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
393                                    query = _.query % (unsafeSQLIdentificatorNaming(db.upper()), unsafeSQLIdentificatorNaming(table.upper()))
394                                else:
395                                    query = _.query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(table))
396
397                                comment = unArrayizeValue(inject.getValue(query, union=False, error=False))
398                                if not isNoneValue(comment):
399                                    infoMsg = "retrieved comment '%s' for table '%s' " % (comment, unsafeSQLIdentificatorNaming(table))
400                                    infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db)
401                                    logger.info(infoMsg)
402                            else:
403                                warnMsg = "on %s it is not " % Backend.getIdentifiedDbms()
404                                warnMsg += "possible to get table comments"
405                                singleTimeWarnMessage(warnMsg)
406
407                if tables:
408                    kb.data.cachedTables[db] = tables
409                else:
410                    warnMsg = "unable to retrieve the table names "
411                    warnMsg += "for database '%s'" % unsafeSQLIdentificatorNaming(db)
412                    logger.warn(warnMsg)
413
414        if isNoneValue(kb.data.cachedTables):
415            kb.data.cachedTables.clear()
416
417        if not kb.data.cachedTables:
418            errMsg = "unable to retrieve the table names for any database"
419            if bruteForce is None:
420                logger.error(errMsg)
421                return self.getTables(bruteForce=True)
422            elif not conf.search:
423                raise SqlmapNoneDataException(errMsg)
424        else:
425            for db, tables in kb.data.cachedTables.items():
426                kb.data.cachedTables[db] = sorted(tables) if tables else tables
427
428        if kb.data.cachedTables:
429            for db in kb.data.cachedTables:
430                kb.data.cachedTables[db] = list(set(kb.data.cachedTables[db]))
431
432        return kb.data.cachedTables
433
434    def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False):
435        self.forceDbmsEnum()
436
437        if conf.db is None or conf.db == CURRENT_DB:
438            if conf.db is None:
439                warnMsg = "missing database parameter. sqlmap is going "
440                warnMsg += "to use the current database to enumerate "
441                warnMsg += "table(s) columns"
442                logger.warn(warnMsg)
443
444            conf.db = self.getCurrentDb()
445
446            if not conf.db:
447                errMsg = "unable to retrieve the current "
448                errMsg += "database name"
449                raise SqlmapNoneDataException(errMsg)
450
451        elif conf.db is not None:
452            if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB, DBMS.H2):
453                conf.db = conf.db.upper()
454
455            if ',' in conf.db:
456                errMsg = "only one database name is allowed when enumerating "
457                errMsg += "the tables' columns"
458                raise SqlmapMissingMandatoryOptionException(errMsg)
459
460        conf.db = safeSQLIdentificatorNaming(conf.db)
461
462        if conf.col:
463            if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
464                conf.col = conf.col.upper()
465
466            colList = conf.col.split(',')
467        else:
468            colList = []
469
470        if conf.exclude:
471            colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
472
473        for col in colList:
474            colList[colList.index(col)] = safeSQLIdentificatorNaming(col)
475
476        colList = [_ for _ in colList if _]
477
478        if conf.tbl:
479            if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB, DBMS.H2):
480                conf.tbl = conf.tbl.upper()
481
482            tblList = conf.tbl.split(',')
483        else:
484            self.getTables()
485
486            if len(kb.data.cachedTables) > 0:
487                if conf.db in kb.data.cachedTables:
488                    tblList = kb.data.cachedTables[conf.db]
489                else:
490                    tblList = list(six.itervalues(kb.data.cachedTables))
491
492                if tblList and isListLike(tblList[0]):
493                    tblList = tblList[0]
494
495                tblList = list(tblList)
496            elif not conf.search:
497                errMsg = "unable to retrieve the tables "
498                errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
499                raise SqlmapNoneDataException(errMsg)
500            else:
501                return kb.data.cachedColumns
502
503        tblList = filterNone(safeSQLIdentificatorNaming(_, True) for _ in tblList)
504
505        if bruteForce is None:
506            if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
507                errMsg = "information_schema not available, "
508                errMsg += "back-end DBMS is MySQL < 5.0"
509                logger.error(errMsg)
510                bruteForce = True
511
512            elif Backend.isDbms(DBMS.ACCESS):
513                errMsg = "cannot retrieve column names, "
514                errMsg += "back-end DBMS is %s" % DBMS.ACCESS
515                logger.error(errMsg)
516                bruteForce = True
517
518        if bruteForce:
519            resumeAvailable = False
520
521            for tbl in tblList:
522                for db, table, colName, colType in kb.brute.columns:
523                    if db == conf.db and table == tbl:
524                        resumeAvailable = True
525                        break
526
527            if resumeAvailable and not conf.freshQueries or colList:
528                columns = {}
529
530                for column in colList:
531                    columns[column] = None
532
533                for tbl in tblList:
534                    for db, table, colName, colType in kb.brute.columns:
535                        if db == conf.db and table == tbl:
536                            columns[colName] = colType
537
538                    if conf.db in kb.data.cachedColumns:
539                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns
540                    else:
541                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns}
542
543                return kb.data.cachedColumns
544
545            message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS,) else "[y/N/q]")
546            choice = readInput(message, default='Y' if 'Y' in message else 'N').upper()
547
548            if choice == 'N':
549                return
550            elif choice == 'Q':
551                raise SqlmapUserQuitException
552            else:
553                return columnExists(paths.COMMON_COLUMNS)
554
555        rootQuery = queries[Backend.getIdentifiedDbms()].columns
556        condition = rootQuery.blind.condition if 'condition' in rootQuery.blind else None
557
558        if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
559            for tbl in tblList:
560                if conf.db is not None and len(kb.data.cachedColumns) > 0 \
561                   and conf.db in kb.data.cachedColumns and tbl in \
562                   kb.data.cachedColumns[conf.db]:
563                    infoMsg = "fetched tables' columns on "
564                    infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
565                    logger.info(infoMsg)
566
567                    return {conf.db: kb.data.cachedColumns[conf.db]}
568
569                infoMsg = "fetching columns "
570                condQuery = ""
571
572                if len(colList) > 0:
573                    if colTuple:
574                        _, colCondParam = colTuple
575                        infoMsg += "LIKE '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList))
576                    else:
577                        colCondParam = "='%s'"
578                        infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList))
579
580                    condQueryStr = "%%s%s" % colCondParam
581                    condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList))
582
583                if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2):
584                    query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db))
585                    query += condQuery
586
587                elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
588                    query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper()))
589                    query += condQuery
590
591                elif Backend.isDbms(DBMS.MSSQL):
592                    query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db,
593                                                      conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1])
594                    query += condQuery.replace("[DB]", conf.db)
595
596                elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD):
597                    query = rootQuery.inband.query % unsafeSQLIdentificatorNaming(tbl)
598
599                elif Backend.isDbms(DBMS.INFORMIX):
600                    query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl))
601                    query += condQuery
602
603                if dumpMode and colList:
604                    values = [(_,) for _ in colList]
605                else:
606                    infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
607                    infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
608                    logger.info(infoMsg)
609
610                    values = None
611                    if Backend.isDbms(DBMS.MSSQL) and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
612                        expression = query
613                        kb.dumpColumns = []
614                        kb.rowXmlMode = True
615
616                        for column in (extractRegexResult(r"SELECT (?P<result>.+?) FROM", query) or "").split(','):
617                            kb.dumpColumns.append(randomStr().lower())
618                            expression = expression.replace(column, "%s AS %s" % (column, kb.dumpColumns[-1]), 1)
619
620                        values = unionUse(expression)
621                        kb.rowXmlMode = False
622                        kb.dumpColumns = None
623
624                    if values is None:
625                        values = inject.getValue(query, blind=False, time=False)
626                        if values and isinstance(values[0], six.string_types):
627                            values = [values]
628
629                if Backend.isDbms(DBMS.MSSQL) and isNoneValue(values):
630                    index, values = 1, []
631
632                    while True:
633                        query = rootQuery.inband.query2 % (conf.db, unsafeSQLIdentificatorNaming(tbl), index)
634                        value = unArrayizeValue(inject.getValue(query, blind=False, time=False))
635
636                        if isNoneValue(value) or value == " ":
637                            break
638                        else:
639                            values.append((value,))
640                            index += 1
641
642                if Backend.isDbms(DBMS.SQLITE):
643                    if dumpMode and colList:
644                        if conf.db not in kb.data.cachedColumns:
645                            kb.data.cachedColumns[conf.db] = {}
646                        kb.data.cachedColumns[conf.db][safeSQLIdentificatorNaming(conf.tbl, True)] = dict((_, None) for _ in colList)
647                    else:
648                        parseSqliteTableSchema(unArrayizeValue(values))
649
650                elif not isNoneValue(values):
651                    table = {}
652                    columns = {}
653
654                    for columnData in values:
655                        if not isNoneValue(columnData):
656                            columnData = [unArrayizeValue(_) for _ in columnData]
657                            name = safeSQLIdentificatorNaming(columnData[0])
658
659                            if name:
660                                if conf.getComments:
661                                    _ = queries[Backend.getIdentifiedDbms()].column_comment
662                                    if hasattr(_, "query"):
663                                        if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
664                                            query = _.query % (unsafeSQLIdentificatorNaming(conf.db.upper()), unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(name.upper()))
665                                        else:
666                                            query = _.query % (unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(name))
667
668                                        comment = unArrayizeValue(inject.getValue(query, blind=False, time=False))
669                                        if not isNoneValue(comment):
670                                            infoMsg = "retrieved comment '%s' for column '%s'" % (comment, name)
671                                            logger.info(infoMsg)
672                                    else:
673                                        warnMsg = "on %s it is not " % Backend.getIdentifiedDbms()
674                                        warnMsg += "possible to get column comments"
675                                        singleTimeWarnMessage(warnMsg)
676
677                                if len(columnData) == 1:
678                                    columns[name] = None
679                                else:
680                                    key = int(columnData[1]) if isinstance(columnData[1], six.string_types) and columnData[1].isdigit() else columnData[1]
681                                    if Backend.isDbms(DBMS.FIREBIRD):
682                                        columnData[1] = FIREBIRD_TYPES.get(key, columnData[1])
683                                    elif Backend.isDbms(DBMS.INFORMIX):
684                                        notNull = False
685                                        if isinstance(key, int) and key > 255:
686                                            key -= 256
687                                            notNull = True
688                                        columnData[1] = INFORMIX_TYPES.get(key, columnData[1])
689                                        if notNull:
690                                            columnData[1] = "%s NOT NULL" % columnData[1]
691
692                                    columns[name] = columnData[1]
693
694                    if conf.db in kb.data.cachedColumns:
695                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns
696                    else:
697                        table[safeSQLIdentificatorNaming(tbl, True)] = columns
698                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table
699
700        elif isInferenceAvailable() and not conf.direct:
701            for tbl in tblList:
702                if conf.db is not None and len(kb.data.cachedColumns) > 0 \
703                   and conf.db in kb.data.cachedColumns and tbl in \
704                   kb.data.cachedColumns[conf.db]:
705                    infoMsg = "fetched tables' columns on "
706                    infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
707                    logger.info(infoMsg)
708
709                    return {conf.db: kb.data.cachedColumns[conf.db]}
710
711                infoMsg = "fetching columns "
712                condQuery = ""
713
714                if len(colList) > 0:
715                    if colTuple:
716                        _, colCondParam = colTuple
717                        infoMsg += "LIKE '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList))
718                    else:
719                        colCondParam = "='%s'"
720                        infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList))
721
722                    condQueryStr = "%%s%s" % colCondParam
723                    condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList))
724
725                if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2):
726                    query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db))
727                    query += condQuery
728
729                elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
730                    query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper()))
731                    query += condQuery
732
733                elif Backend.isDbms(DBMS.MSSQL):
734                    query = rootQuery.blind.count % (conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1])
735                    query += condQuery.replace("[DB]", conf.db)
736
737                elif Backend.isDbms(DBMS.FIREBIRD):
738                    query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(tbl)
739                    query += condQuery
740
741                elif Backend.isDbms(DBMS.INFORMIX):
742                    query = rootQuery.blind.count % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl))
743                    query += condQuery
744
745                elif Backend.isDbms(DBMS.SQLITE):
746                    if dumpMode and colList:
747                        if conf.db not in kb.data.cachedColumns:
748                            kb.data.cachedColumns[conf.db] = {}
749                        kb.data.cachedColumns[conf.db][safeSQLIdentificatorNaming(conf.tbl, True)] = dict((_, None) for _ in colList)
750                    else:
751                        query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl)
752                        value = unArrayizeValue(inject.getValue(query, union=False, error=False))
753                        parseSqliteTableSchema(unArrayizeValue(value))
754
755                    return kb.data.cachedColumns
756
757                table = {}
758                columns = {}
759
760                if dumpMode and colList:
761                    count = 0
762                    for value in colList:
763                        columns[safeSQLIdentificatorNaming(value)] = None
764                else:
765                    infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
766                    infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
767                    logger.info(infoMsg)
768
769                    count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
770
771                    if not isNumPosStrValue(count):
772                        if Backend.isDbms(DBMS.MSSQL):
773                            count, index, values = 0, 1, []
774                            while True:
775                                query = rootQuery.blind.query3 % (conf.db, unsafeSQLIdentificatorNaming(tbl), index)
776                                value = unArrayizeValue(inject.getValue(query, union=False, error=False))
777
778                                if isNoneValue(value) or value == " ":
779                                    break
780                                else:
781                                    columns[safeSQLIdentificatorNaming(value)] = None
782                                    index += 1
783
784                        if not columns:
785                            errMsg = "unable to retrieve the %scolumns " % ("number of " if not Backend.isDbms(DBMS.MSSQL) else "")
786                            errMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
787                            errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
788                            logger.error(errMsg)
789                            continue
790
791                for index in getLimitRange(count):
792                    if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB):
793                        query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db))
794                        query += condQuery
795                        field = None
796                    elif Backend.isDbms(DBMS.H2):
797                        query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db))
798                        query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery)
799                        field = None
800                    elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
801                        query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper()))
802                        query += condQuery
803                        field = None
804                    elif Backend.isDbms(DBMS.MSSQL):
805                        query = rootQuery.blind.query.replace("'%s'", "'%s'" % unsafeSQLIdentificatorNaming(tbl).split(".")[-1]).replace("%s", conf.db).replace("%d", str(index))
806                        query += condQuery.replace("[DB]", conf.db)
807                        field = condition.replace("[DB]", conf.db)
808                    elif Backend.isDbms(DBMS.FIREBIRD):
809                        query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl)
810                        query += condQuery
811                        field = None
812                    elif Backend.isDbms(DBMS.INFORMIX):
813                        query = rootQuery.blind.query % (index, conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl))
814                        query += condQuery
815                        field = condition
816
817                    query = agent.limitQuery(index, query, field, field)
818                    column = unArrayizeValue(inject.getValue(query, union=False, error=False))
819
820                    if not isNoneValue(column):
821                        if conf.getComments:
822                            _ = queries[Backend.getIdentifiedDbms()].column_comment
823                            if hasattr(_, "query"):
824                                if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
825                                    query = _.query % (unsafeSQLIdentificatorNaming(conf.db.upper()), unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(column.upper()))
826                                else:
827                                    query = _.query % (unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(column))
828
829                                comment = unArrayizeValue(inject.getValue(query, union=False, error=False))
830                                if not isNoneValue(comment):
831                                    infoMsg = "retrieved comment '%s' for column '%s'" % (comment, column)
832                                    logger.info(infoMsg)
833                            else:
834                                warnMsg = "on %s it is not " % Backend.getIdentifiedDbms()
835                                warnMsg += "possible to get column comments"
836                                singleTimeWarnMessage(warnMsg)
837
838                        if not onlyColNames:
839                            if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2):
840                                query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db))
841                            elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
842                                query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column, unsafeSQLIdentificatorNaming(conf.db.upper()))
843                            elif Backend.isDbms(DBMS.MSSQL):
844                                query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, column, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1])
845                            elif Backend.isDbms(DBMS.FIREBIRD):
846                                query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column)
847                            elif Backend.isDbms(DBMS.INFORMIX):
848                                query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl), column)
849
850                            colType = unArrayizeValue(inject.getValue(query, union=False, error=False))
851                            key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType
852
853                            if Backend.isDbms(DBMS.FIREBIRD):
854                                colType = FIREBIRD_TYPES.get(key, colType)
855                            elif Backend.isDbms(DBMS.INFORMIX):
856                                notNull = False
857                                if isinstance(key, int) and key > 255:
858                                    key -= 256
859                                    notNull = True
860                                colType = INFORMIX_TYPES.get(key, colType)
861                                if notNull:
862                                    colType = "%s NOT NULL" % colType
863
864                            column = safeSQLIdentificatorNaming(column)
865                            columns[column] = colType
866                        else:
867                            column = safeSQLIdentificatorNaming(column)
868                            columns[column] = None
869
870                if columns:
871                    if conf.db in kb.data.cachedColumns:
872                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns
873                    else:
874                        table[safeSQLIdentificatorNaming(tbl, True)] = columns
875                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table
876
877        if not kb.data.cachedColumns:
878            warnMsg = "unable to retrieve column names for "
879            warnMsg += ("table '%s' " % unsafeSQLIdentificatorNaming(unArrayizeValue(tblList))) if len(tblList) == 1 else "any table "
880            warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
881            logger.warn(warnMsg)
882
883            if bruteForce is None:
884                return self.getColumns(onlyColNames=onlyColNames, colTuple=colTuple, bruteForce=True)
885
886        return kb.data.cachedColumns
887
888    @stackedmethod
889    def getSchema(self):
890        infoMsg = "enumerating database management system schema"
891        logger.info(infoMsg)
892
893        try:
894            pushValue(conf.db)
895            pushValue(conf.tbl)
896            pushValue(conf.col)
897
898            kb.data.cachedTables = {}
899            kb.data.cachedColumns = {}
900
901            self.getTables()
902
903            infoMsg = "fetched tables: "
904            infoMsg += ", ".join(["%s" % ", ".join("%s%s%s" % (unsafeSQLIdentificatorNaming(db), ".." if Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) else '.', unsafeSQLIdentificatorNaming(_)) for _ in tbl) for db, tbl in kb.data.cachedTables.items()])
905            logger.info(infoMsg)
906
907            for db, tables in kb.data.cachedTables.items():
908                for tbl in tables:
909                    conf.db = db
910                    conf.tbl = tbl
911
912                    self.getColumns()
913        finally:
914            conf.col = popValue()
915            conf.tbl = popValue()
916            conf.db = popValue()
917
918        return kb.data.cachedColumns
919
920    def _tableGetCount(self, db, table):
921        if not db or not table:
922            return None
923
924        if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
925            db = db.upper()
926            table = table.upper()
927
928        if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
929            query = "SELECT %s FROM %s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(table, True))
930        else:
931            query = "SELECT %s FROM %s.%s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True))
932
933        query = agent.whereQuery(query)
934        count = inject.getValue(query, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
935
936        if isNumPosStrValue(count):
937            if safeSQLIdentificatorNaming(db) not in kb.data.cachedCounts:
938                kb.data.cachedCounts[safeSQLIdentificatorNaming(db)] = {}
939
940            if int(count) in kb.data.cachedCounts[safeSQLIdentificatorNaming(db)]:
941                kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)].append(safeSQLIdentificatorNaming(table, True))
942            else:
943                kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)] = [safeSQLIdentificatorNaming(table, True)]
944
945    def getCount(self):
946        if not conf.tbl:
947            warnMsg = "missing table parameter, sqlmap will retrieve "
948            warnMsg += "the number of entries for all database "
949            warnMsg += "management system databases' tables"
950            logger.warn(warnMsg)
951
952        elif "." in conf.tbl:
953            if not conf.db:
954                conf.db, conf.tbl = conf.tbl.split('.', 1)
955
956        if conf.tbl is not None and conf.db is None and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
957            warnMsg = "missing database parameter. sqlmap is going to "
958            warnMsg += "use the current database to retrieve the "
959            warnMsg += "number of entries for table '%s'" % unsafeSQLIdentificatorNaming(conf.tbl)
960            logger.warn(warnMsg)
961
962            conf.db = self.getCurrentDb()
963
964        self.forceDbmsEnum()
965
966        if conf.tbl:
967            for table in conf.tbl.split(','):
968                self._tableGetCount(conf.db, table)
969        else:
970            self.getTables()
971
972            for db, tables in kb.data.cachedTables.items():
973                for table in tables:
974                    self._tableGetCount(db, table)
975
976        return kb.data.cachedCounts
977
978    def getStatements(self):
979        infoMsg = "fetching SQL statements"
980        logger.info(infoMsg)
981
982        rootQuery = queries[Backend.getIdentifiedDbms()].statements
983
984        if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
985            query = rootQuery.inband.query
986
987            while True:
988                values = inject.getValue(query, blind=False, time=False)
989
990                if not isNoneValue(values):
991                    kb.data.cachedStatements = []
992                    for value in arrayizeValue(values):
993                        value = (unArrayizeValue(value) or "").strip()
994                        if not isNoneValue(value):
995                            kb.data.cachedStatements.append(value.strip())
996
997                elif Backend.isDbms(DBMS.PGSQL) and "current_query" not in query:
998                    query = query.replace("query", "current_query")
999                    continue
1000
1001                break
1002
1003        if not kb.data.cachedStatements and isInferenceAvailable() and not conf.direct:
1004            infoMsg = "fetching number of statements"
1005            logger.info(infoMsg)
1006
1007            query = rootQuery.blind.count
1008            count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
1009
1010            if count == 0:
1011                return kb.data.cachedStatements
1012            elif not isNumPosStrValue(count):
1013                errMsg = "unable to retrieve the number of statements"
1014                raise SqlmapNoneDataException(errMsg)
1015
1016            plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2)
1017            indexRange = getLimitRange(count, plusOne=plusOne)
1018
1019            for index in indexRange:
1020                value = None
1021
1022                if Backend.getIdentifiedDbms() in (DBMS.MYSQL,):  # case with multiple processes
1023                    query = rootQuery.blind.query3 % index
1024                    identifier = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT))
1025
1026                    if not isNoneValue(identifier):
1027                        query = rootQuery.blind.query2 % identifier
1028                        value = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT))
1029
1030                if isNoneValue(value):
1031                    query = rootQuery.blind.query % index
1032                    value = unArrayizeValue(inject.getValue(query, union=False, error=False))
1033
1034                if not isNoneValue(value):
1035                    kb.data.cachedStatements.append(value)
1036
1037        if not kb.data.cachedStatements:
1038            errMsg = "unable to retrieve the statements"
1039            logger.error(errMsg)
1040        else:
1041            kb.data.cachedStatements = [_.replace(REFLECTED_VALUE_MARKER, "<payload>") for _ in kb.data.cachedStatements]
1042
1043        return kb.data.cachedStatements
1044