Minor update

This commit is contained in:
Miroslav Štampar 2026-07-29 00:14:38 +02:00
parent 2afa92451a
commit 023d85fba8
2 changed files with 27 additions and 15 deletions

View file

@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.7.239" VERSION = "1.10.7.240"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} 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) VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)

View file

@ -6,17 +6,19 @@ See the file 'LICENSE' for copying permission
Adversarial JITTER stress harness for time-based blind extraction. Adversarial JITTER stress harness for time-based blind extraction.
Drives the REAL bisection() + REAL wasLastResponseDelayed() against a mock oracle that returns a Drives the REAL bisection() + REAL wasLastResponseDelayed() + REAL validateChar() re-validation
simulated RESPONSE DURATION (base + jitter + timeSec-if-condition-true) instead of a boolean - so against a mock oracle that returns a simulated RESPONSE DURATION (base + jitter + timeSec-if-condition-
the actual statistical delay-decision and its re-validation run under controlled network jitter, true) instead of a boolean - so the whole time-based decision stack runs under controlled network
with NO real sleeping (thousands of extractions per second, fully deterministic per seed). jitter, with NO real sleeping (thousands of extractions per second, fully deterministic per seed).
The delimiter-wrapped template is what lets validateChar's per-char '!=' re-check actually fire (it is
sqlmap's main defense against a single spike faking one bit); without it the harness is far too harsh.
Two tiers: Two tiers:
* TestJitterRegression - ALWAYS runs. Low/mild jitter MUST extract perfectly. A real regression * TestJitterRegression - ALWAYS runs. Low/mild jitter MUST extract perfectly, and a spike in the
guard for the time-based decision stack (deterministic, fast, non-flaky). baseline model MUST NOT hide genuine delays. Deterministic, fast, non-flaky.
* TestJitterStressSweep - OPT-IN (set env SQLMAP_JITTER_STRESS=1). The adversarial sweeps that map * TestJitterStressSweep - OPT-IN (set env SQLMAP_JITTER_STRESS=1). Adversarial sweeps (Gaussian
the failure surface (Gaussian sigma, heavy-tailed spikes). Informational sigma, heavy-tailed spikes) mapping where extraction finally degrades.
+ loose bounds only; kept out of normal CI to avoid slowness/flakiness. Informational + loose bounds only; kept out of normal CI (slow/noisy).
Run the sweep on demand: SQLMAP_JITTER_STRESS=1 python -m unittest tests.test_jitter_stress -v Run the sweep on demand: SQLMAP_JITTER_STRESS=1 python -m unittest tests.test_jitter_stress -v
""" """
@ -35,11 +37,15 @@ from lib.core.data import conf, kb
from lib.core.common import getCurrentThreadData, setTechnique from lib.core.common import getCurrentThreadData, setTechnique
from lib.core.datatype import AttribDict from lib.core.datatype import AttribDict
from lib.core.enums import ADJUST_TIME_DELAY, PAYLOAD from lib.core.enums import ADJUST_TIME_DELAY, PAYLOAD
from lib.core.settings import PAYLOAD_DELIMITER
from lib.request.connect import Connect from lib.request.connect import Connect
import lib.techniques.blind.inference as inf import lib.techniques.blind.inference as inf
_TEMPLATE = "EXPR=%s IDX=%d CMP>%d" # The comparison must sit BETWEEN PAYLOAD_DELIMITERs: validateChar (inference.py) rewrites '>' to '!='
_PARSE = re.compile(r"IDX=(\d+) CMP(.)(\d+)") # with a regex anchored on the delimiters, and without them that per-char re-validation silently
# no-ops (defeating sqlmap's main per-request-spike defense and making this harness far too pessimistic).
_TEMPLATE = "%sEXPR=%%s IDX=%%d CMP>%%d%s" % (PAYLOAD_DELIMITER, PAYLOAD_DELIMITER)
_PARSE = re.compile(r"IDX=(\d+) CMP(!=|=|>)(\d+)") # bisection '>'/'=' plus validateChar's '!='
_TIMESEC = 5.0 _TIMESEC = 5.0
_BASE = 0.10 # base (non-delay) round-trip latency, seconds _BASE = 0.10 # base (non-delay) round-trip latency, seconds
_STRESS = os.environ.get("SQLMAP_JITTER_STRESS") _STRESS = os.environ.get("SQLMAP_JITTER_STRESS")
@ -107,7 +113,7 @@ class _JitterBase(unittest.TestCase):
return False return False
idx, op, thr = int(m.group(1)), m.group(2), int(m.group(3)) idx, op, thr = int(m.group(1)), m.group(2), int(m.group(3))
ch = ord(secret[idx - 1]) if 0 <= idx - 1 < len(secret) else 0 ch = ord(secret[idx - 1]) if 0 <= idx - 1 < len(secret) else 0
cond = (ch > thr) if op == ">" else (ch == thr) cond = (ch > thr) if op == ">" else (ch != thr) if op == "!=" else (ch == thr)
if "NOT(" in payload: if "NOT(" in payload:
cond = not cond cond = not cond
td.lastQueryDuration = _BASE + abs(jitter(rng)) + (_TIMESEC if cond else 0.0) td.lastQueryDuration = _BASE + abs(jitter(rng)) + (_TIMESEC if cond else 0.0)
@ -183,11 +189,14 @@ class TestJitterRegression(_JitterBase):
@unittest.skipUnless(_STRESS, "adversarial jitter sweep is opt-in (set SQLMAP_JITTER_STRESS=1)") @unittest.skipUnless(_STRESS, "adversarial jitter sweep is opt-in (set SQLMAP_JITTER_STRESS=1)")
class TestJitterStressSweep(_JitterBase): class TestJitterStressSweep(_JitterBase):
"""Opt-in failure-surface map. Prints correctness vs jitter and asserts only loose, non-flaky """Opt-in failure-surface map. Prints correctness vs jitter and asserts only loose, non-flaky
invariants (clean case perfect, degradation is monotone-ish). Use to evaluate hardening changes.""" invariants (clean case perfect). Use to evaluate hardening changes."""
SECRET = "Str0ng!" SECRET = "Str0ng!"
def test_gaussian_sweep(self): def test_gaussian_sweep(self):
# Continuous jitter: degrades only once sigma approaches timeSec/7 (7*stdev threshold nears the
# real delay). That is the FUNDAMENTAL limit of the statistic - the answer there is a larger
# timeSec (--time-sec), not a code change; shown here so a regression that degrades it earlier is visible.
print("\n[jitter] Gaussian sigma sweep (timeSec=%.0f, base=%.2f):" % (_TIMESEC, _BASE)) print("\n[jitter] Gaussian sigma sweep (timeSec=%.0f, base=%.2f):" % (_TIMESEC, _BASE))
for sigma in (0.0, 0.3, 0.5, 0.7, 0.9, 1.2): for sigma in (0.0, 0.3, 0.5, 0.7, 0.9, 1.2):
ok, n = self._rate(self.SECRET, _gaussian(sigma)) ok, n = self._rate(self.SECRET, _gaussian(sigma))
@ -196,8 +205,11 @@ class TestJitterStressSweep(_JitterBase):
self.assertEqual(ok, n) self.assertEqual(ok, n)
def test_heavy_tailed_spike_sweep(self): def test_heavy_tailed_spike_sweep(self):
# One-off +8s spikes: baseline-trim (stripTimeOutliers) keeps the model clean and validateChar's
# '!=' re-check catches a spike that fakes a single bit, so extraction stays ~perfect until an
# absurd spike rate (a fifth of all requests). This is the payoff of both defenses together.
print("\n[jitter] Heavy-tailed spike sweep (base sigma=0.2, spike=+8s):") print("\n[jitter] Heavy-tailed spike sweep (base sigma=0.2, spike=+8s):")
for p in (0.0, 0.01, 0.03, 0.05, 0.10): for p in (0.0, 0.01, 0.03, 0.05, 0.10, 0.20):
ok, n = self._rate(self.SECRET, _spike(0.2, p, 8.0)) ok, n = self._rate(self.SECRET, _spike(0.2, p, 8.0))
print(" spike_p=%.2f -> %d/%d (%3.0f%%)" % (p, ok, n, 100.0 * ok / n)) print(" spike_p=%.2f -> %d/%d (%3.0f%%)" % (p, ok, n, 100.0 * ok / n))
if p == 0.0: if p == 0.0: