diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 031f81732..7d63eb0e0 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -1d7ed24bc41b9b73d7483a41c8b9162e95c7c027b1d07e52904c75fcad42fcfd lib/core/agent.py +9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py 12d0f1f28796b6fbf5629a3fd335b4098eac0583f832d1aa650efa22bf52e782 lib/core/bigarray.py f3725380a33c370c263516863d8e2bf3582f0ea6e37d45df8c176aa62ade19a1 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -6931bc15cb35d138913beb2ecca2432821b7d128e95f0db58ab99e32e24259dc lib/core/settings.py +317075c57f444f34b9f4b913a0754b3ffc48e477593f0ecd29ad462ccecd6401 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -240,8 +240,8 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py -30cae858e2a5a75b40854399f65ad074e6bb808d56d5ee66b94d4002dc6e101b lib/techniques/union/test.py -0a9d884d95734986a628e5846ed85c985a96534fb0c56f9d7042a89377801bc2 lib/techniques/union/use.py +ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py +9d916ad5d61f9ce467a5ff4b416e61b8ad76d1d950fdd06f23f70a6f7f941a1c lib/techniques/union/use.py aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py diff --git a/lib/core/agent.py b/lib/core/agent.py index 67b5d8857..bc0d1ed01 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -51,6 +51,7 @@ from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import GENERIC_SQL_COMMENT from lib.core.settings import GENERIC_SQL_COMMENT_MARKER from lib.core.settings import INFERENCE_MARKER +from lib.core.settings import MYSQL_UNION_VALUE_CAST from lib.core.settings import NULL from lib.core.settings import PAYLOAD_DELIMITER from lib.core.settings import REPLACEMENT_MARKER @@ -825,7 +826,7 @@ class Agent(object): return concatenatedQuery - def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None): + def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None, collate=False): """ Take in input a query (pseudo query) string and return its processed UNION ALL SELECT query. @@ -867,10 +868,21 @@ class Agent(object): if query.startswith("SELECT "): query = query[len("SELECT "):] + # On MySQL 8+ the retrieved value (connection collation) cannot be merged in a + # UNION column with a table column of a different collation (e.g. utf8mb4_0900_ai_ci), + # raising "Illegal mix of collations". Normalizing the charset and forcing an explicit + # collation (highest coercibility) wins the merge (Note: skipped for NULL/numeric values). + # Note: requires the utf8mb4 charset (MySQL >= 5.5.3) used in MYSQL_UNION_VALUE_CAST; on + # older versions there is no such collation clash to begin with (unknown version => assumed recent). + collateField = collate and Backend.isDbms(DBMS.MYSQL) and isDBMSVersionAtLeast('5.5.3') is not False + + def _collate(value): + return MYSQL_UNION_VALUE_CAST % value if collateField and value and value != NULL and not value.isdigit() else value + unionQuery = self.prefixQuery("UNION ALL SELECT ", prefix=prefix) if limited: - unionQuery += ','.join(char if _ != position else '(SELECT %s)' % query for _ in xrange(0, count)) + unionQuery += ','.join(char if _ != position else _collate('(SELECT %s)' % query) for _ in xrange(0, count)) unionQuery += fromTable unionQuery = self.suffixQuery(unionQuery, comment, suffix) @@ -900,6 +912,9 @@ class Agent(object): else: infoFile = None + if not infoFile: + query = _collate(query) + for element in xrange(0, count): if element > 0: unionQuery += ',' @@ -928,7 +943,7 @@ class Agent(object): unionQuery += ',' if element == position: - unionQuery += multipleUnions + unionQuery += _collate(multipleUnions) else: unionQuery += char diff --git a/lib/core/settings.py b/lib/core/settings.py index 12a42e3ee..d01875d09 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.6.125" +VERSION = "1.10.6.126" 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) @@ -511,6 +511,9 @@ SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", " # Maximum number of threads (avoiding connection issues and/or DoS) MAX_NUMBER_OF_THREADS = 10 +# Wrapper applied to MySQL UNION-based retrieval values to neutralize "Illegal mix of collations" errors (e.g. utf8mb4_0900_ai_ci tables vs a utf8mb4_general_ci connection on MySQL 8+). CONVERT normalizes the (possibly binary) charset to utf8mb4 and the explicit COLLATE then wins the UNION column merge (highest coercibility) +MYSQL_UNION_VALUE_CAST = "CONVERT(%s USING utf8mb4) COLLATE utf8mb4_bin" + # Minimum range between minimum and maximum of statistical set MIN_STATISTICAL_RANGE = 0.01 diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index d5e8a44df..0a8facf78 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -235,7 +235,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO randQueryUnescaped = unescaper.escape(randQueryProcessed) # Forge the union SQL injection request - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where) + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, collate=True) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request @@ -255,7 +255,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO randQueryUnescaped2 = unescaper.escape(randQueryProcessed2) # Confirm that it is a full union SQL injection - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2) + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2, collate=True) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request @@ -268,7 +268,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO 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()) # Check for limited row output - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable) + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable, collate=True) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index bb008579f..46f068761 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -84,12 +84,12 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): except IndexError: pass - query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) + query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited, collate=True) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6] else: injExpression = unescaper.escape(expression) where = vector[6] - query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False) + query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False, collate=True) payload = agent.payload(newValue=query, where=where)