From ec58f57fa96d7af2159a0ba0b97562fb97540f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 27 Jul 2026 11:35:14 +0200 Subject: [PATCH] Expanding capabilities of MySQL to correctly retrieve UTF8MB4 chars --- lib/core/agent.py | 3 +++ lib/core/enums.py | 1 + lib/core/option.py | 1 + lib/core/settings.py | 2 +- lib/techniques/blind/inference.py | 4 ++-- plugins/dbms/mysql/fingerprint.py | 10 ++++++++++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index c7aea8824..a30d33c5b 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -527,6 +527,9 @@ class Agent(object): 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 not (Backend.isDbms(DBMS.SQLITE) and not isDBMSVersionAtLeast('3')): nulledCastedField = rootQuery.cast.query % field diff --git a/lib/core/enums.py b/lib/core/enums.py index aa8cc4e65..6249b42ef 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 4c92f0e26..b1aa4e4bd 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 4b234d5e0..468929951 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.201" +VERSION = "1.10.7.202" 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) diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index bb449c3d5..95f618c3d 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -672,8 +672,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=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/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index e3fcb1cf6..f6e1d6600 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -95,6 +95,14 @@ class Fingerprint(GenericFingerprint): 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 @@ class Fingerprint(GenericFingerprint): hashDBWrite(HASHDB_KEYS.DBMS_FORK, fork) + self._checkUtf8mb4() + value = "" wsOsFp = Format.getOs("web server", kb.headersFp)