From f932a3f30f3f7ec20f3282e4485b8fb38dcb67d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 30 Jun 2026 20:45:46 +0200 Subject: [PATCH] Minor refactoring --- data/txt/sha256sums.txt | 10 ++--- lib/core/optiondict.py | 2 - lib/core/settings.py | 2 +- lib/parse/cmdline.py | 50 +++++++++++------------- lib/techniques/ssti/inject.py | 72 +---------------------------------- tests/test_ssti.py | 33 ---------------- 6 files changed, 30 insertions(+), 139 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b510dbb87..2b0de81f4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,7 +181,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -33ed53b263fa766a808be6797dd812822bb115d3b9db6e3a34763f500f5359e8 lib/core/optiondict.py +5a576f802f1298d0aa357e766ae6502fa53cacbbe0b1d328b7410a8b20a885b2 lib/core/optiondict.py e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -2498555483d50cf55f24dcb82b3253816a1ad6c3325b17e502d5063f2c9cbc87 lib/core/settings.py +098e5d86a0da05d4be5f5ed5371083954be2369abce57fda4bd906d12e1f8870 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -200,7 +200,7 @@ b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unesc 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -316cdcb3d8d839dab639ed7eb4935780375d49c93371edbd6224976cbb968c2e lib/parse/cmdline.py +403ebb5b54531cf907a30ed439fc881cf3cbae68c3a4ec600c75312e5f6b9001 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -247,7 +247,7 @@ c3e5cf7e5e35ae5fd86b63a515b37e6f06e61c70d2690252f2ee8373aa16637e lib/techniques 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py bde75d41ac3e5747b96d2af4c33922573158cb43b48714a28490d6720dd85d89 lib/techniques/nosql/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ssti/__init__.py -8eaf90c2fa517a4577467ac0d7534a927c23931b946b27e88e63ae022f794a1c lib/techniques/ssti/inject.py +14637b64878248e5965887b07aa68e62615dac88e2ffc6c3a581430bdd4e309e lib/techniques/ssti/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py c65766f71e285fc85cdf58e7448c4c1d015af2a9dbb44fa3b665a9f13362fbcc lib/techniques/union/use.py @@ -643,7 +643,7 @@ cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_saf a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py 29d0278e3718b0fee422d3f6bb85ca02560138d48cd76f9fe1f35ac19d96071b tests/test_sgmllib.py d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py -4a9409a070770cc6300ed2b0c954254273479252fa602ffd19d78917f895756c tests/test_ssti.py +412a61053c2531cc0380b34dfd01d52bd118f6a6473728c069c467054c7e3c8e tests/test_ssti.py 8bcbf1091134dd0a62f6201f8b3645ed87b5ff2f7ba40a87231a29dac412591f tests/test_strings.py 8f1c5f0f337ecd26d35c5551060034e0aa33a62cce5385fc1227fdc485f6383e tests/test_tamper.py 67472bd71c20782cc0f738e2c2e674c29d6985669e14d15b69baef7d0e33de62 tests/test_target_parsing.py diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 69d76f704..7b05a0652 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -173,8 +173,6 @@ optDict = { "lastChar": "integer", "sqlQuery": "string", "sqlShell": "boolean", - "sstiQuery": "string", - "sstiShell": "boolean", "sqlFile": "string", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index 092cf2fd0..e55e69f12 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.6.197" +VERSION = "1.10.6.198" 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/lib/parse/cmdline.py b/lib/parse/cmdline.py index 3a134484c..086ffd903 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -133,7 +133,7 @@ def cmdLineParser(argv=None): help="Parse target(s) from Burp or WebScarab proxy log file") target.add_argument("-m", dest="bulkFile", - help="Scan multiple targets given in a textual file ") + help="Scan multiple targets given in a textual file") target.add_argument("-r", dest="requestFile", help="Load HTTP request from a file") @@ -335,7 +335,7 @@ def cmdLineParser(argv=None): help="Skip testing for given parameter(s)") injection.add_argument("--skip-static", dest="skipStatic", action="store_true", - help="Skip testing parameters that not appear to be dynamic") + help="Skip testing parameters that do not appear to be dynamic") injection.add_argument("--param-exclude", dest="paramExclude", help="Regexp to exclude parameters from testing (e.g. \"ses\")") @@ -442,21 +442,6 @@ def cmdLineParser(argv=None): techniques.add_argument("--second-req", dest="secondReq", help="Load second-order HTTP request from file") - techniques.add_argument("--graphql", dest="graphql", action="store_true", - help="Test for GraphQL injection") - - techniques.add_argument("--ldap", dest="ldap", action="store_true", - help="Test for LDAP injection") - - techniques.add_argument("--nosql", dest="nosql", action="store_true", - help="Test for NoSQL injection") - - techniques.add_argument("--xpath", dest="xpath", action="store_true", - help="Test for XPath injection") - - techniques.add_argument("--ssti", dest="ssti", action="store_true", - help="Test for server-side template injection") - # Fingerprint options fingerprint = parser.add_argument_group("Fingerprint", "These options can be used to perform a back-end database management system version fingerprint") @@ -515,7 +500,7 @@ def cmdLineParser(argv=None): help="Dump DBMS database table entries") enumeration.add_argument("--dump-all", dest="dumpAll", action="store_true", - help="Dump all DBMS databases tables entries") + help="Dump entries of all DBMS database tables") enumeration.add_argument("--search", dest="search", action="store_true", help="Search column(s), table(s) and/or database name(s)") @@ -571,12 +556,6 @@ def cmdLineParser(argv=None): enumeration.add_argument("--sql-shell", dest="sqlShell", action="store_true", help="Prompt for an interactive SQL shell") - enumeration.add_argument("--ssti-query", dest="sstiQuery", - help="SSTI expression to evaluate in-band on the vulnerable parameter") - - enumeration.add_argument("--ssti-shell", dest="sstiShell", action="store_true", - help="Prompt for an interactive SSTI expression shell") - enumeration.add_argument("--sql-file", dest="sqlFile", help="Execute SQL statements from given file(s)") @@ -626,11 +605,10 @@ def cmdLineParser(argv=None): help="Prompt for an OOB shell, Meterpreter or VNC") takeover.add_argument("--os-smbrelay", dest="osSmb", action="store_true", - help="One click prompt for an OOB shell, Meterpreter or VNC") + help="One-click prompt for an OOB shell, Meterpreter or VNC") takeover.add_argument("--os-bof", dest="osBof", action="store_true", - help="Stored procedure buffer overflow " - "exploitation") + help="Stored procedure buffer overflow exploitation") takeover.add_argument("--priv-esc", dest="privEsc", action="store_true", help="Database process user privilege escalation") @@ -788,6 +766,24 @@ def cmdLineParser(argv=None): general.add_argument("--web-root", dest="webRoot", help="Web server document root directory (e.g. \"/var/www\")") + # Non-SQL injection options + nonsql = parser.add_argument_group("Non-SQL injection", "These options can be used to test for non-SQL injection types") + + nonsql.add_argument("--graphql", dest="graphql", action="store_true", + help="Test for GraphQL injection") + + nonsql.add_argument("--ldap", dest="ldap", action="store_true", + help="Test for LDAP injection") + + nonsql.add_argument("--nosql", dest="nosql", action="store_true", + help="Test for NoSQL injection") + + nonsql.add_argument("--xpath", dest="xpath", action="store_true", + help="Test for XPath injection") + + nonsql.add_argument("--ssti", dest="ssti", action="store_true", + help="Test for server-side template injection") + # Miscellaneous options miscellaneous = parser.add_argument_group("Miscellaneous", "These options do not fit into any other category") diff --git a/lib/techniques/ssti/inject.py b/lib/techniques/ssti/inject.py index bbbc6e90c..736a70b51 100644 --- a/lib/techniques/ssti/inject.py +++ b/lib/techniques/ssti/inject.py @@ -59,12 +59,6 @@ def _arithmeticPayload(fmt, a, b): return fmt.replace("%d", str(a), 1).replace("%d", str(b), 1) -def _expressionPayload(fmt, value): - # Same rationale as _arithmeticPayload(): literal %s substitution so '%'-delimited engines - # (notably ERB) can wrap expressions instead of crashing on fmt % value. - return fmt.replace("%s", value, 1) - - def _degroup(text): # Strip digit-group (thousands) separators so an arithmetic result still matches when the # engine formats large numbers with grouping (e.g. FreeMarker renders 234*567 as "132,678"). @@ -642,7 +636,7 @@ def sstiScan(): place, parameter, engine, evidence = slot from lib.core.common import readInput - wantsTakeover = any(conf.get(_) for _ in ("osCmd", "osShell", "sstiQuery", "sstiShell")) + wantsTakeover = any(conf.get(_) for _ in ("osCmd", "osShell")) # If the user did not ask for exploitation, confirm (benignly) whether OS command # execution is reachable and, if so, advise the relevant switches. @@ -651,20 +645,6 @@ def sstiScan(): "you are advised to try '--os-shell' (interactive) or " "'--os-cmd=' (single command)" % engine.name) - # --ssti-query: user-provided expression evaluated in-band - if conf.get("sstiQuery"): - _evalExpression(place, parameter, engine, conf.sstiQuery) - - # --ssti-shell: interactive expression evaluation loop (interactive even under --batch, - # like sqlmap's SQL --sql-shell/--os-shell, which read straight from the terminal) - if conf.get("sstiShell"): - logger.info("calling SSTI shell. Enter expressions (e.g. 7*7) or 'exit'/'quit' to leave") - while True: - expr = readInput("ssti-shell> ", checkBatch=False) - if not expr or expr.strip().lower() in ("exit", "quit"): - break - _evalExpression(place, parameter, engine, expr.strip()) - # --os-cmd / --os-shell: RCE via SSTI (reuses existing SQL takeover flags) if conf.get("osCmd") or conf.get("osShell"): if not _canTakeover(engine, evidence): @@ -692,56 +672,6 @@ def _escapeSingleQuoted(value): return value.replace("\\", "\\\\").replace("'", "\\'") -def _evalExpression(place, parameter, engine, expr): - """Wrap expr in the engine's expression format, extract result between - random markers for deterministic output, fall back to baseline diff.""" - - if not engine.expressionFmt: - logger.error("expression evaluation not supported for engine '%s'" % engine.name) - return - - original = _originalValue(place, parameter) or "" - startMarker = randomStr(length=8, lowercase=True) - endMarker = randomStr(length=8, lowercase=True) - - # Three-part payload: marker, expression, marker -- each in its own template tag - # so the expression is evaluated independently of the markers - payload = original + _expressionPayload(engine.expressionFmt, "'%s'" % startMarker) - payload += " " + _expressionPayload(engine.expressionFmt, expr) - payload += " " + _expressionPayload(engine.expressionFmt, "'%s'" % endMarker) - page = _send(place, parameter, payload) - - if not page: - logger.warning("no response for SSTI expression '%s'" % expr) - return - - text = getUnicode(page) - result = None - - # Extract content between the random markers - if startMarker in text and endMarker in text: - start = text.index(startMarker) + len(startMarker) - end = text.index(endMarker, start) - result = text[start:end].strip() - - # Fallback: diff against baseline - if not result: - baseline = _send(place, parameter, original) - if baseline: - sm = difflib.SequenceMatcher(None, getUnicode(baseline), text) - parts = [] - for tag, i1, i2, j1, j2 in sm.get_opcodes(): - if tag in ("insert", "replace"): - parts.append(text[j1:j2]) - if parts: - result = "".join(parts).strip() - - if result: - conf.dumper.singleString("SSTI expression result: %s" % result) - else: - logger.warning("could not extract expression result from response") - - def _canTakeover(engine, evidence): """Require exact engine fingerprint (not a family guess) and confirmed proof before attempting OS command execution.""" diff --git a/tests/test_ssti.py b/tests/test_ssti.py index 02ff44f35..96b714bc0 100644 --- a/tests/test_ssti.py +++ b/tests/test_ssti.py @@ -275,39 +275,6 @@ class TestCrossEngineDisambiguation(unittest.TestCase): self.assertIn("Twig", engine.name) -class TestExpressionEvaluation(unittest.TestCase): - def setUp(self): - self.original_send = ssti._send - - def tearDown(self): - ssti._send = self.original_send - - def test_eval_uses_expressionFmt(self): - engine = ssti._ENGINE_TABLE[0] # Jinja2: expressionFmt = "{{ %s }}" - results = [] - - def mock(place, parameter, value): - results.append(value) - return "Hello __marker__ 49 __marker2__" - - ssti._send = mock - ssti._evalExpression("GET", "q", engine, "7*7") - # Payload must use expressionFmt, not raw delimiter concatenation - self.assertIn("{{ ", results[0]) - self.assertIn(" }}", results[0]) - - def test_eval_falls_back_when_no_expressionFmt(self): - engine = [e for e in ssti._ENGINE_TABLE if e.name == "Handlebars"][0] - self.assertEqual(engine.expressionFmt, "") - - def mock(place, parameter, value): - return "irrelevant" - - ssti._send = mock - # Should not raise; just logs error - ssti._evalExpression("GET", "q", engine, "7*7") - - class TestBooleanUniqueness(unittest.TestCase): def test_jinja2_boolean_unique_among_curlies(self): jinja2 = ssti._ENGINE_TABLE[0]