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