diff --git a/data/xml/queries.xml b/data/xml/queries.xml
index dfaabd38c8..b619a7a882 100644
--- a/data/xml/queries.xml
+++ b/data/xml/queries.xml
@@ -222,7 +222,7 @@
-
+
diff --git a/extra/dbwire/__init__.py b/extra/dbwire/__init__.py
new file mode 100644
index 0000000000..e48a84e4d5
--- /dev/null
+++ b/extra/dbwire/__init__.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+"""
+dbwire - minimal, dependency-free (stdlib-only) database wire-protocol clients used as a fallback for
+sqlmap's direct ('-d') connection when no native driver (and no SQLAlchemy) is installed.
+
+Design note: connectors speak a *wire protocol*, not a product, so a single client covers the whole
+compatible family - e.g. the PostgreSQL client also serves CockroachDB, CrateDB, Redshift and Greenplum;
+a MySQL client serves MariaDB/TiDB/Aurora; a TDS client serves MSSQL/Sybase. Each module exposes a small
+PEP 249 (DB-API 2.0) subset (connect(), Connection.cursor()/commit()/close(), Cursor.execute()/fetchall()).
+"""
+
+__version__ = "0.1"
+
+apilevel = "2.0"
+threadsafety = 1
+paramstyle = "pyformat"
+
+# PEP 249 exception hierarchy (shared by every wire module)
+class Error(Exception):
+ pass
+
+class InterfaceError(Error):
+ pass
+
+class DatabaseError(Error):
+ pass
+
+class OperationalError(DatabaseError):
+ pass
+
+class DataError(DatabaseError):
+ pass
+
+class IntegrityError(DatabaseError):
+ pass
+
+class ProgrammingError(DatabaseError):
+ pass
+
+class InternalError(DatabaseError):
+ pass
+
+class NotSupportedError(DatabaseError):
+ pass
diff --git a/extra/dbwire/clickhouse.py b/extra/dbwire/clickhouse.py
new file mode 100644
index 0000000000..b6a9cae588
--- /dev/null
+++ b/extra/dbwire/clickhouse.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+"""
+Minimal pure-python ClickHouse client over its native HTTP interface (stdlib only, no clickhouse_connect).
+
+ClickHouse exposes an HTTP endpoint that runs a query in the request body and streams the result back in a
+chosen format; we use TabSeparatedWithNames (first line = column names, then tab-separated rows with
+backslash escaping and \\N for NULL). Covers ClickHouse and its HTTP-compatible forks.
+"""
+
+import base64
+import socket
+
+try:
+ from urllib.request import Request, urlopen # Python 3
+ from urllib.error import HTTPError, URLError
+except ImportError:
+ from urllib2 import Request, urlopen, HTTPError, URLError # Python 2
+
+from extra.dbwire import OperationalError
+from extra.dbwire import ProgrammingError
+
+# TabSeparated backslash escapes -> the literal byte they denote
+_ESCAPE = {ord("t"): 9, ord("n"): 10, ord("r"): 13, ord("0"): 0, ord("b"): 8,
+ ord("f"): 12, ord("a"): 7, ord("v"): 11, ord("\\"): 92, ord("'"): 39}
+
+def _unescape(value):
+ # value: the raw bytes of one TSV field -> None (\N) or the unescaped bytes. Operates on bytes because a
+ # String/FixedString column can hold arbitrary non-UTF-8 data, which a whole-body utf-8 decode would destroy.
+ if value == b"\\N":
+ return None
+ if b"\\" not in value:
+ return value
+ src, out, i, n = bytearray(value), bytearray(), 0, len(value)
+ while i < n:
+ c = src[i]
+ if c == 0x5c and i + 1 < n: # backslash
+ out.append(_ESCAPE.get(src[i + 1], src[i + 1])); i += 2
+ else:
+ out.append(c); i += 1
+ return bytes(out)
+
+def _decode_cell(value):
+ # keep text as str; hand back raw bytes only when a value is not valid UTF-8 (sqlmap then hex-encodes it)
+ if value is None:
+ return None
+ try:
+ return value.decode("utf-8")
+ except UnicodeDecodeError:
+ return value
+
+class Cursor(object):
+ def __init__(self, connection):
+ self.connection = connection
+ self.description = None
+ self.rowcount = -1
+ self._rows = []
+ self._pos = 0
+
+ def execute(self, query, params=None):
+ if params is not None:
+ raise ProgrammingError("parameter binding is not supported; pass a fully-formed query string")
+ self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0
+ self.description, self._rows = self.connection._query(query)
+ self.rowcount = len(self._rows)
+ return self
+
+ def fetchall(self):
+ retVal = self._rows[self._pos:]
+ self._pos = len(self._rows)
+ return retVal
+
+ def fetchone(self):
+ if self._pos >= len(self._rows):
+ return None
+ retVal = self._rows[self._pos]
+ self._pos += 1
+ return retVal
+
+ def close(self):
+ self._rows = []
+
+class Connection(object):
+ def __init__(self, host, port, user, password, database, timeout):
+ self._url = "http://%s:%d/?database=%s&default_format=TabSeparatedWithNames" % (host, port, database or "default")
+ self._headers = {}
+ if user or password:
+ token = base64.b64encode(("%s:%s" % (user or "", password or "")).encode("utf-8")).decode("ascii")
+ self._headers["Authorization"] = "Basic %s" % token
+ self._timeout = timeout
+
+ def cursor(self):
+ return Cursor(self)
+
+ def commit(self):
+ pass # ClickHouse statements are executed immediately (no client-side transaction)
+
+ def rollback(self):
+ pass
+
+ def close(self):
+ pass # HTTP is stateless
+
+ def _query(self, query):
+ req = Request(self._url, data=query.encode("utf-8"), headers=self._headers)
+ try:
+ body = urlopen(req, timeout=self._timeout).read() # bytes: column data may be non-UTF-8
+ except HTTPError as ex:
+ raise ProgrammingError("(remote) %s" % ex.read().decode("utf-8", "replace").strip())
+ except URLError as ex:
+ raise OperationalError("(remote) %s" % ex)
+ except (socket.timeout, socket.error) as ex:
+ raise OperationalError("(remote) %s" % ex)
+
+ if not body:
+ return None, []
+ lines = body.split(b"\n")
+ if lines and lines[-1] == b"":
+ lines.pop()
+ if not lines:
+ return None, []
+ description = [(name, None, None, None, None, None, None) for name in (_decode_cell(_unescape(_)) for _ in lines[0].split(b"\t"))]
+ rows = [tuple(_decode_cell(_unescape(_)) for _ in line.split(b"\t")) for line in lines[1:]]
+ return description, rows
+
+def connect(host=None, port=8123, user=None, password=None, database=None, connect_timeout=None, **kwargs):
+ connection = Connection(host or "localhost", int(port or 8123), user, password, database, connect_timeout)
+ try:
+ connection._query("SELECT 1") # verify connectivity/credentials up front
+ except ProgrammingError:
+ raise
+ except Exception as ex:
+ raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex))
+ return connection
diff --git a/extra/dbwire/monetdb.py b/extra/dbwire/monetdb.py
new file mode 100644
index 0000000000..37f6db5c02
--- /dev/null
+++ b/extra/dbwire/monetdb.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+"""
+Minimal pure-python MonetDB MAPI client (stdlib only, no pymonetdb).
+
+MAPI is a block-framed text protocol: a 2-byte little-endian header (length << 1 | last-flag) wraps each
+block; login is a colon-separated challenge/response with a chosen password hash; queries are sent with an
+'s' prefix and results come back as '&' headers, '%' metadata and '[' tuples. Paging is disabled up front
+(Xreply_size -1) so a whole result set arrives at once.
+"""
+
+import hashlib
+import re
+import socket
+import struct
+
+from extra.dbwire import InterfaceError
+from extra.dbwire import NotSupportedError
+from extra.dbwire import OperationalError
+from extra.dbwire import ProgrammingError
+
+_MAX_BLOCK = 0xffff >> 1
+
+def _recvn(sock, n):
+ buf = b""
+ while len(buf) < n:
+ chunk = sock.recv(n - len(buf))
+ if not chunk:
+ raise InterfaceError("connection closed by server")
+ buf += chunk
+ return buf
+
+def _getblock(sock):
+ out = b""
+ while True:
+ (header,) = struct.unpack("> 1, header & 1
+ out += _recvn(sock, length)
+ if last:
+ break
+ return out.decode("utf-8", "replace")
+
+def _putblock(sock, text):
+ data = text.encode("utf-8")
+ off = 0
+ while True:
+ chunk = data[off:off + _MAX_BLOCK]
+ off += _MAX_BLOCK
+ last = off >= len(data)
+ sock.sendall(struct.pack("= 2 and value[0] == '"' and value[-1] == '"':
+ body = value[1:-1]
+ if "\\" not in body:
+ return body
+ # MonetDB renders control bytes as C octal escapes (\ooo) etc.; decode with unicode_escape but only
+ # on the ASCII runs so raw multibyte (> 0x7f) is preserved (mirrors pymonetdb's result decoding)
+ return "".join(seg.encode("utf-8").decode("unicode_escape") if "\\" in seg else seg
+ for seg in re.split(r"([\x00-\x7f]+)", body))
+ return value
+
+def _parse_result(text):
+ description, rows = None, []
+ for line in text.split("\n"):
+ if not line:
+ continue
+ marker = line[0]
+ if marker == "!": # error
+ raise ProgrammingError("(remote) %s" % line[1:].strip())
+ elif marker == "%": # metadata: " # "
+ payload, _, kind = line[1:].rpartition("#")
+ if kind.strip() == "name":
+ description = [(name.strip(), None, None, None, None, None, None) for name in payload.split(",\t")]
+ elif marker == "[": # tuple: "[ v1,\tv2,\t... ]"
+ body = line.strip()
+ if body.startswith("[") and body.endswith("]"):
+ body = body[1:-1].strip()
+ rows.append(tuple(_unquote(v.strip()) for v in body.split(",\t")))
+ # "&" result headers, "#" info, "=" no-slice tuples are ignored for our purposes
+ return description, rows
+
+class Cursor(object):
+ def __init__(self, connection):
+ self.connection = connection
+ self.description = None
+ self.rowcount = -1
+ self._rows = []
+ self._pos = 0
+
+ def execute(self, query, params=None):
+ if params is not None:
+ raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string")
+ self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0
+ self.description, self._rows = self.connection._query(query)
+ self.rowcount = len(self._rows)
+ return self
+
+ def fetchall(self):
+ retVal = self._rows[self._pos:]
+ self._pos = len(self._rows)
+ return retVal
+
+ def fetchone(self):
+ if self._pos >= len(self._rows):
+ return None
+ retVal = self._rows[self._pos]
+ self._pos += 1
+ return retVal
+
+ def close(self):
+ self._rows = []
+
+class Connection(object):
+ def __init__(self, sock):
+ self._sock = sock
+
+ def cursor(self):
+ return Cursor(self)
+
+ def commit(self):
+ pass # sqlmap runs autonomous statements; MonetDB SQL is auto-committed unless a transaction is opened
+
+ def rollback(self):
+ pass
+
+ def close(self):
+ try:
+ self._sock.close()
+ except Exception:
+ pass
+
+ def _command(self, text):
+ _putblock(self._sock, text)
+ reply = _getblock(self._sock)
+ if reply.startswith("!"):
+ raise OperationalError("(remote) %s" % reply[1:].strip())
+
+ def _query(self, query):
+ try:
+ _putblock(self._sock, "s" + query + ";\n")
+ return _parse_result(_getblock(self._sock))
+ except (socket.error, socket.timeout) as ex:
+ raise OperationalError("connection error: %s" % ex)
+ except (struct.error, IndexError, ValueError) as ex:
+ raise InterfaceError("malformed server response: %s" % ex)
+
+def connect(host=None, port=50000, user=None, password=None, database=None, connect_timeout=None, **kwargs):
+ host, port = host or "localhost", int(port or 50000)
+ try:
+ sock = socket.create_connection((host, port), timeout=connect_timeout)
+ sock.settimeout(None)
+ except (socket.error, socket.timeout) as ex:
+ raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex))
+
+ try:
+ for _ in range(10): # bounded: merovingian proxy stage + optional redirect + mserver challenge
+ block = _getblock(sock)
+ if block == "": # login accepted
+ break
+ if block[0] == "^": # redirect
+ url = block[1:].strip()
+ m = re.match(r"mapi:monetdb://([^:/]+):(\d+)/(\S*)", url)
+ if m and (m.group(1) != host or int(m.group(2)) != port):
+ sock.close()
+ host, port, database = m.group(1), int(m.group(2)), m.group(3) or database
+ sock = socket.create_connection((host, port), timeout=connect_timeout)
+ sock.settimeout(None)
+ continue # merovingian proxy redirect: keep reading the next challenge on this socket
+ if block[0] == "!":
+ raise OperationalError("(remote) %s" % block[1:].strip())
+ _putblock(sock, _challenge_response(block, user, password, database))
+ else:
+ raise OperationalError("MonetDB login did not converge")
+ except (OperationalError, NotSupportedError, InterfaceError):
+ try:
+ sock.close()
+ except Exception:
+ pass
+ raise
+ except (socket.error, socket.timeout) as ex: # I/O error during the login/redirect exchange
+ try:
+ sock.close()
+ except Exception:
+ pass
+ raise OperationalError("connection error: %s" % ex)
+
+ connection = Connection(sock)
+ try:
+ connection._command("Xreply_size -1\n") # disable row paging so a whole result set is returned at once
+ except (socket.error, socket.timeout) as ex:
+ connection.close()
+ raise OperationalError("connection error: %s" % ex)
+ return connection
diff --git a/extra/dbwire/mysql.py b/extra/dbwire/mysql.py
new file mode 100644
index 0000000000..4f9b672622
--- /dev/null
+++ b/extra/dbwire/mysql.py
@@ -0,0 +1,340 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+"""
+Minimal pure-python MySQL client/server protocol client (stdlib only).
+
+Covers the whole MySQL-wire family (MySQL, MariaDB, TiDB, Aurora-MySQL, Percona, ...). Auth:
+mysql_native_password (full), plus caching_sha2_password fast path; caching_sha2 *full* auth over a
+plaintext connection needs RSA (not in the stdlib), so that case raises a clean NotSupportedError - use a
+mysql_native_password account (as MariaDB/TiDB default to) for the dependency-free path.
+"""
+
+import hashlib
+import socket
+import struct
+
+from extra.dbwire import DatabaseError
+from extra.dbwire import InterfaceError
+from extra.dbwire import NotSupportedError
+from extra.dbwire import OperationalError
+from extra.dbwire import ProgrammingError
+
+# capability flags
+_CLIENT_LONG_PASSWORD = 0x00000001
+_CLIENT_LONG_FLAG = 0x00000004
+_CLIENT_CONNECT_WITH_DB = 0x00000008
+_CLIENT_PROTOCOL_41 = 0x00000200
+_CLIENT_TRANSACTIONS = 0x00002000
+_CLIENT_SECURE_CONNECTION = 0x00008000
+_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).
+# 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
+
+def _xor(a, b):
+ if str is bytes: # Python 2
+ return b"".join(chr(ord(x) ^ ord(y)) for x, y in zip(a, b))
+ return bytes(x ^ y for x, y in zip(a, b))
+
+def _u8(data, off):
+ return struct.unpack(" _MAX_MESSAGE_LENGTH:
+ raise InterfaceError("backend message too large (%d bytes)" % total)
+ payload += _recvn(sock, length)
+ return seq, payload
+
+def _send_packet(sock, seq, payload):
+ while True: # split payloads >= 16 MB into 0xffffff-sized packets (with a trailing short packet)
+ chunk = payload[:0xffffff]
+ sock.sendall(struct.pack(" len(data):
+ raise InterfaceError("length-encoded string overruns packet")
+ return data[off:off + length], off + length
+
+def _err_message(payload):
+ # ERR packet: 0xff, Int2 code, (if PROTOCOL_41) '#' + 5-byte SQLSTATE, then message
+ off = 3
+ if payload[3:4] == b"#":
+ off = 9
+ return payload[off:].decode("utf-8", "replace")
+
+def _scramble_native(password, salt):
+ if not password:
+ return b""
+ stage1 = hashlib.sha1(password.encode("utf-8")).digest()
+ stage2 = hashlib.sha1(stage1).digest()
+ return _xor(stage1, hashlib.sha1(salt + stage2).digest())
+
+def _scramble_sha2(password, salt):
+ if not password:
+ return b""
+ d1 = hashlib.sha256(password.encode("utf-8")).digest()
+ d2 = hashlib.sha256(hashlib.sha256(d1).digest() + salt).digest()
+ return _xor(d1, d2)
+
+class Cursor(object):
+ def __init__(self, connection):
+ self.connection = connection
+ self.description = None
+ self.rowcount = -1
+ self._rows = []
+ self._pos = 0
+
+ def execute(self, query, params=None):
+ if params is not None:
+ raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string")
+ self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0
+ self.description, self._rows, self.rowcount = self.connection._query(query)
+ return self
+
+ def fetchall(self):
+ retVal = self._rows[self._pos:]
+ self._pos = len(self._rows)
+ return retVal
+
+ def fetchone(self):
+ if self._pos >= len(self._rows):
+ return None
+ retVal = self._rows[self._pos]
+ self._pos += 1
+ return retVal
+
+ def close(self):
+ self._rows = []
+
+class Connection(object):
+ def __init__(self, sock):
+ self._sock = sock
+
+ def cursor(self):
+ return Cursor(self)
+
+ def commit(self):
+ pass # autocommit is enabled right after connect(), matching sqlmap's autonomous-statement model
+
+ def rollback(self):
+ pass
+
+ def close(self):
+ try:
+ _send_packet(self._sock, 0, b"\x01") # COM_QUIT
+ except Exception:
+ pass
+ try:
+ self._sock.close()
+ except Exception:
+ pass
+
+ def _query(self, query):
+ _send_packet(self._sock, 0, b"\x03" + query.encode("utf-8")) # COM_QUERY
+ try:
+ return self._read_query_response()
+ except (struct.error, IndexError, ValueError) as ex:
+ raise InterfaceError("malformed server response: %s" % ex)
+
+ def _read_query_response(self):
+ seq, payload = _read_packet(self._sock)
+ first = _u8(payload, 0)
+
+ if first == 0xff: # ERR
+ raise ProgrammingError("(remote) %s" % _err_message(payload))
+ if first == 0x00 or (first == 0xfe and len(payload) < 9): # OK packet (no result set)
+ affected, _ = _lenc_int(payload, 1)
+ return None, [], (affected if affected is not None else -1)
+ if first == 0xfb: # LOCAL INFILE request
+ raise NotSupportedError("LOCAL INFILE is not supported")
+
+ column_count, _ = _lenc_int(payload, 0)
+ description, binary = [], []
+ for _ in range(column_count):
+ _, cpay = _read_packet(self._sock)
+ off = 0
+ for _ in range(4): # catalog, schema, table, org_table
+ _, off = _lenc_str(cpay, off)
+ name, off = _lenc_str(cpay, off) # name
+ _, off = _lenc_str(cpay, off) # org_name
+ _, off = _lenc_int(cpay, off) # length of the fixed-length block (0x0c)
+ charset = struct.unpack(" end of rows
+ break
+ if _u8(payload, 0) == 0xff:
+ raise ProgrammingError("(remote) %s" % _err_message(payload))
+ off, row = 0, []
+ for i in range(column_count):
+ value, off = _lenc_str(payload, off)
+ if value is None:
+ row.append(None)
+ elif binary[i]:
+ row.append(value) # keep binary/BLOB columns as raw bytes (sqlmap hex-encodes them)
+ else:
+ row.append(value.decode("utf-8", "replace"))
+ rows.append(tuple(row))
+ return description, rows, len(rows)
+
+def _finish_auth(sock, password, plugin, salt):
+ # read the auth result, handling AuthSwitchRequest (0xfe) and AuthMoreData (0x01) for caching_sha2
+ while True:
+ seq, payload = _read_packet(sock)
+ marker = _u8(payload, 0)
+ if marker == 0x00: # OK
+ return
+ if marker == 0xff: # ERR
+ raise OperationalError("(remote) %s" % _err_message(payload))
+ if marker == 0xfe: # AuthSwitchRequest: \x00
+ plugin, off = _cstring(payload, 1)
+ plugin = plugin.decode("ascii", "replace")
+ salt = payload[off:].rstrip(b"\x00")
+ if plugin == "mysql_native_password":
+ data = _scramble_native(password, salt)
+ elif plugin == "caching_sha2_password":
+ data = _scramble_sha2(password, salt)
+ else:
+ raise NotSupportedError("unsupported authentication plugin '%s'" % plugin)
+ _send_packet(sock, seq + 1, data)
+ elif marker == 0x01: # AuthMoreData (caching_sha2)
+ status = _u8(payload, 1)
+ if status == 0x03: # fast auth success -> OK packet follows
+ continue
+ elif status == 0x04: # full auth required (needs TLS or RSA - not available stdlib-only)
+ raise NotSupportedError("caching_sha2_password full authentication over a plaintext connection "
+ "requires RSA/TLS; use a mysql_native_password account for the dependency-free client")
+ else:
+ raise OperationalError("unexpected caching_sha2 auth status %d" % status)
+ else:
+ raise InterfaceError("unexpected authentication response 0x%02x" % marker)
+
+def connect(host=None, port=3306, user=None, password=None, database=None, connect_timeout=None, **kwargs):
+ try:
+ sock = socket.create_connection((host or "localhost", int(port or 3306)), timeout=connect_timeout)
+ sock.settimeout(None)
+ except (socket.error, socket.timeout) as ex:
+ raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex))
+
+ try:
+ seq, payload = _read_packet(sock)
+ if _u8(payload, 0) == 0xff:
+ raise OperationalError("(remote) %s" % _err_message(payload))
+
+ off = 1 # protocol version (10)
+ _, off = _cstring(payload, off) # server version
+ off += 4 # connection id
+ salt = payload[off:off + 8]; off += 8 + 1 # auth-plugin-data part 1 (+ filler)
+ off += 2 # capability flags (lower)
+ off += 1 # character set
+ off += 2 # status flags
+ off += 2 # capability flags (upper)
+ auth_data_len = _u8(payload, off); off += 1
+ off += 10 # reserved
+ salt += payload[off:off + max(13, auth_data_len - 8) - 1] # part 2 (drop trailing NUL)
+ off += max(13, auth_data_len - 8)
+ plugin = "mysql_native_password"
+ if off < len(payload):
+ name, _ = _cstring(payload, off)
+ plugin = name.decode("ascii", "replace") or plugin
+
+ if plugin == "caching_sha2_password":
+ auth_response = _scramble_sha2(password or "", salt)
+ else:
+ plugin = "mysql_native_password"
+ auth_response = _scramble_native(password or "", salt)
+
+ flags = (_CLIENT_LONG_PASSWORD | _CLIENT_LONG_FLAG | _CLIENT_PROTOCOL_41 |
+ _CLIENT_TRANSACTIONS | _CLIENT_SECURE_CONNECTION | _CLIENT_PLUGIN_AUTH)
+ if database:
+ flags |= _CLIENT_CONNECT_WITH_DB
+ response = struct.pack(" DB-API exception, so callers can distinguish (mirrors psycopg2)
+_SQLSTATE_CLASS = {
+ "22": DataError, "23": IntegrityError,
+ "08": OperationalError, "28": OperationalError, "53": OperationalError,
+ "57": OperationalError, "58": OperationalError,
+}
+
+def _xor(a, b):
+ # byte-wise XOR of two equal-length byte strings (Python 2 and 3 safe)
+ if str is bytes: # Python 2: iterating bytes yields 1-char strings
+ return b"".join(chr(ord(x) ^ ord(y)) for x, y in zip(a, b))
+ return bytes(x ^ y for x, y in zip(a, b))
+
+def _recvn(sock, n):
+ buf = b""
+ while len(buf) < n:
+ chunk = sock.recv(n - len(buf))
+ if not chunk:
+ raise InterfaceError("connection closed by server")
+ buf += chunk
+ return buf
+
+def _read_message(sock):
+ mtype = _recvn(sock, 1)
+ (length,) = struct.unpack("!I", _recvn(sock, 4))
+ if length < 4 or length > _MAX_MESSAGE_LENGTH:
+ raise InterfaceError("invalid backend message length (%d)" % length)
+ return mtype, _recvn(sock, length - 4)
+
+def _send(sock, mtype, payload):
+ sock.sendall((mtype or b"") + struct.pack("!I", len(payload) + 4) + payload)
+
+def _error_message(payload):
+ # ErrorResponse/NoticeResponse: series of (byte field-code, cstring value), terminated by a NUL byte.
+ # Returns (human message, SQLSTATE). Tolerant of a truncated/unterminated stream (find() not index()).
+ fields, off = {}, 0
+ while off < len(payload) and payload[off:off + 1] != b"\x00":
+ code = payload[off:off + 1]
+ end = payload.find(b"\x00", off + 1)
+ if end == -1:
+ break
+ fields[code] = payload[off + 1:end].decode("utf-8", "replace")
+ off = end + 1
+ return fields.get(b"M", "unknown error"), fields.get(b"C", "")
+
+def _raise_server_error(message, sqlstate):
+ raise _SQLSTATE_CLASS.get((sqlstate or "")[:2], ProgrammingError)("(remote) %s" % message)
+
+class Cursor(object):
+ def __init__(self, connection):
+ self.connection = connection
+ self.description = None
+ self.rowcount = -1
+ self._rows = []
+ self._pos = 0
+
+ def execute(self, query, params=None):
+ if params is not None:
+ raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string")
+ self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 # reset before (a failed) query
+ self.description, self._rows, self._pos, self.rowcount = self.connection._simple_query(query)
+ return self
+
+ def fetchall(self):
+ retVal = self._rows[self._pos:]
+ self._pos = len(self._rows)
+ return retVal
+
+ def fetchone(self):
+ if self._pos >= len(self._rows):
+ return None
+ retVal = self._rows[self._pos]
+ self._pos += 1
+ return retVal
+
+ def close(self):
+ self._rows = []
+
+class Connection(object):
+ def __init__(self, sock):
+ self._sock = sock
+
+ def cursor(self):
+ return Cursor(self)
+
+ def commit(self):
+ pass # simple-query protocol commits each statement implicitly
+
+ def rollback(self):
+ pass
+
+ def close(self):
+ try:
+ _send(self._sock, b"X", b"") # Terminate
+ except Exception:
+ pass
+ try:
+ self._sock.close()
+ except Exception:
+ pass
+
+ def _simple_query(self, query):
+ _send(self._sock, b"Q", query.encode("utf-8") + b"\x00")
+
+ description, rows, rowcount, error = None, [], -1, None
+ while True:
+ mtype, payload = _read_message(self._sock)
+ try:
+ if mtype == b"T": # RowDescription (a new result set: reset rows so we return only the last one)
+ (count,) = struct.unpack("!H", payload[:2])
+ description, rows, rowcount, off = [], [], -1, 2
+ for _ in range(count):
+ end = payload.index(b"\x00", off)
+ name = payload[off:end].decode("utf-8", "replace")
+ off = end + 1
+ (typeoid,) = struct.unpack("!I", payload[off + 6:off + 10])
+ off += 18 # tableoid4 colno2 typeoid4 typelen2 typmod4 format2
+ description.append((name, typeoid, None, None, None, None, None))
+ elif mtype == b"D": # DataRow
+ (count,) = struct.unpack("!H", payload[:2])
+ off, row = 2, []
+ for _ in range(count):
+ (vlen,) = struct.unpack("!i", payload[off:off + 4])
+ off += 4
+ if vlen == -1:
+ row.append(None)
+ else:
+ if off + vlen > len(payload):
+ raise InterfaceError("truncated DataRow")
+ row.append(payload[off:off + vlen].decode("utf-8", "replace"))
+ off += vlen
+ rows.append(tuple(row))
+ elif mtype == b"C": # CommandComplete ("SELECT 3", "INSERT 0 1", ...)
+ tag = payload[:-1].decode("utf-8", "replace").split()
+ if tag and tag[-1].isdigit():
+ rowcount = int(tag[-1])
+ elif mtype == b"G": # CopyInResponse - server now waits for client CopyData; refuse to avoid a deadlock
+ _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)
+ break
+ # ParameterStatus(S)/NoticeResponse(N)/EmptyQueryResponse(I)/CopyData(d)/CopyDone(c)/... ignored
+ except (struct.error, IndexError, ValueError) as ex:
+ raise InterfaceError("malformed backend message: %s" % ex)
+ if error is not None:
+ _raise_server_error(*error)
+ return description, rows, 0, rowcount
+
+def _authenticate(sock, user, password):
+ cfirst_bare = None
+ while True:
+ mtype, payload = _read_message(sock)
+ if mtype in (b"N", b"S"): # NoticeResponse / ParameterStatus may legally precede AuthenticationOk
+ continue
+ if mtype == b"E":
+ _raise_server_error_as_operational(payload)
+ if mtype != b"R":
+ raise InterfaceError("unexpected message %r during authentication" % mtype)
+ (code,) = struct.unpack("!I", payload[:4])
+ if code == 0: # AuthenticationOk (also the trust case)
+ return
+ elif code == 3: # cleartext password
+ _send(sock, b"p", (password or "").encode("utf-8") + b"\x00")
+ elif code == 5: # MD5 password
+ salt = payload[4:8]
+ inner = hashlib.md5((password or "").encode("utf-8") + (user or "").encode("utf-8")).hexdigest()
+ token = b"md5" + hashlib.md5(inner.encode("ascii") + salt).hexdigest().encode("ascii")
+ _send(sock, b"p", token + b"\x00")
+ elif code == 10: # SASL (SCRAM-SHA-256)
+ if not hasattr(hashlib, "pbkdf2_hmac"):
+ raise NotSupportedError("SCRAM-SHA-256 authentication requires Python >= 2.7.8 (hashlib.pbkdf2_hmac)")
+ nonce = base64.b64encode(os.urandom(18)).decode("ascii")
+ cfirst_bare = "n=,r=%s" % nonce
+ client_first = "n,," + cfirst_bare
+ _send(sock, b"p", b"SCRAM-SHA-256\x00" + struct.pack("!I", len(client_first)) + client_first.encode("ascii"))
+ elif code == 11: # SASLContinue (server-first)
+ try:
+ server_first = payload[4:].decode("ascii")
+ attrs = dict(kv.split("=", 1) for kv in server_first.split(","))
+ snonce, salt, iterations = attrs["r"], base64.b64decode(attrs["s"]), int(attrs["i"])
+ except (KeyError, ValueError, binascii.Error, UnicodeDecodeError) as ex:
+ raise OperationalError("malformed SCRAM server-first message (%s)" % ex)
+ salted = hashlib.pbkdf2_hmac("sha256", (password or "").encode("utf-8"), salt, iterations)
+ client_key = hmac.new(salted, b"Client Key", hashlib.sha256).digest()
+ stored_key = hashlib.sha256(client_key).digest()
+ client_final_noproof = "c=biws,r=%s" % snonce
+ auth_message = "%s,%s,%s" % (cfirst_bare, server_first, client_final_noproof)
+ client_sig = hmac.new(stored_key, auth_message.encode("ascii"), hashlib.sha256).digest()
+ proof = base64.b64encode(_xor(client_key, client_sig)).decode("ascii")
+ _send(sock, b"p", ("%s,p=%s" % (client_final_noproof, proof)).encode("ascii"))
+ elif code == 12: # SASLFinal
+ pass
+ else:
+ raise InterfaceError("unsupported authentication request %d" % code)
+
+def _raise_server_error_as_operational(payload):
+ message, _ = _error_message(payload)
+ raise OperationalError("(remote) %s" % message)
+
+def connect(host=None, port=5432, user=None, password=None, database=None, connect_timeout=None, **kwargs):
+ try:
+ sock = socket.create_connection((host or "localhost", int(port or 5432)), timeout=connect_timeout)
+ sock.settimeout(None)
+ except (socket.error, socket.timeout) as ex:
+ raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex))
+
+ params = b""
+ 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:
+ _authenticate(sock, user, password)
+ while True: # drain until ReadyForQuery (ParameterStatus/BackendKeyData/NoticeResponse)
+ mtype, payload = _read_message(sock)
+ if mtype == b"E":
+ _raise_server_error_as_operational(payload)
+ if mtype == b"Z":
+ break
+ except Exception: # any setup failure (DB-API or otherwise) must still close the socket
+ try:
+ sock.close()
+ except Exception:
+ pass
+ raise
+
+ return Connection(sock)
diff --git a/extra/dbwire/presto.py b/extra/dbwire/presto.py
new file mode 100644
index 0000000000..7deede433a
--- /dev/null
+++ b/extra/dbwire/presto.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+"""
+Minimal pure-python Presto/Trino client over its native HTTP/REST interface (stdlib only, no
+presto-python-client). A query is POSTed to /v1/statement; the server returns JSON pages carrying
+'columns'/'data' and a 'nextUri' to poll until the statement finishes. Both X-Presto-* and X-Trino-*
+headers are sent so the same client works against Presto and Trino.
+"""
+
+import base64
+import json
+import socket
+
+try:
+ from urllib.request import Request, urlopen # Python 3
+ from urllib.error import HTTPError, URLError
+except ImportError:
+ from urllib2 import Request, urlopen, HTTPError, URLError # Python 2
+
+from extra.dbwire import InterfaceError
+from extra.dbwire import NotSupportedError
+from extra.dbwire import OperationalError
+from extra.dbwire import ProgrammingError
+
+def _convert(value, coltype):
+ # normalize Presto/Trino JSON cells for sqlmap: VARBINARY arrives base64-encoded (decode to bytes so
+ # direct()'s binary handling hex-encodes it), ARRAY/MAP/ROW arrive as JSON structures (serialize to text)
+ if value is None:
+ return value
+ if coltype.startswith("varbinary"):
+ try:
+ return base64.b64decode(value)
+ except Exception:
+ return value
+ if isinstance(value, (list, dict)):
+ return json.dumps(value)
+ return value
+
+class Cursor(object):
+ def __init__(self, connection):
+ self.connection = connection
+ self.description = None
+ self.rowcount = -1
+ self._rows = []
+ self._pos = 0
+
+ def execute(self, query, params=None):
+ if params is not None:
+ raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string")
+ self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0
+ self.description, self._rows = self.connection._query(query)
+ self.rowcount = len(self._rows)
+ return self
+
+ def fetchall(self):
+ retVal = self._rows[self._pos:]
+ self._pos = len(self._rows)
+ return retVal
+
+ def fetchone(self):
+ if self._pos >= len(self._rows):
+ return None
+ retVal = self._rows[self._pos]
+ self._pos += 1
+ return retVal
+
+ def close(self):
+ self._rows = []
+
+class Connection(object):
+ def __init__(self, host, port, user, password, catalog, schema, timeout):
+ self._statement_url = "http://%s:%d/v1/statement" % (host, port)
+ self._timeout = timeout
+ self._headers = {"Content-Type": "text/plain"}
+ for prefix in ("X-Presto-", "X-Trino-"):
+ self._headers[prefix + "User"] = user or "sqlmap"
+ self._headers[prefix + "Source"] = "dbwire"
+ # only send Catalog/Schema when supplied: a Schema without a Catalog makes Trino reject every
+ # request ("Schema is set but catalog is not"), so never force a "default" schema
+ if catalog:
+ self._headers[prefix + "Catalog"] = catalog
+ if schema:
+ self._headers[prefix + "Schema"] = schema
+ if password:
+ token = base64.b64encode(("%s:%s" % (user or "", password)).encode("utf-8")).decode("ascii")
+ self._headers["Authorization"] = "Basic %s" % token
+
+ def cursor(self):
+ return Cursor(self)
+
+ def commit(self):
+ pass
+
+ def rollback(self):
+ pass
+
+ def close(self):
+ pass # HTTP is stateless
+
+ def _request(self, url, data=None):
+ req = Request(url, data=data.encode("utf-8") if data is not None else None, headers=self._headers)
+ try:
+ body = urlopen(req, timeout=self._timeout).read().decode("utf-8", "replace")
+ except HTTPError as ex:
+ raise ProgrammingError("(remote) HTTP %s: %s" % (ex.code, ex.read().decode("utf-8", "replace")[:200]))
+ except URLError as ex:
+ raise OperationalError("(remote) %s" % ex)
+ except (socket.timeout, socket.error) as ex:
+ raise OperationalError("(remote) %s" % ex)
+ try:
+ return json.loads(body)
+ except ValueError as ex:
+ raise InterfaceError("malformed server response: %s" % ex)
+
+ def _query(self, query):
+ page = self._request(self._statement_url, data=query)
+ columns, rows, types = None, [], []
+ while True:
+ if page.get("error"):
+ message = page["error"].get("message", "unknown error")
+ raise ProgrammingError("(remote) %s" % message)
+ if page.get("columns") and columns is None:
+ columns = [(c.get("name"), c.get("type"), None, None, None, None, None) for c in page["columns"]]
+ types = [(c.get("type") or "") for c in page["columns"]]
+ for row in page.get("data") or []:
+ rows.append(tuple(_convert(v, types[i] if i < len(types) else "") for i, v in enumerate(row)))
+ next_uri = page.get("nextUri")
+ if not next_uri:
+ break
+ page = self._request(next_uri)
+ return columns, rows
+
+def connect(host=None, port=8080, user=None, password=None, database=None, connect_timeout=None, schema=None, **kwargs):
+ connection = Connection(host or "localhost", int(port or 8080), user, password, database, schema, connect_timeout)
+ try:
+ connection._query("SELECT 1") # verify connectivity/credentials
+ except ProgrammingError:
+ raise
+ except Exception as ex:
+ raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex))
+ return connection
diff --git a/extra/dbwire/tds.py b/extra/dbwire/tds.py
new file mode 100644
index 0000000000..28ec16d041
--- /dev/null
+++ b/extra/dbwire/tds.py
@@ -0,0 +1,608 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+"""
+Minimal pure-python TDS (Tabular Data Stream) client for Microsoft SQL Server / Sybase (stdlib only).
+
+Cleartext login only (TDS pre-login encryption negotiated to NOT_SUP); a server that forces encryption
+would need TLS-in-TDS which is out of scope for the dependency-free client. Implements PRELOGIN, LOGIN7,
+SQL batch, and decoding of the common column types (int/bit/float/money/decimal, (n)char/(n)varchar and
+their MAX/PLP forms, binary, guid, datetime family) to text (binary columns are returned as raw bytes so
+sqlmap hex-encodes them).
+"""
+
+import socket
+import struct
+
+from extra.dbwire import DatabaseError
+from extra.dbwire import InterfaceError
+from extra.dbwire import NotSupportedError
+from extra.dbwire import OperationalError
+from extra.dbwire import ProgrammingError
+
+_MAX_MESSAGE_LENGTH = 0x40000000
+
+# packet types
+_PKT_SQL_BATCH = 0x01
+_PKT_LOGIN7 = 0x10
+_PKT_PRELOGIN = 0x12
+_STATUS_EOM = 0x01
+
+def _u8(data, off):
+ return struct.unpack("= len(data)
+ header = struct.pack(">BBHHBB", mtype, _STATUS_EOM if last else 0x00, len(chunk) + 8, 0, packet_id & 0xff, 0)
+ sock.sendall(header + chunk)
+ packet_id += 1
+ if last:
+ break
+
+def _read_message(sock):
+ # reassemble a full TDS message across packets (EOM status bit marks the last)
+ body = b""
+ while True:
+ header = _recvn(sock, 8)
+ mtype, status, length = struct.unpack(">BBH", header[:4])
+ if length < 8 or length > _MAX_MESSAGE_LENGTH:
+ raise InterfaceError("invalid TDS packet length (%d)" % length)
+ body += _recvn(sock, length - 8)
+ if status & _STATUS_EOM:
+ break
+ return body
+
+# ---- PRELOGIN ----------------------------------------------------------------------------------------
+
+def _prelogin(sock):
+ ver = struct.pack(">IH", 0x11000000, 0)
+ enc = b"\x02" # ENCRYPT_NOT_SUP
+ tokens = b"\x00" + struct.pack(">HH", 11, len(ver))
+ tokens += b"\x01" + struct.pack(">HH", 11 + len(ver), len(enc))
+ tokens += b"\xff"
+ _send_message(sock, _PKT_PRELOGIN, tokens + ver + enc)
+ body = _read_message(sock)
+ off = 0
+ while off < len(body) and _u8(body, off) != 0xff:
+ token = _u8(body, off)
+ toff, tlen = struct.unpack(">HH", body[off + 1:off + 5])
+ if token == 0x01 and _u8(body, toff) == 0x03: # server requires encryption
+ raise NotSupportedError("server requires TDS encryption; the dependency-free client supports cleartext only")
+ off += 5
+
+# ---- LOGIN7 ------------------------------------------------------------------------------------------
+
+def _encode_password(password):
+ out = bytearray()
+ for b in bytearray(password.encode("utf-16-le")):
+ b = ((b << 4) & 0xf0) | ((b >> 4) & 0x0f)
+ out.append(b ^ 0xa5)
+ return bytes(out)
+
+def _login7(sock, user, password, database, hostname="dbwire", appname="dbwire"):
+ fields = [
+ hostname.encode("utf-16-le"),
+ (user or "").encode("utf-16-le"),
+ _encode_password(password or ""),
+ appname.encode("utf-16-le"),
+ b"", # server name
+ b"", # (extension / unused)
+ "dbwire".encode("utf-16-le"), # client interface name
+ b"", # language
+ (database or "").encode("utf-16-le"),
+ ]
+ char_counts = [6, len(user or ""), len(password or ""), 6, 0, 0, 6, 0, len(database or "")]
+
+ base = 94 # fixed header (36) + offset/length block (58)
+ var, offsets, cursor = b"", b"", base
+ for i, data in enumerate(fields):
+ offsets += struct.pack("= 0 else ("-", -offset)
+ s += " %s%02d:%02d" % (sign, mins // 60, mins % 60)
+ return s
+
+# SQL Server COLLATION -> Python codec. The 5-byte collation is a little-endian uint32 (low 20 bits = LCID)
+# plus a 1-byte sort id: a non-zero sort id fixes the code page, else the LCID does. Only single-byte / DBCS
+# code pages need a codec (NVARCHAR is UTF-16, handled separately). Derived from pytds; default cp1252 (the
+# stock SQL_Latin1_General code page - NOT latin-1, whose 0x80-0x9F differ, corrupting e.g. the euro sign).
+_LCID_CP = {
+ 0x405: "cp1250", 0x40e: "cp1250", 0x415: "cp1250", 0x418: "cp1250", 0x41a: "cp1250", 0x41b: "cp1250",
+ 0x41c: "cp1250", 0x424: "cp1250", 0x402: "cp1251", 0x419: "cp1251", 0x422: "cp1251", 0x423: "cp1251",
+ 0x42f: "cp1251", 0x408: "cp1253", 0x41f: "cp1254", 0x42c: "cp1254", 0x443: "cp1254", 0x40d: "cp1255",
+ 0x401: "cp1256", 0x420: "cp1256", 0x429: "cp1256", 0x425: "cp1257", 0x426: "cp1257", 0x427: "cp1257",
+ 0x42a: "cp1258", 0x41e: "cp874", 0x411: "cp932", 0x804: "cp936", 0x1004: "cp936", 0x412: "cp949",
+ 0x404: "cp950", 0xc04: "cp950", 0x1404: "cp950",
+}
+
+def _sortid_cp(sid):
+ if 30 <= sid <= 34:
+ return "cp437"
+ if 40 <= sid <= 44 or sid == 49 or 55 <= sid <= 61:
+ return "cp850"
+ if sid in (51, 52, 53, 54) or 183 <= sid <= 186:
+ return "cp1252"
+ if 80 <= sid <= 96:
+ return "cp1250"
+ if 104 <= sid <= 108:
+ return "cp1251"
+ if 112 <= sid <= 124:
+ return "cp1253"
+ if 128 <= sid <= 130:
+ return "cp1254"
+ if 136 <= sid <= 138:
+ return "cp1255"
+ if 144 <= sid <= 146:
+ return "cp1256"
+ if 152 <= sid <= 160:
+ return "cp1257"
+ return None
+
+def _collation_codec(collation):
+ if not collation or len(collation) < 5:
+ return "cp1252"
+ lump = struct.unpack(" raw bytes
+ if base in (0xa7, 0xaf): # (var)char: metadata = 5-byte collation + 2-byte max length
+ return val.decode(_collation_codec(meta[:5]), "replace")
+ if base == 0x28:
+ return _decode_temporal(base, 0, val)
+ if base in (0x29, 0x2a, 0x2b): # metadata = scale
+ return _decode_temporal(base, bytearray(meta)[0], val)
+ return "".join("%02x" % x for x in bytearray(val)) # unknown base type -> hex (never desyncs)
+
+class _Column(object):
+ __slots__ = ("name", "type", "size", "scale", "binary", "collation")
+
+def _parse_type_info(data, off):
+ col = _Column()
+ col.type = _u8(data, off); off += 1
+ col.size, col.scale, col.binary, col.collation = 0, 0, False, None
+ t = col.type
+ if t in (0x30, 0x32, 0x34, 0x38, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x7a, 0x7f, 0x1f):
+ pass # fixed-length types, size implied by type
+ elif t in (0x26, 0x68, 0x6d, 0x6e, 0x6f, 0x24): # INTN/BITN/FLTN/MONEYN/DATETIMN/GUID
+ col.size = _u8(data, off); off += 1
+ elif t in (0x6a, 0x6c, 0x37, 0x3f): # DECIMALN/NUMERICN + legacy DECIMAL/NUMERIC (size, precision, scale)
+ col.size = _u8(data, off); off += 1
+ off += 1 # precision
+ col.scale = _u8(data, off); off += 1
+ elif t in (0xa7, 0xaf, 0xe7, 0xef): # (BIG)VARCHAR/CHAR, N(VAR)CHAR
+ col.size = struct.unpack(" raw bytes
+ if t in (0xe7, 0xef):
+ return raw.decode("utf-16-le", "replace"), off
+ return raw.decode(_collation_codec(col.collation), "replace"), off # (var)char: the collation's code page
+
+ if t in (0x23, 0x63, 0x22): # TEXT/NTEXT/IMAGE: 1-byte textptr len (0 = NULL) then textptr+timestamp then 4-byte len
+ ptr_len = _u8(data, off); off += 1
+ if ptr_len == 0:
+ return None, off
+ off += ptr_len + 8
+ (n,) = struct.unpack(" sqlmap hex-encodes them
+ return _read_plp(data, off)
+
+ if t == 0x62: # SQL_VARIANT: 4-byte total length (0 = NULL) then a self-describing value body
+ (total,) = struct.unpack("= len(self._rows):
+ return None
+ retVal = self._rows[self._pos]
+ self._pos += 1
+ return retVal
+
+ def close(self):
+ self._rows = []
+
+class Connection(object):
+ def __init__(self, sock):
+ self._sock = sock
+
+ def cursor(self):
+ return Cursor(self)
+
+ def commit(self):
+ pass # sqlmap issues autonomous statements; SET IMPLICIT_TRANSACTIONS is off by default
+
+ def rollback(self):
+ pass
+
+ def close(self):
+ try:
+ self._sock.close()
+ except Exception:
+ pass
+
+ def _query(self, query):
+ # TDS 7.2+ SQL batch must be prefixed with ALL_HEADERS carrying the transaction descriptor header
+ headers = struct.pack(" pure-python 'extra/dbwire' wire-protocol module, used as a dependency-free '-d' fallback when
+# neither a native driver nor SQLAlchemy is installed (a single module serves the whole compatible family,
+# e.g. 'postgres' also covers CockroachDB/CrateDB/Redshift/Greenplum)
+DBWIRE_MODULES = {
+ DBMS.PGSQL: "postgres",
+ DBMS.CRATEDB: "postgres", # CrateDB speaks the PostgreSQL wire protocol
+ DBMS.MYSQL: "mysql",
+ DBMS.MSSQL: "tds",
+ DBMS.SYBASE: "tds",
+ DBMS.CLICKHOUSE: "clickhouse",
+ DBMS.MONETDB: "monetdb",
+ DBMS.PRESTO: "presto",
+}
+
# Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/
FROM_DUMMY_TABLE = {
DBMS.ORACLE: " FROM DUAL",
diff --git a/lib/core/settings.py b/lib/core/settings.py
index fba21d81b4..814a1a1c49 100644
--- a/lib/core/settings.py
+++ b/lib/core/settings.py
@@ -20,7 +20,7 @@
from thirdparty import six
# sqlmap version (...)
-VERSION = "1.10.7.84"
+VERSION = "1.10.7.87"
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/utils/dbwire.py b/lib/utils/dbwire.py
new file mode 100644
index 0000000000..ce81a0fb20
--- /dev/null
+++ b/lib/utils/dbwire.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
+See the file 'LICENSE' for copying permission
+"""
+
+import importlib
+import logging
+
+import extra.dbwire
+
+from lib.core.common import getSafeExString
+from lib.core.data import conf
+from lib.core.data import logger
+from lib.core.exception import SqlmapConnectionException
+from plugins.generic.connector import Connector as GenericConnector
+
+class Connector(GenericConnector):
+ """
+ Adapter exposing sqlmap's connector interface over a dependency-free 'extra/dbwire' pure-python
+ wire-protocol client. Used for '-d' when neither a native driver nor SQLAlchemy is available.
+ """
+
+ def __init__(self, module):
+ GenericConnector.__init__(self)
+ self._driver = importlib.import_module("extra.dbwire.%s" % module)
+
+ def connect(self):
+ self.initConnection()
+
+ try:
+ self.connector = self._driver.connect(host=self.hostname, port=self.port, user=self.user, password=self.password, database=self.db, connect_timeout=conf.timeout)
+ except extra.dbwire.Error as ex:
+ raise SqlmapConnectionException(getSafeExString(ex))
+
+ self.initCursor()
+ self.printConnected()
+
+ def fetchall(self):
+ try:
+ return self.cursor.fetchall()
+ except extra.dbwire.Error as ex:
+ logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex))
+ return None
+
+ def execute(self, query):
+ try:
+ self.cursor.execute(query)
+ except extra.dbwire.Error as ex:
+ logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex))
+
+ self.connector.commit()
+
+ def select(self, query):
+ self.execute(query)
+ return self.fetchall()