Skip to content

Commit 71afbf0

Browse files
committed
Fixes #6083
1 parent 7ee21bb commit 71afbf0

3 files changed

Lines changed: 63 additions & 22 deletions

File tree

lib/core/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from thirdparty import six
2121

2222
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
23-
VERSION = "1.10.7.103"
23+
VERSION = "1.10.7.104"
2424
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
2525
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
2626
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)

lib/request/http2.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
# HTTP/2 frame codec (RFC 7540 section 4.1) - the zero-table-risk brick. Pure stdlib, py2/py3, ASCII.
145145

146146
# frame types (RFC 7540 s6)
147-
DATA, HEADERS, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9
147+
DATA, HEADERS, RST_STREAM, SETTINGS, PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9
148148
# flags
149149
FLAG_END_STREAM = 0x1
150150
FLAG_ACK = 0x1
@@ -374,6 +374,7 @@ def encode(self, headers):
374374
out += encode_string(value)
375375
return bytes(out)
376376

377+
SETTINGS_ENABLE_PUSH = 0x2
377378
SETTINGS_INITIAL_WINDOW_SIZE = 0x4
378379
BIG_WINDOW = (1 << 31) - 1
379380

@@ -469,9 +470,12 @@ def __init__(self, host, port, proxy, timeout):
469470
if self.sock.selected_alpn_protocol() != "h2":
470471
raise IOError("server did not negotiate h2 (ALPN=%r)" % self.sock.selected_alpn_protocol())
471472
self.sock.settimeout(timeout)
472-
# connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window
473+
# connection preface + client SETTINGS (disable server push + advertise a large per-stream window)
474+
# + bump conn window. ENABLE_PUSH=0 keeps servers from opening pushed streams whose HPACK header
475+
# block we would otherwise have to decode to keep the dynamic table in sync (skipping it desyncs
476+
# the decoder and corrupts every later header) - this client has no use for pushed responses.
473477
self.sock.sendall(CONNECTION_PREFACE)
474-
self.sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW)))
478+
self.sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HIHI", SETTINGS_ENABLE_PUSH, 0, SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW)))
475479
self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535)))
476480
except Exception:
477481
self.close()
@@ -514,6 +518,9 @@ def exchange(self, method, path, authority, headers, body, timeout):
514518
elif ftype == PING:
515519
if not (flags & FLAG_ACK):
516520
self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload))
521+
elif ftype == PUSH_PROMISE:
522+
self.usable = False # we advertised ENABLE_PUSH=0; a push would desync HPACK
523+
raise _UnprocessedStream("unexpected PUSH_PROMISE despite SETTINGS_ENABLE_PUSH=0")
517524
elif ftype == GOAWAY:
518525
self.usable = False # server won't accept new streams -> retire connection
519526
last_sid = (struct.unpack("!I", payload[4:8])[0] & 0x7fffffff) if len(payload) >= 8 else 0
@@ -601,12 +608,18 @@ def exchange_pair(self, requests, timeout):
601608
elif ftype == PING:
602609
if not (flags & FLAG_ACK):
603610
self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload))
611+
elif ftype == PUSH_PROMISE:
612+
self.usable = False # we advertised ENABLE_PUSH=0; a push would desync HPACK
613+
raise _UnprocessedStream("unexpected PUSH_PROMISE despite SETTINGS_ENABLE_PUSH=0")
604614
elif ftype == GOAWAY:
615+
# Routine on a long-lived connection (server retires it after its per-connection request cap).
616+
# The pair did not complete cleanly, so it must be re-sent on a fresh connection; flag it
617+
# retry-safe (idempotent boolean read) rather than crashing the extraction.
605618
self.usable = False
606-
raise IOError("GOAWAY during timeless pair")
619+
raise _UnprocessedStream("GOAWAY during timeless pair")
607620
elif ftype == RST_STREAM and fsid in state:
608621
self.usable = False
609-
raise IOError("stream reset during timeless pair")
622+
raise _UnprocessedStream("stream reset during timeless pair")
610623
elif ftype in (HEADERS, CONTINUATION) and fsid in state:
611624
p = payload
612625
if ftype == HEADERS:

lib/request/timeless.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
# front-proxy makes order track arrival, not work. calibrate() detects that (and estimates the readable
1818
# delta) so the caller never applies the oracle blind - it falls back to classic time-based instead.
1919

20+
import socket
21+
import ssl
2022
import threading
2123

2224
from lib.core.enums import DBMS
@@ -49,18 +51,32 @@ def buildConditionPair(condition, heavy, cheap="0"):
4951
return condExpr, negExpr
5052

5153

52-
def _pairOrder(conn, reqA, reqB, timeout):
54+
def _pairOrder(connSource, reqA, reqB, timeout, retries=2):
5355
"""Send reqA and reqB as one coalesced pair; return the stream id that finished FIRST plus the two
54-
stream ids in send order (reqA got the lower id)."""
55-
order, _results = conn.exchange_pair([reqA, reqB], timeout)
56-
loSid = conn.next_sid - 4
57-
hiSid = conn.next_sid - 2
58-
return order[0], loSid, hiSid
56+
stream ids in send order (reqA got the lower id).
57+
58+
`connSource` is either a live _H2Connection or a zero-arg callable returning one. The callable form
59+
lets a dropped connection be replaced transparently and the pair re-sent: a long extraction routinely
60+
outlives a single HTTP/2 connection (the server retires it with GOAWAY after its per-connection request
61+
cap), and a coalesced boolean-read pair is idempotent, so re-sending it on a fresh connection is safe.
62+
A raw connection (used by calibration and the self-test) is not retried - it simply raises."""
63+
attempt = 0
64+
while True:
65+
conn = connSource() if callable(connSource) else connSource
66+
try:
67+
order, _results = conn.exchange_pair([reqA, reqB], timeout)
68+
return order[0], conn.next_sid - 4, conn.next_sid - 2
69+
except (socket.error, ssl.SSLError, IOError):
70+
conn.close() # retire; a callable source reopens on the next pass
71+
attempt += 1
72+
if not callable(connSource) or attempt > retries:
73+
raise
5974

6075

61-
def readBit(conn, reqCond, reqNeg, votes=5, timeout=30):
76+
def readBit(connSource, reqCond, reqNeg, votes=5, timeout=30):
6277
"""Read one boolean by the cond-last FRACTION over symmetric pairs, ESCALATING when the fraction is
63-
ambiguous (load-degraded).
78+
ambiguous (load-degraded). `connSource` is a live connection or a factory (see _pairOrder) so a
79+
connection dropped mid-vote (e.g. server GOAWAY) is replaced and that pair re-sent transparently.
6480
6581
reqCond does the heavy work iff the guessed condition is TRUE; reqNeg does the SAME heavy work iff the
6682
condition is FALSE (it carries the negated condition). Exactly one runs heavy, so whichever finishes
@@ -83,10 +99,10 @@ def readBit(conn, reqCond, reqNeg, votes=5, timeout=30):
8399
cap = votes * 5 # escalation ceiling for ambiguous (load-degraded) bits
84100
while True:
85101
if i % 2 == 0:
86-
first, loSid, _hiSid = _pairOrder(conn, reqCond, reqNeg, timeout)
102+
first, loSid, _hiSid = _pairOrder(connSource, reqCond, reqNeg, timeout)
87103
condSid = loSid
88104
else:
89-
first, _loSid, hiSid = _pairOrder(conn, reqNeg, reqCond, timeout)
105+
first, _loSid, hiSid = _pairOrder(connSource, reqNeg, reqCond, timeout)
90106
condSid = hiSid
91107
if first != condSid: # reqCond finished last -> it ran heavy -> vote says TRUE
92108
condLast += 1
@@ -159,6 +175,16 @@ def __init__(self, host, port, refReq, proxy=None, asymVotes=12, votes=5, timeou
159175
def _conn(self):
160176
conn = getattr(self._local, "conn", None)
161177
if conn is None or not conn.usable:
178+
if conn is not None: # retire the dead one so reconnects don't leak sockets
179+
try:
180+
conn.close()
181+
except Exception:
182+
pass
183+
with self._lock:
184+
try:
185+
self._conns.remove(conn)
186+
except ValueError:
187+
pass
162188
conn = self._local.conn = connect(self.host, self.port, self.proxy, self.timeout)
163189
with self._lock:
164190
self._conns.append(conn)
@@ -172,9 +198,11 @@ def readBitFromSpecs(self, condSpec, negSpec=None):
172198
be used for a heavy-base condition (the trivial-base reference is not comparable). Specs are the
173199
(url, method, headers, post) tuples from getPage(buildOnly=True)."""
174200
reqCond = _specToReq(condSpec, self.host)
201+
# Pass the bound _conn factory (not a resolved connection) so a pair whose connection is retired
202+
# mid-read (server GOAWAY on a long dump) is transparently re-sent on a fresh one.
175203
if negSpec is not None:
176-
return readBit(self._conn(), reqCond, _specToReq(negSpec, self.host), votes=self.votes, timeout=self.timeout)
177-
return readBitAsymmetric(self._conn(), reqCond, self.refReq, self.asymVotes, self.timeout)
204+
return readBit(self._conn, reqCond, _specToReq(negSpec, self.host), votes=self.votes, timeout=self.timeout)
205+
return readBitAsymmetric(self._conn, reqCond, self.refReq, self.asymVotes, self.timeout)
178206

179207
def close(self):
180208
with self._lock:
@@ -655,7 +683,7 @@ def readBitLive(conn, condition, vector=None, votes=1, timeout=30):
655683
return readBit(conn, reqCond, reqNeg, votes=votes, timeout=timeout)
656684

657685

658-
def readBitAsymmetric(conn, reqCond, reqRef, votes=12, timeout=30):
686+
def readBitAsymmetric(connSource, reqCond, reqRef, votes=12, timeout=30):
659687
"""Read one boolean WITHOUT the negated comparison - only the condition request and a FIXED always-heavy
660688
reference. When the condition is TRUE, reqCond runs the SAME heavy work as reqRef, so they race ~50/50
661689
and reqCond finishes last about HALF the votes; when FALSE - or when the comparison is NULL / the DBMS
@@ -668,10 +696,10 @@ def readBitAsymmetric(conn, reqCond, reqRef, votes=12, timeout=30):
668696
condLast = 0
669697
for i in range(votes):
670698
if i % 2 == 0:
671-
order, _ = conn.exchange_pair([reqCond, reqRef], timeout); condSid = conn.next_sid - 4
699+
first, condSid, _hiSid = _pairOrder(connSource, reqCond, reqRef, timeout)
672700
else:
673-
order, _ = conn.exchange_pair([reqRef, reqCond], timeout); condSid = conn.next_sid - 2
674-
if order[0] != condSid: # cond finished last -> it ran the heavy branch this vote
701+
first, _loSid, condSid = _pairOrder(connSource, reqRef, reqCond, timeout)
702+
if first != condSid: # cond finished last -> it ran the heavy branch this vote
675703
condLast += 1
676704
return condLast * 4 >= votes # >= 25% cond-last -> TRUE (mid-way between ~50% and ~0%)
677705

0 commit comments

Comments
 (0)