This commit is contained in:
Miroslav Štampar 2026-07-21 11:34:18 +02:00
parent 002828a734
commit 26e13122ca
3 changed files with 27 additions and 6 deletions

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.161"
VERSION = "1.10.7.162"
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)

View file

@ -1572,6 +1572,7 @@ class Connect(object):
# it skips the time-based statistical warm-up entirely. The comparison request is assembled exactly
# as it would be sent (buildOnly) and the bit is read from a coalesced pair. Not engaged -> timing.
if timeBasedCompare and kb.get("timeless") is not None:
from lib.request.timeless import CONNECTIVITY_ERRORS
from lib.request.timeless import negatePayload
# Build the condition and negation requests through the SAME path (queryPage buildOnly on the
# raw pre-placement value) so the pair differs ONLY by the negated comparison - building cond
@ -1580,7 +1581,17 @@ class Connect(object):
negValue = negatePayload(timelessOrigValue)
condSpec = Connect.queryPage(timelessOrigValue, place=place, buildOnly=True)
negSpec = Connect.queryPage(negValue, place=place, buildOnly=True) if negValue is not None else None
return kb.timeless.readBitFromSpecs(condSpec, negSpec)
try:
return kb.timeless.readBitFromSpecs(condSpec, negSpec)
except CONNECTIVITY_ERRORS as ex:
# The oracle's own per-pair retries (see _pairOrder) are exhausted - the target has stopped
# negotiating HTTP/2 altogether (e.g. a load-balanced backend that only some nodes speak it
# on), not just dropped one connection. Disengage (restores the classic time-based vector)
# and fall through below to the normal wall-clock comparison instead of crashing the scan.
from lib.request.timeless import disengage
warnMsg = "HTTP/2 timeless timing lost connectivity ('%s'). Falling back to classic time-based" % getSafeExString(ex)
singleTimeWarnMessage(warnMsg)
disengage()
if timeBasedCompare and not conf.disableStats:
if len(kb.responseTimes.get(kb.responseTimeMode, [])) < MIN_TIME_RESPONSES:

View file

@ -27,6 +27,12 @@ from lib.request.http2 import _H2Connection
# Serializes the one-shot autoEngage() so concurrent worker threads never double-calibrate/double-engage.
_engageLock = threading.Lock()
# Transport-level failures that mean "this connection/attempt is unusable" - covers a mid-exchange drop
# (GOAWAY, reset) as well as a failed (re)connect, including the h2 client's own IOError when the server
# does not negotiate ALPN 'h2' on a fresh socket (seen on backends that speak h2 inconsistently, e.g. only
# some nodes behind a load balancer). Shared by _pairOrder (per-pair retry) and connect.py (the give-up path).
CONNECTIVITY_ERRORS = (socket.error, ssl.SSLError, IOError)
def buildConditionPair(condition, heavy, cheap="0"):
"""Turn a boolean `condition` (the same comparison bisection injects at INFERENCE_MARKER, e.g.
@ -59,15 +65,19 @@ def _pairOrder(connSource, reqA, reqB, timeout, retries=2):
lets a dropped connection be replaced transparently and the pair re-sent: a long extraction routinely
outlives a single HTTP/2 connection (the server retires it with GOAWAY after its per-connection request
cap), and a coalesced boolean-read pair is idempotent, so re-sending it on a fresh connection is safe.
A raw connection (used by calibration and the self-test) is not retried - it simply raises."""
Opening that fresh connection is retried the same way - it can fail just like an in-progress exchange
(including ALPN renegotiation failing on the new socket). A raw connection (used by calibration and the
self-test) is not retried - it simply raises."""
attempt = 0
while True:
conn = connSource() if callable(connSource) else connSource
conn = None
try:
conn = connSource() if callable(connSource) else connSource
order, _results = conn.exchange_pair([reqA, reqB], timeout)
return order[0], conn.next_sid - 4, conn.next_sid - 2
except (socket.error, ssl.SSLError, IOError):
conn.close() # retire; a callable source reopens on the next pass
except CONNECTIVITY_ERRORS:
if conn is not None:
conn.close() # retire; a callable source reopens on the next pass
attempt += 1
if not callable(connSource) or attempt > retries:
raise