diff --git a/lib/controller/action.py b/lib/controller/action.py index b90df8944..518ab58c4 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -27,6 +27,7 @@ from lib.core.common import getHtmlErrorFp from lib.core.common import dataToStdout from lib.core.data import conf from lib.core.data import kb +from lib.core.data import paths from lib.core.exception import sqlmapUnsupportedDBMSException from lib.core.settings import SUPPORTED_DBMS from lib.techniques.blind.timebased import timeTest @@ -111,6 +112,12 @@ def action(): if conf.getTables: conf.dumper.dbTables(conf.dbmsHandler.getTables()) + if conf.cExists: + conf.dumper.dbTables(conf.dbmsHandler.tableExists(paths.COMMON_TABLES)) + + if conf.tableFile: + conf.dumper.dbTables(conf.dbmsHandler.tableExists(conf.tableFile)) + if conf.getColumns: conf.dumper.dbTableColumns(conf.dbmsHandler.getColumns()) diff --git a/lib/core/common.py b/lib/core/common.py index 30d208b77..a7642bf1e 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -411,8 +411,8 @@ def filePathToString(filePath): return strRepl -def dataToStdout(data): - if conf.verbose > 0: +def dataToStdout(data, forceOutput=False): + if conf.verbose > 0 or forceOutput: try: sys.stdout.write(data) sys.stdout.flush() @@ -657,6 +657,8 @@ def setPaths(): # sqlmap files paths.SQLMAP_HISTORY = os.path.join(paths.SQLMAP_ROOT_PATH, ".sqlmap_history") paths.SQLMAP_CONFIG = os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap-%s.conf" % randomStr()) + paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt') + paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt") paths.FUZZ_VECTORS = os.path.join(paths.SQLMAP_TXT_PATH, "fuzz_vectors.txt") paths.DETECTION_RULES_XML = os.path.join(paths.SQLMAP_XML_PATH, "detection.xml") paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml") @@ -1233,8 +1235,7 @@ def initCommonOutputs(): kb.commonOutputs = {} key = None - fileName = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt') - cfile = codecs.open(fileName, 'r', conf.dataEncoding) + cfile = codecs.open(paths.COMMON_OUTPUTS, 'r', conf.dataEncoding) for line in cfile.readlines(): # xreadlines doesn't return unicode strings when codec.open() is used if line.find('#') != -1: @@ -1254,6 +1255,21 @@ def initCommonOutputs(): cfile.close() +def getFileItems(filename): + retVal = [] + + checkFile(filename) + file = codecs.open(filename, 'r', conf.dataEncoding) + + for line in file.readlines(): # xreadlines doesn't return unicode strings when codec.open() is used + if line.find('#') != -1: + line = line[:line.find('#')] + line = line.strip() + if line: + retVal.append(line) + + return retVal + def goGoodSamaritan(prevValue, originalCharset): """ Function for retrieving parameters needed for common prediction (good @@ -1411,4 +1427,10 @@ def replaceSpaces(query): if query: return query if conf.space is None else query.replace(' ', conf.space) else: - return query \ No newline at end of file + return query + +def pushValue(value): + kb.valueStack.append(value) + +def popValue(): + return kb.valueStack.pop() diff --git a/lib/core/dump.py b/lib/core/dump.py index 418d0b76a..466739415 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -157,37 +157,58 @@ class Dump: self.lister("available databases", dbs) def dbTables(self, dbTables): - if not isinstance(dbTables, dict): - self.string("tables", dbTables) + if isinstance(dbTables, list): + maxlength = 0 - return - - maxlength = 0 - - for tables in dbTables.values(): - for table in tables: + for table in dbTables: maxlength = max(maxlength, len(table)) - lines = "-" * (int(maxlength) + 2) + lines = "-" * (int(maxlength) + 2) - for db, tables in dbTables.items(): - tables.sort(key=lambda x: x.lower()) + dbTables.sort(key=lambda x: x.lower()) - self.__write("Database: %s" % db) - - if len(tables) == 1: + if len(dbTables) == 1: self.__write("[1 table]") else: - self.__write("[%d tables]" % len(tables)) + self.__write("[%d tables]" % len(dbTables)) self.__write("+%s+" % lines) - for table in tables: + for table in dbTables: blank = " " * (maxlength - len(table)) self.__write("| %s%s |" % (table, blank)) self.__write("+%s+\n" % lines) + elif isinstance(dbTables, dict): + maxlength = 0 + + for tables in dbTables.values(): + for table in tables: + maxlength = max(maxlength, len(table)) + + lines = "-" * (int(maxlength) + 2) + + for db, tables in dbTables.items(): + tables.sort(key=lambda x: x.lower()) + + self.__write("Database: %s" % db) + + if len(tables) == 1: + self.__write("[1 table]") + else: + self.__write("[%d tables]" % len(tables)) + + self.__write("+%s+" % lines) + + for table in tables: + blank = " " * (maxlength - len(table)) + self.__write("| %s%s |" % (table, blank)) + + self.__write("+%s+\n" % lines) + else: + self.string("tables", dbTables) + def dbTableColumns(self, tableColumns): for db, tables in tableColumns.items(): if not db: diff --git a/lib/core/option.py b/lib/core/option.py index 8b8f10ee1..4b3e0574d 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1031,6 +1031,7 @@ def __setKnowledgeBaseAttributes(): kb.unionPosition = None kb.unionNegative = False kb.unionFalseCond = False + kb.valueStack = [] def __saveCmdline(): """ diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 3a5dcb26c..43e630a69 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -332,6 +332,12 @@ def cmdLineParser(): action="store_true", default=False, help="Prompt for an interactive SQL shell") + enumeration.add_option("--common-exists", dest="cExists", action="store_true", + default=False, help="Check existence of common tables") + + enumeration.add_option("--exists", dest="tableFile", + help="Check existence of user specified tables") + # User-defined function options udf = OptionGroup(parser, "User-defined function injection", "These " "options can be used to create custom user-defined " diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 8e0c82e4e..e64fed64a 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -23,12 +23,18 @@ Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ import re +import time from lib.core.agent import agent +from lib.core.common import dataToStdout from lib.core.common import getRange from lib.core.common import getCompiledRegex +from lib.core.common import getConsoleWidth +from lib.core.common import getFileItems from lib.core.common import getUnicode from lib.core.common import parsePasswordHash +from lib.core.common import popValue +from lib.core.common import pushValue from lib.core.common import readInput from lib.core.common import safeStringFormat from lib.core.convert import urlencode @@ -47,6 +53,7 @@ from lib.core.shell import autoCompletion from lib.core.unescaper import unescaper from lib.parse.banner import bannerParser from lib.request import inject +from lib.request.connect import Connect as Request from lib.techniques.inband.union.test import unionTest from lib.techniques.outband.stacked import stackedTest @@ -801,6 +808,41 @@ class Enumeration: return kb.data.cachedTables + def tableExists(self, tableFile): + tables = getFileItems(tableFile) + retVal = [] + infoMsg = "checking tables existence using items from '%s'" % tableFile + logger.info(infoMsg) + + pushValue(conf.verbose) + conf.verbose = 0 + count = 0 + length = len(tables) + + for table in tables: + query = agent.prefixQuery(" %s" % safeStringFormat("AND EXISTS(SELECT 1 FROM %s)", table)) + query = agent.postfixQuery(query) + result = Request.queryPage(urlencode(agent.payload(newValue=query))) + + if result: + infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), table) + infoMsg = "%s%s\n" % (infoMsg, " "*(getConsoleWidth()-1-len(infoMsg))) + dataToStdout(infoMsg, True) + retVal.append(table) + + count += 1 + status = '%d/%d (%d%s)' % (count, length, round(100.0*count/length), '%') + dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status), True) + + conf.verbose = popValue() + + dataToStdout("\r%s\n" % (" "*(getConsoleWidth()-1)), True) + if not retVal: + warnMsg = "no table found" + logger.warn(warnMsg) + + return retVal + def getColumns(self, onlyColNames=False): if kb.dbms == "MySQL" and not kb.data.has_information_schema: errMsg = "information_schema not available, "