Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/xml/queries.xml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@
</columns>
<dump_table>
<inband query="SELECT %s FROM %s.%s"/>
<blind query="SELECT MIN(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s)>'%s'" query2="SELECT MAX(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s) LIKE '%s'" query3="SELECT %s FROM (SELECT %s, ROW_NUMBER() OVER (ORDER BY %s) AS CAP FROM %s)x WHERE CAP=%d" count="SELECT LTRIM(STR(COUNT(*))) FROM %s" count2="SELECT LTRIM(STR(COUNT(DISTINCT(%s)))) FROM %s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY" keyset_ordered="SELECT TOP 1 %s FROM %s WHERE %s ORDER BY %s" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
<blind query="SELECT MIN(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s)>'%s'" query2="SELECT MAX(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s) LIKE '%s'" query3="SELECT %s FROM (SELECT *, %s AS SQLMAPCAPVAL, ROW_NUMBER() OVER (ORDER BY %s) AS CAP FROM %s)x WHERE CAP=%d" count="SELECT LTRIM(STR(COUNT(*))) FROM %s" count2="SELECT LTRIM(STR(COUNT(DISTINCT(%s)))) FROM %s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY" keyset_ordered="SELECT TOP 1 %s FROM %s WHERE %s ORDER BY %s" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
<primary_key count="SELECT COUNT(*) FROM sys.indexes i JOIN sys.index_columns ic ON i.object_id=ic.object_id AND i.index_id=ic.index_id WHERE i.is_primary_key=1 AND i.object_id=OBJECT_ID('%s.dbo.%s')" query="SELECT name FROM (SELECT c.name AS name, ROW_NUMBER() OVER (ORDER BY ic.key_ordinal) AS rn FROM sys.indexes i JOIN sys.index_columns ic ON i.object_id=ic.object_id AND i.index_id=ic.index_id JOIN sys.columns c ON ic.object_id=c.object_id AND c.column_id=ic.column_id WHERE i.is_primary_key=1 AND i.object_id=OBJECT_ID('%s.dbo.%s')) x WHERE rn=%d+1"/>
</dump_table>
<search_db>
Expand Down
50 changes: 50 additions & 0 deletions extra/dbwire/__init__.py
Original file line number Diff line number Diff line change
@@ -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
139 changes: 139 additions & 0 deletions extra/dbwire/clickhouse.py
Original file line number Diff line number Diff line change
@@ -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
Loading