diff --git a/extra/dbwire/clickhouse.py b/extra/dbwire/clickhouse.py index 5d808d4a5..a172c25d0 100644 --- a/extra/dbwire/clickhouse.py +++ b/extra/dbwire/clickhouse.py @@ -14,6 +14,7 @@ backslash escaping and \\N for NULL). Covers ClickHouse and its HTTP-compatible """ import base64 +import socket try: from urllib.request import Request, urlopen # Python 3 @@ -29,14 +30,14 @@ def _unescape(value): return None if "\\" not in value: return value - out, it = [], iter(range(len(value))) + out = [] i = 0 n = len(value) while i < n: ch = value[i] if ch == "\\" and i + 1 < n: nxt = value[i + 1] - out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "\\": "\\", "'": "'"}.get(nxt, nxt)) + out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "b": "\b", "f": "\f", "a": "\a", "v": "\v", "\\": "\\", "'": "'"}.get(nxt, nxt)) i += 2 else: out.append(ch) @@ -103,6 +104,8 @@ class Connection(object): 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, [] diff --git a/extra/dbwire/postgres.py b/extra/dbwire/postgres.py index 0e848f3cb..031ec6b3d 100644 --- a/extra/dbwire/postgres.py +++ b/extra/dbwire/postgres.py @@ -23,7 +23,6 @@ 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 diff --git a/extra/dbwire/presto.py b/extra/dbwire/presto.py index a0857e1ef..7deede433 100644 --- a/extra/dbwire/presto.py +++ b/extra/dbwire/presto.py @@ -14,7 +14,7 @@ headers are sent so the same client works against Presto and Trino. import base64 import json -import time +import socket try: from urllib.request import Request, urlopen # Python 3 @@ -27,6 +27,20 @@ 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 @@ -65,9 +79,13 @@ class Connection(object): self._headers = {"Content-Type": "text/plain"} for prefix in ("X-Presto-", "X-Trino-"): self._headers[prefix + "User"] = user or "sqlmap" - self._headers[prefix + "Catalog"] = catalog or "" - self._headers[prefix + "Schema"] = schema or "default" 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 @@ -92,6 +110,8 @@ class Connection(object): 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: @@ -99,15 +119,16 @@ class Connection(object): def _query(self, query): page = self._request(self._statement_url, data=query) - columns, rows = None, [] + 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(row)) + 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 diff --git a/extra/dbwire/tds.py b/extra/dbwire/tds.py index 3c3bd3a12..ad99c89df 100644 --- a/extra/dbwire/tds.py +++ b/extra/dbwire/tds.py @@ -355,7 +355,6 @@ def _parse_tokens(sock, login=False): rows.append(tuple(row)) elif token == 0xaa: # ERROR (tlen,) = struct.unpack("...) -VERSION = "1.10.7.85" +VERSION = "1.10.7.86" 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)