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.common import isListLike
11from lib.core.common import readInput
12from lib.core.common import safeSQLIdentificatorNaming
13from lib.core.common import unsafeSQLIdentificatorNaming
14from lib.core.data import conf
15from lib.core.data import kb
16from lib.core.data import logger
17from lib.core.data import paths
18from lib.core.data import queries
19from lib.core.enums import DBMS
20from lib.core.exception import SqlmapMissingMandatoryOptionException
21from lib.core.exception import SqlmapNoneDataException
22from lib.core.exception import SqlmapUserQuitException
23from lib.core.settings import CURRENT_DB
24from lib.utils.brute import columnExists
25from lib.utils.pivotdumptable import pivotDumpTable
26from plugins.generic.enumeration import Enumeration as GenericEnumeration
27from thirdparty import six
28from thirdparty.six.moves import zip as _zip
29
30class Enumeration(GenericEnumeration):
31    def __init__(self):
32        GenericEnumeration.__init__(self)
33
34        kb.data.processChar = lambda x: x.replace('_', ' ') if x else x
35
36    def getPasswordHashes(self):
37        warnMsg = "on SAP MaxDB it is not possible to enumerate the user password hashes"
38        logger.warn(warnMsg)
39
40        return {}
41
42    def getDbs(self):
43        if len(kb.data.cachedDbs) > 0:
44            return kb.data.cachedDbs
45
46        infoMsg = "fetching database names"
47        logger.info(infoMsg)
48
49        rootQuery = queries[DBMS.MAXDB].dbs
50        query = rootQuery.inband.query
51        retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.schemaname' % kb.aliasName], blind=True)
52
53        if retVal:
54            kb.data.cachedDbs = next(six.itervalues(retVal[0]))
55
56        if kb.data.cachedDbs:
57            kb.data.cachedDbs.sort()
58
59        return kb.data.cachedDbs
60
61    def getTables(self, bruteForce=None):
62        if len(kb.data.cachedTables) > 0:
63            return kb.data.cachedTables
64
65        self.forceDbmsEnum()
66
67        if conf.db == CURRENT_DB:
68            conf.db = self.getCurrentDb()
69
70        if conf.db:
71            dbs = conf.db.split(',')
72        else:
73            dbs = self.getDbs()
74
75        for db in (_ for _ in dbs if _):
76            dbs[dbs.index(db)] = safeSQLIdentificatorNaming(db)
77
78        infoMsg = "fetching tables for database"
79        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)))
80        logger.info(infoMsg)
81
82        rootQuery = queries[DBMS.MAXDB].tables
83
84        for db in dbs:
85            query = rootQuery.inband.query % (("'%s'" % db) if db != "USER" else 'USER')
86            retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.tablename' % kb.aliasName], blind=True)
87
88            if retVal:
89                for table in list(retVal[0].values())[0]:
90                    if db not in kb.data.cachedTables:
91                        kb.data.cachedTables[db] = [table]
92                    else:
93                        kb.data.cachedTables[db].append(table)
94
95        for db, tables in kb.data.cachedTables.items():
96            kb.data.cachedTables[db] = sorted(tables) if tables else tables
97
98        return kb.data.cachedTables
99
100    def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False):
101        self.forceDbmsEnum()
102
103        if conf.db is None or conf.db == CURRENT_DB:
104            if conf.db is None:
105                warnMsg = "missing database parameter. sqlmap is going "
106                warnMsg += "to use the current database to enumerate "
107                warnMsg += "table(s) columns"
108                logger.warn(warnMsg)
109
110            conf.db = self.getCurrentDb()
111
112        elif conf.db is not None:
113            if ',' in conf.db:
114                errMsg = "only one database name is allowed when enumerating "
115                errMsg += "the tables' columns"
116                raise SqlmapMissingMandatoryOptionException(errMsg)
117
118        conf.db = safeSQLIdentificatorNaming(conf.db)
119
120        if conf.col:
121            colList = conf.col.split(',')
122        else:
123            colList = []
124
125        if conf.exclude:
126            colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
127
128        for col in colList:
129            colList[colList.index(col)] = safeSQLIdentificatorNaming(col)
130
131        if conf.tbl:
132            tblList = conf.tbl.split(',')
133        else:
134            self.getTables()
135
136            if len(kb.data.cachedTables) > 0:
137                tblList = list(kb.data.cachedTables.values())
138
139                if tblList and isListLike(tblList[0]):
140                    tblList = tblList[0]
141            else:
142                errMsg = "unable to retrieve the tables "
143                errMsg += "on database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
144                raise SqlmapNoneDataException(errMsg)
145
146        for tbl in tblList:
147            tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True)
148
149        if bruteForce:
150            resumeAvailable = False
151
152            for tbl in tblList:
153                for db, table, colName, colType in kb.brute.columns:
154                    if db == conf.db and table == tbl:
155                        resumeAvailable = True
156                        break
157
158            if resumeAvailable and not conf.freshQueries or colList:
159                columns = {}
160
161                for column in colList:
162                    columns[column] = None
163
164                for tbl in tblList:
165                    for db, table, colName, colType in kb.brute.columns:
166                        if db == conf.db and table == tbl:
167                            columns[colName] = colType
168
169                    if conf.db in kb.data.cachedColumns:
170                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns
171                    else:
172                        kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns}
173
174                return kb.data.cachedColumns
175
176            message = "do you want to use common column existence check? [y/N/q] "
177            choice = readInput(message, default='Y' if 'Y' in message else 'N').upper()
178
179            if choice == 'N':
180                return
181            elif choice == 'Q':
182                raise SqlmapUserQuitException
183            else:
184                return columnExists(paths.COMMON_COLUMNS)
185
186        rootQuery = queries[DBMS.MAXDB].columns
187
188        for tbl in tblList:
189            if conf.db is not None and len(kb.data.cachedColumns) > 0 and conf.db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[conf.db]:
190                infoMsg = "fetched tables' columns on "
191                infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
192                logger.info(infoMsg)
193
194                return {conf.db: kb.data.cachedColumns[conf.db]}
195
196            if dumpMode and colList:
197                table = {}
198                table[safeSQLIdentificatorNaming(tbl, True)] = dict((_, None) for _ in colList)
199                kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table
200                continue
201
202            infoMsg = "fetching columns "
203            infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
204            infoMsg += "on database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
205            logger.info(infoMsg)
206
207            query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), ("'%s'" % unsafeSQLIdentificatorNaming(conf.db)) if unsafeSQLIdentificatorNaming(conf.db) != "USER" else 'USER')
208            retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.columnname' % kb.aliasName, '%s.datatype' % kb.aliasName, '%s.len' % kb.aliasName], blind=True)
209
210            if retVal:
211                table = {}
212                columns = {}
213
214                for columnname, datatype, length in _zip(retVal[0]["%s.columnname" % kb.aliasName], retVal[0]["%s.datatype" % kb.aliasName], retVal[0]["%s.len" % kb.aliasName]):
215                    columns[safeSQLIdentificatorNaming(columnname)] = "%s(%s)" % (datatype, length)
216
217                table[tbl] = columns
218                kb.data.cachedColumns[conf.db] = table
219
220        return kb.data.cachedColumns
221
222    def getPrivileges(self, *args, **kwargs):
223        warnMsg = "on SAP MaxDB it is not possible to enumerate the user privileges"
224        logger.warn(warnMsg)
225
226        return {}
227
228    def search(self):
229        warnMsg = "on SAP MaxDB search option is not available"
230        logger.warn(warnMsg)
231
232    def getHostname(self):
233        warnMsg = "on SAP MaxDB it is not possible to enumerate the hostname"
234        logger.warn(warnMsg)
235
236    def getStatements(self):
237        warnMsg = "on SAP MaxDB it is not possible to enumerate the SQL statements"
238        logger.warn(warnMsg)
239
240        return []
241