diff --git a/lib/core/option.py b/lib/core/option.py index 7624f884a..55f2f8af2 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -847,6 +847,24 @@ def _listTamperingFunctions(): comment = match.group(1).strip() dataToStdout("* %s - %s\n" % (setColor(os.path.basename(script), "yellow"), re.sub(r" *\n *", " ", comment.split("\n\n")[0].strip()))) +def _argSpec(function): + """ + Cross Py2/Py3 argument-name introspection - inspect.getargspec() is deprecated since + Python 3.0 and removed entirely since 3.11, while inspect.getfullargspec() (used here + whenever available) covers the same 'args'/'keywords' need without the warning/removal + """ + + return inspect.getfullargspec(function) if hasattr(inspect, "getfullargspec") else inspect.getargspec(function) + +def _kwargsName(function): + """ + Name of 'function's **kwargs-style catch-all parameter, or None - the field holding it is + called 'varkw' on a getfullargspec() result and 'keywords' on a (Python 2) getargspec() one + """ + + spec = _argSpec(function) + return getattr(spec, "varkw", None) or getattr(spec, "keywords", None) + def _setTamperingFunctions(): """ Loads tampering functions from given script(s) @@ -913,7 +931,7 @@ def _setTamperingFunctions(): priority = PRIORITY.NORMAL for name, function in inspect.getmembers(module, inspect.isfunction): - if name == "tamper" and (hasattr(inspect, "signature") and all(_ in inspect.signature(function).parameters for _ in ("payload", "kwargs")) or inspect.getargspec(function).args and inspect.getargspec(function).keywords == "kwargs"): + if name == "tamper" and (hasattr(inspect, "signature") and all(_ in inspect.signature(function).parameters for _ in ("payload", "kwargs")) or _argSpec(function).args and _kwargsName(function) == "kwargs"): found = True kb.tamperFunctions.append(function) function.__name__ = module.__name__ @@ -1023,7 +1041,7 @@ def _setPreprocessFunctions(): for name, function in inspect.getmembers(module, inspect.isfunction): try: - if name == "preprocess" and inspect.getargspec(function).args and all(_ in inspect.getargspec(function).args for _ in ("req",)): + if name == "preprocess" and _argSpec(function).args and all(_ in _argSpec(function).args for _ in ("req",)): found = True kb.preprocessFunctions.append(function) @@ -1106,7 +1124,7 @@ def _setPostprocessFunctions(): raise SqlmapSyntaxException("cannot import postprocess module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) for name, function in inspect.getmembers(module, inspect.isfunction): - if name == "postprocess" and inspect.getargspec(function).args and all(_ in inspect.getargspec(function).args for _ in ("page", "headers", "code")): + if name == "postprocess" and _argSpec(function).args and all(_ in _argSpec(function).args for _ in ("page", "headers", "code")): found = True kb.postprocessFunctions.append(function) diff --git a/lib/core/settings.py b/lib/core/settings.py index f5b2b7ed1..4be70d30d 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.9.28" +VERSION = "1.10.9.29" 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) diff --git a/tests/test_esperanto.py b/tests/test_esperanto.py index 57f6b2b12..5da31bedc 100644 --- a/tests/test_esperanto.py +++ b/tests/test_esperanto.py @@ -49,9 +49,15 @@ CORPUS = [ u"\U00010348", u"Aé€\U00010348Z", ] +# every connection _oracle()/_disguisedOracle() open outlives the helper call (the returned +# closure keeps using it), so callers can't close it themselves - tracked here and closed once +# in tearDownModule() instead of leaking to GC (ResourceWarning: unclosed database) +_OPEN_CONNECTIONS = [] + def _oracle(value=None, is_null=False): con = sqlite3.connect(":memory:") + _OPEN_CONNECTIONS.append(con) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES (?)", (None if is_null else value,)) con.commit() @@ -84,6 +90,7 @@ def _disguisedOracle(blocked=None, rewrites=(), funcs=()): `funcs` registers extra SQL functions (e.g. a variadic CONCAT SQLite lacks). The engine never sees SQLite - it must adapt to whatever back-end the scenario emulates.""" con = sqlite3.connect(":memory:") + _OPEN_CONNECTIONS.append(con) for name, narg, fn in funcs: con.create_function(name, narg, fn) for stmt in _SEED: @@ -221,6 +228,7 @@ class TestEsperanto(unittest.TestCase): # hostExtract must mirror the native EXACT/AMBIGUOUS verdict: in a mode with no byte-exact # witness (ordinal), both must be WHOLE_BUT_AMBIGUOUS, not native-ambiguous/host-exact. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES ('Admin-42')") con.commit() @@ -249,6 +257,7 @@ class TestEsperanto(unittest.TestCase): # must reject the shortened token -> fall back to cell-by-cell (which reads the true # length via substring), so a 400-char value is recovered whole, not silently as 300. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)") big = "A" * 400 con.execute("INSERT INTO t VALUES (1, ?)", (big,)) @@ -273,6 +282,7 @@ class TestEsperanto(unittest.TestCase): # operands -> bisection converges off-by-one. The final `expr = recovered` check must # reject it (fail closed) rather than return a silently-wrong integer. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) def ask(cond): c = cond @@ -302,6 +312,7 @@ class TestEsperanto(unittest.TestCase): # (LENGTH(..)>n, UNICODE(..)>n) converges off-by-one; the '=' confirmation must fail closed # rather than hand back silently-wrong code-point text as exact. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES ('Admin-42')") con.commit() @@ -329,6 +340,7 @@ class TestEsperanto(unittest.TestCase): # (e.g. utf-8 bytes with an embedded NUL heuristically read as UTF-16). Bytes are faithful; # only a PROVEN codec makes the decoded text exact. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES (?)", (u"é",)) # utf-8 C3 A9 -> non-ASCII bytes con.execute("CREATE TABLE a (v TEXT)") @@ -356,6 +368,7 @@ class TestEsperanto(unittest.TestCase): # Round 8 #5: once a byte-length witness is selected, an UNDECIDED reading must reject the # bytes (fail closed), never treat "couldn't verify" as "matched". con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES ('abc')") con.commit() @@ -389,6 +402,7 @@ class TestEsperanto(unittest.TestCase): # never ran on the very failure it defends against. The canary must run whenever a textcast # was applied, and an unproven cast must not certify source identity. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)") con.execute(u"INSERT INTO t VALUES (1, 'café')") con.commit() @@ -415,6 +429,7 @@ class TestEsperanto(unittest.TestCase): # the engine must escalate to the cast+hex path (which the framed dump proves works) and # recover the value exactly. Without hex, it stays honestly incomplete (no false-complete). con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute(u"INSERT INTO t VALUES ('café-€')") con.commit() @@ -466,6 +481,7 @@ class TestEsperanto(unittest.TestCase): # False). the full truth table must reject 'gt' (and fall to BETWEEN) so counts/lengths # aren't read off-by-one. reproduced: extractInteger('5',max=10) used to return 6. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) def ask(cond): try: @@ -481,6 +497,7 @@ class TestEsperanto(unittest.TestCase): # without a proven hex/binary witness (or code-codepoint), a value is WHOLE_BUT_AMBIGUOUS, # never EXACT - plain '=' is collation-dependent and can't certify byte-exactness. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES ('Zz')") con.commit() @@ -510,6 +527,7 @@ class TestEsperanto(unittest.TestCase): def test_host_maxlen_zero(self): con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES ('abcdef')") con.commit() @@ -541,6 +559,7 @@ class TestEsperanto(unittest.TestCase): # `complete` (walk finished) must be distinct from `exact` (bytes proven identical). # a case-insensitive collation recovers a WHOLE value that is NOT exact. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES ('A')") con.commit() @@ -568,6 +587,7 @@ class TestEsperanto(unittest.TestCase): # hostExtract returns an ExtractResult: a bounded read is TRUNCATED (not a bare string), # and an undecided (None) host observation degrades to a FAILED result, never a fake bit. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute("INSERT INTO t VALUES ('abcdef')") con.commit() @@ -602,6 +622,7 @@ class TestEsperanto(unittest.TestCase): # key's first column repeats -> `> prev` paging silently drops rows sharing it, so it # must be REJECTED (COUNT(*) != COUNT(DISTINCT col)); only a unique column is accepted. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE k (a INT, b INT, u INT, nul INT)") con.executemany("INSERT INTO k VALUES (?,?,?,?)", [(1, 1, 10, 1), (1, 2, 20, None), (2, 1, 30, 3)]) con.commit() @@ -651,6 +672,7 @@ class TestEsperanto(unittest.TestCase): def test_enumerate(self): con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE alpha (x)") con.execute("CREATE TABLE beta (y)") con.commit() @@ -668,6 +690,7 @@ class TestEsperanto(unittest.TestCase): def test_dump(self): # row DATA byte-exact, including commas / quotes / unicode (hex framing keeps intact) con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE users (id, uname, note)") truth = [(1, u"admin", u"all,good"), (2, u"o'brien", u"café,€"), (3, u"x", u"")] for row in truth: @@ -692,6 +715,7 @@ class TestEsperanto(unittest.TestCase): # extraction. Quotes/commas/NULLs in the data must survive (a single value has # no framing ambiguity). This is the scavenger's whole reason to exist. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE users (id INTEGER, name TEXT, email TEXT)") for row in ((1, "luther", "a@b.c"), (2, "o'brien", "x,y@z"), (3, "wu", None)): con.execute("INSERT INTO users VALUES (?,?,?)", row) @@ -723,6 +747,7 @@ class TestEsperanto(unittest.TestCase): # catalog detection, brute-force existence probing, and identifier quoting are # all COUNT-free (scalar-subquery existence), so tables/columns/dump still work. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE users (id INTEGER, name TEXT)") con.execute("INSERT INTO users VALUES (1, 'admin'), (2, 'root')") con.commit() @@ -747,6 +772,7 @@ class TestEsperanto(unittest.TestCase): def test_quoting(self): # reserved-word / spaced column names must be quoted, not interpolated raw con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute('CREATE TABLE q (id INTEGER, "order" TEXT, "group by" TEXT)') con.execute('INSERT INTO q VALUES (1, ?, ?)', ("a'b", "x,y")) con.commit() @@ -767,6 +793,7 @@ class TestEsperanto(unittest.TestCase): def test_strategy_handoff(self): # the frozen InferenceStrategy is a *sufficient* host interface, and immutable con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE k (v TEXT)") con.execute("INSERT INTO k VALUES ('Str4t3gy!')") con.commit() @@ -788,6 +815,7 @@ class TestEsperanto(unittest.TestCase): def test_pattern_match_fallback(self): # SUBSTR + LENGTH + hex + code fns ALL blacklisted -> pure GLOB/LIKE floor con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE flag (id INTEGER, v TEXT)") con.execute("INSERT INTO flag VALUES (1, 'FLAG{no_SUBSTR_%_needed}')") con.commit() @@ -812,6 +840,7 @@ class TestEsperanto(unittest.TestCase): # like 'all_products' must escape them (\_ ESCAPE '\'), not treat them as # match-anything. GLOB is blocked here to force LIKE (where '_'/'%' are magic). con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE v (val TEXT)") con.execute("INSERT INTO v VALUES ('all_products')") for t in ("all_products", "wp_users", "sales%2024"): # '_'/'%' in identifiers @@ -838,6 +867,7 @@ class TestEsperanto(unittest.TestCase): def test_length_from_substring(self): # every length fn blacklisted but SUBSTR present -> length derived from the end con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t6 (v TEXT)") con.execute(u"INSERT INTO t6 VALUES ('Admin-42€')") con.commit() @@ -859,6 +889,7 @@ class TestEsperanto(unittest.TestCase): # a permission/charset wall mid-walk (oracle can't decide the keyset bound) # must STOP with partial results, never crash with OracleUndecided con = sqlite3.connect(":memory:") + self.addCleanup(con.close) for t in ("alpha", "beta", "gamma"): con.execute("CREATE TABLE %s (x)" % t) con.commit() @@ -884,6 +915,7 @@ class TestEsperanto(unittest.TestCase): # RIGHT( onto them (the same rewrite the disguised-dialect oracles use). Keeps the # test portable across SQLite builds while still exercising the real left_right rung. con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t7 (v TEXT)") con.execute("INSERT INTO t7 VALUES ('Zagreb-42')") con.commit() @@ -1029,6 +1061,7 @@ class TestEsperanto(unittest.TestCase): seed = ('CREATE TABLE q (id INTEGER, "order" TEXT, "group" TEXT)', "INSERT INTO q VALUES (1, ?, ?)") con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute(seed[0]); con.execute(seed[1], ("a,b", "x'y")); con.commit() blk = re.compile(r'"|`') @@ -1050,6 +1083,7 @@ class TestEsperanto(unittest.TestCase): # a first-byte charcode fn (MySQL-style ASCII) is lossy for non-ASCII; the # engine must detect that and escalate to hex, recovering the bytes exactly con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE t (v TEXT)") con.execute(u"INSERT INTO t VALUES ('café-€')") # cafe-EUR con.commit() @@ -1071,6 +1105,7 @@ class TestEsperanto(unittest.TestCase): # non-ASCII data recovered byte-exact even while the dialect is disguised # (SUBSTR/MID/CHAR_LENGTH/LENGTH blocked -> SUBSTRING/LEN mapped to SQLite) con = sqlite3.connect(":memory:") + self.addCleanup(con.close) con.execute("CREATE TABLE s (v TEXT)") con.execute(u"INSERT INTO s VALUES ('Zagreb-župa-€42')") # Zagreb-zupa-EUR42 con.commit() @@ -1091,5 +1126,13 @@ class TestEsperanto(unittest.TestCase): self.assertEqual(esp.extractText("(SELECT v FROM s)"), u"Zagreb-župa-€42") +def tearDownModule(): + while _OPEN_CONNECTIONS: + try: + _OPEN_CONNECTIONS.pop().close() + except Exception: + pass + + if __name__ == "__main__": unittest.main(verbosity=2)