Making boolean inference some more robust against jitter

This commit is contained in:
Miroslav Štampar
2026-07-29 02:13:39 +02:00
parent 306738539e
commit 39d22d26c3
5 changed files with 94 additions and 5 deletions
+6
View File
@@ -818,6 +818,12 @@ def checkSqlInjection(place, parameter, value):
injection.data[stype].trueCode = trueCode
injection.data[stype].falseCode = falseCode
# reference bodies for inference.py's "resembles neither TRUE nor FALSE model"
# anomaly guard (runtime-only; lets a transient same-HTTP-code junk response
# trigger a validateChar re-check during boolean extraction)
if method == PAYLOAD.METHOD.COMPARISON:
kb.trueTemplate, kb.falseTemplate = truePage, falsePage
injection.conf.textOnly = conf.textOnly
injection.conf.titles = conf.titles
injection.conf.code = conf.code
+4
View File
@@ -2305,6 +2305,10 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.pageTemplate = None
kb.pageTemplates = dict()
kb.pageEncoding = DEFAULT_PAGE_ENCODING
# calibrated TRUE/FALSE reference bodies for the boolean same-HTTP-code anomaly guard (inference.py)
kb.trueTemplate = None
kb.falseTemplate = None
kb.pageStable = None
kb.pageStructurallyStable = None
kb.partRun = None
+6 -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.7.241"
VERSION = "1.10.7.242"
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)
@@ -103,6 +103,11 @@ LIVE_COOKIES_TIMEOUT = 120
LOWER_RATIO_BOUND = 0.02
UPPER_RATIO_BOUND = 0.98
# Minimum similarity at which a boolean extraction response is judged to "resemble" the calibrated
# TRUE or FALSE model. A response resembling NEITHER (a transient same-HTTP-code junk page: WAF/CDN
# interstitial, captcha, maintenance, empty/truncated body) triggers an extra validateChar re-check.
BOOLEAN_MODEL_MATCH_RATIO = 0.9
# Number of candidate names probed per request while mining for hidden parameters ('--mine-params')
PARAMETER_MINING_BUCKET_SIZE = 25
+37 -1
View File
@@ -7,6 +7,7 @@ See the file 'LICENSE' for copying permission
from __future__ import division
import difflib
import heapq
import re
import time
@@ -26,6 +27,7 @@ from lib.core.common import getTechnique
from lib.core.common import getTechniqueData
from lib.core.common import getText
from lib.core.common import predictValue
from lib.core.common import removeDynamicContent
from lib.core.common import hashDBRetrieve
from lib.core.common import hashDBWrite
from lib.core.common import incrementCounter
@@ -45,6 +47,7 @@ from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapThreadException
from lib.core.exception import SqlmapUnsupportedFeatureException
from lib.core.wordlist import Wordlist
from lib.core.settings import BOOLEAN_MODEL_MATCH_RATIO
from lib.core.settings import CHAR_INFERENCE_MARK
from lib.core.settings import HUFFMAN_PROBE_LIMIT
from lib.core.settings import HUFFMAN_PRIOR_WEIGHTS
@@ -256,6 +259,31 @@ def oracleReliabilityLitmus(expressionUnescaped, value, timeBasedCompare):
return bool(mustBeTrue) and not bool(mustBeFalse)
def _resemblesNeitherModel(page):
"""
Returns True when a boolean extraction response resembles NEITHER the calibrated TRUE nor FALSE
model (kb.trueTemplate / kb.falseTemplate). A transient response that keeps the expected HTTP code
but swaps the body for junk (WAF/CDN interstitial, captcha, maintenance banner, empty/truncated
page) is invisible to the HTTP-code check, so it is flagged here for an extra validateChar re-check.
This only ever ADDS a re-validation - it never changes a decided bit - so it is a safe no-op on a
clean target (every response resembles its own model) and when no models were recorded (a resumed
session, or a non-boolean technique).
"""
refs = [_ for _ in (kb.trueTemplate, kb.falseTemplate) if _]
if not refs:
return False
if page is None:
return True
cleaned = removeDynamicContent(page)
for ref in refs:
if difflib.SequenceMatcher(None, removeDynamicContent(ref), cleaned).quick_ratio() >= BOOLEAN_MODEL_MATCH_RATIO:
return False
return True
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False):
"""
Bisection algorithm that can be used to perform blind SQL injection
@@ -714,6 +742,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
firstCheck = False
lastCheck = False
unexpectedCode = False
unexpectedResponse = False
if continuousOrder:
while len(charTbl) > 1:
@@ -787,6 +816,13 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
singleTimeWarnMessage(warnMsg)
# same-HTTP-code body anomaly (WAF/CDN interstitial, captcha, maintenance, empty
# or truncated body) - invisible to the code check above, so re-validate when the
# response resembles neither calibrated model
elif not unexpectedResponse and not kb.nullConnection and _resemblesNeitherModel(threadData.lastPage):
unexpectedResponse = True
singleTimeWarnMessage("unexpected response content detected. Will use (extra) validation step in similar cases")
if result:
minValue = posValue
@@ -826,7 +862,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
retVal = minValue + 1
if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload):
if (timeBasedCompare or unexpectedCode) and kb.get("timeless") is None and not validateChar(idx, retVal):
if (timeBasedCompare or unexpectedCode or unexpectedResponse) and kb.get("timeless") is None and not validateChar(idx, retVal):
if restricted:
# the character fell outside this column's observed range - re-extract
# over the full charset (not timing noise, so no delay increase / retry count)