diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt
index 7576be296de..772e791c8b5 100644
--- a/data/txt/sha256sums.txt
+++ b/data/txt/sha256sums.txt
@@ -77,14 +77,14 @@ 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
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
+6eca98949c361bbcf5edd5e24dcf001dbaee5b37b244978df7e319cf48dac514 data/xml/queries.xml
127799739f9aeabca367027197f3c0240f141303bd7499928ccfa1443bf148c7 doc/ARCHITECTURE.md
0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS
ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md
@@ -161,27 +161,27 @@ 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
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
6c8d40d6bbab4a60d09eb03324a3352d85df1a741c62044e73701e92172d1d38 lib/core/datatype.py
f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py
147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py
-7ce2c09ebcd63d57f7b6751f70f536e2a562230d51181eb24f5024bb6f3d74cc 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
-90a49806b83a83f6402b3dd6e35f7f2468d3dbcc0cafc3c382bda6e248344609 lib/core/settings.py
+c7a6dd94cf738716cc48f1daacdd402ddb0e78a6c9260233e319cde4f9054a60 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
@@ -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
@@ -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
@@ -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
@@ -584,10 +584,10 @@ 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
+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/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/data/xml/queries.xml b/data/xml/queries.xml
index 64e8823cc54..9cfbce4e810 100644
--- a/data/xml/queries.xml
+++ b/data/xml/queries.xml
@@ -47,6 +47,10 @@
+
+
+
+
@@ -123,6 +127,10 @@
+
+
+
+
@@ -195,6 +203,11 @@
+
+
+
+
+
@@ -290,6 +303,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/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/dicts.py b/lib/core/dicts.py
index 6d3864328a6..b699a52d1ec 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)",
@@ -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 b00474f0cef..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.152"
+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)
@@ -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/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/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/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/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
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):
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():