diff --git a/extra/dbwire/mysql.py b/extra/dbwire/mysql.py index 4f9b67262..6d1773ba5 100644 --- a/extra/dbwire/mysql.py +++ b/extra/dbwire/mysql.py @@ -36,10 +36,11 @@ _CLIENT_PLUGIN_AUTH = 0x00080000 _MAX_PACKET = 0x1000000 _MAX_MESSAGE_LENGTH = 0x40000000 # cap on a (re-assembled) payload, to bound a hostile/corrupt stream _BINARY_CHARSET = 63 # collation id 63 == 'binary' -# field types for which charset==63 genuinely denotes raw bytes (BLOB/BINARY/VARBINARY/BIT/GEOMETRY family). +# field types for which charset==63 genuinely denotes raw bytes (BLOB/BINARY/VARBINARY/GEOMETRY family). # Numeric & temporal columns ALSO report charset 63 in the text protocol, but carry their ASCII text form - # they must be decoded, not returned as bytes (else -d hexifies e.g. the int 12345 to '3132333435'). -_BINARY_TYPES = frozenset((15, 16, 249, 250, 251, 252, 253, 254, 255)) # VARCHAR,BIT,*BLOB,VAR_STRING,STRING,GEOMETRY +_BINARY_TYPES = frozenset((15, 249, 250, 251, 252, 253, 254, 255)) # VARCHAR,*BLOB,VAR_STRING,STRING,GEOMETRY +_TYPE_BIT = 16 # BIT reports charset 63 but is decoded to a big-endian integer (matches SQLAlchemy/mysql-connector) def _xor(a, b): if str is bytes: # Python 2 @@ -117,6 +118,12 @@ def _err_message(payload): off = 9 return payload[off:].decode("utf-8", "replace") +def _bit_int(value): + n = 0 # BIT arrives as a big-endian byte string + for b in bytearray(value): + n = (n << 8) | b + return n + def _scramble_native(password, salt): if not password: return b"" @@ -232,6 +239,8 @@ class Connection(object): value, off = _lenc_str(payload, off) if value is None: row.append(None) + elif description[i][1] == _TYPE_BIT: + row.append(str(_bit_int(value))) # big-endian integer, e.g. b'\x2a' -> '42' elif binary[i]: row.append(value) # keep binary/BLOB columns as raw bytes (sqlmap hex-encodes them) else: @@ -327,10 +336,15 @@ def connect(host=None, port=3306, user=None, password=None, database=None, conne raise OperationalError("handshake failed (%s)" % ex) connection = Connection(sock) - try: - connection._query("SET autocommit=1") # so DML persists even if the server default is autocommit=0 - except Exception: - pass + # SET NAMES: reset collation_connection to the server's default (the fixed handshake collation 45 = + # utf8mb4_general_ci otherwise clashes with MySQL 8's utf8mb4_0900_ai_ci columns -> 'illegal mix of + # collations' 1271 in a UNION/CONCAT); results stay utf8mb4 so the utf-8 decode is unchanged. autocommit=1 + # so DML persists even if the server default is autocommit=0. Both best-effort (one-time, at connect). + for setup in ("SET NAMES utf8mb4", "SET autocommit=1"): + try: + connection._query(setup) + except Exception: + pass return connection def _safe_close(sock): diff --git a/extra/dbwire/postgres.py b/extra/dbwire/postgres.py index 031ec6b3d..227fe6954 100644 --- a/extra/dbwire/postgres.py +++ b/extra/dbwire/postgres.py @@ -32,6 +32,28 @@ from extra.dbwire import ProgrammingError _PROTOCOL_VERSION = 196608 # 3.0 _MAX_MESSAGE_LENGTH = 0x40000000 # 1 GB - guard against a hostile/corrupt length triggering an unbounded read +_OID_BYTEA = 17 # bytea arrives as the server's text form; decode to bytes so it hexes like the native driver + +def _decode_bytea(raw): + # PG text output: modern 'hex' = b'\\x'; legacy 'escape' = octal \ooo + literal bytes + if raw[:2] == b"\\x": + try: + return binascii.unhexlify(raw[2:]) + except (binascii.Error, ValueError): + return raw.decode("utf-8", "replace") + src, out, i, n = bytearray(raw), bytearray(), 0, len(raw) + while i < n: + if src[i] == 0x5c and i + 1 < n: # backslash + nxt = src[i + 1] + if nxt == 0x5c: + out.append(0x5c); i += 2 + elif 0x30 <= nxt <= 0x37 and i + 3 < n: # \ooo octal + out.append(((nxt - 48) << 6) | ((src[i + 2] - 48) << 3) | (src[i + 3] - 48)); i += 4 + else: + out.append(nxt); i += 2 + else: + out.append(src[i]); i += 1 + return bytes(out) # SQLSTATE class (first 2 chars) -> DB-API exception, so callers can distinguish (mirrors psycopg2) _SQLSTATE_CLASS = { @@ -114,6 +136,7 @@ class Cursor(object): class Connection(object): def __init__(self, sock): self._sock = sock + self._txn_status = b"I" # last ReadyForQuery transaction status: I(dle) / T(ransaction) / E(rror) def cursor(self): return Cursor(self) @@ -134,7 +157,19 @@ class Connection(object): except Exception: pass + def _clear_aborted(self): + # a prior statement left an aborted transaction block ('E'): every further statement errors with + # 25P02 until it is rolled back. Clear it so the reused connection recovers (psycopg2 rollback semantics). + _send(self._sock, b"Q", b"ROLLBACK\x00") + while True: + mtype, payload = _read_message(self._sock) + if mtype == b"Z": + self._txn_status = payload[:1] or b"I" + break + def _simple_query(self, query): + if self._txn_status == b"E": + self._clear_aborted() _send(self._sock, b"Q", query.encode("utf-8") + b"\x00") description, rows, rowcount, error = None, [], -1, None @@ -154,7 +189,7 @@ class Connection(object): elif mtype == b"D": # DataRow (count,) = struct.unpack("!H", payload[:2]) off, row = 2, [] - for _ in range(count): + for col in range(count): (vlen,) = struct.unpack("!i", payload[off:off + 4]) off += 4 if vlen == -1: @@ -162,8 +197,15 @@ class Connection(object): else: if off + vlen > len(payload): raise InterfaceError("truncated DataRow") - row.append(payload[off:off + vlen].decode("utf-8", "replace")) + raw = payload[off:off + vlen] off += vlen + if description and col < len(description) and description[col][1] == _OID_BYTEA: + row.append(_decode_bytea(raw)) # bytes so sqlmap hex-encodes it (like the native driver) + else: + try: + row.append(raw.decode("utf-8")) + except UnicodeDecodeError: + row.append(raw) # non-UTF-8 (e.g. a SQL_ASCII db): keep bytes (hex-encoded), not lossy U+FFFD rows.append(tuple(row)) elif mtype == b"C": # CommandComplete ("SELECT 3", "INSERT 0 1", ...) tag = payload[:-1].decode("utf-8", "replace").split() @@ -173,7 +215,8 @@ class Connection(object): _send(self._sock, b"f", b"COPY FROM STDIN is not supported\x00") # CopyFail elif mtype == b"E": # ErrorResponse error = _error_message(payload) - elif mtype == b"Z": # ReadyForQuery (end of response) + elif mtype == b"Z": # ReadyForQuery (end of response); payload byte = transaction status + self._txn_status = payload[:1] or b"I" break # ParameterStatus(S)/NoticeResponse(N)/EmptyQueryResponse(I)/CopyData(d)/CopyDone(c)/... ignored except (struct.error, IndexError, ValueError) as ex: diff --git a/extra/dbwire/tds.py b/extra/dbwire/tds.py index 28ec16d04..fe4a4dbb6 100644 --- a/extra/dbwire/tds.py +++ b/extra/dbwire/tds.py @@ -131,7 +131,10 @@ def _login7(sock, user, password, database, hostname="dbwire", appname="dbwire") header += struct.pack("...) -VERSION = "1.10.7.87" +VERSION = "1.10.7.88" 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)