From 1b48ff223d03a1f9b58485c92c75d10c1cfdb80d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 23 Sep 2016 12:33:27 +0200 Subject: [PATCH] Adding initial support for Informix (Issue #552) --- lib/controller/handler.py | 4 + lib/core/agent.py | 2 +- lib/core/dicts.py | 7 +- lib/core/enums.py | 2 + lib/core/settings.py | 2 +- lib/utils/deps.py | 2 + plugins/dbms/informix/__init__.py | 34 +++++++++ plugins/dbms/informix/connector.py | 63 ++++++++++++++++ plugins/dbms/informix/enumeration.py | 14 ++++ plugins/dbms/informix/filesystem.py | 12 +++ plugins/dbms/informix/fingerprint.py | 107 +++++++++++++++++++++++++++ plugins/dbms/informix/syntax.py | 37 +++++++++ plugins/dbms/informix/takeover.py | 15 ++++ plugins/generic/databases.py | 10 ++- plugins/generic/entries.py | 7 +- txt/checksum.md5 | 9 ++- xml/queries.xml | 63 ++++++++++++++++ 17 files changed, 383 insertions(+), 7 deletions(-) create mode 100644 plugins/dbms/informix/__init__.py create mode 100644 plugins/dbms/informix/connector.py create mode 100644 plugins/dbms/informix/enumeration.py create mode 100644 plugins/dbms/informix/filesystem.py create mode 100644 plugins/dbms/informix/fingerprint.py create mode 100644 plugins/dbms/informix/syntax.py create mode 100644 plugins/dbms/informix/takeover.py diff --git a/lib/controller/handler.py b/lib/controller/handler.py index 4028b241a..afa725b26 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -22,6 +22,7 @@ from lib.core.settings import MAXDB_ALIASES from lib.core.settings import SYBASE_ALIASES from lib.core.settings import DB2_ALIASES from lib.core.settings import HSQLDB_ALIASES +from lib.core.settings import INFORMIX_ALIASES from lib.utils.sqlalchemy import SQLAlchemy from plugins.dbms.mssqlserver import MSSQLServerMap @@ -46,6 +47,8 @@ from plugins.dbms.db2 import DB2Map from plugins.dbms.db2.connector import Connector as DB2Conn from plugins.dbms.hsqldb import HSQLDBMap from plugins.dbms.hsqldb.connector import Connector as HSQLDBConn +from plugins.dbms.informix import InformixMap +from plugins.dbms.informix.connector import Connector as InformixConn def setHandler(): """ @@ -65,6 +68,7 @@ def setHandler(): (DBMS.SYBASE, SYBASE_ALIASES, SybaseMap, SybaseConn), (DBMS.DB2, DB2_ALIASES, DB2Map, DB2Conn), (DBMS.HSQLDB, HSQLDB_ALIASES, HSQLDBMap, HSQLDBConn), + (DBMS.INFORMIX, INFORMIX_ALIASES, InformixMap, InformixConn), ] _ = max(_ if (Backend.getIdentifiedDbms() or "").lower() in _[1] else None for _ in items) diff --git a/lib/core/agent.py b/lib/core/agent.py index b2d879476..244fb63e2 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -486,7 +486,7 @@ class Agent(object): @rtype: C{str} """ - prefixRegex = r"(?:\s+(?:FIRST|SKIP|LIMIT \d+)\s+\d+)*" + prefixRegex = r"(?:\s+(?:FIRST|SKIP|LIMIT( \d+)?)\s+\d+)*" fieldsSelectTop = re.search(r"\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", query, re.I) fieldsSelectRownum = re.search(r"\ASELECT\s+([^()]+?),\s*ROWNUM AS LIMIT FROM", query, re.I) fieldsSelectDistinct = re.search(r"\ASELECT%s\s+DISTINCT\((.+?)\)\s+FROM" % prefixRegex, query, re.I) diff --git a/lib/core/dicts.py b/lib/core/dicts.py index d2e4e234f..82b460bd8 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -21,6 +21,7 @@ from lib.core.settings import MAXDB_ALIASES from lib.core.settings import SYBASE_ALIASES from lib.core.settings import DB2_ALIASES from lib.core.settings import HSQLDB_ALIASES +from lib.core.settings import INFORMIX_ALIASES FIREBIRD_TYPES = { 261: "BLOB", @@ -146,8 +147,9 @@ DBMS_DICT = { DBMS.FIREBIRD: (FIREBIRD_ALIASES, "python-kinterbasdb", "http://kinterbasdb.sourceforge.net/", "firebird"), DBMS.MAXDB: (MAXDB_ALIASES, None, None, "maxdb"), DBMS.SYBASE: (SYBASE_ALIASES, "python-pymssql", "http://pymssql.sourceforge.net/", "sybase"), - DBMS.DB2: (DB2_ALIASES, "python ibm-db", "http://code.google.com/p/ibm-db/", "ibm_db_sa"), + DBMS.DB2: (DB2_ALIASES, "python ibm-db", "https://github.com/ibmdb/python-ibmdb", "ibm_db_sa"), DBMS.HSQLDB: (HSQLDB_ALIASES, "python jaydebeapi & python-jpype", "https://pypi.python.org/pypi/JayDeBeApi/ & http://jpype.sourceforge.net/", None), + DBMS.INFORMIX: (INFORMIX_ALIASES, "python ibm-db", "https://github.com/ibmdb/python-ibmdb", "ibm_db_sa"), } FROM_DUMMY_TABLE = { @@ -156,7 +158,8 @@ FROM_DUMMY_TABLE = { DBMS.FIREBIRD: " FROM RDB$DATABASE", DBMS.MAXDB: " FROM VERSIONS", DBMS.DB2: " FROM SYSIBM.SYSDUMMY1", - DBMS.HSQLDB: " FROM INFORMATION_SCHEMA.SYSTEM_USERS" + DBMS.HSQLDB: " FROM INFORMATION_SCHEMA.SYSTEM_USERS", + DBMS.INFORMIX: " FROM SYSMASTER:SYSDUAL" } SQL_STATEMENTS = { diff --git a/lib/core/enums.py b/lib/core/enums.py index 0a879d54b..88f7d8695 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -34,6 +34,7 @@ class DBMS: SQLITE = "SQLite" SYBASE = "Sybase" HSQLDB = "HSQLDB" + INFORMIX = "Informix" class DBMS_DIRECTORY_NAME: ACCESS = "access" @@ -47,6 +48,7 @@ class DBMS_DIRECTORY_NAME: SQLITE = "sqlite" SYBASE = "sybase" HSQLDB = "hsqldb" + INFORMIX = "informix" class CUSTOM_LOGGING: PAYLOAD = 9 diff --git a/lib/core/settings.py b/lib/core/settings.py index 0769e7070..daa1133e7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from lib.core.enums import OS from lib.core.revision import getRevisionNumber # sqlmap version (...) -VERSION = "1.0.9.22" +VERSION = "1.0.9.23" REVISION = getRevisionNumber() TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} diff --git a/lib/utils/deps.py b/lib/utils/deps.py index cbb9787a1..c4d472203 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -44,6 +44,8 @@ def checkDependencies(): elif dbmsName == DBMS.HSQLDB: import jaydebeapi import jpype + elif dbmsName == DBMS.INFORMIX: + import ibm_db_dbi except ImportError: warnMsg = "sqlmap requires '%s' third-party library " % data[1] warnMsg += "in order to directly connect to the DBMS " diff --git a/plugins/dbms/informix/__init__.py b/plugins/dbms/informix/__init__.py new file mode 100644 index 000000000..00612c2d0 --- /dev/null +++ b/plugins/dbms/informix/__init__.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +from lib.core.enums import DBMS +from lib.core.settings import INFORMIX_SYSTEM_DBS +from lib.core.unescaper import unescaper + +from plugins.dbms.informix.enumeration import Enumeration +from plugins.dbms.informix.filesystem import Filesystem +from plugins.dbms.informix.fingerprint import Fingerprint +from plugins.dbms.informix.syntax import Syntax +from plugins.dbms.informix.takeover import Takeover +from plugins.generic.misc import Miscellaneous + +class InformixMap(Syntax, Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover): + """ + This class defines Informix methods + """ + + def __init__(self): + self.excludeDbsList = INFORMIX_SYSTEM_DBS + + Syntax.__init__(self) + Fingerprint.__init__(self) + Enumeration.__init__(self) + Filesystem.__init__(self) + Miscellaneous.__init__(self) + Takeover.__init__(self) + + unescaper[DBMS.INFORMIX] = Syntax.escape diff --git a/plugins/dbms/informix/connector.py b/plugins/dbms/informix/connector.py new file mode 100644 index 000000000..c19fabdeb --- /dev/null +++ b/plugins/dbms/informix/connector.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +try: + import ibm_db_dbi +except ImportError: + pass + +import logging + +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: http://code.google.com/p/ibm-db/ + User guide: http://code.google.com/p/ibm-db/wiki/README + API: http://www.python.org/dev/peps/pep-0249/ + License: Apache License 2.0 + """ + + def __init__(self): + GenericConnector.__init__(self) + + def connect(self): + self.initConnection() + + try: + database = "DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;" % (self.db, self.hostname, self.port) + self.connector = ibm_db_dbi.connect(database, self.user, self.password) + except ibm_db_dbi.OperationalError, msg: + raise SqlmapConnectionException(msg) + + + self.initCursor() + self.printConnected() + + def fetchall(self): + try: + return self.cursor.fetchall() + except ibm_db_dbi.ProgrammingError, msg: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % msg[1]) + return None + + def execute(self, query): + try: + self.cursor.execute(query) + except (ibm_db_dbi.OperationalError, ibm_db_dbi.ProgrammingError), msg: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % msg[1]) + except ibm_db_dbi.InternalError, msg: + raise SqlmapConnectionException(msg[1]) + + self.connector.commit() + + def select(self, query): + self.execute(query) + return self.fetchall() diff --git a/plugins/dbms/informix/enumeration.py b/plugins/dbms/informix/enumeration.py new file mode 100644 index 000000000..1b0a582fd --- /dev/null +++ b/plugins/dbms/informix/enumeration.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + + +from lib.core.data import logger +from plugins.generic.enumeration import Enumeration as GenericEnumeration + +class Enumeration(GenericEnumeration): + def __init__(self): + GenericEnumeration.__init__(self) diff --git a/plugins/dbms/informix/filesystem.py b/plugins/dbms/informix/filesystem.py new file mode 100644 index 000000000..9cfc18626 --- /dev/null +++ b/plugins/dbms/informix/filesystem.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +from plugins.generic.filesystem import Filesystem as GenericFilesystem + +class Filesystem(GenericFilesystem): + def __init__(self): + GenericFilesystem.__init__(self) diff --git a/plugins/dbms/informix/fingerprint.py b/plugins/dbms/informix/fingerprint.py new file mode 100644 index 000000000..98783d864 --- /dev/null +++ b/plugins/dbms/informix/fingerprint.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +import re + +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 INFORMIX_ALIASES +from lib.request import inject +from plugins.generic.fingerprint import Fingerprint as GenericFingerprint + +class Fingerprint(GenericFingerprint): + def __init__(self): + GenericFingerprint.__init__(self, DBMS.INFORMIX) + + 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.INFORMIX + return value + + actVer = Format.getDbms() + blank = " " * 15 + value += "active fingerprint: %s" % actVer + + if kb.bannerFp: + banVer = kb.bannerFp["dbmsVersion"] if 'dbmsVersion' in kb.bannerFp else None + 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(INFORMIX_ALIASES) or (conf.dbms or "").lower() in INFORMIX_ALIASES): + setDbms(DBMS.INFORMIX) + + self.getBanner() + + return True + + infoMsg = "testing %s" % DBMS.INFORMIX + logger.info(infoMsg) + + result = inject.checkBooleanExpression("[RANDNUM]=(SELECT [RANDNUM] FROM SYSMASTER:SYSDUAL)") + + if result: + infoMsg = "confirming %s" % DBMS.INFORMIX + logger.info(infoMsg) + + result = inject.checkBooleanExpression("(SELECT DBINFO('DBNAME') FROM SYSMASTER:SYSDUAL) IS NOT NULL") + + if not result: + warnMsg = "the back-end DBMS is not %s" % DBMS.INFORMIX + logger.warn(warnMsg) + + return False + + setDbms(DBMS.INFORMIX) + + self.getBanner() + + if not conf.extensiveFp: + return True + + infoMsg = "actively fingerprinting %s" % DBMS.INFORMIX + logger.info(infoMsg) + + for version in ("12.1", "11.7", "11.5"): + 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.INFORMIX + logger.warn(warnMsg) + + return False diff --git a/plugins/dbms/informix/syntax.py b/plugins/dbms/informix/syntax.py new file mode 100644 index 000000000..da819a30c --- /dev/null +++ b/plugins/dbms/informix/syntax.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' for copying permission +""" + +import re + +from lib.core.common import randomStr +from plugins.generic.syntax import Syntax as GenericSyntax + +class Syntax(GenericSyntax): + def __init__(self): + GenericSyntax.__init__(self) + + @staticmethod + def escape(expression, quote=True): + """ + >>> Syntax.escape("SELECT 'abcdefgh' FROM foobar") + 'SELECT CHR(97)||CHR(98)||CHR(99)||CHR(100)||CHR(101)||CHR(102)||CHR(103)||CHR(104) FROM foobar' + """ + + def escaper(value): + return "||".join("CHR(%d)" % ord(_) for _ in value) + + 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 \ No newline at end of file diff --git a/plugins/dbms/informix/takeover.py b/plugins/dbms/informix/takeover.py new file mode 100644 index 000000000..d1504b06f --- /dev/null +++ b/plugins/dbms/informix/takeover.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2016 sqlmap developers (http://sqlmap.org/) +See the file 'doc/COPYING' 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 aae07ec6a..82c7ef859 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -335,7 +335,7 @@ class Databases: query = rootQuery.blind.query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ") elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): query = rootQuery.blind.query % index - elif Backend.isDbms(DBMS.HSQLDB): + elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX): query = rootQuery.blind.query % (index, unsafeSQLIdentificatorNaming(db)) else: query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(db), index) @@ -656,6 +656,10 @@ class Databases: query = rootQuery.blind.count % (tbl) query += condQuery + elif Backend.isDbms(DBMS.INFORMIX): + query = rootQuery.blind.count % (conf.db, conf.db, conf.db, conf.db, conf.db, tbl) + query += condQuery + elif Backend.isDbms(DBMS.SQLITE): query = rootQuery.blind.query % tbl value = unArrayizeValue(inject.getValue(query, union=False, error=False)) @@ -712,6 +716,10 @@ class Databases: query = rootQuery.blind.query % (tbl) query += condQuery field = None + elif Backend.isDbms(DBMS.INFORMIX): + query = rootQuery.blind.query % (index, conf.db, conf.db, conf.db, conf.db, conf.db, tbl) + query += condQuery + field = condition query = agent.limitQuery(index, query, field, field) column = unArrayizeValue(inject.getValue(query, union=False, error=False)) diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 41b244147..9df7b351d 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -110,7 +110,10 @@ class Entries: kb.data.cachedColumns = foundData try: - kb.dumpTable = "%s.%s" % (conf.db, tbl) + if Backend.isDbms(DBMS.INFORMIX): + kb.dumpTable = "%s:%s" % (conf.db, tbl) + else: + kb.dumpTable = "%s.%s" % (conf.db, tbl) if not safeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \ or safeSQLIdentificatorNaming(tbl, True) not in \ @@ -236,6 +239,8 @@ class Entries: query = rootQuery.blind.count % ("%s.%s" % (conf.db, tbl)) elif Backend.isDbms(DBMS.MAXDB): query = rootQuery.blind.count % tbl + elif Backend.isDbms(DBMS.INFORMIX): + query = rootQuery.blind.count % (conf.db, tbl) else: query = rootQuery.blind.count % (conf.db, tbl) diff --git a/txt/checksum.md5 b/txt/checksum.md5 index daeecf9ee..116c8c9e2 100644 --- a/txt/checksum.md5 +++ b/txt/checksum.md5 @@ -45,7 +45,7 @@ e60456db5380840a586654344003d4e6 lib/core/readlineng.py 5ef56abb8671c2ca6ceecb208258e360 lib/core/replication.py 99a2b496b9d5b546b335653ca801153f lib/core/revision.py 7c15dd2777af4dac2c89cab6df17462e lib/core/session.py -603f6a62397f96fd9253146b4625473e lib/core/settings.py +f707e42739fd5451a37dabd559b26170 lib/core/settings.py 7af83e4f18cab6dff5e67840eb65be80 lib/core/shell.py 23657cd7d924e3c6d225719865855827 lib/core/subprocessng.py 0bc2fae1dec18cdd11954b22358293f2 lib/core/target.py @@ -144,6 +144,13 @@ c9d59b7c60aa0f0b23f920f932547e40 plugins/dbms/hsqldb/fingerprint.py d278ad5f1c13fea871ed1120942244d5 plugins/dbms/hsqldb/__init__.py d781720e15c23b662bae3098ed470756 plugins/dbms/hsqldb/syntax.py 2f957281cfe80396f73a3dccc0cb6d45 plugins/dbms/hsqldb/takeover.py +78917f19ea0750a665094d7dd7778d0c plugins/dbms/informix/connector.py +7c6b1ac474274d0edaef377d3aa49bc9 plugins/dbms/informix/enumeration.py +e8f0f28da98020dce27970a50e10a23b plugins/dbms/informix/filesystem.py +6644eea7451bc26dcff598b59c0fa000 plugins/dbms/informix/fingerprint.py +99a77ad7aa7ca4a4b5981f2fa0d9c616 plugins/dbms/informix/__init__.py +e96b4721cfc65271a2de948c47474aaa plugins/dbms/informix/syntax.py +5f130772d2295ae61140acba894eaceb plugins/dbms/informix/takeover.py cc9c82cfffd8ee9b25ba3af6284f057e plugins/dbms/__init__.py 4c8667e8af763ddf82ee314c6681d4e1 plugins/dbms/maxdb/connector.py 075fd66b8bbabed18aeb304c6c0ef2a2 plugins/dbms/maxdb/enumeration.py diff --git a/xml/queries.xml b/xml/queries.xml index 98b79cac7..88d0524b0 100644 --- a/xml/queries.xml +++ b/xml/queries.xml @@ -714,4 +714,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +