From 57dcc04cbe2896784a487e187be79b7f9ae78aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 22:28:19 +0200 Subject: [PATCH 1/5] Minor improvements --- data/txt/sha256sums.txt | 8 ++--- lib/core/settings.py | 10 +++++- lib/techniques/blind/inference.py | 11 +++++++ lib/utils/dialect.py | 43 +++++++++++++++---------- tests/test_dialectdbms.py | 52 ++++++++++++++++++++++--------- 5 files changed, 87 insertions(+), 37 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 7576be296de..ee05cd0f89a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -90a49806b83a83f6402b3dd6e35f7f2468d3dbcc0cafc3c382bda6e248344609 lib/core/settings.py +527ee951185f691c68638f03d4da8f9bc894a93f1a791865fc2cc0992ad5f03e lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -232,7 +232,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py 23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py 8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py -63e2bc0e2fb6407760245b4f36d7430b626b9654bce51485b6cbf24717225246 lib/techniques/blind/inference.py +a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques/blind/inference.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py @@ -247,7 +247,7 @@ aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api. 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py -0fd055877e8b21d17c11447dac7f91ef1766e0b04d470c494a6d98f5249e3186 lib/utils/dialect.py +b0d8ae8513c1f5ffcaa4bf0398790f26bc2180a6acf07bf5b2c86555bf9113f6 lib/utils/dialect.py 51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py 853c3595e1d2efc54b8bfb6ab12c55d1efc1603be266978e3a7d96d553d91a52 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py @@ -584,7 +584,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_datafiles.py 9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py -9c0a0cd0b2d52a53f75c98c60f87a022354b7c3dc4baaf3fe1e272a0af5b7f0a tests/test_dialectdbms.py +b6d8a4bc9c46a332a2dc7b3cf862ea67e38b5c5701cfd8eb3556021f6b611416 tests/test_dialectdbms.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py 7f12466974394312dad3d98651ef8a50d1585bee0f8cd25da0b77b08c2047e46 tests/test_dns_engine.py diff --git a/lib/core/settings.py b/lib/core/settings.py index b00474f0cef..00e3231091d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.6.152" +VERSION = "1.10.6.153" 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) @@ -533,6 +533,14 @@ for _char in _chars: HUFFMAN_PRIOR_WEIGHTS[ord(_char)] = _weight +# Bounds for feeding extracted values back into the "good samaritan" (--predict-output) common-output +# pool for their enumeration context, so later same-context items that share structure (e.g. +# wp_posts / wp_users / wp_options ...) are predicted faster. MAX_LENGTH keeps large data cells from +# bloating/polluting the pool (identifiers are short); MAX_ITEMS bounds per-context growth so a huge +# enumeration cannot make the per-character prediction scan costly. Misses always fall back to bisection. +PREDICTION_FEEDBACK_MAX_LENGTH = 128 +PREDICTION_FEEDBACK_MAX_ITEMS = 10000 + # Minimum range between minimum and maximum of statistical set MIN_STATISTICAL_RANGE = 0.01 diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 3b20202331f..46a99430c4a 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -44,6 +44,8 @@ from lib.core.settings import CHAR_INFERENCE_MARK from lib.core.settings import HUFFMAN_PROBE_LIMIT from lib.core.settings import HUFFMAN_PRIOR_WEIGHTS +from lib.core.settings import PREDICTION_FEEDBACK_MAX_ITEMS +from lib.core.settings import PREDICTION_FEEDBACK_MAX_LENGTH from lib.core.settings import INFERENCE_BLANK_BREAK from lib.core.settings import INFERENCE_EQUALS_CHAR from lib.core.settings import INFERENCE_GREATER_CHAR @@ -828,6 +830,15 @@ def blindThread(): finalValue = decodeDbmsHexValue(finalValue) if conf.hexConvert else finalValue if not (conf.firstChar or conf.lastChar): # Note: --first/--last give a range-limited (non-complete) output; caching it unmarked would let a later resume serve the truncated value as the full one hashDBWrite(expression, finalValue) + + # Adaptive intra-run prediction (good samaritan / --predict-output): remember this extracted + # value for its enumeration context so later same-context items sharing structure are predicted + # faster. Length-capped (identifiers are short -> large data cells never bloat/pollute the pool); + # a wrong prediction only ever costs a probe and falls back to bisection. + if (conf.predictOutput and kb.partRun and kb.commonOutputs is not None + and 0 < len(finalValue) <= PREDICTION_FEEDBACK_MAX_LENGTH + and len(kb.commonOutputs.get(kb.partRun) or ()) < PREDICTION_FEEDBACK_MAX_ITEMS): + kb.commonOutputs.setdefault(kb.partRun, set()).add(finalValue) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) diff --git a/lib/utils/dialect.py b/lib/utils/dialect.py index 1d225c3d27a..3be67eac89d 100644 --- a/lib/utils/dialect.py +++ b/lib/utils/dialect.py @@ -28,23 +28,28 @@ # OTHER valid rows, which sqlmap's fuzzy page comparison conflates with the anchor row, producing # false positives. See PROVE_DESIGN.md.) # -# Truth table measured on a live OWASP-CRS platform across 11 engines (MySQL, MariaDB/TiDB, -# PostgreSQL, CockroachDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse, H2, HSQLDB, Derby); -# only the zero-false-positive rules are kept (see _classify). With anchor value 2: +# Truth table measured on a live OWASP-CRS platform across 16 engines (MySQL/MySQL5, MariaDB/TiDB, +# PostgreSQL, CockroachDB, CrateDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse, H2, HSQLDB, +# Derby, MonetDB, IRIS, Trino); only the zero-false-positive rules are kept (see _classify). With +# anchor value 2: # -# * 2^0=2 -> '^' is bitwise XOR (MySQL/MSSQL: 2^0=2) vs exponentiation (PostgreSQL: 2^0=1) vs -# no such operator (SQLite/Oracle/... -> error, so false) -# * 2^3=8 -> '^' is exponentiation (PostgreSQL/CockroachDB: 2^3=8) - false for XOR dialects +# * 2^0=2 -> '^' is bitwise XOR (MySQL/MSSQL/MonetDB: 2^0=2) vs exponentiation (PostgreSQL: 2^0=1) +# vs no such operator (SQLite/Oracle/... -> error, so false) +# * 2^3=8 -> '^' is exponentiation (PostgreSQL/CockroachDB/CrateDB: 2^3=8) - false for XOR dialects # (2^3=1) and erroring dialects; a positive PostgreSQL-family marker. CAVEAT: # '^'=exponentiation is not strictly unique to PostgreSQL - MS Access/Jet and DuckDB # also use it (neither on the platform), so this can read as PostgreSQL there. -# * 5/2=2 -> integer division (PostgreSQL/MSSQL/SQLite) vs real division (MySQL/Oracle: 2.5) +# * 5/2=2 -> integer division (PostgreSQL/MSSQL/SQLite/MonetDB) vs real division (MySQL/Oracle: 2.5) # * 2|0=2 -> a bitwise OR operator exists (absent in Firebird/Oracle/ClickHouse/H2) +# * 1<<2=4 -> a bit-shift operator exists. MonetDB shares MSSQL's (xor, intdiv) = (True, True) +# signature exactly, which would misread MonetDB as SQL Server; MonetDB HAS '<<' while +# SQL Server has NO shift operator (any version) -> this probe splits that one collision. DIALECT_PROBES = ( ("xor", "2^0=2"), ("pgpow", "2^3=8"), ("intdiv", "5/2=2"), ("bitor", "2|0=2"), + ("shift", "1<<2=4"), ) def _classify(signature): @@ -58,28 +63,32 @@ def _classify(signature): all-false signature, which a minimal engine like ClickHouse/H2/Firebird/HSQLDB/Derby or a fully WAF-blocked channel also produces) deliberately fall through to None: - >>> _classify((True, False, False, True)) # MySQL / MariaDB / TiDB + >>> _classify((True, False, False, True, True)) # MySQL / MariaDB / TiDB 'MySQL' - >>> _classify((True, False, True, True)) # Microsoft SQL Server + >>> _classify((True, False, True, True, False)) # Microsoft SQL Server (no bit-shift) 'Microsoft SQL Server' - >>> _classify((False, True, True, True)) # PostgreSQL + >>> _classify((True, False, True, True, True)) # MonetDB (same xor/intdiv as MSSQL, but has '<<') + 'MonetDB' + >>> _classify((False, True, True, True, False)) # PostgreSQL 'PostgreSQL' - >>> _classify((False, True, False, True)) # CockroachDB (pgwire) -> PostgreSQL family + >>> _classify((False, True, False, True, False)) # CockroachDB (pgwire) -> PostgreSQL family 'PostgreSQL' - >>> _classify((False, False, True, True)) # SQLite + >>> _classify((False, False, True, True, True)) # SQLite 'SQLite' - >>> _classify((False, False, True, False)) is None # Firebird/HSQLDB/Derby/H2 -> no prior + >>> _classify((False, False, True, False, False)) is None # Firebird/HSQLDB/Derby/H2/Trino -> no prior True - >>> _classify((False, False, False, False)) is None # all-false (Oracle/ClickHouse/blocked) -> no prior + >>> _classify((False, False, False, False, False)) is None # all-false (Oracle/ClickHouse/IRIS/blocked) -> no prior True """ - xor, pgpow, intdiv, bitor = signature + xor, pgpow, intdiv, bitor, shift = signature if pgpow: # '^' is exponentiation -> PostgreSQL family return DBMS.PGSQL - if xor and intdiv: # '^' is XOR AND integer division -> SQL Server - return DBMS.MSSQL + if xor and intdiv: # '^' is XOR AND integer division -> SQL Server ... + # ... except MonetDB shares this exact signature; it alone has a working bit-shift operator + # ('1<<2=4'), SQL Server has none -> split the collision (measured zero-FP across 16 engines). + return DBMS.MONETDB if shift else DBMS.MSSQL if xor and not intdiv: # '^' is XOR AND real division -> MySQL family return DBMS.MYSQL if not xor and intdiv and bitor: # no '^', integer division, bitwise '|' -> SQLite diff --git a/tests/test_dialectdbms.py b/tests/test_dialectdbms.py index 6b464cbc5cd..81de07ece32 100644 --- a/tests/test_dialectdbms.py +++ b/tests/test_dialectdbms.py @@ -28,46 +28,68 @@ from lib.utils.dialect import dialectCheckDbms # measured 2026-06 across the sqli-platform (boolean form "id=2 AND ", anchor value 2); -# signature = (2^0=2, 2^3=8, 5/2=2, 2|0=2) +# base signature = (2^0=2, 2^3=8, 5/2=2, 2|0=2). The 5th probe (1<<2=4, bit-shift) is the MonetDB-vs- +# SQL Server disambiguator and is asserted separately (SHIFT_SENSITIVE); for every other engine the +# shift flag does NOT change the classification, which the test proves by trying it both ways. MEASURED = { "mysql": ((True, False, False, True), DBMS.MYSQL), + "mysql5": ((True, False, False, True), DBMS.MYSQL), "tidb": ((True, False, False, True), DBMS.MYSQL), # MySQL wire-compatible - "mssql": ((True, False, True, True), DBMS.MSSQL), "postgres": ((False, True, True, True), DBMS.PGSQL), "cockroach": ((False, True, False, True), DBMS.PGSQL), # pgwire (exponent '^', decimal division) + "cratedb": ((False, True, True, True), DBMS.PGSQL), # pgwire family "sqlite": ((False, False, True, True), DBMS.SQLITE), # not distinctive enough -> deliberately no prior (operators alone can't safely separate these) "firebird": ((False, False, True, False), None), "hsqldb": ((False, False, True, False), None), # collides with firebird/derby/h2 "derby": ((False, False, True, False), None), "h2": ((False, False, True, False), None), + "trino": ((False, False, True, False), None), + "iris": ((False, False, False, False), None), # all-error, like Oracle/broken channel "clickhouse": ((False, False, False, False), None), # all-error, like Oracle/broken channel } +# engines whose full 5-probe signature (incl. 1<<2=4) is needed because they share base-4 (xor,intdiv) +# and only the bit-shift probe separates them: SQL Server has no shift operator, MonetDB does. +SHIFT_SENSITIVE = { + "mssql": ((True, False, True, True, False), DBMS.MSSQL), + "monetdb": ((True, False, True, True, True), DBMS.MONETDB), +} + class TestDialectClassification(unittest.TestCase): - def test_measured_engines_map_as_expected(self): - for engine, (signature, expected) in MEASURED.items(): + def test_shift_sensitive_engines_split_correctly(self): + # MonetDB shared MSSQL's (xor, intdiv) signature exactly (a false positive before the shift + # probe); 1<<2=4 (MonetDB only) now separates them. + for engine, (signature, expected) in SHIFT_SENSITIVE.items(): self.assertEqual(_classify(signature), expected, "engine %r misclassified" % engine) + def test_measured_engines_map_as_expected(self): + # for non-shift-sensitive engines the shift flag is irrelevant: assert BOTH values map to the + # expected DBMS (proves the new probe never perturbs the existing classifications). + for engine, (base, expected) in MEASURED.items(): + for shift in (False, True): + self.assertEqual(_classify(base + (shift,)), expected, "engine %r misclassified (shift=%s)" % (engine, shift)) + def test_no_false_positive_across_measured_set(self): - # ambiguous engines must not borrow a major-DBMS identity; concrete ones must stay in range - for engine, (signature, expected) in MEASURED.items(): - result = _classify(signature) - if expected is None: - self.assertIsNone(result, "ambiguous engine %r leaked a DBMS prior" % engine) - else: - self.assertIn(result, (DBMS.MYSQL, DBMS.MSSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.ORACLE)) + for engine, (base, expected) in MEASURED.items(): + for shift in (False, True): + result = _classify(base + (shift,)) + if expected is None: + self.assertIsNone(result, "ambiguous engine %r leaked a DBMS prior" % engine) + else: + self.assertIn(result, (DBMS.MYSQL, DBMS.MSSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.MONETDB, DBMS.ORACLE)) def test_all_error_signature_yields_no_prior(self): - # an all-error signature (Oracle, ClickHouse, or simply a WAF-blocked channel) is not + # an all-error signature (Oracle, ClickHouse, IRIS, or simply a WAF-blocked channel) is not # distinctive enough - it must NOT be guessed as any DBMS - self.assertIsNone(_classify((False, False, False, False))) + self.assertIsNone(_classify((False, False, False, False, False))) + self.assertIsNone(_classify((False, False, False, False, True))) def test_pgpow_dominates_as_postgres_marker(self): # exponentiation '^' is a positive PostgreSQL-family marker regardless of division flavour - self.assertEqual(_classify((False, True, True, True)), DBMS.PGSQL) - self.assertEqual(_classify((False, True, False, True)), DBMS.PGSQL) + self.assertEqual(_classify((False, True, True, True, False)), DBMS.PGSQL) + self.assertEqual(_classify((False, True, False, True, False)), DBMS.PGSQL) class TestDialectCheckDbmsGuard(unittest.TestCase): From 0cfa88e50e2dd112273454aad26fc114ca16547a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 22:40:50 +0200 Subject: [PATCH 2/5] Minor patch --- data/txt/sha256sums.txt | 4 ++-- data/xml/errors.xml | 3 ++- lib/core/settings.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ee05cd0f89a..614668f177b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -77,7 +77,7 @@ a7eb4d1bcbdfd155383dcd35396e2d9dd40c2e89ce9d5a02e63a95a94f0ab4ea data/xml/banne e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banner/x-aspnet-version.xml 3a440fbbf8adffbe6f570978e96657da2750c76043f8e88a2c269fe9a190778c data/xml/banner/x-powered-by.xml a32fc8796082d2e45cfc969f0b45ad476bf87a8515d67b2fed77c5058df5a0f5 data/xml/boundaries.xml -0baf0fade74d4ad294ee88ef306743da0c6a4631b8d640708809103ef9cf63ed data/xml/errors.xml +23c3ac7f73c4db5beaf9df06c39a63571b29b3f3bee161e182a62c7fcc563054 data/xml/errors.xml 43910a73d7de51e3541bfe4bdffe8923c73b0fbd74300912d4cec95d4f728673 data/xml/payloads/boolean_blind.xml c8d467837c8567b61a11e2dfd75a2d8305a8b317041ee81eda6d0e47609dabb7 data/xml/payloads/error_based.xml 516a2ff314bba3ecf65d0371bf8c2654ad79b09c0737b1fe0f178d7885a9508d data/xml/payloads/inline_query.xml @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -527ee951185f691c68638f03d4da8f9bc894a93f1a791865fc2cc0992ad5f03e lib/core/settings.py +530fae56ab8dfd3f7b68a973f2e42d47d8061a42e03b56b40350f50423ea8935 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/data/xml/errors.xml b/data/xml/errors.xml index 1b34d551582..f066da0b92d 100644 --- a/data/xml/errors.xml +++ b/data/xml/errors.xml @@ -179,6 +179,7 @@ + @@ -224,7 +225,7 @@ - + diff --git a/lib/core/settings.py b/lib/core/settings.py index 00e3231091d..748bf530829 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.6.153" +VERSION = "1.10.6.154" 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) From a2bbca1ee34d97f12066dbae025306eeaa7b1188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 23:20:09 +0200 Subject: [PATCH 3/5] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/dicts.py | 4 ++-- lib/core/settings.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 614668f177b..89144295da3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -175,7 +175,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 6c8d40d6bbab4a60d09eb03324a3352d85df1a741c62044e73701e92172d1d38 lib/core/datatype.py f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py -7ce2c09ebcd63d57f7b6751f70f536e2a562230d51181eb24f5024bb6f3d74cc lib/core/dicts.py +8b9033027229db2b44134cc8bf3a47db1165ef64a13ebeccc6394d9d6453998d lib/core/dicts.py a3125c682e891f67255b89d2db891cbaae241f36dd277a272ae6db943111a157 lib/core/dump.py 6b6514202c6ca2d29069176bccf10492927d83e6ede06c9f4b4fcc6164e61856 lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -530fae56ab8dfd3f7b68a973f2e42d47d8061a42e03b56b40350f50423ea8935 lib/core/settings.py +dde396241b71e93a6ee1a9a07b0726d6674dde7aeed05cb80ecb96cce0e78612 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 6d3864328a6..aa191a20fed 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -286,9 +286,9 @@ DBMS.PRESTO: "FROM_HEX(NULL)", DBMS.ALTIBASE: "TDESENCRYPT(NULL,NULL)", DBMS.MIMERSQL: "ASCII_CHAR(256)", - DBMS.CRATEDB: "MD5(NULL~NULL)", # NOTE: NULL~NULL also being evaluated on H2 and Ignite + DBMS.CRATEDB: "GEN_RANDOM_TEXT_UUID()~NULL", # NOTE: old MD5(NULL~NULL) was too loose (also NULL on MonetDB/H2/Ignite -> they mis-identified as CrateDB); gen_random_text_uuid() is CrateDB-only, and ~NULL keeps it a NULL-eval DBMS.CUBRID: "(NULL SETEQ NULL)", - DBMS.CACHE: "%SQLUPPER NULL", + DBMS.CACHE: "%EXACT(NULL)", # NOTE: '%SQLUPPER NULL' does not parse inside the heuristic's (SELECT ...) form, so Cache/IRIS fell through to a later, non-unique marker (e.g. Mckoi TONUMBER); the %-prefixed collation function %EXACT() is InterSystems-unique and works here DBMS.EXTREMEDB: "NULLIFZERO(hashcode(NULL))", DBMS.RAIMA: "IF(ROWNUMBER()>0,CONVERT(NULL,TINYINT),NULL)", DBMS.VIRTUOSO: "__MAX_NOTNULL(NULL)", diff --git a/lib/core/settings.py b/lib/core/settings.py index 748bf530829..6c4ab289517 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.6.154" +VERSION = "1.10.6.155" 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) From 15715a2513c7a05fa844392d81179128010bdb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 24 Jun 2026 01:35:02 +0200 Subject: [PATCH 4/5] Adding switch --procs (#778) --- data/txt/sha256sums.txt | 18 +++++------ data/xml/queries.xml | 17 ++++++++++ lib/controller/action.py | 3 ++ lib/core/dicts.py | 1 + lib/core/dump.py | 3 ++ lib/core/enums.py | 1 + lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 ++ plugins/generic/databases.py | 60 ++++++++++++++++++++++++++++++++++++ 10 files changed, 99 insertions(+), 10 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 89144295da3..89807f23e72 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -84,7 +84,7 @@ c8d467837c8567b61a11e2dfd75a2d8305a8b317041ee81eda6d0e47609dabb7 data/xml/paylo 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 379fc92f2dadd948f401e17490d8a8f03a1988d817323cbe1feff5fe87726079 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -ff368554d3320ffa50751e32c903aeec21221f351f3efa573a211081947f69e8 data/xml/queries.xml +a6127cc68b62709149a0e58a314d9003865945018cc5a43d60afc3698d92c6e9 data/xml/queries.xml 127799739f9aeabca367027197f3c0240f141303bd7499928ccfa1443bf148c7 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md @@ -161,7 +161,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py -3abecaec1a9c59645a4821463a2d761235f7a4f763a491f188a41a083bbddd98 lib/controller/action.py +a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py 9387fb775b694156a71b336a2a9638ef24c577aa38746f391ac040ff05306d95 lib/controller/checks.py 96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py @@ -175,13 +175,13 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 6c8d40d6bbab4a60d09eb03324a3352d85df1a741c62044e73701e92172d1d38 lib/core/datatype.py f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py -8b9033027229db2b44134cc8bf3a47db1165ef64a13ebeccc6394d9d6453998d lib/core/dicts.py -a3125c682e891f67255b89d2db891cbaae241f36dd277a272ae6db943111a157 lib/core/dump.py -6b6514202c6ca2d29069176bccf10492927d83e6ede06c9f4b4fcc6164e61856 lib/core/enums.py +8e4f4b5ea37a49d445bb0df83bf04b34f61035ec33fd8acf598ebcf371cb19a7 lib/core/dicts.py +854073f899b876ab13b36e93e174b9cfe51408f7343040197a80afd9fc9c65ee lib/core/dump.py +6dd47f52082e98dc0cda6969b277b7d81c6f7c68dac4688821f873a1c65c6edf lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -b5da34bba9ce71ede23349698988939501f5df07be151856007b9b8425a228db lib/core/optiondict.py +8b260bff7f24947ece55727277d526c88a91f7cb9ffe059c4b9c190bf85f80e1 lib/core/optiondict.py 4e7f2ad3d2866093aa195616a0e93de1687406edc0b9038fbfa76bf1c9c174b2 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -dde396241b71e93a6ee1a9a07b0726d6674dde7aeed05cb80ecb96cce0e78612 lib/core/settings.py +d6572ecbd0d7a26839f5098d68cb02fb5b498c88f0d1c36928c5611a96f9d19a 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 -d0aa9559d1aa94f5c1a647997e9298eb03403a5800ffb739bb3ceba8b5a37da9 lib/parse/cmdline.py +386065c4c40e07a10875d0b73b4ca2fb682c598e8d52b41d0b6b08d5c2c7b3c1 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -482,7 +482,7 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v 2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py 51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py -020f0f828121fe03704fdef241364ffd33c5dce1e5d04028bc7375b4563c3696 plugins/generic/databases.py +6f77b5cae6781a746f8490fe3e85456e575165b38edd280a69c9327af8bee85f plugins/generic/databases.py 13086bfae6022edc2bbd35512fa3bda3402c269e9d6148ffe386ba5b8b4ba461 plugins/generic/entries.py d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py a02ac4ebc1cc488a2aa5ae07e6d0c3d5064e99ded7fd529dfa073735692f11df plugins/generic/filesystem.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 64e8823cc54..82674355375 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -47,6 +47,10 @@ + + + + @@ -123,6 +127,10 @@ + + + + @@ -195,6 +203,10 @@ + + + + @@ -290,6 +302,11 @@ + + + + + diff --git a/lib/controller/action.py b/lib/controller/action.py index b6153548160..8fe73ebf5a6 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -114,6 +114,9 @@ def action(): if conf.getStatements: conf.dumper.statements(conf.dbmsHandler.getStatements()) + if conf.getProcs: + conf.dumper.procedures(conf.dbmsHandler.getProcedures()) + if conf.getPasswordHashes: try: conf.dumper.userSettings("database management system users password hashes", conf.dbmsHandler.getPasswordHashes(), "password hash", CONTENT_TYPE.PASSWORDS) diff --git a/lib/core/dicts.py b/lib/core/dicts.py index aa191a20fed..b699a52d1ec 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -428,6 +428,7 @@ "search": CONTENT_TYPE.SEARCH, "sqlQuery": CONTENT_TYPE.SQL_QUERY, "getStatements": CONTENT_TYPE.STATEMENTS, + "getProcs": CONTENT_TYPE.PROCEDURES, "tableExists": CONTENT_TYPE.COMMON_TABLES, "columnExists": CONTENT_TYPE.COMMON_COLUMNS, "readFile": CONTENT_TYPE.FILE_READ, diff --git a/lib/core/dump.py b/lib/core/dump.py index f62bae82376..37264e93ec2 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -216,6 +216,9 @@ def users(self, users): def statements(self, statements): self.lister("SQL statements", statements, content_type=CONTENT_TYPE.STATEMENTS) + def procedures(self, procedures): + self.lister("stored procedures", procedures, content_type=CONTENT_TYPE.PROCEDURES) + def userSettings(self, header, userSettings, subHeader, content_type=None): self._areAdmins = set() diff --git a/lib/core/enums.py b/lib/core/enums.py index b96312b9a23..479b9f6826b 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -409,6 +409,7 @@ class CONTENT_TYPE(object): OS_CMD = 24 REG_READ = 25 STATEMENTS = 26 + PROCEDURES = 27 class CONTENT_STATUS(object): IN_PROGRESS = 0 diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 98e33e047da..1a7d34b0129 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -153,6 +153,7 @@ "search": "boolean", "getComments": "boolean", "getStatements": "boolean", + "getProcs": "boolean", "db": "string", "tbl": "string", "col": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index 6c4ab289517..8a8e81bfe6a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.6.155" +VERSION = "1.10.6.156" 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 84d54301401..e0b2b5793bf 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -511,6 +511,9 @@ def cmdLineParser(argv=None): enumeration.add_argument("--statements", dest="getStatements", action="store_true", help="Retrieve SQL statements being run on DBMS") + enumeration.add_argument("--procs", dest="getProcs", action="store_true", + help="Retrieve stored procedures/functions and their source") + enumeration.add_argument("-D", dest="db", help="DBMS database to enumerate") diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index d3eef7ea37f..20d0941bd2d 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -70,6 +70,7 @@ def __init__(self): kb.data.cachedCounts = {} kb.data.dumpedTable = {} kb.data.cachedStatements = [] + kb.data.cachedProcedures = [] def getCurrentDb(self): infoMsg = "fetching current database" @@ -1127,3 +1128,62 @@ def getStatements(self): kb.data.cachedStatements = [_.replace(REFLECTED_VALUE_MARKER, "") for _ in kb.data.cachedStatements] return kb.data.cachedStatements + + def getProcedures(self): + infoMsg = "fetching stored procedures" + logger.info(infoMsg) + + rootQuery = queries[Backend.getIdentifiedDbms()].procedures + + # Generic-first by design: a DBMS is supported iff it declares a query block in + # queries.xml (INFORMATION_SCHEMA.ROUTINES / pg_proc / sys.sql_modules / ALL_SOURCE / RDB$PROCEDURES). + # Engines without stored procedures (or without a declared block) fall through with a clean + # warning - same model as getStatements() (uneven coverage is the established convention). + if "inband" not in rootQuery and "blind" not in rootQuery: + warnMsg = "on %s it is not possible to enumerate the stored procedures" % Backend.getIdentifiedDbms() + logger.warning(warnMsg) + return kb.data.cachedProcedures + + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: + query = rootQuery.inband.query + + values = inject.getValue(query, blind=False, time=False) + + if not isNoneValue(values): + kb.data.cachedProcedures = [] + for value in arrayizeValue(values): + value = (unArrayizeValue(value) or "").strip() + if not isNoneValue(value): + kb.data.cachedProcedures.append(value.strip()) + + if not kb.data.cachedProcedures and isInferenceAvailable() and not conf.direct: + infoMsg = "fetching number of stored procedures" + logger.info(infoMsg) + + count = inject.getValue(rootQuery.blind.count, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if count == 0: + return kb.data.cachedProcedures + elif not isNumPosStrValue(count): + errMsg = "unable to retrieve the number of stored procedures" + raise SqlmapNoneDataException(errMsg) + + # every blind query uses 0-based paging (MySQL "LIMIT %d,1", PostgreSQL/MSSQL/Oracle + # "OFFSET %d"), so the index range stays 0-based here regardless of PLUS_ONE_DBMSES (unlike + # getStatements(), whose MSSQL/Oracle idioms are 1-based) + indexRange = getLimitRange(count) + + for index in indexRange: + query = rootQuery.blind.query % index + value = unArrayizeValue(inject.getValue(query, union=False, error=False)) + + if not isNoneValue(value): + kb.data.cachedProcedures.append((value or "").strip()) + + if not kb.data.cachedProcedures: + errMsg = "unable to retrieve the stored procedures" + logger.error(errMsg) + else: + kb.data.cachedProcedures = [_.replace(REFLECTED_VALUE_MARKER, "") for _ in kb.data.cachedProcedures] + + return kb.data.cachedProcedures From cef105f3225f47867761a98d010c2d846f520775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 24 Jun 2026 01:59:02 +0200 Subject: [PATCH 5/5] Minor bug fixes --- data/txt/sha256sums.txt | 12 ++++++------ data/xml/queries.xml | 1 + lib/core/agent.py | 6 +++--- lib/core/common.py | 9 ++++++++- lib/core/settings.py | 2 +- lib/request/connect.py | 2 +- tests/test_dns_engine.py | 12 ++++++++++++ 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 89807f23e72..772e791c8b5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -84,7 +84,7 @@ c8d467837c8567b61a11e2dfd75a2d8305a8b317041ee81eda6d0e47609dabb7 data/xml/paylo 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 379fc92f2dadd948f401e17490d8a8f03a1988d817323cbe1feff5fe87726079 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -a6127cc68b62709149a0e58a314d9003865945018cc5a43d60afc3698d92c6e9 data/xml/queries.xml +6eca98949c361bbcf5edd5e24dcf001dbaee5b37b244978df7e319cf48dac514 data/xml/queries.xml 127799739f9aeabca367027197f3c0240f141303bd7499928ccfa1443bf148c7 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md @@ -166,9 +166,9 @@ a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller 96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -1276ff64ad145157d8c65ce08f3066b6db041d12f7d1eee590c06123c700b18d lib/core/agent.py +9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -5a8dcfc6c43927e4a132d34abf5d75193eaeb3feb0cb58d0ff5bdc059c876ba9 lib/core/common.py +122767794156afa41b19baa706ad4c124eef6eaf73ed8fd208d8f634e97e82eb lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -d6572ecbd0d7a26839f5098d68cb02fb5b498c88f0d1c36928c5611a96f9d19a lib/core/settings.py +c7a6dd94cf738716cc48f1daacdd402ddb0e78a6c9260233e319cde4f9054a60 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -212,7 +212,7 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site 369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py d4bb0869b03602a0c8f9e0e0fd217753f14ddadf848fc9f3c65a74d03feb9958 lib/request/comparison.py -b9e2db44d265909792f6cc821ff910727b14aa2d5063c74b0f2ea6d40c4f3d9d lib/request/connect.py +729e07a2ca6b1d83563e9c6dc5a884d1b664c1764be06776ea93bde305164f0c lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py @@ -587,7 +587,7 @@ c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_dat b6d8a4bc9c46a332a2dc7b3cf862ea67e38b5c5701cfd8eb3556021f6b611416 tests/test_dialectdbms.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py -7f12466974394312dad3d98651ef8a50d1585bee0f8cd25da0b77b08c2047e46 tests/test_dns_engine.py +ed5a0e453b811dc3dcc5ca28e14a9d7552aacaa7e316e1bca1b042dc5939e204 tests/test_dns_engine.py 703faac01f38224ba85bd0fc398d939ea034f1d7fd641cdc15da4f77ec049443 tests/test_dns_server.py 9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 82674355375..9cfbce4e810 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -203,6 +203,7 @@ + diff --git a/lib/core/agent.py b/lib/core/agent.py index 686eb43bb54..ec781a43e58 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -70,9 +70,9 @@ def payloadDirect(self, query): query = self.cleanupPayload(query) if query.upper().startswith("AND "): - query = re.sub(r"(?i)AND ", "SELECT ", query, 1) + query = re.sub(r"(?i)AND ", "SELECT ", query, count=1) elif query.upper().startswith(" UNION ALL "): - query = re.sub(r"(?i) UNION ALL ", "", query, 1) + query = re.sub(r"(?i) UNION ALL ", "", query, count=1) elif query.startswith("; "): query = query.replace("; ", "", 1) @@ -1126,7 +1126,7 @@ def limitQuery(self, num, query, field=None, uniqueField=None): original = query.split("SELECT ", 1)[1].split(" FROM", 1)[0] for part in original.split(','): if re.search(r"\b%s\b" % re.escape(field), part): - _ = re.sub(r"SELECT.+?FROM", "SELECT %s AS z,row_number() over() AS y FROM" % part, query, 1) + _ = re.sub(r"SELECT.+?FROM", "SELECT %s AS z,row_number() over() AS y FROM" % part, query, count=1) replacement = "SELECT x.z FROM (%s)x WHERE x.y-1=%d" % (_, num) limitedQuery = replacement break diff --git a/lib/core/common.py b/lib/core/common.py index 5b04c9589f0..a8eca14ad4d 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2271,7 +2271,7 @@ def safeStringFormat(format_, params): if match: try: _ = getUnicode(params[count % len(params)]) - retVal = re.sub(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>" % _.replace('\\', r'\\'), retVal, 1) + retVal = re.sub(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>" % _.replace('\\', r'\\'), retVal, count=1) except re.error: retVal = retVal.replace(match.group(0), match.group(0) % params[count % len(params)], 1) count += 1 @@ -3884,6 +3884,13 @@ def openFile(filename, mode='r', encoding=UNICODE_ENCODING, errors="reversible", if 'b' in mode: buffering = 0 encoding = None + elif buffering == 1 and codecs_open is codecs.open: + # codecs.open() always opens the underlying file in binary mode, where line buffering + # (buffering=1) is unsupported: on Python 3.12+ it emits a benign RuntimeWarning and is + # silently downgraded to the default buffer size anyway. Request that default explicitly + # so the warning never reaches users (the >=3.14 _codecs_open shim handles buffering=1 + # itself, preserving flush-on-newline, so this only adjusts the legacy codecs.open path). + buffering = -1 if filename == STDIN_PIPE_DASH: if filename not in kb.cache.content: diff --git a/lib/core/settings.py b/lib/core/settings.py index 8a8e81bfe6a..c25f9e0f6a3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.6.156" +VERSION = "1.10.6.157" 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/request/connect.py b/lib/request/connect.py index daa2a66ee9d..40c42390bfb 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1018,7 +1018,7 @@ def _read(count=None): if conn and getattr(conn, "redurl", None): _ = _urllib.parse.urlsplit(conn.redurl) _ = ("%s%s" % (_.path or "/", ("?%s" % _.query) if _.query else "")) - requestMsg = re.sub(r"(\n[A-Z]+ ).+?( HTTP/\d)", r"\g<1>%s\g<2>" % getUnicode(_).replace("\\", "\\\\"), requestMsg, 1) + requestMsg = re.sub(r"(\n[A-Z]+ ).+?( HTTP/\d)", r"\g<1>%s\g<2>" % getUnicode(_).replace("\\", "\\\\"), requestMsg, count=1) if kb.resendPostOnRedirect is False: requestMsg = re.sub(r"(\[#\d+\]:\n)POST ", r"\g<1>GET ", requestMsg) diff --git a/tests/test_dns_engine.py b/tests/test_dns_engine.py index bce8bff6a81..5eaf2c0a7cf 100644 --- a/tests/test_dns_engine.py +++ b/tests/test_dns_engine.py @@ -43,6 +43,7 @@ from lib.core.agent import agent from lib.core.common import Backend from lib.core.data import conf, kb +from lib.core.threads import getCurrentThreadData from lib.core.enums import DBMS from lib.core.exception import SqlmapNotVulnerableException from lib.core.settings import DNS_BOUNDARIES_ALPHABET @@ -89,7 +90,12 @@ class _DnsCase(unittest.TestCase): def setUpClass(cls): cls.server = _HighPortDNSServer() cls.server.run() + # bounded wait: never spin indefinitely if the in-process server fails to bind/init + # (e.g. a taken port on CI) - fail loudly instead of hanging the whole suite + deadline = time.time() + 10 while not cls.server._initialized: + if time.time() > deadline: + raise RuntimeError("in-process DNS test server failed to initialize within 10s") time.sleep(0.02) @classmethod @@ -107,6 +113,11 @@ def setUp(self): self._saved_randomInt = dnstestmod.randomInt self._saved_dnsServer = conf.get("dnsServer") self._saved_hdbR, self._saved_hdbW = dnsmod.hashDBRetrieve, dnsmod.hashDBWrite + # the DNS exfil path prints its own "[INFO] retrieved: ..." progress straight to stdout + # via dataToStdout() (it bypasses the logger, so the suite's log-level silencing can't + # catch it); suppress it through sqlmap's own per-thread stdout gate so the run stays clean + self._saved_disableStdOut = getCurrentThreadData().disableStdOut + getCurrentThreadData().disableStdOut = True for k, v in _CONF.items(): conf[k] = v for k, v in _KB.items(): @@ -125,6 +136,7 @@ def setUp(self): set_dbms(self.DBMS_NAME) def tearDown(self): + getCurrentThreadData().disableStdOut = self._saved_disableStdOut for k, v in self._saved_conf.items(): conf[k] = v for k, v in self._saved_kb.items():