From 05005dde58329b6e76b235e40df06c20479c8387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 12:55:58 +0200 Subject: [PATCH] Minor update of dbwire --- extra/dbwire/README.md | 68 ++++ extra/dbwire/firebird.py | 838 +++++++++++++++++++++++++++++++++++++++ lib/core/dicts.py | 1 + lib/core/settings.py | 2 +- 4 files changed, 908 insertions(+), 1 deletion(-) create mode 100644 extra/dbwire/README.md create mode 100644 extra/dbwire/firebird.py diff --git a/extra/dbwire/README.md b/extra/dbwire/README.md new file mode 100644 index 000000000..857384129 --- /dev/null +++ b/extra/dbwire/README.md @@ -0,0 +1,68 @@ +# dbwire + +Minimal, dependency-free database wire-protocol clients used as a fallback for sqlmap's direct +(`-d`) connection mode. + +## What this is + +sqlmap's `-d` mode talks to a database server directly instead of through an injection point. It +normally does so via a native driver (`psycopg2`, `pymysql`, `pymssql`, ...) or, if one is missing, +via SQLAlchemy. When neither is installed, `dbwire` provides a small pure-python client so that `-d` +still works out of the box. + +Every module here is written against the database's **wire protocol** using only the Python standard +library (`socket`, `struct`, `hashlib`, `hmac`, `base64`, `urllib`). There are no third-party +dependencies, and the sources run unmodified on Python 2.7 and Python 3. + +## Coverage + +A wire protocol is shared across a whole family of products, so one client serves several engines: + +| Module | Protocol | Engines | +|-----------------|---------------------|---------| +| `postgres.py` | PostgreSQL v3 | PostgreSQL, CockroachDB, CrateDB, Redshift, Greenplum | +| `mysql.py` | MySQL client/server | MySQL, MariaDB, TiDB, Aurora (MySQL), Percona | +| `tds.py` | TDS | Microsoft SQL Server, Sybase | +| `firebird.py` | Firebird wire | Firebird 3 / 4 / 5 | +| `clickhouse.py` | HTTP (TabSeparated) | ClickHouse and HTTP-compatible forks | +| `monetdb.py` | MAPI | MonetDB | +| `presto.py` | HTTP/REST (JSON) | Presto, Trino | + +The mapping from a DBMS to its module lives in `lib/core/dicts.py` (`DBWIRE_MODULES`). The connector +tier order is: native driver, then SQLAlchemy, then dbwire. + +## Interface + +Each module exposes a small [PEP 249](https://peps.python.org/pep-0249/) (DB-API 2.0) subset: + +- `connect(host, port, user, password, database, connect_timeout, ...)` -> `Connection` +- `Connection.cursor()`, `.commit()`, `.rollback()`, `.close()` +- `Cursor.execute(query)`, `.fetchall()`, `.fetchone()`, `.close()`, `.description`, `.rowcount` +- the shared exception hierarchy in `__init__.py` (`Error` -> `InterfaceError` / `DatabaseError` -> + `OperationalError` / `DataError` / `IntegrityError` / `ProgrammingError` / `InternalError` / + `NotSupportedError`) + +It is deliberately read-oriented for sqlmap's use: `execute()` takes a fully-formed query string +(no parameter binding), statements auto-commit, and binary column values are returned as `bytes` so +that sqlmap renders them as hex. + +## Scope and limitations + +This is a fallback for `-d`, not a general-purpose driver. It intentionally does not implement +parameter binding, prepared statements, bulk load/`COPY`, or TLS. Notable per-protocol notes: + +- **PostgreSQL** - `trust`, cleartext, MD5 and SCRAM-SHA-256 authentication. +- **MySQL** - `mysql_native_password` and the `caching_sha2_password` fast path. Full + `caching_sha2_password` authentication over a plaintext connection requires RSA/TLS and is not + supported; use a `mysql_native_password` account for the dependency-free path. +- **TDS** - cleartext login only. Servers that force encryption (for example Azure SQL Database) + require TLS and are not supported here; the native driver or SQLAlchemy tier covers those. +- **Firebird** - SRP-256 (and SRP) authentication with ChaCha20 or RC4 wire encryption, as required by + default on Firebird 3 and later. Legacy (pre-SRP) authentication is not implemented. + +When a case is not supported, the client raises a clear `NotSupportedError` rather than returning +wrong data, so `-d` cleanly falls through to another connector tier where possible. + +--- + +Part of [sqlmap](https://sqlmap.org). See the top-level `LICENSE` for copying permission. diff --git a/extra/dbwire/firebird.py b/extra/dbwire/firebird.py new file mode 100644 index 000000000..0e1c8ec5a --- /dev/null +++ b/extra/dbwire/firebird.py @@ -0,0 +1,838 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python Firebird wire-protocol client (stdlib only, no firebirdsql). + +Speaks the Firebird v13-17 protocol (Firebird 3/4/5): op_connect, SRP-256 authentication, ChaCha20 (or +Arc4) wire encryption - which Firebird 4+ requires by default - then attach / transaction / prepare / +execute / fetch with XSQLDA column description and row decoding. Read-oriented for sqlmap: execute() takes +a fully-formed query string, binary/blob values come back as bytes (sqlmap hex-encodes them). +""" + +import datetime +import hashlib +import os +import socket +import struct + +from extra.dbwire import DatabaseError +from extra.dbwire import DataError +from extra.dbwire import IntegrityError +from extra.dbwire import InterfaceError +from extra.dbwire import NotSupportedError +from extra.dbwire import OperationalError + +# operation codes +_op_connect = 1 +_op_accept = 3 +_op_reject = 4 +_op_response = 9 +_op_attach = 19 +_op_detach = 21 +_op_transaction = 29 +_op_commit_retaining = 50 +_op_rollback_retaining = 86 +_op_get_segment = 36 +_op_close_blob = 39 +_op_open_blob2 = 56 +_op_allocate_statement = 62 +_op_execute = 63 +_op_fetch = 65 +_op_fetch_response = 66 +_op_free_statement = 67 +_op_prepare_statement = 68 +_op_info_sql = 70 +_op_dummy = 71 +_op_cont_auth = 92 +_op_crypt = 96 +_op_accept_data = 94 +_op_cond_accept = 98 + +# CNCT parameter codes +_CNCT_user = 1 +_CNCT_host = 4 +_CNCT_user_verification = 6 +_CNCT_specific_data = 7 +_CNCT_plugin_name = 8 +_CNCT_login = 9 +_CNCT_plugin_list = 10 +_CNCT_client_crypt = 11 + +# database / transaction parameter block items +_isc_dpb_version1 = 1 +_isc_dpb_user_name = 28 +_isc_dpb_lc_ctype = 48 +_isc_dpb_process_id = 71 +_isc_dpb_process_name = 74 +_isc_tpb_version3 = 3 +_isc_tpb_wait = 6 +_isc_tpb_write = 9 +_isc_tpb_read_committed = 15 +_isc_tpb_rec_version = 17 + +# isc_info_sql_* describe items +_isc_info_end = 1 +_isc_info_truncated = 2 +_isc_info_sql_select = 4 +_isc_info_sql_describe_vars = 7 +_isc_info_sql_describe_end = 8 +_isc_info_sql_sqlda_seq = 9 +_isc_info_sql_type = 11 +_isc_info_sql_sub_type = 12 +_isc_info_sql_scale = 13 +_isc_info_sql_length = 14 +_isc_info_sql_null_ind = 15 +_isc_info_sql_field = 16 +_isc_info_sql_relation = 17 +_isc_info_sql_owner = 18 +_isc_info_sql_alias = 19 +_isc_info_sql_sqlda_start = 20 +_isc_info_sql_stmt_type = 21 +_INFO_SQL_SELECT_DESCRIBE_VARS = bytes(bytearray([ + _isc_info_sql_select, _isc_info_sql_describe_vars, _isc_info_sql_sqlda_seq, + _isc_info_sql_type, _isc_info_sql_sub_type, _isc_info_sql_scale, _isc_info_sql_length, + _isc_info_sql_null_ind, _isc_info_sql_field, _isc_info_sql_relation, _isc_info_sql_owner, + _isc_info_sql_alias, _isc_info_sql_describe_end])) + +_isc_info_sql_stmt_select = 1 +_DSQL_drop = 2 + +# SQL type codes +_SQL_VARYING = 448 +_SQL_TEXT = 452 +_SQL_DOUBLE = 480 +_SQL_FLOAT = 482 +_SQL_LONG = 496 +_SQL_SHORT = 500 +_SQL_TIMESTAMP = 510 +_SQL_BLOB = 520 +_SQL_TIME = 560 +_SQL_DATE = 570 +_SQL_INT64 = 580 +_SQL_INT128 = 32752 +_SQL_TIMESTAMP_TZ = 32754 +_SQL_TIME_TZ = 32756 +_SQL_BOOLEAN = 32764 +_SQL_TYPE_LENGTH = { # fixed on-the-wire length by SQL type (VARYING is length-prefixed -> -1) + _SQL_VARYING: -1, _SQL_SHORT: 4, _SQL_LONG: 4, _SQL_FLOAT: 4, _SQL_TIME: 4, _SQL_DATE: 4, + _SQL_DOUBLE: 8, _SQL_TIMESTAMP: 8, _SQL_BLOB: 8, _SQL_INT64: 8, _SQL_INT128: 16, + _SQL_TIMESTAMP_TZ: 12, _SQL_TIME_TZ: 8, _SQL_BOOLEAN: 1, +} +# per-type output BLR fragment used to describe the fetched row (see calc_blr) +_SQL_TYPE_BLR = { + _SQL_DOUBLE: [27], _SQL_FLOAT: [10], _SQL_DATE: [12], _SQL_TIME: [13], _SQL_TIMESTAMP: [35], + _SQL_BLOB: [9, 0], _SQL_BOOLEAN: [23], _SQL_TIME_TZ: [28], _SQL_TIMESTAMP_TZ: [29], +} + +# status-vector argument tags +_isc_arg_end = 0 +_isc_arg_gds = 1 +_isc_arg_string = 2 +_isc_arg_number = 4 +_isc_arg_interpreted = 5 +_isc_arg_sql_state = 19 +_GDS_INTEGRITY = frozenset((335544838, 335544879, 335544880, 335544466, 335544665, 335544347, 335544558)) +_GDS_DATA = frozenset((335544321,)) +_GDS_WARNING = 335544434 + +# SRP-6a group used by Firebird (fixed 1024-bit prime, generator 2) +_SRP_N = int("E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDE" + "BF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F" + "1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7", 16) +_SRP_g = 2 +_SRP_k = 1277432915985975349439481660349303019122249719989 + +def _minbe(n): + # minimal big-endian bytes of a non-negative integer (matches firebirdsql long2bytes/pad for these sizes) + out = bytearray() + while n > 0: + out.insert(0, n & 0xff) + n >>= 8 + return bytes(out) + +def _b2l(b): + n = 0 + for c in bytearray(b): + n = (n << 8) | c + return n + +def _sha1(*parts): + h = hashlib.sha1() + for p in parts: + h.update(p if isinstance(p, bytes) else _minbe(p)) + return h.digest() + +def _srp_client_seed(): + a = _b2l(os.urandom(16)) # client private key (128-bit) + return pow(_SRP_g, a, _SRP_N), a + +def _srp_client_proof(user, password, salt, A, B, a, hash_algo): + # session key K (always SHA-1) then the Firebird-specific proof M (SHA-1 for Srp, SHA-256 for Srp256) + u = _b2l(_sha1(_minbe(A), _minbe(B))) + x = _b2l(_sha1(salt, _sha1(user, b":", password))) + S = pow((B - _SRP_k * pow(_SRP_g, x, _SRP_N)) % _SRP_N, (a + u * x) % _SRP_N, _SRP_N) + K = _sha1(_minbe(S)) + n1 = _b2l(_sha1(_minbe(_SRP_N))) + n2 = _b2l(_sha1(_minbe(_SRP_g))) + n1 = pow(n1, n2, _SRP_N) # NOTE: modular exponentiation, not XOR (Firebird quirk) + n2 = _b2l(_sha1(user)) + h = hash_algo() + for p in (_minbe(n1), _minbe(n2), salt, _minbe(A), _minbe(B), K): + h.update(p) + return h.digest(), K + +class _ARC4(object): + def __init__(self, key): + s = list(range(256)) + key = bytearray(key) + j = 0 + for i in range(256): + j = (j + s[i] + key[i % len(key)]) & 0xff + s[i], s[j] = s[j], s[i] + self._s, self._i, self._j = s, 0, 0 + + def translate(self, data): + s, i, j, out = self._s, self._i, self._j, bytearray() + for c in bytearray(data): + i = (i + 1) & 0xff + j = (j + s[i]) & 0xff + s[i], s[j] = s[j], s[i] + out.append(c ^ s[(s[i] + s[j]) & 0xff]) + self._i, self._j = i, j + return bytes(out) + +class _ChaCha20(object): + _SIGMA = b"expand 32-byte k" + + def __init__(self, key, nonce): + self._nonce = nonce + self._counter = 0 + block = self._SIGMA + key + self._ctr_bytes() + nonce + self._state = list(struct.unpack("<16L", block)) + self._make_block() + + def _ctr_bytes(self): + return struct.pack("> (32 - n))) & 0xffffffff + x[a] = (x[a] + x[b]) & 0xffffffff; x[d] = rot(x[d] ^ x[a], 16) + x[c] = (x[c] + x[d]) & 0xffffffff; x[b] = rot(x[b] ^ x[c], 12) + x[a] = (x[a] + x[b]) & 0xffffffff; x[d] = rot(x[d] ^ x[a], 8) + x[c] = (x[c] + x[d]) & 0xffffffff; x[b] = rot(x[b] ^ x[c], 7) + + def translate(self, data): + out = bytearray() + block = bytearray(self._block) + for c in bytearray(data): + out.append(c ^ block[self._pos]) + self._pos += 1 + if self._pos == 64: + self._counter += 1 + cb = self._ctr_bytes() + self._state[12] = struct.unpack(" (plugin, nonce) + plugins, nonces, buf, i = [], [], bytearray(buf), 0 + while i < len(buf): + t, ln = buf[i], buf[i + 1] + v = bytes(buf[i + 2:i + 2 + ln]) + i += 2 + ln + if t == 1: + plugins = v.split() + elif t == 3: + nonces.append(v) + if b"ChaCha64" in plugins: + for s in nonces: + if s[:9] == b"ChaCha64\x00": + return b"ChaCha64", s[9:] + if b"ChaCha" in plugins: + for s in nonces: + if s[:7] == b"ChaCha\x00": + return b"ChaCha", s[7:7 + 12] + if b"Arc4" in plugins: + return b"Arc4", None + return None, None + +class _Wire(object): + def __init__(self, sock): + self._sock = sock + self._rc = self._wc = None + + def set_ciphers(self, rc, wc): + self._rc, self._wc = rc, wc + + def send(self, data): + self._sock.sendall(self._wc.translate(data) if self._wc else data) + + def _recv_raw(self, n): + buf = b"" + while len(buf) < n: + chunk = self._sock.recv(n - len(buf)) + if not chunk: + raise InterfaceError("connection closed by server") + buf += chunk + return buf + + def recv(self, n, align=False): + total = n + ((4 - n % 4) % 4) if align else n + data = self._recv_raw(total) + if self._rc: + data = self._rc.translate(data) + return data[:n] + + def recv_int(self): + return struct.unpack("!i", self.recv(4))[0] + + def recv_bytes(self): + return self.recv(self.recv_int(), align=True) + + def close(self): + try: + self._sock.close() + except Exception: + pass + +def _pack_int(v): + return struct.pack("!i", v) + +def _pack_bytes(v): + return _pack_int(len(v)) + v + b"\x00" * ((4 - len(v) % 4) % 4) + +def _le(b): + n = 0 + for c in reversed(bytearray(b)): + n = (n << 8) | c + return n + +def _le_signed(b): + n = _le(b) # info-buffer scalars are little-endian; scale is signed (usually negative) + if b and (bytearray(b)[-1] & 0x80): + n -= 1 << (8 * len(b)) + return n + +def _b2i_signed(b): + n = _b2l(b) + if bytearray(b) and bytearray(b)[0] & 0x80: + n -= 1 << (8 * len(b)) + return n + +def _scaled(n, scale): + # integer n represents n * 10**scale (scale <= 0); render as an exact decimal string + if scale >= 0: + return str(n * (10 ** scale)) + digits = "%0*d" % (-scale + 1, abs(n)) + return ("-" if n < 0 else "") + digits[:scale] + "." + digits[scale:] + +_EPOCH_DAYS = datetime.date(1858, 11, 17).toordinal() + +def _decode_date(raw): + return datetime.date.fromordinal(_EPOCH_DAYS + struct.unpack("!i", raw)[0]) + +def _decode_time(raw): + n = struct.unpack("!I", raw)[0] + s, frac = divmod(n, 10000) + return datetime.time(s // 3600, (s // 60) % 60, s % 60, frac * 100) + +class _Column(object): + __slots__ = ("name", "sqltype", "subtype", "scale", "length") + + def io_length(self): + return self.length if self.sqltype == _SQL_TEXT else _SQL_TYPE_LENGTH[self.sqltype] + +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, wire, filename, user, password): + self._wire = wire + self._filename = filename + self._user = user + self._password = password + self._db_handle = None + self._trans_handle = None + + def cursor(self): + return Cursor(self) + + def commit(self): + if self._trans_handle is not None: + self._send(_pack_int(_op_commit_retaining) + _pack_int(self._trans_handle)) + self._response() + + def rollback(self): + if self._trans_handle is not None: + self._send(_pack_int(_op_rollback_retaining) + _pack_int(self._trans_handle)) + self._response() + + def close(self): + try: + if self._db_handle is not None: + self._send(_pack_int(_op_detach) + _pack_int(self._db_handle)) + self._response() + except Exception: + pass + self._wire.close() + + # ---- wire helpers ---- + + def _send(self, data): + self._wire.send(data) + + def _response(self): + op = self._wire.recv_int() + while op == _op_dummy: + op = self._wire.recv_int() + if op != _op_response: + raise OperationalError("unexpected Firebird operation %d" % op) + return self._parse_response() + + def _parse_response(self): + head = self._wire.recv(16) + handle = struct.unpack("!i", head[:4])[0] + object_id = head[4:12] + buf = self._wire.recv(struct.unpack("!i", head[12:16])[0], align=True) + self._check_status() + return handle, object_id, buf + + def _check_status(self): + gds, message = set(), "" + n = self._wire.recv_int() + while n != _isc_arg_end: + if n == _isc_arg_gds: + gds_code = self._wire.recv_int() + if gds_code: + gds.add(gds_code) + elif n == _isc_arg_number: + message += " %d" % self._wire.recv_int() + elif n in (_isc_arg_string, _isc_arg_interpreted, _isc_arg_sql_state): + s = self._wire.recv(self._wire.recv_int(), align=True) + if n != _isc_arg_sql_state: + message += " " + s.decode("utf-8", "replace") + n = self._wire.recv_int() + if gds: + message = ("(remote) firebird error %s%s" % (sorted(gds), message)).strip() + if gds & _GDS_INTEGRITY: + raise IntegrityError(message) + if gds & _GDS_DATA: + raise DataError(message) + if _GDS_WARNING not in gds: + raise OperationalError(message) + + # ---- query ---- + + def _query(self, query): + try: + return self._run(query) + except (struct.error, IndexError, ValueError, KeyError) as ex: + raise InterfaceError("malformed server response: %s" % ex) + + def _run(self, query): + qbytes = query.encode("utf-8") + self._send(_pack_int(_op_allocate_statement) + _pack_int(self._db_handle)) + stmt = self._response()[0] + + desc_items = bytes(bytearray([_isc_info_sql_stmt_type])) + _INFO_SQL_SELECT_DESCRIBE_VARS + self._send(_pack_int(_op_prepare_statement) + _pack_int(self._trans_handle) + _pack_int(stmt) + + _pack_int(3) + _pack_bytes(qbytes) + _pack_bytes(desc_items) + _pack_int(1024)) + buf = self._response()[2] + stmt_type, columns = self._parse_describe(stmt, buf) + + exec_msg = (_pack_int(_op_execute) + _pack_int(stmt) + _pack_int(self._trans_handle) + + _pack_bytes(b"") + _pack_int(0) + _pack_int(0) + _pack_int(0)) + self._send(exec_msg) + self._response() + + description, rows = None, [] + if stmt_type == _isc_info_sql_stmt_select and columns: + description = [(c.name, c.sqltype, None, None, None, None, None) for c in columns] + rows = self._fetch(stmt, columns) + self._send(_pack_int(_op_free_statement) + _pack_int(stmt) + _pack_int(_DSQL_drop)) + self._response() + return description, rows + + def _parse_describe(self, stmt, buf): + stmt_type, columns = None, [] + i = 0 + while i < len(buf): + if bytearray(buf[i:i + 3]) == bytearray([_isc_info_sql_stmt_type, 4, 0]): + stmt_type = _le(buf[i + 3:i + 7]) + i += 7 + elif bytearray(buf[i:i + 2]) == bytearray([_isc_info_sql_select, _isc_info_sql_describe_vars]): + i += 2 + ln = _le(buf[i:i + 2]); i += 2 + count = _le(buf[i:i + ln]); i += ln + columns = [_Column() for _ in range(count)] + next_index = self._parse_items(buf[i:], columns) + while next_index > 0: # describe buffer truncated: request the remaining columns + self._send(_pack_int(_op_info_sql) + _pack_int(stmt) + _pack_int(0) + _pack_bytes( + bytes(bytearray([_isc_info_sql_sqlda_start, 2])) + struct.pack("> 8] + for c in columns: + t = c.sqltype + if t == _SQL_VARYING: + blr += [37, c.length & 0xff, c.length >> 8] + elif t == _SQL_TEXT: + blr += [14, c.length & 0xff, c.length >> 8] + elif t == _SQL_LONG: + blr += [8, c.scale] + elif t == _SQL_SHORT: + blr += [7, c.scale] + elif t == _SQL_INT64: + blr += [16, c.scale] + elif t == _SQL_INT128: + blr += [26, c.scale] + else: + blr += _SQL_TYPE_BLR[t] + blr += [7, 0] + blr += [255, 76] + return bytes(bytearray((256 + b) if b < 0 else b for b in blr)) + + def _fetch(self, stmt, columns): + blr = self._calc_blr(columns) + nbytes = (len(columns) + 7) // 8 + blob_cols = [i for i, c in enumerate(columns) if c.sqltype == _SQL_BLOB] + rows = [] + more = True + while more: + self._send(_pack_int(_op_fetch) + _pack_int(stmt) + _pack_bytes(blr) + _pack_int(0) + _pack_int(400)) + op = self._wire.recv_int() + while op == _op_dummy: + op = self._wire.recv_int() + if op != _op_fetch_response: + if op == _op_response: + self._parse_response() + raise OperationalError("unexpected Firebird operation %d during fetch" % op) + status = self._wire.recv_int() + count = self._wire.recv_int() + while count: + null_bitmap = _le(self._wire.recv(nbytes, align=True)) + row = [] + for i, col in enumerate(columns): + if null_bitmap & (1 << i): + row.append(None) + continue + io = col.io_length() + ln = self._wire.recv_int() if io < 0 else io + raw = self._wire.recv(ln, align=True) + # blob columns yield an 8-byte blob id; resolve AFTER the fetch batch is fully drained + # (a blob sub-request mid-batch would interleave with the still-streaming rows and desync) + row.append(raw if col.sqltype == _SQL_BLOB else self._decode(col, raw)) + rows.append(row) + op = self._wire.recv_int() + status = self._wire.recv_int() + count = self._wire.recv_int() + more = status != 100 + for i in blob_cols: + for row in rows: + if row[i] is not None: + row[i] = self._read_blob(row[i], columns[i].subtype) + return [tuple(row) for row in rows] + + def _decode(self, col, raw): + t = col.sqltype + if t == _SQL_TEXT: + return self._decode_text(raw, rstrip=True) + if t == _SQL_VARYING: + return self._decode_text(raw, rstrip=False) + if t in (_SQL_SHORT, _SQL_LONG, _SQL_INT64, _SQL_INT128): + n = _b2i_signed(raw) + return _scaled(n, col.scale) if col.scale else str(n) + if t == _SQL_FLOAT: + return repr(struct.unpack("!f", raw)[0]) + if t == _SQL_DOUBLE: + return repr(struct.unpack("!d", raw)[0]) + if t == _SQL_BOOLEAN: + return "true" if bytearray(raw)[0] else "false" + if t == _SQL_DATE: + return "%s" % _decode_date(raw) + if t == _SQL_TIME: + return "%s" % _decode_time(raw) + if t == _SQL_TIMESTAMP: + return "%s %s" % (_decode_date(raw[:4]), _decode_time(raw[4:])) + if t == _SQL_BLOB: + return self._read_blob(raw, col.subtype) + return raw # unknown/decimal-float type -> raw bytes (sqlmap hex-encodes) + + def _decode_text(self, raw, rstrip): + try: + s = raw.decode("utf-8") + except UnicodeDecodeError: + return raw # OCTETS / binary text -> bytes (sqlmap hex-encodes) + return s.rstrip(" ") if rstrip else s + + def _read_blob(self, blob_id, subtype): + self._send(_pack_int(_op_open_blob2) + _pack_int(0) + _pack_int(self._trans_handle) + blob_id) + blob_handle = self._response()[0] + data = b"" + while True: + self._send(_pack_int(_op_get_segment) + _pack_int(blob_handle) + _pack_int(1024) + _pack_int(0)) + seg_status, _, buf = self._response() + buf = bytearray(buf) + j = 0 + while j < len(buf): + seg_len = _le(buf[j:j + 2]) + data += bytes(buf[j + 2:j + 2 + seg_len]) + j += 2 + seg_len + if seg_status == 2: # last segment + break + self._send(_pack_int(_op_close_blob) + _pack_int(blob_handle)) + self._response() + if subtype == 1: + try: + return data.decode("utf-8") + except UnicodeDecodeError: + return data + return data + +def _uid(user, plugin, plugin_list, public_key, wire_crypt): + def param(k, v): + if k != _CNCT_specific_data: + return bytes(bytearray([k, len(v)])) + v + out, i = b"", 0 + while len(v) > 254: + out += bytes(bytearray([k, 255, i])) + v[:254] + v = v[254:] + i += 1 + return out + bytes(bytearray([k, len(v) + 1, i])) + v + + try: + os_user = os.environ.get("USER", "") or os.environ.get("USERNAME", "") + except Exception: + os_user = "" + specific = _hex(_minbe(public_key)) + r = param(_CNCT_login, user.encode("utf-8")) + r += param(_CNCT_plugin_name, plugin) + r += param(_CNCT_plugin_list, plugin_list) + r += param(_CNCT_specific_data, specific) + r += param(_CNCT_client_crypt, b"\x01\x00\x00\x00" if wire_crypt else b"\x00\x00\x00\x00") + r += param(_CNCT_user, os_user.encode("utf-8")) + r += param(_CNCT_host, socket.gethostname().encode("utf-8", "replace")) + r += param(_CNCT_user_verification, b"") + return r + +def _hex(b): + return "".join("%02x" % c for c in bytearray(b)).encode("ascii") + +# protocol version tuples (version, arch=Generic 1, min_type=0, max_type=batch_send 3, weight); max_type is +# deliberately capped at 3 (not lazy_send 5) so every operation gets an immediate response (no deferred handles) +_PROTOCOLS = ("0000000a00000001000000000000000300000002", + "ffff800b00000001000000000000000300000004", + "ffff800c00000001000000000000000300000006", + "ffff800d00000001000000000000000300000008", + "ffff800e0000000100000000000000030000000a", + "ffff800f0000000100000000000000030000000c", + "ffff80100000000100000000000000030000000e", + "ffff801100000001000000000000000300000010") + +def connect(host=None, port=3050, user=None, password=None, database=None, connect_timeout=None, **kwargs): + user = user or "SYSDBA" + password = password or "" + filename = (database or "").encode("utf-8") + plugin, plugin_list = b"Srp256", b"Srp256,Srp,Legacy_Auth" + + try: + sock = socket.create_connection((host or "localhost", int(port or 3050)), 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)) + + wire = _Wire(sock) + try: + public_key, private_key = _srp_client_seed() + packet = (_pack_int(_op_connect) + _pack_int(_op_attach) + _pack_int(3) + _pack_int(1) + + _pack_bytes(filename) + _pack_int(len(_PROTOCOLS)) + + _pack_bytes(_uid(user, plugin, plugin_list, public_key, True))) + for p in _PROTOCOLS: + packet += _unhex(p) + wire.send(packet) + + _authenticate(wire, user, password, public_key, private_key) + connection = Connection(wire, filename, user, password) + _attach(connection, wire, user) + except (DatabaseError, InterfaceError): + wire.close() + raise + except Exception as ex: + wire.close() + raise OperationalError("Firebird login failed (%s)" % ex) + return connection + +def _unhex(s): + return bytes(bytearray(int(s[i:i + 2], 16) for i in range(0, len(s), 2))) + +def _normalize_user(user): + if len(user) >= 2 and user[0] == '"' and user[-1] == '"': + return user[1:-1].replace('""', '"') + return user.upper() + +def _authenticate(wire, user, password, public_key, private_key): + op = wire.recv_int() + while op == _op_dummy: + op = wire.recv_int() + if op == _op_reject: + raise OperationalError("Firebird connection rejected") + if op == _op_response: + Connection(wire, b"", user, password)._parse_response() # will raise the server error + raise OperationalError("Firebird connection rejected") + + wire.recv(12) # accept block: protocol version / architecture / type (not needed once lazy-send is off) + if op == _op_accept: + return b"" # plaintext, no encryption negotiated + + data = wire.recv_bytes() + plugin_name = wire.recv_bytes() + wire.recv_int() # is_authenticated + wire.recv_bytes() # keys + if plugin_name not in (b"Srp256", b"Srp"): + raise NotSupportedError("unsupported Firebird auth plugin %r" % plugin_name) + if not data: + raise OperationalError("Firebird server sent no SRP challenge") + + salt_len = _le(data[:2]) + salt = data[2:2 + salt_len] + # the server sends B as a hex integer, dropping a leading zero nibble when its top nibble is 0 (odd-length + # hex ~5% of the time) - parse it as an integer, which is length-agnostic (byte-pairing would corrupt it) + server_public = int(data[4 + salt_len:].decode("ascii"), 16) + hash_algo = hashlib.sha256 if plugin_name == b"Srp256" else hashlib.sha1 + proof, session_key = _srp_client_proof(_normalize_user(user).encode("utf-8"), + password.encode("utf-8"), salt, + public_key, server_public, private_key, hash_algo) + + wire.send(_pack_int(_op_cont_auth) + _pack_bytes(_hex(proof)) + _pack_bytes(plugin_name) + + _pack_bytes(b"Srp256,Srp,Legacy_Auth") + _pack_bytes(b"")) + buf = _read_response(wire, user, password) + + enc_plugin, nonce = _guess_wire_crypt(buf) + if not (enc_plugin and session_key): + raise NotSupportedError("Firebird server did not offer a supported wire-crypt plugin") + wire.send(_pack_int(_op_crypt) + _pack_bytes(enc_plugin) + _pack_bytes(b"Symmetric")) + if enc_plugin in (b"ChaCha", b"ChaCha64"): + k = hashlib.sha256(session_key).digest() + wire.set_ciphers(_ChaCha20(k, nonce), _ChaCha20(k, nonce)) + elif enc_plugin == b"Arc4": + wire.set_ciphers(_ARC4(session_key), _ARC4(session_key)) + else: + raise NotSupportedError("unsupported Firebird wire-crypt plugin %r" % enc_plugin) + _read_response(wire, user, password) # first encrypted message + return session_key + +def _read_response(wire, user, password): + # a bare op_response reader used during the login handshake (before a Connection exists) + op = wire.recv_int() + while op == _op_dummy: + op = wire.recv_int() + if op == _op_cont_auth: + raise OperationalError("Firebird authentication failed") + if op != _op_response: + raise OperationalError("unexpected Firebird operation %d during login" % op) + return Connection(wire, b"", user, password)._parse_response()[2] + +def _attach(connection, wire, user): + dpb = bytearray([_isc_dpb_version1]) + dpb += bytearray([_isc_dpb_lc_ctype, 4]) + bytearray(b"UTF8") + ub = user.encode("utf-8") + dpb += bytearray([_isc_dpb_user_name, len(ub)]) + bytearray(ub) + dpb += bytearray([_isc_dpb_process_id, 4]) + bytearray(struct.pack("...) -VERSION = "1.10.7.88" +VERSION = "1.10.7.89" 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)