mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-08-04 14:55:40 +00:00
Minor update of dbwire
This commit is contained in:
parent
9d08b1dc03
commit
05005dde58
4 changed files with 908 additions and 1 deletions
68
extra/dbwire/README.md
Normal file
68
extra/dbwire/README.md
Normal file
|
|
@ -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.
|
||||
838
extra/dbwire/firebird.py
Normal file
838
extra/dbwire/firebird.py
Normal file
|
|
@ -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("<Q", self._counter)[:16 - len(self._nonce)]
|
||||
|
||||
def _make_block(self):
|
||||
x = list(self._state)
|
||||
for _ in range(10):
|
||||
self._qr(x, 0, 4, 8, 12); self._qr(x, 1, 5, 9, 13)
|
||||
self._qr(x, 2, 6, 10, 14); self._qr(x, 3, 7, 11, 15)
|
||||
self._qr(x, 0, 5, 10, 15); self._qr(x, 1, 6, 11, 12)
|
||||
self._qr(x, 2, 7, 8, 13); self._qr(x, 3, 4, 9, 14)
|
||||
self._block = struct.pack("<16L", *[(x[i] + self._state[i]) & 0xffffffff for i in range(16)])
|
||||
self._pos = 0
|
||||
|
||||
@staticmethod
|
||||
def _qr(x, a, b, c, d):
|
||||
def rot(v, n):
|
||||
return ((v << n) | (v >> (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("<L", cb[:4])[0]
|
||||
if len(self._nonce) == 8:
|
||||
self._state[13] = struct.unpack("<L", cb[4:8])[0]
|
||||
self._make_block()
|
||||
block = bytearray(self._block)
|
||||
return bytes(out)
|
||||
|
||||
def _guess_wire_crypt(buf):
|
||||
# parse the crypt-key callback (type/length/value items) -> (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("<H", next_index) +
|
||||
_INFO_SQL_SELECT_DESCRIBE_VARS) + _pack_int(1024))
|
||||
more = self._response()[2]
|
||||
ln = _le(more[2:4])
|
||||
next_index = self._parse_items(more[4 + ln:], columns)
|
||||
break
|
||||
else:
|
||||
break
|
||||
return stmt_type, columns
|
||||
|
||||
def _parse_items(self, buf, columns):
|
||||
index, i = 0, 0
|
||||
buf = bytearray(buf)
|
||||
while i < len(buf):
|
||||
item = buf[i]
|
||||
if item == _isc_info_end:
|
||||
break
|
||||
if item == _isc_info_truncated:
|
||||
return index # continue from here in another round
|
||||
if item == _isc_info_sql_describe_end:
|
||||
i += 1
|
||||
continue
|
||||
ln = _le(buf[i + 1:i + 3])
|
||||
value = buf[i + 3:i + 3 + ln]
|
||||
if item == _isc_info_sql_sqlda_seq:
|
||||
index = _le(value)
|
||||
elif item == _isc_info_sql_type:
|
||||
columns[index - 1].sqltype = _le(value) & ~1
|
||||
elif item == _isc_info_sql_sub_type:
|
||||
columns[index - 1].subtype = _le(value)
|
||||
elif item == _isc_info_sql_scale:
|
||||
columns[index - 1].scale = _le_signed(value)
|
||||
elif item == _isc_info_sql_length:
|
||||
columns[index - 1].length = _le(value)
|
||||
elif item == _isc_info_sql_alias:
|
||||
columns[index - 1].name = value.decode("utf-8", "replace")
|
||||
elif item == _isc_info_sql_field and not getattr(columns[index - 1], "name", None):
|
||||
columns[index - 1].name = value.decode("utf-8", "replace")
|
||||
i += 3 + ln
|
||||
return -1
|
||||
|
||||
def _calc_blr(self, columns):
|
||||
ln = len(columns) * 2
|
||||
blr = [5, 2, 4, 0, ln & 0xff, ln >> 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("<i", os.getpid() & 0x7fffffff))
|
||||
name = b"sqlmap"
|
||||
dpb += bytearray([_isc_dpb_process_name, len(name)]) + bytearray(name)
|
||||
wire.send(_pack_int(_op_attach) + _pack_int(0) + _pack_bytes(connection._filename) + _pack_bytes(bytes(dpb)))
|
||||
connection._db_handle = connection._response()[0]
|
||||
|
||||
tpb = bytes(bytearray([_isc_tpb_version3, _isc_tpb_write, _isc_tpb_wait,
|
||||
_isc_tpb_read_committed, _isc_tpb_rec_version]))
|
||||
wire.send(_pack_int(_op_transaction) + _pack_int(connection._db_handle) + _pack_bytes(tpb))
|
||||
connection._trans_handle = connection._response()[0]
|
||||
|
|
@ -270,6 +270,7 @@ DBWIRE_MODULES = {
|
|||
DBMS.CLICKHOUSE: "clickhouse",
|
||||
DBMS.MONETDB: "monetdb",
|
||||
DBMS.PRESTO: "presto",
|
||||
DBMS.FIREBIRD: "firebird",
|
||||
}
|
||||
|
||||
# Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/
|
||||
|
|
|
|||
|
|
@ -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.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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue