55See the file 'LICENSE' for copying permission
66"""
77
8+ from . import __version__
89from .atlas import _REPL
910from .engine import Esperanto , hostExtract
1011from .records import (
1112 OracleUndecided , ExtractResult , QueryBudgetExceeded )
1213
14+ _SITE = "https://sqlmap.org"
15+
1316# ratio-mode confidence margin: if the true/false similarity scores are within this of each
1417# other the page can't be classified, so the oracle returns UNDECIDED rather than guessing.
1518_RATIO_MARGIN = 0.05
@@ -331,14 +334,14 @@ def mssql():
331334 return ok
332335
333336
334- def _httpOracle (url , data = None , cookie = None , headers = None , string = None , notString = None , code = None ):
337+ def _httpOracle (url , data = None , cookie = None , headers = None , string = None , code = None ):
335338 """A boolean oracle over a real HTTP target, for standalone use. The condition is
336339 substituted at the injection marker: '[INFERENCE]' is replaced verbatim (you supply
337340 the context, e.g. `id=1 AND [INFERENCE]`), else a single '*' is replaced with
338341 ` AND (<condition>)`.
339342
340343 True/false is decided by a reduced port of sqlmap's response differentiation - the
341- Pareto 80%: explicit --string/--not-string/-- code win; otherwise it CALIBRATES from
344+ Pareto 80%: explicit --string/--code win; otherwise it CALIBRATES from
342345 two true (1=1) baselines + one false (1=2) and auto-picks the cheapest reliable
343346 signal - HTTP status code, else a stable text line present in true but not false,
344347 else a difflib similarity ratio. Two noise-killers borrowed from sqlmap make it
@@ -428,8 +431,6 @@ def _clean(body, cond):
428431 mode , wanted , base = None , None , {}
429432 if string is not None :
430433 mode = "string"
431- elif notString is not None :
432- mode = "notstring"
433434 elif code is not None :
434435 mode , wanted = "code" , int (code )
435436 else : # auto-calibrate
@@ -456,7 +457,7 @@ def _clean(body, cond):
456457 break
457458 if wanted is None : # (3) similarity ratio floor
458459 mode , base = "ratio" , {"t" : tc , "f" : fc }
459- if not string and not notString :
460+ if string is None :
460461 print ("[*] calibrated oracle: %s%s" % (mode , (" (%r)" % wanted ) if mode in ("code" , "autostring" ) else "" ))
461462
462463 def classify (cond ):
@@ -465,8 +466,6 @@ def classify(cond):
465466 return None # let the engine retry/degrade, never a fake bool
466467 if mode == "string" :
467468 return string in body
468- if mode == "notstring" :
469- return notString not in body
470469 if mode == "code" :
471470 return status == wanted
472471 if mode == "autostring" :
@@ -647,6 +646,25 @@ def _report(esp, args):
647646 _printTable (result ["columns" ], result ["rows" ])
648647
649648
649+ def _banner ():
650+ # modest CLI identity, sqlmap-styled: a small globe (DBMS-agnostic/universal) + the Esperanto
651+ # green star. Colored only on a TTY; pure-ASCII (no coding header on this file); text columns
652+ # aligned at a fixed offset so the escape codes (zero display width) don't skew them.
653+ import sys as _sys
654+ tty = _sys .stdout .isatty ()
655+ G = "\033 [0;32m" if tty else "" # esperanto green (the globe)
656+ S = "\033 [1;32m" if tty else "" # bright green (the star)
657+ W = "\033 [1;37m" if tty else "" # bold white (the name)
658+ U = "\033 [4;37m" if tty else "" # underline (the site)
659+ R = "\033 [0m" if tty else ""
660+ return (
661+ " %(G)s___%(R)s\n "
662+ " %(G)s/ _ \\ %(R)s %(W)sesperanto%(R)s {%(ver)s}\n "
663+ " %(G)s| (_) |%(R)s\n "
664+ " %(G)s\\ ___/ %(S)s*%(R)s %(U)s%(site)s%(R)s\n "
665+ ) % dict (G = G , S = S , W = W , U = U , R = R , ver = __version__ , site = _SITE )
666+
667+
650668def main (argv = None ):
651669 """Standalone entry point: drive the engine against a live HTTP target."""
652670 import argparse
@@ -665,13 +683,14 @@ def _format_action_invocation(self, action):
665683
666684 parser = argparse .ArgumentParser (
667685 prog = "esperanto" , formatter_class = _Formatter ,
686+ usage = "esperanto -u URL [options]" , # short synopsis, not an auto-listing of every flag
668687 description = "DBMS-agnostic blind-SQLi enumeration engine (standalone)" )
688+ parser .add_argument ("--version" , action = "version" , version = "esperanto %s (%s)" % (__version__ , _SITE ))
669689 parser .add_argument ("-u" , "--url" , help = "target URL (with a '*'/'[INFERENCE]' marker)" )
670690 parser .add_argument ("--data" , help = "POST data string" )
671691 parser .add_argument ("--cookie" , help = "HTTP Cookie header" )
672692 parser .add_argument ("-H" , "--header" , action = "append" , help = "extra HTTP header (repeatable)" )
673693 parser .add_argument ("--string" , help = "match string for a True response" )
674- parser .add_argument ("--not-string" , dest = "not_string" , help = "match string for a False response" )
675694 parser .add_argument ("--code" , type = int , help = "HTTP code for a True response" )
676695 parser .add_argument ("--banner" , action = "store_true" , help = "retrieve DBMS banner" )
677696 parser .add_argument ("--current-user" , action = "store_true" , dest = "current_user" , help = "retrieve current user" )
@@ -697,13 +716,14 @@ def _format_action_invocation(self, action):
697716 if args .selftest : # self-test only on EXPLICIT request
698717 _selftest ()
699718 return 0
719+ print (_banner ()) # CLI identity (after the dev-harness paths above)
700720 if not args .url : # no target and nothing to do -> show help, don't surprise
701721 parser .print_help ()
702722 return 1
703723
704724 target = args .url if "://" in args .url else ("http://" + args .url ) # tolerate a scheme-less URL
705725 esp = Esperanto (_httpOracle (target , args .data , args .cookie , args .header ,
706- args .string , args .not_string , args . code ))
726+ args .string , args .code ))
707727 import sys as _sys
708728 _tty = _sys .stdout .isatty ()
709729 def _charLive (partial , total ):
0 commit comments