From e6a5e8ff05f2190421e8f94a8184747920d5f1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 22 Jul 2026 01:48:54 +0200 Subject: [PATCH] More style changes for Esperanto DBMS engine --- extra/esperanto/__main__.py | 40 ++++++++++++++-------------------- extra/esperanto/enumeration.py | 14 +++++++----- lib/core/settings.py | 2 +- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/extra/esperanto/__main__.py b/extra/esperanto/__main__.py index 1ee6e02f2..e6c8ab46f 100644 --- a/extra/esperanto/__main__.py +++ b/extra/esperanto/__main__.py @@ -360,7 +360,7 @@ def _httpOracle(url, data=None, cookie=None, headers=None, string=None, code=Non if "[INFERENCE]" not in (url + (data or "")) and "*" not in (url + (data or "")): url = url + "*" # no marker given -> inject at the end of the URL by default - print("[*] no injection marker ('*' or '[INFERENCE]') given; defaulting to end of URL: %s" % url) + print("[i] no injection marker ('*' or '[INFERENCE]') given; defaulting to end of URL: %s" % url) # ONE kept-alive connection reused across every probe. a blind dump is thousands of # requests; opening a fresh TCP+TLS handshake per probe (what urlopen does) is ~0.2s of @@ -458,7 +458,7 @@ def _httpOracle(url, data=None, cookie=None, headers=None, string=None, code=Non if wanted is None: # (3) similarity ratio floor mode, base = "ratio", {"t": tc, "f": fc} if string is None: - print("[*] calibrated oracle: %s%s" % (mode, (" (%r)" % wanted) if mode in ("code", "autostring") else "")) + print("[i] calibrated oracle: %s%s" % (mode, (" (%r)" % wanted) if mode in ("code", "autostring") else "")) def classify(cond): body, status = fetch(cond) @@ -598,7 +598,7 @@ def _report(esp, args): else: scope = cur if scope: - print("[*] scoping to database/schema: %s" % scope) + print("[i] scoping to database/schema: %s" % scope) if args.current_user: expr = esp.dialect.identity.get("user") print("[*] current user: %s" % (_scalar(esp, expr) if expr else "n/a")) @@ -606,35 +606,29 @@ def _report(esp, args): expr = esp.dialect.identity.get("database") print("[*] current database: %s" % (_scalar(esp, expr) if expr else "n/a")) if args.tables: - print("[*] fetching tables ...") + print("[i] fetching tables ...") print("[*] tables: %s" % ", ".join(esp.enumerate("table", schema=scope) or [""])) if args.columns: if not args.tbl: print("[!] --columns needs -T ") else: - print("[*] fetching columns for '%s' ..." % args.tbl) + print("[i] fetching columns for '%s' ..." % args.tbl) print("[*] columns of %s: %s" % (args.tbl, ", ".join(esp.columns(args.tbl, schema=scope) or [""]))) - if args.query: - print("[*] fetching %s ..." % args.query) - print("[*] %s = %s" % (args.query, _scalar(esp, args.query))) if args.dump: if not args.tbl: print("[!] --dump needs -T
") else: cols = [c.strip() for c in args.col.split(",")] if args.col else None if cols is None: # enumerate columns FIRST (own phase), so the - print("[*] fetching columns for table '%s' ..." % args.tbl) # 'entries' phase below streams ROWS, not column names + print("[i] fetching columns for table '%s' ..." % args.tbl) # 'entries' phase below streams ROWS, not column names cols = esp.columns(args.tbl, schema=scope) or None - print("[*] fetching entries for table '%s' ..." % args.tbl) - # NO silent internal 10-row cap: honor --stop, else dump ALL rows (by COUNT) and - # always print whether the result is complete. - if getattr(args, "stop", None): - limit = args.stop - else: - try: - limit = esp.extractInteger("(SELECT COUNT(*) FROM %s)" % esp.qualify(args.tbl, scope)) or 10 - except Exception: - limit = 1 << 30 + print("[i] fetching entries for table '%s' ..." % args.tbl) + # NO silent internal cap: dump ALL rows (bounded by the live COUNT) and always + # print whether the result came back complete. + try: + limit = esp.extractInteger("(SELECT COUNT(*) FROM %s)" % esp.qualify(args.tbl, scope)) or 10 + except Exception: + limit = 1 << 30 result = esp.dump(args.tbl, columns=cols, schema=scope, limit=limit) if not result or not result["columns"]: print("[!] could not dump %s" % args.tbl) @@ -698,11 +692,9 @@ def main(argv=None): parser.add_argument("--tables", action="store_true", help="enumerate tables") parser.add_argument("--columns", action="store_true", help="enumerate table columns (needs -T)") parser.add_argument("--dump", action="store_true", help="dump table entries (needs -T)") - parser.add_argument("--sql-query", dest="query", help="run a custom scalar SQL query") parser.add_argument("-D", dest="db", help="database/schema to enumerate") parser.add_argument("-T", dest="tbl", help="table to enumerate") parser.add_argument("-C", dest="col", help="columns to dump (comma-separated)") - parser.add_argument("--stop", type=int, help="max rows to dump (default: all)") # internal dev/test harness switches - functional but hidden from --help (--live drives the # local-Docker DBMS livetest; --waf is a livetest-only fault-injection mode, NOT a real WAF bypass) parser.add_argument("--live", action="store_true", help=argparse.SUPPRESS) @@ -733,18 +725,18 @@ def main(argv=None): shown = _previewFramed(partial) if shown is None: shown = partial - _sys.stdout.write("\r\033[K[*] retrieved: %s" % _safeterm(shown[-200:])) + _sys.stdout.write("\r\033[K[i] retrieved: %s" % _safeterm(shown[-200:])) if len(partial) >= total: # value complete -> keep it and drop to a new line _sys.stdout.write("\n") _sys.stdout.flush() def _plainLive(value): # piped/non-tty: one plain line per value, no control codes - _sys.stdout.write("[*] retrieved: %s\n" % _safeterm(value)) + _sys.stdout.write("[i] retrieved: %s\n" % _safeterm(value)) _sys.stdout.flush() if _tty: esp._charProgress = _charLive # animated char-by-char is the sole feedback on a terminal else: esp._progress = _plainLive # logs/pipes get clean per-value lines instead - print("[*] discovering the back-end SQL dialect (agnostic mode) ...") + print("[i] discovering the back-end SQL dialect (agnostic mode) ...") try: esp.discover() _report(esp, args) diff --git a/extra/esperanto/enumeration.py b/extra/esperanto/enumeration.py index a295d5f8e..ef6591efe 100644 --- a/extra/esperanto/enumeration.py +++ b/extra/esperanto/enumeration.py @@ -329,12 +329,14 @@ class _Enumeration(object): # take the alphabetically-first key column (deterministic); a compound key # still yields a usable ordering column for the walk keyexpr = "(SELECT MIN(%s) FROM %s WHERE %s)" % (ncol, source, filt) - with self._probePhase(): # a catalog that lacks this key structure errors -> "no key", not fatal - present = self._ask("%s IS NOT NULL" % keyexpr) - if present: - res = self.extractResult(keyexpr) # a key COLUMN NAME becomes SQL -> require an EXACT value - if res.exact and res.value: # (a case-ambiguous name could mis-target) - return res.value + # discovering the key COLUMN NAME is setup, not data - run it inside the probe phase so a + # catalog lacking this key structure degrades to "no key" (not fatal) AND so the name does + # not surface on the live "retrieved:" feed (it is a column name, not a dumped entry). + with self._probePhase(): + if self._ask("%s IS NOT NULL" % keyexpr): + res = self.extractResult(keyexpr) # a key COLUMN NAME becomes SQL -> require an EXACT value + if res.exact and res.value: # (a case-ambiguous name could mis-target) + return res.value return None def columnType(self, expr): diff --git a/lib/core/settings.py b/lib/core/settings.py index e72cf91e2..843398181 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.175" +VERSION = "1.10.7.176" 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)