diff --git a/data/txt/keywords.txt b/data/txt/keywords.txt index 8113c553c..ec3e58c4a 100644 --- a/data/txt/keywords.txt +++ b/data/txt/keywords.txt @@ -259,6 +259,7 @@ YEAR ZONE # MySQL 5.0 keywords (reference: http://dev.mysql.com/doc/refman/5.0/en/reserved-words.html) + ADD ALL ALTER @@ -450,3 +451,752 @@ WITH WRITEXOR YEAR_MONTH ZEROFILL + +# PostgreSQL keywords (reference: https://www.postgresql.org/docs/9.3/sql-keywords-appendix.html) + +A +ABORT +ABS +ABSENT +ABSOLUTE +ACCESS +ACCORDING +ACTION +ADA +ADD +ADMIN +AFTER +AGGREGATE +ALL +ALLOCATE +ALSO +ALTER +ALWAYS +ANALYSE +ANALYZE +AND +ANY +ARE +ARRAY +ARRAY_AGG +ARRAY_MAX_CARDINALITY +AS +ASC +ASENSITIVE +ASSERTION +ASSIGNMENT +ASYMMETRIC +AT +ATOMIC +ATTRIBUTE +ATTRIBUTES +AUTHORIZATION +AVG +BACKWARD +BASE64 +BEFORE +BEGIN +BEGIN_FRAME +BEGIN_PARTITION +BERNOULLI +BETWEEN +BIGINT +BINARY +BIT +BIT_LENGTH +BLOB +BLOCKED +BOM +BOOLEAN +BOTH +BREADTH +BY +C +CACHE +CALL +CALLED +CARDINALITY +CASCADE +CASCADED +CASE +CAST +CATALOG +CATALOG_NAME +CEIL +CEILING +CHAIN +CHAR +CHARACTER +CHARACTERISTICS +CHARACTERS +CHARACTER_LENGTH +CHARACTER_SET_CATALOG +CHARACTER_SET_NAME +CHARACTER_SET_SCHEMA +CHAR_LENGTH +CHECK +CHECKPOINT +CLASS +CLASS_ORIGIN +CLOB +CLOSE +CLUSTER +COALESCE +COBOL +COLLATE +COLLATION +COLLATION_CATALOG +COLLATION_NAME +COLLATION_SCHEMA +COLLECT +COLUMN +COLUMNS +COLUMN_NAME +COMMAND_FUNCTION +COMMAND_FUNCTION_CODE +COMMENT +COMMENTS +COMMIT +COMMITTED +CONCURRENTLY +CONDITION +CONDITION_NUMBER +CONFIGURATION +CONNECT +CONNECTION +CONNECTION_NAME +CONSTRAINT +CONSTRAINTS +CONSTRAINT_CATALOG +CONSTRAINT_NAME +CONSTRAINT_SCHEMA +CONSTRUCTOR +CONTAINS +CONTENT +CONTINUE +CONTROL +CONVERSION +CONVERT +COPY +CORR +CORRESPONDING +COST +COUNT +COVAR_POP +COVAR_SAMP +CREATE +CROSS +CSV +CUBE +CUME_DIST +CURRENT +CURRENT_CATALOG +CURRENT_DATE +CURRENT_DEFAULT_TRANSFORM_GROUP +CURRENT_PATH +CURRENT_ROLE +CURRENT_ROW +CURRENT_SCHEMA +CURRENT_TIME +CURRENT_TIMESTAMP +CURRENT_TRANSFORM_GROUP_FOR_TYPE +CURRENT_USER +CURSOR +CURSOR_NAME +CYCLE +DATA +DATABASE +DATALINK +DATE +DATETIME_INTERVAL_CODE +DATETIME_INTERVAL_PRECISION +DAY +DB +DEALLOCATE +DEC +DECIMAL +DECLARE +DEFAULT +DEFAULTS +DEFERRABLE +DEFERRED +DEFINED +DEFINER +DEGREE +DELETE +DELIMITER +DELIMITERS +DENSE_RANK +DEPTH +DEREF +DERIVED +DESC +DESCRIBE +DESCRIPTOR +DETERMINISTIC +DIAGNOSTICS +DICTIONARY +DISABLE +DISCARD +DISCONNECT +DISPATCH +DISTINCT +DLNEWCOPY +DLPREVIOUSCOPY +DLURLCOMPLETE +DLURLCOMPLETEONLY +DLURLCOMPLETEWRITE +DLURLPATH +DLURLPATHONLY +DLURLPATHWRITE +DLURLSCHEME +DLURLSERVER +DLVALUE +DO +DOCUMENT +DOMAIN +DOUBLE +DROP +DYNAMIC +DYNAMIC_FUNCTION +DYNAMIC_FUNCTION_CODE +EACH +ELEMENT +ELSE +EMPTY +ENABLE +ENCODING +ENCRYPTED +END +END-EXEC +END_FRAME +END_PARTITION +ENFORCED +ENUM +EQUALS +ESCAPE +EVENT +EVERY +EXCEPT +EXCEPTION +EXCLUDE +EXCLUDING +EXCLUSIVE +EXEC +EXECUTE +EXISTS +EXP +EXPLAIN +EXPRESSION +EXTENSION +EXTERNAL +EXTRACT +FALSE +FAMILY +FETCH +FILE +FILTER +FINAL +FIRST +FIRST_VALUE +FLAG +FLOAT +FLOOR +FOLLOWING +FOR +FORCE +FOREIGN +FORTRAN +FORWARD +FOUND +FRAME_ROW +FREE +FREEZE +FROM +FS +FULL +FUNCTION +FUNCTIONS +FUSION +G +GENERAL +GENERATED +GET +GLOBAL +GO +GOTO +GRANT +GRANTED +GREATEST +GROUP +GROUPING +GROUPS +HANDLER +HAVING +HEADER +HEX +HIERARCHY +HOLD +HOUR +ID +IDENTITY +IF +IGNORE +ILIKE +IMMEDIATE +IMMEDIATELY +IMMUTABLE +IMPLEMENTATION +IMPLICIT +IMPORT +IN +INCLUDING +INCREMENT +INDENT +INDEX +INDEXES +INDICATOR +INHERIT +INHERITS +INITIALLY +INLINE +INNER +INOUT +INPUT +INSENSITIVE +INSERT +INSTANCE +INSTANTIABLE +INSTEAD +INT +INTEGER +INTEGRITY +INTERSECT +INTERSECTION +INTERVAL +INTO +INVOKER +IS +ISNULL +ISOLATION +JOIN +K +KEY +KEY_MEMBER +KEY_TYPE +LABEL +LAG +LANGUAGE +LARGE +LAST +LAST_VALUE +LATERAL +LC_COLLATE +LC_CTYPE +LEAD +LEADING +LEAKPROOF +LEAST +LEFT +LENGTH +LEVEL +LIBRARY +LIKE +LIKE_REGEX +LIMIT +LINK +LISTEN +LN +LOAD +LOCAL +LOCALTIME +LOCALTIMESTAMP +LOCATION +LOCATOR +LOCK +LOWER +M +MAP +MAPPING +MATCH +MATCHED +MATERIALIZED +MAX +MAXVALUE +MAX_CARDINALITY +MEMBER +MERGE +MESSAGE_LENGTH +MESSAGE_OCTET_LENGTH +MESSAGE_TEXT +METHOD +MIN +MINUTE +MINVALUE +MOD +MODE +MODIFIES +MODULE +MONTH +MORE +MOVE +MULTISET +MUMPS +NAME +NAMES +NAMESPACE +NATIONAL +NATURAL +NCHAR +NCLOB +NESTING +NEW +NEXT +NFC +NFD +NFKC +NFKD +NIL +NO +NONE +NORMALIZE +NORMALIZED +NOT +NOTHING +NOTIFY +NOTNULL +NOWAIT +NTH_VALUE +NTILE +NULL +NULLABLE +NULLIF +NULLS +NUMBER +NUMERIC +OBJECT +OCCURRENCES_REGEX +OCTETS +OCTET_LENGTH +OF +OFF +OFFSET +OIDS +OLD +ON +ONLY +OPEN +OPERATOR +OPTION +OPTIONS +OR +ORDER +ORDERING +ORDINALITY +OTHERS +OUT +OUTER +OUTPUT +OVER +OVERLAPS +OVERLAY +OVERRIDING +OWNED +OWNER +P +PAD +PARAMETER +PARAMETER_MODE +PARAMETER_NAME +PARAMETER_ORDINAL_POSITION +PARAMETER_SPECIFIC_CATALOG +PARAMETER_SPECIFIC_NAME +PARAMETER_SPECIFIC_SCHEMA +PARSER +PARTIAL +PARTITION +PASCAL +PASSING +PASSTHROUGH +PASSWORD +PATH +PERCENT +PERCENTILE_CONT +PERCENTILE_DISC +PERCENT_RANK +PERIOD +PERMISSION +PLACING +PLANS +PLI +PORTION +POSITION +POSITION_REGEX +POWER +PRECEDES +PRECEDING +PRECISION +PREPARE +PREPARED +PRESERVE +PRIMARY +PRIOR +PRIVILEGES +PROCEDURAL +PROCEDURE +PROGRAM +PUBLIC +QUOTE +RANGE +RANK +READ +READS +REAL +REASSIGN +RECHECK +RECOVERY +RECURSIVE +REF +REFERENCES +REFERENCING +REFRESH +REGR_AVGX +REGR_AVGY +REGR_COUNT +REGR_INTERCEPT +REGR_R2 +REGR_SLOPE +REGR_SXX +REGR_SXY +REGR_SYY +REINDEX +RELATIVE +RELEASE +RENAME +REPEATABLE +REPLACE +REPLICA +REQUIRING +RESET +RESPECT +RESTART +RESTORE +RESTRICT +RESULT +RETURN +RETURNED_CARDINALITY +RETURNED_LENGTH +RETURNED_OCTET_LENGTH +RETURNED_SQLSTATE +RETURNING +RETURNS +REVOKE +RIGHT +ROLE +ROLLBACK +ROLLUP +ROUTINE +ROUTINE_CATALOG +ROUTINE_NAME +ROUTINE_SCHEMA +ROW +ROWS +ROW_COUNT +ROW_NUMBER +RULE +SAVEPOINT +SCALE +SCHEMA +SCHEMA_NAME +SCOPE +SCOPE_CATALOG +SCOPE_NAME +SCOPE_SCHEMA +SCROLL +SEARCH +SECOND +SECTION +SECURITY +SELECT +SELECTIVE +SELF +SENSITIVE +SEQUENCE +SEQUENCES +SERIALIZABLE +SERVER +SERVER_NAME +SESSION +SESSION_USER +SET +SETOF +SETS +SHARE +SHOW +SIMILAR +SIMPLE +SIZE +SMALLINT +SNAPSHOT +SOME +SOURCE +SPACE +SPECIFIC +SPECIFICTYPE +SPECIFIC_NAME +SQL +SQLCODE +SQLERROR +SQLEXCEPTION +SQLSTATE +SQLWARNING +SQRT +STABLE +STANDALONE +START +STATE +STATEMENT +STATIC +STATISTICS +STDDEV_POP +STDDEV_SAMP +STDIN +STDOUT +STORAGE +STRICT +STRIP +STRUCTURE +STYLE +SUBCLASS_ORIGIN +SUBMULTISET +SUBSTRING +SUBSTRING_REGEX +SUCCEEDS +SUM +SYMMETRIC +SYSID +SYSTEM +SYSTEM_TIME +SYSTEM_USER +T +TABLE +TABLES +TABLESAMPLE +TABLESPACE +TABLE_NAME +TEMP +TEMPLATE +TEMPORARY +TEXT +THEN +TIES +TIME +TIMESTAMP +TIMEZONE_HOUR +TIMEZONE_MINUTE +TO +TOKEN +TOP_LEVEL_COUNT +TRAILING +TRANSACTION +TRANSACTIONS_COMMITTED +TRANSACTIONS_ROLLED_BACK +TRANSACTION_ACTIVE +TRANSFORM +TRANSFORMS +TRANSLATE +TRANSLATE_REGEX +TRANSLATION +TREAT +TRIGGER +TRIGGER_CATALOG +TRIGGER_NAME +TRIGGER_SCHEMA +TRIM +TRIM_ARRAY +TRUE +TRUNCATE +TRUSTED +TYPE +TYPES +UESCAPE +UNBOUNDED +UNCOMMITTED +UNDER +UNENCRYPTED +UNION +UNIQUE +UNKNOWN +UNLINK +UNLISTEN +UNLOGGED +UNNAMED +UNNEST +UNTIL +UNTYPED +UPDATE +UPPER +URI +USAGE +USER +USER_DEFINED_TYPE_CATALOG +USER_DEFINED_TYPE_CODE +USER_DEFINED_TYPE_NAME +USER_DEFINED_TYPE_SCHEMA +USING +VACUUM +VALID +VALIDATE +VALIDATOR +VALUE +VALUES +VALUE_OF +VARBINARY +VARCHAR +VARIADIC +VARYING +VAR_POP +VAR_SAMP +VERBOSE +VERSION +VERSIONING +VIEW +VOLATILE +WHEN +WHENEVER +WHERE +WHITESPACE +WIDTH_BUCKET +WINDOW +WITH +WITHIN +WITHOUT +WORK +WRAPPER +WRITE +XML +XMLAGG +XMLATTRIBUTES +XMLBINARY +XMLCAST +XMLCOMMENT +XMLCONCAT +XMLDECLARATION +XMLDOCUMENT +XMLELEMENT +XMLEXISTS +XMLFOREST +XMLITERATE +XMLNAMESPACES +XMLPARSE +XMLPI +XMLQUERY +XMLROOT +XMLSCHEMA +XMLSERIALIZE +XMLTABLE +XMLTEXT +XMLVALIDATE +YEAR +YES +ZONE diff --git a/data/xml/queries.xml b/data/xml/queries.xml index be1f273d5..13ae91c23 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1560,4 +1560,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/controller/handler.py b/lib/controller/handler.py index 9ff195075..810769048 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -20,6 +20,7 @@ from lib.core.settings import DB2_ALIASES from lib.core.settings import DERBY_ALIASES from lib.core.settings import EXTREMEDB_ALIASES from lib.core.settings import FIREBIRD_ALIASES +from lib.core.settings import FRONTBASE_ALIASES from lib.core.settings import H2_ALIASES from lib.core.settings import HSQLDB_ALIASES from lib.core.settings import INFORMIX_ALIASES @@ -55,6 +56,8 @@ from plugins.dbms.extremedb.connector import Connector as ExtremeDBConn from plugins.dbms.extremedb import ExtremeDBMap from plugins.dbms.firebird.connector import Connector as FirebirdConn from plugins.dbms.firebird import FirebirdMap +from plugins.dbms.frontbase.connector import Connector as FrontBaseConn +from plugins.dbms.frontbase import FrontBaseMap from plugins.dbms.h2.connector import Connector as H2Conn from plugins.dbms.h2 import H2Map from plugins.dbms.hsqldb.connector import Connector as HSQLDBConn @@ -117,6 +120,7 @@ def setHandler(): (DBMS.CUBRID, CUBRID_ALIASES, CubridMap, CubridConn), (DBMS.CACHE, CACHE_ALIASES, CacheMap, CacheConn), (DBMS.EXTREMEDB, EXTREMEDB_ALIASES, ExtremeDBMap, ExtremeDBConn), + (DBMS.FRONTBASE, FRONTBASE_ALIASES, FrontBaseMap, FrontBaseConn), ] _ = 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 cbed9c624..b1492ef1f 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -535,7 +535,7 @@ class Agent(object): """ prefixRegex = r"(?:\s+(?:FIRST|SKIP|LIMIT(?: \d+)?)\s+\d+)*" - fieldsSelectTop = re.search(r"\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", query, re.I) + 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) fieldsSelectCase = re.search(r"\ASELECT%s\s+(\(CASE WHEN\s+.+\s+END\))" % prefixRegex, query, re.I) @@ -560,7 +560,7 @@ class Agent(object): if fieldsSelect: fieldsToCastStr = fieldsSelect.group(1) elif fieldsSelectTop: - fieldsToCastStr = fieldsSelectTop.group(1) + fieldsToCastStr = fieldsSelectTop.group(2) elif fieldsSelectRownum: fieldsToCastStr = fieldsSelectRownum.group(1) elif fieldsSelectDistinct: @@ -660,7 +660,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, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.ALTIBASE, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.EXTREMEDB): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2, DBMS.FIREBIRD, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.ALTIBASE, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE): if fieldsExists: concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1) concatenatedQuery += "||'%s'" % kb.chars.stop @@ -983,6 +983,11 @@ class Agent(object): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (1, num) limitedQuery += " %s" % limitStr + elif Backend.getIdentifiedDbms() in (DBMS.FRONTBASE,): + limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) + if query.startswith("SELECT "): + limitedQuery = query.replace("SELECT ", "SELECT %s " % limitStr, 1) + 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] diff --git a/lib/core/common.py b/lib/core/common.py index a18fbd0ff..458ce5fa4 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2821,6 +2821,10 @@ def urlencode(value, safe="%&=-_", convall=False, limit=False, spaceplus=False): >>> urlencode('AND 1>(2+3)#') 'AND%201%3E%282%2B3%29%23' + >>> urlencode('AND COUNT(SELECT name FROM users WHERE name LIKE \\'%DBA%\\')>0') + 'AND%20COUNT%28SELECT%20name%20FROM%20users%20WHERE%20name%20LIKE%20%27%25DBA%25%27%29%3E0' + >>> urlencode('AND COUNT(SELECT name FROM users WHERE name LIKE \\'%_SYSTEM%\\')>0') + 'AND%20COUNT%28SELECT%20name%20FROM%20users%20WHERE%20name%20LIKE%20%27%25_SYSTEM%25%27%29%3E0' """ if conf.get("direct"): @@ -2843,8 +2847,8 @@ def urlencode(value, safe="%&=-_", convall=False, limit=False, spaceplus=False): # encoded (when not representing URL encoded char) # except in cases when tampering scripts are used if all('%' in _ for _ in (safe, value)) and not kb.tamperFunctions: - value = re.sub(r"%(?![0-9a-fA-F]{2})", "%25", value) value = re.sub(r"(?<= ')%", "%25", value) # e.g. LIKE '%DBA%' + value = re.sub(r"%(?![0-9a-fA-F]{2})", "%25", value) while True: result = _urllib.parse.quote(getBytes(value), safe) @@ -4086,12 +4090,13 @@ def safeSQLIdentificatorNaming(name, isTable=False): if _: retVal = re.sub(r"(?i)\A\[?%s\]?\." % DEFAULT_MSSQL_SCHEMA, "%s." % DEFAULT_MSSQL_SCHEMA, retVal) - if retVal.upper() in kb.keywords or (retVal or " ")[0].isdigit() or not re.match(r"\A[A-Za-z0-9_@%s\$]+\Z" % ('.' if _ else ""), retVal): # MsSQL is the only DBMS where we automatically prepend schema to table name (dot is normal) + # Note: SQL 92 has restrictions for identifiers starting with underscore (e.g. http://www.frontbase.com/documentation/FBUsers_4.pdf) + if retVal.upper() in kb.keywords or (not isTable and (retVal or " ")[0] == '_') or (retVal or " ")[0].isdigit() or not re.match(r"\A[A-Za-z0-9_@%s\$]+\Z" % ('.' if _ else ""), retVal): # MsSQL is the only DBMS where we automatically prepend schema to table name (dot is normal) retVal = unsafeSQLIdentificatorNaming(retVal) if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, 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, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE): retVal = "\"%s\"" % retVal elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): retVal = "\"%s\"" % retVal.upper() @@ -4129,7 +4134,7 @@ def unsafeSQLIdentificatorNaming(name): if isinstance(name, six.string_types): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE): retVal = name.replace("`", "") - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE): retVal = name.replace("\"", "") elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): retVal = name.replace("\"", "").upper() diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 05386c9e1..5804140b3 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -19,6 +19,7 @@ from lib.core.settings import DB2_ALIASES from lib.core.settings import DERBY_ALIASES from lib.core.settings import EXTREMEDB_ALIASES from lib.core.settings import FIREBIRD_ALIASES +from lib.core.settings import FRONTBASE_ALIASES from lib.core.settings import H2_ALIASES from lib.core.settings import HSQLDB_ALIASES from lib.core.settings import INFORMIX_ALIASES @@ -242,6 +243,7 @@ DBMS_DICT = { DBMS.CUBRID: (CUBRID_ALIASES, "CUBRID-Python", "https://github.com/CUBRID/cubrid-python", None), DBMS.CACHE: (CACHE_ALIASES, "python jaydebeapi & python-jpype", "https://pypi.python.org/pypi/JayDeBeApi/ & http://jpype.sourceforge.net/", None), DBMS.EXTREMEDB: (EXTREMEDB_ALIASES, None, None, None), + DBMS.FRONTBASE: (FRONTBASE_ALIASES, None, None, None), } # Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/ @@ -255,6 +257,7 @@ FROM_DUMMY_TABLE = { DBMS.INFORMIX: " FROM SYSMASTER:SYSDUAL", DBMS.DERBY: " FROM SYSIBM.SYSDUMMY1", DBMS.MIMERSQL: " FROM SYSTEM.ONEROW", + DBMS.FRONTBASE: " FROM INFORMATION_SCHEMA.IO_STATISTICS" } HEURISTIC_NULL_EVAL = { diff --git a/lib/core/dump.py b/lib/core/dump.py index ebc36a384..925bc5914 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -164,7 +164,7 @@ class Dump(object): self.string("current user", data, content_type=CONTENT_TYPE.CURRENT_USER) def currentDb(self, data): - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.CRATEDB, DBMS.CACHE): + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE): self.string("current database (equivalent to schema on %s)" % Backend.getIdentifiedDbms(), data, content_type=CONTENT_TYPE.CURRENT_DB) elif Backend.getIdentifiedDbms() in (DBMS.ALTIBASE, DBMS.DB2, DBMS.MIMERSQL, DBMS.MAXDB): self.string("current database (equivalent to owner on %s)" % Backend.getIdentifiedDbms(), data, content_type=CONTENT_TYPE.CURRENT_DB) diff --git a/lib/core/enums.py b/lib/core/enums.py index c0f99c3fc..36784c999 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -56,6 +56,7 @@ class DBMS(object): CUBRID = "Cubrid" CACHE = "InterSystems Cache" EXTREMEDB = "eXtremeDB" + FRONTBASE = "FrontBase" class DBMS_DIRECTORY_NAME(object): ACCESS = "access" @@ -82,6 +83,7 @@ class DBMS_DIRECTORY_NAME(object): CUBRID = "cubrid" CACHE = "cache" EXTREMEDB = "extremedb" + FRONTBASE = "frontbase" class FORK(object): MARIADB = "MariaDB" @@ -426,3 +428,8 @@ class TIMEOUT_STATE(object): class HINT(object): PREPEND = 0 APPEND = 1 + +class FUZZ_UNION_COLUMN: + STRING = "" + INTEGER = "" + NULL = "NULL" diff --git a/lib/core/option.py b/lib/core/option.py index 524945510..b0f676959 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1920,6 +1920,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.forceWhere = None kb.forkNote = None kb.futileUnion = None + kb.fuzzUnionTest = None kb.heavilyDynamic = False kb.headersFile = None kb.headersFp = {} @@ -2019,6 +2020,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.uChar = NULL kb.udfFail = False kb.unionDuplicates = False + kb.unionTemplate = None kb.webSocketRecvCount = None kb.wizardMode = False kb.xpCmdshellAvailable = False diff --git a/lib/core/settings.py b/lib/core/settings.py index 7cb2388da..95915484a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -93,6 +93,12 @@ PERMISSION_DENIED_REGEX = r"(?P(command|permission|access)\s*(was|is)?\s # Regular expression used in recognition of generic protection mechanisms GENERIC_PROTECTION_REGEX = r"(?i)\b(rejected|blocked|protection|incident|denied|detected|dangerous|firewall)\b" +# Regular expression used to detect errors in fuzz(y) UNION test +FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|comparable|compatible|conversion|converting|failed|error" + +# Upper threshold for starting the fuzz(y) UNION test +FUZZ_UNION_MAX_COLUMNS = 10 + # Regular expression used for recognition of generic maximum connection messages MAX_CONNECTIONS_REGEX = r"\bmax.+?\bconnection" @@ -270,6 +276,7 @@ CRATEDB_SYSTEM_DBS = ("information_schema", "pg_catalog", "sys") CUBRID_SYSTEM_DBS = ("DBA",) CACHE_SYSTEM_DBS = ("%Dictionary", "INFORMATION_SCHEMA", "%SYS") EXTREMEDB_SYSTEM_DBS = ("",) +FRONTBASE_SYSTEM_DBS = ("DEFINITION_SCHEMA", "INFORMATION_SCHEMA") # Note: () + () MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms") @@ -296,13 +303,14 @@ CRATEDB_ALIASES = ("cratedb", "crate") CUBRID_ALIASES = ("cubrid",) CACHE_ALIASES = ("cachedb", "cache") EXTREMEDB_ALIASES = ("extremedb", "extreme") +FRONTBASE_ALIASES = ("frontbase",) 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 + MONETDB_ALIASES + DERBY_ALIASES + VERTICA_ALIASES + MCKOI_ALIASES + PRESTO_ALIASES + ALTIBASE_ALIASES + MIMERSQL_ALIASES + CRATEDB_ALIASES + CUBRID_ALIASES + CACHE_ALIASES + EXTREMEDB_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.MONETDB, MONETDB_ALIASES), (DBMS.DERBY, DERBY_ALIASES), (DBMS.VERTICA, VERTICA_ALIASES), (DBMS.MCKOI, MCKOI_ALIASES), (DBMS.PRESTO, PRESTO_ALIASES), (DBMS.ALTIBASE, ALTIBASE_ALIASES), (DBMS.MIMERSQL, MIMERSQL_ALIASES), (DBMS.CRATEDB, CRATEDB_ALIASES), (DBMS.CUBRID, CUBRID_ALIASES), (DBMS.CACHE, CACHE_ALIASES), (DBMS.EXTREMEDB, EXTREMEDB_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), (DBMS.DERBY, DERBY_ALIASES), (DBMS.VERTICA, VERTICA_ALIASES), (DBMS.MCKOI, MCKOI_ALIASES), (DBMS.PRESTO, PRESTO_ALIASES), (DBMS.ALTIBASE, ALTIBASE_ALIASES), (DBMS.MIMERSQL, MIMERSQL_ALIASES), (DBMS.CRATEDB, CRATEDB_ALIASES), (DBMS.CUBRID, CUBRID_ALIASES), (DBMS.CACHE, CACHE_ALIASES), (DBMS.EXTREMEDB, EXTREMEDB_ALIASES), (DBMS.FRONTBASE, FRONTBASE_ALIASES)) USER_AGENT_ALIASES = ("ua", "useragent", "user-agent") REFERER_ALIASES = ("ref", "referer", "referrer") diff --git a/lib/core/unescaper.py b/lib/core/unescaper.py index 5d26f7a48..a3ad35b6d 100644 --- a/lib/core/unescaper.py +++ b/lib/core/unescaper.py @@ -22,7 +22,7 @@ class Unescaper(AttribDict): if dbms is not None: retVal = self[dbms](expression, quote=quote) - elif identifiedDbms is not None: + elif identifiedDbms is not None and identifiedDbms in self: retVal = self[identifiedDbms](expression, quote=quote) else: retVal = expression diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 7219814b4..96b74a297 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -116,6 +116,16 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None payload = payload.replace(right, "(SELECT %s FROM %s)" % (right, match.group(2).strip())) expression = match.group(1).strip() + elif Backend.isDbms(DBMS.FRONTBASE): + match = re.search(r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z", expression, re.I) + if match: + payload = payload.replace(INFERENCE_GREATER_CHAR, " FROM %s)%s" % (match.group(3).strip(), INFERENCE_GREATER_CHAR)) + payload = payload.replace("SUBSTRING", "(SELECT%sSUBSTRING" % (match.group(1) if match.group(1) else " "), 1) + expression = match.group(2).strip() + + +# + try: # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API if conf.predictOutput: @@ -203,7 +213,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None hintValue = kb.hintValue if payload is not None and len(hintValue or "") > 0 and len(hintValue) >= idx: - if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.MAXDB, DBMS.DB2): + if "'%s'" % CHAR_INFERENCE_MARK in payload: posValue = hintValue[idx - 1] else: posValue = ord(hintValue[idx - 1]) @@ -649,8 +659,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None elif (conf.verbose in (1, 2) and not kb.bruteMode) or conf.api: dataToStdout(filterControlChars(val)) - # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces - if Backend.getIdentifiedDbms() in (DBMS.FIREBIRD, DBMS.DB2, DBMS.MAXDB, DBMS.DERBY) and len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace(): + # Note: some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces + if Backend.getIdentifiedDbms() in (DBMS.FIREBIRD, DBMS.DB2, DBMS.MAXDB, DBMS.DERBY, DBMS.FRONTBASE) and len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[-INFERENCE_BLANK_BREAK:].isspace(): finalValue = partialValue[:-INFERENCE_BLANK_BREAK] break elif charsetType and partialValue[-1:].isspace(): diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index 8e4d25c58..65a76506c 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -5,6 +5,7 @@ Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ +import itertools import logging import random import re @@ -12,6 +13,7 @@ import re from lib.core.agent import agent from lib.core.common import average from lib.core.common import Backend +from lib.core.common import getPublicTypeMembers from lib.core.common import isNullValue from lib.core.common import listToStrValue from lib.core.common import popValue @@ -29,9 +31,13 @@ from lib.core.compat import xrange from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.data import queries from lib.core.decorators import stackedmethod from lib.core.dicts import FROM_DUMMY_TABLE +from lib.core.enums import FUZZ_UNION_COLUMN from lib.core.enums import PAYLOAD +from lib.core.settings import FUZZ_UNION_ERROR_REGEX +from lib.core.settings import FUZZ_UNION_MAX_COLUMNS from lib.core.settings import LIMITED_ROWS_TEST_NUMBER from lib.core.settings import MAX_RATIO from lib.core.settings import MIN_RATIO @@ -171,6 +177,36 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where= return retVal +def _fuzzUnionCols(place, parameter, prefix, suffix): + retVal = None + + if Backend.getIdentifiedDbms() and not re.search(FUZZ_UNION_ERROR_REGEX, kb.pageTemplate or "") and kb.orderByColumns: + comment = queries[Backend.getIdentifiedDbms()].comment.query + + choices = getPublicTypeMembers(FUZZ_UNION_COLUMN, True) + random.shuffle(choices) + + for candidate in itertools.product(choices, repeat=kb.orderByColumns): + if retVal: + break + elif FUZZ_UNION_COLUMN.STRING not in candidate: + continue + else: + candidate = [_.replace(FUZZ_UNION_COLUMN.INTEGER, str(randomInt())).replace(FUZZ_UNION_COLUMN.STRING, "'%s'" % randomStr(20)) for _ in candidate] + + query = agent.prefixQuery("UNION ALL SELECT %s%s" % (','.join(candidate), FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")), prefix=prefix) + query = agent.suffixQuery(query, suffix=suffix, comment=comment) + payload = agent.payload(newValue=query, place=place, parameter=parameter, where=PAYLOAD.WHERE.NEGATIVE) + page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False) + + if not re.search(FUZZ_UNION_ERROR_REGEX, page or ""): + for column in candidate: + if column.startswith("'") and column.strip("'") in (page or ""): + retVal = [(_ if _ != column else "%s") for _ in candidate] + break + + return retVal + def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL): validPayload = None vector = None @@ -205,7 +241,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO if content and phrase in content: validPayload = payload kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1 - vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, conf.forcePartial) + vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, conf.forcePartial, kb.tableFrom, kb.unionTemplate) if where == PAYLOAD.WHERE.ORIGINAL: # Prepare expression with delimiters @@ -223,7 +259,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO content = ("%s%s" % (page or "", listToStrValue(headers.headers if headers else None) or "")).lower() if not all(_ in content for _ in (phrase, phrase2)): - vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True) + vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True, kb.tableFrom, kb.unionTemplate) elif not kb.unionDuplicates: fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr()) @@ -237,7 +273,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER: warnMsg = "output with limited number of rows detected. Switching to partial mode" logger.warn(warnMsg) - vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True) + vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True, kb.tableFrom, kb.unionTemplate) unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError() @@ -277,17 +313,27 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) vector = None orderBy = kb.orderByColumns uChars = (conf.uChar, kb.uChar) + where = PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE # In case that user explicitly stated number of columns affected if conf.uColsStop == conf.uColsStart: count = conf.uColsStart else: - count = _findUnionCharCount(comment, place, parameter, value, prefix, suffix, PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE) + count = _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where) if count: validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) - if not all((validPayload, vector)) and not all((conf.uChar, conf.dbms)): + if not all((validPayload, vector)) and not all((conf.uChar, conf.dbms, kb.unionTemplate)): + if Backend.getIdentifiedDbms() and kb.orderByColumns and kb.orderByColumns < FUZZ_UNION_MAX_COLUMNS: + if kb.fuzzUnionTest is None: + msg = "do you want to (re)try to find proper " + msg += "UNION column types with fuzzy test? [y/N] " + + kb.fuzzUnionTest = readInput(msg, default='N', boolean=True) + if kb.fuzzUnionTest: + kb.unionTemplate = _fuzzUnionCols(place, parameter, prefix, suffix) + warnMsg = "if UNION based SQL injection is not detected, " warnMsg += "please consider " diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index 64ef7b96a..973c42cfd 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -78,6 +78,14 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) kb.unionDuplicates = vector[7] kb.forcePartialUnion = vector[8] + + # Note: introduced columns in 1.4.2.42#dev + try: + kb.tableFrom = vector[9] + kb.unionTemplate = vector[10] + except IndexError: + pass + query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6] else: diff --git a/plugins/dbms/access/enumeration.py b/plugins/dbms/access/enumeration.py index cc691205b..5b4ffd922 100644 --- a/plugins/dbms/access/enumeration.py +++ b/plugins/dbms/access/enumeration.py @@ -10,7 +10,7 @@ from plugins.generic.enumeration import Enumeration as GenericEnumeration class Enumeration(GenericEnumeration): def getBanner(self): - warnMsg = "on Microsoft Access it is not possible to get a banner" + warnMsg = "on Microsoft Access it is not possible to get the banner" logger.warn(warnMsg) return None diff --git a/plugins/dbms/extremedb/enumeration.py b/plugins/dbms/extremedb/enumeration.py index f507af2c5..fcf53c961 100644 --- a/plugins/dbms/extremedb/enumeration.py +++ b/plugins/dbms/extremedb/enumeration.py @@ -10,7 +10,7 @@ from plugins.generic.enumeration import Enumeration as GenericEnumeration class Enumeration(GenericEnumeration): def getBanner(self): - warnMsg = "on eXtremeDB it is not possible to get a banner" + warnMsg = "on eXtremeDB it is not possible to get the banner" logger.warn(warnMsg) return None diff --git a/plugins/dbms/frontbase/__init__.py b/plugins/dbms/frontbase/__init__.py new file mode 100644 index 000000000..ddf00d893 --- /dev/null +++ b/plugins/dbms/frontbase/__init__.py @@ -0,0 +1,29 @@ +#!/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 FRONTBASE_SYSTEM_DBS +from lib.core.unescaper import unescaper +from plugins.dbms.frontbase.enumeration import Enumeration +from plugins.dbms.frontbase.filesystem import Filesystem +from plugins.dbms.frontbase.fingerprint import Fingerprint +from plugins.dbms.frontbase.syntax import Syntax +from plugins.dbms.frontbase.takeover import Takeover +from plugins.generic.misc import Miscellaneous + +class FrontBaseMap(Syntax, Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover): + """ + This class defines FrontBase methods + """ + + def __init__(self): + self.excludeDbsList = FRONTBASE_SYSTEM_DBS + + for cls in self.__class__.__bases__: + cls.__init__(self) + + unescaper[DBMS.FRONTBASE] = Syntax.escape diff --git a/plugins/dbms/frontbase/connector.py b/plugins/dbms/frontbase/connector.py new file mode 100644 index 000000000..35ce94513 --- /dev/null +++ b/plugins/dbms/frontbase/connector.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 lib.core.exception import SqlmapUnsupportedFeatureException +from plugins.generic.connector import Connector as GenericConnector + +class Connector(GenericConnector): + def connect(self): + errMsg = "on FrontBase it is not (currently) possible to establish a " + errMsg += "direct connection" + raise SqlmapUnsupportedFeatureException(errMsg) diff --git a/plugins/dbms/frontbase/enumeration.py b/plugins/dbms/frontbase/enumeration.py new file mode 100644 index 000000000..cbe00e571 --- /dev/null +++ b/plugins/dbms/frontbase/enumeration.py @@ -0,0 +1,32 @@ +#!/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 getBanner(self): + warnMsg = "on FrontBase it is not possible to get the banner" + logger.warn(warnMsg) + + return None + + def getPrivileges(self, *args, **kwargs): + warnMsg = "on FrontBase it is not possible to enumerate the user privileges" + logger.warn(warnMsg) + + return {} + + def getHostname(self): + warnMsg = "on FrontBase it is not possible to enumerate the hostname" + logger.warn(warnMsg) + + def getStatements(self): + warnMsg = "on FrontBase it is not possible to enumerate the SQL statements" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/frontbase/filesystem.py b/plugins/dbms/frontbase/filesystem.py new file mode 100644 index 000000000..d337945d4 --- /dev/null +++ b/plugins/dbms/frontbase/filesystem.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from lib.core.exception import SqlmapUnsupportedFeatureException +from plugins.generic.filesystem import Filesystem as GenericFilesystem + +class Filesystem(GenericFilesystem): + def readFile(self, remoteFile): + errMsg = "on FrontBase it is not possible to read files" + raise SqlmapUnsupportedFeatureException(errMsg) + + def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): + errMsg = "on FrontBase it is not possible to write files" + raise SqlmapUnsupportedFeatureException(errMsg) diff --git a/plugins/dbms/frontbase/fingerprint.py b/plugins/dbms/frontbase/fingerprint.py new file mode 100644 index 000000000..b4c6c2792 --- /dev/null +++ b/plugins/dbms/frontbase/fingerprint.py @@ -0,0 +1,89 @@ +#!/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 FRONTBASE_ALIASES +from lib.request import inject +from plugins.generic.fingerprint import Fingerprint as GenericFingerprint + +class Fingerprint(GenericFingerprint): + def __init__(self): + GenericFingerprint.__init__(self, DBMS.FRONTBASE) + + 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.FRONTBASE + 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(FRONTBASE_ALIASES): + setDbms(DBMS.FRONTBASE) + return True + + infoMsg = "testing %s" % DBMS.FRONTBASE + logger.info(infoMsg) + + result = inject.checkBooleanExpression("(SELECT degradedTransactions FROM INFORMATION_SCHEMA.IO_STATISTICS)>=0") + + if result: + infoMsg = "confirming %s" % DBMS.FRONTBASE + logger.info(infoMsg) + + result = inject.checkBooleanExpression("(SELECT TOP (0,1) file_version FROM INFORMATION_SCHEMA.FRAGMENTATION)>=0") + + if not result: + warnMsg = "the back-end DBMS is not %s" % DBMS.FRONTBASE + logger.warn(warnMsg) + + return False + + setDbms(DBMS.FRONTBASE) + + return True + else: + warnMsg = "the back-end DBMS is not %s" % DBMS.FRONTBASE + logger.warn(warnMsg) + + return False diff --git a/plugins/dbms/frontbase/syntax.py b/plugins/dbms/frontbase/syntax.py new file mode 100644 index 000000000..dc6c66174 --- /dev/null +++ b/plugins/dbms/frontbase/syntax.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from plugins.generic.syntax import Syntax as GenericSyntax + +class Syntax(GenericSyntax): + @staticmethod + def escape(expression, quote=True): + """ + >>> Syntax.escape("SELECT 'abcdefgh' FROM foobar") == u"SELECT 'abcdefgh' FROM foobar" + True + """ + + return expression diff --git a/plugins/dbms/frontbase/takeover.py b/plugins/dbms/frontbase/takeover.py new file mode 100644 index 000000000..3ff1bfbde --- /dev/null +++ b/plugins/dbms/frontbase/takeover.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +from lib.core.exception import SqlmapUnsupportedFeatureException +from plugins.generic.takeover import Takeover as GenericTakeover + +class Takeover(GenericTakeover): + def osCmd(self): + errMsg = "on FrontBase it is not possible to execute commands" + raise SqlmapUnsupportedFeatureException(errMsg) + + def osShell(self): + errMsg = "on FrontBase it is not possible to execute commands" + raise SqlmapUnsupportedFeatureException(errMsg) + + def osPwn(self): + errMsg = "on FrontBase it is not possible to establish an " + errMsg += "out-of-band connection" + raise SqlmapUnsupportedFeatureException(errMsg) + + def osSmb(self): + errMsg = "on FrontBase it is not possible to establish an " + errMsg += "out-of-band connection" + raise SqlmapUnsupportedFeatureException(errMsg) diff --git a/plugins/dbms/mckoi/enumeration.py b/plugins/dbms/mckoi/enumeration.py index c9bd17e85..c6176385a 100644 --- a/plugins/dbms/mckoi/enumeration.py +++ b/plugins/dbms/mckoi/enumeration.py @@ -10,7 +10,7 @@ from plugins.generic.enumeration import Enumeration as GenericEnumeration class Enumeration(GenericEnumeration): def getBanner(self): - warnMsg = "on Mckoi it is not possible to get a banner" + warnMsg = "on Mckoi it is not possible to get the banner" logger.warn(warnMsg) return None diff --git a/plugins/dbms/presto/enumeration.py b/plugins/dbms/presto/enumeration.py index e36ed2ab9..b3e7517bb 100644 --- a/plugins/dbms/presto/enumeration.py +++ b/plugins/dbms/presto/enumeration.py @@ -10,7 +10,7 @@ from plugins.generic.enumeration import Enumeration as GenericEnumeration class Enumeration(GenericEnumeration): def getBanner(self): - warnMsg = "on Presto it is not possible to get a banner" + warnMsg = "on Presto it is not possible to get the banner" logger.warn(warnMsg) return None diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index 3d95eb9c7..8c1cc2d05 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -85,7 +85,7 @@ class Databases(object): if not kb.data.currentDb and Backend.isDbms(DBMS.VERTICA): kb.data.currentDb = VERTICA_DEFAULT_SCHEMA - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE): + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE): warnMsg = "on %s you'll need to use " % Backend.getIdentifiedDbms() warnMsg += "schema names for enumeration as the counterpart to database " warnMsg += "names on other DBMSes" @@ -110,7 +110,7 @@ class Databases(object): warnMsg += "names will be fetched from 'mysql' database" logger.warn(warnMsg) - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE): + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE): warnMsg = "schema names are going to be used on %s " % Backend.getIdentifiedDbms() warnMsg += "for enumeration as the counterpart to database " warnMsg += "names on other DBMSes" @@ -399,7 +399,7 @@ class Databases(object): 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.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX): + elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE): query = rootQuery.blind.query % (index, unsafeSQLIdentificatorNaming(db)) else: query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(db), index) @@ -605,7 +605,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, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE): query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery @@ -752,7 +752,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, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE): query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery @@ -819,7 +819,7 @@ class Databases(object): continue for index in getLimitRange(count): - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE): query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery field = None @@ -873,7 +873,7 @@ class Databases(object): singleTimeWarnMessage(warnMsg) if not onlyColNames: - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE): query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column, unsafeSQLIdentificatorNaming(conf.db.upper())) diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 7f5dc2c96..4ff87855e 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -417,6 +417,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.FRONTBASE): + query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl) else: query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index) diff --git a/plugins/generic/users.py b/plugins/generic/users.py index adbba052f..fe42fd285 100644 --- a/plugins/generic/users.py +++ b/plugins/generic/users.py @@ -351,9 +351,7 @@ class Users(object): if not kb.data.cachedUsersPasswords: errMsg = "unable to retrieve the password hashes for the " - errMsg += "database users (probably because the DBMS " - errMsg += "current user has no read privileges over the relevant " - errMsg += "system database table(s))" + errMsg += "database users" logger.error(errMsg) else: for user in kb.data.cachedUsersPasswords: diff --git a/tamper/randomcase.py b/tamper/randomcase.py index c39b6648c..da4faed87 100644 --- a/tamper/randomcase.py +++ b/tamper/randomcase.py @@ -42,7 +42,7 @@ def tamper(payload, **kwargs): >>> tamper('function()') 'FuNcTiOn()' >>> tamper('SELECT id FROM `user`') - 'SeLeCt id FrOm `user`' + 'SeLeCt Id FrOm `user`' """ retVal = payload