From 95560da7c1495edf538faf6f03b749a9bf65f529 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 29 May 2019 15:52:33 +0200 Subject: [PATCH] Implements #1222 --- data/xml/queries.xml | 25 +++++++++ lib/controller/action.py | 3 ++ lib/core/dump.py | 3 ++ lib/core/enums.py | 1 + lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 ++ plugins/dbms/access/enumeration.py | 6 +++ plugins/dbms/db2/enumeration.py | 6 +++ plugins/dbms/firebird/enumeration.py | 6 +++ plugins/dbms/h2/enumeration.py | 6 +++ plugins/dbms/hsqldb/enumeration.py | 6 +++ plugins/dbms/informix/enumeration.py | 6 +++ plugins/dbms/maxdb/enumeration.py | 6 +++ plugins/dbms/sqlite/enumeration.py | 6 +++ plugins/dbms/sybase/enumeration.py | 6 +++ plugins/generic/databases.py | 76 +++++++++++++++++++++++++++- sqlmap.conf | 4 ++ 18 files changed, 169 insertions(+), 3 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 56983fc1e..e0c56ae74 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -41,6 +41,10 @@ + + + + @@ -112,6 +116,10 @@ + + + + @@ -180,6 +188,10 @@ + + + + @@ -268,6 +280,10 @@ + + + + @@ -332,6 +348,7 @@ + @@ -392,6 +409,7 @@ + @@ -435,6 +453,7 @@ + @@ -504,6 +523,7 @@ + @@ -549,6 +569,7 @@ + @@ -620,6 +641,7 @@ + @@ -690,6 +712,7 @@ + @@ -753,6 +776,7 @@ + @@ -825,6 +849,7 @@ + diff --git a/lib/controller/action.py b/lib/controller/action.py index 6c370c738..07aaeda73 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -72,6 +72,9 @@ def action(): if conf.getUsers: conf.dumper.users(conf.dbmsHandler.getUsers()) + if conf.getStatements: + conf.dumper.statements(conf.dbmsHandler.getStatements()) + if conf.getPasswordHashes: try: conf.dumper.userSettings("database management system users password hashes", conf.dbmsHandler.getPasswordHashes(), "password hash", CONTENT_TYPE.PASSWORDS) diff --git a/lib/core/dump.py b/lib/core/dump.py index e79f96b2b..0a1b3979e 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -188,6 +188,9 @@ class Dump(object): def users(self, users): self.lister("database management system users", users, content_type=CONTENT_TYPE.USERS) + def statements(self, statements): + self.lister("SQL statements", statements, content_type=CONTENT_TYPE.STATEMENTS) + def userSettings(self, header, userSettings, subHeader, content_type=None): self._areAdmins = set() diff --git a/lib/core/enums.py b/lib/core/enums.py index a13dbcd1e..bc5748de5 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -348,6 +348,7 @@ class CONTENT_TYPE: FILE_WRITE = 23 OS_CMD = 24 REG_READ = 25 + STATEMENTS = 26 class CONTENT_STATUS: IN_PROGRESS = 0 diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index ccc5cfca1..c5d1c2165 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -139,6 +139,7 @@ optDict = { "dumpAll": "boolean", "search": "boolean", "getComments": "boolean", + "getStatements": "boolean", "db": "string", "tbl": "string", "col": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index 383e1d14d..8d7137dd1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -18,7 +18,7 @@ from lib.core.enums import OS from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.3.5.151" +VERSION = "1.3.5.152" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 5342dffdb..fa889c9eb 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -417,6 +417,9 @@ def cmdLineParser(argv=None): enumeration.add_option("--comments", dest="getComments", action="store_true", help="Check for DBMS comments during enumeration") + enumeration.add_option("--statements", dest="getStatements", action="store_true", + help="Retrieve SQL statements being run on DBMS") + enumeration.add_option("-D", dest="db", help="DBMS database to enumerate") diff --git a/plugins/dbms/access/enumeration.py b/plugins/dbms/access/enumeration.py index 3028f9366..a20c59ddf 100644 --- a/plugins/dbms/access/enumeration.py +++ b/plugins/dbms/access/enumeration.py @@ -76,3 +76,9 @@ class Enumeration(GenericEnumeration): def getHostname(self): warnMsg = "on Microsoft Access it is not possible to enumerate the hostname" logger.warn(warnMsg) + + def getStatements(self): + warnMsg = "on Microsoft Access it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/db2/enumeration.py b/plugins/dbms/db2/enumeration.py index 3fbfa1ae0..4f29cfb64 100644 --- a/plugins/dbms/db2/enumeration.py +++ b/plugins/dbms/db2/enumeration.py @@ -14,3 +14,9 @@ class Enumeration(GenericEnumeration): logger.warn(warnMsg) return {} + + def getStatements(self): + warnMsg = "on DB2 it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/firebird/enumeration.py b/plugins/dbms/firebird/enumeration.py index 29f1a3852..4281f8bb6 100644 --- a/plugins/dbms/firebird/enumeration.py +++ b/plugins/dbms/firebird/enumeration.py @@ -36,3 +36,9 @@ class Enumeration(GenericEnumeration): def getHostname(self): warnMsg = "on Firebird it is not possible to enumerate the hostname" logger.warn(warnMsg) + + def getStatements(self): + warnMsg = "on Firebird it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/h2/enumeration.py b/plugins/dbms/h2/enumeration.py index abe022463..23ee0dfff 100644 --- a/plugins/dbms/h2/enumeration.py +++ b/plugins/dbms/h2/enumeration.py @@ -47,3 +47,9 @@ class Enumeration(GenericEnumeration): logger.warn(warnMsg) return {} + + def getStatements(self): + warnMsg = "on H2 it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/hsqldb/enumeration.py b/plugins/dbms/hsqldb/enumeration.py index 7c415cb66..2b4dcd589 100644 --- a/plugins/dbms/hsqldb/enumeration.py +++ b/plugins/dbms/hsqldb/enumeration.py @@ -41,3 +41,9 @@ class Enumeration(GenericEnumeration): def getCurrentDb(self): return HSQLDB_DEFAULT_SCHEMA + + def getStatements(self): + warnMsg = "on HSQLDB it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/informix/enumeration.py b/plugins/dbms/informix/enumeration.py index 092224c94..5b44899c6 100644 --- a/plugins/dbms/informix/enumeration.py +++ b/plugins/dbms/informix/enumeration.py @@ -30,3 +30,9 @@ class Enumeration(GenericEnumeration): def search(self): warnMsg = "on Informix search option is not available" logger.warn(warnMsg) + + def getStatements(self): + warnMsg = "on Informix it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/maxdb/enumeration.py b/plugins/dbms/maxdb/enumeration.py index 146762d52..53fd81c84 100644 --- a/plugins/dbms/maxdb/enumeration.py +++ b/plugins/dbms/maxdb/enumeration.py @@ -230,3 +230,9 @@ class Enumeration(GenericEnumeration): def getHostname(self): warnMsg = "on SAP MaxDB it is not possible to enumerate the hostname" logger.warn(warnMsg) + + def getStatements(self): + warnMsg = "on SAP MaxDB it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/sqlite/enumeration.py b/plugins/dbms/sqlite/enumeration.py index c596318ba..4812f57a0 100644 --- a/plugins/dbms/sqlite/enumeration.py +++ b/plugins/dbms/sqlite/enumeration.py @@ -61,3 +61,9 @@ class Enumeration(GenericEnumeration): def getHostname(self): warnMsg = "on SQLite it is not possible to enumerate the hostname" logger.warn(warnMsg) + + def getStatements(self): + warnMsg = "on SQLite it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/sybase/enumeration.py b/plugins/dbms/sybase/enumeration.py index faccae6e7..19e3532f8 100644 --- a/plugins/dbms/sybase/enumeration.py +++ b/plugins/dbms/sybase/enumeration.py @@ -316,3 +316,9 @@ class Enumeration(GenericEnumeration): def getHostname(self): warnMsg = "on Sybase it is not possible to enumerate the hostname" logger.warn(warnMsg) + + def getStatements(self): + warnMsg = "on Sybase it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index b9753aa90..0c4ed0f16 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -44,6 +44,7 @@ from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUserQuitException from lib.core.settings import CURRENT_DB +from lib.core.settings import REFLECTED_VALUE_MARKER from lib.request import inject from lib.techniques.union.use import unionUse from lib.utils.brute import columnExists @@ -62,6 +63,7 @@ class Databases: kb.data.cachedColumns = {} kb.data.cachedCounts = {} kb.data.dumpedTable = {} + kb.data.cachedStatements = [] def getCurrentDb(self): infoMsg = "fetching current database" @@ -142,9 +144,10 @@ class Databases: query = rootQuery.blind.query2 % index else: query = rootQuery.blind.query % index + db = unArrayizeValue(inject.getValue(query, union=False, error=False)) - if db: + if not isNoneValue(db): kb.data.cachedDbs.append(safeSQLIdentificatorNaming(db)) if not kb.data.cachedDbs and Backend.isDbms(DBMS.MSSQL): @@ -375,6 +378,7 @@ class Databases: query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(db), index) table = unArrayizeValue(inject.getValue(query, union=False, error=False)) + if not isNoneValue(table): kb.hintValue = table table = safeSQLIdentificatorNaming(table, True) @@ -761,6 +765,7 @@ class Databases: while True: query = rootQuery.blind.query3 % (conf.db, unsafeSQLIdentificatorNaming(tbl), index) value = unArrayizeValue(inject.getValue(query, union=False, error=False)) + if isNoneValue(value) or value == " ": break else: @@ -834,8 +839,8 @@ class Databases: query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl), column) colType = unArrayizeValue(inject.getValue(query, union=False, error=False)) - key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType + if Backend.isDbms(DBMS.FIREBIRD): colType = FIREBIRD_TYPES.get(key, colType) elif Backend.isDbms(DBMS.INFORMIX): @@ -960,3 +965,70 @@ class Databases: self._tableGetCount(db, table) return kb.data.cachedCounts + + def getStatements(self): + infoMsg = "fetching SQL statements" + logger.info(infoMsg) + + rootQuery = queries[Backend.getIdentifiedDbms()].statements + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: + query = rootQuery.inband.query + + while True: + values = inject.getValue(query, blind=False, time=False) + + if not isNoneValue(values): + kb.data.cachedStatements = [] + for value in arrayizeValue(values): + value = (unArrayizeValue(value) or "").strip() + if not isNoneValue(value): + kb.data.cachedStatements.append(value.strip()) + + elif Backend.isDbms(DBMS.PGSQL) and "current_query" not in query: + query = query.replace("query", "current_query") + continue + + break + + if not kb.data.cachedStatements and isInferenceAvailable() and not conf.direct: + infoMsg = "fetching number of statements" + logger.info(infoMsg) + + query = rootQuery.blind.count + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if count == 0: + return kb.data.cachedStatements + elif not isNumPosStrValue(count): + errMsg = "unable to retrieve the number of statements" + raise SqlmapNoneDataException(errMsg) + + plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2) + indexRange = getLimitRange(count, plusOne=plusOne) + + for index in indexRange: + value = None + + if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): # case with multiple processes + query = rootQuery.blind.query3 % index + identifier = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT)) + + if not isNoneValue(identifier): + query = rootQuery.blind.query2 % identifier + value = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT)) + + if isNoneValue(value): + query = rootQuery.blind.query % index + value = unArrayizeValue(inject.getValue(query, union=False, error=False)) + + if not isNoneValue(value): + kb.data.cachedStatements.append(value) + + if not kb.data.cachedStatements: + errMsg = "unable to retrieve the statements" + logger.error(errMsg) + else: + kb.data.cachedStatements = [_.replace(REFLECTED_VALUE_MARKER, "") for _ in kb.data.cachedStatements] + + return kb.data.cachedStatements \ No newline at end of file diff --git a/sqlmap.conf b/sqlmap.conf index e83e09e80..b3056cc6f 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -496,6 +496,10 @@ search = False # Valid: True or False getComments = False +# Retrieve SQL statements being run on database management system. +# Valid: True or False +getStatements = False + # Back-end database management system database to enumerate. db =