diff --git a/lib/core/settings.py b/lib/core/settings.py index f8a12caa4..7924ef165 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.103" +VERSION = "1.10.7.104" 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) diff --git a/lib/request/http2.py b/lib/request/http2.py index a8aa6287f..7af598c41 100644 --- a/lib/request/http2.py +++ b/lib/request/http2.py @@ -144,7 +144,7 @@ STATIC_LEN = len(STATIC_TABLE) # HTTP/2 frame codec (RFC 7540 section 4.1) - the zero-table-risk brick. Pure stdlib, py2/py3, ASCII. # frame types (RFC 7540 s6) -DATA, HEADERS, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9 +DATA, HEADERS, RST_STREAM, SETTINGS, PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 # flags FLAG_END_STREAM = 0x1 FLAG_ACK = 0x1 @@ -374,6 +374,7 @@ class Encoder(object): out += encode_string(value) return bytes(out) +SETTINGS_ENABLE_PUSH = 0x2 SETTINGS_INITIAL_WINDOW_SIZE = 0x4 BIG_WINDOW = (1 << 31) - 1 @@ -469,9 +470,12 @@ class _H2Connection(object): if self.sock.selected_alpn_protocol() != "h2": raise IOError("server did not negotiate h2 (ALPN=%r)" % self.sock.selected_alpn_protocol()) self.sock.settimeout(timeout) - # connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window + # connection preface + client SETTINGS (disable server push + advertise a large per-stream window) + # + bump conn window. ENABLE_PUSH=0 keeps servers from opening pushed streams whose HPACK header + # block we would otherwise have to decode to keep the dynamic table in sync (skipping it desyncs + # the decoder and corrupts every later header) - this client has no use for pushed responses. self.sock.sendall(CONNECTION_PREFACE) - self.sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW))) + self.sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HIHI", SETTINGS_ENABLE_PUSH, 0, SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW))) self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535))) except Exception: self.close() @@ -514,6 +518,9 @@ class _H2Connection(object): elif ftype == PING: if not (flags & FLAG_ACK): self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) + elif ftype == PUSH_PROMISE: + self.usable = False # we advertised ENABLE_PUSH=0; a push would desync HPACK + raise _UnprocessedStream("unexpected PUSH_PROMISE despite SETTINGS_ENABLE_PUSH=0") elif ftype == GOAWAY: self.usable = False # server won't accept new streams -> retire connection last_sid = (struct.unpack("!I", payload[4:8])[0] & 0x7fffffff) if len(payload) >= 8 else 0 @@ -601,12 +608,18 @@ class _H2Connection(object): elif ftype == PING: if not (flags & FLAG_ACK): self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) + elif ftype == PUSH_PROMISE: + self.usable = False # we advertised ENABLE_PUSH=0; a push would desync HPACK + raise _UnprocessedStream("unexpected PUSH_PROMISE despite SETTINGS_ENABLE_PUSH=0") elif ftype == GOAWAY: + # Routine on a long-lived connection (server retires it after its per-connection request cap). + # The pair did not complete cleanly, so it must be re-sent on a fresh connection; flag it + # retry-safe (idempotent boolean read) rather than crashing the extraction. self.usable = False - raise IOError("GOAWAY during timeless pair") + raise _UnprocessedStream("GOAWAY during timeless pair") elif ftype == RST_STREAM and fsid in state: self.usable = False - raise IOError("stream reset during timeless pair") + raise _UnprocessedStream("stream reset during timeless pair") elif ftype in (HEADERS, CONTINUATION) and fsid in state: p = payload if ftype == HEADERS: diff --git a/lib/request/timeless.py b/lib/request/timeless.py index 39f496153..f0997594e 100644 --- a/lib/request/timeless.py +++ b/lib/request/timeless.py @@ -17,6 +17,8 @@ See the file 'LICENSE' for copying permission # front-proxy makes order track arrival, not work. calibrate() detects that (and estimates the readable # delta) so the caller never applies the oracle blind - it falls back to classic time-based instead. +import socket +import ssl import threading from lib.core.enums import DBMS @@ -49,18 +51,32 @@ def buildConditionPair(condition, heavy, cheap="0"): return condExpr, negExpr -def _pairOrder(conn, reqA, reqB, timeout): +def _pairOrder(connSource, reqA, reqB, timeout, retries=2): """Send reqA and reqB as one coalesced pair; return the stream id that finished FIRST plus the two - stream ids in send order (reqA got the lower id).""" - order, _results = conn.exchange_pair([reqA, reqB], timeout) - loSid = conn.next_sid - 4 - hiSid = conn.next_sid - 2 - return order[0], loSid, hiSid + stream ids in send order (reqA got the lower id). + + `connSource` is either a live _H2Connection or a zero-arg callable returning one. The callable form + 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.""" + attempt = 0 + while True: + conn = connSource() if callable(connSource) else connSource + try: + 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 + attempt += 1 + if not callable(connSource) or attempt > retries: + raise -def readBit(conn, reqCond, reqNeg, votes=5, timeout=30): +def readBit(connSource, reqCond, reqNeg, votes=5, timeout=30): """Read one boolean by the cond-last FRACTION over symmetric pairs, ESCALATING when the fraction is - ambiguous (load-degraded). + ambiguous (load-degraded). `connSource` is a live connection or a factory (see _pairOrder) so a + connection dropped mid-vote (e.g. server GOAWAY) is replaced and that pair re-sent transparently. reqCond does the heavy work iff the guessed condition is TRUE; reqNeg does the SAME heavy work iff the 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): cap = votes * 5 # escalation ceiling for ambiguous (load-degraded) bits while True: if i % 2 == 0: - first, loSid, _hiSid = _pairOrder(conn, reqCond, reqNeg, timeout) + first, loSid, _hiSid = _pairOrder(connSource, reqCond, reqNeg, timeout) condSid = loSid else: - first, _loSid, hiSid = _pairOrder(conn, reqNeg, reqCond, timeout) + first, _loSid, hiSid = _pairOrder(connSource, reqNeg, reqCond, timeout) condSid = hiSid if first != condSid: # reqCond finished last -> it ran heavy -> vote says TRUE condLast += 1 @@ -159,6 +175,16 @@ class TimelessOracle(object): def _conn(self): conn = getattr(self._local, "conn", None) if conn is None or not conn.usable: + if conn is not None: # retire the dead one so reconnects don't leak sockets + try: + conn.close() + except Exception: + pass + with self._lock: + try: + self._conns.remove(conn) + except ValueError: + pass conn = self._local.conn = connect(self.host, self.port, self.proxy, self.timeout) with self._lock: self._conns.append(conn) @@ -172,9 +198,11 @@ class TimelessOracle(object): be used for a heavy-base condition (the trivial-base reference is not comparable). Specs are the (url, method, headers, post) tuples from getPage(buildOnly=True).""" reqCond = _specToReq(condSpec, self.host) + # Pass the bound _conn factory (not a resolved connection) so a pair whose connection is retired + # mid-read (server GOAWAY on a long dump) is transparently re-sent on a fresh one. if negSpec is not None: - return readBit(self._conn(), reqCond, _specToReq(negSpec, self.host), votes=self.votes, timeout=self.timeout) - return readBitAsymmetric(self._conn(), reqCond, self.refReq, self.asymVotes, self.timeout) + return readBit(self._conn, reqCond, _specToReq(negSpec, self.host), votes=self.votes, timeout=self.timeout) + return readBitAsymmetric(self._conn, reqCond, self.refReq, self.asymVotes, self.timeout) def close(self): with self._lock: @@ -655,7 +683,7 @@ def readBitLive(conn, condition, vector=None, votes=1, timeout=30): return readBit(conn, reqCond, reqNeg, votes=votes, timeout=timeout) -def readBitAsymmetric(conn, reqCond, reqRef, votes=12, timeout=30): +def readBitAsymmetric(connSource, reqCond, reqRef, votes=12, timeout=30): """Read one boolean WITHOUT the negated comparison - only the condition request and a FIXED always-heavy reference. When the condition is TRUE, reqCond runs the SAME heavy work as reqRef, so they race ~50/50 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): condLast = 0 for i in range(votes): if i % 2 == 0: - order, _ = conn.exchange_pair([reqCond, reqRef], timeout); condSid = conn.next_sid - 4 + first, condSid, _hiSid = _pairOrder(connSource, reqCond, reqRef, timeout) else: - order, _ = conn.exchange_pair([reqRef, reqCond], timeout); condSid = conn.next_sid - 2 - if order[0] != condSid: # cond finished last -> it ran the heavy branch this vote + first, _loSid, condSid = _pairOrder(connSource, reqRef, reqCond, timeout) + if first != condSid: # cond finished last -> it ran the heavy branch this vote condLast += 1 return condLast * 4 >= votes # >= 25% cond-last -> TRUE (mid-way between ~50% and ~0%)