mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-10-12 02:22:55 +00:00
Some patching of dbwire
This commit is contained in:
+12
-2
@@ -132,6 +132,10 @@ class _Reader(object):
|
||||
v = struct.unpack_from(">d", self._buf, self._off)[0]; self._off += 8; return v
|
||||
|
||||
def raw(self, n):
|
||||
# a slice past the end returns short data *silently*, which would hand a truncated value to the
|
||||
# caller (the fixed-width readers above raise struct.error instead)
|
||||
if n < 0 or n > self.remaining():
|
||||
raise InterfaceError("CAS response too short (wanted %d of %d bytes)" % (n, self.remaining()))
|
||||
v = self._buf[self._off:self._off + n]; self._off += n; return bytes(v)
|
||||
|
||||
def skip(self, n):
|
||||
@@ -371,9 +375,15 @@ class Connection(object):
|
||||
reader.byte() # is_updatable
|
||||
columns = self._parse_columns(reader, reader.int())
|
||||
|
||||
# args: handle, flag, max_col_size, max_row, binds, fetch_flag, auto_commit, forward_only_cursor,
|
||||
# cache_time, query_timeout. auto_commit makes the CAS worker commit the statement and end the
|
||||
# transaction - without it DML is rolled back when the connection drops, whatever _open() asked
|
||||
# SET_DB_PARAMETER for. It must stay off for a SELECT: ending the transaction there invalidates the
|
||||
# request handle the paged _fetch_remaining() still reads from.
|
||||
select = stmt_type == _STMT_SELECT
|
||||
exec_writer = (_Writer(_FC_EXECUTE).arg_int(handle).arg_byte(0).arg_int(0).arg_int(0)
|
||||
.arg_null().arg_byte(1 if stmt_type == _STMT_SELECT else 0)
|
||||
.arg_byte(0).arg_byte(1).arg_cache_time().arg_int(0))
|
||||
.arg_null().arg_byte(1 if select else 0)
|
||||
.arg_byte(0 if select else 1).arg_byte(1).arg_cache_time().arg_int(0))
|
||||
reader = self._call(exec_writer)
|
||||
|
||||
total = reader.int()
|
||||
|
||||
@@ -142,6 +142,8 @@ _GDS_INTEGRITY = frozenset((335544838, 335544879, 335544880, 335544466, 33554466
|
||||
_GDS_DATA = frozenset((335544321,))
|
||||
_GDS_WARNING = 335544434
|
||||
|
||||
_MAX_MESSAGE_LENGTH = 0x40000000 # cap on a wire-supplied length, to bound a hostile/corrupt stream
|
||||
|
||||
# SRP-6a group used by Firebird (fixed 1024-bit prime, generator 2)
|
||||
_SRP_N = int("E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDE"
|
||||
"BF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F"
|
||||
@@ -306,6 +308,10 @@ class _Wire(object):
|
||||
return buf
|
||||
|
||||
def recv(self, n, align=False):
|
||||
# every length here comes off the wire (response buffers, status strings, per-value lengths): a
|
||||
# negative one would silently return short data, a huge one would read until memory ran out
|
||||
if n < 0 or n > _MAX_MESSAGE_LENGTH:
|
||||
raise InterfaceError("invalid Firebird length (%d)" % n)
|
||||
total = n + ((4 - n % 4) % 4) if align else n
|
||||
data = self._recv_raw(total)
|
||||
if self._rc:
|
||||
|
||||
@@ -27,6 +27,7 @@ from extra.dbwire import keepalive
|
||||
from extra.dbwire import ProgrammingError
|
||||
|
||||
_MAX_BLOCK = 0xffff >> 1
|
||||
_MAX_MESSAGE_LENGTH = 0x40000000 # cap on a (re-assembled) response, to bound a hostile/corrupt stream
|
||||
|
||||
def _recvn(sock, n):
|
||||
buf = b""
|
||||
@@ -41,14 +42,19 @@ def _recvn(sock, n):
|
||||
return buf
|
||||
|
||||
def _getblock(sock):
|
||||
out = b""
|
||||
# the block length is a 15-bit field, so bounding IT is pointless - a peer that never sets the last-flag
|
||||
# simply streams blocks forever. Bound the accumulated response instead (as the other wire modules do).
|
||||
chunks, total = [], 0
|
||||
while True:
|
||||
(header,) = struct.unpack("<H", _recvn(sock, 2))
|
||||
length, last = header >> 1, header & 1
|
||||
out += _recvn(sock, length)
|
||||
total += length
|
||||
if total > _MAX_MESSAGE_LENGTH:
|
||||
raise InterfaceError("backend message too large (%d bytes)" % total)
|
||||
chunks.append(_recvn(sock, length))
|
||||
if last:
|
||||
break
|
||||
return out.decode("utf-8", "replace")
|
||||
return b"".join(chunks).decode("utf-8", "replace")
|
||||
|
||||
def _putblock(sock, text):
|
||||
data = text.encode("utf-8")
|
||||
|
||||
@@ -321,9 +321,9 @@ def connect(host=None, port=5432, user=None, password=None, database=None, conne
|
||||
for key, value in (("user", user or ""), ("database", database or user or ""), ("client_encoding", "UTF8")):
|
||||
params += key.encode("ascii") + b"\x00" + ("%s" % value).encode("utf-8") + b"\x00"
|
||||
params += b"\x00"
|
||||
_send(sock, b"", struct.pack("!I", _PROTOCOL_VERSION) + params)
|
||||
|
||||
try:
|
||||
_send(sock, b"", struct.pack("!I", _PROTOCOL_VERSION) + params) # StartupMessage
|
||||
_authenticate(sock, user, password)
|
||||
while True: # drain until ReadyForQuery (ParameterStatus/BackendKeyData/NoticeResponse)
|
||||
mtype, payload = _read_message(sock)
|
||||
|
||||
@@ -20,7 +20,7 @@ from lib.core.enums import OS
|
||||
from thirdparty import six
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.10.8.29"
|
||||
VERSION = "1.10.8.30"
|
||||
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)
|
||||
|
||||
@@ -31,7 +31,10 @@ bootstrap()
|
||||
|
||||
import extra.dbwire as dbwire
|
||||
from extra.dbwire import connection_lost
|
||||
from extra.dbwire import cubrid as _cubrid
|
||||
from extra.dbwire import firebird as _firebird
|
||||
from extra.dbwire import http_origin
|
||||
from extra.dbwire import monetdb as _monetdb
|
||||
from extra.dbwire import mysql as _mysql
|
||||
from extra.dbwire import postgres as _postgres
|
||||
from extra.dbwire import presto as _presto
|
||||
@@ -314,6 +317,76 @@ class TrinoSessionStateTest(unittest.TestCase):
|
||||
self.assertNotIn("X-Presto-Schema", c._headers)
|
||||
|
||||
|
||||
class CubridAutoCommitTest(unittest.TestCase):
|
||||
"""CUBRID has no server-side autocommit switch that outlives a statement: the EXECUTE request carries
|
||||
its own auto_commit byte, and a statement sent with it clear is rolled back when the CAS worker goes
|
||||
away. It must stay clear for a SELECT, whose request handle the paged fetch still reads from."""
|
||||
|
||||
def _auto_commit_byte(self, stmt_type):
|
||||
connection = object.__new__(_cubrid.Connection)
|
||||
connection._protocol_version = 8
|
||||
connection._sock = None
|
||||
sent = []
|
||||
|
||||
def _call(writer):
|
||||
sent.append(writer.payload())
|
||||
# EXECUTE response: total(4) cache_reusable(1) result_count(4) includes_column_info(1) shard_id(4)
|
||||
return _cubrid._Reader(struct.pack(">iBiBi", 0, 0, 0, 0, 0))
|
||||
|
||||
connection._call = _call
|
||||
prepare = _cubrid._Reader(struct.pack(">iBiBi", 0, stmt_type, 0, 0, 0))
|
||||
connection._execute(1, prepare)
|
||||
|
||||
args, off = [], 1 # skip the function code, then walk [len(4)][value] args
|
||||
payload = sent[0]
|
||||
while off < len(payload):
|
||||
(length,) = struct.unpack(">i", payload[off:off + 4])
|
||||
args.append(payload[off + 4:off + 4 + length])
|
||||
off += 4 + length
|
||||
return bytearray(args[6])[0] # handle, flag, max_col_size, max_row, binds, fetch, auto_commit
|
||||
|
||||
def test_dml_is_committed_by_the_execute_request(self):
|
||||
self.assertEqual(self._auto_commit_byte(_cubrid._STMT_SELECT + 1), 1)
|
||||
|
||||
def test_select_does_not_end_the_transaction(self):
|
||||
self.assertEqual(self._auto_commit_byte(_cubrid._STMT_SELECT), 0)
|
||||
|
||||
|
||||
class BoundedReadTest(unittest.TestCase):
|
||||
"""A length taken off the wire is attacker/corruption controlled: unchecked, it either reads until
|
||||
memory runs out or (on a short buffer) hands back silently truncated data."""
|
||||
|
||||
def test_cubrid_short_response_is_not_silently_truncated(self):
|
||||
reader = _cubrid._Reader(b"AB")
|
||||
self.assertRaises(dbwire.InterfaceError, reader.raw, 8)
|
||||
self.assertRaises(dbwire.InterfaceError, reader.raw, -1)
|
||||
|
||||
def test_firebird_rejects_an_out_of_range_length(self):
|
||||
wire = _firebird._Wire(FakeSocket())
|
||||
self.assertRaises(dbwire.InterfaceError, wire.recv, -1)
|
||||
self.assertRaises(dbwire.InterfaceError, wire.recv, _firebird._MAX_MESSAGE_LENGTH + 1)
|
||||
|
||||
def test_monetdb_unterminated_block_stream_is_bounded(self):
|
||||
"""The MAPI block length is a 15-bit field, so only the accumulated response can be bounded."""
|
||||
|
||||
block = struct.pack("<H", (4000 << 1) | 0) + b"A" * 4000 # last-flag clear -> never ends
|
||||
sock = FakeSocket(block * 32)
|
||||
original = sock.recv
|
||||
|
||||
def recv(count):
|
||||
if not sock.inbound:
|
||||
sock.feed(block * 32)
|
||||
return original(count)
|
||||
|
||||
sock.recv = recv
|
||||
saved = _monetdb._MAX_MESSAGE_LENGTH
|
||||
try:
|
||||
_monetdb._MAX_MESSAGE_LENGTH = 100000
|
||||
self.assertRaises(dbwire.InterfaceError, _monetdb._getblock, sock)
|
||||
finally:
|
||||
_monetdb._MAX_MESSAGE_LENGTH = saved
|
||||
|
||||
|
||||
class HelperTest(unittest.TestCase):
|
||||
def test_socket_failure_maps_into_the_dbapi_hierarchy(self):
|
||||
"""Callers of a PEP 249 driver only catch Error and its subclasses."""
|
||||
|
||||
Reference in New Issue
Block a user