mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-08-04 14:55:40 +00:00
Some more bug fixes for dbwire
This commit is contained in:
parent
a61e35980a
commit
9d08b1dc03
4 changed files with 83 additions and 21 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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<hexdigits>'; 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:
|
||||
|
|
|
|||
|
|
@ -131,7 +131,10 @@ def _login7(sock, user, password, database, hostname="dbwire", appname="dbwire")
|
|||
header += struct.pack("<I", 0) # client prog version
|
||||
header += struct.pack("<I", 0) # client PID
|
||||
header += struct.pack("<I", 0) # connection id
|
||||
header += struct.pack("<BBBB", 0, 0, 0, 0) # option flags 1/2, type flags, option flags 3
|
||||
# OptionFlags2 fODBC (0x02): make the server prepare the session like ODBC/FreeTDS - SET ANSI_DEFAULTS ON
|
||||
# (ANSI_NULLS/WARNINGS/PADDING, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER) + TEXTSIZE 2147483647 (else the
|
||||
# default 4096 silently truncates varchar(max)/text/image dumps) + IMPLICIT_TRANSACTIONS OFF + ROWCOUNT OFF
|
||||
header += struct.pack("<BBBB", 0, 0x02, 0, 0) # option flags 1, option flags 2 (fODBC), type flags, option flags 3
|
||||
header += struct.pack("<i", 0) # client time zone
|
||||
header += struct.pack("<I", 0) # client LCID
|
||||
|
||||
|
|
@ -157,7 +160,13 @@ def _decode_money(raw):
|
|||
else: # 8 bytes: signed high dword, unsigned low dword
|
||||
hi, lo = struct.unpack("<iI", raw)
|
||||
v = (hi << 32) | lo
|
||||
return "%.4f" % (v / 10000.0)
|
||||
s = "%05d" % abs(v) # scale 4; format from the integer (int64 money exceeds float's ~15-digit precision)
|
||||
return ("-" if v < 0 else "") + s[:-4] + "." + s[-4:]
|
||||
|
||||
def _decode_smalldatetime(raw):
|
||||
import datetime
|
||||
days, mins = struct.unpack("<HH", raw) # 2-byte days since 1900-01-01 + 2-byte minutes
|
||||
return "%s" % (datetime.datetime(1900, 1, 1) + datetime.timedelta(days=days, minutes=mins))
|
||||
|
||||
def _decode_numeric(raw, scale):
|
||||
sign = bytearray(raw)[0] # 1 == positive, 0 == negative; magnitude is little-endian
|
||||
|
|
@ -276,6 +285,8 @@ def _decode_variant(body):
|
|||
return _decode_money(val)
|
||||
if base == 0x3d:
|
||||
return _decode_datetime(val)
|
||||
if base == 0x3a:
|
||||
return _decode_smalldatetime(val)
|
||||
if base == 0x24:
|
||||
return _decode_guid(val)
|
||||
if base in (0x6a, 0x6c): # decimal/numeric: metadata = precision, scale
|
||||
|
|
@ -381,10 +392,8 @@ def _decode_value(col, data, off):
|
|||
return _decode_money(data[off:off + 8]), off + 8
|
||||
if t == 0x3d: # DATETIME (8 bytes)
|
||||
return _decode_datetime(data[off:off + 8]), off + 8
|
||||
if t == 0x3a: # DATETIM4 / smalldatetime (2-byte days since 1900 + 2-byte minutes)
|
||||
import datetime
|
||||
days, mins = struct.unpack("<HH", data[off:off + 4])
|
||||
return "%s" % (datetime.datetime(1900, 1, 1) + datetime.timedelta(days=days, minutes=mins)), off + 4
|
||||
if t == 0x3a: # DATETIM4 / smalldatetime (4 bytes)
|
||||
return _decode_smalldatetime(data[off:off + 4]), off + 4
|
||||
|
||||
# variable-length with a length prefix
|
||||
if t in (0xa7, 0xaf, 0xe7, 0xef, 0xa5, 0xad):
|
||||
|
|
@ -448,11 +457,7 @@ def _decode_value(col, data, off):
|
|||
if t == 0x24: # GUID
|
||||
return _decode_guid(raw), off
|
||||
if t == 0x6f: # DATETIMN (n=8 datetime, n=4 smalldatetime)
|
||||
if n == 8:
|
||||
return _decode_datetime(raw), off
|
||||
import datetime
|
||||
days, mins = struct.unpack("<HH", raw)
|
||||
return "%s" % (datetime.datetime(1900, 1, 1) + datetime.timedelta(days=days, minutes=mins)), off
|
||||
return (_decode_datetime(raw) if n == 8 else _decode_smalldatetime(raw)), off
|
||||
if t in (0x28, 0x29, 0x2a, 0x2b): # DATE / TIME / DATETIME2 / DATETIMEOFFSET
|
||||
return _decode_temporal(t, col.scale, raw), off
|
||||
# unknown layout: return the raw hex as a last resort (never desyncs)
|
||||
|
|
|
|||
|
|
@ -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.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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue